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

Underspecification of format for workflow credentialTemplates property #457

Open
kezike opened this issue Feb 19, 2025 · 8 comments
Open
Labels
ready for PR Issue ready to be resolved via a Pull Request

Comments

@kezike
Copy link

kezike commented Feb 19, 2025

I noticed that the credentialTemplates property in workflow.steps[STEP_NAME].credentialTemplates found in the workflow creation request and response as well as the workflow retrieval response is specified as an array, but it does not specify the format of each item in the specification text (although the OpenAPI definition does specify that the fields should be type and template). Additionally, since this is an array at the workflow level without IDs, it is unclear how the templates should be applied to an exchange/step. Should this property reside at the exchange level instead? If not, could we at least add an ID property to the items and/or modify the format of credentialTemplates to a map with keys being the step names?

@dlongley
Copy link
Contributor

dlongley commented Feb 19, 2025

Each object in credentialTemplates should have a schema like this:

{
  title: 'Typed Template',
  type: 'object',
  required: ['type', 'template'],
  additionalProperties: false,
  properties: {
    id: {
      type: 'string'
    },
    type: {
      type: 'string',
      // with other options possible too
      enum: ['jsonata']
    },
    template: {
      type: 'string'
    }
  }
}

The id property is optional but if present can be used to reference each template in an exchange for issuance purposes. Alternatively the index of the template can be used. Each exchange step can define an array of issueRequests where each element is an object that references one of these credential templates; the objects have a schema like this:

{
  title: 'Issue Request Parameters',
  type: 'object',
  oneOf: [{
    required: ['credentialTemplateId']
  }, {
    required: ['credentialTemplateIndex']
  }],
  additionalProperties: false,
  properties: {
    credentialTemplateId: {
      type: 'string'
    },
    credentialTemplateIndex: {
      type: 'number'
    },
    // optionally specify different variables from `exchange.variables`
    // (either a string that is the name of a variable in `exchange.variables`
    // to use, or a whole object with alternative variables)
    variables: {
      oneOf: [{type: 'string'}, {type: 'object'}]
    }
  }
}

@kezike
Copy link
Author

kezike commented Feb 20, 2025

OK, according to your response, there are two missing items:

  1. credentialTemplates[].id in both the specification text and the OpenAPI definition
  2. workflow.steps[STEP_NAME].issueRequests in both the specification text and the OpenAPI definition

Also, I have a question about workflow.steps[STEP_NAME].issueRequests. Would that be mutually exclusive from workflow.steps[STEP_NAME].verifiablePresentationRequest (and possibly all other fields in workflow.steps[STEP_NAME]) or could a step both deliver a credentials and request credentials? My recollection is the latter, but I figured I would confirm here.

Additionally, would a credential issuing step always be required to use issueRequests to specify the issuing credential template? I ask, because as currently constructed, the specification text does not make it clear how workflow configs should specify the credentials that a credential-issuing step should return.

Finally, with the new issueRequests field, this is how I imagine a simple workflow that requests a DIDAuth presentation before credential delivery would work (please correct me if I am misunderstanding):

{
  "id": "dbee9358-5327-4673-809e-3afa31f4e155",
  "creationDate": "2025-02-17T18:36:34.303Z",
  "steps": {
    "didAuthRequest": {
      "createChallenge": true,
      "verifiablePresentationRequest": {
        "query": [
          {
            "type": "DIDAuthentication",
            "acceptedMethods": [{ "method": "key" }, { "method": "web" }],
            "acceptedCryptosuites": [{ "cryptosuite": "ed25519-2020" }]
          }
        ],
        "challenge": "9f281fe1-278b-4bac-b51c-0aec8c18ccbd",
        "domain": "https://test-issuer.example.com",
        "interact": {
          "service": [
            {
              "type": "UnmediatedPresentationService2021",
              "serviceEndpoint": "https://test-issuer.example.com/74043ab1-48ca-4f36-a734-17334036cf10"
            }
          ]
        }
      },
      "nextStep": "credentialDelivery"
    },
    "credentialDelivery": {
      "issueRequests": [
        {
          "credentialTemplateId": "af5854e7-f069-40e8-a75f-532ec146a024",
          "variables": {
            "credentialSubject": {
              "id": "did:example:ebfeb1f712ebc6f1c276e12ec21"
            }
          }
        }
      ]
    }
  },
  "initialStep": "didAuthRequest",
  "credentialTemplates": [
    {
      "id": "af5854e7-f069-40e8-a75f-532ec146a024",
      "type": "jsonata",
      "template": JSON.stringify({
        "@context": [
          "https://www.w3.org/ns/credentials/v2",
          "https://www.w3.org/ns/credentials/examples/v2"
        ],
        "id": "http://university.example/credentials/58473",
        "type": ["VerifiableCredential", "ExampleAlumniCredential"],
        "issuer": "did:example:2g55q912ec3476eba2l9812ecbfe",
        "validFrom": "2010-01-01T00:00:00Z",
        "credentialSubject": {
          "id": "credentialSubject.id",
          "alumniOf": {
            "id": "did:example:c276e12ec21ebfeb1f712ebc6f1",
            "name": "Example University"
          }
        }
      })
    }
  ]
}

This is a contrived example that is almost certainly incorrect. The piece that is not entirely clear to me in the specification, as well as your proposed modifications, is how to use workflow.steps[STEP_NAME].issueRequests[].variables in combination with workflow.credentialTemplates AND exchange.variables. My understanding has been that exchange.variables is supposed to be used to populate the holder (and maybe issuer) data into workflow.credentialTemplates. But, I am having a hard time seeing how a workflow config deterministically specifies the credentials that a credential-issuing exchange step returns.

@dlongley
Copy link
Contributor

...or could a step both deliver a credentials and request credentials? My recollection is the latter, but I figured I would confirm here.

Yes, a step can both request and deliver credentials.

Additionally, would a credential issuing step always be required to use issueRequests to specify the issuing credential template? I ask, because as currently constructed, the specification text does not make it clear how workflow configs should specify the credentials that a credential-issuing step should return.

Originally, this was not required, and it was assumed that all templates (if present) would be used for a single-step flow. But, I think at this point it's become clear that it is better for a step to always deliberately call out that it is issuing and which templates are being used in the step. So I'd argue that we should make it a requirement for any step that issues any VCs to use issueRequests.

... example ...
This is a contrived example that is almost certainly incorrect.

It's pretty close, but I think a few things are probably off from what would be expected in a real workflow, such as the custom variables in the second step always using a static value for the credential subject as opposed to using, for example, some top-level variables populated from the results of the previous step (i.e., using the DID submitted in the first step by the client).

That being said, I think it would mostly technically work and follow the spec, it just doesn't seem to do anything with the DID auth information, etc. I also only say "mostly" because I'm just eyeballing it and I'm making some assumptions about things like that JSON.stringify() not literally being part of a jsonata template.

The piece that is not entirely clear to me in the specification, as well as your proposed modifications, is how to use workflow.steps[STEP_NAME].issueRequests[].variables in combination with workflow.credentialTemplates AND exchange.variables.

So in the example above, the custom variables in issueRequests would be used instead of exchange.variables and this gets to my point above. Either exchange.variables should be used instead, with say, for example: results.didAuthRequest.verifiablePresentation.holder (which references exchange.variables.results. ... being used in the credential template for the credential subject ID.

Alternatively, the step could be generated from a (jsonata) template itself, such that the custom variables in issueRequests would be replaced with whatever was desirable (including generating it from results.didAuthRequest.verifiablePresentation.holder in that way. To give pseudo examples of the above two options:

First, without using custom variables per issue request:

    // ...
    "credentialDelivery": {
      "issueRequests": [
        {
          "credentialTemplateId": "af5854e7-f069-40e8-a75f-532ec146a024"
        }
      ]
    }
  },
  // ...
  "credentialTemplates": [
    {
      "id": "af5854e7-f069-40e8-a75f-532ec146a024",
      "type": "jsonata",
      // assume appropriate escaping here
      "template": "{
        "@context": [
          "https://www.w3.org/ns/credentials/v2",
          "https://www.w3.org/ns/credentials/examples/v2"
        ],
        "id": "http://university.example/credentials/58473",
        "type": ["VerifiableCredential", "ExampleAlumniCredential"],
        "issuer": "did:example:2g55q912ec3476eba2l9812ecbfe",
        "validFrom": "2010-01-01T00:00:00Z",
        "credentialSubject": {
          // note the change here:
          "id": results.didAuthRequest.verifiablePresentation.holder,
          "alumniOf": {
            "id": "did:example:c276e12ec21ebfeb1f712ebc6f1",
            "name": "Example University"
          }
        }
      }"
    }
  ]

Or with a step generated from a template using custom variables per issue request:

{
  ...,
  credentialDelivery: {
    stepTemplate: {
      type: 'jsonata',
      // assume appropriate escaping here
      template: "{
      "issueRequests": [
        {
          "credentialTemplateId": "af5854e7-f069-40e8-a75f-532ec146a024",
          "variables": {
            "credentialSubject": {
              "id": results.didAuthRequest.verifiablePresentation.holder
            }
          }
        }
      ]
    }"
  },
  // ...
}

In this case, it's probably simplest to use the first option, as there's no particular reason that I can see to introduce a new set of custom variables for such a simple workflow. A more complex workflow, however, might involve issuing multiple VCs of the same type (or even different types) at once -- and so you might pull variables from an array of variables and want to pass those in as custom variables to the template:

{
  ...,
  credentialDelivery: {
    stepTemplate: {
      type: 'jsonata',
      // assume appropriate escaping here
      template: "{
      // note N-many requests, each using the same VC template, but
      // using variables that come from an array that's been built up from
      // previous step(s), each that needs to be remapped to a single set
      // of isolated variables for each use of the template
      "issueRequests": [
        {
          "credentialTemplateId": "af5854e7-f069-40e8-a75f-532ec146a024",
          "variables": someArrayInExchangeVariables[0]
          }
        },
        {
          "credentialTemplateId": "af5854e7-f069-40e8-a75f-532ec146a024",
          "variables": someArrayInExchangeVariables[1]
        }
      ]
    }"
  },
  // ...
}

@kezike
Copy link
Author

kezike commented Feb 21, 2025

This is very useful. Thanks a lot @dlongley! To summarize, this is what is needed from a PR standpoint:

  1. Add format of credentialTemplates[] in the specification text
  2. Add credentialTemplates[].id in both the specification text and the OpenAPI definition
  3. Add workflow.steps[STEP_NAME].issueRequests in both the specification text and the OpenAPI definition
  4. (Maybe) Add 1-2 examples of a workflow config, like the ones @dlongley provided in this thread

@dlongley
Copy link
Contributor

dlongley commented Feb 21, 2025

+1 to the above. There's one more consideration that is in existing implementations today that we could either codify in the spec or we could ask those implementations to change to be conformant.

It is that if there's a single step workflow that has both issueRequests and verifiablePresentationRequest, that the step executes by sending the VPR -- and then issuing after processing the response from the client. It doesn't make sense for a single step with both of these to work any other way.

Note that this is different from how a workflow with multiple steps would be handled, where the issuance would happen and then both the resulting VP (with issued VCs) and the VPR would be sent together for the next step to handle the response to.

We have to choose whether multi-step flows should be treated differently from single step flows in this way or if all of these flows need to become multi-step flows. Another way to think about this is whether or not the last step in a flow can have a VPR -- and what happens if it does. Perhaps the last step of a flow with a VPR will always send the VPR and require a response to it, before performing the issuance (which covers the single step flow because it's always the last step). There are complexity trade offs either way.

@kezike
Copy link
Author

kezike commented Feb 23, 2025

I feel that it would be best to make verifiablePresentationRequest and issueRequests mutually exclusive, requiring steps to choose one or the other. Most workflows don't have that many steps, so I think adding an extra step to separate these two cases is worth the reduction in complexity for implementers. I am curious though, are there currently implementations with workflows that use both of these properties in a single step, such that this mutual exclusion would cause issues?

@kezike
Copy link
Author

kezike commented Feb 24, 2025

As I am returning to this, I am recalling that redirectUrl can also be returned from the exchange participation request. I mention this here for two reasons:

  • This discussion should also take this property into consideration, since the spec seems to suggest that it should be specified at the same level as verifiablePresentationRequest and issueRequests (workflow.steps[STEP_NAME]). Should the presence of this property be independent of that of verifiablePresentationRequest and issueRequests or do we only expect for this to be included when issueRequests is present or in the last step?
  • This property is included in the response of the exchange participation endpoint, but it is not included as a property for workflow or exchange creation. I feel that this should be specified under workflow.steps[STEP_NAME].redirectUrl. If you agree, I can create an issue for this.

@msporny
Copy link
Contributor

msporny commented Feb 25, 2025

The group discussed this on the 2025-02-25 telecon:

@dlongley noted that work done in the Conexxus retail standards setting body allow responses to have both VPRs and VPs in a single message. What do we do about the last step in the flow, can it have a VPR? (clearly, nothing to do w/ the request) and is there an exception if there is only one step. Simplest flows are single step with VPR and issuance requests -- when exchange is started, if no response to VPR is posted to start the exchange, if that's not present, then VPR is sent and nothing is issued. If it is present, its evaluated, and if requirements are met, VCs issued. We could say we dont' allow one step flows to make things more consistent, but it does add complexity. The main thing driving single-step flows, VC API is designed to integrate with other flows that provide VPR through another channel (like CHAPI) so you don't need an additional back and forth. @kezike noted that what we might need is a matrix for what happens if one, the other, or both are provided (what to expect). @dlongley noted that if we haven't specified redirect URL (step object w/ key), does it make sense to have redirect at any other than last step? Should assert that it is in the last step only (redirect URL can only exist on last step).

Two PRs will need to be raised, where the first PR needs to do the following:

  1. Add format of credentialTemplates[] in the specification text.
  2. Add credentialTemplates[].id in both the specification text and the OpenAPI definition.
  3. Add workflow.steps[STEP_NAME].issueRequests in both the specification text and the OpenAPI definition.
  4. (Maybe) Add 1-2 examples of a workflow config, like the ones @dlongley provided in this thread.
  5. Define the redirectUrl inside of the workflow step body and specify that it can only exist on the last step.

The second PR needs to the following:

  1. How to process a step and whether or not it is the last step changes the processing rules.

@msporny msporny added the ready for PR Issue ready to be resolved via a Pull Request label Feb 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready for PR Issue ready to be resolved via a Pull Request
Projects
None yet
Development

No branches or pull requests

3 participants