Expand description
The commitment chain: how a transcript’s lines fold into its fingerprint.
node_0 = SHA-256(0x02 ‖ JCS(header))
leaf_i = SHA-256(0x00 ‖ salt_i ‖ JCS(fact_i))
node_i = SHA-256(0x01 ‖ node_{i-1} ‖ len(at_i) ‖ at_i ‖ level_i ‖ leaf_i)
fingerprint = node_nJCS is the RFC 8785 canonical form (canonical_json).
The functions here take it already computed, since the writer puts the same
text on the line. A salt is SALT_LEN bytes from the OS random source,
fresh for every fact; it is never drawn from the ceremony entropy source,
which is public by design and would let anyone holding a redacted
transcript test guesses against a withheld fact. A line writes the salt as 32 lowercase hex digits, and the
leaf hashes the 16 decoded bytes, so the commitment does not depend on how
the line spells them. at is UTF-8, preceded by its length. Lengths and
the level are 8-byte big-endian integers.
The leaf commits to the fact; the node commits to the leaf, the time and the level, and to everything before it. A line whose fact is withheld keeps its leaf, so it folds to the same node, and a transcript with withheld lines has the same fingerprint as the complete one. The one-byte prefixes keep a leaf, a node and the header from ever hashing the same input.
§Example
Folding a header and one fact, as a verifier does line by line; the values are the construction’s test vectors.
use rite_model::commitment::{chain_node, fact_leaf, header_node};
use rite_model::{Level, Sha256Digest, canonical_json};
use serde_json::json;
let header = json!({
"rite_transcript": 1,
"vocabulary": 1,
"producer": "rite 0.6.0",
"run_id": "00000000000000000000000000000000",
"dry_run": false,
"levels": { "public": 10, "restricted": 20, "confidential": 30 },
});
let node_0 = header_node(&canonical_json(&header)?);
let fact = json!({ "type": "act_started", "id": "setup", "label": "Setup" });
let leaf = fact_leaf(&[0x11; 16], &canonical_json(&fact)?);
let node_1 = chain_node(&node_0, "2026-09-22T10:00:00.000000Z", Level::PUBLIC, &leaf);
assert_eq!(
Sha256Digest::from_bytes(&node_1).as_str(),
"sha256:e1942a1ab439cad63eb64ed52692d425d898c2c16b0fac24c08e5c04f71a1af5"
);Constants§
- SALT_
LEN - Length of a fact’s salt, in bytes.
Functions§
- chain_
node node_i: the chain value after one line.- fact_
leaf leaf_i: the commitment to one fact, given as its canonical JSON, under its salt.- header_
node node_0: the commitment to the header, given as its canonical JSON.