From 8e17128cc054d35fb5df94b598573a78acb2247c Mon Sep 17 00:00:00 2001 From: tsaplia Date: Wed, 20 May 2026 16:04:00 +0200 Subject: [PATCH 1/3] spec fixed --- src/content/docs/spec/fido4vc-jcs-2026.mdx | 194 +++++++++++++-------- 1 file changed, 117 insertions(+), 77 deletions(-) diff --git a/src/content/docs/spec/fido4vc-jcs-2026.mdx b/src/content/docs/spec/fido4vc-jcs-2026.mdx index 966ceea..44e2056 100644 --- a/src/content/docs/spec/fido4vc-jcs-2026.mdx +++ b/src/content/docs/spec/fido4vc-jcs-2026.mdx @@ -19,11 +19,12 @@ This page is the normative specification for the `fido4vc-jcs-2026` cryptosuite. |---|---| | **Cryptosuite name** | `fido4vc-jcs-2026` | | **Proof type** | `DataIntegrityProof` | -| **Canonicalization** | JSON Canonicalization Scheme (RFC 8785) | -| **Hash algorithm** | SHA-256 | -| **Signature algorithm** | ECDSA over P-256 with SHA-256 (ES256) | -| **Signing mechanism** | WebAuthn `navigator.credentials.get()` ceremony | -| **DID methods supported** | `did:jwk` (other methods may be added by extensions) | +| **Canonicalization** | [JSON Canonicalization Scheme (RFC 8785)](https://www.rfc-editor.org/rfc/rfc8785) | +| **Hash algorithm** | SHA-256 ([FIPS 180-4](https://csrc.nist.gov/publications/detail/fips/180/4/final)) | +| **Signature algorithm** | ECDSA over P-256 with SHA-256 (ES256, [FIPS 186-5](https://csrc.nist.gov/pubs/fips/186-5/final), [RFC 7518 §3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4)) | +| **Signing mechanism** | WebAuthn [`navigator.credentials.get()`](https://www.w3.org/TR/webauthn-3/#sctn-getAssertion) ceremony | +| **Verification method type** | `JsonWebKey` with `kty: "EC"`, `crv: "P-256"` | +| **DID methods** | Any DID method whose resolution yields a conforming `JsonWebKey` (tested with `did:jwk`, `did:key`, `did:web`) | The cryptosuite name follows the W3C VC-DI naming convention `--`: `fido4vc` identifies the underlying signing mechanism family, `jcs` identifies the canonicalization, `2026` is the year of registration. @@ -31,109 +32,148 @@ The cryptosuite name follows the W3C VC-DI naming convention `-", - "domain": "https://verifier.example.com/openid4vp/verify", - "proofValue": { - "signature": "", - "authenticatorData": "", - "clientData": "" - } - } -} -``` +To ensure the resulting assertions are verifiable under `fido4vc-jcs-2026`, the registration ceremony MUST constrain the credential to this cryptosuite's signature algorithm. The [`PublicKeyCredentialCreationOptions.pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-pubkeycredparams) array MUST contain at least one entry whose: -### `proofValue` object +- `type` is `"public-key"`; +- `alg` is `-7` (the [COSE Algorithm](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) identifier for ES256, i.e. ECDSA with SHA-256 over P-256, defined in [RFC 7518 §3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4)). -Unlike most VC-DI cryptosuites whose `proofValue` is a single base64url-encoded byte string, this cryptosuite's `proofValue` is a JSON object with three fields: +The array SHOULD NOT contain entries for algorithms unsupported by this cryptosuite (e.g., `-257` RS256, `-8` EdDSA). If the authenticator selects an unsupported algorithm during credential creation, the resulting assertions will not verify and the credential will be unusable for `fido4vc-jcs-2026`. -| Field | Type | Encoding | Description | -|---|---|---|---| -| `signature` | string | base64url | The ECDSA signature emitted by the FIDO authenticator. DER-encoded `r ‖ s` (ASN.1 SEQUENCE). | -| `authenticatorData` | string | base64url | The WebAuthn `authenticatorData` byte string. Contains the RP ID hash, flags (including User Verification bit), and signature counter. | -| `clientData` | string | base64url | The WebAuthn `clientDataJSON` (raw UTF-8 JSON bytes, base64url-encoded). | +Other aspects of credential creation — RP/user identifier provisioning, attestation conveyance, resident-key requirements, authenticator-selection criteria — are WebAuthn-layer concerns and out of scope of this cryptosuite. See [W3C WebAuthn Level 3 §5.1.3](https://www.w3.org/TR/webauthn-3/#sctn-createCredential) for the full registration ceremony specification. -This structure is necessary because verification requires recomputing the WebAuthn signed bytes from `authenticatorData` and `clientDataJSON` — discarding either would make verification impossible. +## Instantiate Cryptosuite -## Sign algorithm +This algorithm configures the `fido4vc-jcs-2026` cryptographic suite to be used by the [Add Proof](https://www.w3.org/TR/vc-data-integrity/#add-proof) and [Verify Proof](https://www.w3.org/TR/vc-data-integrity/#verify-proof) functions of [Verifiable Credential Data Integrity 1.0](https://www.w3.org/TR/vc-data-integrity/). The algorithm takes an options object (map `options`) as input and returns a cryptosuite instance (struct `cryptosuite`). -The cryptosuite does **not** define a sign algorithm in the conventional sense. Signing is performed by a FIDO2 / WebAuthn authenticator outside the cryptosuite library, using the following sequence: +1. Initialize `cryptosuite` to an empty struct. +2. If `options.type` does not equal `DataIntegrityProof`, return `cryptosuite`. +3. If `options.cryptosuite` equals `fido4vc-jcs-2026`, then: + - Set `cryptosuite.createProof` to the algorithm in [§ Create Proof (fido4vc-jcs-2026)](#create-proof-fido4vc-jcs-2026). + - Set `cryptosuite.verifyProof` to the algorithm in [§ Verify Proof (fido4vc-jcs-2026)](#verify-proof-fido4vc-jcs-2026). +4. Return `cryptosuite`. -### Input +## Algorithms -- `document`: a JSON-LD Verifiable Presentation, complete except for `proof.proofValue`. +This cryptosuite uses [JSON Canonicalization Scheme](https://datatracker.ietf.org/doc/html/rfc8785) (JCS, RFC 8785) for canonicalization, SHA-256 for hashing, and ECDSA over P-256 with SHA-256 (ES256) executed inside a FIDO2/WebAuthn authenticator for signature production. The proof value packages the WebAuthn assertion (`authenticatorData`, `signature`, `clientDataJSON`) into a deterministic CBOR blob (RFC 8949 §4.2) encoded as a multibase base64url string (prefix `u`), conforming to W3C VC-DI §2.1. -### Procedure +### Transformation (fido4vc-jcs-2026) -1. Let `proof` = `document.proof`. Let `proofOptions` = `proof` minus the `proofValue` field. Let `docWithoutProof` = `document` minus the `proof` field. -2. Let `jcsDoc` = `JCS(docWithoutProof)` (UTF-8 byte string). -3. Let `jcsProof` = `JCS(proofOptions)` (UTF-8 byte string). -4. Let `challenge` = `SHA-256(jcsDoc ‖ jcsProof)`. -5. Let `challengeB64` = `base64url(challenge)` (without padding). -6. Invoke WebAuthn `navigator.credentials.get()` with options containing `challenge = challenge`, the relying party's `rpId`, the user's `allowCredentials`, and `userVerification = "required"`. -7. The authenticator returns `AuthenticatorAssertionResponse` with fields: - - `signature` — ECDSA signature over `authenticatorData ‖ SHA-256(clientDataJSON)` - - `authenticatorData` — RP-side ceremony bytes - - `clientDataJSON` — JSON containing `{type, challenge, origin, ...}` where `challenge` equals `challengeB64`. -8. Set `proof.proofValue` to `{signature, authenticatorData, clientData: clientDataJSON}`, all base64url-encoded. +The following algorithm specifies how to transform an unsecured input document into a transformed document ready to be provided as input to the [hashing algorithm](#hashing-fido4vc-jcs-2026). -### Notes +Required inputs are an [unsecured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-unsecured-data-document) (map `unsecuredDocument`) and transformation options (map `options`). The transformation options MUST contain a type identifier (`type`) for the cryptographic suite and a cryptosuite identifier (`cryptosuite`). A transformed data document (byte sequence) is produced as output. Whenever this algorithm encodes strings, it MUST use UTF-8 encoding. -- `challengeB64` MUST be reflected verbatim in `clientDataJSON.challenge`. This is enforced by the verifier — see [Verify algorithm](#verify-algorithm) below. -- The `challenge` field at `proof.challenge` (verifier-supplied nonce) is part of the canonicalized data and therefore folded into the WebAuthn challenge. It is not redundant — it carries verifier-supplied freshness that ends up cryptographically bound to the signature. +1. If `options.type` is not `DataIntegrityProof` or `options.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of `PROOF_TRANSFORMATION_ERROR`. +2. Let `transformedDocument` be the result of applying [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785) canonicalization to `unsecuredDocument`, producing a UTF-8 byte sequence. +3. Return `transformedDocument` as the transformed data document. -## Verify algorithm +### Hashing (fido4vc-jcs-2026) -### Input +The following algorithm specifies how to cryptographically hash a transformed data document and a canonical proof configuration into a hash value ready to be provided as input to the [proof serialization](#proof-serialization-fido4vc-jcs-2026) or [proof verification](#verify-proof-fido4vc-jcs-2026) algorithms. -- `document`: a JSON-LD Verifiable Presentation with a complete `proof.proofValue`. +Required inputs are a transformed data document (byte sequence `transformedDocument`) and a canonical proof configuration (byte sequence `canonicalProofConfig`). A single hash value (byte sequence `hashData`) is produced as output. -### Procedure +1. Let `combinedBytes` be the concatenation `transformedDocument ‖ canonicalProofConfig`. +2. Let `hashData` be the result of applying SHA-256 to `combinedBytes`. +3. Return `hashData` as the hash data. -1. **Recompute the expected challenge.** Repeat steps 1–5 of the sign algorithm: extract `proofOptions` and `docWithoutProof`, JCS-canonicalize each, concatenate, SHA-256, base64url-encode. Result: `expectedChallengeB64`. -2. **Extract proof value fields.** - - `signature` = base64url-decode(`proof.proofValue.signature`) - - `authenticatorData` = base64url-decode(`proof.proofValue.authenticatorData`) - - `clientData` = base64url-decode(`proof.proofValue.clientData`) -3. **Parse clientData.** Interpret `clientData` as UTF-8 JSON. Extract `clientData.challenge`. -4. **Bind challenge.** If `clientData.challenge ≠ expectedChallengeB64`, return `{verified: false, error: "Challenge mismatch"}`. -5. **Resolve verification method.** From `proof.verificationMethod` (a DID with optional `#fragment`), resolve to a public key in JWK form. - - For `did:jwk:`: let `jwkJson` = base64url-decode(`identifier`) (strip any `#fragment`); parse `jwkJson` as JSON; the result is the JWK. -6. **Verify signature.** Compute `signedBytes` = `authenticatorData ‖ SHA-256(clientData)`. Verify the ECDSA `signature` against the JWK public key over `signedBytes` using ES256. If verification fails, return `{verified: false, error: "Invalid signature"}`. -7. **Return success.** Return `{verified: true}`. +### Proof Configuration (fido4vc-jcs-2026) + +The following algorithm specifies how to generate a canonical proof configuration from a set of proof options. The output is used as input to the [hashing algorithm](#hashing-fido4vc-jcs-2026). + +Required inputs are proof options (map `options`) and the unsecured data document (map `unsecuredDocument`). The proof options MUST contain a type identifier (`type`) and a cryptosuite identifier (`cryptosuite`). A canonical proof configuration (byte sequence) is produced as output. + +1. Let `proofConfig` be a clone of `options`. +2. If `proofConfig.type` is not `DataIntegrityProof` or `proofConfig.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of `PROOF_GENERATION_ERROR`. +3. If `proofConfig.created` is set and the value is not a valid [[XMLSCHEMA11-2]] datetime, an error MUST be raised and SHOULD convey an error type of `PROOF_GENERATION_ERROR`. +4. Remove the `proofValue` property from `proofConfig`, if present. +5. Set `proofConfig.@context` to `unsecuredDocument.@context`. +6. Let `canonicalProofConfig` be the result of applying [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785) canonicalization to `proofConfig`. +7. Return `canonicalProofConfig`. + +### Proof Serialization (fido4vc-jcs-2026) + +The following algorithm specifies how to derive a proof value from hash data. The cryptographic signing operation is performed by a FIDO2/WebAuthn authenticator external to the cryptosuite library; the library prepares the WebAuthn challenge from `hashData` and packages the authenticator's response into the proof value. + +Required inputs are a hash value (byte sequence `hashData`) and proof options (map `options`). A proof value (string `proofValue`) is produced as output. + +1. Invoke a WebAuthn [`navigator.credentials.get()`](https://www.w3.org/TR/webauthn-3/#sctn-getAssertion) ceremony with [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictionary-assertion-options) containing: + - `challenge` set to `hashData`; + - the relying party identifier (`rpId`); + - the user's `allowCredentials`; + - `userVerification` set to `"required"`. +2. Let `assertion` be the resulting [`AuthenticatorAssertionResponse`](https://www.w3.org/TR/webauthn-3/#iface-authenticatorassertionresponse), containing: + - [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data) (byte sequence) — the WebAuthn authenticator data structure; + - [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson) (byte sequence) — the UTF-8 JSON encoding of [CollectedClientData](https://www.w3.org/TR/webauthn-3/#dictdef-collectedclientdata), which MUST include a `challenge` field whose value equals the base64url encoding of `challengeBytes`; + - `signature` (byte sequence) — DER-encoded ECDSA-P256-SHA-256 signature over `authenticatorData ‖ SHA-256(clientDataJSON)`. +3. Let `proofBytes` be the deterministic CBOR encoding ([RFC 8949 §4.2](https://www.rfc-editor.org/rfc/rfc8949#section-4.2)) of the 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`, in that fixed order. +4. Let `proofValue` be the multibase encoding of `proofBytes` using prefix `u` (base64url without padding, [RFC 4648 §5](https://www.rfc-editor.org/rfc/rfc4648#section-5)). +5. Return `proofValue`. + +### Create Proof (fido4vc-jcs-2026) + +The following algorithm specifies how to create a [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) given an unsecured data document. + +Required inputs are an unsecured data document (map `unsecuredDocument`) and a set of proof options (map `options`). A data integrity proof (map `proof`), or an error, is produced as output. + +1. Let `proof` be a clone of `options`. +2. Let `canonicalProofConfig` be the result of running the [Proof Configuration](#proof-configuration-fido4vc-jcs-2026) algorithm with `options` and `unsecuredDocument` passed as parameters. +3. Let `transformedData` be the result of running the [Transformation](#transformation-fido4vc-jcs-2026) algorithm with `unsecuredDocument` and `options` passed as parameters. +4. Let `hashData` be the result of running the [Hashing](#hashing-fido4vc-jcs-2026) algorithm with `transformedData` and `canonicalProofConfig` passed as parameters. +5. Let `proofValue` be the result of running the [Proof Serialization](#proof-serialization-fido4vc-jcs-2026) algorithm with `hashData` and `options` passed as parameters. +6. Set `proof.proofValue` to `proofValue`. +7. Return `proof` as the data integrity proof. + +### Verify Proof (fido4vc-jcs-2026) + +The following algorithm specifies how to verify a [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) given a secured data document. + +Required input is a [secured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-secured-data-document) (map `securedDocument`). The algorithm returns a verification result, a struct whose items are: + +- `verified` — `true` or `false` +- `verifiedDocument` — `Null` if `verified` is `false`; otherwise an [unsecured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-unsecured-data-document) + +When a step below says "an error MUST be raised", the algorithm MUST return a verification result with `verified` set to `false` and `verifiedDocument` set to `Null`, and SHOULD convey the specified error type. + +1. Let `unsecuredDocument` be a copy of `securedDocument` with the `proof` property removed. +2. Let `proof` be the value of `securedDocument.proof`. +3. **Validate proof purpose.** If `proof.proofPurpose` is not equal to `"authentication"`, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. (This cryptosuite is bound to the WebAuthn authentication primitive; other proof purposes such as `assertionMethod` are not supported.) +4. **Decode proof value.** + - If `proof.proofValue` is not a string beginning with the multibase prefix `u`, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. + - Strip the leading `u`; base64url-decode the remaining characters to obtain `proofBytes`. + - CBOR-decode `proofBytes` as a 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`. If the structure does not match, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. +5. Let `options` be a clone of `proof` with the `proofValue` property removed. +6. Let `canonicalProofConfig` be the result of running the [Proof Configuration](#proof-configuration-fido4vc-jcs-2026) algorithm with `options` and `unsecuredDocument` passed as parameters. +7. Let `transformedData` be the result of running the [Transformation](#transformation-fido4vc-jcs-2026) algorithm with `unsecuredDocument` and `options` passed as parameters. +8. Let `hashData` be the result of running the [Hashing](#hashing-fido4vc-jcs-2026) algorithm with `transformedData` and `canonicalProofConfig` passed as parameters. +9. **Parse clientDataJSON.** Interpret `clientDataJSON` as a UTF-8 JSON object. If parsing fails, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. +10. **Validate WebAuthn assertion type.** If `clientDataJSON.type` is not equal to `"webauthn.get"`, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. (This prevents confusion attacks where a registration-ceremony assertion is substituted for an authentication assertion.) +11. **Bind challenge.** If `clientDataJSON.challenge` does not equal the base64url encoding of `hashData`, an error MUST be raised and SHOULD convey an error type of `INVALID_CHALLENGE_ERROR`. +12. **Retrieve verification method.** Let `publicKey` be the public key associated with `proof.verificationMethod`, retrieved as described in [VC-DI §4 — Retrieve Verification Method](https://www.w3.org/TR/vc-data-integrity/#algorithms) and [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method). The dereferenced object MUST contain a `type` of `JsonWebKey` whose `publicKeyJwk` has `kty: "EC"` and `crv: "P-256"`; otherwise an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. +13. **Verify signature.** Let `signedBytes` be `authenticatorData ‖ SHA-256(clientDataJSON)`. Verify the ECDSA `signature` against `publicKey` over `signedBytes` using ES256. If verification fails, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. +14. Return a verification result with `verified` set to `true` and `verifiedDocument` set to `unsecuredDocument`. ### Notes for implementers - **DER vs. raw ECDSA.** WebAuthn authenticators emit DER-encoded ECDSA signatures (ASN.1 SEQUENCE of two INTEGERs). Java's `Signature.getInstance("SHA256withECDSA")` accepts DER directly. Node's WebCrypto `subtle.verify` requires raw `r ‖ s` and needs an unwrap step. -- **`clientDataJSON` parsing.** Be defensive about whitespace and field order in `clientDataJSON`. Per the WebAuthn spec, the authenticator produces a specific format, but extension fields may appear; only `challenge` is relied on. -- **Origin / type validation.** The base spec does not mandate validating `clientData.type === "webauthn.get"` or `clientData.origin` against the verifier's expected origin. Implementations MAY add these checks; production deployments SHOULD. Note that `origin` validation requires the verifier to know the wallet's origin a priori. -- **Counter / UV flag.** The `authenticatorData` byte string carries a signature counter and the User-Verification bit. Implementations MAY assert UV bit is set; production deployments SHOULD. Counter monotonicity is harder to enforce in this stateless verifier model — left as an open extension. +- **`clientDataJSON` parsing.** Be defensive about whitespace and field order in `clientDataJSON`. Per the WebAuthn spec, the authenticator produces a specific format, but extension fields may appear; only `challenge` is relied on by this cryptosuite. +- **WebAuthn ceremony validation is out of scope of this cryptosuite.** Validation of `rpId`, `clientDataJSON.origin`, the `authenticatorData` flags (UV, AT, BE, BS), signature-counter monotonicity, and attestation chain are WebAuthn-layer concerns, not cryptosuite concerns. Verifiers operating inside a full WebAuthn ceremony context SHOULD perform the additional checks described in [W3C WebAuthn Level 3 §7.2](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion). The cryptosuite makes one exception: `clientDataJSON.type` MUST equal `"webauthn.get"` (enforced in the [Verify Proof](#verify-proof-fido4vc-jcs-2026) algorithm) because the check is cheap and prevents a well-known confusion-attack class. +- **`proof.challenge` and `proof.domain`.** When present in `options`, these fields are folded into the canonical proof configuration by the [Proof Configuration](#proof-configuration-fido4vc-jcs-2026) algorithm and therefore become cryptographically bound to the signature via `hashData`. The WebAuthn `challenge` is `hashData` itself, not a separate verifier-supplied nonce. ## Test vectors From 76d6b8f31f9b27e868a17460e82f43b40bb3c6e4 Mon Sep 17 00:00:00 2001 From: tsaplia Date: Tue, 26 May 2026 16:57:56 +0200 Subject: [PATCH 2/3] Spec fix 1 --- src/content/docs/spec/fido4vc-jcs-2026.mdx | 60 +++++++++++----------- src/content/docs/spec/integration.mdx | 2 +- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/src/content/docs/spec/fido4vc-jcs-2026.mdx b/src/content/docs/spec/fido4vc-jcs-2026.mdx index 44e2056..e38acc6 100644 --- a/src/content/docs/spec/fido4vc-jcs-2026.mdx +++ b/src/content/docs/spec/fido4vc-jcs-2026.mdx @@ -24,7 +24,7 @@ This page is the normative specification for the `fido4vc-jcs-2026` cryptosuite. | **Signature algorithm** | ECDSA over P-256 with SHA-256 (ES256, [FIPS 186-5](https://csrc.nist.gov/pubs/fips/186-5/final), [RFC 7518 §3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4)) | | **Signing mechanism** | WebAuthn [`navigator.credentials.get()`](https://www.w3.org/TR/webauthn-3/#sctn-getAssertion) ceremony | | **Verification method type** | `JsonWebKey` with `kty: "EC"`, `crv: "P-256"` | -| **DID methods** | Any DID method whose resolution yields a conforming `JsonWebKey` (tested with `did:jwk`, `did:key`, `did:web`) | +| **DID methods** | Any DID method whose resolution yields a conforming `JsonWebKey` (tested with `did:jwk`) | The cryptosuite name follows the W3C VC-DI naming convention `--`: `fido4vc` identifies the underlying signing mechanism family, `jcs` identifies the canonicalization, `2026` is the year of registration. @@ -36,7 +36,7 @@ Implementations conforming to this specification MUST: 2. Use SHA-256 for all hashing operations. 3. Recognize `proofValue` as a multibase-encoded string (prefix `u`, base64url without padding per [RFC 4648 §5](https://www.rfc-editor.org/rfc/rfc4648#section-5)) whose decoded payload is a CBOR-encoded ([RFC 8949](https://www.rfc-editor.org/rfc/rfc8949)) 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`, in that fixed field order. 4. Validate the WebAuthn challenge binding before checking the signature. -5. Dereference `proof.verificationMethod` per [VC-DI §4 — Retrieve Verification Method](https://www.w3.org/TR/vc-data-integrity/#algorithms) and [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method) to a `JsonWebKey` whose `publicKeyJwk` has `kty: "EC"` and `crv: "P-256"`. The cryptosuite is DID-method-agnostic; any URL whose dereferencing yields such a key MAY be used (e.g., `did:jwk`, `did:key`, `did:web`). +5. Dereference `proof.verificationMethod` per [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method) to a `JsonWebKey` whose `publicKeyJwk` has `kty: "EC"` and `crv: "P-256"`. 6. Set `proof.proofPurpose` to `"authentication"`. WebAuthn is fundamentally an authentication primitive; verifiers MUST reject proofs whose `proofPurpose` has any other value. Implementations MAY: @@ -55,6 +55,8 @@ To ensure the resulting assertions are verifiable under `fido4vc-jcs-2026`, the The array SHOULD NOT contain entries for algorithms unsupported by this cryptosuite (e.g., `-257` RS256, `-8` EdDSA). If the authenticator selects an unsupported algorithm during credential creation, the resulting assertions will not verify and the credential will be unusable for `fido4vc-jcs-2026`. +After registration, the authenticator returns the credential's public key. The `proof.verificationMethod` in every proof produced by this credential MUST resolve to that public key — a [`JsonWebKey`](https://www.w3.org/TR/cid-1.0/#JsonWebKey) per [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method). For example, with `did:jwk` the DID encodes the JWK directly, so no separate provisioning step is required. + Other aspects of credential creation — RP/user identifier provisioning, attestation conveyance, resident-key requirements, authenticator-selection criteria — are WebAuthn-layer concerns and out of scope of this cryptosuite. See [W3C WebAuthn Level 3 §5.1.3](https://www.w3.org/TR/webauthn-3/#sctn-createCredential) for the full registration ceremony specification. ## Instantiate Cryptosuite @@ -70,62 +72,59 @@ This algorithm configures the `fido4vc-jcs-2026` cryptographic suite to be used ## Algorithms -This cryptosuite uses [JSON Canonicalization Scheme](https://datatracker.ietf.org/doc/html/rfc8785) (JCS, RFC 8785) for canonicalization, SHA-256 for hashing, and ECDSA over P-256 with SHA-256 (ES256) executed inside a FIDO2/WebAuthn authenticator for signature production. The proof value packages the WebAuthn assertion (`authenticatorData`, `signature`, `clientDataJSON`) into a deterministic CBOR blob (RFC 8949 §4.2) encoded as a multibase base64url string (prefix `u`), conforming to W3C VC-DI §2.1. +This cryptosuite uses [JSON Canonicalization Scheme](https://datatracker.ietf.org/doc/html/rfc8785) (JCS, RFC 8785) for canonicalization, SHA-256 for hashing, and ECDSA over P-256 with SHA-256 (ES256) executed inside a FIDO2/WebAuthn authenticator for signature production. The proof value packages the WebAuthn assertion (`authenticatorData`, `signature`, `clientDataJSON`) into a deterministic CBOR blob (RFC 8949 §4.2) encoded as a multibase base64url string (prefix `u`). -### Transformation (fido4vc-jcs-2026) +### Transformation The following algorithm specifies how to transform an unsecured input document into a transformed document ready to be provided as input to the [hashing algorithm](#hashing-fido4vc-jcs-2026). Required inputs are an [unsecured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-unsecured-data-document) (map `unsecuredDocument`) and transformation options (map `options`). The transformation options MUST contain a type identifier (`type`) for the cryptographic suite and a cryptosuite identifier (`cryptosuite`). A transformed data document (byte sequence) is produced as output. Whenever this algorithm encodes strings, it MUST use UTF-8 encoding. -1. If `options.type` is not `DataIntegrityProof` or `options.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of `PROOF_TRANSFORMATION_ERROR`. +1. If `options.type` is not `DataIntegrityProof` or `options.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of [`PROOF_TRANSFORMATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_TRANSFORMATION_ERROR). 2. Let `transformedDocument` be the result of applying [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785) canonicalization to `unsecuredDocument`, producing a UTF-8 byte sequence. 3. Return `transformedDocument` as the transformed data document. -### Hashing (fido4vc-jcs-2026) +### Hashing The following algorithm specifies how to cryptographically hash a transformed data document and a canonical proof configuration into a hash value ready to be provided as input to the [proof serialization](#proof-serialization-fido4vc-jcs-2026) or [proof verification](#verify-proof-fido4vc-jcs-2026) algorithms. Required inputs are a transformed data document (byte sequence `transformedDocument`) and a canonical proof configuration (byte sequence `canonicalProofConfig`). A single hash value (byte sequence `hashData`) is produced as output. -1. Let `combinedBytes` be the concatenation `transformedDocument ‖ canonicalProofConfig`. -2. Let `hashData` be the result of applying SHA-256 to `combinedBytes`. -3. Return `hashData` as the hash data. +1. Let `documentHash` be the result of applying SHA-256 to `transformedDocument`. +2. Let `proofConfigHash` be the result of applying SHA-256 to `canonicalProofConfig`. +3. Let `hashData` be the concatenation `documentHash ‖ proofConfigHash`. +4. Return `hashData` as the hash data. -### Proof Configuration (fido4vc-jcs-2026) +### Proof Configuration The following algorithm specifies how to generate a canonical proof configuration from a set of proof options. The output is used as input to the [hashing algorithm](#hashing-fido4vc-jcs-2026). Required inputs are proof options (map `options`) and the unsecured data document (map `unsecuredDocument`). The proof options MUST contain a type identifier (`type`) and a cryptosuite identifier (`cryptosuite`). A canonical proof configuration (byte sequence) is produced as output. 1. Let `proofConfig` be a clone of `options`. -2. If `proofConfig.type` is not `DataIntegrityProof` or `proofConfig.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of `PROOF_GENERATION_ERROR`. -3. If `proofConfig.created` is set and the value is not a valid [[XMLSCHEMA11-2]] datetime, an error MUST be raised and SHOULD convey an error type of `PROOF_GENERATION_ERROR`. +2. If `proofConfig.type` is not `DataIntegrityProof` or `proofConfig.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of [`PROOF_GENERATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_GENERATION_ERROR). +3. If `proofConfig.created` is set and the value is not a valid [[XMLSCHEMA11-2]] datetime, an error MUST be raised and SHOULD convey an error type of [`PROOF_GENERATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_GENERATION_ERROR). 4. Remove the `proofValue` property from `proofConfig`, if present. 5. Set `proofConfig.@context` to `unsecuredDocument.@context`. 6. Let `canonicalProofConfig` be the result of applying [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785) canonicalization to `proofConfig`. 7. Return `canonicalProofConfig`. -### Proof Serialization (fido4vc-jcs-2026) +### Proof Serialization The following algorithm specifies how to derive a proof value from hash data. The cryptographic signing operation is performed by a FIDO2/WebAuthn authenticator external to the cryptosuite library; the library prepares the WebAuthn challenge from `hashData` and packages the authenticator's response into the proof value. Required inputs are a hash value (byte sequence `hashData`) and proof options (map `options`). A proof value (string `proofValue`) is produced as output. -1. Invoke a WebAuthn [`navigator.credentials.get()`](https://www.w3.org/TR/webauthn-3/#sctn-getAssertion) ceremony with [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictionary-assertion-options) containing: - - `challenge` set to `hashData`; - - the relying party identifier (`rpId`); - - the user's `allowCredentials`; - - `userVerification` set to `"required"`. +1. Invoke a WebAuthn [`navigator.credentials.get()`](https://www.w3.org/TR/webauthn-3/#sctn-getAssertion) ceremony with [`PublicKeyCredentialRequestOptions`](https://www.w3.org/TR/webauthn-3/#dictionary-assertion-options) whose `challenge` is set to `hashData`. All other options (`rpId`, `allowCredentials`, `timeout`, `extensions`, etc.) are application-layer concerns outside the scope of this cryptosuite. Implementations SHOULD set `userVerification` to `"required"`. 2. Let `assertion` be the resulting [`AuthenticatorAssertionResponse`](https://www.w3.org/TR/webauthn-3/#iface-authenticatorassertionresponse), containing: - [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data) (byte sequence) — the WebAuthn authenticator data structure; - - [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson) (byte sequence) — the UTF-8 JSON encoding of [CollectedClientData](https://www.w3.org/TR/webauthn-3/#dictdef-collectedclientdata), which MUST include a `challenge` field whose value equals the base64url encoding of `challengeBytes`; + - [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson) (byte sequence) — the UTF-8 JSON encoding of [CollectedClientData](https://www.w3.org/TR/webauthn-3/#dictdef-collectedclientdata), which MUST include a `challenge` field whose value equals the base64url encoding of `hashData`; - `signature` (byte sequence) — DER-encoded ECDSA-P256-SHA-256 signature over `authenticatorData ‖ SHA-256(clientDataJSON)`. 3. Let `proofBytes` be the deterministic CBOR encoding ([RFC 8949 §4.2](https://www.rfc-editor.org/rfc/rfc8949#section-4.2)) of the 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`, in that fixed order. 4. Let `proofValue` be the multibase encoding of `proofBytes` using prefix `u` (base64url without padding, [RFC 4648 §5](https://www.rfc-editor.org/rfc/rfc4648#section-5)). 5. Return `proofValue`. -### Create Proof (fido4vc-jcs-2026) +### Create Proof The following algorithm specifies how to create a [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) given an unsecured data document. @@ -139,7 +138,7 @@ Required inputs are an unsecured data document (map `unsecuredDocument`) and a s 6. Set `proof.proofValue` to `proofValue`. 7. Return `proof` as the data integrity proof. -### Verify Proof (fido4vc-jcs-2026) +### Verify Proof The following algorithm specifies how to verify a [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) given a secured data document. @@ -152,28 +151,27 @@ When a step below says "an error MUST be raised", the algorithm MUST return a ve 1. Let `unsecuredDocument` be a copy of `securedDocument` with the `proof` property removed. 2. Let `proof` be the value of `securedDocument.proof`. -3. **Validate proof purpose.** If `proof.proofPurpose` is not equal to `"authentication"`, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. (This cryptosuite is bound to the WebAuthn authentication primitive; other proof purposes such as `assertionMethod` are not supported.) +3. **Validate proof purpose.** If `proof.proofPurpose` is not equal to `"authentication"`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). (This cryptosuite is bound to the WebAuthn authentication primitive; other proof purposes such as `assertionMethod` are not supported.) 4. **Decode proof value.** - - If `proof.proofValue` is not a string beginning with the multibase prefix `u`, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. + - If `proof.proofValue` is not a string beginning with the multibase prefix `u`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). - Strip the leading `u`; base64url-decode the remaining characters to obtain `proofBytes`. - - CBOR-decode `proofBytes` as a 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`. If the structure does not match, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. + - CBOR-decode `proofBytes` as a 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`. If the structure does not match, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). 5. Let `options` be a clone of `proof` with the `proofValue` property removed. 6. Let `canonicalProofConfig` be the result of running the [Proof Configuration](#proof-configuration-fido4vc-jcs-2026) algorithm with `options` and `unsecuredDocument` passed as parameters. 7. Let `transformedData` be the result of running the [Transformation](#transformation-fido4vc-jcs-2026) algorithm with `unsecuredDocument` and `options` passed as parameters. 8. Let `hashData` be the result of running the [Hashing](#hashing-fido4vc-jcs-2026) algorithm with `transformedData` and `canonicalProofConfig` passed as parameters. -9. **Parse clientDataJSON.** Interpret `clientDataJSON` as a UTF-8 JSON object. If parsing fails, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. -10. **Validate WebAuthn assertion type.** If `clientDataJSON.type` is not equal to `"webauthn.get"`, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. (This prevents confusion attacks where a registration-ceremony assertion is substituted for an authentication assertion.) -11. **Bind challenge.** If `clientDataJSON.challenge` does not equal the base64url encoding of `hashData`, an error MUST be raised and SHOULD convey an error type of `INVALID_CHALLENGE_ERROR`. -12. **Retrieve verification method.** Let `publicKey` be the public key associated with `proof.verificationMethod`, retrieved as described in [VC-DI §4 — Retrieve Verification Method](https://www.w3.org/TR/vc-data-integrity/#algorithms) and [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method). The dereferenced object MUST contain a `type` of `JsonWebKey` whose `publicKeyJwk` has `kty: "EC"` and `crv: "P-256"`; otherwise an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. -13. **Verify signature.** Let `signedBytes` be `authenticatorData ‖ SHA-256(clientDataJSON)`. Verify the ECDSA `signature` against `publicKey` over `signedBytes` using ES256. If verification fails, an error MUST be raised and SHOULD convey an error type of `PROOF_VERIFICATION_ERROR`. +9. **Parse clientDataJSON.** Interpret `clientDataJSON` as a UTF-8 JSON object. If parsing fails, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). +10. **Validate WebAuthn assertion type.** If `clientDataJSON.type` is not equal to `"webauthn.get"`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). (This prevents confusion attacks where a registration-ceremony assertion is substituted for an authentication assertion.) +11. **Bind challenge.** If `clientDataJSON.challenge` does not equal the base64url encoding of `hashData`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). +12. **Retrieve verification method.** Let `publicKey` be the public key associated with `proof.verificationMethod`, retrieved as described in [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method). The dereferenced object MUST contain a `type` of `JsonWebKey` whose `publicKeyJwk` has `kty: "EC"` and `crv: "P-256"`; otherwise an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). +13. **Verify signature.** Let `signedBytes` be `authenticatorData ‖ SHA-256(clientDataJSON)`. Verify the ECDSA `signature` against `publicKey` over `signedBytes` using ES256. If verification fails, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). 14. Return a verification result with `verified` set to `true` and `verifiedDocument` set to `unsecuredDocument`. ### Notes for implementers -- **DER vs. raw ECDSA.** WebAuthn authenticators emit DER-encoded ECDSA signatures (ASN.1 SEQUENCE of two INTEGERs). Java's `Signature.getInstance("SHA256withECDSA")` accepts DER directly. Node's WebCrypto `subtle.verify` requires raw `r ‖ s` and needs an unwrap step. +- **Two distinct "challenge" values.** `proof.challenge` is a verifier-supplied nonce placed in the VC-DI proof options; it is folded into `canonicalProofConfig` and therefore into `hashData`, binding the proof to a specific verifier session. The *WebAuthn challenge* is `hashData` itself — the 64-byte binary value passed to `navigator.credentials.get()`. - **`clientDataJSON` parsing.** Be defensive about whitespace and field order in `clientDataJSON`. Per the WebAuthn spec, the authenticator produces a specific format, but extension fields may appear; only `challenge` is relied on by this cryptosuite. - **WebAuthn ceremony validation is out of scope of this cryptosuite.** Validation of `rpId`, `clientDataJSON.origin`, the `authenticatorData` flags (UV, AT, BE, BS), signature-counter monotonicity, and attestation chain are WebAuthn-layer concerns, not cryptosuite concerns. Verifiers operating inside a full WebAuthn ceremony context SHOULD perform the additional checks described in [W3C WebAuthn Level 3 §7.2](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion). The cryptosuite makes one exception: `clientDataJSON.type` MUST equal `"webauthn.get"` (enforced in the [Verify Proof](#verify-proof-fido4vc-jcs-2026) algorithm) because the check is cheap and prevents a well-known confusion-attack class. -- **`proof.challenge` and `proof.domain`.** When present in `options`, these fields are folded into the canonical proof configuration by the [Proof Configuration](#proof-configuration-fido4vc-jcs-2026) algorithm and therefore become cryptographically bound to the signature via `hashData`. The WebAuthn `challenge` is `hashData` itself, not a separate verifier-supplied nonce. ## Test vectors diff --git a/src/content/docs/spec/integration.mdx b/src/content/docs/spec/integration.mdx index 6c5294b..2a1a2e0 100644 --- a/src/content/docs/spec/integration.mdx +++ b/src/content/docs/spec/integration.mdx @@ -142,7 +142,7 @@ A successful flow: wallet submits a presentation with a `fido4vc-jcs-2026` proof If you're not using walt.id, the integration is conceptually identical: 1. **Detect the cryptosuite.** When your verifier sees a VP with `proof.type = "DataIntegrityProof"` and `proof.cryptosuite = "fido4vc-jcs-2026"`, route to the verification logic. -2. **Verify per the spec.** Run the [verify algorithm](/spec/fido4vc-jcs-2026/#verify-algorithm). Either: +2. **Verify per the spec.** Run the [verify algorithm](/spec/fido4vc-jcs-2026/#verify-proof). Either: - Call the sidecar over HTTP, or - Import the TS reference implementation directly (for Node-based verifier stacks). 3. **Return success/failure to your verification pipeline.** The cryptosuite verification is just one step — your pipeline still validates the embedded VCs, presentation definition, freshness, etc. From 8dfa167dc5272cc12e04eaba173679746830d1a8 Mon Sep 17 00:00:00 2001 From: Yunna Kheifets Date: Thu, 28 May 2026 16:44:34 +0200 Subject: [PATCH 3/3] Rewrite spec to standard format --- src/content/docs/spec/fido4vc-jcs-2026.mdx | 280 +++++++++++++-------- src/content/docs/spec/integration.mdx | 8 +- 2 files changed, 176 insertions(+), 112 deletions(-) diff --git a/src/content/docs/spec/fido4vc-jcs-2026.mdx b/src/content/docs/spec/fido4vc-jcs-2026.mdx index e38acc6..002e892 100644 --- a/src/content/docs/spec/fido4vc-jcs-2026.mdx +++ b/src/content/docs/spec/fido4vc-jcs-2026.mdx @@ -5,111 +5,203 @@ sidebar: order: 1 --- -This page is the normative specification for the `fido4vc-jcs-2026` cryptosuite. Implementers should be able to produce interoperable signers and verifiers from this page alone. +## 1. Introduction -## Overview +This specification defines a cryptographic suite for the purpose of creating and verifying proofs produced by a FIDO2 / WebAuthn authenticator, in conformance with the Data Integrity [VC-DATA-INTEGRITY](https://www.w3.org/TR/vc-data-integrity/) specification. Signatures are produced by the Elliptic Curve Digital Signature Algorithm (ECDSA) as specified in [FIPS-186-5](https://csrc.nist.gov/pubs/fips/186-5/final). Unlike conventional cryptosuites, the signing operation is not performed by a cryptosuite library: it is delegated to an external FIDO authenticator over a [WebAuthn](https://www.w3.org/TR/webauthn-3/) ceremony, so that the holder's private key never leaves tamper-resistant hardware and every signature is gated behind a user-verification gesture. This lets credential holders authenticate Verifiable Presentations with the same hardware-backed Passkeys they already use elsewhere. -`fido4vc-jcs-2026` is a [W3C VC Data Integrity](https://www.w3.org/TR/vc-data-integrity/) cryptosuite. It maps a FIDO2 / WebAuthn assertion produced by an external authenticator into a `DataIntegrityProof` value attached to a Verifiable Presentation (or, in principle, any signed JSON-LD document). +This suite provides two high-level functions that work within the issuer, holder, verifier model. A holder uses the `createProof` function to authenticate a document — typically a Verifiable Presentation — by canonicalizing it into a WebAuthn challenge, invoking the authenticator over a [`navigator.credentials.get()`](https://www.w3.org/TR/webauthn-3/#sctn-getAssertion) ceremony, and packaging the resulting assertion (`authenticatorData`, `clientDataJSON`, and the ECDSA `signature`) into a single `DataIntegrityProof`. A verifier uses the `verifyProof` function to reconstruct the same challenge from the secured document and verify the ECDSA signature against the holder's public key. The signing key is the holder's WebAuthn credential, created out-of-band during a registration ceremony (see [§3.9 Credential Registration](#39-credential-registration)). -**Verify-only by design.** Signing is performed *outside* a cryptosuite library — by a FIDO authenticator over a WebAuthn ceremony. The cryptosuite specifies how to construct the WebAuthn challenge from the document so the resulting assertion can be embedded as a proof and later verified. +In general the suite uses the JSON Canonicalization Scheme [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) to transform an input document into its canonical form. The canonical form, together with the canonicalized proof options, is hashed to form the WebAuthn challenge. The authenticator then signs over this challenge using ECDSA on a NIST curve (P-256 or P-384), and the resulting assertion is serialized into the proof value as a deterministic CBOR structure encoded with multibase. -## Identifiers +### 1.1 Terminology -| Property | Value | -|---|---| -| **Cryptosuite name** | `fido4vc-jcs-2026` | -| **Proof type** | `DataIntegrityProof` | -| **Canonicalization** | [JSON Canonicalization Scheme (RFC 8785)](https://www.rfc-editor.org/rfc/rfc8785) | -| **Hash algorithm** | SHA-256 ([FIPS 180-4](https://csrc.nist.gov/publications/detail/fips/180/4/final)) | -| **Signature algorithm** | ECDSA over P-256 with SHA-256 (ES256, [FIPS 186-5](https://csrc.nist.gov/pubs/fips/186-5/final), [RFC 7518 §3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4)) | -| **Signing mechanism** | WebAuthn [`navigator.credentials.get()`](https://www.w3.org/TR/webauthn-3/#sctn-getAssertion) ceremony | -| **Verification method type** | `JsonWebKey` with `kty: "EC"`, `crv: "P-256"` | -| **DID methods** | Any DID method whose resolution yields a conforming `JsonWebKey` (tested with `did:jwk`) | +Terminology used throughout this document is defined in the [Terminology](https://www.w3.org/TR/vc-data-integrity/#terminology) section of the [Verifiable Credential Data Integrity 1.0](https://www.w3.org/TR/vc-data-integrity/) specification. WebAuthn-specific terms (`authenticatorData`, `clientDataJSON`, `AuthenticatorAssertionResponse`, relying party) are used as defined in [Web Authentication: An API for accessing Public Key Credentials Level 3](https://www.w3.org/TR/webauthn-3/). -The cryptosuite name follows the W3C VC-DI naming convention `--`: `fido4vc` identifies the underlying signing mechanism family, `jcs` identifies the canonicalization, `2026` is the year of registration. +### 1.2 Conformance -## Conformance requirements +As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative. -Implementations conforming to this specification MUST: +The key words MAY, MUST, MUST NOT, SHOULD, and SHOULD NOT in this document are to be interpreted as described in [BCP 14](https://www.rfc-editor.org/info/bcp14) [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174) when, and only when, they appear in all capitals, as shown here. -1. Use [RFC 8785 (JCS)](https://www.rfc-editor.org/rfc/rfc8785) for canonicalization. Implementations SHOULD use Erdtman's reference implementation (`canonicalize` on npm, `io.github.erdtman:java-json-canonicalization` on Maven Central) to ensure bit-identical canonicalization across language bindings. -2. Use SHA-256 for all hashing operations. -3. Recognize `proofValue` as a multibase-encoded string (prefix `u`, base64url without padding per [RFC 4648 §5](https://www.rfc-editor.org/rfc/rfc4648#section-5)) whose decoded payload is a CBOR-encoded ([RFC 8949](https://www.rfc-editor.org/rfc/rfc8949)) 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`, in that fixed field order. -4. Validate the WebAuthn challenge binding before checking the signature. -5. Dereference `proof.verificationMethod` per [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method) to a `JsonWebKey` whose `publicKeyJwk` has `kty: "EC"` and `crv: "P-256"`. -6. Set `proof.proofPurpose` to `"authentication"`. WebAuthn is fundamentally an authentication primitive; verifiers MUST reject proofs whose `proofPurpose` has any other value. +A **conforming proof** is any concrete expression of the data model that complies with the normative statements in this specification. Specifically, all relevant normative statements in Sections [2. Data Model](#2-data-model) and [3. Algorithms](#3-algorithms) of this document MUST be enforced. -Implementations MAY: +A **conforming processor** is any algorithm realized as software and/or hardware that generates or consumes a conforming proof. Conforming processors MUST produce errors when non-conforming documents are consumed. -1. Support additional EC curves beyond P-256 (e.g., P-384) via extension. Such implementations MUST register a distinct cryptosuite name. -2. Support attested authenticators (validate `attestationObject` chain). The base spec leaves attestation out-of-scope. +This document contains examples of JSON and JSON-LD data. Some of these examples are invalid JSON, as they include features such as inline comments (`//`) and ellipses (`...`) indicating the omission of information that is irrelevant to the example. These parts would have to be removed to treat the examples as valid JSON or JSON-LD. -## Credential registration +## 2. Data Model -This cryptosuite specifies how to verify WebAuthn assertions; it does not perform credential creation. Before [Proof Serialization](#proof-serialization-fido4vc-jcs-2026) can run, a WebAuthn credential MUST already exist on the user's authenticator. The credential is created out-of-band, prior to any signing ceremony, via the WebAuthn [`navigator.credentials.create()`](https://www.w3.org/TR/webauthn-3/#sctn-createCredential) ceremony. +The following sections outline the data model used by this specification to express verification methods, such as cryptographic public keys, and data integrity proofs, such as digital signatures. -To ensure the resulting assertions are verifiable under `fido4vc-jcs-2026`, the registration ceremony MUST constrain the credential to this cryptosuite's signature algorithm. The [`PublicKeyCredentialCreationOptions.pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-pubkeycredparams) array MUST contain at least one entry whose: +### 2.1 Verification Methods -- `type` is `"public-key"`; -- `alg` is `-7` (the [COSE Algorithm](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) identifier for ES256, i.e. ECDSA with SHA-256 over P-256, defined in [RFC 7518 §3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4)). +This cryptographic suite is used to verify Data Integrity Proofs [VC-DATA-INTEGRITY](https://www.w3.org/TR/vc-data-integrity/) produced using Elliptic Curve cryptographic key material that is compliant with [FIPS-186-5](https://csrc.nist.gov/pubs/fips/186-5/final). The encoding formats for these key types are provided in this section. Lossless cryptographic key transformation processes that result in equivalent cryptographic key material MAY be used for the processing of digital signatures; for example, a verifier MAY accept the same public key expressed either as a `JsonWebKey` or as a `Multikey`. -The array SHOULD NOT contain entries for algorithms unsupported by this cryptosuite (e.g., `-257` RS256, `-8` EdDSA). If the authenticator selects an unsupported algorithm during credential creation, the resulting assertions will not verify and the credential will be unusable for `fido4vc-jcs-2026`. +The public key MUST be a NIST P-256 (secp256r1) or P-384 (secp384r1) key. A verification method that resolves to any other curve or key type — for example RSA or Ed25519 — MUST be rejected. -After registration, the authenticator returns the credential's public key. The `proof.verificationMethod` in every proof produced by this credential MUST resolve to that public key — a [`JsonWebKey`](https://www.w3.org/TR/cid-1.0/#JsonWebKey) per [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method). For example, with `did:jwk` the DID encodes the JWK directly, so no separate provisioning step is required. +#### 2.1.1 JsonWebKey -Other aspects of credential creation — RP/user identifier provisioning, attestation conveyance, resident-key requirements, authenticator-selection criteria — are WebAuthn-layer concerns and out of scope of this cryptosuite. See [W3C WebAuthn Level 3 §5.1.3](https://www.w3.org/TR/webauthn-3/#sctn-createCredential) for the full registration ceremony specification. +The [JsonWebKey](https://www.w3.org/TR/cid-1.0/#JsonWebKey) format, defined in [Controlled Identifiers v1.0](https://www.w3.org/TR/cid-1.0/), is used to express public keys for this cryptographic suite. The `publicKeyJwk` value of the verification method MUST be a JSON Web Key [RFC 7517](https://www.rfc-editor.org/rfc/rfc7517) whose: -## Instantiate Cryptosuite +- `kty` is `"EC"`; +- `crv` is `"P-256"` or `"P-384"`. + +The `publicKeyJwk` SHOULD have `alg` set to the algorithm matching the curve (`"ES256"` for P-256, `"ES384"` for P-384) where the property is present. Any verification method whose `publicKeyJwk` does not satisfy these constraints MUST be rejected. + +```json +// Example: a P-256 public key encoded as a JsonWebKey +{ + "id": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Ii4uLn0#0", + "type": "JsonWebKey", + "controller": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Ii4uLn0", + "publicKeyJwk": { + "kty": "EC", + "crv": "P-256", + "alg": "ES256", + "x": "...", + "y": "..." + } +} +``` + +#### 2.1.2 Multikey + +The [Multikey](https://www.w3.org/TR/cid-1.0/#Multikey) format, defined in [Controlled Identifiers v1.0](https://www.w3.org/TR/cid-1.0/), MAY also be used to express public keys for this cryptographic suite. The `publicKeyMultibase` property represents a Multibase-encoded Multikey expression of a P-256 or P-384 public key. The `publicKeyMultibase` value of the verification method MUST start with the base-58-btc prefix (`z`), as defined in the [Multibase section](https://www.w3.org/TR/cid-1.0/#multibase-0) of [Controlled Identifiers v1.0](https://www.w3.org/TR/cid-1.0/). A Multibase-encoded ECDSA 256-bit public key value or an ECDSA 384-bit public key value follows, as defined in the [Multikey section](https://www.w3.org/TR/cid-1.0/#Multikey) of [Controlled Identifiers v1.0](https://www.w3.org/TR/cid-1.0/). Any other encoding MUST NOT be allowed. + +Implementations of this specification will raise errors in the event of a Multicodec value other than `0x1200` (`p256-pub`) or `0x1201` (`p384-pub`) being used in a `publicKeyMultibase` value. + +```json +// Example: a P-256 public key encoded as a Multikey +{ + "id": "did:example:123#key-0", + "type": "Multikey", + "controller": "did:example:123", + "publicKeyMultibase": "zDnaerx9CtbPJ1q36T5Ln5wYt3MQYeGRG5ehnPAmxcf5mDZpv" +} +``` + +### 2.2 Proof Representations + +This section details the proof representation format defined by this specification. + +#### 2.2.1 DataIntegrityProof + +A proof contains the attributes specified in the [Proofs section](https://www.w3.org/TR/vc-data-integrity/#proofs) of [VC-DATA-INTEGRITY](https://www.w3.org/TR/vc-data-integrity/) with the following restrictions. + +The `type` property MUST be `DataIntegrityProof`. + +The `cryptosuite` property of the proof MUST be `fido4vc-jcs-2026`. + +The `proofPurpose` property MUST be `authentication`. WebAuthn is fundamentally an authentication primitive; verifiers MUST reject proofs whose `proofPurpose` has any other value. + +The `verificationMethod` property MUST be a URL. Dereferencing it per [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method) MUST return a verification method whose `type` is either `JsonWebKey` or `Multikey`, representing a P-256 or P-384 public key and satisfying the constraints in [§2.1 Verification Methods](#21-verification-methods), and listed under the controller document's `authentication` verification relationship. + +The `proofValue` property MUST be a multibase-encoded string using the base64url-no-pad header (`u`), as defined in the [Multibase section](https://www.w3.org/TR/cid-1.0/#multibase-0) of [Controlled Identifiers v1.0](https://www.w3.org/TR/cid-1.0/) and [RFC 4648 §5](https://www.rfc-editor.org/rfc/rfc4648#section-5). The decoded payload MUST be the deterministic CBOR encoding [RFC 8949 §4.2](https://www.rfc-editor.org/rfc/rfc8949#section-4.2) of a 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`, in that fixed field order. + +```json +// Example: a WebAuthn assertion expressed as a DataIntegrityProof +{ + "@context": [ + "https://www.w3.org/ns/credentials/v2" + ], + "proof": { + "type": "DataIntegrityProof", + "cryptosuite": "fido4vc-jcs-2026", + "created": "2026-01-15T19:23:24Z", + "verificationMethod": "did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Ii4uLn0#0", + "proofPurpose": "authentication", + "challenge": "f8c1e2...", + "proofValue": "uoWNhdXRoRGF0YVg..." + } +} +``` + +## 3. Algorithms + +This cryptosuite uses the JSON Canonicalization Scheme (JCS) [RFC 8785](https://www.rfc-editor.org/rfc/rfc8785) for canonicalization, SHA-256 for hashing, and ECDSA over P-256 with SHA-256 (ES256) executed inside a FIDO2/WebAuthn authenticator for signature production. The proof value packages the WebAuthn assertion (`authenticatorData`, `signature`, `clientDataJSON`) into a deterministic CBOR blob and encodes it as a multibase base64url string (prefix `u`). + +### 3.1 Instantiate Cryptosuite This algorithm configures the `fido4vc-jcs-2026` cryptographic suite to be used by the [Add Proof](https://www.w3.org/TR/vc-data-integrity/#add-proof) and [Verify Proof](https://www.w3.org/TR/vc-data-integrity/#verify-proof) functions of [Verifiable Credential Data Integrity 1.0](https://www.w3.org/TR/vc-data-integrity/). The algorithm takes an options object (map `options`) as input and returns a cryptosuite instance (struct `cryptosuite`). 1. Initialize `cryptosuite` to an empty struct. 2. If `options.type` does not equal `DataIntegrityProof`, return `cryptosuite`. 3. If `options.cryptosuite` equals `fido4vc-jcs-2026`, then: - - Set `cryptosuite.createProof` to the algorithm in [§ Create Proof (fido4vc-jcs-2026)](#create-proof-fido4vc-jcs-2026). - - Set `cryptosuite.verifyProof` to the algorithm in [§ Verify Proof (fido4vc-jcs-2026)](#verify-proof-fido4vc-jcs-2026). + - Set `cryptosuite.createProof` to the algorithm in [§3.2 Create Proof](#32-create-proof). + - Set `cryptosuite.verifyProof` to the algorithm in [§3.3 Verify Proof](#33-verify-proof). 4. Return `cryptosuite`. -## Algorithms +### 3.2 Create Proof -This cryptosuite uses [JSON Canonicalization Scheme](https://datatracker.ietf.org/doc/html/rfc8785) (JCS, RFC 8785) for canonicalization, SHA-256 for hashing, and ECDSA over P-256 with SHA-256 (ES256) executed inside a FIDO2/WebAuthn authenticator for signature production. The proof value packages the WebAuthn assertion (`authenticatorData`, `signature`, `clientDataJSON`) into a deterministic CBOR blob (RFC 8949 §4.2) encoded as a multibase base64url string (prefix `u`). +The following algorithm specifies how to create a [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) given an unsecured data document. Required inputs are an [unsecured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-unsecured-data-document) (map `unsecuredDocument`) and a set of proof options (map `options`). A [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) (map `proof`), or an error, is produced as output. -### Transformation +1. Let `proof` be a clone of the proof options, `options`. +2. Let `proofConfig` be the result of running the [§3.5 Proof Configuration](#35-proof-configuration) algorithm with `options` and `unsecuredDocument` passed as parameters. +3. Let `transformedData` be the result of running the [§3.4 Transformation](#34-transformation) algorithm with `unsecuredDocument` and `options` passed as parameters. +4. Let `hashData` be the result of running the [§3.6 Hashing](#36-hashing) algorithm with `transformedData` and `proofConfig` passed as parameters. +5. Let `proofValue` be the result of running the [§3.7 Proof Serialization](#37-proof-serialization) algorithm with `hashData` and `options` passed as parameters. +6. Set `proof.proofValue` to `proofValue`. +7. Return `proof` as the data integrity proof. -The following algorithm specifies how to transform an unsecured input document into a transformed document ready to be provided as input to the [hashing algorithm](#hashing-fido4vc-jcs-2026). +### 3.3 Verify Proof -Required inputs are an [unsecured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-unsecured-data-document) (map `unsecuredDocument`) and transformation options (map `options`). The transformation options MUST contain a type identifier (`type`) for the cryptographic suite and a cryptosuite identifier (`cryptosuite`). A transformed data document (byte sequence) is produced as output. Whenever this algorithm encodes strings, it MUST use UTF-8 encoding. +The following algorithm specifies how to verify a [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) given a secured data document. Required input is a [secured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-secured-data-document) (map `securedDocument`). This algorithm returns a verification result, a struct whose items are: -1. If `options.type` is not `DataIntegrityProof` or `options.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of [`PROOF_TRANSFORMATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_TRANSFORMATION_ERROR). -2. Let `transformedDocument` be the result of applying [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785) canonicalization to `unsecuredDocument`, producing a UTF-8 byte sequence. -3. Return `transformedDocument` as the transformed data document. +- `verified` — `true` or `false` +- `verifiedDocument` — if `verified` is `false`, `Null`; otherwise an [unsecured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-unsecured-data-document) -### Hashing +When a step below says "an error MUST be raised", the algorithm MUST return a verification result with `verified` set to `false` and `verifiedDocument` set to `Null`, and SHOULD convey the specified error type. -The following algorithm specifies how to cryptographically hash a transformed data document and a canonical proof configuration into a hash value ready to be provided as input to the [proof serialization](#proof-serialization-fido4vc-jcs-2026) or [proof verification](#verify-proof-fido4vc-jcs-2026) algorithms. +1. Let `unsecuredDocument` be a copy of `securedDocument` with the `proof` property removed. +2. Let `proof` be the value of `securedDocument.proof`. +3. If `proof.proofPurpose` is not equal to `"authentication"`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). +4. Decode proof value. + - If `proof.proofValue` is not a string beginning with the multibase prefix `u`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). + - Strip the leading `u`; base64url-decode the remaining characters to obtain `proofBytes`. + - CBOR-decode `proofBytes` as a 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`. If the structure does not match, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). +5. Let `options` be a clone of `proof` with the `proofValue` property removed. +6. Let `proofConfig` be the result of running the [§3.5 Proof Configuration](#35-proof-configuration) algorithm with `options` and `unsecuredDocument` passed as parameters. +7. Let `transformedData` be the result of running the [§3.4 Transformation](#34-transformation) algorithm with `unsecuredDocument` and `options` passed as parameters. +8. Let `hashData` be the result of running the [§3.6 Hashing](#36-hashing) algorithm with `transformedData` and `proofConfig` passed as parameters. +9. Let `verified` be the result of running the [§3.8 Proof Verification](#38-proof-verification) algorithm with `hashData`, `authenticatorData`, `signature`, `clientDataJSON`, and `options` passed as parameters. +10. Return a verification result with `verified` set to the value produced in the previous step, and `verifiedDocument` set to `unsecuredDocument` if `verified` is `true`, otherwise `Null`. -Required inputs are a transformed data document (byte sequence `transformedDocument`) and a canonical proof configuration (byte sequence `canonicalProofConfig`). A single hash value (byte sequence `hashData`) is produced as output. +### 3.4 Transformation -1. Let `documentHash` be the result of applying SHA-256 to `transformedDocument`. -2. Let `proofConfigHash` be the result of applying SHA-256 to `canonicalProofConfig`. -3. Let `hashData` be the concatenation `documentHash ‖ proofConfigHash`. -4. Return `hashData` as the hash data. +The following algorithm specifies how to transform an unsecured input document into a transformed document ready to be provided as input to the [hashing algorithm](#36-hashing). + +Required inputs are an [unsecured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-unsecured-data-document) (map `unsecuredDocument`) and transformation options (map `options`). The transformation options MUST contain a type identifier (`type`) for the cryptographic suite and a cryptosuite identifier (`cryptosuite`). A transformed data document (byte sequence) is produced as output. Whenever this algorithm encodes strings, it MUST use UTF-8 encoding. -### Proof Configuration +1. If `options.type` is not `DataIntegrityProof` or `options.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of [`PROOF_TRANSFORMATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_TRANSFORMATION_ERROR). +2. Let `transformedDocument` be the result of applying [RFC 8785 (JCS)](https://www.rfc-editor.org/rfc/rfc8785) canonicalization to `unsecuredDocument`, producing a UTF-8 byte sequence. +3. Return `transformedDocument` as the transformed data document. -The following algorithm specifies how to generate a canonical proof configuration from a set of proof options. The output is used as input to the [hashing algorithm](#hashing-fido4vc-jcs-2026). +### 3.5 Proof Configuration + +The following algorithm specifies how to generate a canonical proof configuration from a set of proof options. The output is used as input to the [hashing algorithm](#36-hashing). Required inputs are proof options (map `options`) and the unsecured data document (map `unsecuredDocument`). The proof options MUST contain a type identifier (`type`) and a cryptosuite identifier (`cryptosuite`). A canonical proof configuration (byte sequence) is produced as output. 1. Let `proofConfig` be a clone of `options`. 2. If `proofConfig.type` is not `DataIntegrityProof` or `proofConfig.cryptosuite` is not `fido4vc-jcs-2026`, an error MUST be raised and SHOULD convey an error type of [`PROOF_GENERATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_GENERATION_ERROR). -3. If `proofConfig.created` is set and the value is not a valid [[XMLSCHEMA11-2]] datetime, an error MUST be raised and SHOULD convey an error type of [`PROOF_GENERATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_GENERATION_ERROR). +3. If `proofConfig.created` is set and the value is not a valid [XMLSCHEMA11-2](https://www.w3.org/TR/xmlschema11-2/) datetime, an error MUST be raised and SHOULD convey an error type of [`PROOF_GENERATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_GENERATION_ERROR). 4. Remove the `proofValue` property from `proofConfig`, if present. -5. Set `proofConfig.@context` to `unsecuredDocument.@context`. -6. Let `canonicalProofConfig` be the result of applying [RFC 8785 (JCS)](https://datatracker.ietf.org/doc/html/rfc8785) canonicalization to `proofConfig`. +5. Set `proofConfig.@context` to `unsecuredDocument.@context`. +6. Let `canonicalProofConfig` be the result of applying [RFC 8785 (JCS)](https://www.rfc-editor.org/rfc/rfc8785) canonicalization to `proofConfig`. 7. Return `canonicalProofConfig`. -### Proof Serialization +### 3.6 Hashing + +The following algorithm specifies how to cryptographically hash a transformed data document and a canonical proof configuration into a hash value ready to be provided as input to the [proof serialization](#37-proof-serialization) or [proof verification](#38-proof-verification) algorithms. + +Required inputs are a transformed data document (byte sequence `transformedDocument`) and a canonical proof configuration (byte sequence `canonicalProofConfig`). A single hash value (byte sequence `hashData`) is produced as output. + +1. Let `proofConfigHash` be the result of applying SHA-256 to `canonicalProofConfig`. `proofConfigHash` will be exactly 32 bytes in size. +2. Let `documentHash` be the result of applying SHA-256 to `transformedDocument`. `documentHash` will be exactly 32 bytes in size. +3. Let `hashData` be the result of concatenating `proofConfigHash` (the first hash) followed by `documentHash` (the second hash). +4. Return `hashData` as the hash data. + +### 3.7 Proof Serialization The following algorithm specifies how to derive a proof value from hash data. The cryptographic signing operation is performed by a FIDO2/WebAuthn authenticator external to the cryptosuite library; the library prepares the WebAuthn challenge from `hashData` and packages the authenticator's response into the proof value. @@ -119,73 +211,51 @@ Required inputs are a hash value (byte sequence `hashData`) and proof options (m 2. Let `assertion` be the resulting [`AuthenticatorAssertionResponse`](https://www.w3.org/TR/webauthn-3/#iface-authenticatorassertionresponse), containing: - [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data) (byte sequence) — the WebAuthn authenticator data structure; - [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson) (byte sequence) — the UTF-8 JSON encoding of [CollectedClientData](https://www.w3.org/TR/webauthn-3/#dictdef-collectedclientdata), which MUST include a `challenge` field whose value equals the base64url encoding of `hashData`; - - `signature` (byte sequence) — DER-encoded ECDSA-P256-SHA-256 signature over `authenticatorData ‖ SHA-256(clientDataJSON)`. + - `signature` (byte sequence) — DER-encoded ECDSA signature over `authenticatorData ‖ SHA-256(clientDataJSON)`, computed with the ECDSA variant matching the credential's curve (ES256 for P-256, ES384 for P-384). 3. Let `proofBytes` be the deterministic CBOR encoding ([RFC 8949 §4.2](https://www.rfc-editor.org/rfc/rfc8949#section-4.2)) of the 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`, in that fixed order. 4. Let `proofValue` be the multibase encoding of `proofBytes` using prefix `u` (base64url without padding, [RFC 4648 §5](https://www.rfc-editor.org/rfc/rfc4648#section-5)). 5. Return `proofValue`. -### Create Proof +### 3.8 Proof Verification -The following algorithm specifies how to create a [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) given an unsecured data document. +The following algorithm specifies how to verify a WebAuthn assertion against a set of cryptographic hash data. This algorithm is designed to be used in conjunction with the algorithms defined in the Data Integrity [VC-DATA-INTEGRITY](https://www.w3.org/TR/vc-data-integrity/) specification, [Section 4: Algorithms](https://www.w3.org/TR/vc-data-integrity/#algorithms). Required inputs are a hash value (byte sequence `hashData`), the three components of the decoded assertion (byte sequences `authenticatorData`, `signature`, and `clientDataJSON`), and proof options (map `options`). A verification result represented as a boolean value is produced as output. -Required inputs are an unsecured data document (map `unsecuredDocument`) and a set of proof options (map `options`). A data integrity proof (map `proof`), or an error, is produced as output. +1. Interpret `clientDataJSON` as a UTF-8 JSON object. If parsing fails, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). +2. If `clientDataJSON.type` is not equal to `"webauthn.get"`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). +3. If `clientDataJSON.challenge` does not equal the base64url encoding of `hashData`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). +4. Let `publicKey` be the public key associated with `options.verificationMethod`, retrieved as described in [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method). +5. Let `verificationResult` be the result of applying the verification algorithm for the Elliptic Curve Digital Signature Algorithm (ECDSA) [FIPS-186-5](https://csrc.nist.gov/pubs/fips/186-5/final), using the variant matching the key's curve (ES256 for P-256, ES384 for P-384), with `authenticatorData ‖ SHA-256(clientDataJSON)` as the data to be verified against the `signature` using the public key `publicKey`. +6. Return `verificationResult` as the verification result. -1. Let `proof` be a clone of `options`. -2. Let `canonicalProofConfig` be the result of running the [Proof Configuration](#proof-configuration-fido4vc-jcs-2026) algorithm with `options` and `unsecuredDocument` passed as parameters. -3. Let `transformedData` be the result of running the [Transformation](#transformation-fido4vc-jcs-2026) algorithm with `unsecuredDocument` and `options` passed as parameters. -4. Let `hashData` be the result of running the [Hashing](#hashing-fido4vc-jcs-2026) algorithm with `transformedData` and `canonicalProofConfig` passed as parameters. -5. Let `proofValue` be the result of running the [Proof Serialization](#proof-serialization-fido4vc-jcs-2026) algorithm with `hashData` and `options` passed as parameters. -6. Set `proof.proofValue` to `proofValue`. -7. Return `proof` as the data integrity proof. +### 3.9 Credential Registration -### Verify Proof +This cryptosuite specifies how to verify WebAuthn assertions; it does not perform credential creation. Before [Proof Serialization](#37-proof-serialization) can run, a WebAuthn credential MUST already exist on the user's authenticator, created out-of-band via the WebAuthn [`navigator.credentials.create()`](https://www.w3.org/TR/webauthn-3/#sctn-createCredential) ceremony. -The following algorithm specifies how to verify a [data integrity proof](https://www.w3.org/TR/vc-data-integrity/#dfn-data-integrity-proof) given a secured data document. +To ensure the resulting assertions are verifiable under `fido4vc-jcs-2026`, the registration ceremony MUST constrain the credential to a signature algorithm supported by this cryptosuite. The [`PublicKeyCredentialCreationOptions.pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptions-pubkeycredparams) array MUST contain at least one entry whose `type` is `"public-key"` and whose `alg` is `-7` (the [COSE Algorithm](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) identifier for ES256) or `-35` (the COSE identifier for ES384). The array SHOULD NOT contain entries for algorithms unsupported by this cryptosuite (e.g., `-257` RS256, `-8` EdDSA). If the authenticator selects an unsupported algorithm during credential creation, the resulting assertions will not verify and the credential will be unusable for `fido4vc-jcs-2026`. -Required input is a [secured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-secured-data-document) (map `securedDocument`). The algorithm returns a verification result, a struct whose items are: +After registration, the authenticator returns the credential's public key. The `proof.verificationMethod` in every proof produced by this credential MUST resolve to that public key per [§2.1 Verification Methods](#21-verification-methods). Other aspects of credential creation — RP/user identifier provisioning, attestation conveyance, resident-key requirements, authenticator-selection criteria — are WebAuthn-layer concerns and out of scope of this cryptosuite. See [W3C WebAuthn Level 3 §5.1.3](https://www.w3.org/TR/webauthn-3/#sctn-createCredential) for the full registration ceremony specification. -- `verified` — `true` or `false` -- `verifiedDocument` — `Null` if `verified` is `false`; otherwise an [unsecured data document](https://www.w3.org/TR/vc-data-integrity/#dfn-unsecured-data-document) - -When a step below says "an error MUST be raised", the algorithm MUST return a verification result with `verified` set to `false` and `verifiedDocument` set to `Null`, and SHOULD convey the specified error type. +### 3.10 Notes for implementers -1. Let `unsecuredDocument` be a copy of `securedDocument` with the `proof` property removed. -2. Let `proof` be the value of `securedDocument.proof`. -3. **Validate proof purpose.** If `proof.proofPurpose` is not equal to `"authentication"`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). (This cryptosuite is bound to the WebAuthn authentication primitive; other proof purposes such as `assertionMethod` are not supported.) -4. **Decode proof value.** - - If `proof.proofValue` is not a string beginning with the multibase prefix `u`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). - - Strip the leading `u`; base64url-decode the remaining characters to obtain `proofBytes`. - - CBOR-decode `proofBytes` as a 3-element byte-string array `[authenticatorData, signature, clientDataJSON]`. If the structure does not match, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). -5. Let `options` be a clone of `proof` with the `proofValue` property removed. -6. Let `canonicalProofConfig` be the result of running the [Proof Configuration](#proof-configuration-fido4vc-jcs-2026) algorithm with `options` and `unsecuredDocument` passed as parameters. -7. Let `transformedData` be the result of running the [Transformation](#transformation-fido4vc-jcs-2026) algorithm with `unsecuredDocument` and `options` passed as parameters. -8. Let `hashData` be the result of running the [Hashing](#hashing-fido4vc-jcs-2026) algorithm with `transformedData` and `canonicalProofConfig` passed as parameters. -9. **Parse clientDataJSON.** Interpret `clientDataJSON` as a UTF-8 JSON object. If parsing fails, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). -10. **Validate WebAuthn assertion type.** If `clientDataJSON.type` is not equal to `"webauthn.get"`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). (This prevents confusion attacks where a registration-ceremony assertion is substituted for an authentication assertion.) -11. **Bind challenge.** If `clientDataJSON.challenge` does not equal the base64url encoding of `hashData`, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). -12. **Retrieve verification method.** Let `publicKey` be the public key associated with `proof.verificationMethod`, retrieved as described in [CID 1.0 §3.3](https://www.w3.org/TR/cid-1.0/#retrieve-verification-method). The dereferenced object MUST contain a `type` of `JsonWebKey` whose `publicKeyJwk` has `kty: "EC"` and `crv: "P-256"`; otherwise an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). -13. **Verify signature.** Let `signedBytes` be `authenticatorData ‖ SHA-256(clientDataJSON)`. Verify the ECDSA `signature` against `publicKey` over `signedBytes` using ES256. If verification fails, an error MUST be raised and SHOULD convey an error type of [`PROOF_VERIFICATION_ERROR`](https://www.w3.org/TR/vc-data-integrity/#PROOF_VERIFICATION_ERROR). -14. Return a verification result with `verified` set to `true` and `verifiedDocument` set to `unsecuredDocument`. - -### Notes for implementers +This section is non-normative. - **Two distinct "challenge" values.** `proof.challenge` is a verifier-supplied nonce placed in the VC-DI proof options; it is folded into `canonicalProofConfig` and therefore into `hashData`, binding the proof to a specific verifier session. The *WebAuthn challenge* is `hashData` itself — the 64-byte binary value passed to `navigator.credentials.get()`. -- **`clientDataJSON` parsing.** Be defensive about whitespace and field order in `clientDataJSON`. Per the WebAuthn spec, the authenticator produces a specific format, but extension fields may appear; only `challenge` is relied on by this cryptosuite. -- **WebAuthn ceremony validation is out of scope of this cryptosuite.** Validation of `rpId`, `clientDataJSON.origin`, the `authenticatorData` flags (UV, AT, BE, BS), signature-counter monotonicity, and attestation chain are WebAuthn-layer concerns, not cryptosuite concerns. Verifiers operating inside a full WebAuthn ceremony context SHOULD perform the additional checks described in [W3C WebAuthn Level 3 §7.2](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion). The cryptosuite makes one exception: `clientDataJSON.type` MUST equal `"webauthn.get"` (enforced in the [Verify Proof](#verify-proof-fido4vc-jcs-2026) algorithm) because the check is cheap and prevents a well-known confusion-attack class. +- **`clientDataJSON` parsing.** Be defensive about whitespace and field order in `clientDataJSON`. Per the WebAuthn spec, the authenticator produces a specific format, but extension fields may appear; only `challenge` (and `type`) are relied on by this cryptosuite. +- **WebAuthn ceremony validation is out of scope of this cryptosuite.** Validation of `rpId`, `clientDataJSON.origin`, the `authenticatorData` flags (UV, AT, BE, BS), signature-counter monotonicity, and attestation chain are WebAuthn-layer concerns, not cryptosuite concerns. Verifiers operating inside a full WebAuthn ceremony context SHOULD perform the additional checks described in [W3C WebAuthn Level 3 §7.2](https://www.w3.org/TR/webauthn-3/#sctn-verifying-assertion). The cryptosuite makes one exception: `clientDataJSON.type` MUST equal `"webauthn.get"` (enforced in [§3.8 Proof Verification](#38-proof-verification)) because the check is cheap and prevents a well-known confusion-attack class. -## Test vectors +## 4. Security Considerations -A small reference fixture is included with the TypeScript reference implementation: [`tests/fido4vc.test.ts`](https://github.com/fido4vc/fido-vc-cryptosuite-ts/blob/main/tests/fido4vc.test.ts). It captures a single end-to-end WebAuthn ceremony from the reference deployment — the holder DID-JWK, the WebAuthn assertion (`authenticatorData`, `clientData`, `signature`), and the full signed VP. +Before reading this section, readers are urged to familiarize themselves with general security advice provided in the [Security Considerations section of the Data Integrity specification](https://www.w3.org/TR/vc-data-integrity/#security-considerations). The following section describes security considerations that developers implementing this specification should be aware of in order to create secure software. -## Security considerations +## 5. Privacy Considerations -**Cross-implementation determinism.** The most likely interoperability hazard is non-RFC-8785-compliant JCS implementations. Implementers SHOULD use the Erdtman references on each platform. Subtle differences in number formatting (notably exponent handling) or escape sequences will silently produce mismatched hashes. +Before reading this section, readers are urged to familiarize themselves with the general guidance in the [Privacy Considerations section of the Data Integrity specification](https://www.w3.org/TR/vc-data-integrity/#privacy-considerations). -**Replay protection.** Replay protection is *not* provided by the cryptosuite. The verifier supplies a `challenge` in the proof options; replay is prevented by the verifier rejecting reused challenge values. If the verifier doesn't track challenges, replay is possible. +### 5.1 Selective and Unlinkable Disclosure -**WebAuthn ceremony validation.** A standalone cryptosuite verifier doesn't have access to the full WebAuthn ceremony context (`origin`, `rpId`, attestation chain). Verifiers wanting strict WebAuthn-grade assurance SHOULD add ceremony-level checks on top of cryptosuite verification. +This cryptosuite does **not** support selective disclosure: a proof commits to the entire canonicalized document, and a holder cannot reveal a subset of claims while keeping the proof valid. Applications requiring selective disclosure should use a suite designed for it, such as [BBS](https://www.w3.org/TR/vc-di-bbs/) or `ecdsa-sd-2023` (see [ECDSA Cryptosuites](https://www.w3.org/TR/vc-di-ecdsa/)). -**Key compromise.** If a user's FIDO authenticator is compromised, the attacker can produce valid `fido4vc-jcs-2026` signatures for any document. Revocation is delegated to: (a) the user revoking the Passkey in their OS account, (b) the wallet operator removing the DID, (c) issuers revoking issued credentials per their own revocation registry. +--- ## Implementations diff --git a/src/content/docs/spec/integration.mdx b/src/content/docs/spec/integration.mdx index 2a1a2e0..e199bbd 100644 --- a/src/content/docs/spec/integration.mdx +++ b/src/content/docs/spec/integration.mdx @@ -142,7 +142,7 @@ A successful flow: wallet submits a presentation with a `fido4vc-jcs-2026` proof If you're not using walt.id, the integration is conceptually identical: 1. **Detect the cryptosuite.** When your verifier sees a VP with `proof.type = "DataIntegrityProof"` and `proof.cryptosuite = "fido4vc-jcs-2026"`, route to the verification logic. -2. **Verify per the spec.** Run the [verify algorithm](/spec/fido4vc-jcs-2026/#verify-proof). Either: +2. **Verify per the spec.** Run the [verify algorithm](/spec/fido4vc-jcs-2026/#33-verify-proof). Either: - Call the sidecar over HTTP, or - Import the TS reference implementation directly (for Node-based verifier stacks). 3. **Return success/failure to your verification pipeline.** The cryptosuite verification is just one step — your pipeline still validates the embedded VCs, presentation definition, freshness, etc. @@ -153,12 +153,6 @@ The cryptosuite is platform-agnostic; nothing about it is walt.id-specific. **Sidecar not reachable.** `LdSignaturePolicy` calls `http://:8081/verify`, where `` defaults to `localhost`. That default only works when the sidecar shares a loopback with walt.id (same host, same docker network, or same Kubernetes pod — containers in one pod share `localhost`). For split deployments, override `baseUrl` to a reachable address — e.g. `LdSignaturePolicy(baseUrl = "http://sidecar.internal:8081")` for a private DNS host, or a Kubernetes Service DNS name like `http://fido-sidecar.default.svc.cluster.local:8081`. -**JCS implementation drift.** If signatures from a JS signer don't verify under a JVM verifier (or vice versa), the cause is almost always a non-RFC-8785-compliant JCS implementation. Use Erdtman's reference on both sides — `canonicalize` npm package for JS, `io.github.erdtman:java-json-canonicalization` for JVM. - -**Wallet returning the wrong proof structure.** `proofValue` MUST be an object with `signature`, `authenticatorData`, `clientData`. Some VC-DI libraries expect a string here and will reject the proof at parse time. If you see schema errors at the verifier, check that the wallet implementation is emitting the structured object format. - -**`origin` mismatch.** WebAuthn's `clientDataJSON.origin` must match the FIDO middleware's configured `origin`. If your middleware moves to a new domain, all previously-registered FIDO credentials become unusable (this is a WebAuthn invariant, not a FIDO4VC choice). - ## Deployment topology A complete reference deployment looks like: