circlefin / verite

https://verite.id
MIT License
137 stars 59 forks source link

Stage 1: Issuance and verification library refactoring #512

Closed kimdhamilton closed 1 year ago

kimdhamilton commented 1 year ago

Goals

Status

This is part one of a major refactor to make the libraries more enjoyable to developers, supporting more composable construction and layering. There are a few intermediate patterns to support breaking down the work into slightly more bite-sized chunks, and the need for some will go away.

For the moment, a lot of unification and simplification were achieved by establishing a consistent pattern for creation and consumption of objects. This is an intermediate checkpoint.

Because these are breaking changes, the npm library will be versioned and updated accordingly with the complete batch of changes, ETA later this week

Creation

composeX = buildX + signX

The general contract I'm using is "build" constructs an object that's ready to be handed over to a did-jwt-vc library for signing. These objects include VC spec objects (VCs/VPs) as well as CM/PE objects.

Example:

So, wallet code could pass the matching credentials to the convenience wrapper as follows:

const signedSubmission = await composePresentationSubmission(
   signer,                    // does the crypto signing
   presentationDefinition,    // provided by verifier -- note that we use this in building the submission for descriptor mapping
   credentials                // credentials matching presentation definition requirements
 )

OR, call them separately like this:

// Build Presentation Submission
const presentationSubmission = await buildPresentationSubmission(
   presentationDefinition,
   credentials 
)

// Sign ...
const signedSubmission = await signPresentationSubmission(
  signer,
  presentationSubmission 
)

Clarifications:

For us, a Presentation Submission is simply a signed VP, so signPresentationSubmission simply passes through to did-jwt-vc signVP. This aliasing may be less or more confusing to people. But note that it also gives type safety and clarity, which is why I opted for it.

Also note that sign* libraries for us result in JWT-encoded objects, because we are using did-jwt-vc.

Consumption

This applies to accepting and assessing input from another party; e.g. a verifier receives a Presentation Submission and wants to:

  1. Decode the JWT (as on the sign/encoding, this ends up being folded into "verify" functions. We'll come back to this)
  2. Verify any VCs/VPs (again, a Presentation Submission is a special kind of VP for us)
  3. Validate per PE (or CM) "input evaluation"

evaluateX = decodeX + validateX

Wait, where did step 2 go? Well, again, with JWTs, 1 & 2 get folded, and so I picked the name that made the most sense when I looked at usage. This came at the expense of "hiding" that verification is also happening.

Example:

Verifier gets a signed/encoded presentation submission and calls:

    const application = await evaluatePresentationSubmission(
      presentationSubmission,  // signed input from wallet holder
      presentationDefinition   // P.D. that verifier released, to evaluate submission against
    )

Or, they can call the wrapped functions. Again, this has the advantage

// This is a VP; and VP verification is implicit to decoding
 const decodedSubmission = await decodePresentationSubmission(
    encodedSubmission
 )

 await validatePresentationSubmission(
    decodedSubmission,
    presentationDefinition
 )

Notes:

The tl;dr is that we're (in theory) making the verite libs more separable from the underlying signing/verifying libs, making it easier for consumers of verite to mix and match verite layers, PE/CM layers, and VP/VC layers. Decisions were made along the way that may be imperfect...

Remaining TODO

Addresses #346

vercel[bot] commented 1 year ago

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated
verity-demo-site ✅ Ready (Inspect) Visit Preview 💬 Add your feedback Mar 12, 2023 at 8:56PM (UTC)
verity-docs ✅ Ready (Inspect) Visit Preview 💬 Add your feedback Mar 12, 2023 at 8:56PM (UTC)
bumblefudge commented 1 year ago

This all makes sense to me, and nothing better comes to mind for evaluateX = decodeX + validateX, TBH.

Thoughts:

  1. I think that the validation-step is maybe what most verite issuers and verifiers would appreciate having hardcoded as much as possible, since they're not interested in VCs in general, just the simple-to-validate VCs of this use-case, i.e., VCs from a cacheable/rarely-expanded allowlist of issuers, typed to an cacheable/rarely-expanded allowlist of types, and structured according to a cacheable/rarely-expanded list of explicit JSONSchema, so most of the validation/filter capacities of presex are coals to newcastle here. Verite repo + docs would ideally include a sample of a presDef that hardcodes all this, so that validation will be almost pro forma since Verite will have already provided all the artefacts; implementers should probably be warned that they get creative or liberal at their own risk, as presentation ex is a pretty complex protocol.
  2. Perhaps the VC-JWT issue (decoding and verifying being kind of coupled by the underlying libraries that no one will want to swap out or re-implement unless maybe they are working in rust or wasm and prefer didkit?) creates a bit of confusion to be triaged by extra code-comments so that people realize that there is an implicit verify in the decode and an implicit encode in the sign? i.e. the naming of functions makes sense but it couldn't hurt to mitigate the asymmetry by mentioning it in the code comm at the top of each assym function
  3. Perhaps this VC-JWT coupling issue would also be slightly triaged by making a pedanticly explicit difference between credentials ("payloads") and verifiable credentials (tamper-proof artefacts that could be used in other contexts if needed?)?

i.e.

const signedSubmission = await composePresentationSubmission(
   signer,                    // does the crypto signing
   presentationDefinition,    // provided by verifier -- note that we use this in building the submission for descriptor mapping
   signedCredentials                // composed from credential payloads matching presentation definition requirements (based on appropriate schemata)
 )

 const signedCredentials = await composeVerifiableCredentials(
   signer,                    // does the crypto signing
   credentialSchema,    // validate before signing against the schema of the relevant use case, i.e. Verite schemata
   credentials                // raw credential payloads
 )

As always, all three are just suggestions, feel free to override if there are counterveiling reasons not to do it this way that i'm not seeing ut here, far from the refactoring and sausage-making. Pumped on the general direction, regardless!

kimdhamilton commented 1 year ago

@bumblefudge great suggestions, thanks! Updates coming soon