Skip to main content

Module commitment

Module commitment 

Source
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_n

JCS 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.