Context. We implemented OpenID4VP 1.0 transaction_data end-to-end for a research prototype (per-action, human-in-the-loop authorization of a delegated action): an SD-JWT VC credential, a payment-style transaction_data entry, presented from the EUDI reference wallet (a commit-pinned research build) to a conformant verifier. It works end-to-end — request accepted, per-action user confirmation, and the verifier returns 200 once transaction_data_hashes is bound into the holder KB-JWT. Sharing two small things back to the WG. (This is a spec-side note to the DCP WG; distinct from the EUDI wallet-app implementation gap we filed separately at eu-digital-identity-wallet/eudi-app-android-wallet-ui#636.)
1. A worked KB-JWT example the spec could carry (non-normative)
The single most useful missing artefact when building this was one concrete, end-to-end example that walks from the transaction_data array value, through the hash, into the holder KB-JWT. A conformant verifier rejected our first attempts until the KB-JWT carried both transaction_data_hashes and transaction_data_hashes_alg: sha-256 (verifier error: 'transaction_data_hashes_alg' must be 'sha-256'). Once bound, the same verifier accepted.
A fully worked example — the hash below is real and recomputable:
Transaction Data object (before encoding):
{"type":"payment","credential_ids":["payment_card"],"transaction_data_hashes_alg":["sha-256"],"payee":"ACME GmbH","amount":"120.00","currency":"EUR"}
Value as it appears in the transaction_data array (base64url, no padding):
eyJ0eXBlIjoicGF5bWVudCIsImNyZWRlbnRpYWxfaWRzIjpbInBheW1lbnRfY2FyZCJdLCJ0cmFuc2FjdGlvbl9kYXRhX2hhc2hlc19hbGciOlsic2hhLTI1NiJdLCJwYXllZSI6IkFDTUUgR21iSCIsImFtb3VudCI6IjEyMC4wMCIsImN1cnJlbmN5IjoiRVVSIn0
Hash entry = base64url( SHA-256( the base64url string above ) ), no padding (cf. #418):
Xzo6puFg3O2W4vzj3HgQKEROrFEEa8lwkQHFAkM5ff8
Holder KB-JWT payload (SD-JWT VC presentation), hash bound in:
{
"nonce": "<verifier-nonce>",
"aud": "x509_hash:<verifier-cert-thumbprint>",
"iat": "<unix-time>",
"sd_hash": "<hash-of-issued-sd-jwt>",
"transaction_data_hashes": ["Xzo6puFg3O2W4vzj3HgQKEROrFEEa8lwkQHFAkM5ff8"],
"transaction_data_hashes_alg": "sha-256"
}
Reviewer self-check (reproduces the hash entry):
printf %s '<the base64url string>' | openssl dgst -sha256 -binary | basenc --base64url | tr -d '='
If a short non-normative worked example like this would be welcome in the Transaction Data section, I'm happy to open a PR — implementers seem to hit the same "which JWT, which claims, what is the hash input" questions we did.
2. One binding detail to confirm — multi-credential credential_ids
For a single presented credential the binding is unambiguous. For a presentation of multiple credentials where different transaction_data entries carry different credential_ids, we read the spec as: each presented credential's KB-JWT should carry hashes for only those transaction_data objects whose credential_ids reference that credential (per-credential filtering), not the full set. Is that the intended behaviour? We could not find a reference implementation exercising the multi-credential case to check against, so we'd like to confirm the reading before contributing test vectors for it.
Offer
We have a working wallet + verifier setup and can contribute (a) the non-normative KB-JWT example above and (b) interop test vectors for both the single- and multi-credential transaction_data cases, if useful.
Thanks — glad to be contributing.
— Anton Sokolov, Tyche Institute (Tallinn); contribution agreements signed with the OpenID Foundation.
Context. We implemented OpenID4VP 1.0
transaction_dataend-to-end for a research prototype (per-action, human-in-the-loop authorization of a delegated action): an SD-JWT VC credential, apayment-styletransaction_dataentry, presented from the EUDI reference wallet (a commit-pinned research build) to a conformant verifier. It works end-to-end — request accepted, per-action user confirmation, and the verifier returns 200 oncetransaction_data_hashesis bound into the holder KB-JWT. Sharing two small things back to the WG. (This is a spec-side note to the DCP WG; distinct from the EUDI wallet-app implementation gap we filed separately at eu-digital-identity-wallet/eudi-app-android-wallet-ui#636.)1. A worked KB-JWT example the spec could carry (non-normative)
The single most useful missing artefact when building this was one concrete, end-to-end example that walks from the
transaction_dataarray value, through the hash, into the holder KB-JWT. A conformant verifier rejected our first attempts until the KB-JWT carried bothtransaction_data_hashesandtransaction_data_hashes_alg: sha-256(verifier error:'transaction_data_hashes_alg' must be 'sha-256'). Once bound, the same verifier accepted.A fully worked example — the hash below is real and recomputable:
Transaction Data object (before encoding):
{"type":"payment","credential_ids":["payment_card"],"transaction_data_hashes_alg":["sha-256"],"payee":"ACME GmbH","amount":"120.00","currency":"EUR"}Value as it appears in the
transaction_dataarray (base64url, no padding):Hash entry =
base64url( SHA-256( the base64url string above ) ), no padding (cf. #418):Holder KB-JWT payload (SD-JWT VC presentation), hash bound in:
{ "nonce": "<verifier-nonce>", "aud": "x509_hash:<verifier-cert-thumbprint>", "iat": "<unix-time>", "sd_hash": "<hash-of-issued-sd-jwt>", "transaction_data_hashes": ["Xzo6puFg3O2W4vzj3HgQKEROrFEEa8lwkQHFAkM5ff8"], "transaction_data_hashes_alg": "sha-256" }Reviewer self-check (reproduces the hash entry):
If a short non-normative worked example like this would be welcome in the Transaction Data section, I'm happy to open a PR — implementers seem to hit the same "which JWT, which claims, what is the hash input" questions we did.
2. One binding detail to confirm — multi-credential
credential_idsFor a single presented credential the binding is unambiguous. For a presentation of multiple credentials where different
transaction_dataentries carry differentcredential_ids, we read the spec as: each presented credential's KB-JWT should carry hashes for only thosetransaction_dataobjects whosecredential_idsreference that credential (per-credential filtering), not the full set. Is that the intended behaviour? We could not find a reference implementation exercising the multi-credential case to check against, so we'd like to confirm the reading before contributing test vectors for it.Offer
We have a working wallet + verifier setup and can contribute (a) the non-normative KB-JWT example above and (b) interop test vectors for both the single- and multi-credential
transaction_datacases, if useful.Thanks — glad to be contributing.
— Anton Sokolov, Tyche Institute (Tallinn); contribution agreements signed with the OpenID Foundation.