Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.