confium_transparency/
test_vectors.rs1use crate::entry::{ArtifactType, MerkleEntry};
10use crate::merkle::{Hash, InclusionProof, MerkleTree};
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct TestVector {
16 pub description: String,
18 pub artifact_hashes_hex: Vec<String>,
20 pub expected_root_hex: String,
22 pub inclusion_proofs: Vec<InclusionProofJson>,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct InclusionProofJson {
29 pub sequence: u64,
31 pub steps: Vec<ProofStepJson>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ProofStepJson {
38 pub sibling_hex: String,
40 pub side: String,
42}
43
44pub fn generate_vector(n: u64) -> TestVector {
50 let mut tree = MerkleTree::new();
51 let fixed_time = chrono::DateTime::parse_from_rfc3339("2026-01-01T00:00:00Z")
52 .unwrap()
53 .with_timezone(&chrono::Utc);
54
55 for i in 0..n {
56 let mut entry = MerkleEntry::new(i, ArtifactType::ThresholdSignature, [i as u8; 32]);
57 entry.timestamp = fixed_time;
58 tree.append(entry);
59 }
60
61 let root = tree.root();
62 let mut proofs = Vec::new();
63 for i in 0..n {
64 let proof = tree.inclusion_proof(i).unwrap();
65 let steps: Vec<ProofStepJson> = proof
66 .steps
67 .iter()
68 .map(|s| ProofStepJson {
69 sibling_hex: hex::encode(s.sibling),
70 side: match s.side {
71 crate::merkle::Side::Left => "left".into(),
72 crate::merkle::Side::Right => "right".into(),
73 },
74 })
75 .collect();
76 proofs.push(InclusionProofJson { sequence: i, steps });
77 }
78
79 let artifact_hashes: Vec<String> = (0..n).map(|i| hex::encode([i as u8; 32])).collect();
80
81 TestVector {
82 description: format!("Tree with {n} entries, deterministic hashes"),
83 artifact_hashes_hex: artifact_hashes,
84 expected_root_hex: hex::encode(root),
85 inclusion_proofs: proofs,
86 }
87}
88
89pub fn generate_suite() -> Vec<TestVector> {
91 vec![
92 generate_vector(1),
93 generate_vector(2),
94 generate_vector(3),
95 generate_vector(4),
96 generate_vector(8),
97 generate_vector(16),
98 generate_vector(32),
99 ]
100}
101
102pub fn verify_vector(vector: &TestVector) -> Result<(), String> {
105 let root = decode_hash(&vector.expected_root_hex)?;
106 let mut tree = MerkleTree::new();
107 let fixed_time = chrono::DateTime::parse_from_rfc3339("2026-01-01T00:00:00Z")
108 .unwrap()
109 .with_timezone(&chrono::Utc);
110
111 for (i, hash_hex) in vector.artifact_hashes_hex.iter().enumerate() {
112 let hash = decode_hash(hash_hex)?;
113 let mut entry = MerkleEntry::new(i as u64, ArtifactType::ThresholdSignature, hash);
114 entry.timestamp = fixed_time;
115 tree.append(entry);
116 }
117
118 let actual_root = tree.root();
119 if actual_root != root {
120 return Err(format!(
121 "root mismatch: expected {}, got {}",
122 hex::encode(root),
123 hex::encode(actual_root)
124 ));
125 }
126
127 for proof_json in &vector.inclusion_proofs {
128 let seq = proof_json.sequence;
129 let entry = tree.entry(seq).map_err(|e| format!("{e:?}"))?.clone();
130 let steps: Vec<crate::merkle::ProofStep> = proof_json
131 .steps
132 .iter()
133 .map(|s| {
134 Ok(crate::merkle::ProofStep {
135 sibling: decode_hash(&s.sibling_hex)?,
136 side: match s.side.as_str() {
137 "left" => crate::merkle::Side::Left,
138 "right" => crate::merkle::Side::Right,
139 _ => return Err(format!("invalid side: {}", s.side)),
140 },
141 })
142 })
143 .collect::<Result<Vec<_>, String>>()?;
144
145 let proof = InclusionProof {
146 sequence: seq,
147 steps,
148 };
149 MerkleTree::verify_inclusion(&entry, &proof, root).map_err(|e| format!("{e:?}"))?;
150 }
151
152 Ok(())
153}
154
155pub fn suite_to_json(suite: &[TestVector]) -> Result<String, serde_json::Error> {
157 serde_json::to_string_pretty(suite)
158}
159
160fn decode_hash(hex_str: &str) -> Result<Hash, String> {
161 let bytes = hex::decode(hex_str).map_err(|e| e.to_string())?;
162 if bytes.len() != 32 {
163 return Err(format!("expected 32 bytes, got {}", bytes.len()));
164 }
165 let mut arr = [0u8; 32];
166 arr.copy_from_slice(&bytes);
167 Ok(arr)
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173
174 #[test]
175 fn single_entry_vector_has_correct_root() {
176 let vector = generate_vector(1);
177 assert_eq!(vector.artifact_hashes_hex.len(), 1);
178 assert_eq!(vector.inclusion_proofs.len(), 1);
179 assert!(!vector.expected_root_hex.is_empty());
180 }
181
182 #[test]
183 fn vector_is_deterministic() {
184 let v1 = generate_vector(5);
185 let v2 = generate_vector(5);
186 assert_eq!(v1.expected_root_hex, v2.expected_root_hex);
187 assert_eq!(v1.inclusion_proofs.len(), v2.inclusion_proofs.len());
188 }
189
190 #[test]
191 fn different_sizes_produce_different_roots() {
192 let v1 = generate_vector(1);
193 let v2 = generate_vector(2);
194 assert_ne!(v1.expected_root_hex, v2.expected_root_hex);
195 }
196
197 #[test]
198 fn verify_vector_passes_for_generated() {
199 for n in [1, 2, 3, 5, 8, 16] {
200 let vector = generate_vector(n);
201 verify_vector(&vector).unwrap_or_else(|e| panic!("verify n={n}: {e}"));
202 }
203 }
204
205 #[test]
206 fn generate_suite_has_7_sizes() {
207 let suite = generate_suite();
208 assert_eq!(suite.len(), 7);
209 }
210
211 #[test]
212 fn suite_serializes_to_json() {
213 let suite = generate_suite();
214 let json = suite_to_json(&suite).unwrap();
215 assert!(json.contains("expected_root_hex"));
216 assert!(json.contains("description"));
217 }
218
219 #[test]
220 fn verify_vector_detects_tampered_root() {
221 let mut vector = generate_vector(3);
222 vector.expected_root_hex = hex::encode([0xFF; 32]);
223 assert!(verify_vector(&vector).is_err());
224 }
225
226 #[test]
227 fn inclusion_proof_count_matches_entries() {
228 let vector = generate_vector(8);
229 assert_eq!(vector.inclusion_proofs.len(), 8);
230 for (i, proof) in vector.inclusion_proofs.iter().enumerate() {
231 assert_eq!(proof.sequence, i as u64);
232 }
233 }
234
235 #[test]
236 fn power_of_two_tree_has_clean_proofs() {
237 let vector = generate_vector(8);
238 for proof in &vector.inclusion_proofs {
239 assert!(
240 proof.steps.len() <= 4,
241 "8-entry tree has at most 4 proof steps"
242 );
243 }
244 }
245}