Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/proof/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ pub enum ProofVerificationError {
},
/// Encountered unexpected empty root node.
UnexpectedEmptyRoot,
/// Proof contains trailing nodes after the expected end.
TrailingProofNodes,
/// Error during RLP decoding of trie node.
Rlp(alloy_rlp::Error),
}
Expand Down Expand Up @@ -61,6 +63,9 @@ impl fmt::Display for ProofVerificationError {
Self::UnexpectedEmptyRoot => {
write!(f, "unexpected empty root node")
}
Self::TrailingProofNodes => {
write!(f, "proof contains trailing nodes after the expected end")
}
Self::Rlp(error) => fmt::Display::fmt(error, f),
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/proof/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ where

// If the proof is empty or contains only an empty node, the expected value must be None.
if proof.peek().is_none_or(|node| node.as_ref() == [EMPTY_STRING_CODE]) {
// Consume the first element (if any), then ensure no trailing nodes remain.
proof.next();
if proof.next().is_some() {
return Err(ProofVerificationError::TrailingProofNodes);
}
return if root == EMPTY_ROOT_HASH {
if expected_value.is_none() {
Ok(())
Expand Down Expand Up @@ -812,6 +817,19 @@ mod tests {
);
}

#[test]
fn empty_proof_with_trailing_nodes_is_rejected() {
// Demonstrates the bug: a proof like [EMPTY, junk...] is accepted as a valid
// exclusion proof when root == EMPTY_ROOT_HASH and expected_value == None.
// The trailing junk bytes should cause verification to fail.
let key = Nibbles::unpack(B256::repeat_byte(42));
let proof_with_junk = vec![Bytes::from([EMPTY_STRING_CODE]), Bytes::from(vec![0xDE, 0xAD])];
let result = verify_proof(EMPTY_ROOT_HASH, key, None, false, proof_with_junk.iter());
// After the fix, this should be Err (trailing proof nodes).
// Before the fix, this incorrectly returns Ok(()).
assert!(result.is_err(), "proof with trailing nodes after empty node should be rejected");
}

#[test]
#[cfg(feature = "arbitrary")]
#[cfg_attr(miri, ignore = "no proptest")]
Expand Down
Loading