Agent Action Capsule · explain
04 / 11

Canonicalization-declared payload binding

A signed digest of structured content is checkable only if the verifier knows how the content was turned into bytes, so the construction is declared by name and never guessed.

Recomputing a declared binding Stage 1: on the left, a Signed Statement box shows protected label 258 equal to -16 (SHA-256) and a payload of raw 32 bytes, shown in hex as 7b3ef8eb through cff0d7cb; below it the content held separately, the JSON text with members z null, a empty object, m empty array, s "CPB"; below that a chip saying the payload class declares the canonicalization jcs, and that label 258 names only the hash. Stage 2: a verifier box reads the declared name jcs and produces the canonical pre-image with keys sorted a, m, s, z and the null and empty members kept. Stage 3: SHA-256 of those bytes gives 7b3ef8eb through cff0d7cb. Stage 4: a line runs from the recomputed digest back to the signed payload, marked equal, with a green check: match, binding verified. Stage 5: a lower lane shows a verifier that guesses instead, dropping null and empty members as the withdrawn jcs-n did; its pre-image is only the member s "CPB", its SHA-256 is a different digest, and a red cross marks mismatch, binding failed. Signed Statement protected 258: -16 (SHA-256) payload: raw 32 bytes, as hex 7b3ef8eb…cff0d7cb signed content, held separately { "z": null, "a": {}, "m": [], "s": "CPB" } payload class declares: jcs label 258 names only the hash apply declared jcs {"a":{},"m":[],"s":"CPB","z":null} sorted, null and empty kept SHA-256 7b3ef8eb… = signed payload ✓ verified guessed: drop null / empty {"s":"CPB"} other bytes different digest ✗ binding fails another verifier

1What it is

CPB, the working short name of draft-mih-sokolov-scitt-payload-binding-05, defines how a SCITT Signed Statement declares the construction behind a digest it carries: the canonicalization, the hash function, and the output representation.

Each construction has a registered name, such as jcs or as-transmitted, and a payload profile declares exactly one, so a verifier recomputes the digest the same way the producer did instead of guessing.

CPB defines no payload format and no payload meaning; it defines the declarations, the registry of names, and how producers and verifiers compute and compare the digests.

2Design choices

Chosen: declare, never infer. A payload class declares exactly one registered canonicalization algorithm.
Rejected: guessing from the payload's shape, or treating Hash Envelope label 258 as the canonicalization; 258 selects only the hash function. (Draft §4, §6.2)
Chosen: a registry of immutable names, each fixing pre-image construction, hash, and output representation together; new behavior means a new name.
Rejected: redefining an existing name. jcs-n was withdrawn, not changed: its token stays bound, it cannot be newly declared, and older records are checked only with proof of pre-cutoff vintage. (Draft §4, §4.2, §14.1)
Chosen: in Hash Envelope mode the COSE payload is the raw digest octets, and representations (raw 32 bytes, bare hex, prefixed text) are declared and never silently converted.
Rejected: carrying the hex text, or letting a verifier coerce between forms; hashing a raw digest and hashing its hex text give different results. (Draft §5.1, §6.2, §7.1)
Chosen: the derived identifier removes the profile's declared exclusion set; the Hash Envelope binding covers the complete content and is checked separately.
Rejected: assuming the Hash Envelope payload is the raw form of the derived identifier, even when both use the same algorithm. (Draft §5, §6.2)
Chosen: as-transmitted for bytes a container already fixes, named by a cited production such as RFC 9052 §4.4 ToBeSigned.
Rejected: an uncited description such as "the payload bytes", or re-canonicalizing bytes a signature already fixed. (Draft §4.4)
Chosen: two digests are compared only under the same established digest context.
Rejected: treating equal-looking hex as a match; values computed under different contexts can coincide. (Draft §8.1)

3What it shows / what it does not

Shows

  • That the content a verifier holds reproduces the signed digest under the declared construction.
  • Which construction was used, by registered name, so independent verifiers recompute the same bytes.
  • Whether a digest is raw octets or text, so a leaf or comparison uses the right form.
  • That a mismatch under the declared construction is not a serializer disagreement: the value differs, whether by edit or by a parser that rounded a number differently (Draft §12.3).

Does not show

  • Anything about content the verifier does not hold. Without it, the signature can be checked but the content binding is not verified (Draft §6.2).
  • That the signer was authorized. A valid signature is Signature-Valid; accepting the key for the issuer is the verifier's policy (Draft §3).
  • What the payload means. Payload format and semantics belong to the payload profile.
  • Agreement on floats when two parsers round the same literal differently; a profile may require exact decimal strings (Draft §12.3).
  • Confidentiality of low-entropy fields: a bare digest of a guessable value can be reversed (Draft §12.2).

4Example

The smallest vector for the active jcs algorithm. It keeps the null, empty-array, and empty-object members, which is what separates plain jcs from the withdrawn jcs-n.

{
  "id": "jcs-pass-01",
  "algorithm": "jcs",
  …
  "must_fail": false,
  "input_json": "{ \"z\": null, \"a\": {}, \"m\": [], \"s\": \"CPB\" }",
  "input_data_model": {
    "z": null,
    "a": {},
    "m": [],
    "s": "CPB"
  },
  "exclusion_set": [],
  "pre_image": "{\"a\":{},\"m\":[],\"s\":\"CPB\",\"z\":null}",
  "pre_image_bytes_hex": "7b2261223a7b7d2c226d223a5b5d2c2273223a22435042222c227a223a6e756c6c7d",
  "digest_alg": "SHA-256",
  "digest_representation": "lowercase-hex",
  "digest": "7b3ef8ebf31ee38952860dd3f30c2e0d101dcdbcd9972e3ed8108ef9cff0d7cb"
}
FieldTypeMeaning
idstringVector identifier.
algorithmstringThe declared registered name: plain RFC 8785 JCS, no normalization pass.
must_failboolfalse: a conforming implementation must reproduce the digest.
input_jsonstringThe content as text, with spacing and member order as written.
input_data_modelobjectThe same content as a JSON value.
exclusion_setarrayTop-level members removed before canonicalization; none here.
pre_imagestringThe JCS output: keys sorted, null and empty members kept.
pre_image_bytes_hexhexThe exact UTF-8 octets that are hashed.
digest_algstringThe hash function the jcs entry pins.
digest_representationstringOutput form: 64-character lowercase hex.
digest64 hexSHA-256 of the pre-image octets.

Source: action-state-group/scitt-payload-binding:vectors/jcs/pass/01-retain-null-and-empty-members.json @ e6e3325d7e41c45dad80baf8c3ca3d07b18db827

5Specs