Skip to content

Commit

Permalink
PEX: Changed path_nested to be object instead of slice (#2609)
Browse files Browse the repository at this point in the history
  • Loading branch information
reinkrul authored Nov 20, 2023
1 parent ab2042e commit 644aa5b
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 43 deletions.
60 changes: 37 additions & 23 deletions vcr/pe/presentation_submission.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ type SignInstruction struct {
// Holder contains the DID of the holder that should sign the VP.
Holder did.DID
// VerifiableCredentials contains the VCs that should be included in the VP.
VerifiableCredentials []vc.VerifiableCredential
inputDescriptorMappingObjects []InputDescriptorMappingObject
VerifiableCredentials []vc.VerifiableCredential
// Mappings contains the Input Descriptor that are mapped by this SignInstruction.
Mappings []InputDescriptorMappingObject
}

// Empty returns true if there are no VCs in the SignInstruction.
Expand Down Expand Up @@ -100,7 +101,6 @@ func (b *PresentationSubmissionBuilder) Build(format string) (PresentationSubmis
Id: uuid.New().String(),
DefinitionId: b.presentationDefinition.Id,
}
signInstructions := make([]SignInstruction, len(b.wallets))

// first we need to select the VCs from all wallets that match the presentation definition
allVCs := make([]vc.VerifiableCredential, 0)
Expand All @@ -115,43 +115,57 @@ func (b *PresentationSubmissionBuilder) Build(format string) (PresentationSubmis

// next we need to map the selected VCs to the correct wallet
// loop over all selected VCs and find the wallet that contains the VC
signInstructions := make([]SignInstruction, len(b.wallets))
walletCredentialIndex := map[did.DID]int{}
for j := range selectedVCs {
for i, walletVCs := range b.wallets {
var index int
for _, walletVC := range walletVCs {
// do a JSON equality check
if vcEqual(selectedVCs[j], walletVC) {
if selectedVCs[j].Raw() == walletVC.Raw() {
signInstructions[i].Holder = b.holders[i]
signInstructions[i].VerifiableCredentials = append(signInstructions[i].VerifiableCredentials, selectedVCs[j])
// remap the path to the correct wallet index
inputDescriptorMappingObjectForVC := inputDescriptorMappingObjects[j]
inputDescriptorMappingObjectForVC.Path = fmt.Sprintf("$.verifiableCredential[%d]", index)
signInstructions[i].inputDescriptorMappingObjects = append(signInstructions[i].inputDescriptorMappingObjects, inputDescriptorMappingObjectForVC)
index++
mapping := inputDescriptorMappingObjects[j]
mapping.Format = selectedVCs[j].Format()
mapping.Path = fmt.Sprintf("$.verifiableCredential[%d]", walletCredentialIndex[b.holders[i]])
signInstructions[i].Mappings = append(signInstructions[i].Mappings, mapping)
walletCredentialIndex[b.holders[i]]++
}
}
}
}

// filter out empty sign instructions
nonEmptySignInstructions := make([]SignInstruction, 0)
for _, signInstruction := range signInstructions {
if !signInstruction.Empty() {
nonEmptySignInstructions = append(nonEmptySignInstructions, signInstruction)
}
}

index := 0
// last we create the descriptor map for the presentation submission
// If there's only one sign instruction the Path will be $. If there are multiple sign instructions the Path will be $[0], $[1], etc.
for _, signInstruction := range signInstructions {
if len(signInstruction.VerifiableCredentials) > 0 {
// wrap each InputDescriptorMappingObject for the outer VP
nestedDescriptorMap := InputDescriptorMappingObject{
Id: "", // todo what to add here?
Format: format,
Path: "$.",
PathNested: signInstruction.inputDescriptorMappingObjects,
}
if len(signInstructions) > 1 {
nestedDescriptorMap.Path = fmt.Sprintf("$[%d]", index)
// If there's only one sign instruction the Path will be $.
// If there are multiple sign instructions (each yielding a VP) the Path will be $[0], $[1], etc.
for _, signInstruction := range nonEmptySignInstructions {
if len(signInstruction.Mappings) > 0 {
for _, inputDescriptorMapping := range signInstruction.Mappings {
// If we have multiple VPs in the resulting submission, wrap each in a nested descriptor map (see path_nested in PEX specification).
if len(nonEmptySignInstructions) > 1 {
presentationSubmission.DescriptorMap = append(presentationSubmission.DescriptorMap, InputDescriptorMappingObject{
Id: inputDescriptorMapping.Id,
Format: format,
Path: fmt.Sprintf("$[%d]", index),
PathNested: &inputDescriptorMapping,
})
} else {
// Just 1 VP, no nesting needed
presentationSubmission.DescriptorMap = append(presentationSubmission.DescriptorMap, inputDescriptorMapping)
}
}
presentationSubmission.DescriptorMap = append(presentationSubmission.DescriptorMap, nestedDescriptorMap)
index++
}
}

return presentationSubmission, signInstructions, nil
return presentationSubmission, nonEmptySignInstructions, nil
}
124 changes: 110 additions & 14 deletions vcr/pe/presentation_submission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,29 @@ func TestPresentationSubmissionBuilder_Build(t *testing.T) {
holder2 := did.MustParseDID("did:example:2")
id1 := ssi.MustParseURI("1")
id2 := ssi.MustParseURI("2")
vc1 := vc.VerifiableCredential{ID: &id1}
vc2 := vc.VerifiableCredential{ID: &id2}
id3 := ssi.MustParseURI("3")
vc1 := credentialToJSONLD(vc.VerifiableCredential{ID: &id1})
vc2 := credentialToJSONLD(vc.VerifiableCredential{ID: &id2})
vc3 := credentialToJSONLD(vc.VerifiableCredential{ID: &id3})

t.Run("ok - single wallet", func(t *testing.T) {
t.Run("1 presentation", func(t *testing.T) {
expectedJSON := `
{
"id": "for-test",
"definition_id": "",
"descriptor_map": [
{
"format": "ldp_vc",
"id": "Match ID=1",
"path": "$.verifiableCredential[0]"
},
{
"format": "ldp_vc",
"id": "Match ID=2",
"path": "$.verifiableCredential[1]"
}
]
}`
presentationDefinition := PresentationDefinition{}
_ = json.Unmarshal([]byte(test.All), &presentationDefinition)
builder := presentationDefinition.PresentationSubmissionBuilder()
Expand All @@ -60,12 +79,42 @@ func TestPresentationSubmissionBuilder_Build(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, signInstructions)
assert.Len(t, signInstructions, 1)
assert.Len(t, submission.DescriptorMap, 1)
assert.Equal(t, "$.", submission.DescriptorMap[0].Path)
require.Len(t, submission.DescriptorMap[0].PathNested, 2)
assert.Equal(t, "$.verifiableCredential[0]", submission.DescriptorMap[0].PathNested[0].Path)
require.Len(t, submission.DescriptorMap, 2)

submission.Id = "for-test" // easier assertion
actualJSON, _ := json.MarshalIndent(submission, "", " ")
println(string(actualJSON))
assert.JSONEq(t, expectedJSON, string(actualJSON))
})
t.Run("ok - two wallets", func(t *testing.T) {
t.Run("2 presentations", func(t *testing.T) {
expectedJSON := `
{
"id": "for-test",
"definition_id": "",
"descriptor_map": [
{
"format": "ldp_vp",
"id": "Match ID=1",
"path": "$[0]",
"path_nested": {
"format": "ldp_vc",
"id": "Match ID=1",
"path": "$.verifiableCredential[0]"
}
},
{
"format": "ldp_vp",
"id": "Match ID=2",
"path": "$[1]",
"path_nested": {
"format": "ldp_vc",
"id": "Match ID=2",
"path": "$.verifiableCredential[0]"
}
}
]
}
`
presentationDefinition := PresentationDefinition{}
_ = json.Unmarshal([]byte(test.All), &presentationDefinition)
builder := presentationDefinition.PresentationSubmissionBuilder()
Expand All @@ -78,11 +127,58 @@ func TestPresentationSubmissionBuilder_Build(t *testing.T) {
require.NotNil(t, signInstructions)
assert.Len(t, signInstructions, 2)
assert.Len(t, submission.DescriptorMap, 2)
assert.Equal(t, "$[0]", submission.DescriptorMap[0].Path)
require.Len(t, submission.DescriptorMap[0].PathNested, 1)
assert.Equal(t, "$.verifiableCredential[0]", submission.DescriptorMap[0].PathNested[0].Path)
assert.Equal(t, "$[1]", submission.DescriptorMap[1].Path)
require.Len(t, submission.DescriptorMap[1].PathNested, 1)
assert.Equal(t, "$.verifiableCredential[0]", submission.DescriptorMap[1].PathNested[0].Path)

submission.Id = "for-test" // easier assertion
actualJSON, _ := json.MarshalIndent(submission, "", " ")
assert.JSONEq(t, expectedJSON, string(actualJSON))
})
t.Run("2 wallets, but 1 VP", func(t *testing.T) {
expectedJSON := `
{
"id": "for-test",
"definition_id": "",
"descriptor_map": [
{
"format": "ldp_vc",
"id": "Match ID=1",
"path": "$.verifiableCredential[0]"
},
{
"format": "ldp_vc",
"id": "Match ID=2",
"path": "$.verifiableCredential[1]"
}
]
}`
presentationDefinition := PresentationDefinition{}
_ = json.Unmarshal([]byte(test.All), &presentationDefinition)
builder := presentationDefinition.PresentationSubmissionBuilder()
builder.AddWallet(holder1, []vc.VerifiableCredential{vc1, vc2})
builder.AddWallet(holder2, []vc.VerifiableCredential{vc3})

submission, signInstructions, err := builder.Build("ldp_vp")

require.NoError(t, err)
require.NotNil(t, signInstructions)
assert.Len(t, signInstructions, 1)
assert.Len(t, submission.DescriptorMap, 2)

submission.Id = "for-test" // easier assertion
actualJSON, _ := json.MarshalIndent(submission, "", " ")
println(string(actualJSON))
assert.JSONEq(t, expectedJSON, string(actualJSON))
})
}

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
}
4 changes: 2 additions & 2 deletions vcr/pe/test/test_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ const All = `
],
"input_descriptors": [
{
"name": "Match ID=1",
"id": "Match ID=1",
"group": ["A"],
"constraints": {
"fields": [
Expand All @@ -200,7 +200,7 @@ const All = `
}
},
{
"name": "Match ID=2",
"id": "Match ID=2",
"group": ["A"],
"constraints": {
"fields": [
Expand Down
8 changes: 4 additions & 4 deletions vcr/pe/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ type PresentationSubmission struct {

// InputDescriptorMappingObject
type InputDescriptorMappingObject struct {
Format string `json:"format"`
Id string `json:"id"`
Path string `json:"path"`
PathNested []InputDescriptorMappingObject `json:"path_nested,omitempty"`
Format string `json:"format"`
Id string `json:"id"`
Path string `json:"path"`
PathNested *InputDescriptorMappingObject `json:"path_nested,omitempty"`
}

// Constraints
Expand Down

0 comments on commit 644aa5b

Please sign in to comment.