github.com/hyperledger/aries-framework-go@v0.3.2/pkg/client/issuecredential/rfc0593/event.go (about)

     1  /*
     2  Copyright SecureKey Technologies Inc. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package rfc0593
     8  
     9  import (
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"reflect"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/google/uuid"
    18  
    19  	"github.com/hyperledger/aries-framework-go/pkg/crypto"
    20  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/common/service"
    21  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/decorator"
    22  	"github.com/hyperledger/aries-framework-go/pkg/didcomm/protocol/issuecredential"
    23  	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/jsonld"
    24  	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/signer"
    25  	"github.com/hyperledger/aries-framework-go/pkg/doc/signature/suite"
    26  	"github.com/hyperledger/aries-framework-go/pkg/doc/verifiable"
    27  	"github.com/hyperledger/aries-framework-go/pkg/kms"
    28  	"github.com/hyperledger/aries-framework-go/pkg/vdr/fingerprint"
    29  	"github.com/hyperledger/aries-framework-go/spi/storage"
    30  )
    31  
    32  const (
    33  	// ProofVCDetailFormat is the attachment format used in the proposal, offer, and request message attachments.
    34  	ProofVCDetailFormat = "aries/ld-proof-vc-detail@v1.0"
    35  	// ProofVCFormat is the attachment format used in the issue-credential message attachment.
    36  	ProofVCFormat = "aries/ld-proof-vc@v1.0"
    37  	// StoreName is the name of the transient store used by AutoExecute.
    38  	StoreName       = "RFC0593TransientStore"
    39  	mediaTypeJSON   = "application/json"
    40  	mediaTypeJSONLD = "application/ld+json"
    41  )
    42  
    43  // ErrRFC0593NotApplicable indicates RFC0593 does not apply to the message being handled because
    44  // it does not contain an attachment with the proof format identifiers.
    45  //
    46  // See also: ProofVCDetailFormat, ProofVCFormat.
    47  var ErrRFC0593NotApplicable = errors.New("RFC0593 is not applicable")
    48  
    49  // CredentialSpec is the attachment payload in messages conforming to the RFC0593 format.
    50  type CredentialSpec struct {
    51  	Template json.RawMessage        `json:"credential"`
    52  	Options  *CredentialSpecOptions `json:"options"`
    53  }
    54  
    55  // CredentialSpecOptions are the options for issuance of the credential.
    56  // TODO support CredentialStatus.
    57  type CredentialSpecOptions struct {
    58  	ProofPurpose string            `json:"proofPurpose"`
    59  	Created      string            `json:"created"`
    60  	Domain       string            `json:"domain"`
    61  	Challenge    string            `json:"challenge"`
    62  	Status       *CredentialStatus `json:"credentialStatus"`
    63  	ProofType    string            `json:"proofType"`
    64  }
    65  
    66  // CredentialStatus is the requested status for the credential.
    67  type CredentialStatus struct {
    68  	Type string `json:"type"`
    69  }
    70  
    71  // AutoExecute will automatically execute the issue-credential V2 protocol using ReplayProposal, ReplayOffer, and
    72  // IssueCredential by handling the associated actions if they contain RFC0593 attachments.
    73  // Other actions are passed through to 'next'.
    74  //
    75  // Usage:
    76  //     client := issuecredential.Client = ...
    77  //     events = make(chan service.DIDCommAction)
    78  //     err := client.RegisterActionEvent(events)
    79  //     if err != nil {
    80  //         panic(err)
    81  //     }
    82  //     var p Provider = ...
    83  //     next := make(chan service.DIDCommAction)
    84  //     go AutoExecute(p, next)(events)
    85  //     for event := range next {
    86  //         // handle events from issue-credential that do not conform to RFC0593
    87  //     }
    88  //
    89  // Note: use the protocol Middleware if the protocol needs to be started with a request-credential message.
    90  //
    91  // See also: service.AutoExecuteActionEvent.
    92  func AutoExecute(p Provider, next chan service.DIDCommAction) func(chan service.DIDCommAction) { // nolint:funlen
    93  	return func(events chan service.DIDCommAction) {
    94  		// TODO make AutoExecute return an error if the store cannot be opened?
    95  		db, storeErr := p.ProtocolStateStorageProvider().OpenStore(StoreName)
    96  
    97  		for event := range events {
    98  			if storeErr != nil {
    99  				event.Stop(fmt.Errorf("rfc0593: failed to open transient store: %w", storeErr))
   100  
   101  				continue
   102  			}
   103  
   104  			var (
   105  				arg     interface{}
   106  				options *CredentialSpecOptions
   107  				err     error
   108  			)
   109  
   110  			switch event.Message.Type() {
   111  			case issuecredential.ProposeCredentialMsgTypeV2:
   112  				arg, options, err = ReplayProposal(p, event.Message)
   113  				err = saveOptionsIfNoError(err, db, event.Message, options)
   114  			case issuecredential.OfferCredentialMsgTypeV2:
   115  				arg, options, err = ReplayOffer(p, event.Message)
   116  				err = saveOptionsIfNoError(err, db, event.Message, options)
   117  			case issuecredential.RequestCredentialMsgTypeV2:
   118  				arg, options, err = IssueCredential(p, event.Message)
   119  				err = saveOptionsIfNoError(err, db, event.Message, options)
   120  			case issuecredential.IssueCredentialMsgTypeV2:
   121  				// TODO credential issued to us. We have middleware that automatically saves the credentials.
   122  				//  Should this package ensure it's saved?
   123  				//  Should we ensure issued VC is up to spec?
   124  				options, err = fetchCredentialSpecOptions(db, event.Message)
   125  				if err != nil {
   126  					err = fmt.Errorf("failed to fetch credential spec options to validate credential: %w", err)
   127  
   128  					break
   129  				}
   130  
   131  				arg, err = VerifyCredential(p, options, uuid.New().String(), event.Message)
   132  				err = deleteOptionsIfNoError(err, db, event.Message)
   133  			default:
   134  				next <- event
   135  
   136  				continue
   137  			}
   138  
   139  			if errors.Is(err, ErrRFC0593NotApplicable) {
   140  				next <- event
   141  
   142  				continue
   143  			}
   144  
   145  			if err != nil {
   146  				event.Stop(fmt.Errorf("rfc0593: %w", err))
   147  
   148  				continue
   149  			}
   150  
   151  			event.Continue(arg)
   152  		}
   153  	}
   154  }
   155  
   156  // ReplayProposal replays the inbound proposed CredentialSpec as an outbound offer that can be sent back to the
   157  // original sender.
   158  //
   159  // Usage:
   160  //     var p JSONLDDocumentLoaderProvider = ...
   161  //     client := issuecredential.Client = ...
   162  //     var events chan service.DIDCommAction = ...
   163  //     err := client.RegisterActionEvent(events)
   164  //     if err != nil {
   165  //         panic(err)
   166  //     }
   167  //     for event := range events {
   168  //         if event.Message.Type() == issuecredential.ProposeCredentialMsgType {
   169  //             arg, options, err := ReplayProposal(p, event.Message)
   170  //             if errors.Is(err, ErrRFC0593NotApplicable) {
   171  //                 // inspect and handle the event yourself
   172  //                 arg, err = handleEvent(event)
   173  //             }
   174  //
   175  //             if err != nil {
   176  //                 event.Stop(err)
   177  //             }
   178  //
   179  //             // inspect options
   180  //
   181  //             event.Continue(arg)
   182  //         }
   183  //     }
   184  func ReplayProposal(p JSONLDDocumentLoaderProvider,
   185  	msg service.DIDCommMsg) (interface{}, *CredentialSpecOptions, error) {
   186  	proposal := &issuecredential.ProposeCredentialV2{}
   187  
   188  	err := msg.Decode(proposal)
   189  	if err != nil {
   190  		return nil, nil, fmt.Errorf("failed to decode msg type %s: %w", msg.Type(), err)
   191  	}
   192  
   193  	payload, err := GetCredentialSpec(p, proposal.Formats, proposal.FiltersAttach)
   194  	if err != nil {
   195  		return nil, nil, fmt.Errorf("failed to extract payload for msg type %s: %w", msg.Type(), err)
   196  	}
   197  
   198  	attachID := uuid.New().String()
   199  
   200  	return issuecredential.WithOfferCredentialV2(&issuecredential.OfferCredentialV2{
   201  		Type:    issuecredential.OfferCredentialMsgTypeV2,
   202  		Comment: fmt.Sprintf("response to msg id: %s", msg.ID()),
   203  		Formats: []issuecredential.Format{{
   204  			AttachID: attachID,
   205  			Format:   ProofVCDetailFormat,
   206  		}},
   207  		OffersAttach: []decorator.Attachment{{
   208  			ID:       attachID,
   209  			MimeType: mediaTypeJSON,
   210  			Data: decorator.AttachmentData{
   211  				JSON: payload,
   212  			},
   213  		}},
   214  	}), payload.Options, nil
   215  }
   216  
   217  // ReplayOffer replays the inbound offered CredentialSpec as an outbound request that can be sent back to the
   218  // original sender.
   219  //
   220  // Usage:
   221  //     var p JSONLDDocumentLoaderProvider = ...
   222  //     client := issuecredential.Client = ...
   223  //     var events chan service.DIDCommAction = ...
   224  //     err := client.RegisterActionEvent(events)
   225  //     if err != nil {
   226  //         panic(err)
   227  //     }
   228  //     for event := range events {
   229  //         if event.Message.Type() == issuecredential.OfferCredentialMsgType {
   230  //             arg, options, err := ReplayOffer(p, event.Message)
   231  //             if errors.Is(err, ErrRFC0593NotApplicable) {
   232  //                 // inspect and handle the event yourself
   233  //                 arg, err = handleEvent(event)
   234  //             }
   235  //
   236  //             if err != nil {
   237  //                 event.Stop(err)
   238  //             }
   239  //
   240  //             // inspect options
   241  //
   242  //             event.Continue(arg)
   243  //         }
   244  //     }
   245  func ReplayOffer(p JSONLDDocumentLoaderProvider, msg service.DIDCommMsg) (interface{}, *CredentialSpecOptions, error) {
   246  	offer := &issuecredential.OfferCredentialV2{}
   247  
   248  	err := msg.Decode(offer)
   249  	if err != nil {
   250  		return nil, nil, fmt.Errorf("failed to decode msg type %s: %w", msg.Type(), err)
   251  	}
   252  
   253  	payload, err := GetCredentialSpec(p, offer.Formats, offer.OffersAttach)
   254  	if err != nil {
   255  		return nil, nil, fmt.Errorf("failed to extract payoad for msg type %s: %w", msg.Type(), err)
   256  	}
   257  
   258  	attachID := uuid.New().String()
   259  
   260  	return issuecredential.WithRequestCredentialV2(&issuecredential.RequestCredentialV2{
   261  		Type:    issuecredential.RequestCredentialMsgTypeV2,
   262  		Comment: fmt.Sprintf("response to msg id: %s", msg.ID()),
   263  		Formats: []issuecredential.Format{{
   264  			AttachID: attachID,
   265  			Format:   ProofVCDetailFormat,
   266  		}},
   267  		RequestsAttach: []decorator.Attachment{{
   268  			ID:       attachID,
   269  			MimeType: mediaTypeJSON,
   270  			Data: decorator.AttachmentData{
   271  				JSON: payload,
   272  			},
   273  		}},
   274  	}), payload.Options, nil
   275  }
   276  
   277  // IssueCredential attaches an LD proof to the template VC in the inbound request message and attaches the
   278  // verifiable credential to an outbound issue-credential message.
   279  //
   280  // Usage:
   281  //     var p Provider = ...
   282  //     client := issuecredential.Client = ...
   283  //     var events chan service.DIDCommAction = ...
   284  //     err := client.RegisterActionEvent(events)
   285  //     if err != nil {
   286  //         panic(err)
   287  //     }
   288  //     for event := range events {
   289  //         if event.Message.Type() == issuecredential.RequestCredentialMsgType {
   290  //             arg, options, err := IssueCredential(p, event.Message)
   291  //             if errors.Is(err, ErrRFC0593NotApplicable) {
   292  //                 // inspect and handle the event yourself
   293  //                 arg, err = handleEvent(event)
   294  //             }
   295  //
   296  //             if err != nil {
   297  //                 event.Stop(err)
   298  //             }
   299  //
   300  //             // inspect options
   301  //
   302  //             event.Continue(arg)
   303  //         }
   304  //     }
   305  func IssueCredential(p Provider, msg service.DIDCommMsg) (interface{}, *CredentialSpecOptions, error) {
   306  	request := &issuecredential.RequestCredentialV2{}
   307  
   308  	err := msg.Decode(request)
   309  	if err != nil {
   310  		return nil, nil, fmt.Errorf("failed to decode msg type %s: %w", msg.Type(), err)
   311  	}
   312  
   313  	payload, err := GetCredentialSpec(p, request.Formats, request.RequestsAttach)
   314  	if err != nil {
   315  		return nil, nil, fmt.Errorf("failed to get payload for msg type %s: %w", msg.Type(), err)
   316  	}
   317  
   318  	ic, err := CreateIssueCredentialMsg(p, payload)
   319  	if err != nil {
   320  		return nil, nil, fmt.Errorf("failed to create issue-credential msg: %w", err)
   321  	}
   322  
   323  	ic.Comment = fmt.Sprintf("response to request with id %s", msg.ID())
   324  
   325  	return issuecredential.WithIssueCredentialV2(ic), payload.Options, nil
   326  }
   327  
   328  // CreateIssueCredentialMsg creates an issue-credential message using the credential spec.
   329  func CreateIssueCredentialMsg(p Provider, spec *CredentialSpec) (*issuecredential.IssueCredentialV2, error) {
   330  	vc, err := verifiable.ParseCredential(
   331  		spec.Template,
   332  		verifiable.WithDisabledProofCheck(), // no proof is expected in this credential
   333  		verifiable.WithJSONLDDocumentLoader(p.JSONLDDocumentLoader()),
   334  	)
   335  	if err != nil {
   336  		return nil, fmt.Errorf("failed to parse vc: %w", err)
   337  	}
   338  
   339  	ctx, err := ldProofContext(p, spec.Options)
   340  	if err != nil {
   341  		return nil, fmt.Errorf("failed to determine the LD context required to add a proof: %w", err)
   342  	}
   343  
   344  	err = vc.AddLinkedDataProof(ctx, jsonld.WithDocumentLoader(p.JSONLDDocumentLoader()))
   345  	if err != nil {
   346  		return nil, fmt.Errorf("failed to add LD proof: %w", err)
   347  	}
   348  
   349  	attachID := uuid.New().String()
   350  
   351  	return &issuecredential.IssueCredentialV2{
   352  		Type: issuecredential.IssueCredentialMsgTypeV2,
   353  		Formats: []issuecredential.Format{{
   354  			AttachID: attachID,
   355  			Format:   ProofVCFormat,
   356  		}},
   357  		CredentialsAttach: []decorator.Attachment{{
   358  			ID:       attachID,
   359  			MimeType: mediaTypeJSONLD,
   360  			Data: decorator.AttachmentData{
   361  				JSON: vc,
   362  			},
   363  		}},
   364  	}, nil
   365  }
   366  
   367  // VerifyCredential verifies the credential received in an RFC0593 issue-credential message.
   368  //
   369  // The credential is validated to ensure it complies with the given CredentialSpecOptions.
   370  //
   371  // The credential will then be saved with the given name.
   372  //
   373  // Usage:
   374  //     var p Provider = ...
   375  //     client := issuecredential.Client = ...
   376  //     var events chan service.DIDCommAction = ...
   377  //     err := client.RegisterActionEvent(events)
   378  //     if err != nil {
   379  //         panic(err)
   380  //     }
   381  //     var options *CredentialSpecOptions
   382  //     for event := range events {
   383  //         switch event.Message.Type() {
   384  //         case issuecredential.OfferCredentialMsgType:
   385  //             arg, opts, err := ReplayOffer(p, event.Message)
   386  //             if err != nil {
   387  //                 event.Stop(err)
   388  //             }
   389  //
   390  //             options = opts
   391  //             event.Continue(arg)
   392  //         case issuecredential.IssueCredentialMsgType:
   393  //             arg, err := VerifyCredential(p, options, "my_vc", event.Message)
   394  //             if errors.Is(err, ErrRFC0593NotApplicable) {
   395  //                 // inspect and handle the event yourself
   396  //                 arg, err = handleEvent(event)
   397  //             }
   398  //
   399  //             if err != nil {
   400  //                 event.Stop(err)
   401  //             }
   402  //
   403  //             event.Continue(arg)
   404  //         }
   405  //     }
   406  func VerifyCredential(p Provider,
   407  	options *CredentialSpecOptions, name string, msg service.DIDCommMsg) (interface{}, error) {
   408  	issueCredential := &issuecredential.IssueCredentialV2{}
   409  
   410  	err := msg.Decode(issueCredential)
   411  	if err != nil {
   412  		return nil, fmt.Errorf("failed to decode msg type %s: %w", msg.Type(), err)
   413  	}
   414  
   415  	attachment, err := FindAttachment(ProofVCFormat, issueCredential.Formats, issueCredential.CredentialsAttach)
   416  	if err != nil {
   417  		return nil, fmt.Errorf("failed to fetch attachment with format %s: %w", ProofVCFormat, err)
   418  	}
   419  
   420  	raw, err := attachment.Data.Fetch()
   421  	if err != nil {
   422  		return nil, fmt.Errorf("failed to fetch the attachment's contents: %w", err)
   423  	}
   424  
   425  	vc, err := verifiable.ParseCredential(
   426  		raw,
   427  		verifiable.WithJSONLDDocumentLoader(p.JSONLDDocumentLoader()),
   428  		verifiable.WithPublicKeyFetcher(verifiable.NewVDRKeyResolver(p.VDRegistry()).PublicKeyFetcher()),
   429  	)
   430  	if err != nil {
   431  		return nil, fmt.Errorf("failed to parse vc: %w", err)
   432  	}
   433  
   434  	err = validateCredentialRequestVC(vc)
   435  	if err != nil {
   436  		return nil, fmt.Errorf("invalid credential: %w", err)
   437  	}
   438  
   439  	err = ValidateVCMatchesSpecOptions(vc, options)
   440  	if err != nil {
   441  		return nil, fmt.Errorf("invalid credential: %w", err)
   442  	}
   443  
   444  	return issuecredential.WithFriendlyNames(name), nil
   445  }
   446  
   447  func ldProofContext(p Provider, options *CredentialSpecOptions) (*verifiable.LinkedDataProofContext, error) {
   448  	now := time.Now()
   449  
   450  	ctx := &verifiable.LinkedDataProofContext{
   451  		SignatureType: options.ProofType,
   452  		Purpose:       "assertionMethod",
   453  		Created:       &now,
   454  		Challenge:     options.Challenge,
   455  		Domain:        options.Domain,
   456  	}
   457  
   458  	ss, spec, verMethod, err := signatureSuite(p, options.ProofType)
   459  	if err != nil {
   460  		return nil, fmt.Errorf("failed to init a signature suite: %w", err)
   461  	}
   462  
   463  	ctx.Suite = ss
   464  	ctx.VerificationMethod = verMethod
   465  	ctx.SignatureRepresentation = spec.SignatureRepresentation // TODO RFC does not specify representation
   466  
   467  	if options.ProofPurpose != "" {
   468  		ctx.Purpose = options.ProofPurpose
   469  	}
   470  
   471  	if options.Created != "" {
   472  		// TODO spec does not specify format for `created`
   473  		created, err := time.Parse(time.RFC3339, options.Created)
   474  		if err != nil {
   475  			return nil, fmt.Errorf("failed to parse `created`: %w", err)
   476  		}
   477  
   478  		ctx.Created = &created
   479  	}
   480  
   481  	return ctx, nil
   482  }
   483  
   484  func signatureSuite(p Provider, proofType string) (signer.SignatureSuite, *SignatureSuiteSpec, string, error) {
   485  	spec, supported := DefaultSignatureSuiteSpecs[proofType]
   486  	if !supported {
   487  		return nil, nil, "", fmt.Errorf("unsupported proof type: %s", proofType)
   488  	}
   489  
   490  	keyID, kh, err := p.KMS().Create(spec.KeyType)
   491  	if err != nil {
   492  		return nil, nil, "", fmt.Errorf("failed to create a new signing key: %w", err)
   493  	}
   494  
   495  	keyBytes, _, err := p.KMS().ExportPubKeyBytes(keyID)
   496  	if err != nil {
   497  		return nil, nil, "", fmt.Errorf("failed to export signing key bytes: %w", err)
   498  	}
   499  
   500  	_, verMethod := fingerprint.CreateDIDKeyByCode(spec.KeyMultiCodec, keyBytes)
   501  	suiteSigner := spec.Signer(p, kh)
   502  
   503  	return spec.Suite(suite.WithSigner(suiteSigner)), &spec, verMethod, nil
   504  }
   505  
   506  // GetCredentialSpec extracts the CredentialSpec from the formats and attachments.
   507  func GetCredentialSpec(p JSONLDDocumentLoaderProvider,
   508  	formats []issuecredential.Format, attachments []decorator.Attachment) (*CredentialSpec, error) {
   509  	attachment, err := FindAttachment(ProofVCDetailFormat, formats, attachments)
   510  	if err != nil {
   511  		return nil, fmt.Errorf("failed to find attachment of type %s: %w", ProofVCDetailFormat, err)
   512  	}
   513  
   514  	payload := &CredentialSpec{}
   515  
   516  	err = unmarshalAttachmentContents(attachment, payload)
   517  	if err != nil {
   518  		return nil, fmt.Errorf("failed to unmarshal attachment contents: %w", err)
   519  	}
   520  
   521  	err = validateCredentialRequestOptions(payload)
   522  	if err != nil {
   523  		return nil, fmt.Errorf("bad request: invalid options: %w", err)
   524  	}
   525  
   526  	vc, err := verifiable.ParseCredential(
   527  		payload.Template,
   528  		verifiable.WithDisabledProofCheck(), // no proof is expected in this credential
   529  		verifiable.WithJSONLDDocumentLoader(p.JSONLDDocumentLoader()),
   530  	)
   531  	if err != nil {
   532  		return nil, fmt.Errorf("bad request: unable to parse vc: %w", err)
   533  	}
   534  
   535  	err = validateCredentialRequestVC(vc)
   536  	if err != nil {
   537  		return nil, fmt.Errorf("bad request: invalid vc: %w", err)
   538  	}
   539  
   540  	return payload, nil
   541  }
   542  
   543  // FindAttachment returns the attachment corresponding to the RFC0593 format entry.
   544  func FindAttachment(formatType string,
   545  	formats []issuecredential.Format, attachments []decorator.Attachment) (*decorator.Attachment, error) {
   546  	// TODO not documented in the RFC but the intent of having `format` and `requests~attach` be an array
   547  	//  is not to enable "bulk issuance" (issuance of multiple vcs), but to requests a single credential
   548  	//  using different request formats.
   549  	// TODO clarify precedence of different enabled middlewares if request has multiple attachment formats
   550  	var attachID string
   551  
   552  	for i := range formats {
   553  		if formats[i].Format == formatType {
   554  			attachID = formats[i].AttachID
   555  			break
   556  		}
   557  	}
   558  
   559  	if attachID == "" {
   560  		return nil, ErrRFC0593NotApplicable
   561  	}
   562  
   563  	for i := range attachments {
   564  		if attachments[i].ID == attachID {
   565  			return &attachments[i], nil
   566  		}
   567  	}
   568  
   569  	return nil, fmt.Errorf(
   570  		"format with attachID=%s indicates support for %s for no attachment with that ID was found",
   571  		attachID, formatType,
   572  	)
   573  }
   574  
   575  func unmarshalAttachmentContents(a *decorator.Attachment, v interface{}) error {
   576  	contents, err := a.Data.Fetch()
   577  	if err != nil {
   578  		return fmt.Errorf("failed to fetch attachment contents: %w", err)
   579  	}
   580  
   581  	return json.Unmarshal(contents, v)
   582  }
   583  
   584  // TODO this should be configurable.
   585  func validateCredentialRequestOptions(_ *CredentialSpec) error {
   586  	// TODO validatations (eg. valid proofPurpose, created, credentialStatus, proofType)
   587  	return nil
   588  }
   589  
   590  // TODO this should be configurable.
   591  func validateCredentialRequestVC(_ *verifiable.Credential) error {
   592  	// TODO validate claims in credential template
   593  	return nil
   594  }
   595  
   596  // ValidateVCMatchesSpecOptions ensures the vc matches the spec.
   597  func ValidateVCMatchesSpecOptions(vc *verifiable.Credential, options *CredentialSpecOptions) error { // nolint:gocyclo
   598  	if len(vc.Proofs) == 0 {
   599  		return errors.New("vc is missing a proof")
   600  	}
   601  
   602  	// TODO which proof?
   603  	proof := vc.Proofs[0]
   604  
   605  	if !reflect.DeepEqual(options.ProofType, proof["type"]) {
   606  		return fmt.Errorf("expected proofType %s but got %s", options.ProofType, proof["type"])
   607  	}
   608  
   609  	if !reflect.DeepEqual(options.Domain, proof["domain"]) {
   610  		return fmt.Errorf("expected domain %s but got %s", options.Domain, proof["domain"])
   611  	}
   612  
   613  	if !reflect.DeepEqual(options.Challenge, proof["challenge"]) {
   614  		return fmt.Errorf("expected challenge %s but got %s", options.Challenge, proof["challenge"])
   615  	}
   616  
   617  	if options.ProofPurpose != "" && !reflect.DeepEqual(options.ProofPurpose, proof["proofPurpose"]) {
   618  		return fmt.Errorf("expected proofPurpose %s but got %s", options.ProofPurpose, proof["proofPurpose"])
   619  	}
   620  
   621  	if options.Status != nil {
   622  		if vc.Status == nil {
   623  			return fmt.Errorf("expected credentialStatus of type %s but VC does not have any", options.Status.Type)
   624  		}
   625  
   626  		if options.Status.Type != vc.Status.Type {
   627  			return fmt.Errorf("expected credentialStatus of type %s but got %s", options.Status.Type, vc.Status.Type)
   628  		}
   629  	}
   630  
   631  	if options.Created == "" {
   632  		return fmt.Errorf("missing 'created' on proof") // RFC: default current system time it unspecified in options
   633  	}
   634  
   635  	if options.Created != "" && !reflect.DeepEqual(options.Created, proof["created"]) {
   636  		return fmt.Errorf("expected proof.created %s but got %s", options.Created, proof["created"])
   637  	}
   638  
   639  	return nil
   640  }
   641  
   642  func saveOptionsIfNoError(err error, s storage.Store, msg service.DIDCommMsg, options *CredentialSpecOptions) error {
   643  	if err != nil {
   644  		return err
   645  	}
   646  
   647  	thid, err := msg.ThreadID()
   648  	if err != nil {
   649  		return fmt.Errorf("failed to get message's threadID: %w", err)
   650  	}
   651  
   652  	raw, err := json.Marshal(options)
   653  	if err != nil {
   654  		return fmt.Errorf("failed to marshal options: %w", err)
   655  	}
   656  
   657  	return s.Put(thid, raw)
   658  }
   659  
   660  func fetchCredentialSpecOptions(s storage.Store, msg service.DIDCommMsg) (*CredentialSpecOptions, error) {
   661  	thid, err := msg.ThreadID()
   662  	if err != nil {
   663  		return nil, fmt.Errorf("failed to get message's threadID: %w", err)
   664  	}
   665  
   666  	raw, err := s.Get(thid)
   667  	if err != nil {
   668  		return nil, fmt.Errorf("failed to fetch options from store with threadID %s: %w", thid, err)
   669  	}
   670  
   671  	options := &CredentialSpecOptions{}
   672  
   673  	return options, json.Unmarshal(raw, options)
   674  }
   675  
   676  func deleteOptionsIfNoError(err error, s storage.Store, msg service.DIDCommMsg) error {
   677  	if err != nil {
   678  		return err
   679  	}
   680  
   681  	thid, err := msg.ThreadID()
   682  	if err != nil {
   683  		return fmt.Errorf("failed to get message's threadID: %w", err)
   684  	}
   685  
   686  	return s.Delete(thid)
   687  }
   688  
   689  type bbsSigner struct {
   690  	km kms.KeyManager
   691  	cr crypto.Crypto
   692  	kh interface{}
   693  }
   694  
   695  func newBBSSigner(km kms.KeyManager, cr crypto.Crypto, keyHandle interface{}) *bbsSigner {
   696  	return &bbsSigner{km: km, cr: cr, kh: keyHandle}
   697  }
   698  
   699  func (s *bbsSigner) Sign(data []byte) ([]byte, error) {
   700  	return s.cr.SignMulti(s.textToLines(string(data)), s.kh)
   701  }
   702  
   703  func (s *bbsSigner) Alg() string {
   704  	return "Bls12381G2Key2020"
   705  }
   706  
   707  func (s *bbsSigner) textToLines(txt string) [][]byte {
   708  	lines := strings.Split(txt, "\n")
   709  	linesBytes := make([][]byte, 0, len(lines))
   710  
   711  	for i := range lines {
   712  		if strings.TrimSpace(lines[i]) != "" {
   713  			linesBytes = append(linesBytes, []byte(lines[i]))
   714  		}
   715  	}
   716  
   717  	return linesBytes
   718  }