1use sha2::{Digest, Sha256};
58
59use crate::transcript::Level;
60
61pub const SALT_LEN: usize = 16;
63
64const LEAF_PREFIX: u8 = 0x00;
65const NODE_PREFIX: u8 = 0x01;
66const HEADER_PREFIX: u8 = 0x02;
67
68#[must_use]
70pub fn header_node(canonical_header: &str) -> [u8; 32] {
71 let mut hasher = Sha256::new();
72 hasher.update([HEADER_PREFIX]);
73 hasher.update(canonical_header.as_bytes());
74 hasher.finalize().into()
75}
76
77#[must_use]
80pub fn fact_leaf(salt: &[u8; SALT_LEN], canonical_fact: &str) -> [u8; 32] {
81 let mut hasher = Sha256::new();
82 hasher.update([LEAF_PREFIX]);
83 hasher.update(salt);
84 hasher.update(canonical_fact.as_bytes());
85 hasher.finalize().into()
86}
87
88#[must_use]
90pub fn chain_node(previous: &[u8; 32], at: &str, level: Level, leaf: &[u8; 32]) -> [u8; 32] {
91 let mut hasher = Sha256::new();
92 hasher.update([NODE_PREFIX]);
93 hasher.update(previous);
94 update_with_length(&mut hasher, at.as_bytes());
95 hasher.update(u64::from(level.value()).to_be_bytes());
96 hasher.update(leaf);
97 hasher.finalize().into()
98}
99
100fn update_with_length(hasher: &mut Sha256, bytes: &[u8]) {
101 let len = u64::try_from(bytes.len()).unwrap_or(u64::MAX);
102 hasher.update(len.to_be_bytes());
103 hasher.update(bytes);
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use crate::canonical_json;
110 use serde_json::{Value, json};
111
112 fn hex(bytes: &[u8; 32]) -> String {
113 base16ct::lower::encode_string(bytes)
114 }
115
116 fn jcs(value: &Value) -> String {
117 canonical_json(value).expect("canonical")
118 }
119
120 #[test]
123 fn construction_vectors() {
124 let header = json!({
125 "rite_transcript": 1,
126 "vocabulary": 1,
127 "producer": "rite 0.6.0",
128 "run_id": "00000000000000000000000000000000",
129 "dry_run": false,
130 "levels": { "public": 10, "restricted": 20, "confidential": 30 },
131 });
132 let node_0 = header_node(&jcs(&header));
133 assert_eq!(
134 hex(&node_0),
135 "acd329c9b34b4e9443db1b47b4a5908b3f66affd630005935e5c5201c3f123ec"
136 );
137
138 let salt = [0x11; SALT_LEN];
139 let fact = json!({ "type": "act_started", "id": "setup", "label": "Setup" });
140 let leaf = fact_leaf(&salt, &jcs(&fact));
141 assert_eq!(
142 hex(&leaf),
143 "60d495f6e5117daeaa777bcac4b9aa5360c43aff76bdcf5461380864e0c053d0"
144 );
145
146 let node_1 = chain_node(&node_0, "2026-09-22T10:00:00.000000Z", Level::PUBLIC, &leaf);
147 assert_eq!(
148 hex(&node_1),
149 "e1942a1ab439cad63eb64ed52692d425d898c2c16b0fac24c08e5c04f71a1af5"
150 );
151 }
152
153 #[test]
154 fn every_committed_input_changes_the_node() {
155 let base = chain_node(&[0; 32], "t", Level::PUBLIC, &[1; 32]);
156 assert_ne!(base, chain_node(&[9; 32], "t", Level::PUBLIC, &[1; 32]));
157 assert_ne!(base, chain_node(&[0; 32], "u", Level::PUBLIC, &[1; 32]));
158 assert_ne!(base, chain_node(&[0; 32], "t", Level::RESTRICTED, &[1; 32]));
159 assert_ne!(base, chain_node(&[0; 32], "t", Level::PUBLIC, &[2; 32]));
160 }
161
162 #[test]
163 fn the_salt_and_the_fact_both_change_the_leaf() {
164 let fact = jcs(&json!({ "type": "x" }));
165 let leaf = fact_leaf(&[0; SALT_LEN], &fact);
166 assert_ne!(leaf, fact_leaf(&[1; SALT_LEN], &fact));
167 assert_ne!(
168 leaf,
169 fact_leaf(&[0; SALT_LEN], &jcs(&json!({ "type": "y" })))
170 );
171 }
172
173 #[test]
174 fn member_order_does_not_change_the_leaf() {
175 let a: Value = serde_json::from_str(r#"{"a":1,"b":2}"#).expect("parse");
176 let b: Value = serde_json::from_str(r#"{"b":2,"a":1}"#).expect("parse");
177 assert_eq!(
178 fact_leaf(&[0; SALT_LEN], &jcs(&a)),
179 fact_leaf(&[0; SALT_LEN], &jcs(&b))
180 );
181 }
182}