diff --git a/pkg/doc/presexch/definition.go b/pkg/doc/presexch/definition.go index ea2b5eaed3..8099fa4773 100644 --- a/pkg/doc/presexch/definition.go +++ b/pkg/doc/presexch/definition.go @@ -23,6 +23,7 @@ import ( "github.com/xeipuuv/gojsonschema" "github.com/hyperledger/aries-framework-go/pkg/common/log" + "github.com/hyperledger/aries-framework-go/pkg/doc/jwt" "github.com/hyperledger/aries-framework-go/pkg/doc/verifiable" ) @@ -41,6 +42,19 @@ const ( tmpEnding = "tmp_unique_id_" credentialSchema = "credentialSchema" + + // FormatJWT presentation exchange format. + FormatJWT = "jwt" + // FormatJWTVC presentation exchange format. + FormatJWTVC = "jwt_vc" + // FormatJWTVP presentation exchange format. + FormatJWTVP = "jwt_vp" + // FormatLDP presentation exchange format. + FormatLDP = "ldp" + // FormatLDPVC presentation exchange format. + FormatLDPVC = "ldp_vc" + // FormatLDPVP presentation exchange format. + FormatLDPVP = "ldp_vp" ) var errPathNotApplicable = errors.New("path not applicable") @@ -327,12 +341,12 @@ func (pd *PresentationDefinition) CreateVP(credentials []*verifiable.Credential, return nil, err } - result, err := pd.applyRequirement(req, credentials, documentLoader, opts...) + format, result, err := pd.applyRequirement(req, credentials, documentLoader, opts...) if err != nil { return nil, err } - applicableCredentials, descriptors := merge(result) + applicableCredentials, descriptors := merge(format, result) vp, err := verifiable.NewPresentation(verifiable.WithCredentials(applicableCredentials...)) if err != nil { @@ -358,8 +372,13 @@ var ErrNoCredentials = errors.New("credentials do not satisfy requirements") // nolint: gocyclo,funlen,gocognit func (pd *PresentationDefinition) applyRequirement(req *requirement, creds []*verifiable.Credential, - documentLoader ld.DocumentLoader, opts ...verifiable.CredentialOpt) (map[string][]*verifiable.Credential, error) { + documentLoader ld.DocumentLoader, + opts ...verifiable.CredentialOpt) (string, map[string][]*verifiable.Credential, error) { result := make(map[string][]*verifiable.Credential) + // assume LDPVP format if pd.Format is not set. + // Usually pd.Format will be set when creds include a non-empty Proofs field since they represent the designated + // format. + vpFormat := FormatLDPVP for _, descriptor := range req.InputDescriptors { format := pd.Format @@ -371,11 +390,11 @@ func (pd *PresentationDefinition) applyRequirement(req *requirement, creds []*ve filtered, err := frameCreds(pd.Frame, filtered, opts...) if err != nil { - return nil, err + return "", nil, err } if format != nil { - filtered = filterFormat(format, filtered) + vpFormat, filtered = filterFormat(format, filtered) } // Validate schema only for v1 @@ -385,7 +404,7 @@ func (pd *PresentationDefinition) applyRequirement(req *requirement, creds []*ve filtered, err = filterConstraints(descriptor.Constraints, filtered, opts...) if err != nil { - return nil, err + return "", nil, err } if len(filtered) != 0 { @@ -395,10 +414,10 @@ func (pd *PresentationDefinition) applyRequirement(req *requirement, creds []*ve if len(req.InputDescriptors) != 0 { if req.isLenApplicable(len(result)) { - return result, nil + return vpFormat, result, nil } - return nil, ErrNoCredentials + return "", nil, ErrNoCredentials } var nestedResult []map[string][]*verifiable.Credential @@ -407,13 +426,13 @@ func (pd *PresentationDefinition) applyRequirement(req *requirement, creds []*ve set := map[string]map[string]string{} for _, r := range req.Nested { - res, err := pd.applyRequirement(r, creds, documentLoader, opts...) + vpFmt, res, err := pd.applyRequirement(r, creds, documentLoader, opts...) if errors.Is(err, ErrNoCredentials) { continue } if err != nil { - return nil, err + return "", nil, err } for desc, credentials := range res { @@ -428,6 +447,7 @@ func (pd *PresentationDefinition) applyRequirement(req *requirement, creds []*ve if len(res) != 0 { nestedResult = append(nestedResult, res) + vpFormat = vpFmt } } @@ -441,7 +461,7 @@ func (pd *PresentationDefinition) applyRequirement(req *requirement, creds []*ve } } - return mergeNestedResult(nestedResult, exclude), nil + return vpFormat, mergeNestedResult(nestedResult, exclude), nil } func mergeNestedResult(nr []map[string][]*verifiable.Credential, @@ -914,7 +934,7 @@ func getPath(keys []interface{}, set map[string]int) [2]string { return [...]string{strings.Join(newPath, "."), strings.Join(originalPath, ".")} } -func merge(setOfCredentials map[string][]*verifiable.Credential) ([]*verifiable.Credential, []*InputDescriptorMapping) { +func merge(format string, setOfCredentials map[string][]*verifiable.Credential) ([]*verifiable.Credential, []*InputDescriptorMapping) { //nolint:lll setOfCreds := make(map[string]int) setOfDescriptors := make(map[string]struct{}) @@ -942,9 +962,8 @@ func merge(setOfCredentials map[string][]*verifiable.Credential) ([]*verifiable. if _, ok := setOfDescriptors[fmt.Sprintf("%s-%s", credential.ID, credential.ID)]; !ok { descriptors = append(descriptors, &InputDescriptorMapping{ - ID: descriptorID, - // TODO: what format should be here? - Format: "ldp_vp", + ID: descriptorID, + Format: format, Path: fmt.Sprintf("$.verifiableCredential[%d]", setOfCreds[credential.ID]), }) } @@ -962,22 +981,105 @@ func (a byID) Len() int { return len(a) } func (a byID) Less(i, j int) bool { return a[i].ID < a[j].ID } func (a byID) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func filterFormat(format *Format, credentials []*verifiable.Credential) []*verifiable.Credential { - var result []*verifiable.Credential - - if format.LdpVP == nil { - return result - } +//nolint:funlen,gocyclo +func filterFormat(format *Format, credentials []*verifiable.Credential) (string, []*verifiable.Credential) { + var ldpCreds, ldpvcCreds, ldpvpCreds, jwtCreds, jwtvcCreds, jwtvpCreds []*verifiable.Credential for _, credential := range credentials { - for _, proofType := range format.LdpVP.ProofType { - if hasProofWithType(credential, proofType) { - result = append(result, credential) + if credByProof(credential, format.Ldp) { + ldpCreds = append(ldpCreds, credential) + } + + if credByProof(credential, format.LdpVC) { + ldpvcCreds = append(ldpvcCreds, credential) + } + + if credByProof(credential, format.LdpVP) { + ldpvpCreds = append(ldpvpCreds, credential) + } + + var ( + alg string + hasAlg bool + ) + + if credential.JWT != "" { + pJWT, err := jwt.Parse(credential.JWT) + if err != nil { + logger.Warnf("unmarshal credential error: %w", err) + + continue } + + alg, hasAlg = pJWT.Headers.Algorithm() + } + + if hasAlg && algMatch(alg, format.Jwt) { + jwtCreds = append(jwtCreds, credential) + } + + if hasAlg && algMatch(alg, format.JwtVC) { + jwtvcCreds = append(jwtvcCreds, credential) + } + + if hasAlg && algMatch(alg, format.JwtVP) { + jwtvpCreds = append(jwtvpCreds, credential) } } - return result + if len(ldpCreds) > 0 { + return FormatLDP, ldpCreds + } + + if len(ldpvcCreds) > 0 { + return FormatLDPVC, ldpvcCreds + } + + if len(ldpvpCreds) > 0 { + return FormatLDPVP, ldpvpCreds + } + + if len(jwtCreds) > 0 { + return FormatJWT, jwtCreds + } + + if len(jwtvcCreds) > 0 { + return FormatJWTVC, jwtvcCreds + } + + if len(jwtvpCreds) > 0 { + return FormatJWTVP, jwtvpCreds + } + + return "", nil +} + +func algMatch(credAlg string, jwtType *JwtType) bool { + if jwtType == nil { + return false + } + + for _, b := range jwtType.Alg { + if strings.EqualFold(credAlg, b) { + return true + } + } + + return false +} + +func credByProof(c *verifiable.Credential, ldp *LdpType) bool { + if ldp == nil { + return false + } + + for _, proofType := range ldp.ProofType { + if hasProofWithType(c, proofType) { + return true + } + } + + return false } // nolint: gocyclo diff --git a/pkg/doc/presexch/definition_test.go b/pkg/doc/presexch/definition_test.go index f2099681a9..77438da9c4 100644 --- a/pkg/doc/presexch/definition_test.go +++ b/pkg/doc/presexch/definition_test.go @@ -81,140 +81,179 @@ func TestPresentationDefinition_CreateVP(t *testing.T) { t.Run("Checks submission requirements", func(t *testing.T) { issuerID := uuid.New().String() - pd := &PresentationDefinition{ - ID: uuid.New().String(), - SubmissionRequirements: []*SubmissionRequirement{ - { - Rule: "all", - From: "A", + tests := []struct { + name string + format string + vFormat *Format + }{ + { + name: "test LDP format", + format: FormatLDP, + vFormat: &Format{ + Ldp: &LdpType{ProofType: []string{"JsonWebSignature2020"}}, }, - { - Rule: "pick", - Count: 1, - FromNested: []*SubmissionRequirement{ - { - Rule: "all", - From: "teenager", - }, + }, + { + name: "test LDPVP format", + format: FormatLDPVP, + vFormat: &Format{ + LdpVP: &LdpType{ProofType: []string{"JsonWebSignature2020"}}, + }, + }, + { + name: "test LDPVC format", + format: FormatLDPVC, + vFormat: &Format{ + LdpVC: &LdpType{ProofType: []string{"JsonWebSignature2020"}}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + pd := &PresentationDefinition{ + ID: uuid.New().String(), + SubmissionRequirements: []*SubmissionRequirement{ { Rule: "all", - From: "child", + From: "A", }, { - Rule: "pick", - From: "adult", - Min: 2, + Rule: "pick", + Count: 1, + FromNested: []*SubmissionRequirement{ + { + Rule: "all", + From: "teenager", + }, + { + Rule: "all", + From: "child", + }, + { + Rule: "pick", + From: "adult", + Min: 2, + }, + }, }, }, - }, - }, - InputDescriptors: []*InputDescriptor{{ - ID: uuid.New().String(), - Group: []string{"A"}, - Schema: []*Schema{{ - URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), - }}, - Constraints: &Constraints{ - SubjectIsIssuer: &subIsIssuerRequired, - Fields: []*Field{{ - Path: []string{"$.first_name", "$.last_name"}, - }}, - }, - }, { - ID: uuid.New().String(), - Group: []string{"child"}, - Schema: []*Schema{{ - URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), - }}, - Constraints: &Constraints{ - SubjectIsIssuer: &subIsIssuerRequired, - Fields: []*Field{{ - Path: []string{"$.age"}, - Filter: &Filter{ - Type: &intFilterType, - Minimum: 3, - Maximum: 12, + InputDescriptors: []*InputDescriptor{{ + ID: uuid.New().String(), + Group: []string{"A"}, + Schema: []*Schema{{ + URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), + }}, + Constraints: &Constraints{ + SubjectIsIssuer: &subIsIssuerRequired, + Fields: []*Field{{ + Path: []string{"$.first_name", "$.last_name"}, + }}, }, - }}, - }, - }, { - ID: uuid.New().String(), - Group: []string{"teenager"}, - Schema: []*Schema{{ - URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), - }}, - Constraints: &Constraints{ - SubjectIsIssuer: &subIsIssuerRequired, - Fields: []*Field{{ - Path: []string{"$.age"}, - Filter: &Filter{ - Type: &intFilterType, - Minimum: 13, - Maximum: 17, + }, { + ID: uuid.New().String(), + Group: []string{"child"}, + Schema: []*Schema{{ + URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), + }}, + Constraints: &Constraints{ + SubjectIsIssuer: &subIsIssuerRequired, + Fields: []*Field{{ + Path: []string{"$.age"}, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 3, + Maximum: 12, + }, + }}, }, - }}, - }, - }, { - ID: uuid.New().String(), - Group: []string{"adult"}, - Schema: []*Schema{{ - URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), - }}, - Constraints: &Constraints{ - SubjectIsIssuer: &subIsIssuerRequired, - Fields: []*Field{{ - Path: []string{"$.age"}, - Filter: &Filter{ - Type: &intFilterType, - Minimum: 18, - Maximum: 23, + }, { + ID: uuid.New().String(), + Group: []string{"teenager"}, + Schema: []*Schema{{ + URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), + }}, + Constraints: &Constraints{ + SubjectIsIssuer: &subIsIssuerRequired, + Fields: []*Field{{ + Path: []string{"$.age"}, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 13, + Maximum: 17, + }, + }}, + }, + }, { + ID: uuid.New().String(), + Group: []string{"adult"}, + Schema: []*Schema{{ + URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), + }}, + Constraints: &Constraints{ + SubjectIsIssuer: &subIsIssuerRequired, + Fields: []*Field{{ + Path: []string{"$.age"}, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 18, + Maximum: 23, + }, + }}, }, }}, - }, - }}, - } - - vp, err := pd.CreateVP([]*verifiable.Credential{ - { - Context: []string{verifiable.ContextURI}, - Types: []string{verifiable.VCType}, - ID: uuid.New().String(), - CustomFields: map[string]interface{}{ - "first_name": "Jesse", - }, - }, - { - Context: []string{verifiable.ContextURI}, - Types: []string{verifiable.VCType}, - ID: uuid.New().String(), - Subject: []verifiable.Subject{{ID: issuerID}}, - Issuer: verifiable.Issuer{ID: issuerID}, - CustomFields: map[string]interface{}{ - "first_name": "Jesse", - "last_name": "Travis", - "age": 17, - }, - }, - { - Context: []string{verifiable.ContextURI}, - Types: []string{verifiable.VCType}, - ID: uuid.New().String(), - Subject: []verifiable.Subject{{ID: issuerID}}, - Issuer: verifiable.Issuer{ID: issuerID}, - CustomFields: map[string]interface{}{ - "first_name": "Jesse", - "last_name": "Travis", - "age": 2, - }, - }, - }, lddl) + Format: tc.vFormat, + } + + vp, err := pd.CreateVP([]*verifiable.Credential{ + { + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + ID: uuid.New().String(), + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + }, + // since Format in InputDescriptor works only with proofs, need to add it in the vc. + Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, + }, + { + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + ID: uuid.New().String(), + Subject: []verifiable.Subject{{ID: issuerID}}, + Issuer: verifiable.Issuer{ID: issuerID}, + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Travis", + "age": 17, + }, + // since Format in InputDescriptor works only with proofs, need to add it in the vc. + Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, + }, + { + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + ID: uuid.New().String(), + Subject: []verifiable.Subject{{ID: issuerID}}, + Issuer: verifiable.Issuer{ID: issuerID}, + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Travis", + "age": 2, + }, + // since Format in InputDescriptor works only with proofs, need to add it in the vc. + Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, + }, + }, lddl) - require.NoError(t, err) - require.NotNil(t, vp) - require.Equal(t, 1, len(vp.Credentials())) + require.NoError(t, err) + require.NotNil(t, vp) + require.Equal(t, 1, len(vp.Credentials())) - checkSubmission(t, vp, pd) - checkVP(t, vp) + checkSubmission(t, vp, pd) + checkVP(t, vp) + }) + } }) t.Run("Checks submission requirements (no descriptor)", func(t *testing.T) { diff --git a/pkg/doc/presexch/example_v1_test.go b/pkg/doc/presexch/example_v1_test.go index cde04f782f..44043f4f83 100644 --- a/pkg/doc/presexch/example_v1_test.go +++ b/pkg/doc/presexch/example_v1_test.go @@ -121,6 +121,212 @@ func ExamplePresentationDefinition_CreateVP_v1() { //} } +func ExamplePresentationDefinition_CreateVP_v1_With_LDP_FormatAndProof() { + required := Required + + pd := &PresentationDefinition{ + ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + Purpose: "To sell you a drink we need to know that you are an adult.", + InputDescriptors: []*InputDescriptor{{ + ID: "age_descriptor", + Purpose: "Your age should be greater or equal to 18.", + Schema: []*Schema{{ + URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), + }}, + Constraints: &Constraints{ + LimitDisclosure: &required, + Fields: []*Field{{ + Path: []string{"$.age"}, + Predicate: &required, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 18, + }, + }}, + }, + }}, + Format: &Format{ + Ldp: &LdpType{ProofType: []string{"JsonWebSignature2020"}}, + }, + } + + loader, err := ldtestutil.DocumentLoader() + if err != nil { + panic(err) + } + + vp, err := pd.CreateVP([]*verifiable.Credential{ + { + ID: "http://example.edu/credentials/777", + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + Issuer: verifiable.Issuer{ + ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", + }, + Issued: &util.TimeWrapper{ + Time: time.Time{}, + }, + Subject: "did:example:76e12ec712ebc6f1c221ebfeb1f", + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Pinkman", + "age": 21, + }, + Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, + }, + }, loader, verifiable.WithJSONLDDocumentLoader(loader)) + if err != nil { + panic(err) + } + + vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy + + vpBytes, err := json.MarshalIndent(vp, "", "\t") + if err != nil { + panic(err) + } + + fmt.Println(string(vpBytes)) + // Output: + //{ + // "@context": [ + // "https://www.w3.org/2018/credentials/v1", + // "https://identity.foundation/presentation-exchange/submission/v1" + // ], + // "presentation_submission": { + // "id": "DUMMY", + // "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + // "descriptor_map": [ + // { + // "id": "age_descriptor", + // "format": "ldp", + // "path": "$.verifiableCredential[0]" + // } + // ] + // }, + // "type": [ + // "VerifiablePresentation", + // "PresentationSubmission" + // ], + // "verifiableCredential": [ + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "age": true, + // "credentialSubject": "did:example:76e12ec712ebc6f1c221ebfeb1f", + // "id": "http://example.edu/credentials/777", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f", + // "type": "VerifiableCredential" + // } + // ] + //} +} + +func ExamplePresentationDefinition_CreateVP_v1_With_LDPVC_FormatAndProof() { + required := Required + + pd := &PresentationDefinition{ + ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + Purpose: "To sell you a drink we need to know that you are an adult.", + InputDescriptors: []*InputDescriptor{{ + ID: "age_descriptor", + Purpose: "Your age should be greater or equal to 18.", + Schema: []*Schema{{ + URI: fmt.Sprintf("%s#%s", verifiable.ContextID, verifiable.VCType), + }}, + Constraints: &Constraints{ + LimitDisclosure: &required, + Fields: []*Field{{ + Path: []string{"$.age"}, + Predicate: &required, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 18, + }, + }}, + }, + }}, + Format: &Format{ + LdpVC: &LdpType{ProofType: []string{"JsonWebSignature2020"}}, + }, + } + + loader, err := ldtestutil.DocumentLoader() + if err != nil { + panic(err) + } + + vp, err := pd.CreateVP([]*verifiable.Credential{ + { + ID: "http://example.edu/credentials/777", + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + Issuer: verifiable.Issuer{ + ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", + }, + Issued: &util.TimeWrapper{ + Time: time.Time{}, + }, + Subject: "did:example:76e12ec712ebc6f1c221ebfeb1f", + CustomFields: map[string]interface{}{ + "first_name": "Jesse", + "last_name": "Pinkman", + "age": 21, + }, + Proofs: []verifiable.Proof{{"type": "JsonWebSignature2020"}}, + }, + }, loader, verifiable.WithJSONLDDocumentLoader(loader)) + if err != nil { + panic(err) + } + + vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy + + vpBytes, err := json.MarshalIndent(vp, "", "\t") + if err != nil { + panic(err) + } + + fmt.Println(string(vpBytes)) + // Output: + //{ + // "@context": [ + // "https://www.w3.org/2018/credentials/v1", + // "https://identity.foundation/presentation-exchange/submission/v1" + // ], + // "presentation_submission": { + // "id": "DUMMY", + // "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + // "descriptor_map": [ + // { + // "id": "age_descriptor", + // "format": "ldp_vc", + // "path": "$.verifiableCredential[0]" + // } + // ] + // }, + // "type": [ + // "VerifiablePresentation", + // "PresentationSubmission" + // ], + // "verifiableCredential": [ + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "age": true, + // "credentialSubject": "did:example:76e12ec712ebc6f1c221ebfeb1f", + // "id": "http://example.edu/credentials/777", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f", + // "type": "VerifiableCredential" + // } + // ] + //} +} + func ExamplePresentationDefinition_CreateVP_multipleMatches() { pd := &PresentationDefinition{ ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", diff --git a/pkg/doc/presexch/example_v2_test.go b/pkg/doc/presexch/example_v2_test.go index 138e417b59..0d94f6a427 100644 --- a/pkg/doc/presexch/example_v2_test.go +++ b/pkg/doc/presexch/example_v2_test.go @@ -141,14 +141,14 @@ func ExamplePresentationDefinition_CreateVP_v2() { //} } -func ExamplePresentationDefinition_CreateVP_withFormat() { +func ExamplePresentationDefinition_CreateVP_with_LdpVC_Format() { required := Required pd := &PresentationDefinition{ ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", Purpose: "To sell you a drink we need to know that you are an adult.", Format: &Format{ - LdpVP: &LdpType{ + LdpVC: &LdpType{ ProofType: []string{"Ed25519Signature2018"}, }, }, @@ -234,7 +234,129 @@ func ExamplePresentationDefinition_CreateVP_withFormat() { // "descriptor_map": [ // { // "id": "age_descriptor", - // "format": "ldp_vp", + // "format": "ldp_vc", + // "path": "$.verifiableCredential[0]" + // } + // ] + // }, + // "type": [ + // "VerifiablePresentation", + // "PresentationSubmission" + // ], + // "verifiableCredential": [ + // { + // "@context": [ + // "https://www.w3.org/2018/credentials/v1" + // ], + // "credentialSubject": { + // "age": true, + // "first_name": "Jesse", + // "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + // "last_name": "Pinkman" + // }, + // "id": "http://example.edu/credentials/777", + // "issuanceDate": "0001-01-01T00:00:00Z", + // "issuer": "did:example:76e12ec712ebc6f1c221ebfeb1f", + // "type": "VerifiableCredential" + // } + // ] + //} +} + +func ExamplePresentationDefinition_CreateVP_with_Ldp_Format() { + required := Required + + pd := &PresentationDefinition{ + ID: "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + Purpose: "To sell you a drink we need to know that you are an adult.", + Format: &Format{ + Ldp: &LdpType{ + ProofType: []string{"Ed25519Signature2018"}, + }, + }, + InputDescriptors: []*InputDescriptor{{ + ID: "age_descriptor", + Purpose: "Your age should be greater or equal to 18.", + Constraints: &Constraints{ + LimitDisclosure: &required, + Fields: []*Field{ + { + Path: []string{"$.credentialSubject.age", "$.vc.credentialSubject.age", "$.age"}, + Predicate: &required, + Filter: &Filter{ + Type: &intFilterType, + Minimum: 18, + }, + }, + { + Path: []string{"$.credentialSchema[0].id", "$.credentialSchema.id", "$.vc.credentialSchema.id"}, + Filter: &Filter{ + Type: &strFilterType, + Const: "hub://did:foo:123/Collections/schema.us.gov/passport.json", + }, + }, + }, + }, + }}, + } + + loader, err := ldtestutil.DocumentLoader() + if err != nil { + panic(err) + } + + vp, err := pd.CreateVP([]*verifiable.Credential{ + { + ID: "http://example.edu/credentials/777", + Context: []string{verifiable.ContextURI}, + Types: []string{verifiable.VCType}, + Issuer: verifiable.Issuer{ + ID: "did:example:76e12ec712ebc6f1c221ebfeb1f", + }, + Issued: &util.TimeWrapper{ + Time: time.Time{}, + }, + Schemas: []verifiable.TypedID{{ + ID: "hub://did:foo:123/Collections/schema.us.gov/passport.json", + Type: "JsonSchemaValidator2018", + }}, + + Subject: map[string]interface{}{ + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "first_name": "Jesse", + "last_name": "Pinkman", + "age": 21, + }, + Proofs: []verifiable.Proof{ + {"type": "Ed25519Signature2018"}, + }, + }, + }, loader, verifiable.WithJSONLDDocumentLoader(loader)) + if err != nil { + panic(err) + } + + vp.CustomFields["presentation_submission"].(*PresentationSubmission).ID = dummy + + vpBytes, err := json.MarshalIndent(vp, "", "\t") + if err != nil { + panic(err) + } + + fmt.Println(string(vpBytes)) + // Output: + //{ + // "@context": [ + // "https://www.w3.org/2018/credentials/v1", + // "https://identity.foundation/presentation-exchange/submission/v1" + // ], + // "presentation_submission": { + // "id": "DUMMY", + // "definition_id": "c1b88ce1-8460-4baf-8f16-4759a2f055fd", + // "descriptor_map": [ + // { + // "id": "age_descriptor", + // "format": "ldp", // "path": "$.verifiableCredential[0]" // } // ] diff --git a/pkg/doc/verifiable/credential.go b/pkg/doc/verifiable/credential.go index 8c9361133d..243b47311d 100644 --- a/pkg/doc/verifiable/credential.go +++ b/pkg/doc/verifiable/credential.go @@ -504,6 +504,7 @@ type Credential struct { Issued *util.TimeWrapper Expired *util.TimeWrapper Proofs []Proof + JWT string Status *TypedID Schemas []TypedID Evidence Evidence @@ -522,6 +523,7 @@ type rawCredential struct { Issued *util.TimeWrapper `json:"issuanceDate,omitempty"` Expired *util.TimeWrapper `json:"expirationDate,omitempty"` Proof json.RawMessage `json:"proof,omitempty"` + JWT string `json:"jwt,omitempty"` Status *TypedID `json:"credentialStatus,omitempty"` Issuer json.RawMessage `json:"issuer,omitempty"` Schema interface{} `json:"credentialSchema,omitempty"`