owned this note
owned this note
Published
Linked with GitHub
# New Credential Query Language
<details><summary>Internal notes</summary>
Changes from last version discussed:
* DF: `credentials_rules` renamed to `rules`
* DF: `pick` renamed to `required` as "pick" might imply that the user should pick
* DF: `rules` renamed to `from` as that is easier to understand
* DF: there is no need for the `claims_rules` in the claims, as we can just omit that and start with an object with require/from
* KY: alg values are communicated in a map where format is a key and object contains the vct/doctype, alg_values, hash_alg_values and any other credential format specific claims. keeping a top level format claim still for the ease of parsing.
</details>
<details><summary>Open Questions</summary>
* When `intent_to_retain` is true, is there a need to provide a period of time to retain the data?
* "purpose" is still being requested.
</details>
## Example 1a - Request one Credential (SD-JWT VC)
Just one credential is requested. It must contain the claims `last_name`, `first_name`, and `address/street_address`.
```json=
{
"credentials": {
"my_cred_1": {
"format": "vc+sd-jwt",
"vc+sd-jwt": { // format-specific requirements
"vct": "https://credentials.example.com/identity_credential",
"alg_values": ["ES256", "ES384"],
"hash_alg_values": ["SHA-256"]
},
"claims":[
{ "path": ["last_name"] },
{
"path": ["first_name"],
"intent_to_retain": false // might be defined as an add-on
},
{ "path": ["address", "street_address"] }
]
}
}
}
```
Note the identifier `my_cred_1`. It will show up in the VP Token, which is restructured to be an object mapping the identifiers in the request to the presentation (string or object depending on a credential format):
```json=
vp_token={
"my_cred_1": "eY..."
}
```
## Example 1b - Request one Credential (mdoc)
The syntax for the claims is slightly different due to the structure of mdocs:
```json=
{
"my_mdoc_credential": {
"format": "mso_mdoc",
"mso_mdoc": { // format-specific requirements
"doctype": "org.iso.7367.1.mVR",
"alg_values": [ "EdDSA" ],
"hash_algorithm_values": [ "SHA-384" ],
},
"claims": [
{
"namespace": "org.iso.7367.1",
"claim_name": "vehicle_holder",
"intent_to_retain": false
},
{
"namespace": "org.iso.18013.5.1",
"claim_name": "first_name",
"intent_to_retain": false
}
]
}
}
}
```
Response:
```json=
vp_token={
"my_mdoc_credential": "<mdoc string>"
}
```
## Example 2 - Request multiple Credentials (all of the requested credentials needs to be returned)
All three credentials have to be delivered:
```json=
{
"credentials": {
"my_cred_1": {
"format": "vc+sd-jwt",
"vc+sd-jwt": {
"vct": "https://credentials.example.com/identity_credential",
"alg_values": ["ES256", "ES384"],
"hash_alg_values": ["SHA-256"]
},
"purpose": "give me the credential", // we might pull this to the top
"claims":[
{ "path": ["last_name"] }
]
},
"my_cred_2": {
"format": "vc+sd-jwt",
"vc+sd-jwt": {
"vct": "https://credentials.example.com/identity_credential/2",
"alg_values": ["ES256", "ES384"],
"hash_alg_values": ["SHA-256"]
},
"claims": [
{ "path": ["addresses"] }
]
},
"my_cred_3": {
"format": "vc+sd-jwt",
"vc+sd-jwt": {
"vct": "https://credentials.example.com/identity_credential/3",
"alg_values": ["ES256", "ES384"],
"hash_alg_values": ["SHA-256"]
},
"claims": [
{ "path": ["addresses"] }
]
}
}
}
```
All three credentials will show up in the VP Token:
```json=
vp_token={
"my_cred_1": "eY...",
"my_cred_2": "eY...",
"my_cred_3": "eY..."
}
```
Note: If there is a requirement on sending multiple credentials fulfilling one request, this should be turned into an array.
## Example 3 - Request alternative claims (one rule)
Require `age_over_18` or `birth_date` or `age_birth_year` by grouping them in an object with the properties `require` and `from`. The order of the claims in the `from` list implies a preference by the verifier.
```json=
{
"credentials": {
"my_cred_1": {
"format": "vc+sd-jwt",
"vc+sd-jwt": {
"vct": "https://credentials.example.com/identity_credential",
"alg_values": ["ES256", "ES384"],
"hash_alg_values": ["SHA-256"]
},
"purpose": "give me the credential",
"claims":[
{ "path": ["last_name"] },
{
"required": 1,
"from": [
{ "path": ["age_over_18"] },
{ "path": ["birth_date"] },
{ "path": ["age_birth_year"] }
]
},
]
}
}
}
```
Note: the expectation is for the wallet to check which of the alternative claims are present and render a user screen based on that - not for the wallet to display to the user all alternative claims (`age_over_18` or `birth_date` or `age_birth_year` in this case).
## Example 4 - Request alternative claims (multiple rules)
By nesting groups, complex queries can be expressed; here, either "zip_code" or both "city" and "state" are required.
```json=
{
"credentials": {
"my_cred_1": {
"format": "vc+sd-jwt",
"vc+sd-jwt": {
"vct": "https://credentials.example.com/identity_credential",
"alg_values": ["ES256", "ES384"],
"hash_alg_values": ["SHA-256"]
},
"purpose": "give me the credential",
"claims":[
{ "path": ["last_name"] },
{
"required": 1,
"from": [
{ "path": [ "zip_code" ] },
{
"required": 2,
"from": [
{ "path": [ "city" ] },
{ "path": [ "state" ] }
]
}
]
}
]
}
}
}
```
## Example 5 - Credentials Alternatives
The same logic to requesting alternative claims can be applied to requesting alternative credentials as well. The "rules" for credentials are separated from the credentials to make the processing easier and the syntax more clean. The following example requests "mycred_1" and either "my_cred_2" or "my_cred_3":
```json=
{
"rules": {
"required": 2,
"from": [
{ "ref": "my_cred_1" },
{
"required": 1,
"from": [
{ "ref": "my_cred_2" },
{ "ref": "my_cred_3" }
]
}
]
},
"credentials": { //each object follows the syntax described above
"my_cred_1": {...},
"my_cred_2": {...},
"my_cred_3": {...}
}
}
```
As shown in the examples further up, if `rules` is omitted, all credentials have to be delivered.
If the verifier just wants the user's claim (like street address/age), it would express it as "give me one of these credentials and outline any number of credentials whose schema includes a desired claim" - the verifier still needs to specify the format/type of the credential to indicate what it can understand.
Only the available/selected credential will show up in the VP Token:
```json=
vp_token={
"my_cred_1": "eY..."
"my_cred_2": "eY..."
}
```
## Example 6 - Further specifying requirements for the claims: by value types, with`value`/`values` and in arrays.
* `value_type` can be used to match a JSON type
* `value` and `values` are used as defined in OpenID Connect Core.
* `*` is a path element to indicate "for all array elements".
* If there is no match for a claim, and no alternative claim is specified, the credential cannot be delivered in the response.
```json=
{
"credentials": {
"my_cred_1": {
"format": "vc+sd-jwt",
"vc+sd-jwt": {
"vct": "https://credentials.example.com/identity_credential",
"alg_values": ["ES256", "ES384"],
"hash_alg_values": ["SHA-256"]
},
"purpose": "give me the credential",
"claims":[
{
"path": ["last_name"],
"value_type": "string",
"value": "Doe"
},
{
"path": ["addresses", "*", "zip_code"],
"value_type": "integer",
"values": ["98101"]
}
]
}
}
}
```
## Example 7 - Advanced Syntax for Claims (Support is optional!)
The functions listed below can be used to transform claims **before matching with value/values**.
Note:
* The functions do not modify the values returned in the presentation, just the one used for matching with `value` or `values`.
* Each function is defined to be self-contained and safe to execute for the wallet.
* Multiple functions can be applied, the order is defined in the request.
* Support for all functions is optional; functions can be listed in metadata for verifier to discover support by the wallet.
* The functions are aligned with OpenID Advanced Syntax for Claims: https://openid.bitbucket.io/ekyc/openid-connect-advanced-syntax-for-claims.html
### Functions
* Transform date to timespan:
* `"years_ago"`
* `"years_until"`
* Compare numbers, also defined for dates and datetimes:
* `["eq", 42]` - is equal to 42
* `["ne", 42]` - is not equal to 42
* `["gt", 42]` - is greater than 42
* `["gte", 42]` - is greater than or equal to 42
* `["lt", 42]` - is less than 42
* `["lte", 42]` - is less than or equal to 42
* Compare strings:
* `["contains", "Foo"]` - contains the string "Foo"`
* `["starts_with", "Foo"]` - starts with the string "Foo"`
* `["ends_with", "Foo"]` - ends with the string "Foo"`
* `["lower", "Foo"]` - is equal to "foo" (for case insensitive matches)
* Hashing, for comparing without releasing the plain text:
* `["hash", "sha-256"]`
* Object access, for comparing a nested field:
* `["get", ["country"]]` - is equal to the value of `address.country` when used in the object `{"address": {"country": "Iceland"}}`
* If, and only if, we really want to implement regular expressions:
* `["match", ".*"]` - matches the regular expression ".*"
### Example
```json=
{
"credentials":{
"my_cred_1": {
"format": "vc+sd-jwt",
"vc+sd-jwt": "...",
"claims":[
// Only return credential if user is above 18
{
{
"path": ["birth_date"],
"fn": [
"years_ago",
["gte", 18 ]
],
"value": true
}
},
// Only return credential if the bank account number matches
{
"path": ["iban"],
"fn": [
["hash", "sha-256"]
],
"value": "1cbec737f863e4922cee63cc2ebbfaafcd1cff8b790d8cfd2e6a5d550b648afa"
},
// Only return address if it is from a specific country
{
"path": ["addresses", "*"],
"fn": [
["get", ["country"] ]
],
"values": ["Germany", "Japan"]
}
]
}
}
```