github.com/hyperledger/aries-framework-go@v0.3.2/pkg/doc/presexch/definition.go (about) 1 /* 2 Copyright SecureKey Technologies Inc. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package presexch 8 9 import ( 10 "bytes" 11 "encoding/json" 12 "errors" 13 "fmt" 14 "sort" 15 "strings" 16 17 "github.com/PaesslerAG/jsonpath" 18 "github.com/google/uuid" 19 jsonpathkeys "github.com/kawamuray/jsonpath" 20 "github.com/piprate/json-gold/ld" 21 "github.com/tidwall/gjson" 22 "github.com/tidwall/sjson" 23 "github.com/xeipuuv/gojsonschema" 24 25 "github.com/hyperledger/aries-framework-go/pkg/common/log" 26 "github.com/hyperledger/aries-framework-go/pkg/doc/jose" 27 "github.com/hyperledger/aries-framework-go/pkg/doc/jwt" 28 "github.com/hyperledger/aries-framework-go/pkg/doc/sdjwt/common" 29 "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" 30 ) 31 32 const ( 33 34 // All rule`s value. 35 All Selection = "all" 36 // Pick rule`s value. 37 Pick Selection = "pick" 38 39 // Required predicate`s value. 40 Required Preference = "required" 41 // Preferred predicate`s value. 42 Preferred Preference = "preferred" 43 44 tmpEnding = "tmp_unique_id_" 45 46 credentialSchema = "credentialSchema" 47 48 // FormatJWT presentation exchange format. 49 FormatJWT = "jwt" 50 // FormatJWTVC presentation exchange format. 51 FormatJWTVC = "jwt_vc" 52 // FormatJWTVP presentation exchange format. 53 FormatJWTVP = "jwt_vp" 54 // FormatLDP presentation exchange format. 55 FormatLDP = "ldp" 56 // FormatLDPVC presentation exchange format. 57 FormatLDPVC = "ldp_vc" 58 // FormatLDPVP presentation exchange format. 59 FormatLDPVP = "ldp_vp" 60 ) 61 62 var errPathNotApplicable = errors.New("path not applicable") 63 64 var logger = log.New("doc/presexch") 65 66 type ( 67 // Selection can be "all" or "pick". 68 Selection string 69 // Preference can be "required" or "preferred". 70 Preference string 71 // StrOrInt type that defines string or integer. 72 StrOrInt interface{} 73 ) 74 75 func (v *Preference) isRequired() bool { 76 if v == nil { 77 return false 78 } 79 80 return *v == Required 81 } 82 83 // Format describes PresentationDefinition`s Format field. 84 type Format struct { 85 Jwt *JwtType `json:"jwt,omitempty"` 86 JwtVC *JwtType `json:"jwt_vc,omitempty"` 87 JwtVP *JwtType `json:"jwt_vp,omitempty"` 88 Ldp *LdpType `json:"ldp,omitempty"` 89 LdpVC *LdpType `json:"ldp_vc,omitempty"` 90 LdpVP *LdpType `json:"ldp_vp,omitempty"` 91 } 92 93 func (f *Format) notNil() bool { 94 return f != nil && 95 (f.Jwt != nil || f.JwtVC != nil || f.JwtVP != nil || f.Ldp != nil || f.LdpVC != nil || f.LdpVP != nil) 96 } 97 98 // JwtType contains alg. 99 type JwtType struct { 100 Alg []string `json:"alg,omitempty"` 101 } 102 103 // LdpType contains proof_type. 104 type LdpType struct { 105 ProofType []string `json:"proof_type,omitempty"` 106 } 107 108 // PresentationDefinition presentation definitions (https://identity.foundation/presentation-exchange/). 109 type PresentationDefinition struct { 110 // ID unique resource identifier. 111 ID string `json:"id,omitempty"` 112 // Name human-friendly name that describes what the Presentation Definition pertains to. 113 Name string `json:"name,omitempty"` 114 // Purpose describes the purpose for which the Presentation Definition’s inputs are being requested. 115 Purpose string `json:"purpose,omitempty"` 116 Locale string `json:"locale,omitempty"` 117 // Format is an object with one or more properties matching the registered Claim Format Designations 118 // (jwt, jwt_vc, jwt_vp, etc.) to inform the Holder of the claim format configurations the Verifier can process. 119 Format *Format `json:"format,omitempty"` 120 // Frame is used for JSON-LD document framing. 121 Frame map[string]interface{} `json:"frame,omitempty"` 122 // SubmissionRequirements must conform to the Submission Requirement Format. 123 // If not present, all inputs listed in the InputDescriptors array are required for submission. 124 SubmissionRequirements []*SubmissionRequirement `json:"submission_requirements,omitempty"` 125 InputDescriptors []*InputDescriptor `json:"input_descriptors,omitempty"` 126 } 127 128 // SubmissionRequirement describes input that must be submitted via a Presentation Submission 129 // to satisfy Verifier demands. 130 type SubmissionRequirement struct { 131 Name string `json:"name,omitempty"` 132 Purpose string `json:"purpose,omitempty"` 133 Rule Selection `json:"rule,omitempty"` 134 Count int `json:"count,omitempty"` 135 Min int `json:"min,omitempty"` 136 Max int `json:"max,omitempty"` 137 From string `json:"from,omitempty"` 138 FromNested []*SubmissionRequirement `json:"from_nested,omitempty"` 139 } 140 141 // InputDescriptor input descriptors. 142 type InputDescriptor struct { 143 ID string `json:"id,omitempty"` 144 Group []string `json:"group,omitempty"` 145 Name string `json:"name,omitempty"` 146 Purpose string `json:"purpose,omitempty"` 147 Metadata map[string]interface{} `json:"metadata,omitempty"` 148 Schema []*Schema `json:"schema,omitempty"` 149 Constraints *Constraints `json:"constraints,omitempty"` 150 Format *Format `json:"format,omitempty"` 151 } 152 153 // Schema input descriptor schema. 154 type Schema struct { 155 URI string `json:"uri,omitempty"` 156 Required bool `json:"required,omitempty"` 157 } 158 159 // Holder describes Constraints`s holder object. 160 type Holder struct { 161 FieldID []string `json:"field_id,omitempty"` 162 Directive *Preference `json:"directive,omitempty"` 163 } 164 165 // Constraints describes InputDescriptor`s Constraints field. 166 type Constraints struct { 167 LimitDisclosure *Preference `json:"limit_disclosure,omitempty"` 168 SubjectIsIssuer *Preference `json:"subject_is_issuer,omitempty"` 169 IsHolder []*Holder `json:"is_holder,omitempty"` 170 Fields []*Field `json:"fields,omitempty"` 171 } 172 173 // Field describes Constraints`s Fields field. 174 type Field struct { 175 Path []string `json:"path,omitempty"` 176 ID string `json:"id,omitempty"` 177 Purpose string `json:"purpose,omitempty"` 178 Filter *Filter `json:"filter,omitempty"` 179 Predicate *Preference `json:"predicate,omitempty"` 180 IntentToRetain bool `json:"intent_to_retain,omitempty"` 181 Optional bool `json:"optional,omitempty"` 182 } 183 184 // Filter describes filter. 185 type Filter struct { 186 Type *string `json:"type,omitempty"` 187 Format string `json:"format,omitempty"` 188 Pattern string `json:"pattern,omitempty"` 189 Minimum StrOrInt `json:"minimum,omitempty"` 190 Maximum StrOrInt `json:"maximum,omitempty"` 191 MinLength int `json:"minLength,omitempty"` 192 MaxLength int `json:"maxLength,omitempty"` 193 ExclusiveMinimum StrOrInt `json:"exclusiveMinimum,omitempty"` 194 ExclusiveMaximum StrOrInt `json:"exclusiveMaximum,omitempty"` 195 Const StrOrInt `json:"const,omitempty"` 196 Enum []StrOrInt `json:"enum,omitempty"` 197 Not map[string]interface{} `json:"not,omitempty"` 198 Contains map[string]interface{} `json:"contains,omitempty"` 199 } 200 201 // MatchedSubmissionRequirement contains information about VCs that matched a presentation definition. 202 type MatchedSubmissionRequirement struct { 203 Name string 204 Purpose string 205 Rule Selection 206 Count int 207 Min int 208 Max int 209 Descriptors []*MatchedInputDescriptor 210 Nested []*MatchedSubmissionRequirement 211 } 212 213 // MatchedInputDescriptor contains information about VCs that matched an input descriptor of presentation definition. 214 type MatchedInputDescriptor struct { 215 ID string 216 Name string 217 Purpose string 218 MatchedVCs []*verifiable.Credential 219 } 220 221 // matchRequirementsOpts holds options for the MatchSubmissionRequirement. 222 type matchRequirementsOpts struct { 223 applySelectiveDisclosure bool 224 credOpts []verifiable.CredentialOpt 225 } 226 227 // MatchRequirementsOpt is the MatchSubmissionRequirement option. 228 type MatchRequirementsOpt func(opts *matchRequirementsOpts) 229 230 // WithSelectiveDisclosureApply enables selective disclosure apply on resulting VC. 231 func WithSelectiveDisclosureApply() MatchRequirementsOpt { 232 return func(opts *matchRequirementsOpts) { 233 opts.applySelectiveDisclosure = true 234 } 235 } 236 237 // WithSDCredentialOptions used when applying selective disclosure. 238 func WithSDCredentialOptions(options ...verifiable.CredentialOpt) MatchRequirementsOpt { 239 return func(opts *matchRequirementsOpts) { 240 opts.credOpts = options 241 } 242 } 243 244 // ValidateSchema validates presentation definition. 245 func (pd *PresentationDefinition) ValidateSchema() error { 246 result, err := gojsonschema.Validate( 247 gojsonschema.NewStringLoader(DefinitionJSONSchemaV1), 248 gojsonschema.NewGoLoader(struct { 249 PD *PresentationDefinition `json:"presentation_definition"` 250 }{PD: pd}), 251 ) 252 253 if err != nil || !result.Valid() { 254 result, err = gojsonschema.Validate( 255 gojsonschema.NewStringLoader(DefinitionJSONSchemaV2), 256 gojsonschema.NewGoLoader(struct { 257 PD *PresentationDefinition `json:"presentation_definition"` 258 }{PD: pd}), 259 ) 260 } 261 262 if err != nil { 263 return err 264 } 265 266 if result.Valid() { 267 return nil 268 } 269 270 resultErrors := result.Errors() 271 272 errs := make([]string, len(resultErrors)) 273 for i := range resultErrors { 274 errs[i] = resultErrors[i].String() 275 } 276 277 return errors.New(strings.Join(errs, ",")) 278 } 279 280 type constraintsFilterResult struct { 281 credential *verifiable.Credential 282 credentialSrc []byte 283 constraints *Constraints 284 } 285 286 type requirement struct { 287 Name string 288 Purpose string 289 Rule Selection 290 Count int 291 Min int 292 Max int 293 InputDescriptors []*InputDescriptor 294 Nested []*requirement 295 } 296 297 func (r *requirement) isLenApplicable(val int) bool { 298 if r.Count > 0 && val != r.Count { 299 return false 300 } 301 302 if r.Min > 0 && r.Min > val { 303 return false 304 } 305 306 if r.Max > 0 && r.Max < val { 307 return false 308 } 309 310 return true 311 } 312 313 func contains(data []string, e string) bool { 314 for _, el := range data { 315 if el == e { 316 return true 317 } 318 } 319 320 return false 321 } 322 323 func toRequirement(sr *SubmissionRequirement, descriptors []*InputDescriptor) (*requirement, error) { 324 var ( 325 inputDescriptors []*InputDescriptor 326 nested []*requirement 327 ) 328 329 var totalCount int 330 331 if sr.From != "" { 332 for _, descriptor := range descriptors { 333 if contains(descriptor.Group, sr.From) { 334 inputDescriptors = append(inputDescriptors, descriptor) 335 } 336 } 337 338 totalCount = len(inputDescriptors) 339 if totalCount == 0 { 340 return nil, fmt.Errorf("no descriptors for from: %s", sr.From) 341 } 342 } else { 343 for _, sReq := range sr.FromNested { 344 req, err := toRequirement(sReq, descriptors) 345 if err != nil { 346 return nil, err 347 } 348 nested = append(nested, req) 349 } 350 351 totalCount = len(nested) 352 } 353 354 count := sr.Count 355 356 if sr.Rule == All { 357 count = totalCount 358 } 359 360 return &requirement{ 361 Name: sr.Name, 362 Purpose: sr.Purpose, 363 Rule: sr.Rule, 364 Count: count, 365 Min: sr.Min, 366 Max: sr.Max, 367 InputDescriptors: inputDescriptors, 368 Nested: nested, 369 }, nil 370 } 371 372 func makeRequirement(requirements []*SubmissionRequirement, descriptors []*InputDescriptor) (*requirement, error) { 373 if len(requirements) == 0 { 374 return &requirement{ 375 Count: len(descriptors), 376 InputDescriptors: descriptors, 377 }, nil 378 } 379 380 req := &requirement{ 381 Count: len(requirements), 382 } 383 384 for _, submissionRequirement := range requirements { 385 r, err := toRequirement(submissionRequirement, descriptors) 386 if err != nil { 387 return nil, err 388 } 389 390 req.Nested = append(req.Nested, r) 391 } 392 393 return req, nil 394 } 395 396 // CreateVP creates verifiable presentation. 397 func (pd *PresentationDefinition) CreateVP(credentials []*verifiable.Credential, 398 documentLoader ld.DocumentLoader, opts ...verifiable.CredentialOpt) (*verifiable.Presentation, error) { 399 applicableCredentials, submission, err := presentationData(pd, credentials, documentLoader, false, opts...) 400 if err != nil { 401 return nil, err 402 } 403 404 vp, err := presentation(applicableCredentials...) 405 if err != nil { 406 return nil, err 407 } 408 409 vp.CustomFields = verifiable.CustomFields{ 410 submissionProperty: submission, 411 } 412 413 return vp, nil 414 } 415 416 // CreateVPArray creates a list of verifiable presentations, with one presentation for each provided credential. 417 // A PresentationSubmission is returned alongside, which uses the presentation list as the root for json paths. 418 func (pd *PresentationDefinition) CreateVPArray( 419 credentials []*verifiable.Credential, 420 documentLoader ld.DocumentLoader, 421 opts ...verifiable.CredentialOpt, 422 ) ([]*verifiable.Presentation, *PresentationSubmission, error) { 423 applicableCredentials, submission, err := presentationData(pd, credentials, documentLoader, true, opts...) 424 if err != nil { 425 return nil, nil, err 426 } 427 428 var presentations []*verifiable.Presentation 429 430 for _, credential := range applicableCredentials { 431 vp, e := presentation(credential) 432 if e != nil { 433 return nil, nil, e 434 } 435 436 presentations = append(presentations, vp) 437 } 438 439 return presentations, submission, nil 440 } 441 442 func presentationData( 443 pd *PresentationDefinition, 444 credentials []*verifiable.Credential, 445 documentLoader ld.DocumentLoader, 446 separatePresentations bool, 447 opts ...verifiable.CredentialOpt, 448 ) ([]*verifiable.Credential, *PresentationSubmission, error) { 449 if err := pd.ValidateSchema(); err != nil { 450 return nil, nil, err 451 } 452 453 req, err := makeRequirement(pd.SubmissionRequirements, pd.InputDescriptors) 454 if err != nil { 455 return nil, nil, err 456 } 457 458 format, result, err := pd.applyRequirement(req, credentials, documentLoader, opts...) 459 if err != nil { 460 return nil, nil, err 461 } 462 463 applicableCredentials, descriptors := merge(format, result, separatePresentations) 464 465 submission := &PresentationSubmission{ 466 ID: uuid.New().String(), 467 DefinitionID: pd.ID, 468 DescriptorMap: descriptors, 469 } 470 471 return applicableCredentials, submission, nil 472 } 473 474 func presentation(credentials ...*verifiable.Credential) (*verifiable.Presentation, error) { 475 vp, e := verifiable.NewPresentation(verifiable.WithCredentials(credentials...)) 476 if e != nil { 477 return nil, e 478 } 479 480 vp.Context = append(vp.Context, PresentationSubmissionJSONLDContextIRI) 481 vp.Type = append(vp.Type, PresentationSubmissionJSONLDType) 482 vp.ID = uuid.NewString() 483 484 return vp, nil 485 } 486 487 func makeRequirementsForMatch(requirements []*SubmissionRequirement, 488 descriptors []*InputDescriptor) ([]*requirement, error) { 489 if len(requirements) == 0 { 490 return []*requirement{{ 491 Name: "", 492 Purpose: "", 493 Rule: All, 494 Count: len(descriptors), 495 InputDescriptors: descriptors, 496 Nested: nil, 497 }}, nil 498 } 499 500 var reqs []*requirement 501 502 for _, submissionRequirement := range requirements { 503 r, err := toRequirement(submissionRequirement, descriptors) 504 if err != nil { 505 return nil, err 506 } 507 508 reqs = append(reqs, r) 509 } 510 511 return reqs, nil 512 } 513 514 // MatchSubmissionRequirement return information about matching VCs. 515 func (pd *PresentationDefinition) MatchSubmissionRequirement(credentials []*verifiable.Credential, 516 documentLoader ld.DocumentLoader, opts ...MatchRequirementsOpt) ([]*MatchedSubmissionRequirement, error) { 517 matchOpts := &matchRequirementsOpts{} 518 for _, opt := range opts { 519 opt(matchOpts) 520 } 521 522 if err := pd.ValidateSchema(); err != nil { 523 return nil, err 524 } 525 526 requirements, err := makeRequirementsForMatch(pd.SubmissionRequirements, pd.InputDescriptors) 527 if err != nil { 528 return nil, err 529 } 530 531 var matchedReqs []*MatchedSubmissionRequirement 532 533 for _, req := range requirements { 534 matched, err := pd.matchRequirement(req, credentials, documentLoader, matchOpts) 535 if err != nil { 536 return nil, err 537 } 538 539 matchedReqs = append(matchedReqs, matched) 540 } 541 542 return matchedReqs, nil 543 } 544 545 // ErrNoCredentials when any credentials do not satisfy requirements. 546 var ErrNoCredentials = errors.New("credentials do not satisfy requirements") 547 548 // nolint: funlen,gocyclo 549 func (pd *PresentationDefinition) matchRequirement(req *requirement, creds []*verifiable.Credential, 550 documentLoader ld.DocumentLoader, opts *matchRequirementsOpts) (*MatchedSubmissionRequirement, error) { 551 matchedReq := &MatchedSubmissionRequirement{ 552 Name: req.Name, 553 Purpose: req.Purpose, 554 Rule: req.Rule, 555 Count: req.Count, 556 Min: req.Min, 557 Max: req.Max, 558 Descriptors: nil, 559 Nested: nil, 560 } 561 562 for _, descriptor := range req.InputDescriptors { 563 framedCreds := creds 564 565 var err error 566 567 if opts.applySelectiveDisclosure { 568 framedCreds, err = frameCreds(pd.Frame, creds, opts.credOpts...) 569 if err != nil { 570 return nil, err 571 } 572 } 573 574 _, filtered, err := pd.filterCredentialsThatMatchDescriptor( 575 framedCreds, descriptor, documentLoader) 576 if err != nil { 577 return nil, err 578 } 579 580 var matchedVCs []*verifiable.Credential 581 582 if opts.applySelectiveDisclosure { 583 matchedVCs, err = limitDisclosure(filtered, opts.credOpts...) 584 if err != nil { 585 return nil, err 586 } 587 588 // TODO: remove this workaround after refactoring "merge" function to get rid of 589 // TODO: the trick with the modification of credential id. 590 for _, cred := range matchedVCs { 591 cred.ID = trimTmpID(cred.ID) 592 } 593 } else { 594 for _, credRes := range filtered { 595 matchedVCs = append(matchedVCs, credRes.credential) 596 } 597 } 598 599 matchedReq.Descriptors = append(matchedReq.Descriptors, &MatchedInputDescriptor{ 600 ID: descriptor.ID, 601 Name: descriptor.Name, 602 Purpose: descriptor.Purpose, 603 MatchedVCs: matchedVCs, 604 }) 605 } 606 607 for _, nestedReq := range req.Nested { 608 nestedMatch, err := pd.matchRequirement(nestedReq, creds, documentLoader, opts) 609 if err != nil { 610 return nil, err 611 } 612 613 matchedReq.Nested = append(matchedReq.Nested, nestedMatch) 614 } 615 616 return matchedReq, nil 617 } 618 619 // nolint: gocyclo,funlen,gocognit 620 func (pd *PresentationDefinition) applyRequirement(req *requirement, creds []*verifiable.Credential, 621 documentLoader ld.DocumentLoader, 622 opts ...verifiable.CredentialOpt) (string, map[string][]*verifiable.Credential, error) { 623 result := make(map[string][]*verifiable.Credential) 624 // assume LDPVP format if pd.Format is not set. 625 // Usually pd.Format will be set when creds include a non-empty Proofs field since they represent the designated 626 // format. 627 vpFormat := FormatLDPVP 628 629 for _, descriptor := range req.InputDescriptors { 630 framedCreds, err := frameCreds(pd.Frame, creds, opts...) 631 if err != nil { 632 return "", nil, err 633 } 634 635 descFormat, filtered, err := pd.filterCredentialsThatMatchDescriptor( 636 framedCreds, descriptor, documentLoader) 637 if err != nil { 638 return "", nil, err 639 } 640 641 if descFormat != "" { 642 vpFormat = descFormat 643 } 644 645 filteredCreds, err := limitDisclosure(filtered, opts...) 646 if err != nil { 647 return "", nil, err 648 } 649 650 if len(filtered) != 0 { 651 result[descriptor.ID] = filteredCreds 652 } 653 } 654 655 if len(req.InputDescriptors) != 0 { 656 if req.isLenApplicable(len(result)) { 657 return vpFormat, result, nil 658 } 659 660 return "", nil, ErrNoCredentials 661 } 662 663 var nestedResult []map[string][]*verifiable.Credential 664 665 // maps credential to descriptors that satisfy requirements 666 set := map[string]map[string]string{} 667 668 for _, r := range req.Nested { 669 vpFmt, res, err := pd.applyRequirement(r, creds, documentLoader, opts...) 670 if errors.Is(err, ErrNoCredentials) { 671 continue 672 } 673 674 if err != nil { 675 return "", nil, err 676 } 677 678 for desc, credentials := range res { 679 for _, cred := range credentials { 680 if _, ok := set[trimTmpID(cred.ID)]; !ok { 681 set[trimTmpID(cred.ID)] = map[string]string{} 682 } 683 684 set[trimTmpID(cred.ID)][desc] = cred.ID 685 } 686 } 687 688 if len(res) != 0 { 689 nestedResult = append(nestedResult, res) 690 vpFormat = vpFmt 691 } 692 } 693 694 exclude := map[string]struct{}{} 695 696 for k := range set { 697 if !req.isLenApplicable(len(set[k])) { 698 for desc, cID := range set[k] { 699 exclude[desc+cID] = struct{}{} 700 } 701 } 702 } 703 704 return vpFormat, mergeNestedResult(nestedResult, exclude), nil 705 } 706 707 func (pd *PresentationDefinition) filterCredentialsThatMatchDescriptor(creds []*verifiable.Credential, 708 descriptor *InputDescriptor, 709 documentLoader ld.DocumentLoader) (string, []constraintsFilterResult, error) { 710 format := pd.Format 711 if descriptor.Format.notNil() { 712 format = descriptor.Format 713 } 714 715 vpFormat := "" 716 filtered := creds 717 718 if format.notNil() { 719 vpFormat, filtered = filterFormat(format, filtered) 720 } 721 722 // Validate schema only for v1 723 if descriptor.Schema != nil { 724 filtered = filterSchema(descriptor.Schema, filtered, documentLoader) 725 } 726 727 filteredByConstraints, err := filterConstraints(descriptor.Constraints, filtered) 728 if err != nil { 729 return "", nil, err 730 } 731 732 return vpFormat, filteredByConstraints, nil 733 } 734 735 func mergeNestedResult(nr []map[string][]*verifiable.Credential, 736 exclude map[string]struct{}) map[string][]*verifiable.Credential { 737 result := make(map[string][]*verifiable.Credential) 738 739 for _, res := range nr { 740 for key, credentials := range res { 741 set := map[string]struct{}{} 742 743 var mergedCredentials []*verifiable.Credential 744 745 for _, credential := range result[key] { 746 if _, ok := set[credential.ID]; !ok { 747 mergedCredentials = append(mergedCredentials, credential) 748 set[credential.ID] = struct{}{} 749 } 750 } 751 752 for _, credential := range credentials { 753 if _, ok := set[credential.ID]; !ok { 754 if _, exist := exclude[key+credential.ID]; !exist { 755 mergedCredentials = append(mergedCredentials, credential) 756 set[credential.ID] = struct{}{} 757 } 758 } 759 } 760 761 result[key] = mergedCredentials 762 } 763 } 764 765 return result 766 } 767 768 func getSubjectIDs(subject interface{}) []string { // nolint: gocyclo 769 switch s := subject.(type) { 770 case string: 771 return []string{s} 772 case []map[string]interface{}: 773 var res []string 774 775 for i := range s { 776 v, ok := s[i]["id"] 777 if !ok { 778 continue 779 } 780 781 sID, ok := v.(string) 782 if !ok { 783 continue 784 } 785 786 res = append(res, sID) 787 } 788 789 return res 790 case map[string]interface{}: 791 v, ok := s["id"] 792 if !ok { 793 return nil 794 } 795 796 sID, ok := v.(string) 797 if !ok { 798 return nil 799 } 800 801 return []string{sID} 802 case verifiable.Subject: 803 return []string{s.ID} 804 805 case []verifiable.Subject: 806 var res []string 807 for i := range s { 808 res = append(res, s[i].ID) 809 } 810 811 return res 812 } 813 814 return nil 815 } 816 817 func subjectIsIssuer(credential *verifiable.Credential) bool { 818 for _, ID := range getSubjectIDs(credential.Subject) { 819 if ID != "" && ID == credential.Issuer.ID { 820 return true 821 } 822 } 823 824 return false 825 } 826 827 // nolint: gocyclo,funlen,gocognit 828 func filterConstraints(constraints *Constraints, creds []*verifiable.Credential) ([]constraintsFilterResult, error) { 829 var result []constraintsFilterResult 830 831 if constraints == nil { 832 for _, credential := range creds { 833 result = append(result, constraintsFilterResult{ 834 credential: credential, 835 }) 836 } 837 838 return result, nil 839 } 840 841 for _, credential := range creds { 842 if constraints.SubjectIsIssuer.isRequired() && !subjectIsIssuer(credential) { 843 continue 844 } 845 846 var applicable bool 847 848 // if credential.JWT is set, credential will marshal to a JSON string. 849 // temporarily clear credential.JWT to avoid this. 850 851 var err error 852 853 credJWT := credential.JWT 854 855 credentialWithFieldValues := credential 856 857 if credential.SDJWTHashAlg != "" { 858 credentialWithFieldValues, err = credential.CreateDisplayCredential(verifiable.DisplayAllDisclosures()) 859 if err != nil { 860 continue 861 } 862 } 863 864 // if credential.JWT is set, credential will marshal to a JSON string. 865 // temporarily clear credential.JWT to avoid this. 866 credentialWithFieldValues.JWT = "" 867 868 credentialSrc, err := json.Marshal(credentialWithFieldValues) 869 if err != nil { 870 continue 871 } 872 873 credentialWithFieldValues.JWT = credJWT 874 875 var credentialMap map[string]interface{} 876 877 err = json.Unmarshal(credentialSrc, &credentialMap) 878 if err != nil { 879 return nil, err 880 } 881 882 for i, field := range constraints.Fields { 883 err = filterField(field, credentialMap) 884 if errors.Is(err, errPathNotApplicable) { 885 applicable = false 886 887 break 888 } 889 890 if err != nil { 891 return nil, fmt.Errorf("filter field.%d: %w", i, err) 892 } 893 894 applicable = true 895 } 896 897 if !applicable { 898 continue 899 } 900 901 filterRes := constraintsFilterResult{ 902 credential: credential, 903 credentialSrc: credentialSrc, 904 constraints: constraints, 905 } 906 907 result = append(result, filterRes) 908 } 909 910 return result, nil 911 } 912 913 // nolint: gocyclo, funlen 914 func limitDisclosure(filterResults []constraintsFilterResult, 915 opts ...verifiable.CredentialOpt) ([]*verifiable.Credential, error) { 916 var result []*verifiable.Credential 917 918 for _, filtered := range filterResults { 919 credential := filtered.credential 920 constraints := filtered.constraints 921 credentialSrc := filtered.credentialSrc 922 923 if constraints == nil { 924 result = append(result, credential) 925 continue 926 } 927 928 var predicate bool 929 930 for _, field := range constraints.Fields { 931 if field.Predicate.isRequired() { 932 predicate = true 933 } 934 } 935 936 if (constraints.LimitDisclosure.isRequired() || predicate) && credential.SDJWTHashAlg == "" { 937 template := credentialSrc 938 939 var contexts []interface{} 940 941 for _, ctx := range credential.Context { 942 contexts = append(contexts, ctx) 943 } 944 945 contexts = append(contexts, credential.CustomContext...) 946 947 if constraints.LimitDisclosure.isRequired() { 948 var err error 949 950 template, err = json.Marshal(map[string]interface{}{ 951 "id": credential.ID, 952 "type": credential.Types, 953 "@context": contexts, 954 "issuer": credential.Issuer, 955 "credentialSubject": toSubject(credential.Subject), 956 "issuanceDate": credential.Issued, 957 }) 958 if err != nil { 959 return nil, err 960 } 961 } 962 963 var err error 964 965 credential, err = createNewCredential(constraints, credentialSrc, template, credential, opts...) 966 if err != nil { 967 return nil, fmt.Errorf("create new credential: %w", err) 968 } 969 970 credential.ID = tmpID(credential.ID) 971 } 972 973 if constraints.LimitDisclosure.isRequired() && credential.SDJWTHashAlg != "" { 974 limitedDisclosures, err := getLimitedDisclosures(constraints, credentialSrc, credential) 975 if err != nil { 976 return nil, err 977 } 978 979 credential.SDJWTDisclosures = limitedDisclosures 980 } 981 982 result = append(result, credential) 983 } 984 985 return result, nil 986 } 987 988 // nolint: gocyclo,funlen,gocognit 989 func getLimitedDisclosures(constraints *Constraints, displaySrc []byte, credential *verifiable.Credential) ([]*common.DisclosureClaim, error) { // nolint:lll 990 hash, err := common.GetCryptoHash(credential.SDJWTHashAlg) 991 if err != nil { 992 return nil, err 993 } 994 995 vcJWT := credential.JWT 996 credential.JWT = "" 997 998 credentialSrc, err := json.Marshal(credential) 999 if err != nil { 1000 return nil, err 1001 } 1002 1003 // revert JWT to original value 1004 credential.JWT = vcJWT 1005 1006 var limitedDisclosures []*common.DisclosureClaim 1007 1008 for _, f := range constraints.Fields { 1009 jPaths, err := getJSONPaths(f.Path, displaySrc) 1010 if err != nil { 1011 return nil, err 1012 } 1013 1014 for _, path := range jPaths { 1015 if strings.Contains(path[0], credentialSchema) { 1016 continue 1017 } 1018 1019 parentPath := "" 1020 1021 key := path[1] 1022 1023 pathParts := strings.Split(path[1], ".") 1024 if len(pathParts) > 1 { 1025 parentPath = strings.Join(pathParts[:len(pathParts)-1], ".") 1026 key = pathParts[len(pathParts)-1] 1027 } 1028 1029 parentObj, ok := gjson.GetBytes(credentialSrc, parentPath).Value().(map[string]interface{}) 1030 if !ok { 1031 // no selective disclosures at this level, so nothing to add to limited disclosures 1032 continue 1033 } 1034 1035 digests, err := common.GetDisclosureDigests(parentObj) 1036 if err != nil { 1037 return nil, err 1038 } 1039 1040 for _, dc := range credential.SDJWTDisclosures { 1041 if dc.Name == key { 1042 digest, err := common.GetHash(hash, dc.Disclosure) 1043 if err != nil { 1044 return nil, err 1045 } 1046 1047 if _, ok := digests[digest]; ok { 1048 limitedDisclosures = append(limitedDisclosures, dc) 1049 } 1050 } 1051 } 1052 } 1053 } 1054 1055 return limitedDisclosures, nil 1056 } 1057 1058 func frameCreds(frame map[string]interface{}, creds []*verifiable.Credential, 1059 opts ...verifiable.CredentialOpt) ([]*verifiable.Credential, error) { 1060 if frame == nil { 1061 return creds, nil 1062 } 1063 1064 var result []*verifiable.Credential 1065 1066 for _, credential := range creds { 1067 bbsVC, err := credential.GenerateBBSSelectiveDisclosure(frame, nil, opts...) 1068 if err != nil { 1069 return nil, err 1070 } 1071 1072 result = append(result, bbsVC) 1073 } 1074 1075 return result, nil 1076 } 1077 1078 func toSubject(subject interface{}) interface{} { 1079 sub, ok := subject.([]verifiable.Subject) 1080 if ok && len(sub) == 1 { 1081 return verifiable.Subject{ID: sub[0].ID} 1082 } 1083 1084 return subject 1085 } 1086 1087 func tmpID(id string) string { 1088 return id + tmpEnding + uuid.New().String() 1089 } 1090 1091 func trimTmpID(id string) string { 1092 idx := strings.Index(id, tmpEnding) 1093 if idx == -1 { 1094 return id 1095 } 1096 1097 return id[:idx] 1098 } 1099 1100 // nolint: funlen,gocognit,gocyclo 1101 func createNewCredential(constraints *Constraints, src, limitedCred []byte, 1102 credential *verifiable.Credential, opts ...verifiable.CredentialOpt) (*verifiable.Credential, error) { 1103 var ( 1104 BBSSupport = hasBBS(credential) 1105 modifiedByPredicate bool 1106 explicitPaths = make(map[string]bool) 1107 ) 1108 1109 for _, f := range constraints.Fields { 1110 jPaths, err := getJSONPaths(f.Path, src) 1111 if err != nil { 1112 return nil, err 1113 } 1114 1115 for _, path := range jPaths { 1116 if strings.Contains(path[0], credentialSchema) { 1117 continue 1118 } 1119 1120 var val interface{} = true 1121 1122 if !modifiedByPredicate { 1123 modifiedByPredicate = f.Predicate.isRequired() 1124 } 1125 1126 if f.Predicate == nil || *f.Predicate != Required { 1127 val = gjson.GetBytes(src, path[1]).Value() 1128 } 1129 1130 if constraints.LimitDisclosure.isRequired() && BBSSupport { 1131 chunks := strings.Split(path[0], ".") 1132 explicitPath := strings.Join(chunks[:len(chunks)-1], ".") 1133 explicitPaths[explicitPath] = true 1134 } 1135 1136 limitedCred, err = sjson.SetBytes(limitedCred, path[0], val) 1137 if err != nil { 1138 return nil, err 1139 } 1140 } 1141 } 1142 1143 if !constraints.LimitDisclosure.isRequired() || !BBSSupport || modifiedByPredicate { 1144 opts = append(opts, verifiable.WithDisabledProofCheck()) 1145 return verifiable.ParseCredential(limitedCred, opts...) 1146 } 1147 1148 limitedCred, err := enhanceRevealDoc(explicitPaths, limitedCred, src) 1149 if err != nil { 1150 return nil, err 1151 } 1152 1153 var doc map[string]interface{} 1154 if err := json.Unmarshal(limitedCred, &doc); err != nil { 1155 return nil, err 1156 } 1157 1158 return credential.GenerateBBSSelectiveDisclosure(doc, []byte(uuid.New().String()), opts...) 1159 } 1160 1161 func getJSONPaths(keys []string, src []byte) ([][2]string, error) { 1162 paths, err := jsonpathkeys.ParsePaths(keys...) 1163 if err != nil { 1164 return nil, err 1165 } 1166 1167 eval, err := jsonpathkeys.EvalPathsInReader(bytes.NewReader(src), paths) 1168 if err != nil { 1169 return nil, err 1170 } 1171 1172 var jPaths [][2]string 1173 1174 set := map[string]int{} 1175 1176 for { 1177 result, ok := eval.Next() 1178 if !ok { 1179 break 1180 } 1181 1182 jPaths = append(jPaths, getPath(result.Keys, set)) 1183 } 1184 1185 return jPaths, nil 1186 } 1187 1188 func enhanceRevealDoc(explicitPaths map[string]bool, limitedCred, vcBytes []byte) ([]byte, error) { 1189 var err error 1190 1191 limitedCred, err = sjson.SetBytes(limitedCred, "@explicit", true) 1192 if err != nil { 1193 return nil, err 1194 } 1195 1196 intermPaths := make(map[string]bool) 1197 1198 for path, fromConstraint := range explicitPaths { 1199 limitedCred, err = enhanceRevealField(path, limitedCred, vcBytes) 1200 if err != nil { 1201 return nil, err 1202 } 1203 1204 if !fromConstraint { 1205 continue 1206 } 1207 1208 pathParts := strings.Split(path, ".") 1209 combinedPath := "" 1210 1211 for i := 0; i < len(pathParts)-1; i++ { 1212 if i == 0 { 1213 combinedPath = pathParts[0] 1214 } else { 1215 combinedPath += "." + pathParts[i] 1216 } 1217 1218 if _, ok := explicitPaths[combinedPath]; !ok { 1219 intermPaths[combinedPath] = false 1220 } 1221 } 1222 } 1223 1224 for path := range intermPaths { 1225 limitedCred, err = enhanceRevealField(path, limitedCred, vcBytes) 1226 if err != nil { 1227 return nil, err 1228 } 1229 } 1230 1231 return limitedCred, nil 1232 } 1233 1234 func enhanceRevealField(path string, limitedCred, vcBytes []byte) ([]byte, error) { 1235 var err error 1236 1237 limitedCred, err = sjson.SetBytes(limitedCred, path+".@explicit", true) 1238 if err != nil { 1239 return nil, err 1240 } 1241 1242 for _, cf := range [...]string{"type", "@context"} { 1243 specialFieldPath := path + "." + cf 1244 1245 specialFieldValue := gjson.GetBytes(vcBytes, specialFieldPath) 1246 if specialFieldValue.Type == gjson.Null { 1247 continue 1248 } 1249 1250 limitedCred, err = sjson.SetBytes(limitedCred, specialFieldPath, specialFieldValue.Value()) 1251 if err != nil { 1252 return nil, err 1253 } 1254 } 1255 1256 return limitedCred, nil 1257 } 1258 1259 func hasBBS(vc *verifiable.Credential) bool { 1260 for _, proof := range vc.Proofs { 1261 if proof["type"] == "BbsBlsSignature2020" { 1262 return true 1263 } 1264 } 1265 1266 return false 1267 } 1268 1269 func hasProofWithType(vc *verifiable.Credential, proofType string) bool { 1270 for _, proof := range vc.Proofs { 1271 if proof["type"] == proofType { 1272 return true 1273 } 1274 } 1275 1276 return false 1277 } 1278 1279 func filterField(f *Field, credential map[string]interface{}) error { 1280 var schema gojsonschema.JSONLoader 1281 1282 if f.Filter != nil { 1283 schema = gojsonschema.NewGoLoader(*f.Filter) 1284 } 1285 1286 var lastErr error 1287 1288 for _, path := range f.Path { 1289 patch, err := jsonpath.Get(path, credential) 1290 if err == nil { 1291 err = validatePatch(schema, patch) 1292 if err == nil { 1293 return nil 1294 } 1295 1296 lastErr = err 1297 } else if f.Optional { 1298 return nil 1299 } else { 1300 lastErr = errPathNotApplicable 1301 } 1302 } 1303 1304 return lastErr 1305 } 1306 1307 func validatePatch(schema gojsonschema.JSONLoader, patch interface{}) error { 1308 if schema == nil { 1309 return nil 1310 } 1311 1312 raw, err := json.Marshal(patch) 1313 if err != nil { 1314 return err 1315 } 1316 1317 result, err := gojsonschema.Validate(schema, gojsonschema.NewBytesLoader(raw)) 1318 if err != nil || !result.Valid() { 1319 return errPathNotApplicable 1320 } 1321 1322 return nil 1323 } 1324 1325 func getPath(keys []interface{}, set map[string]int) [2]string { 1326 var ( 1327 newPath []string 1328 originalPath []string 1329 ) 1330 1331 for _, k := range keys { 1332 switch v := k.(type) { 1333 case int: 1334 counterKey := strings.Join(originalPath, ".") 1335 originalPath = append(originalPath, fmt.Sprintf("%d", v)) 1336 mapperKey := strings.Join(originalPath, ".") 1337 1338 if _, ok := set[mapperKey]; !ok { 1339 set[mapperKey] = set[counterKey] 1340 set[counterKey]++ 1341 } 1342 1343 newPath = append(newPath, fmt.Sprintf("%d", set[mapperKey])) 1344 default: 1345 originalPath = append(originalPath, fmt.Sprintf("%s", v)) 1346 newPath = append(newPath, fmt.Sprintf("%s", v)) 1347 } 1348 } 1349 1350 return [...]string{strings.Join(newPath, "."), strings.Join(originalPath, ".")} 1351 } 1352 1353 func merge( 1354 presentationFormat string, 1355 setOfCredentials map[string][]*verifiable.Credential, 1356 separatePresentations bool, 1357 ) ([]*verifiable.Credential, []*InputDescriptorMapping) { //nolint:lll 1358 setOfCreds := make(map[string]int) 1359 setOfDescriptors := make(map[string]struct{}) 1360 1361 var ( 1362 result []*verifiable.Credential 1363 descriptors []*InputDescriptorMapping 1364 ) 1365 1366 keys := make([]string, 0, len(setOfCredentials)) 1367 for k := range setOfCredentials { 1368 keys = append(keys, k) 1369 } 1370 1371 sort.Strings(keys) 1372 1373 for _, descriptorID := range keys { 1374 credentials := setOfCredentials[descriptorID] 1375 1376 for _, credential := range credentials { 1377 if _, ok := setOfCreds[credential.ID]; !ok { 1378 credential.ID = trimTmpID(credential.ID) 1379 result = append(result, credential) 1380 setOfCreds[credential.ID] = len(result) - 1 1381 } 1382 1383 vcFormat := FormatLDPVC 1384 if credential.JWT != "" { 1385 vcFormat = FormatJWTVC 1386 } 1387 1388 if _, ok := setOfDescriptors[fmt.Sprintf("%s-%s", credential.ID, credential.ID)]; !ok { 1389 desc := &InputDescriptorMapping{ 1390 ID: descriptorID, 1391 Format: presentationFormat, 1392 PathNested: &InputDescriptorMapping{ 1393 ID: descriptorID, 1394 Format: vcFormat, 1395 }, 1396 } 1397 1398 if separatePresentations { 1399 desc.Path = fmt.Sprintf("$[%d]", setOfCreds[credential.ID]) 1400 desc.PathNested.Path = "$.verifiableCredential[0]" 1401 } else { 1402 desc.Path = "$" 1403 desc.PathNested.Path = fmt.Sprintf("$.verifiableCredential[%d]", setOfCreds[credential.ID]) 1404 } 1405 1406 descriptors = append(descriptors, desc) 1407 } 1408 } 1409 } 1410 1411 sort.Sort(byID(descriptors)) 1412 1413 return result, descriptors 1414 } 1415 1416 type byID []*InputDescriptorMapping 1417 1418 func (a byID) Len() int { return len(a) } 1419 func (a byID) Less(i, j int) bool { return a[i].ID < a[j].ID } 1420 func (a byID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 1421 1422 //nolint:funlen,gocyclo 1423 func filterFormat(format *Format, credentials []*verifiable.Credential) (string, []*verifiable.Credential) { 1424 var ldpCreds, ldpvcCreds, ldpvpCreds, jwtCreds, jwtvcCreds, jwtvpCreds []*verifiable.Credential 1425 1426 for _, credential := range credentials { 1427 if credByProof(credential, format.Ldp) { 1428 ldpCreds = append(ldpCreds, credential) 1429 } 1430 1431 if credByProof(credential, format.LdpVC) { 1432 ldpvcCreds = append(ldpvcCreds, credential) 1433 } 1434 1435 if credByProof(credential, format.LdpVP) { 1436 ldpvpCreds = append(ldpvpCreds, credential) 1437 } 1438 1439 var ( 1440 alg string 1441 hasAlg bool 1442 ) 1443 1444 if credential.JWT != "" { 1445 pJWT, _, err := jwt.Parse(credential.JWT, jwt.WithSignatureVerifier(&noVerifier{})) 1446 if err != nil { 1447 logger.Warnf("unmarshal credential error: %w", err) 1448 1449 continue 1450 } 1451 1452 alg, hasAlg = pJWT.Headers.Algorithm() 1453 } 1454 1455 if hasAlg && algMatch(alg, format.Jwt) { 1456 jwtCreds = append(jwtCreds, credential) 1457 } 1458 1459 if hasAlg && algMatch(alg, format.JwtVC) { 1460 jwtvcCreds = append(jwtvcCreds, credential) 1461 } 1462 1463 if hasAlg && algMatch(alg, format.JwtVP) { 1464 jwtvpCreds = append(jwtvpCreds, credential) 1465 } 1466 } 1467 1468 if len(ldpCreds) > 0 { 1469 return FormatLDP, ldpCreds 1470 } 1471 1472 if len(ldpvcCreds) > 0 { 1473 return FormatLDPVC, ldpvcCreds 1474 } 1475 1476 if len(ldpvpCreds) > 0 { 1477 return FormatLDPVP, ldpvpCreds 1478 } 1479 1480 if len(jwtCreds) > 0 { 1481 return FormatJWT, jwtCreds 1482 } 1483 1484 if len(jwtvcCreds) > 0 { 1485 return FormatJWTVC, jwtvcCreds 1486 } 1487 1488 if len(jwtvpCreds) > 0 { 1489 return FormatJWTVP, jwtvpCreds 1490 } 1491 1492 return "", nil 1493 } 1494 1495 // noVerifier is used when no JWT signature verification is needed. 1496 // To be used with precaution. 1497 type noVerifier struct{} 1498 1499 func (v noVerifier) Verify(_ jose.Headers, _, _, _ []byte) error { 1500 return nil 1501 } 1502 1503 func algMatch(credAlg string, jwtType *JwtType) bool { 1504 if jwtType == nil { 1505 return false 1506 } 1507 1508 for _, b := range jwtType.Alg { 1509 if strings.EqualFold(credAlg, b) { 1510 return true 1511 } 1512 } 1513 1514 return false 1515 } 1516 1517 func credByProof(c *verifiable.Credential, ldp *LdpType) bool { 1518 if ldp == nil { 1519 return false 1520 } 1521 1522 for _, proofType := range ldp.ProofType { 1523 if hasProofWithType(c, proofType) { 1524 return true 1525 } 1526 } 1527 1528 return false 1529 } 1530 1531 // nolint: gocyclo 1532 func filterSchema(schemas []*Schema, credentials []*verifiable.Credential, 1533 documentLoader ld.DocumentLoader) []*verifiable.Credential { 1534 var result []*verifiable.Credential 1535 1536 contexts := map[string]*ld.Context{} 1537 1538 for _, credential := range credentials { 1539 schemaSatisfied := map[string]struct{}{} 1540 1541 for _, ctx := range credential.Context { 1542 ctxObj, ok := contexts[ctx] 1543 if !ok { 1544 context, err := getContext(ctx, documentLoader) 1545 if err != nil { 1546 logger.Errorf("failed to load context '%s': %s", ctx, err.Error()) 1547 return nil 1548 } 1549 1550 contexts[ctx] = context 1551 ctxObj = context 1552 } 1553 1554 for _, typ := range credential.Types { 1555 ids, err := typeFoundInContext(typ, ctxObj) 1556 if err != nil { 1557 continue 1558 } 1559 1560 for _, id := range ids { 1561 schemaSatisfied[id] = struct{}{} 1562 } 1563 } 1564 } 1565 1566 var applicable bool 1567 1568 for _, schema := range schemas { 1569 _, ok := schemaSatisfied[schema.URI] 1570 if ok { 1571 applicable = true 1572 } else if schema.Required { 1573 applicable = false 1574 break 1575 } 1576 } 1577 1578 if applicable { 1579 result = append(result, credential) 1580 } 1581 } 1582 1583 return result 1584 } 1585 1586 func typeFoundInContext(typ string, ctxObj *ld.Context) ([]string, error) { 1587 var out []string 1588 1589 td := ctxObj.GetTermDefinition(typ) 1590 if td == nil { 1591 return nil, nil 1592 } 1593 1594 id, ok := td["@id"].(string) 1595 if ok { 1596 out = append(out, id) 1597 } 1598 1599 tdCtx, ok := td["@context"].(map[string]interface{}) 1600 if !ok { 1601 return out, nil 1602 } 1603 1604 extendedCtx, err := ctxObj.Parse(tdCtx) 1605 if err != nil { 1606 return nil, err 1607 } 1608 1609 iri, err := extendedCtx.ExpandIri(id, false, false, nil, nil) 1610 if err != nil { 1611 return nil, err 1612 } 1613 1614 out = append(out, iri) 1615 1616 return out, nil 1617 } 1618 1619 func getContext(contextURI string, documentLoader ld.DocumentLoader) (*ld.Context, error) { 1620 contextURI = strings.SplitN(contextURI, "#", 2)[0] 1621 1622 remoteDoc, err := documentLoader.LoadDocument(contextURI) 1623 if err != nil { 1624 return nil, fmt.Errorf("loading document: %w", err) 1625 } 1626 1627 doc, ok := remoteDoc.Document.(map[string]interface{}) 1628 if !ok { 1629 return nil, fmt.Errorf("expects jsonld document to be unmarshaled into map[string]interface{}") 1630 } 1631 1632 ctx, ok := doc["@context"] 1633 if !ok { 1634 return nil, fmt.Errorf("@context field not found in context %s", contextURI) 1635 } 1636 1637 var opt *ld.JsonLdOptions 1638 if documentLoader != nil { 1639 opt = ld.NewJsonLdOptions("") 1640 opt.DocumentLoader = documentLoader 1641 } 1642 1643 activeCtx, err := ld.NewContext(nil, opt).Parse(ctx) 1644 if err != nil { 1645 return nil, err 1646 } 1647 1648 return activeCtx, nil 1649 }