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 }