diff --git a/Cargo.lock b/Cargo.lock index 94f93bdb..3a9e47c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -299,6 +299,7 @@ dependencies = [ name = "clvm_rs-fuzz" version = "1.0.0" dependencies = [ + "chia-sha2", "clvmr", "libfuzzer-sys", ] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 8e22d2a2..ef1c8f15 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -11,6 +11,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = { workspace = true } clvmr = { workspace = true } +chia-sha2 = { workspace = true } [[bin]] name = "fuzz_run_program" @@ -77,3 +78,9 @@ name = "keccak" path = "fuzz_targets/keccak.rs" test = false doc = false + +[[bin]] +name = "object-cache" +path = "fuzz_targets/object_cache.rs" +test = false +doc = false diff --git a/fuzz/fuzz_targets/fuzzing_utils.rs b/fuzz/fuzz_targets/fuzzing_utils.rs index 9da1049c..24e30e05 100644 --- a/fuzz/fuzz_targets/fuzzing_utils.rs +++ b/fuzz/fuzz_targets/fuzzing_utils.rs @@ -1,5 +1,5 @@ -use clvmr::allocator::Allocator; -use clvmr::allocator::NodePtr; +use chia_sha2::Sha256; +use clvmr::allocator::{Allocator, NodePtr, SExp}; pub struct BitCursor<'a> { data: &'a [u8], @@ -77,3 +77,77 @@ pub fn make_tree(a: &mut Allocator, cursor: &mut BitCursor, short_atoms: bool) - } } } + +#[allow(dead_code)] +fn hash_atom(buf: &[u8]) -> [u8; 32] { + let mut ctx = Sha256::new(); + ctx.update([1_u8]); + ctx.update(buf); + ctx.finalize() +} + +#[allow(dead_code)] +fn hash_pair(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] { + let mut ctx = Sha256::new(); + ctx.update([2_u8]); + ctx.update(left); + ctx.update(right); + ctx.finalize() +} + +#[allow(dead_code)] +enum TreeOp { + SExp(NodePtr), + Cons, +} + +#[allow(dead_code)] +pub fn tree_hash(a: &Allocator, node: NodePtr) -> [u8; 32] { + let mut hashes = Vec::new(); + let mut ops = vec![TreeOp::SExp(node)]; + + while let Some(op) = ops.pop() { + match op { + TreeOp::SExp(node) => match a.sexp(node) { + SExp::Atom => { + hashes.push(hash_atom(a.atom(node).as_ref())); + } + SExp::Pair(left, right) => { + ops.push(TreeOp::Cons); + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); + } + }, + TreeOp::Cons => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + hashes.push(hash_pair(&first, &rest)); + } + } + } + + assert!(hashes.len() == 1); + hashes[0] +} + +#[allow(dead_code)] +pub fn visit_tree(a: &Allocator, node: NodePtr, mut visit: impl FnMut(&Allocator, NodePtr)) { + let mut nodes = vec![node]; + let mut visited_index = 0; + + while nodes.len() > visited_index { + match a.sexp(nodes[visited_index]) { + SExp::Atom => {} + SExp::Pair(left, right) => { + nodes.push(left); + nodes.push(right); + } + } + visited_index += 1; + } + + // visit nodes bottom-up (right to left). + for node in nodes.into_iter().rev() { + visit(a, node); + } +} diff --git a/fuzz/fuzz_targets/object_cache.rs b/fuzz/fuzz_targets/object_cache.rs new file mode 100644 index 00000000..e6573af2 --- /dev/null +++ b/fuzz/fuzz_targets/object_cache.rs @@ -0,0 +1,30 @@ +#![no_main] +mod fuzzing_utils; + +use clvmr::serde::{node_to_bytes, serialized_length, treehash, ObjectCache}; +use clvmr::Allocator; +use libfuzzer_sys::fuzz_target; + +use fuzzing_utils::{make_tree, tree_hash, visit_tree, BitCursor}; + +fn do_fuzz(data: &[u8], short_atoms: bool) { + let mut cursor = BitCursor::new(data); + let mut allocator = Allocator::new(); + let program = make_tree(&mut allocator, &mut cursor, short_atoms); + + let mut hash_cache = ObjectCache::new(treehash); + let mut length_cache = ObjectCache::new(serialized_length); + visit_tree(&allocator, program, |a, node| { + let expect_hash = tree_hash(a, node); + let expect_len = node_to_bytes(a, node).unwrap().len() as u64; + let computed_hash = hash_cache.get_or_calculate(a, &node).unwrap(); + let computed_len = length_cache.get_or_calculate(a, &node).unwrap(); + assert_eq!(computed_hash, &expect_hash); + assert_eq!(computed_len, &expect_len); + }); +} + +fuzz_target!(|data: &[u8]| { + do_fuzz(data, true); + do_fuzz(data, false); +}); diff --git a/src/serde/mod.rs b/src/serde/mod.rs index 19b5e9d6..f5355adb 100644 --- a/src/serde/mod.rs +++ b/src/serde/mod.rs @@ -18,6 +18,7 @@ mod test; pub use de::node_from_bytes; pub use de_br::{node_from_bytes_backrefs, node_from_bytes_backrefs_record}; pub use de_tree::{parse_triples, ParsedTriple}; +pub use object_cache::{serialized_length, treehash, ObjectCache}; pub use ser::{node_to_bytes, node_to_bytes_limit}; pub use ser_br::{node_to_bytes_backrefs, node_to_bytes_backrefs_limit}; pub use tools::{