Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PEX: Changed path_nested to be object instead of slice #2609

Merged
merged 6 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading