-
Notifications
You must be signed in to change notification settings - Fork 23
Description
Parent PRD
#4067 (see PRD comment)
What to build
Inject a configurable credential selection strategy into the PD matcher, so that when multiple credentials match a single input descriptor, the selection logic determines which one is used — without changing the existing Match public API or return types.
Call stack
The credential selection flows through the following call stack:
API: RequestServiceAccessToken (auth/api/iam/api.go)
→ RequestRFC021AccessToken (auth/client/iam/openid4vp.go)
→ wallet.BuildSubmission (vcr/holder/sql_wallet.go)
→ presenter.buildSubmission (vcr/holder/presenter.go)
→ PresentationSubmissionBuilder (vcr/pe/presentation_submission.go)
→ builder.Build()
→ presentationDefinition.Match() (vcr/pe/presentation_definition.go)
→ matchBasic() or matchSubmissionRequirements()
→ matchConstraints()
Design
CredentialSelector type (vcr/pe/presentation_definition.go):
A function type that picks one credential from a list of candidates matching an input descriptor:
type CredentialSelector func(descriptor InputDescriptor, candidates []vc.VerifiableCredential) (*vc.VerifiableCredential, error)FirstMatchSelector (vcr/pe/presentation_definition.go):
The default selector, extracted from the current matchConstraints logic. Picks the first matching credential, preserving existing behavior:
func FirstMatchSelector(_ InputDescriptor, candidates []vc.VerifiableCredential) (*vc.VerifiableCredential, error)MatchWithSelector (vcr/pe/presentation_definition.go):
New method on PresentationDefinition that accepts a CredentialSelector. The existing Match method delegates to MatchWithSelector with FirstMatchSelector:
func (pd PresentationDefinition) Match(vcs []vc.VerifiableCredential) ([]vc.VerifiableCredential, []InputDescriptorMappingObject, error)
func (pd PresentationDefinition) MatchWithSelector(vcs []vc.VerifiableCredential, selector CredentialSelector) ([]vc.VerifiableCredential, []InputDescriptorMappingObject, error)matchConstraints refactor (vcr/pe/presentation_definition.go):
Currently breaks on the first matching credential per input descriptor. Refactored to:
- Collect all matching VCs per input descriptor (remove the
break) - Call the
CredentialSelectorto pick one from the candidates - Return the same
Candidatestruct — no change to callers
PresentationSubmissionBuilder (vcr/pe/presentation_submission.go):
Add SetCredentialSelector method to the builder. When set, Build passes the selector through to MatchWithSelector. When not set, falls back to FirstMatchSelector:
func (b *PresentationSubmissionBuilder) SetCredentialSelector(selector CredentialSelector) *PresentationSubmissionBuilderThis allows the presenter layer (vcr/holder/presenter.go) to configure the selector when credential_query is provided, without changing the builder's existing API.
Future: DCQL selector (#4089)
The DCQL selector (implemented in #4089) will be a CredentialSelector that:
- Takes the
credential_queryarray as configuration - For a given input descriptor, checks if a
credential_querywith matching ID exists - If yes: filters candidates using
dcql.Match, errors on zero or multiple matches - If no: falls back to
FirstMatchSelector
The presenter layer will call builder.SetCredentialSelector(dcqlSelector) when credential_query is provided.
Acceptance criteria
-
CredentialSelectorfunction type defined and exported invcr/pe/presentation_definition.go -
FirstMatchSelectorextracted as an exported function — picks the first matching credential -
matchConstraintsrefactored to collect all matching VCs per input descriptor and call the selector -
MatchWithSelectormethod added toPresentationDefinition -
Matchdelegates toMatchWithSelectorwithFirstMatchSelector— no behavior change -
SetCredentialSelectormethod added toPresentationSubmissionBuilder -
Builduses the configured selector when set, falls back toFirstMatchSelector -
Candidatestruct unchanged — still holds one*vc.VerifiableCredential -
Matchpublic API return type unchanged - All existing PD matching tests pass unchanged (backward compatible)
- All existing
PresentationSubmissionBuildertests pass unchanged - New test: when multiple VCs match an input descriptor, all candidates are passed to the selector
- New test: custom selector can pick a non-first credential
Blocked by
None — can start immediately (independent of DCQL parser).
User stories addressed
- User story 11: existing behavior preserved for input descriptors without
credential_query