-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding support for eip-1186 proofs #146
Merged
Merged
Changes from 5 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
d92084a
Adding support for eip-1186 proofs
ae90c59
first round of PR review
9a02df9
rustfmt on eip1186.rs files
7156093
second round of PR review
4cd6076
cargo fmt - 2
caeac17
CHANGELOG update
bf5712a
moving eip1186 to new crate: trie-eip1186
071118b
cargo fmt
4797aab
Update trie-db/CHANGELOG.md
lightyear15 44a3c5e
Update trie-eip1186/src/lib.rs
lightyear15 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,306 @@ | ||
use crate::{ | ||
nibble::NibbleSlice, | ||
node::{decode_hash, Node, NodeHandle, Value}, | ||
recorder::Recorder, | ||
rstd::{result::Result, vec::Vec}, | ||
CError, NodeCodec, Result as TrieResult, Trie, TrieHash, TrieLayout, | ||
}; | ||
use hash_db::Hasher; | ||
|
||
/// Generate an eip-1186 compatible proof for key-value pairs in a trie given a key. | ||
pub fn generate_proof<T, L>( | ||
trie: &T, | ||
key: &[u8], | ||
) -> TrieResult<(Vec<Vec<u8>>, Option<Vec<u8>>), TrieHash<L>, CError<L>> | ||
where | ||
T: Trie<L>, | ||
L: TrieLayout, | ||
{ | ||
let mut recorder = Recorder::new(); | ||
let item = trie.get_with(key, &mut recorder)?; | ||
let proof: Vec<Vec<u8>> = recorder.drain().into_iter().map(|r| r.data).collect(); | ||
Ok((proof, item)) | ||
} | ||
|
||
/// 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 VerifyError<'a, HO, CE> { | ||
/// The proof does not contain any value for the given key | ||
/// the error carries the nibbles left after traversing the trie | ||
NonExistingValue(NibbleSlice<'a>), | ||
/// The proof contains a value for the given key | ||
/// while we were expecting to find a non-existence proof | ||
ExistingValue(Vec<u8>), | ||
/// The proof indicates that the trie contains a different value. | ||
/// the error carries the value contained in the trie | ||
ValueMismatch(Vec<u8>), | ||
/// The proof is missing trie nodes required to verify. | ||
IncompleteProof, | ||
/// The node hash computed from the proof is not matching. | ||
HashMismatch(HO), | ||
/// One of the proof nodes could not be decoded. | ||
DecodeError(CE), | ||
/// Error in converting a plain hash into a HO | ||
HashDecodeError(&'a [u8]), | ||
} | ||
|
||
#[cfg(feature = "std")] | ||
impl<'a, HO: std::fmt::Debug, CE: std::error::Error> std::fmt::Display for VerifyError<'a, HO, CE> { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { | ||
match self { | ||
VerifyError::NonExistingValue(key) => { | ||
write!(f, "Key does not exist in trie: reaming key={:?}", key) | ||
}, | ||
VerifyError::ExistingValue(value) => { | ||
write!(f, "trie contains a value for given key value={:?}", value) | ||
}, | ||
VerifyError::ValueMismatch(key) => { | ||
write!(f, "Expected value was not found in the trie: key={:?}", key) | ||
}, | ||
VerifyError::IncompleteProof => write!(f, "Proof is incomplete -- expected more nodes"), | ||
VerifyError::HashMismatch(hash) => write!(f, "hash mismatch found: hash={:?}", hash), | ||
VerifyError::DecodeError(err) => write!(f, "Unable to decode proof node: {}", err), | ||
VerifyError::HashDecodeError(plain_hash) => { | ||
write!(f, "Unable to decode hash value plain_hash: {:?}", plain_hash) | ||
}, | ||
} | ||
} | ||
} | ||
|
||
#[cfg(feature = "std")] | ||
impl<'a, HO: std::fmt::Debug, CE: std::error::Error + 'static> std::error::Error | ||
for VerifyError<'a, HO, CE> | ||
{ | ||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { | ||
match self { | ||
VerifyError::DecodeError(err) => Some(err), | ||
_ => None, | ||
} | ||
} | ||
} | ||
|
||
/// Verify a compact proof for key-value pairs in a trie given a root hash. | ||
pub fn verify_proof<'a, L>( | ||
root: &<L::Hash as Hasher>::Out, | ||
proof: &'a [Vec<u8>], | ||
raw_key: &'a [u8], | ||
expected_value: Option<&[u8]>, | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
if proof.is_empty() { | ||
return Err(VerifyError::IncompleteProof) | ||
} | ||
let key = NibbleSlice::new(raw_key); | ||
process_node::<L>(Some(root), &proof[0], key, expected_value, &proof[1..]) | ||
} | ||
|
||
fn process_node<'a, L>( | ||
expected_node_hash: Option<&<L::Hash as Hasher>::Out>, | ||
encoded_node: &'a [u8], | ||
key: NibbleSlice<'a>, | ||
expected_value: Option<&[u8]>, | ||
proof: &'a [Vec<u8>], | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
if let Some(value) = expected_value { | ||
if encoded_node == value { | ||
return Ok(()) | ||
} | ||
} | ||
if let Some(expected) = expected_node_hash { | ||
let calculated_node_hash = <L::Hash as Hasher>::hash(encoded_node); | ||
if calculated_node_hash != *expected { | ||
return Err(VerifyError::HashMismatch(calculated_node_hash)) | ||
} | ||
} | ||
let node = <L::Codec as NodeCodec>::decode(encoded_node).map_err(VerifyError::DecodeError)?; | ||
match node { | ||
Node::Empty => process_empty::<L>(key, expected_value, proof), | ||
Node::Leaf(nib, data) => process_leaf::<L>(nib, data, key, expected_value, proof), | ||
Node::Extension(nib, handle) => | ||
process_extension::<L>(&nib, handle, key, expected_value, proof), | ||
Node::Branch(children, maybe_data) => | ||
process_branch::<L>(children, maybe_data, key, expected_value, proof), | ||
Node::NibbledBranch(nib, children, maybe_data) => | ||
process_nibbledbranch::<L>(nib, children, maybe_data, key, expected_value, proof), | ||
} | ||
} | ||
|
||
fn process_empty<'a, L>( | ||
key: NibbleSlice<'a>, | ||
expected_value: Option<&[u8]>, | ||
_: &[Vec<u8>], | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
if expected_value.is_none() { | ||
Ok(()) | ||
} else { | ||
Err(VerifyError::NonExistingValue(key)) | ||
} | ||
} | ||
|
||
fn process_leaf<'a, L>( | ||
nib: NibbleSlice, | ||
data: Value<'a>, | ||
key: NibbleSlice<'a>, | ||
expected_value: Option<&[u8]>, | ||
proof: &'a [Vec<u8>], | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
if key != nib && expected_value.is_none() { | ||
return Ok(()) | ||
} else if key != nib { | ||
return Err(VerifyError::NonExistingValue(key)) | ||
} | ||
match_value::<L>(Some(data), key, expected_value, proof) | ||
} | ||
fn process_extension<'a, L>( | ||
nib: &NibbleSlice, | ||
handle: NodeHandle<'a>, | ||
mut key: NibbleSlice<'a>, | ||
expected_value: Option<&[u8]>, | ||
proof: &'a [Vec<u8>], | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
if !key.starts_with(nib) && expected_value.is_none() { | ||
return Ok(()) | ||
} else if !key.starts_with(nib) { | ||
return Err(VerifyError::NonExistingValue(key)) | ||
} | ||
key.advance(nib.len()); | ||
|
||
match handle { | ||
NodeHandle::Inline(encoded_node) => | ||
process_node::<L>(None, encoded_node, key, expected_value, proof), | ||
NodeHandle::Hash(plain_hash) => { | ||
let new_root = decode_hash::<L::Hash>(plain_hash) | ||
.ok_or(VerifyError::HashDecodeError(plain_hash))?; | ||
process_node::<L>(Some(&new_root), &proof[0], key, expected_value, &proof[1..]) | ||
}, | ||
} | ||
} | ||
|
||
fn process_nibbledbranch<'a, L>( | ||
nib: NibbleSlice, | ||
children: [Option<NodeHandle<'a>>; 16], | ||
maybe_data: Option<Value<'a>>, | ||
mut key: NibbleSlice<'a>, | ||
expected_value: Option<&[u8]>, | ||
proof: &'a [Vec<u8>], | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
if !key.starts_with(&nib) && expected_value.is_none() { | ||
return Ok(()) | ||
} else if !key.starts_with(&nib) && expected_value.is_some() { | ||
return Err(VerifyError::NonExistingValue(key)) | ||
} | ||
key.advance(nib.len()); | ||
|
||
if key.is_empty() { | ||
match_value::<L>(maybe_data, key, expected_value, proof) | ||
} else { | ||
match_children::<L>(children, key, expected_value, proof) | ||
} | ||
} | ||
|
||
fn process_branch<'a, L>( | ||
children: [Option<NodeHandle<'a>>; 16], | ||
maybe_data: Option<Value<'a>>, | ||
key: NibbleSlice<'a>, | ||
expected_value: Option<&[u8]>, | ||
proof: &'a [Vec<u8>], | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
if key.is_empty() { | ||
match_value::<L>(maybe_data, key, expected_value, proof) | ||
} else { | ||
match_children::<L>(children, key, expected_value, proof) | ||
} | ||
} | ||
fn match_children<'a, L>( | ||
children: [Option<NodeHandle<'a>>; 16], | ||
mut key: NibbleSlice<'a>, | ||
expected_value: Option<&[u8]>, | ||
proof: &'a [Vec<u8>], | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
match children.get(key.at(0) as usize) { | ||
Some(Some(NodeHandle::Hash(hash))) => | ||
if proof.is_empty() { | ||
Err(VerifyError::IncompleteProof) | ||
} else { | ||
key.advance(1); | ||
let new_root = | ||
decode_hash::<L::Hash>(hash).ok_or(VerifyError::HashDecodeError(hash))?; | ||
process_node::<L>(Some(&new_root), &proof[0], key, expected_value, &proof[1..]) | ||
}, | ||
Some(Some(NodeHandle::Inline(encoded_node))) => { | ||
key.advance(1); | ||
process_node::<L>(None, encoded_node, key, expected_value, proof) | ||
}, | ||
Some(None) => | ||
if expected_value.is_none() { | ||
Ok(()) | ||
} else { | ||
Err(VerifyError::NonExistingValue(key)) | ||
}, | ||
None => panic!("key index is out of range in children array"), | ||
} | ||
} | ||
|
||
fn match_value<'a, L>( | ||
maybe_data: Option<Value<'a>>, | ||
key: NibbleSlice<'a>, | ||
expected_value: Option<&[u8]>, | ||
proof: &'a [Vec<u8>], | ||
) -> Result<(), VerifyError<'a, TrieHash<L>, CError<L>>> | ||
where | ||
L: TrieLayout, | ||
{ | ||
match (maybe_data, proof.first(), expected_value) { | ||
(None, _, None) => Ok(()), | ||
(None, _, Some(_)) => Err(VerifyError::NonExistingValue(key)), | ||
(Some(Value::Inline(inline_data)), _, Some(value)) => | ||
if inline_data == value { | ||
Ok(()) | ||
} else { | ||
Err(VerifyError::ValueMismatch(inline_data.to_vec())) | ||
}, | ||
(Some(Value::Inline(inline_data)), _, None) => | ||
Err(VerifyError::ExistingValue(inline_data.to_vec())), | ||
(Some(Value::Node(plain_hash, _)), Some(next_proof_item), Some(value)) => { | ||
let value_hash = L::Hash::hash(value); | ||
let node_hash = decode_hash::<L::Hash>(plain_hash) | ||
.ok_or(VerifyError::HashDecodeError(plain_hash))?; | ||
if node_hash != value_hash { | ||
Err(VerifyError::HashMismatch(node_hash)) | ||
} else if next_proof_item != value { | ||
Err(VerifyError::ValueMismatch(next_proof_item.to_vec())) | ||
} else { | ||
Ok(()) | ||
} | ||
}, | ||
(Some(Value::Node(_, _)), None, _) => Err(VerifyError::IncompleteProof), | ||
(Some(Value::Node(_, _)), Some(proof_item), None) => | ||
Err(VerifyError::ExistingValue(proof_item.to_vec())), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,5 +35,6 @@ pub use self::{ | |
verify::{verify_proof, Error as VerifyError}, | ||
}; | ||
|
||
pub mod eip1186; | ||
mod generate; | ||
mod verify; |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice addition to eip1196 (could also just return an error since eth do not have this kind of nodes).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this comment referring line 287 or 286? I think case 287 is the one actually used, your comment probably refers to line 286, isn't it?
If we leave the case as it is, even if eth does not use such node type, is it going to cause any issue in proof verification?
Can we leave it as it is? and make this code even more general? (not strictly linked to ethereum, but also, maybe, embracing other ethereum-like networks)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry just refering to handling the
Value::Node
case.Value::Node
is never use with ethereum trie layout, values are always inlined in either a branch or a leaf (also there is no NibbledBranch with ethereum codec).But the code you write looks good to me (adding the value node as a following node is what I did for the other proofs), so I'd rather keep it (it allows running the tests on all layout which looks nice).