COSE and deterministic encoding
A signature over a digest checks only if the verifier hashes the same bytes the signer hashed, so a structured record must turn into exactly one byte string.
1What it is
A COSE_Sign1 (RFC 9052) is a CBOR structure holding a protected header, an unprotected header, a payload, and one signature computed over the encoded protected header and the payload.
A signature over a digest can be checked only by hashing exactly the bytes the signer hashed, so two implementations that rebuild those bytes from the same structured record must encode it the same way.
JSON records here use the JSON Canonicalization Scheme (JCS, RFC 8785), and for CBOR content the COSE deterministic encodings Internet-Draft in preparation lets a COSE Hash Envelope name the deterministic encoding that was applied.
2Design choices
capsule_id; the protected header holds exactly alg -8 (Ed25519), content type application/agent-action-capsule-id, and kid (the raw 32-byte public key); the unprotected header is empty.capsule_id, with CWT iss and sub in the protected header.iss and sub; the three-entry envelope header has neither. (draft-mih-scitt-agent-action-capsule-05 §3.3)canonicalization_id: "jcs") and checked before any digest is computed; null, empty arrays, and empty objects take part.jcs-n), and any undeclared or unknown construction; these fail closed. (draft-mih-scitt-agent-action-capsule-05 §2)payload-preimage-encoding, whose value comes from a small registry; its initial entry is core-deterministic (RFC 8949 §4.2.1).preimage-content-type (259). application/cbor defines no parameters, so one would be needed per media type, and a CoAP Content-Format number per pairing. (COSE deterministic encodings, Introduction)3What it shows / what it does not
Shows
- That two independent implementations derive the same bytes, and so the same digest, from the same record when both apply the declared encoding.
- Which key signed the 32-byte identity: the envelope signature verifies under the public key in
kid. - Which encoding produced a Hash Envelope preimage, when
payload-preimage-encodingis present. - That a digest mismatch under a declared encoding is not an encoder disagreement: the value differs, whether by edit or by a parser that read it differently (see the next column).
Does not show
- Unicode equivalence. JCS does not normalize Unicode, so an NFC key and its NFD spelling are different records with different digests.
- That a parser kept the value exactly. Decoding into native types can lose distinctions such as integer versus float, and then re-encoding cannot reproduce the preimage.
- That the signing key is authorized for the operator; that is the caller's policy.
- Anything when the verifier has neither the preimage nor the encoding: that case is unverified, never verified and never failed.
- Secrecy. A deterministic digest is stable, so it is linkable wherever the content recurs, and low-entropy content can be guessed.
4Example
A conformance vector whose object keys sort differently under UTF-16 code units (what JCS requires) and under code points. The input carries the keys in JCS order; the expected result gives the canonical preimage and a recomputed capsule_id equal to the carried one.
input.json
{
"action_id": "canonical-key-sort-utf16-vs-codepoint",
…
"canonicalization_id": "jcs",
"capsule_id": "9d07be6c8b0cb9efafd089ee02a2e06846854ee70c3c0f56e88925c1d2cf5dde",
…
"extension": {
"canonical_test_value": {
"😀": "hi",
"Z": "wide"
}
},
"format_version": "4",
…
}
expected.json
{
"canonical_preimages": {
"capsule_id": "{\"action_id\":\"canonical-key-sort-utf16-vs-codepoint\",…,\"extension\":{\"canonical_test_value\":{\"😀\":\"hi\",\"Z\":\"wide\"}},\"format_version\":\"4\",…}"
},
"capsule_id_recomputed": "9d07be6c8b0cb9efafd089ee02a2e06846854ee70c3c0f56e88925c1d2cf5dde",
…
"findings": [],
"kind": "positive",
"ok": true,
"provenance": "spec-derived"
}
| Field | Type | Meaning |
|---|---|---|
| canonicalization_id | string | The declared construction: plain RFC 8785 JCS. |
| capsule_id | 64 hex | SHA-256 over the JCS bytes of the Capsule minus this field. |
| extension.canonical_test_value | object | Keys U+1F600 (UTF-16 D83D DE00) and U+FF3A (FF3A); JCS puts the emoji ahead of Z. |
| format_version | string | Serialization suite "4", the only one supported. |
| canonical_preimages.capsule_id | string | The exact JCS string that was hashed (trimmed here). |
| capsule_id_recomputed | 64 hex | The verifier's own SHA-256 of that preimage; equals the carried value. |
| ok / findings | bool / array | Class 1 verification passes with no findings. |
| provenance | string | "spec-derived": "derived from the draft and RFC 8785 without running any repository implementation" (vectors/README.md). |
Source: action-state-group/agent-action-capsule:vectors/capsule/canonical-key-sort-utf16-vs-codepoint/input.json and …/expected.json @ 2ef8aeb49e2334e24c404bd62d3fdb161770873c
5Specs
- RFC RFC 9052 — CBOR Object Signing and Encryption (COSE): COSE_Sign1.
- RFC RFC 8785 — JSON Canonicalization Scheme (JCS).
- RFC RFC 9995 — COSE Hash Envelope: signs the digest of a preimage held elsewhere.
- In preparation COSE deterministic encodings (Internet-Draft in preparation) — the
payload-preimage-encodingparameter and a registry of deterministic encodings. - Internet-Draft draft-mih-scitt-agent-action-capsule-05 — the Producer Envelope wire profile and the format-4 JCS rules.
- Internet-Draft draft-mih-sokolov-scitt-payload-binding-05 — how a Signed Statement declares its canonicalization (page 04).
- Code action-state-group/agent-action-capsule — canonicalization edge-case vectors under
vectors/capsule/.