diff --git a/.gitignore b/.gitignore index 950b38de..268b61f7 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ core/trie/.criterion/ **/.criterion/ + +**/fuzz/artifacts/ +**/fuzz/corpus/ diff --git a/test-support/reference-trie/src/lib.rs b/test-support/reference-trie/src/lib.rs index 43bae999..c06862c9 100644 --- a/test-support/reference-trie/src/lib.rs +++ b/test-support/reference-trie/src/lib.rs @@ -20,6 +20,7 @@ use std::marker::PhantomData; use std::ops::Range; use parity_scale_codec::{Decode, Input, Output, Encode, Compact, Error as CodecError}; use trie_root::Hasher; + use trie_db::{ node::{NibbleSlicePlan, NodePlan, NodeHandlePlan}, triedbmut::ChildReference, @@ -33,11 +34,11 @@ use std::borrow::Borrow; use keccak_hasher::KeccakHasher; pub use trie_db::{ - Trie, TrieMut, TrieDB, TrieDBMut, TrieError, TrieIterator, TrieDBNodeIterator, - NibbleSlice, NibbleVec, NodeCodec, Recorder, - encode_compact, decode_compact, + decode_compact, encode_compact, + nibble_ops, NibbleSlice, NibbleVec, NodeCodec, proof, Record, Recorder, + Trie, TrieConfiguration, TrieDB, TrieDBMut, TrieDBNodeIterator, TrieError, TrieIterator, + TrieLayout, TrieMut, }; -pub use trie_db::{Record, TrieLayout, TrieConfiguration, nibble_ops}; pub use trie_root::TrieStream; pub mod node { pub use trie_db::node::Node; diff --git a/trie-db/benches/bench.rs b/trie-db/benches/bench.rs index b9ee6d56..6a9f55b9 100644 --- a/trie-db/benches/bench.rs +++ b/trie-db/benches/bench.rs @@ -14,7 +14,16 @@ #[macro_use] extern crate criterion; -use criterion::{Criterion, black_box, Bencher}; +extern crate memory_db; +extern crate rand; +extern crate trie_db; +extern crate trie_standardmap; + +use criterion::{Bencher, black_box, Criterion}; + +use trie_db::{NibbleSlice, proof::{generate_proof, verify_proof}, Trie}; +use trie_standardmap::{Alphabet, StandardMap, ValueMode}; + criterion_group!(benches, root_old, root_new, @@ -32,16 +41,10 @@ criterion_group!(benches, trie_mut_build_b, trie_iteration, nibble_common_prefix, + trie_proof_verification, ); criterion_main!(benches); -extern crate trie_standardmap; -extern crate trie_db; -extern crate memory_db; -extern crate rand; -use trie_standardmap::{Alphabet, StandardMap, ValueMode}; -use trie_db::NibbleSlice; - fn nibble_common_prefix(b: &mut Criterion) { let st = StandardMap { alphabet: Alphabet::Custom(b"abcd".to_vec()), @@ -463,3 +466,40 @@ fn trie_iteration(c: &mut Criterion) { }) ); } + +fn trie_proof_verification(c: &mut Criterion) { + use memory_db::HashKey; + + let mut data = input_unsorted(29, 204800, 32); + let mut keys = data[(data.len() / 3)..] + .iter() + .map(|(key, _)| key.clone()) + .collect::>(); + data.truncate(data.len() * 2 / 3); + + let data = data_sorted_unique(data); + keys.sort(); + keys.dedup(); + + let mut mdb = memory_db::MemoryDB::<_, HashKey<_>, _>::default(); + let root = reference_trie::calc_root_build(data, &mut mdb); + + let trie = reference_trie::RefTrieDB::new(&mdb, &root).unwrap(); + let proof = generate_proof(&trie, keys.iter()).unwrap(); + let items = keys.into_iter() + .map(|key| { + let value = trie.get(&key).unwrap(); + (key, value) + }) + .collect::>(); + + c.bench_function("trie_proof_verification", move |b: &mut Bencher| + b.iter(|| { + verify_proof::( + &root, + &proof, + items.iter() + ).unwrap(); + }) + ); +} diff --git a/trie-db/fuzz/Cargo.toml b/trie-db/fuzz/Cargo.toml index cfc7c434..9abe5d3b 100644 --- a/trie-db/fuzz/Cargo.toml +++ b/trie-db/fuzz/Cargo.toml @@ -47,3 +47,11 @@ path = "fuzz_targets/no_ext_insert.rs" [[bin]] name = "no_ext_insert_rem" path = "fuzz_targets/no_ext_insert_rem.rs" + +[[bin]] +name = "trie_proof_valid" +path = "fuzz_targets/trie_proof_valid.rs" + +[[bin]] +name = "trie_proof_invalid" +path = "fuzz_targets/trie_proof_invalid.rs" diff --git a/trie-db/fuzz/fuzz_targets/trie_proof_invalid.rs b/trie-db/fuzz/fuzz_targets/trie_proof_invalid.rs new file mode 100644 index 00000000..565e7eb9 --- /dev/null +++ b/trie-db/fuzz/fuzz_targets/trie_proof_invalid.rs @@ -0,0 +1,9 @@ + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use trie_db_fuzz::fuzz_that_verify_rejects_invalid_proofs; + +fuzz_target!(|data: &[u8]| { + fuzz_that_verify_rejects_invalid_proofs(data); +}); \ No newline at end of file diff --git a/trie-db/fuzz/fuzz_targets/trie_proof_valid.rs b/trie-db/fuzz/fuzz_targets/trie_proof_valid.rs new file mode 100644 index 00000000..44f2b120 --- /dev/null +++ b/trie-db/fuzz/fuzz_targets/trie_proof_valid.rs @@ -0,0 +1,9 @@ + +#![no_main] + +use libfuzzer_sys::fuzz_target; +use trie_db_fuzz::fuzz_that_verify_accepts_valid_proofs; + +fuzz_target!(|data: &[u8]| { + fuzz_that_verify_accepts_valid_proofs(data); +}); \ No newline at end of file diff --git a/trie-db/fuzz/src/lib.rs b/trie-db/fuzz/src/lib.rs index 7b89e12f..dc397ba8 100644 --- a/trie-db/fuzz/src/lib.rs +++ b/trie-db/fuzz/src/lib.rs @@ -13,19 +13,21 @@ // limitations under the License. - -use memory_db::{MemoryDB, HashKey, PrefixedKey}; +use hash_db::Hasher; +use keccak_hasher::KeccakHasher; +use memory_db::{HashKey, MemoryDB, PrefixedKey}; use reference_trie::{ - RefTrieDBMutNoExt, - RefTrieDBMut, - reference_trie_root, calc_root_no_extension, compare_no_extension_insert_remove, + ExtensionLayout, + NoExtensionLayout, + proof::{generate_proof, verify_proof}, + reference_trie_root, + RefTrieDBMut, + RefTrieDBMutNoExt, }; -use trie_db::{TrieMut, DBValue}; -use keccak_hasher::KeccakHasher; - - +use std::convert::TryInto; +use trie_db::{DBValue, Trie, TrieDB, TrieDBMut, TrieLayout, TrieMut}; fn fuzz_to_data(input: &[u8]) -> Vec<(Vec,Vec)> { let mut result = Vec::new(); @@ -173,3 +175,95 @@ pub fn fuzz_that_no_extension_insert_remove(input: &[u8]) { let memdb = MemoryDB::<_, PrefixedKey<_>, _>::default(); compare_no_extension_insert_remove(data, memdb); } + +pub fn fuzz_that_verify_accepts_valid_proofs(input: &[u8]) { + let mut data = fuzz_to_data(input); + // Split data into 3 parts: + // - the first 1/3 is added to the trie and not included in the proof + // - the second 1/3 is added to the trie and included in the proof + // - the last 1/3 is not added to the trie and the proof proves non-inclusion of them + let mut keys = data[(data.len() / 3)..] + .iter() + .map(|(key, _)| key.clone()) + .collect::>(); + data.truncate(data.len() * 2 / 3); + + let data = data_sorted_unique(data); + keys.sort(); + keys.dedup(); + + let (root, proof, items) = test_generate_proof::(data, keys); + assert!(verify_proof::(&root, &proof, items.iter()).is_ok()); +} + +pub fn fuzz_that_verify_rejects_invalid_proofs(input: &[u8]) { + if input.len() < 4 { + return; + } + + let random_int = u32::from_le_bytes( + input[0..4].try_into().expect("slice is 4 bytes") + ) as usize; + + let mut data = fuzz_to_data(&input[4..]); + // Split data into 3 parts: + // - the first 1/3 is added to the trie and not included in the proof + // - the second 1/3 is added to the trie and included in the proof + // - the last 1/3 is not added to the trie and the proof proves non-inclusion of them + let mut keys = data[(data.len() / 3)..] + .iter() + .map(|(key, _)| key.clone()) + .collect::>(); + data.truncate(data.len() * 2 / 3); + + let data = data_sorted_unique(data); + keys.sort(); + keys.dedup(); + + if keys.is_empty() { + return; + } + + let (root, proof, mut items) = test_generate_proof::(data, keys); + + // Make one item at random incorrect. + let items_idx = random_int % items.len(); + match &mut items[items_idx] { + (_, Some(value)) if random_int % 2 == 0 => value.push(0), + (_, value) if value.is_some() => *value = None, + (_, value) => *value = Some(DBValue::new()), + } + assert!(verify_proof::(&root, &proof, items.iter()).is_err()); +} + +fn test_generate_proof( + entries: Vec<(Vec, Vec)>, + keys: Vec>, +) -> (::Out, Vec>, Vec<(Vec, Option)>) +{ + // Populate DB with full trie from entries. + let (db, root) = { + let mut db = , _>>::default(); + let mut root = Default::default(); + { + let mut trie = >::new(&mut db, &mut root); + for (key, value) in entries { + trie.insert(&key, &value).unwrap(); + } + } + (db, root) + }; + + // Generate proof for the given keys.. + let trie = >::new(&db, &root).unwrap(); + let proof = generate_proof::<_, L, _, _>(&trie, keys.iter()).unwrap(); + let items = keys.into_iter() + .map(|key| { + let value = trie.get(&key).unwrap(); + (key,value) + }) + .collect(); + + (root, proof, items) +} + diff --git a/trie-db/src/lib.rs b/trie-db/src/lib.rs index 4ea86048..a76af90c 100644 --- a/trie-db/src/lib.rs +++ b/trie-db/src/lib.rs @@ -72,6 +72,7 @@ impl MaybeDebug for T {} pub mod node; +pub mod proof; pub mod triedb; pub mod triedbmut; pub mod sectriedb; diff --git a/trie-db/src/nibble/leftnibbleslice.rs b/trie-db/src/nibble/leftnibbleslice.rs new file mode 100644 index 00000000..ae495e53 --- /dev/null +++ b/trie-db/src/nibble/leftnibbleslice.rs @@ -0,0 +1,229 @@ +// Copyright 2019 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use core_::cmp::{self, Ordering}; + +use crate::nibble::{nibble_ops::{self, NIBBLE_PER_BYTE}, NibbleSlice}; + +/// A representation of a nibble slice which is left-aligned. The regular `NibbleSlice` is +/// right-aligned, meaning it does not support efficient truncation from the right side. +/// +/// This is an immutable struct. No operations actually change it. +pub struct LeftNibbleSlice<'a> { + bytes: &'a [u8], + len: usize, +} + +impl<'a> LeftNibbleSlice<'a> { + /// Constructs a byte-aligned nibble slice from a byte slice. + pub fn new(bytes: &'a [u8]) -> Self { + LeftNibbleSlice { + bytes, + len: bytes.len() * NIBBLE_PER_BYTE, + } + } + + /// Returns the length of the slice in nibbles. + pub fn len(&self) -> usize { + self.len + } + + /// Get the nibble at a nibble index padding with a 0 nibble. Returns None if the index is + /// out of bounds. + pub fn at(&self, index: usize) -> Option { + if index < self.len() { + Some(nibble_ops::left_nibble_at(self.bytes, index)) + } else { + None + } + } + + /// Returns a new slice truncated from the right side to the given length. If the given length + /// is greater than that of this slice, the function just returns a copy. + pub fn truncate(&self, len: usize) -> Self { + LeftNibbleSlice { + bytes: self.bytes, + len: cmp::min(len, self.len), + } + } + + /// Returns whether the given slice is a prefix of this one. + pub fn starts_with(&self, prefix: &LeftNibbleSlice<'a>) -> bool { + self.truncate(prefix.len()) == *prefix + } + + /// Returns whether another regular (right-aligned) nibble slice is contained in this one at + /// the given offset. + pub fn contains(&self, partial: &NibbleSlice, offset: usize) -> bool { + (0..partial.len()).all(|i| self.at(offset + i) == Some(partial.at(i))) + } + + fn cmp(&self, other: &Self) -> Ordering { + let common_len = cmp::min(self.len(), other.len()); + let common_byte_len = common_len / NIBBLE_PER_BYTE; + + // Quickly compare the common prefix of the byte slices. + match self.bytes[..common_byte_len].cmp(&other.bytes[..common_byte_len]) { + Ordering::Equal => {} + ordering => return ordering, + } + + // Compare nibble-by-nibble (either 0 or 1 nibbles) any after the common byte prefix. + for i in (common_byte_len * NIBBLE_PER_BYTE)..common_len { + let a = self.at(i).expect("i < len; len == self.len() qed"); + let b = other.at(i).expect("i < len; len == other.len(); qed"); + match a.cmp(&b) { + Ordering::Equal => {} + ordering => return ordering, + } + } + + // If common nibble prefix is the same, finally compare lengths. + self.len().cmp(&other.len()) + } +} + +impl<'a> PartialEq for LeftNibbleSlice<'a> { + fn eq(&self, other: &Self) -> bool { + let len = self.len(); + if other.len() != len { + return false; + } + + // Quickly compare the common prefix of the byte slices. + let byte_len = len / NIBBLE_PER_BYTE; + if self.bytes[..byte_len] != other.bytes[..byte_len] { + return false; + } + + // Compare nibble-by-nibble (either 0 or 1 nibbles) any after the common byte prefix. + for i in (byte_len * NIBBLE_PER_BYTE)..len { + let a = self.at(i).expect("i < len; len == self.len() qed"); + let b = other.at(i).expect("i < len; len == other.len(); qed"); + if a != b { + return false + } + } + + true + } +} + +impl<'a> Eq for LeftNibbleSlice<'a> {} + +impl<'a> PartialOrd for LeftNibbleSlice<'a> { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl<'a> Ord for LeftNibbleSlice<'a> { + fn cmp(&self, other: &Self) -> Ordering { + self.cmp(other) + } +} + +#[cfg(feature = "std")] +impl<'a> std::fmt::Debug for LeftNibbleSlice<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for i in 0..self.len() { + let nibble = self.at(i).expect("i < self.len(); qed"); + match i { + 0 => write!(f, "{:01x}", nibble)?, + _ => write!(f, "'{:01x}", nibble)?, + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_len() { + assert_eq!(LeftNibbleSlice::new(&[]).len(), 0); + assert_eq!(LeftNibbleSlice::new(&b"hello"[..]).len(), 10); + assert_eq!(LeftNibbleSlice::new(&b"hello"[..]).truncate(7).len(), 7); + } + + #[test] + fn test_at() { + let slice = LeftNibbleSlice::new(&b"\x01\x23\x45\x67"[..]).truncate(7); + assert_eq!(slice.at(0), Some(0)); + assert_eq!(slice.at(6), Some(6)); + assert_eq!(slice.at(7), None); + assert_eq!(slice.at(8), None); + } + + #[test] + fn test_starts_with() { + assert!( + LeftNibbleSlice::new(b"hello").starts_with(&LeftNibbleSlice::new(b"heli").truncate(7)) + ); + assert!( + !LeftNibbleSlice::new(b"hello").starts_with(&LeftNibbleSlice::new(b"heli").truncate(8)) + ); + } + + #[test] + fn test_contains() { + assert!( + LeftNibbleSlice::new(b"hello").contains(&NibbleSlice::new_offset(b"ello", 0), 2) + ); + assert!( + LeftNibbleSlice::new(b"hello").contains(&NibbleSlice::new_offset(b"ello", 1), 3) + ); + assert!( + !LeftNibbleSlice::new(b"hello").contains(&NibbleSlice::new_offset(b"allo", 1), 3) + ); + assert!( + !LeftNibbleSlice::new(b"hello").contains(&NibbleSlice::new_offset(b"ello!", 1), 3) + ); + } + + #[test] + fn test_cmp() { + assert!(LeftNibbleSlice::new(b"hallo") < LeftNibbleSlice::new(b"hello")); + assert!(LeftNibbleSlice::new(b"hello") > LeftNibbleSlice::new(b"hallo")); + assert_eq!( + LeftNibbleSlice::new(b"hello").cmp(&LeftNibbleSlice::new(b"hello")), + Ordering::Equal + ); + + assert!( + LeftNibbleSlice::new(b"hello\x10") + < LeftNibbleSlice::new(b"hello\x20").truncate(11) + ); + assert!( + LeftNibbleSlice::new(b"hello\x20").truncate(11) + > LeftNibbleSlice::new(b"hello\x10") + ); + + assert!( + LeftNibbleSlice::new(b"hello\x10").truncate(11) + < LeftNibbleSlice::new(b"hello\x10") + ); + assert!( + LeftNibbleSlice::new(b"hello\x10") + > LeftNibbleSlice::new(b"hello\x10").truncate(11) + ); + assert_eq!( + LeftNibbleSlice::new(b"hello\x10").truncate(11) + .cmp(&LeftNibbleSlice::new(b"hello\x10").truncate(11)), + Ordering::Equal + ); + } +} \ No newline at end of file diff --git a/trie-db/src/nibble/mod.rs b/trie-db/src/nibble/mod.rs index 84db2a39..04fd85a4 100644 --- a/trie-db/src/nibble/mod.rs +++ b/trie-db/src/nibble/mod.rs @@ -14,14 +14,17 @@ //! Nibble oriented methods. -mod nibblevec; -mod nibbleslice; use crate::node::NodeKey; use crate::core_::cmp; +pub use self::leftnibbleslice::LeftNibbleSlice; + +mod nibblevec; +mod nibbleslice; +mod leftnibbleslice; + /// Utility methods to work on radix 16 nibble. pub mod nibble_ops { - use super::*; /// Single nibble length in bit. diff --git a/trie-db/src/proof/generate.rs b/trie-db/src/proof/generate.rs new file mode 100644 index 00000000..d1ef0b4a --- /dev/null +++ b/trie-db/src/proof/generate.rs @@ -0,0 +1,528 @@ +// Copyright 2019 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Generation of compact proofs for Merkle-Patricia tries. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core_::convert::TryInto; +use core_::marker::PhantomData; +use core_::ops::Range; + +use hash_db::Hasher; + +use crate::{ + CError, ChildReference, nibble::LeftNibbleSlice, nibble_ops::NIBBLE_LENGTH, NibbleSlice, node::{NodeHandle, NodeHandlePlan, NodePlan, OwnedNode}, NodeCodec, Recorder, + Result as TrieResult, Trie, TrieError, TrieHash, + TrieLayout, +}; + +struct StackEntry<'a, C: NodeCodec> { + /// The prefix is the nibble path to the node in the trie. + prefix: LeftNibbleSlice<'a>, + node: OwnedNode>, + /// The hash of the node or None if it is referenced inline. + node_hash: Option, + /// Whether the value should be omitted in the generated proof. + omit_value: bool, + /// The next entry in the stack is a child of the preceding entry at this index. For branch + /// nodes, the index is in [0, NIBBLE_LENGTH] and for extension nodes, the index is in [0, 1]. + child_index: usize, + /// The child references to use in constructing the proof nodes. + children: Vec>>, + /// The index into the proof vector that the encoding of this entry should be placed at. + output_index: Option, + _marker: PhantomData, +} + +impl<'a, C: NodeCodec> StackEntry<'a, C> { + fn new( + prefix: LeftNibbleSlice<'a>, + node_data: Vec, + node_hash: Option, + output_index: Option, + ) -> TrieResult + { + let node = OwnedNode::new::(node_data) + .map_err(|err| Box::new( + TrieError::DecoderError(node_hash.unwrap_or_default(), err) + ))?; + let children_len = match node.node_plan() { + NodePlan::Empty | NodePlan::Leaf { .. } => 0, + NodePlan::Extension { .. } => 1, + NodePlan::Branch { .. } | NodePlan::NibbledBranch { .. } => NIBBLE_LENGTH, + }; + Ok(StackEntry { + prefix, + node, + node_hash, + omit_value: false, + child_index: 0, + children: vec![None; children_len], + output_index, + _marker: PhantomData::default(), + }) + } + + /// Encode this entry to an encoded trie node with data properly omitted. + fn encode_node(mut self) -> TrieResult, C::HashOut, C::Error> { + let node_data = self.node.data(); + Ok(match self.node.node_plan() { + NodePlan::Empty => node_data.to_vec(), + NodePlan::Leaf { .. } if !self.omit_value => node_data.to_vec(), + NodePlan::Leaf { partial, value: _ } => { + let partial = partial.build(node_data); + C::leaf_node(partial.right(), &[]) + } + NodePlan::Extension { .. } if self.child_index == 0 => node_data.to_vec(), + NodePlan::Extension { partial: partial_plan, child: _ } => { + let partial = partial_plan.build(node_data); + let child = self.children[0] + .expect( + "for extension nodes, children[0] is guaranteed to be Some when \ + child_index > 0; \ + the branch guard guarantees that child_index > 0" + ); + C::extension_node( + partial.right_iter(), + partial.len(), + child + ) + } + NodePlan::Branch { value, children } => { + Self::complete_branch_children( + node_data, + children, + self.child_index, + &mut self.children + )?; + C::branch_node( + self.children.into_iter(), + value_with_omission(node_data, value, self.omit_value) + ) + }, + NodePlan::NibbledBranch { partial: partial_plan, value, children } => { + let partial = partial_plan.build(node_data); + Self::complete_branch_children( + node_data, + children, + self.child_index, + &mut self.children + )?; + C::branch_node_nibbled( + partial.right_iter(), + partial.len(), + self.children.into_iter(), + value_with_omission(node_data, value, self.omit_value) + ) + }, + }) + } + + /// Populate the remaining references in `children` with references copied from + /// `child_handles`. + /// + /// Preconditions: + /// - children has size NIBBLE_LENGTH. + fn complete_branch_children( + node_data: &[u8], + child_handles: &[Option; NIBBLE_LENGTH], + child_index: usize, + children: &mut [Option>], + ) -> TrieResult<(), C::HashOut, C::Error> + { + for i in child_index..NIBBLE_LENGTH { + children[i] = child_handles[i] + .as_ref() + .map(|child_plan| + child_plan + .build(node_data) + .try_into() + .map_err(|hash| Box::new( + TrieError::InvalidHash(C::HashOut::default(), hash) + )) + ) + .transpose()?; + } + Ok(()) + } + + /// Sets the reference for the child at index `child_index`. If the child is hash-referenced in + /// the trie, the proof node reference will be an omitted child. If the child is + /// inline-referenced in the trie, the proof node reference will also be inline. + fn set_child(&mut self, encoded_child: &[u8]) { + let child_ref = match self.node.node_plan() { + NodePlan::Empty | NodePlan::Leaf { .. } => panic!( + "empty and leaf nodes have no children; \ + thus they are never descended into; \ + thus set_child will not be called on an entry with one of these types" + ), + NodePlan::Extension { child, .. } => { + assert_eq!( + self.child_index, 0, + "extension nodes only have one child; \ + set_child is called when the only child is popped from the stack; \ + child_index is 0 before child is pushed to the stack; qed" + ); + Some(Self::replacement_child_ref(encoded_child, child)) + } + NodePlan::Branch { children, .. } | NodePlan::NibbledBranch { children, .. } => { + assert!( + self.child_index < NIBBLE_LENGTH, + "extension nodes have at most NIBBLE_LENGTH children; \ + set_child is called when the only child is popped from the stack; \ + child_index is ChildReference + { + match child { + NodeHandlePlan::Hash(_) => ChildReference::Inline(C::HashOut::default(), 0), + NodeHandlePlan::Inline(_) => { + let mut hash = C::HashOut::default(); + assert!( + encoded_child.len() <= hash.as_ref().len(), + "the encoding of the raw inline node is checked to be at most the hash length + before descending; \ + the encoding of the proof node is always smaller than the raw node as data is \ + only stripped" + ); + &mut hash.as_mut()[..encoded_child.len()].copy_from_slice(encoded_child); + ChildReference::Inline(hash, encoded_child.len()) + } + } + } +} + +/// Generate a compact proof for key-value pairs in a trie given a set of keys. +/// +/// Assumes inline nodes have only inline children. +pub fn generate_proof<'a, T, L, I, K>(trie: &T, keys: I) + -> TrieResult>, TrieHash, CError> + where + T: Trie, + L: TrieLayout, + I: IntoIterator, + K: 'a + AsRef<[u8]> +{ + // Sort and deduplicate keys. + let mut keys = keys.into_iter() + .map(|key| key.as_ref()) + .collect::>(); + keys.sort(); + keys.dedup(); + + // The stack of nodes through a path in the trie. Each entry is a child node of the preceding + // entry. + let mut stack = >>::new(); + + // The mutated trie nodes comprising the final proof. + let mut proof_nodes = Vec::new(); + + for key_bytes in keys { + let key = LeftNibbleSlice::new(key_bytes); + + // Unwind the stack until the new entry is a child of the last entry on the stack. + unwind_stack(&mut stack, &mut proof_nodes, Some(&key))?; + + // Perform the trie lookup for the next key, recording the sequence of nodes traversed. + let mut recorder = Recorder::new(); + let expected_value = trie.get_with(key_bytes, &mut recorder)?; + let mut recorded_nodes = recorder.drain().into_iter().peekable(); + + // Skip over recorded nodes already on the stack. Their indexes into the respective vector + // (either `stack` or `recorded_nodes`) match under the assumption that inline nodes have + // only inline children. + { + let mut stack_iter = stack.iter().peekable(); + while let (Some(next_record), Some(next_entry)) = + (recorded_nodes.peek(), stack_iter.peek()) + { + if next_entry.node_hash != Some(next_record.hash) { + break; + } + recorded_nodes.next(); + stack_iter.next(); + } + } + + loop { + let step = match stack.last_mut() { + Some(entry) => match_key_to_node::( + entry.node.data(), + entry.node.node_plan(), + &mut entry.omit_value, + &mut entry.child_index, + &mut entry.children, + &key, + entry.prefix.len(), + )?, + // If stack is empty, descend into the root node. + None => Step::Descend { + child_prefix_len: 0, + child: NodeHandle::Hash(trie.root().as_ref()), + }, + }; + + match step { + Step::Descend { child_prefix_len, child } => { + let child_prefix = key.truncate(child_prefix_len); + let child_entry = match child { + NodeHandle::Hash(hash) => { + let child_record = recorded_nodes.next() + .expect( + "this function's trie traversal logic mirrors that of Lookup; \ + thus the sequence of traversed nodes must be the same; \ + so the next child node must have been recorded and must have \ + the expected hash" + ); + // Proof for `assert_eq` is in the `expect` proof above. + assert_eq!(child_record.hash.as_ref(), hash); + + let output_index = proof_nodes.len(); + // Insert a placeholder into output which will be replaced when this + // new entry is popped from the stack. + proof_nodes.push(Vec::new()); + StackEntry::new( + child_prefix, + child_record.data, + Some(child_record.hash), + Some(output_index), + )? + } + NodeHandle::Inline(data) => { + if data.len() > L::Hash::LENGTH { + return Err(Box::new( + TrieError::InvalidHash(>::default(), data.to_vec()) + )); + } + StackEntry::new( + child_prefix, + data.to_vec(), + None, + None, + )? + } + }; + stack.push(child_entry); + } + Step::FoundValue(value) => { + assert_eq!( + value, + expected_value.as_ref().map(|v| v.as_ref()), + "expected_value is found using `trie_db::Lookup`; \ + value is found by traversing the same nodes recorded during the lookup \ + using the same logic; \ + thus the values found must be equal" + ); + assert!( + recorded_nodes.next().is_none(), + "the recorded nodes are only recorded on the lookup path to the current \ + key; \ + recorded nodes is the minimal sequence of trie nodes on the lookup path; \ + the value was found by traversing recorded nodes, so there must be none \ + remaining" + ); + break; + } + } + } + } + + unwind_stack(&mut stack, &mut proof_nodes, None)?; + Ok(proof_nodes) +} + +enum Step<'a> { + Descend { + child_prefix_len: usize, + child: NodeHandle<'a>, + }, + FoundValue(Option<&'a [u8]>), +} + +/// Determine the next algorithmic step to take by matching the current key against the current top +/// entry on the stack. +fn match_key_to_node<'a, C: NodeCodec>( + node_data: &'a [u8], + node_plan: &NodePlan, + omit_value: &mut bool, + child_index: &mut usize, + children: &mut [Option>], + key: &LeftNibbleSlice, + prefix_len: usize, +) -> TrieResult, C::HashOut, C::Error> +{ + Ok(match node_plan { + NodePlan::Empty => Step::FoundValue(None), + NodePlan::Leaf { partial: partial_plan, value: value_range } => { + let partial = partial_plan.build(node_data); + if key.contains(&partial, prefix_len) && + key.len() == prefix_len + partial.len() + { + *omit_value = true; + Step::FoundValue(Some(&node_data[value_range.clone()])) + } else { + Step::FoundValue(None) + } + } + NodePlan::Extension { partial: partial_plan, child: child_plan } => { + let partial = partial_plan.build(node_data); + if key.contains(&partial, prefix_len) { + assert_eq!(*child_index, 0); + let child_prefix_len = prefix_len + partial.len(); + let child = child_plan.build(&node_data); + Step::Descend { child_prefix_len, child } + } else { + Step::FoundValue(None) + } + } + NodePlan::Branch { value, children: child_handles } => + match_key_to_branch_node::( + node_data, + value, + &child_handles, + omit_value, + child_index, + children, + key, + prefix_len, + NibbleSlice::new(&[]), + )?, + NodePlan::NibbledBranch { partial: partial_plan, value, children: child_handles } => + match_key_to_branch_node::( + node_data, + value, + &child_handles, + omit_value, + child_index, + children, + key, + prefix_len, + partial_plan.build(node_data), + )?, + }) +} + +fn match_key_to_branch_node<'a, 'b, C: NodeCodec>( + node_data: &'a [u8], + value_range: &'b Option>, + child_handles: &'b [Option; NIBBLE_LENGTH], + omit_value: &mut bool, + child_index: &mut usize, + children: &mut [Option>], + key: &'b LeftNibbleSlice<'b>, + prefix_len: usize, + partial: NibbleSlice<'b>, +) -> TrieResult, C::HashOut, C::Error> +{ + if !key.contains(&partial, prefix_len) { + return Ok(Step::FoundValue(None)); + } + + if key.len() == prefix_len + partial.len() { + *omit_value = true; + let value = value_range.clone().map(|range| &node_data[range]); + return Ok(Step::FoundValue(value)); + } + + let new_index = key.at(prefix_len + partial.len()) + .expect( + "key contains partial key after entry key offset; \ + thus key len is greater than equal to entry key len plus partial key len; \ + also they are unequal due to else condition; + qed" + ) + as usize; + assert!(*child_index <= new_index); + while *child_index < new_index { + children[*child_index] = child_handles[*child_index] + .as_ref() + .map(|child_plan| + child_plan + .build(node_data) + .try_into() + .map_err(|hash| Box::new( + TrieError::InvalidHash(C::HashOut::default(), hash) + )) + ) + .transpose()?; + *child_index += 1; + } + if let Some(child_plan) = &child_handles[*child_index] { + Ok(Step::Descend { + child_prefix_len: prefix_len + partial.len() + 1, + child: child_plan.build(node_data), + }) + } else { + Ok(Step::FoundValue(None)) + } +} + +fn value_with_omission<'a>( + node_data: &'a [u8], + value_range: &Option>, + omit: bool +) -> Option<&'a [u8]> +{ + if omit { + None + } else { + value_range.clone().map(|range| &node_data[range]) + } +} + +/// Unwind the stack until the given key is prefixed by the entry at the top of the stack. If the +/// key is None, unwind the stack completely. As entries are popped from the stack, they are +/// encoded into proof nodes and added to the finalized proof. +fn unwind_stack( + stack: &mut Vec>, + proof_nodes: &mut Vec>, + maybe_key: Option<&LeftNibbleSlice>, +) -> TrieResult<(), C::HashOut, C::Error> +{ + while let Some(entry) = stack.pop() { + match maybe_key { + Some(key) if key.starts_with(&entry.prefix) => { + // Stop if the key lies below this entry in the trie. + stack.push(entry); + break; + } + _ => { + // Pop and finalize node from the stack. + let index = entry.output_index; + let encoded = entry.encode_node()?; + if let Some(parent_entry) = stack.last_mut() { + parent_entry.set_child(&encoded); + } + if let Some(index) = index { + proof_nodes[index] = encoded; + } + } + } + } + Ok(()) +} \ No newline at end of file diff --git a/trie-db/src/proof/mod.rs b/trie-db/src/proof/mod.rs new file mode 100644 index 00000000..30f5206d --- /dev/null +++ b/trie-db/src/proof/mod.rs @@ -0,0 +1,312 @@ +// Copyright 2019 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Generation and verification of compact proofs for Merkle-Patricia tries. +//! +//! Using this module, it is possible to generate a logarithmic-space proof of inclusion or +//! non-inclusion of certain key-value pairs in a trie with a known root. The proof contains +//! information so that the verifier can reconstruct the subset of nodes in the trie required to +//! lookup the keys. The trie nodes are not included in their entirety as data which the verifier +//! can compute for themself is omitted. In particular, the values of included keys and and hashes +//! of other trie nodes in the proof are omitted. +//! +//! The proof is a sequence of the subset of nodes in the trie traversed while performing lookups +//! on all keys. The trie nodes are listed in pre-order traversal order with some values and +//! internal hashes omitted. In particular, values on leaf nodes, child references on extension +//! nodes, values on branch nodes corresponding to a key in the statement, and child references on +//! branch nodes corresponding to another node in the proof are all omitted. The proof is verified +//! by iteratively reconstructing the trie nodes using the values proving as part of the statement +//! and the hashes of other reconstructed nodes. Since the nodes in the proof are arranged in +//! pre-order traversal order, the construction can be done efficiently using a stack. + +pub use self::generate::generate_proof; +pub use self::verify::{Error as VerifyError, verify_proof}; + +mod generate; +mod verify; + +#[cfg(test)] +mod tests { + use hash_db::Hasher; + use reference_trie::{ + ExtensionLayout, NoExtensionLayout, + proof::{generate_proof, verify_proof, VerifyError}, Trie, TrieDB, TrieDBMut, TrieLayout, + TrieMut, + }; + + use crate::DBValue; + + type MemoryDB = memory_db::MemoryDB, DBValue>; + + fn test_entries() -> Vec<(&'static [u8], &'static [u8])> { + vec![ + // "alfa" is at a hash-referenced leaf node. + (b"alfa", &[0; 32]), + // "bravo" is at an inline leaf node. + (b"bravo", b"bravo"), + // "do" is at a hash-referenced branch node. + (b"do", b"verb"), + // "dog" is at a hash-referenced branch node. + (b"dog", b"puppy"), + // "doge" is at a hash-referenced leaf node. + (b"doge", &[0; 32]), + // extension node "o" (plus nibble) to next branch. + (b"horse", b"stallion"), + (b"house", b"building"), + ] + } + + fn test_generate_proof( + entries: Vec<(&'static [u8], &'static [u8])>, + keys: Vec<&'static [u8]>, + ) -> (::Out, Vec>, Vec<(&'static [u8], Option)>) + { + // Populate DB with full trie from entries. + let (db, root) = { + let mut db = >::default(); + let mut root = Default::default(); + { + let mut trie = >::new(&mut db, &mut root); + for (key, value) in entries.iter() { + trie.insert(key, value).unwrap(); + } + } + (db, root) + }; + + // Generate proof for the given keys.. + let trie = >::new(&db, &root).unwrap(); + let proof = generate_proof::<_, L, _, _>(&trie, keys.iter()).unwrap(); + let items = keys.into_iter() + .map(|key| (key, trie.get(key).unwrap())) + .collect(); + + (root, proof, items) + } + + #[test] + fn trie_proof_works_with_ext() { + let (root, proof, items) = test_generate_proof::( + test_entries(), + vec![ + b"do", + b"dog", + b"doge", + b"bravo", + b"alfabet", // None, not found under leaf node + b"d", // None, witness is extension node with omitted child + b"do\x10", // None, empty branch child + b"halp", // None, witness is extension node with non-omitted child + ], + ); + + verify_proof::(&root, &proof, items.iter()).unwrap(); + } + + #[test] + fn trie_proof_works_without_ext() { + let (root, proof, items) = test_generate_proof::( + test_entries(), + vec![ + b"do", + b"dog", + b"doge", + b"bravo", + b"alfabet", // None, not found under leaf node + b"d", // None, witness is extension node with omitted child + b"do\x10", // None, empty branch child + b"halp", // None, witness is extension node with non-omitted child + ], + ); + + verify_proof::(&root, &proof, items.iter()).unwrap(); + } + + #[test] + fn trie_proof_works_for_empty_trie() { + let (root, proof, items) = test_generate_proof::( + vec![], + vec![ + b"alpha", + b"bravo", + b"\x42\x42", + ], + ); + + verify_proof::(&root, &proof, items.iter()).unwrap(); + } + + #[test] + fn test_verify_duplicate_keys() { + let (root, proof, _) = test_generate_proof::( + test_entries(), + vec![b"bravo"], + ); + + let items = vec![ + (b"bravo", Some(b"bravo")), + (b"bravo", Some(b"bravo")), + ]; + assert_eq!( + verify_proof::(&root, &proof, items.iter()), + Err(VerifyError::DuplicateKey(b"bravo".to_vec())) + ); + } + + #[test] + fn test_verify_extraneous_node() { + let (root, proof, _) = test_generate_proof::( + test_entries(), + vec![b"bravo", b"do"], + ); + + let items = vec![ + (b"bravo", Some(b"bravo")), + ]; + assert_eq!( + verify_proof::(&root, &proof, items.iter()), + Err(VerifyError::ExtraneousNode) + ); + } + + #[test] + fn test_verify_extraneous_value() { + let (root, proof, _) = test_generate_proof::( + test_entries(), + vec![b"doge"], + ); + + let items = vec![ + (&b"do"[..], Some(&b"verb"[..])), + (&b"doge"[..], Some(&[0; 32][..])), + ]; + assert_eq!( + verify_proof::(&root, &proof, items.iter()), + Err(VerifyError::ExtraneousValue(b"do".to_vec())) + ); + } + + #[test] + fn test_verify_extraneous_hash_reference() { + let (root, proof, _) = test_generate_proof::( + test_entries(), + vec![b"do"], + ); + + let items = vec![ + (&b"alfa"[..], Some(&[0; 32][..])), + (&b"do"[..], Some(&b"verb"[..])), + ]; + match verify_proof::(&root, &proof, items.iter()) { + Err(VerifyError::ExtraneousHashReference(_)) => {} + result => panic!("expected VerifyError::ExtraneousHashReference, got {:?}", result), + } + } + + #[test] + fn test_verify_invalid_child_reference() { + let (root, proof, _) = test_generate_proof::( + test_entries(), + vec![b"bravo"], + ); + + // InvalidChildReference because "bravo" is in an inline leaf node and a 32-byte value cannot + // fit in an inline leaf. + let items = vec![ + (b"bravo", Some([0; 32])), + ]; + match verify_proof::(&root, &proof, items.iter()) { + Err(VerifyError::InvalidChildReference(_)) => {} + result => panic!("expected VerifyError::InvalidChildReference, got {:?}", result), + } + } + + #[test] + fn test_verify_value_mismatch_some_to_none() { + let (root, proof, _) = test_generate_proof::( + test_entries(), + vec![b"horse"], + ); + + let items = vec![ + (&b"horse"[..], Some(&b"stallion"[..])), + (&b"halp"[..], Some(&b"plz"[..])), + ]; + assert_eq!( + verify_proof::(&root, &proof, items.iter()), + Err(VerifyError::ValueMismatch(b"halp".to_vec())) + ); + } + + #[test] + fn test_verify_value_mismatch_none_to_some() { + let (root, proof, _) = test_generate_proof::( + test_entries(), + vec![b"alfa", b"bravo"], + ); + + let items = vec![ + (&b"alfa"[..], Some(&[0; 32][..])), + (&b"bravo"[..], None), + ]; + assert_eq!( + verify_proof::(&root, &proof, items.iter()), + Err(VerifyError::ValueMismatch(b"bravo".to_vec())) + ); + } + + #[test] + fn test_verify_incomplete_proof() { + let (root, mut proof, items) = test_generate_proof::( + test_entries(), + vec![b"alfa"], + ); + + proof.pop(); + assert_eq!( + verify_proof::(&root, &proof, items.iter()), + Err(VerifyError::IncompleteProof) + ); + } + + #[test] + fn test_verify_root_mismatch() { + let (root, proof, _) = test_generate_proof::( + test_entries(), + vec![b"bravo"], + ); + + let items = vec![ + (b"bravo", Some("incorrect")), + ]; + match verify_proof::(&root, &proof, items.iter()) { + Err(VerifyError::RootMismatch(_)) => {} + result => panic!("expected VerifyError::RootMismatch, got {:?}", result), + } + } + + #[test] + fn test_verify_decode_error() { + let (root, mut proof, items) = test_generate_proof::( + test_entries(), + vec![b"bravo"], + ); + + proof.insert(0, b"this is not a trie node".to_vec()); + match verify_proof::(&root, &proof, items.iter()) { + Err(VerifyError::DecodeError(_)) => {} + result => panic!("expected VerifyError::DecodeError, got {:?}", result), + } + } +} diff --git a/trie-db/src/proof/verify.rs b/trie-db/src/proof/verify.rs new file mode 100644 index 00000000..4e1de38e --- /dev/null +++ b/trie-db/src/proof/verify.rs @@ -0,0 +1,489 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Verification of compact proofs for Merkle-Patricia tries. + +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; +use core_::convert::TryInto; +use core_::iter::Peekable; +use core_::marker::PhantomData; +use core_::result::Result; + +use hash_db::Hasher; + +use crate::{ + CError, ChildReference, nibble::LeftNibbleSlice, nibble_ops::NIBBLE_LENGTH, + node::{Node, NodeHandle}, NodeCodec, TrieHash, TrieLayout, +}; + +/// Errors that may occur during proof verification. Most of the errors types simply indicate that +/// the proof is invalid with respect to the statement being verified, and the exact error type can +/// be used for debugging. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub enum Error { + /// The statement being verified contains multiple key-value pairs with the same key. The + /// parameter is the duplicated key. + DuplicateKey(Vec), + /// The proof contains at least one extraneous node. + ExtraneousNode, + /// The proof contains at least one extraneous value which should have been omitted from the + /// proof. + ExtraneousValue(Vec), + /// The proof contains at least one extraneous hash reference the should have been omitted. + ExtraneousHashReference(HO), + /// The proof contains an invalid child reference that exceeds the hash length. + InvalidChildReference(Vec), + /// The proof indicates that an expected value was not found in the trie. + ValueMismatch(Vec), + /// The proof is missing trie nodes required to verify. + IncompleteProof, + /// The root hash computed from the proof is incorrect. + RootMismatch(HO), + /// One of the proof nodes could not be decoded. + DecodeError(CE), +} + +#[cfg(feature = "std")] +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Error::DuplicateKey(key) => + write!(f, "Duplicate key in input statement: key={:?}", key), + Error::ExtraneousNode => + write!(f, "Extraneous node found in proof"), + Error::ExtraneousValue(key) => + write!( + f, + "Extraneous value found in proof should have been omitted: key={:?}", + key + ), + Error::ExtraneousHashReference(hash) => + write!( + f, + "Extraneous hash reference found in proof should have been omitted: hash={:?}", + hash + ), + Error::InvalidChildReference(data) => + write!(f, "Invalid child reference exceeds hash length: {:?}", data), + Error::ValueMismatch(key) => + write!(f, "Expected value was not found in the trie: key={:?}", key), + Error::IncompleteProof => + write!(f, "Proof is incomplete -- expected more nodes"), + Error::RootMismatch(hash) => + write!(f, "Computed incorrect root {:?} from proof", hash), + Error::DecodeError(err) => + write!(f, "Unable to decode proof node: {}", err), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::DecodeError(err) => Some(err), + _ => None, + } + } +} + +struct StackEntry<'a, C: NodeCodec> { + /// The prefix is the nibble path to the node in the trie. + prefix: LeftNibbleSlice<'a>, + node: Node<'a>, + is_inline: bool, + /// The value associated with this trie node. + value: Option<&'a [u8]>, + /// The next entry in the stack is a child of the preceding entry at this index. For branch + /// nodes, the index is in [0, NIBBLE_LENGTH] and for extension nodes, the index is in [0, 1]. + child_index: usize, + /// The child references to use in reconstructing the trie nodes. + children: Vec>>, + _marker: PhantomData, +} + +impl<'a, C: NodeCodec> StackEntry<'a, C> { + fn new(node_data: &'a [u8], prefix: LeftNibbleSlice<'a>, is_inline: bool) + -> Result> + { + let node = C::decode(node_data) + .map_err(Error::DecodeError)?; + let children_len = match node { + Node::Empty | Node::Leaf(..) => 0, + Node::Extension(..) => 1, + Node::Branch(..) | Node::NibbledBranch(..) => NIBBLE_LENGTH, + }; + let value = match node { + Node::Empty | Node::Extension(_, _) => None, + Node::Leaf(_, value) => Some(value), + Node::Branch(_, value) | Node::NibbledBranch(_, _, value) => value, + }; + Ok(StackEntry { + node, + is_inline, + prefix, + value, + child_index: 0, + children: vec![None; children_len], + _marker: PhantomData::default(), + }) + } + + /// Encode this entry to an encoded trie node with data properly reconstructed. + fn encode_node(mut self) -> Result, Error> { + self.complete_children()?; + Ok(match self.node { + Node::Empty => + C::empty_node().to_vec(), + Node::Leaf(partial, _) => { + let value = self.value + .expect( + "value is assigned to Some in StackEntry::new; \ + value is only ever reassigned in the ValueMatch::MatchesLeaf match \ + clause, which assigns only to Some" + ); + C::leaf_node(partial.right(), value) + } + Node::Extension(partial, _) => { + let child = self.children[0] + .expect("the child must be completed since child_index is 1"); + C::extension_node( + partial.right_iter(), + partial.len(), + child + ) + } + Node::Branch(_, _) => + C::branch_node( + self.children.iter(), + self.value, + ), + Node::NibbledBranch(partial, _, _) => + C::branch_node_nibbled( + partial.right_iter(), + partial.len(), + self.children.iter(), + self.value, + ), + }) + } + + fn advance_child_index( + &mut self, + child_prefix: LeftNibbleSlice<'a>, + proof_iter: &mut I, + ) -> Result> + where + I: Iterator>, + { + match self.node { + Node::Extension(_, child) => { + // Guaranteed because of sorted keys order. + assert_eq!(self.child_index, 0); + Self::make_child_entry(proof_iter, child, child_prefix) + } + Node::Branch(children, _) | Node::NibbledBranch(_, children, _) => { + // because this is a branch + assert!(child_prefix.len() > 0); + let child_index = child_prefix.at(child_prefix.len() - 1) + .expect("it's less than prefix.len(); qed") + as usize; + while self.child_index < child_index { + if let Some(child) = children[self.child_index] { + let child_ref = child.try_into() + .map_err(Error::InvalidChildReference)?; + self.children[self.child_index] = Some(child_ref); + } + self.child_index += 1; + } + let child = children[self.child_index] + .expect("guaranteed by advance_item"); + Self::make_child_entry(proof_iter, child, child_prefix) + } + _ => panic!("cannot have children"), + } + } + + /// Populate the remaining references in `children` with references copied the node itself. + fn complete_children(&mut self) -> Result<(), Error> { + match self.node { + Node::Extension(_, child) if self.child_index == 0 => { + let child_ref = child.try_into() + .map_err(Error::InvalidChildReference)?; + self.children[self.child_index] = Some(child_ref); + self.child_index += 1; + } + Node::Branch(children, _) | Node::NibbledBranch(_, children, _) => { + while self.child_index < NIBBLE_LENGTH { + if let Some(child) = children[self.child_index] { + let child_ref = child.try_into() + .map_err(Error::InvalidChildReference)?; + self.children[self.child_index] = Some(child_ref); + } + self.child_index += 1; + } + } + _ => {} + } + Ok(()) + } + + fn make_child_entry( + proof_iter: &mut I, + child: NodeHandle<'a>, + prefix: LeftNibbleSlice<'a>, + ) -> Result> + where + I: Iterator>, + { + match child { + NodeHandle::Inline(data) => { + if data.is_empty() { + let node_data = proof_iter.next() + .ok_or(Error::IncompleteProof)?; + StackEntry::new(node_data, prefix, false) + } else { + StackEntry::new(data, prefix, true) + } + } + NodeHandle::Hash(data) => { + let mut hash = C::HashOut::default(); + if data.len() != hash.as_ref().len() { + return Err(Error::InvalidChildReference(data.to_vec())); + } + hash.as_mut().copy_from_slice(data); + Err(Error::ExtraneousHashReference(hash)) + } + } + } + + fn advance_item(&mut self, items_iter: &mut Peekable) + -> Result, Error> + where + I: Iterator)> + { + let step = loop { + if let Some((key_bytes, value)) = items_iter.peek().cloned() { + let key = LeftNibbleSlice::new(key_bytes); + if key.starts_with(&self.prefix) { + match match_key_to_node(&key, self.prefix.len(), &self.node) { + ValueMatch::MatchesLeaf => { + if value.is_none() { + return Err(Error::ValueMismatch(key_bytes.to_vec())); + } + self.value = value; + } + ValueMatch::MatchesBranch => + self.value = value, + ValueMatch::NotFound => + if value.is_some() { + return Err(Error::ValueMismatch(key_bytes.to_vec())); + }, + ValueMatch::NotOmitted => + return Err(Error::ExtraneousValue(key_bytes.to_vec())), + ValueMatch::IsChild(child_prefix) => + break Step::Descend(child_prefix), + } + + items_iter.next(); + continue; + } + } + break Step::UnwindStack; + }; + Ok(step) + } +} + +enum ValueMatch<'a> { + /// The key matches a leaf node, so the value at the key must be present. + MatchesLeaf, + /// The key matches a branch node, so the value at the key may or may not be present. + MatchesBranch, + /// The key was not found to correspond to value in the trie, so must not be present. + NotFound, + /// The key matches a location in trie, but the value was not omitted. + NotOmitted, + /// The key may match below a child of this node. Parameter is the prefix of the child node. + IsChild(LeftNibbleSlice<'a>), +} + +/// Determines whether a node on the stack carries a value at the given key or whether any nodes +/// in the subtrie do. The prefix of the node is given by the first `prefix_len` nibbles of `key`. +fn match_key_to_node<'a>(key: &LeftNibbleSlice<'a>, prefix_len: usize, node: &Node) + -> ValueMatch<'a> +{ + match node { + Node::Empty => ValueMatch::NotFound, + Node::Leaf(partial, value) => { + if key.contains(partial, prefix_len) && + key.len() == prefix_len + partial.len() { + if value.is_empty() { + ValueMatch::MatchesLeaf + } else { + ValueMatch::NotOmitted + } + } else { + ValueMatch::NotFound + } + } + Node::Extension(partial, _) => { + if key.contains(partial, prefix_len) { + ValueMatch::IsChild(key.truncate(prefix_len + partial.len())) + } else { + ValueMatch::NotFound + } + } + Node::Branch(children, value) => { + match_key_to_branch_node(key, prefix_len, children, value) + } + Node::NibbledBranch(partial, children, value) => { + if key.contains(partial, prefix_len) { + match_key_to_branch_node(key, prefix_len + partial.len(), children, value) + } else { + ValueMatch::NotFound + } + } + } +} + +/// Determines whether a branch node on the stack carries a value at the given key or whether any +/// nodes in the subtrie do. The key of the branch node value is given by the first +/// `prefix_plus_partial_len` nibbles of `key`. +fn match_key_to_branch_node<'a>( + key: &LeftNibbleSlice<'a>, + prefix_plus_partial_len: usize, + children: &[Option; NIBBLE_LENGTH], + value: &Option<&[u8]>, +) -> ValueMatch<'a> +{ + if key.len() == prefix_plus_partial_len { + if value.is_none() { + ValueMatch::MatchesBranch + } else { + ValueMatch::NotOmitted + } + } else { + let index = key.at(prefix_plus_partial_len) + .expect("it's less than prefix.len(); qed") + as usize; + if children[index].is_some() { + ValueMatch::IsChild(key.truncate(prefix_plus_partial_len + 1)) + } else { + ValueMatch::NotFound + } + } +} + +enum Step<'a> { + Descend(LeftNibbleSlice<'a>), + UnwindStack, +} + +/// Verify a compact proof for key-value pairs in a trie given a root hash. +pub fn verify_proof<'a, L, I, K, V>(root: &::Out, proof: &[Vec], items: I) + -> Result<(), Error, CError>> + where + L: TrieLayout, + I: IntoIterator)>, + K: 'a + AsRef<[u8]>, + V: 'a + AsRef<[u8]>, +{ + // Sort items. + let mut items = items.into_iter() + .map(|(k, v)| (k.as_ref(), v.as_ref().map(|v| v.as_ref()))) + .collect::>(); + items.sort(); + + if items.is_empty() { + return if proof.is_empty() { + Ok(()) + } else { + Err(Error::ExtraneousNode) + }; + } + + // Check for duplicates. + for i in 1..items.len() { + if items[i].0 == items[i - 1].0 { + return Err(Error::DuplicateKey(items[i].0.to_vec())); + } + } + + // Iterate simultaneously in order through proof nodes and key-value pairs to verify. + let mut proof_iter = proof.iter(); + let mut items_iter = items.into_iter().peekable(); + + // A stack of child references to fill in omitted branch children for later trie nodes in the + // proof. + let mut stack: Vec> = Vec::new(); + + let root_node = match proof_iter.next() { + Some(node) => node, + None => return Err(Error::IncompleteProof), + }; + let mut last_entry = StackEntry::new( + root_node, + LeftNibbleSlice::new(&[]), + false + )?; + loop { + // Insert omitted value. + match last_entry.advance_item(&mut items_iter)? { + Step::Descend(child_prefix) => { + let next_entry = last_entry.advance_child_index(child_prefix, &mut proof_iter)?; + stack.push(last_entry); + last_entry = next_entry; + } + Step::UnwindStack => { + let is_inline = last_entry.is_inline; + let node_data = last_entry.encode_node()?; + + let child_ref = if is_inline { + if node_data.len() > L::Hash::LENGTH { + return Err(Error::InvalidChildReference(node_data)); + } + let mut hash = >::default(); + &mut hash.as_mut()[..node_data.len()].copy_from_slice(node_data.as_ref()); + ChildReference::Inline(hash, node_data.len()) + } else { + let hash = L::Hash::hash(&node_data); + ChildReference::Hash(hash) + }; + + if let Some(entry) = stack.pop() { + last_entry = entry; + last_entry.children[last_entry.child_index] = Some(child_ref); + last_entry.child_index += 1; + } else { + if proof_iter.next().is_some() { + return Err(Error::ExtraneousNode); + } + let computed_root = match child_ref { + ChildReference::Hash(hash) => hash, + ChildReference::Inline(_, _) => panic!( + "the bottom item on the stack has is_inline = false; qed" + ), + }; + if computed_root != *root { + return Err(Error::RootMismatch(computed_root)); + } + break; + } + } + } + } + + Ok(()) +} \ No newline at end of file diff --git a/trie-db/src/triedbmut.rs b/trie-db/src/triedbmut.rs index 3dc526c9..95c0578a 100644 --- a/trie-db/src/triedbmut.rs +++ b/trie-db/src/triedbmut.rs @@ -284,6 +284,7 @@ enum Stored { /// Used to build a collection of child nodes from a collection of `NodeHandle`s #[derive(Clone, Copy)] +#[cfg_attr(feature = "std", derive(Debug))] pub enum ChildReference { // `HO` is e.g. `H256`, i.e. the output of a `Hasher` Hash(HO), Inline(HO, usize), // usize is the length of the node data we store in the `H::Out`