Skip to content

Commit

Permalink
PEX: Validate presentation submission
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul committed Nov 21, 2023
1 parent 4f75d58 commit 72a8fe8
Show file tree
Hide file tree
Showing 2 changed files with 265 additions and 0 deletions.
42 changes: 42 additions & 0 deletions vcr/pe/presentation_submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/google/uuid"
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/vcr/credential"
v2 "github.com/nuts-foundation/nuts-node/vcr/pe/schema/v2"
"strings"
)
Expand Down Expand Up @@ -254,3 +255,44 @@ func resolveCredential(path []string, mapping InputDescriptorMappingObject, valu
_ = json.Unmarshal(decodedValueJSON, &decodedValueMap)
return resolveCredential(fullPath, *mapping.PathNested, decodedValueMap)
}

// Validate validates the Presentation Submission to the Verifiable Presentations and Presentation Definition and returns the mapped credentials.
// The credentials will be returned as map with the InputDescriptor.Id as key.
// It assumes credentials of the presentations only map in 1 way to the input descriptors.
func (s PresentationSubmission) Validate(presentations []vc.VerifiablePresentation, definition PresentationDefinition) (map[string]vc.VerifiableCredential, error) {
actualCredentials, err := s.Resolve(presentations)
if err != nil {
return nil, err
}

// Create a new presentation submission and resolve the credentials using our own copy of the Presentation Definition.
// This is then used to validate the presentation submission. The Input Descriptor IDs should map to the same credentials.
submissionBuilder := definition.PresentationSubmissionBuilder()
for _, presentation := range presentations {
signer, err := credential.PresentationSigner(presentation)
if err != nil {
return nil, fmt.Errorf("unable to derive presentation signer: %w", err)
}
submissionBuilder.AddWallet(*signer, presentation.VerifiableCredential)
}
// TODO: What if there the presentations are of mixed format?
//expectedPresentationSubmission, _, err := submissionBuilder.Build(presentations[0].Format())
expectedPresentationSubmission, _, err := submissionBuilder.Build("")
if err != nil {
return nil, err
}
expectedCredenials, err := expectedPresentationSubmission.Resolve(presentations)
if err != nil {
return nil, err
}

if len(actualCredentials) != len(expectedCredenials) {
return nil, fmt.Errorf("expected %d credentials, got %d", len(expectedCredenials), len(actualCredentials))
}
for inputDescriptorID, expectedCredential := range expectedCredenials {
if actualCredentials[inputDescriptorID].Raw() != expectedCredential.Raw() {
return nil, fmt.Errorf("incorrect mapping for input descriptor : %s", inputDescriptorID)
}
}
return expectedCredenials, nil
}
223 changes: 223 additions & 0 deletions vcr/pe/presentation_submission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/nuts-foundation/go-did/did"
"github.com/nuts-foundation/go-did/vc"
"github.com/nuts-foundation/nuts-node/vcr/pe/test"
"github.com/nuts-foundation/nuts-node/vcr/signature/proof"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
Expand Down Expand Up @@ -383,3 +384,225 @@ func TestPresentationSubmission_Resolve(t *testing.T) {
assert.Nil(t, credentials)
})
}

func credentialToJSONLD(credential vc.VerifiableCredential) vc.VerifiableCredential {
bytes, err := credential.MarshalJSON()
if err != nil {
panic(err)
}
var result vc.VerifiableCredential
err = json.Unmarshal(bytes, &result)
if err != nil {
panic(err)
}
return result
}

func TestPresentationSubmission_Validate(t *testing.T) {
vcID := ssi.MustParseURI("did:example:123#first-vc")
vp := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{
{ID: &vcID},
},
Proof: []interface{}{
proof.LDProof{VerificationMethod: vcID},
},
}

t.Run("ok - 1 presentation", func(t *testing.T) {
constant := vcID.String()
definition := PresentationDefinition{
InputDescriptors: []*InputDescriptor{
{
Id: "1",
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &constant,
},
},
},
},
},
},
}
submission := PresentationSubmission{
DescriptorMap: []InputDescriptorMappingObject{
{
Id: "1",
Path: "$.verifiableCredential",
Format: "ldp_vc",
},
},
}

credentials, err := submission.Validate([]vc.VerifiablePresentation{vp}, definition)

require.NoError(t, err)
require.Len(t, credentials, 1)
assert.Equal(t, vcID.String(), credentials["1"].ID.String())
})
t.Run("ok - 2 presentations", func(t *testing.T) {
constant1 := vcID.String()
secondVCID := ssi.MustParseURI("did:example:123#second-vc")
constant2 := secondVCID.String()
secondVP := vc.VerifiablePresentation{
VerifiableCredential: []vc.VerifiableCredential{
{ID: &secondVCID},
},
Proof: []interface{}{
proof.LDProof{VerificationMethod: vcID},
},
}
definition := PresentationDefinition{
InputDescriptors: []*InputDescriptor{
{
Id: "1",
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &constant1,
},
},
},
},
},
{
Id: "2",
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &constant2,
},
},
},
},
},
},
}
submission := PresentationSubmission{
DescriptorMap: []InputDescriptorMappingObject{
{
Id: "1",
Path: "$[0]",
PathNested: &InputDescriptorMappingObject{
Id: "1",
Path: "$.verifiableCredential[0]",
Format: "ldp_vc",
},
},
{
Id: "2",
Path: "$[0]",
PathNested: &InputDescriptorMappingObject{
Id: "2",
Path: "$.verifiableCredential[0]",
Format: "ldp_vc",
},
},
},
}

credentials, err := submission.Validate([]vc.VerifiablePresentation{vp, secondVP}, definition)

require.NoError(t, err)
require.Len(t, credentials, 2)
assert.Equal(t, vcID.String(), credentials["1"].ID.String())
assert.Equal(t, secondVCID.String(), credentials["2"].ID.String())
})
t.Run("credentials don't match input descriptors", func(t *testing.T) {
constant := "incorrect ID"
definition := PresentationDefinition{
InputDescriptors: []*InputDescriptor{
{
Id: "1",
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &constant,
},
},
},
},
},
},
}

credentials, err := PresentationSubmission{}.Validate([]vc.VerifiablePresentation{vp}, definition)

assert.EqualError(t, err, "credential submission is invalid, credentials do not match the presentation definition")
assert.Empty(t, credentials)
})
t.Run("credentials match wrong input descriptors", func(t *testing.T) {
incorrectID := "incorrect ID"
correctID := vcID.String()
count := 1
definition := PresentationDefinition{
SubmissionRequirements: []*SubmissionRequirement{
{
Count: &count,
From: "any",
Rule: "pick",
},
},
InputDescriptors: []*InputDescriptor{
{
Id: "1",
Group: []string{"any"},
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &incorrectID,
},
},
},
},
},
{
Id: "2",
Group: []string{"any"},
Constraints: &Constraints{
Fields: []Field{
{
Path: []string{"$.id"},
Filter: &Filter{
Type: "string",
Const: &correctID,
},
},
},
},
},
},
}
submission := PresentationSubmission{
DescriptorMap: []InputDescriptorMappingObject{
{
Id: "1", // actually maps to input descriptor 2, so should cause an error
Path: "$.verifiableCredential[0]",
Format: "ldp_vc",
},
},
}

credentials, err := submission.Validate([]vc.VerifiablePresentation{vp}, definition)

assert.EqualError(t, err, "credential submission is invalid, input descriptor mapping looks invalid")
assert.Empty(t, credentials)
})
}

0 comments on commit 72a8fe8

Please sign in to comment.