Skip to content
This repository has been archived by the owner on Mar 27, 2024. It is now read-only.

Commit

Permalink
feat: PresentationDefinition API for submitting multi-presentation (#…
Browse files Browse the repository at this point in the history
…3545)

Signed-off-by: Filip Burlacu <filip.burlacu@securekey.com>
  • Loading branch information
Moopli authored Mar 6, 2023
1 parent 46985c2 commit 06f7338
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 21 deletions.
104 changes: 84 additions & 20 deletions pkg/doc/presexch/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,38 +366,90 @@ func makeRequirement(requirements []*SubmissionRequirement, descriptors []*Input
// CreateVP creates verifiable presentation.
func (pd *PresentationDefinition) CreateVP(credentials []*verifiable.Credential,
documentLoader ld.DocumentLoader, opts ...verifiable.CredentialOpt) (*verifiable.Presentation, error) {
if err := pd.ValidateSchema(); err != nil {
applicableCredentials, submission, err := presentationData(pd, credentials, documentLoader, false, opts...)
if err != nil {
return nil, err
}

req, err := makeRequirement(pd.SubmissionRequirements, pd.InputDescriptors)
vp, err := presentation(applicableCredentials...)
if err != nil {
return nil, err
}

format, result, err := pd.applyRequirement(req, credentials, documentLoader, opts...)
vp.CustomFields = verifiable.CustomFields{
submissionProperty: submission,
}

return vp, nil
}

// CreateVPArray creates a list of verifiable presentations, with one presentation for each provided credential.
// A PresentationSubmission is returned alongside, which uses the presentation list as the root for json paths.
func (pd *PresentationDefinition) CreateVPArray(
credentials []*verifiable.Credential,
documentLoader ld.DocumentLoader,
opts ...verifiable.CredentialOpt,
) ([]*verifiable.Presentation, *PresentationSubmission, error) {
applicableCredentials, submission, err := presentationData(pd, credentials, documentLoader, true, opts...)
if err != nil {
return nil, err
return nil, nil, err
}

var presentations []*verifiable.Presentation

for _, credential := range applicableCredentials {
vp, e := presentation(credential)
if e != nil {
return nil, nil, e
}

presentations = append(presentations, vp)
}

applicableCredentials, descriptors := merge(format, result)
return presentations, submission, nil
}

func presentationData(
pd *PresentationDefinition,
credentials []*verifiable.Credential,
documentLoader ld.DocumentLoader,
separatePresentations bool,
opts ...verifiable.CredentialOpt,
) ([]*verifiable.Credential, *PresentationSubmission, error) {
if err := pd.ValidateSchema(); err != nil {
return nil, nil, err
}

vp, err := verifiable.NewPresentation(verifiable.WithCredentials(applicableCredentials...))
req, err := makeRequirement(pd.SubmissionRequirements, pd.InputDescriptors)
if err != nil {
return nil, err
return nil, nil, err
}

vp.Context = append(vp.Context, PresentationSubmissionJSONLDContextIRI)
vp.Type = append(vp.Type, PresentationSubmissionJSONLDType)
format, result, err := pd.applyRequirement(req, credentials, documentLoader, opts...)
if err != nil {
return nil, nil, err
}

vp.CustomFields = verifiable.CustomFields{
submissionProperty: &PresentationSubmission{
ID: uuid.New().String(),
DefinitionID: pd.ID,
DescriptorMap: descriptors,
},
applicableCredentials, descriptors := merge(format, result, separatePresentations)

submission := &PresentationSubmission{
ID: uuid.New().String(),
DefinitionID: pd.ID,
DescriptorMap: descriptors,
}

return applicableCredentials, submission, nil
}

func presentation(credentials ...*verifiable.Credential) (*verifiable.Presentation, error) {
vp, e := verifiable.NewPresentation(verifiable.WithCredentials(credentials...))
if e != nil {
return nil, e
}

vp.Context = append(vp.Context, PresentationSubmissionJSONLDContextIRI)
vp.Type = append(vp.Type, PresentationSubmissionJSONLDType)

return vp, nil
}

Expand Down Expand Up @@ -1193,7 +1245,11 @@ func getPath(keys []interface{}, set map[string]int) [2]string {
return [...]string{strings.Join(newPath, "."), strings.Join(originalPath, ".")}
}

func merge(presentationFormat string, setOfCredentials map[string][]*verifiable.Credential) ([]*verifiable.Credential, []*InputDescriptorMapping) { //nolint:lll
func merge(
presentationFormat string,
setOfCredentials map[string][]*verifiable.Credential,
separatePresentations bool,
) ([]*verifiable.Credential, []*InputDescriptorMapping) { //nolint:lll
setOfCreds := make(map[string]int)
setOfDescriptors := make(map[string]struct{})

Expand Down Expand Up @@ -1225,16 +1281,24 @@ func merge(presentationFormat string, setOfCredentials map[string][]*verifiable.
}

if _, ok := setOfDescriptors[fmt.Sprintf("%s-%s", credential.ID, credential.ID)]; !ok {
descriptors = append(descriptors, &InputDescriptorMapping{
desc := &InputDescriptorMapping{
ID: descriptorID,
Format: presentationFormat,
Path: "$",
PathNested: &InputDescriptorMapping{
ID: descriptorID,
Format: vcFormat,
Path: fmt.Sprintf("$.verifiableCredential[%d]", setOfCreds[credential.ID]),
},
})
}

if separatePresentations {
desc.Path = fmt.Sprintf("$[%d]", setOfCreds[credential.ID])
desc.PathNested.Path = "$.verifiableCredential[0]"
} else {
desc.Path = "$"
desc.PathNested.Path = fmt.Sprintf("$.verifiableCredential[%d]", setOfCreds[credential.ID])
}

descriptors = append(descriptors, desc)
}
}
}
Expand Down
75 changes: 75 additions & 0 deletions pkg/doc/presexch/definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2017,6 +2017,50 @@ func TestPresentationDefinition_CreateVP(t *testing.T) {
})
}

func TestPresentationDefinition_CreateVPArray(t *testing.T) {
lddl := createTestJSONLDDocumentLoader(t)

t.Run("Matches two descriptors", func(t *testing.T) {
pd := &PresentationDefinition{
ID: uuid.New().String(),
InputDescriptors: []*InputDescriptor{{
ID: uuid.New().String(),
Schema: []*Schema{{
URI: "https://example.org/examples#UniversityDegreeCredential",
}},
}, {
ID: uuid.New().String(),
Schema: []*Schema{{
URI: "https://example.org/examples#DocumentVerification",
}},
}},
}

vpList, ps, err := pd.CreateVPArray([]*verifiable.Credential{
{
Context: []string{verifiable.ContextURI, "https://www.w3.org/2018/credentials/examples/v1"},
Types: []string{verifiable.VCType, "UniversityDegreeCredential"},
ID: uuid.New().String(),
},
{
Context: []string{verifiable.ContextURI, "https://trustbloc.github.io/context/vc/examples-v1.jsonld"},
Types: []string{verifiable.VCType, "DocumentVerification"},
ID: uuid.New().String(),
},
}, lddl)

require.NoError(t, err)
require.NotNil(t, vpList)
require.Len(t, vpList, 2)

checkExternalSubmission(t, vpList, ps, pd)

for _, vp := range vpList {
checkVP(t, vp)
}
})
}

func createEdDSAJWS(t *testing.T, cred *verifiable.Credential, signer verifiable.Signer,
keyID string, minimize bool) string {
t.Helper()
Expand Down Expand Up @@ -2159,6 +2203,37 @@ func checkSubmission(t *testing.T, vp *verifiable.Presentation, pd *Presentation
}
}

func checkExternalSubmission(
t *testing.T,
vpList []*verifiable.Presentation,
ps *PresentationSubmission,
pd *PresentationDefinition,
) {
t.Helper()

require.NotEmpty(t, ps.ID)
require.Equal(t, ps.DefinitionID, pd.ID)

src, err := json.Marshal(vpList)
require.NoError(t, err)

rawVPList := []interface{}{}
require.NoError(t, json.Unmarshal(src, &rawVPList))

builder := gval.Full(jsonpath.PlaceholderExtension())

for _, descriptor := range ps.DescriptorMap {
require.NotEmpty(t, descriptor.ID)
require.NotEmpty(t, descriptor.Path)
require.NotEmpty(t, descriptor.Format)

path, err := builder.NewEvaluable(descriptor.Path)
require.NoError(t, err)
_, err = path(context.TODO(), rawVPList)
require.NoError(t, err)
}
}

func checkVP(t *testing.T, vp *verifiable.Presentation) {
t.Helper()

Expand Down
10 changes: 9 additions & 1 deletion pkg/doc/verifiable/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,7 @@ type credentialOpts struct {
strictValidation bool
ldpSuites []verifier.SignatureSuite
defaultSchema string
disableValidation bool

jsonldCredentialOpts
}
Expand All @@ -600,6 +601,13 @@ func WithDisabledProofCheck() CredentialOpt {
}
}

// WithCredDisableValidation options for disabling of JSON-LD and json-schema validation.
func WithCredDisableValidation() CredentialOpt {
return func(opts *credentialOpts) {
opts.disableValidation = true
}
}

// WithSchema option to set custom schema.
func WithSchema(schema string) CredentialOpt {
return func(opts *credentialOpts) {
Expand Down Expand Up @@ -839,7 +847,7 @@ func ParseCredential(vcData []byte, opts ...CredentialOpt) (*Credential, error)
return nil, err
}

if externalJWT == "" {
if externalJWT == "" && !vcOpts.disableValidation {
// TODO: consider new validation options for, eg, jsonschema only, for JWT VC
err = validateCredential(vc, vcDataDecoded, vcOpts)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions pkg/doc/verifiable/credential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,15 @@ func TestWithDisabledProofCheck(t *testing.T) {
require.True(t, opts.disabledProofCheck)
}

func TestWithCredDisableValidation(t *testing.T) {
credentialOpt := WithCredDisableValidation()
require.NotNil(t, credentialOpt)

opts := &credentialOpts{}
credentialOpt(opts)
require.True(t, opts.disableValidation)
}

func TestWithCredentialSchemaLoader(t *testing.T) {
httpClient := &http.Client{}
jsonSchemaLoader := gojsonschema.NewStringLoader(JSONSchemaLoader())
Expand Down

0 comments on commit 06f7338

Please sign in to comment.