Getting started
This program hashes three records, checks an inclusion proof for each, then checks that a fourth record is an append. It uses only rfc9162. Storage and signatures are the tutorial.
Install Rust from the installation page, then:
cargo new ct-log
cd ct-log
cargo add rfc9162@0.10.0
Replace src/main.rs with the program below and run cargo run.
The head
leaf_hash hashes each record. Head::over is the size and the hexadecimal root.
//! The program the getting-started page copies into a new crate.
fn main() {
let records = ["alpha", "beta", "gamma"];
let leaves: Vec<[u8; 32]> = records
.iter()
.map(|record| rfc9162::leaf_hash(record.as_bytes()))
.collect();
let head = rfc9162::Head::over(&leaves);
println!("size {}", head.size);
println!("root {}", head.root);
for (index, record) in records.iter().enumerate() {
let path = rfc9162::inclusion_proof(&leaves, index).expect("index is in range");
let ok = rfc9162::verify_inclusion(&leaves[index], index, head.size, &path, &head.root);
println!("inclusion {index} {record} steps {} ok {ok}", path.len());
}
let grown: Vec<[u8; 32]> = ["alpha", "beta", "gamma", "delta"]
.iter()
.map(|record| rfc9162::leaf_hash(record.as_bytes()))
.collect();
let later = rfc9162::Head::over(&grown);
let proof = rfc9162::consistency_proof(&grown, head.size).expect("3 is a prefix of 4");
let ok = rfc9162::verify_consistency(head.size, &head.root, later.size, &later.root, &proof);
println!("later size {} root {}", later.size, later.root);
println!(
"consistency {} -> {} steps {} ok {ok}",
head.size,
later.size,
proof.len()
);
let material = String::from_utf8(head.material("example.org/log")).expect("utf-8");
println!("material {material}");
}
Inclusion
inclusion_proof returns the sibling hashes for one index, innermost first. verify_inclusion folds that path back to the root. The inclusion page draws the path for beta.
//! The program the getting-started page copies into a new crate.
fn main() {
let records = ["alpha", "beta", "gamma"];
let leaves: Vec<[u8; 32]> = records
.iter()
.map(|record| rfc9162::leaf_hash(record.as_bytes()))
.collect();
let head = rfc9162::Head::over(&leaves);
println!("size {}", head.size);
println!("root {}", head.root);
for (index, record) in records.iter().enumerate() {
let path = rfc9162::inclusion_proof(&leaves, index).expect("index is in range");
let ok = rfc9162::verify_inclusion(&leaves[index], index, head.size, &path, &head.root);
println!("inclusion {index} {record} steps {} ok {ok}", path.len());
}
let grown: Vec<[u8; 32]> = ["alpha", "beta", "gamma", "delta"]
.iter()
.map(|record| rfc9162::leaf_hash(record.as_bytes()))
.collect();
let later = rfc9162::Head::over(&grown);
let proof = rfc9162::consistency_proof(&grown, head.size).expect("3 is a prefix of 4");
let ok = rfc9162::verify_consistency(head.size, &head.root, later.size, &later.root, &proof);
println!("later size {} root {}", later.size, later.root);
println!(
"consistency {} -> {} steps {} ok {ok}",
head.size,
later.size,
proof.len()
);
let material = String::from_utf8(head.material("example.org/log")).expect("utf-8");
println!("material {material}");
}
Consistency
The second head is the same three records plus delta. consistency_proof returns the nodes that recompute both roots. verify_consistency accepts the pair when the later tree is the earlier tree with entries appended. The consistency page lists the three nodes in this proof.
//! The program the getting-started page copies into a new crate.
fn main() {
let records = ["alpha", "beta", "gamma"];
let leaves: Vec<[u8; 32]> = records
.iter()
.map(|record| rfc9162::leaf_hash(record.as_bytes()))
.collect();
let head = rfc9162::Head::over(&leaves);
println!("size {}", head.size);
println!("root {}", head.root);
for (index, record) in records.iter().enumerate() {
let path = rfc9162::inclusion_proof(&leaves, index).expect("index is in range");
let ok = rfc9162::verify_inclusion(&leaves[index], index, head.size, &path, &head.root);
println!("inclusion {index} {record} steps {} ok {ok}", path.len());
}
let grown: Vec<[u8; 32]> = ["alpha", "beta", "gamma", "delta"]
.iter()
.map(|record| rfc9162::leaf_hash(record.as_bytes()))
.collect();
let later = rfc9162::Head::over(&grown);
let proof = rfc9162::consistency_proof(&grown, head.size).expect("3 is a prefix of 4");
let ok = rfc9162::verify_consistency(head.size, &head.root, later.size, &later.root, &proof);
println!("later size {} root {}", later.size, later.root);
println!(
"consistency {} -> {} steps {} ok {ok}",
head.size,
later.size,
proof.len()
);
let material = String::from_utf8(head.material("example.org/log")).expect("utf-8");
println!("material {material}");
}
Bytes a signature can cover
Head::material is the domain, the size and the root, separated by tabs. The domain names the log. The tile crate signs a C2SP checkpoint over the same size and root.
//! The program the getting-started page copies into a new crate.
fn main() {
let records = ["alpha", "beta", "gamma"];
let leaves: Vec<[u8; 32]> = records
.iter()
.map(|record| rfc9162::leaf_hash(record.as_bytes()))
.collect();
let head = rfc9162::Head::over(&leaves);
println!("size {}", head.size);
println!("root {}", head.root);
for (index, record) in records.iter().enumerate() {
let path = rfc9162::inclusion_proof(&leaves, index).expect("index is in range");
let ok = rfc9162::verify_inclusion(&leaves[index], index, head.size, &path, &head.root);
println!("inclusion {index} {record} steps {} ok {ok}", path.len());
}
let grown: Vec<[u8; 32]> = ["alpha", "beta", "gamma", "delta"]
.iter()
.map(|record| rfc9162::leaf_hash(record.as_bytes()))
.collect();
let later = rfc9162::Head::over(&grown);
let proof = rfc9162::consistency_proof(&grown, head.size).expect("3 is a prefix of 4");
let ok = rfc9162::verify_consistency(head.size, &head.root, later.size, &later.root, &proof);
println!("later size {} root {}", later.size, later.root);
println!(
"consistency {} -> {} steps {} ok {ok}",
head.size,
later.size,
proof.len()
);
let material = String::from_utf8(head.material("example.org/log")).expect("utf-8");
println!("material {material}");
}
Output
cargo run -q prints:
size 3
root 385da30f3917282c8939dff851957e519ab1846b1351a14c0adb3b11632742aa
inclusion 0 alpha steps 2 ok true
inclusion 1 beta steps 2 ok true
inclusion 2 gamma steps 1 ok true
later size 4 root 42fc54eeb6352f90cc81fdd5791292cca3974a168208b395b06a76240b24884d
consistency 3 -> 4 steps 3 ok true
material example.org/log 3 385da30f3917282c8939dff851957e519ab1846b1351a14c0adb3b11632742aa
The root at size 3 is 385da30f3917282c8939dff851957e519ab1846b1351a14c0adb3b11632742aa. alpha and beta each need two siblings. gamma needs one. The consistency proof from size 3 to size 4 has three nodes, and the check returns true.
Next, build the same log on disk and read the proofs back from the store.