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 · 6 comments
Open

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants