diff --git a/grovedb/src/lib.rs b/grovedb/src/lib.rs index 2aa0c0f0c..76249bc15 100644 --- a/grovedb/src/lib.rs +++ b/grovedb/src/lib.rs @@ -9,8 +9,8 @@ use std::{ }; pub use merk::proofs::{query::QueryItem, Query}; -use merk::{self, Merk}; -use rs_merkle::{algorithms::Sha256, MerkleTree}; +use merk::{self, proofs::query::Map, Merk}; +use rs_merkle::{algorithms::Sha256, MerkleProof, MerkleTree}; use storage::{ rocksdb_storage::{PrefixedRocksDbStorage, PrefixedRocksDbStorageError}, Storage, @@ -32,6 +32,8 @@ pub enum Error { CyclicReference, #[error("reference hops limit exceeded")] ReferenceLimit, + #[error("invalid proof: {0}")] + InvalidProof(&'static str), #[error("invalid path: {0}")] InvalidPath(&'static str), // Irrecoverable errors @@ -321,6 +323,90 @@ impl GroveDb { Ok(proof_result) } + // Validates proof structure and returns the root hash + // and query result + pub fn execute_proof( + path: &[&[u8]], + proofs: &mut Vec>, + ) -> Result<([u8; 32], Map), Error> { + if proofs.len() < 2 { + return Err(Error::InvalidProof("Proof length should be 2 or more")); + } + + if proofs.len() - 2 != path.len() { + return Err(Error::InvalidProof( + "Proof length should be two greater than path", + )); + } + + let root_leaf_keys: HashMap, usize> = + bincode::deserialize(&proofs.pop().unwrap()[..]) + .map_err(|_| Error::CorruptedData(String::from("unable to deserialize element")))?; + + let mut proof_iterator = proofs.iter(); + let reverse_path_iterator = path.iter().rev(); + + let leaf_proof = proof_iterator + .next() + .expect("Constraint checks above enforces leaf proof must exist"); + + let (mut last_root_hash, leaf_result_map) = match merk::execute_proof(&leaf_proof[..]) { + Ok(result) => Ok(result), + Err(_) => Err(Error::InvalidProof("Invalid proof element")), + }?; + + let mut proof_path_zip = proof_iterator.zip(reverse_path_iterator).peekable(); + let mut root_hash: Option<[u8; 32]> = None; + + while let Some((proof, key)) = proof_path_zip.next() { + if proof_path_zip.peek().is_some() { + // Non root proof, validate that the proof is valid and + // the result map contains the last subtree root hash i.e the previous + // subtree is a child of this tree + let proof_result = match merk::execute_proof(&proof[..]) { + Ok(result) => Ok(result), + Err(_) => Err(Error::InvalidProof("Invalid proof element")), + }?; + let result_map = proof_result.1; + + let elem: Element = + bincode::deserialize(result_map.get(key).unwrap().unwrap()).unwrap(); + let merk_root_hash = match elem { + Element::Tree(hash) => Ok(hash), + _ => Err(Error::InvalidProof( + "Intermediate proofs should be for trees", + )), + }?; + + if merk_root_hash != last_root_hash { + return Err(Error::InvalidProof("Bad path")); + } + + last_root_hash = proof_result.0; + } else { + // Last proof (root proof) + let root_proof = match MerkleProof::::try_from(&proof[..]) { + Ok(root_proof) => Ok(root_proof), + Err(_) => Err(Error::InvalidProof("Invalid proof element")), + }?; + let a: [u8; 32] = last_root_hash; + root_hash = + Some( + match root_proof.root(&[root_leaf_keys[*key]], &[a], root_leaf_keys.len()) { + Ok(hash) => Ok(hash), + Err(_) => Err(Error::InvalidProof("Invalid proof element")), + }?, + ); + } + } + + return if let Some(hash) = root_hash { + Ok((hash, leaf_result_map)) + } else { + Err(Error::InvalidProof("Invalid proof element")) + } + } + /// Method to propagate updated subtree root hashes up to GroveDB root fn propagate_changes(&mut self, path: &[&[u8]]) -> Result<(), Error> { let mut split_path = path.split_last(); diff --git a/grovedb/src/tests.rs b/grovedb/src/tests.rs index ee03c7ae2..fcec067ce 100644 --- a/grovedb/src/tests.rs +++ b/grovedb/src/tests.rs @@ -308,6 +308,116 @@ fn test_proof_construction() { } #[test] +fn test_successful_proof_verification() { + let mut temp_db = make_grovedb(); + temp_db + .insert(&[TEST_LEAF], b"innertree".to_vec(), Element::empty_tree()) + .expect("successful subtree insert"); + temp_db + .insert( + &[TEST_LEAF, b"innertree"], + b"innertree1.1".to_vec(), + Element::empty_tree(), + ) + .expect("successful subtree insert"); + + temp_db + .insert(&[TEST_LEAF], b"innertree2".to_vec(), Element::empty_tree()) + .expect("successful subtree insert"); + + temp_db + .insert( + &[TEST_LEAF, b"innertree", b"innertree1.1"], + b"key1".to_vec(), + Element::Item(b"value1".to_vec()), + ) + .expect("successful item insert"); + + temp_db + .insert( + &[TEST_LEAF, b"innertree2"], + b"key1".to_vec(), + Element::Item(b"value2".to_vec()), + ) + .expect("successful item insert"); + + // dbg!(temp_db.root_tree.root().unwrap()); + + let mut proof_query = Query::new(); + proof_query.insert_key(b"key1".to_vec()); + let mut proof = temp_db + .proof(&[TEST_LEAF, b"innertree", b"innertree1.1"], proof_query) + .unwrap(); + + let (root_hash, result_map) = + GroveDb::execute_proof(&[TEST_LEAF, b"innertree", b"innertree1.1"], &mut proof).unwrap(); + + // Check that the root hash matches + assert_eq!(temp_db.root_tree.root().unwrap(), root_hash); + + // Check that the result map is correct + let elem: Element = bincode::deserialize(result_map.get(b"key1").unwrap().unwrap()).unwrap(); + assert_eq!(elem, Element::Item(b"value1".to_vec())); +} + +#[test] +#[should_panic] +fn test_malicious_proof_verification() { + // Verification should detect when the proofs don't follow a valid path + // i.e. root - leaf (with each individual merk connected to their parent by + // their root hash) Grovedb enforces a valid path, so will manually + // construct a malicious proof + + // 4 trees, merk_one, merk_two, merk_three, root + // root references m3, m3 references m1 (instead of m2), m2 references m1 + // m3 breaks the chain and as such the proof should not be considered valid + + let mut proofs: Vec> = Vec::new(); + + // Merk One + let mut merk_one = TempMerk::new(); + let value_element = Element::Item(b"value1".to_vec()); + value_element.insert(&mut merk_one, b"key1".to_vec()); + + let mut proof_query = Query::new(); + proof_query.insert_key(b"key1".to_vec()); + proofs.push(merk_one.prove(proof_query).unwrap()); + + // Merk Two + let mut merk_two = TempMerk::new(); + let merk_two_element = Element::Tree(merk_one.root_hash()); + merk_two_element.insert(&mut merk_two, b"innertree-2".to_vec()); + + let mut proof_query = Query::new(); + proof_query.insert_key(b"innertree-2".to_vec()); + proofs.push(merk_two.prove(proof_query).unwrap()); + + // Merk Three + let mut merk_three = TempMerk::new(); + let merk_three_element = Element::Tree(merk_one.root_hash()); + merk_three_element.insert(&mut merk_three, b"innertree".to_vec()); + + let mut proof_query = Query::new(); + proof_query.insert_key(b"innertree".to_vec()); + proofs.push(merk_three.prove(proof_query).unwrap()); + + let another_test_leaf_merk = TempMerk::new(); + + // Root Tree + let leaves = [merk_three.root_hash(), another_test_leaf_merk.root_hash()]; + let root_tree = MerkleTree::::from_leaves(&leaves); + proofs.push(root_tree.proof(&vec![0]).to_bytes()); + + let (root_hash, result_map) = + GroveDb::execute_proof(&[TEST_LEAF, b"innertree", b"innertree-2"], &mut proofs).unwrap(); + + // Check that the root hash matches + assert_eq!(root_tree.root().unwrap(), root_hash); + + let elem: Element = bincode::deserialize(result_map.get(b"key1").unwrap().unwrap()).unwrap(); + assert_eq!(elem, Element::Item(b"value1".to_vec())); +} + fn test_checkpoint() { let mut db = make_grovedb(); let element1 = Element::Item(b"ayy".to_vec());