From 9dc9315b476e63b9b1705c6523afd73b4ac99426 Mon Sep 17 00:00:00 2001 From: Patrick St-Louis Date: Tue, 11 Aug 2026 14:37:21 -0400 Subject: [PATCH 1/3] docs: add QBE matching and example-to-JSON-pointer algorithms Add normative QueryByExample credential matching (#574) and conversion of example objects to selective-disclosure JSON pointers (#545), using vc-di-ecdsa algorithm style. Fixes @context prefix matching and acceptedIssuers against the current property table. Co-authored-by: Cursor --- index.html | 391 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) diff --git a/index.html b/index.html index e002d701..73cb1fda 100644 --- a/index.html +++ b/index.html @@ -1730,6 +1730,397 @@

Query By Example

such as bbs-2023 or ecdsa-sd-2023 in the acceptedCryptosuites array.

+
+

Matching a Credential to a QueryByExample

+ +

+The following algorithm specifies how a conforming [=holder=] implementation +determines whether a [=verifiable credential=] matches the selection criteria +in a QueryByExample credentialQuery object. Required inputs are a +[=verifiable credential=] ([=map=] credential) and a +credentialQuery object ([=map=] credentialQuery). A +boolean match result is produced as output. A conforming implementation MUST +produce the same output as this algorithm. +

+ +

+Matching proceeds in two stages. First, if an example is present, +each of its properties is treated as a requirement against +credential. Second, if acceptedIssuers is present, the +credential's issuer MUST be acceptable. The credential matches only if both +stages succeed when present. +

+ +
    +
  1. +If credential is not a [=map=], return false. +
  2. +
  3. +If credentialQuery contains an example property, +initialize example to its value and perform the following steps: +
      +
    1. +If example contains an @context property, initialize +expectedContexts to its value normalized to a [=list=], and +initialize credentialContexts to the value of the +@context property of credential normalized to a +[=list=]. If the length of credentialContexts is less than the +length of expectedContexts, return false. For each +index i from 0 to the length of +expectedContexts minus one, if +expectedContexts[i] is not strictly equal to +credentialContexts[i], return false. +
    2. +
    3. +If example contains a type property, initialize +expectedTypes to its value normalized to a [=list=], and initialize +credentialTypes to the value of the type property of +credential normalized to a [=list=]. If no value in +expectedTypes is strictly equal to any value in +credentialTypes, return false. +
    4. +
    5. +Initialize remaining to a copy of example with the +@context and type properties removed, if present. If +remaining contains any properties, set match to the +result of running the algorithm in Section +[[[#matching-object-properties]]], passing remaining as +expectedObject and credential as +actualObject. If match is false, return +false. +
    6. +
    +
  4. +
  5. +If credentialQuery contains an acceptedIssuers +property, initialize acceptedIssuers to its value and perform the +following steps: +
      +
    1. +Initialize credentialIssuer to the value of the +issuer property of credential. If +credentialIssuer is a [=map=], initialize +credentialIssuer to the value of its id property. +
    2. +
    3. +Initialize issuerItems to an empty [=list=]. For each +item in acceptedIssuers, if item is a +[=list=], append each of its elements to issuerItems; otherwise, +append item to issuerItems. +
    4. +
    5. +If no item in issuerItems is acceptable for +credentialIssuer, return false. An item is +acceptable if any of the following is true: +
        +
      1. +item is a [=string=] that is strictly equal to +credentialIssuer. +
      2. +
      3. +item is a [=map=] that contains an id property whose +value is strictly equal to credentialIssuer. +
      4. +
      5. +item is a [=map=] that contains a recognizedIn +property, and credentialIssuer is recognized according to the +[[[VC-RECOG-ENT]]] specification using that property's value. +
      6. +
      +
    6. +
    +
  6. +
  7. +Return true. +
  8. +
+ +

+The reason, acceptedCryptosuites, and +acceptedEnvelopes properties of a credentialQuery +object are not used during credential matching. The reason +property is intended for display to the [=holder=] by their software. The +acceptedCryptosuites and acceptedEnvelopes properties +inform the [=holder=]'s software about which proof mechanisms and envelope +formats the [=verifier=] will accept, and are used during presentation +construction rather than credential selection. +

+ +

+This algorithm compares JSON structure and values. It does not require JSON-LD +compaction, expansion, or term rewriting. Implementations MAY apply JSON-LD +transformations before matching, provided the boolean result is the same as +this algorithm when run on the transformed inputs. +

+
+ +
+
Matching Object Properties
+ +

+The following algorithm determines whether every property of an expected object +is satisfied by an actual object. It is used recursively for nested objects +such as credentialSubject. Required inputs are an expected object +([=map=] expectedObject) and an actual object ([=map=] +actualObject). A boolean is produced as output. +

+ +
    +
  1. +For each propertyName and expectedValue pair in +expectedObject: +
      +
    1. +If actualObject does not contain a property named +propertyName, return false. +
    2. +
    3. +Initialize actualValue to the value of the +propertyName property of actualObject. +
    4. +
    5. +If expectedValue is an empty [=string=] (""), continue +to the next property. An empty string indicates that the property MUST exist, +but any value is acceptable. +
    6. +
    7. +If expectedValue is a [=map=]: +
        +
      1. +If expectedValue contains no properties, continue to the next +property. An empty map indicates that the property MUST exist, but any value is +acceptable. +
      2. +
      3. +If actualValue is a [=list=], then at least one element in +actualValue MUST be a [=map=] for which this algorithm returns +true when called with expectedValue as +expectedObject and the element as actualObject. If no +such element exists, return false. +
      4. +
      5. +Otherwise, set nestedMatch to the result of running this algorithm +with expectedValue as expectedObject and +actualValue as actualObject. If nestedMatch is +false, return false. +
      6. +
      +
    8. +
    9. +If expectedValue is a [=list=]: +
        +
      1. +If expectedValue contains no elements, continue to the next +property. An empty list indicates that the property MUST exist, but any value +is acceptable. +
      2. +
      3. +Initialize actualValues to actualValue normalized to a +[=list=] (if actualValue is not already a [=list=], wrap it in a +[=list=] containing only actualValue). +
      4. +
      5. +If no element in actualValues is strictly equal to any element in +expectedValue, return false. +
      6. +
      +
    10. +
    11. +Otherwise, expectedValue is a primitive value. Initialize +actualValues to actualValue normalized to a [=list=]. At +least one element in actualValues MUST satisfy one of the following: +
        +
      1. +The element is strictly equal to expectedValue. +
      2. +
      3. +Numeric coercion: both the element and expectedValue can be +interpreted as numeric values, and their numeric values are equal. +Implementations SHOULD perform numeric coercion when equivalent numeric values +are represented differently (for example, the [=string=] "21" and +the number 21). A value is eligible for coercion only if it is a +[=string=] that represents a valid numeric value in its entirety; strings that +contain non-numeric characters MUST NOT be coerced. +
      4. +
      +
    12. +
    +
  2. +
  3. +Return true. +
  4. +
+ +

+This recursive property matching algorithm is functionally equivalent to +flattening the expected object into a map of +JSON Pointer paths paired +with expected values, and then checking each path against the credential. +Implementations MAY use either approach, provided the results are equivalent. +See also Section [[[#converting-querybyexample-to-json-pointers]]]. +

+
+ +
+

Converting QueryByExample to JSON Pointers

+ +

+The following algorithm converts a QueryByExample example object +into a list of JSON +Pointers that specify which fields to reveal for [=selective disclosure=], +such as the selectivePointers option used with credential +derivation (see Section [[[#derive-credential]]]). Required input is an +example object ([=map=] example). An array of JSON Pointer strings +(pointers) is produced as output. A conforming implementation that +converts a QueryByExample example for [=selective disclosure=] +MUST produce the same output as this algorithm. +

+ +
    +
  1. +Initialize object to a copy of example with the +@context property removed, if present. +
  2. +
  3. +Initialize pointers to the list of JSON Pointers that identify every +value in object (for example, by treating object as a +tree and emitting a pointer for each leaf value). +
  4. +
  5. +Add any VCDM mandatory reveal pointers appropriate for the credential's data +model version to pointers. For [[VC-DATA-MODEL-2.0]], these include +at least /type and /issuer when those properties are +required to be disclosed by the securing mechanism in use. +
  6. +
  7. +Set pointers to the result of running the algorithm in Section +[[[#adjust-reveal-pointers]]], passing pointers. +
  8. +
  9. +Return pointers. +
  10. +
+ +

+The following example uses the Permanent Resident Card QueryByExample from this +section. After removing @context, generating pointers, adding +mandatory VCDM 2.0 reveal pointers, ensuring +credentialSubject coverage, pruning shallow pointers, and +normalizing type pointers, a conforming conversion produces: +

+ +
+{
+  "selectivePointers": [
+    "/type",
+    "/issuer",
+    "/credentialSubject/birthCountry"
+  ]
+}
+          
+ +

+In this result, /type and /issuer are the explicit +mandatory reveal pointers for the VC Data Model 2.0 securing mechanism in use, +and /credentialSubject/birthCountry is the claim requested by the +example (via an empty-string value). These pointers are suitable for use as +selectivePointers when deriving a credential for presentation. +

+
+ +
+
Adjust Reveal Pointers
+ +

+The following algorithm adjusts a list of JSON Pointers produced from a +QueryByExample example so they are suitable for selective +disclosure. Required input is an array of JSON Pointer strings +(pointers). An array of JSON Pointer strings is produced as output. +

+ +
    +
  1. +If no pointer in pointers is equal to +/credentialSubject and no pointer has +/credentialSubject/ as a prefix, append +/credentialSubject to pointers. +
  2. +
  3. +Set pointers to the result of running the algorithm in Section +[[[#prune-shallow-pointers]]], passing pointers. +
  4. +
  5. +Initialize adjustedPointers to an empty [=list=]. For each +pointer in pointers: +
      +
    1. +Initialize typeIndex to the index of the substring +/type/ in pointer, or -1 if it does not +occur. +
    2. +
    3. +If typeIndex is not -1, append the substring of +pointer from the start through /type (that is, truncate +any array index after /type) to adjustedPointers. +Otherwise, append pointer to adjustedPointers. +
    4. +
    +
  6. +
  7. +Return adjustedPointers. +
  8. +
+
+ +
+
Prune Shallow Pointers
+ +

+The following algorithm retains only the deepest JSON Pointers from a list, so +that a shallow pointer is removed when a deeper pointer under the same path +exists. Required input is an array of JSON Pointer strings +(pointers). An array of JSON Pointer strings is produced as output. +

+ +

+For example, given +["/a/b", "/a/b/c", "/a/b/c/d"], the result is +["/a/b/c/d"]. +

+ +
    +
  1. +Initialize deep to an empty [=list=]. +
  2. +
  3. +For each pointer in pointers: +
      +
    1. +Initialize isDeep to true. +
    2. +
    3. +For each other in pointers: +
        +
      1. +If the length of pointer is less than the length of +other and other starts with pointer, set +isDeep to false and stop comparing for this +pointer. +
      2. +
      +
    4. +
    5. +If isDeep is true, append pointer to +deep. +
    6. +
    +
  4. +
  5. +Return deep. +
  6. +
+
+
From b41ad8828af5fdfcd019948d4a6e1fa3efc74318 Mon Sep 17 00:00:00 2001 From: Patrick St-Louis Date: Tue, 11 Aug 2026 14:57:40 -0400 Subject: [PATCH 2/3] docs: tighten QBE list matching and mandatory reveal pointers Recurse into maps when matching list elements, and always append /type and /issuer during example-to-pointer conversion so the MUST output bar matches the selectivePointers example. Co-authored-by: Cursor --- index.html | 53 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/index.html b/index.html index 73cb1fda..86a00307 100644 --- a/index.html +++ b/index.html @@ -1920,8 +1920,29 @@
Matching Object Properties
[=list=] containing only actualValue).
  • -If no element in actualValues is strictly equal to any element in -expectedValue, return false. +At least one element expectedElement in expectedValue +MUST match at least one element actualElement in +actualValues. An expectedElement matches an +actualElement if any of the following is true: +
      +
    1. +expectedElement is a [=map=], actualElement is a +[=map=], and this algorithm returns true when called with +expectedElement as expectedObject and +actualElement as actualObject. +
    2. +
    3. +expectedElement is not a [=map=] and +actualElement is strictly equal to expectedElement. +
    4. +
    5. +expectedElement is not a [=map=] and numeric coercion applies: +both actualElement and expectedElement can be +interpreted as numeric values (using the same eligibility rules as for +primitive values below), and their numeric values are equal. +
    6. +
    +If no such matching pair exists, return false.
  • @@ -1984,13 +2005,15 @@

    Converting QueryByExample to
  • Initialize pointers to the list of JSON Pointers that identify every value in object (for example, by treating object as a -tree and emitting a pointer for each leaf value). +tree and emitting a pointer for each leaf value, including properties whose +value is an empty [=string=]).
  • -Add any VCDM mandatory reveal pointers appropriate for the credential's data -model version to pointers. For [[VC-DATA-MODEL-2.0]], these include -at least /type and /issuer when those properties are -required to be disclosed by the securing mechanism in use. +Append /type and /issuer to pointers if +they are not already present. These pointers are the deterministic mandatory +reveal paths for selective disclosure conversion under +[[VC-DATA-MODEL-2.0]]. Securing mechanisms MAY require additional mandatory +pointers at issuance time; those are outside the scope of this conversion.
  • Set pointers to the result of running the algorithm in Section @@ -2003,10 +2026,12 @@

    Converting QueryByExample to

    The following example uses the Permanent Resident Card QueryByExample from this -section. After removing @context, generating pointers, adding -mandatory VCDM 2.0 reveal pointers, ensuring -credentialSubject coverage, pruning shallow pointers, and -normalizing type pointers, a conforming conversion produces: +section. After removing @context, generating leaf pointers +(including /credentialSubject/birthCountry from the empty-string +claim), appending the mandatory /type and /issuer +pointers, ensuring credentialSubject coverage, pruning shallow +pointers, and normalizing type pointers, a conforming conversion +produces:

    @@ -2020,9 +2045,9 @@ 

    Converting QueryByExample to

    -In this result, /type and /issuer are the explicit -mandatory reveal pointers for the VC Data Model 2.0 securing mechanism in use, -and /credentialSubject/birthCountry is the claim requested by the +In this result, /type and /issuer are always added by +the conversion algorithm as mandatory reveal pointers, and +/credentialSubject/birthCountry is the claim requested by the example (via an empty-string value). These pointers are suitable for use as selectivePointers when deriving a credential for presentation.

    From 27532b959205c2047f387005e54ec4f4284103e8 Mon Sep 17 00:00:00 2001 From: Patrick St-Louis Date: Tue, 11 Aug 2026 14:51:54 -0400 Subject: [PATCH 3/3] docs: add informative reference workflow step processing appendix Document one non-normative exchange step-processing loop as an appendix, using ol.algorithm style and protocol hooks, with a cross-link from Workflows and Exchanges. Addresses #596; supersedes the approach in #626. Co-authored-by: Cursor --- index.html | 266 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) diff --git a/index.html b/index.html index 86a00307..9e6fc2c8 100644 --- a/index.html +++ b/index.html @@ -2768,6 +2768,11 @@

    Workflows and Exchanges

    more complex workflow templates, can be found in the appendix Workflow Step and Template Examples.

    +An informative reference algorithm for walking [=exchange=] steps until client +input is required or a response is ready is provided in Appendix +[[[#reference-workflow-step-processing-algorithm]]]. +

    +

    A given interaction with an exchange is expected to be short-lived but other mechanisms can be used to enable longer or multi-stage interactions. Examples of other interaction mechanisms include SMS, email, web notifications, @@ -4895,6 +4900,267 @@

    Complex Examples

  • +
    +

    Reference Workflow Step Processing Algorithm

    + +

    +This appendix is non-normative. +

    + +

    +This specification does not mandate a single internal implementation for +advancing an [=exchange=]. The algorithm below documents one possible +approach for walking [=workflow=] steps until the [=workflow service=] must +return a partial response to the exchange client (for example, to +ask for a [=verifiable presentation=]), until the exchange completes, or until +the exchange fails or times out. Implementations MAY differ in ordering, +persistence, error handling, and protocol bindings. +

    + +

    +Hooks such as prepareStep, +validateReceivedPresentation, +validateReceivedPresentationRequest, inputRequired, +createVerifiablePresentationRequest, and +isStepComplete exist so that the same logical loop can be adapted +when exchanges are fronted by this HTTP API, OpenID for Verifiable Presentations +(OID4VP), OpenID for Verifiable Credential Issuance (OID4VCI), invite-based +flows, or other interaction mechanisms. The response object +produced when the loop pauses or finishes can be returned directly to clients +using this API or mapped into other protocol messages as needed. +

    + +
    +

    Process Exchange Steps

    + +

    +The following algorithm processes an exchange by repeatedly evaluating the +current workflow step. Required inputs are a workflow configuration ([=map=] +workflow) and an exchange ([=map=] exchange). Optional +inputs are a received [=verifiable presentation=] ([=map=] +receivedPresentation) and a received [=verifiable presentation +request=] ([=map=] receivedPresentationRequest). A response object +([=map=] response), which MAY be empty, is produced as output, or an +error is raised. +

    + +
      +
    1. +Initialize step and response to null. +
    2. +
    3. +If exchange.state is complete or +invalid, throw a NotAllowedError. +
    4. +
    5. +If exchange.state is pending, set it to +active. +
    6. +
    7. +Continuously loop to process exchange steps, optionally saving any error thrown +as exchange.lastError. Return out of the loop when a +full response is generated, input from the exchange client is required, or the +exchange times out. An implementation-specific maximum step count MAY be +enforced to handle misconfigured workflows. In each iteration: +
        +
      1. +Set step to the current step (evaluating a step template as needed). +
      2. +
      3. +Call the prepareStep hook, passing workflow, +exchange, step, receivedPresentation, and +receivedPresentationRequest, to perform any protocol-specific step +preparation. If prepareStep returns a result with +receivedPresentation and/or +receivedPresentationRequest set, update +receivedPresentation and/or receivedPresentationRequest +accordingly. +
      4. +
      5. +If receivedPresentation is set, call +validateReceivedPresentation, passing workflow, +exchange, step, and receivedPresentation. +
      6. +
      7. +If receivedPresentationRequest is set, call +validateReceivedPresentationRequest, passing exchange, +step, and receivedPresentationRequest. +
      8. +
      9. +If the implementation supports blocking callbacks that can return results to be +added to exchange variables (or return errors), call the callback and store its +results in +exchange.variables.results[exchange.step].callbackResults, or throw +any error received. +
      10. +
      11. +Set isInputRequired to the result of calling the +inputRequired hook, passing step and +receivedPresentation, to perform any protocol-specific input checks. +
      12. +
      13. +If isInputRequired is true: +
          +
        1. +If response is null, set it to an empty object. +
        2. +
        3. +If step.verifiablePresentationRequest is set, call +createVerifiablePresentationRequest, passing workflow, +exchange, step, and response. +
        4. +
        5. +Save the exchange (and call any non-blocking callback in the step) and return +response. +
        6. +
        +
      14. +
      15. +Set issueToClient to true if +step.issueRequests includes any issuer requests for +[=verifiable credentials=] that are to be sent to the client +(issueRequest.result is not set); otherwise set it to +false. +
      16. +
      17. +If step.verifiablePresentation is set or +issueToClient is true: +
          +
        1. +If response is not null: +
            +
          1. +If response.verifiablePresentationRequest is not set, +set it to an empty object (to indicate that the exchange is not yet complete). +
          2. +
          3. +Save the exchange (and call any non-blocking callback in the step). +
          4. +
          5. +Return response. +
          6. +
          +
        2. +
        3. +Set response to an empty object. +
        4. +
        5. +If step.verifiablePresentation is set, set +response.verifiablePresentation to a copy of it; +otherwise set response.verifiablePresentation to a new, +empty [=verifiable presentation=] (using [[VC-DATA-MODEL-2.0]] by default; a +custom configuration MAY specify another version). +
        6. +
        +
      18. +
      19. +Perform every issue request (optionally in parallel), returning an error +response to the client if any fails. Implementations MAY implement failure +recovery or retry issue requests at their own discretion: +
          +
        1. +For each issue request where result is set to an exchange variable +path or name, save the issued credential in the referenced exchange variable. +
        2. +
        3. +For each issue request where result is not specified, save the +issued credential in +response.verifiablePresentation (for a VCDM +presentation, append it to +response.verifiablePresentation.verifiableCredential). +
        4. +
        +
      20. +
      21. +If response.verifiablePresentation is set and the step +configuration indicates it should be signed, sign the presentation (for example, +by using a VCALM holder instance's /presentations create endpoint). +
      22. +
      23. +Call isStepComplete, passing workflow, +exchange, step, receivedPresentation, and +receivedPresentationRequest, to perform any protocol-specific +behavior to determine if the step is complete. Set stepComplete to +the result, defaulting to true. +
      24. +
      25. +If stepComplete is true: +
          +
        1. +If step.redirectUrl is set: +
            +
          1. +If response is null, set it to an empty object. +
          2. +
          3. +Set response.redirectUrl to +step.redirectUrl. +
          4. +
          +
        2. +
        3. +If step.nextStep is not set, set +exchange.state to complete. +
        4. +
        5. +Otherwise, delete +exchange.variables.results[step.nextStep] if it exists, and set +exchange.step to +step.nextStep. +
        6. +
        +
      26. +
      27. +Save the exchange (and call any non-blocking callback in the step). +
      28. +
      29. +If exchange.state is complete, return +response if it is not null; otherwise return an empty +object. +
      30. +
      31. +Set receivedPresentation to null and continue the loop. +
      32. +
      +
    8. +
    +
    + +
    +

    Protocol and implementation hooks

    + +

    +The following hooks are intentionally underspecified so that implementations can +bind the reference loop to a particular protocol or deployment: +

    + +
      +
    • +prepareStep — optional protocol-specific preparation before +validating client input for the current step. +
    • +
    • +validateReceivedPresentation / +validateReceivedPresentationRequest — validate a client-supplied +presentation or presentation request against the current step. +
    • +
    • +inputRequired — determine whether the loop must pause and return a +response (typically including a presentation request) to the client. +
    • +
    • +createVerifiablePresentationRequest — populate +response.verifiablePresentationRequest from the step configuration +and exchange variables. +
    • +
    • +isStepComplete — determine whether the current step is finished and +the exchange may advance to nextStep or complete. +
    • +
    +
    +
    +

    Relationship to Other Specifications