Skip to content

Commit e929d43

Browse files
authored
imp: removed alloy-trie and refactored verify_membership (#391)
1 parent 591e0a6 commit e929d43

File tree

14 files changed

+160
-152
lines changed

14 files changed

+160
-152
lines changed

Cargo.lock

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ alloy = { version = "0.11", default-features = false }
8989
alloy-contract = { version = "0.11", default-features = false }
9090
alloy-sol-types = { version = "0.8", default-features = false }
9191
alloy-primitives = { version = "0.8", default-features = false }
92-
alloy-trie = { version = "0.7", default-features = false }
9392
alloy-serde = { version = "0.11", default-features = false }
9493
alloy-network = { version = "0.11", default-features = false }
9594
alloy-signer-local = { version = "0.11", default-features = false }
Binary file not shown.

justfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ test-benchmark testname=".\\*":
4747
forge test -vvv --show-progress --gas-report --match-path test/solidity-ibc/BenchmarkTest.t.sol --match-test {{testname}}
4848

4949
# Run the cargo tests
50-
test-cargo:
51-
cargo test --all --locked
50+
test-cargo testname="--all":
51+
cargo test {{testname}} --locked --no-fail-fast -- --nocapture
5252

5353
# Run the tests in abigen
5454
test-abigen:

packages/ethereum/ethereum-light-client/Cargo.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ path = "src/bin/generate_json_schema.rs"
1515
ethereum-trie-db = { workspace = true }
1616
ethereum-types = { workspace = true }
1717

18-
alloy-primitives = { workspace = true, features = ["serde", "hex-compat"] }
19-
alloy-trie = { workspace = true }
18+
alloy-primitives = { workspace = true, features = ["serde", "hex-compat", "rlp"] }
2019
alloy-rlp = { workspace = true, features = ["arrayvec"] }
2120
tree_hash = { workspace = true }
2221
serde = { workspace = true, features = ["derive"] }

packages/ethereum/ethereum-light-client/src/error.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use ethereum_types::consensus::bls::BlsPublicKey;
66
#[derive(thiserror::Error, Debug, Clone, PartialEq)]
77
#[allow(missing_docs, clippy::module_name_repetitions)]
88
pub enum EthereumIBCError {
9-
#[error("IBC path is empty")]
10-
EmptyPath,
9+
#[error("invalid path length, expected {expected} but found {found}")]
10+
InvalidPathLength { expected: usize, found: usize },
1111

1212
#[error("unable to decode storage proof")]
1313
StorageProofDecode,

packages/ethereum/ethereum-light-client/src/membership.rs

+79-48
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
//! This module provides [`verify_membership`] function to verify the membership of a key in the
22
//! storage trie.
33
4-
use alloy_primitives::{keccak256, Bytes, Keccak256, U256};
5-
use alloy_rlp::encode_fixed_size;
6-
use alloy_trie::{proof::verify_proof, Nibbles};
4+
use alloy_primitives::{keccak256, Keccak256, U256};
5+
use ethereum_trie_db::trie_db::{verify_storage_exclusion_proof, verify_storage_inclusion_proof};
76
use ethereum_types::execution::storage_proof::StorageProof;
87

98
use crate::{client_state::ClientState, consensus_state::ConsensusState, error::EthereumIBCError};
@@ -17,67 +16,100 @@ pub fn verify_membership(
1716
client_state: ClientState,
1817
proof: Vec<u8>,
1918
path: Vec<Vec<u8>>,
20-
raw_value: Option<Vec<u8>>,
19+
raw_value: Vec<u8>,
2120
) -> Result<(), EthereumIBCError> {
22-
let path = path.first().ok_or(EthereumIBCError::EmptyPath)?;
23-
2421
let storage_proof: StorageProof = serde_json::from_slice(proof.as_slice())
2522
.map_err(|_| EthereumIBCError::StorageProofDecode)?;
2623

27-
check_commitment_key(
28-
path.clone(),
24+
check_commitment_path(
25+
&path,
2926
client_state.ibc_commitment_slot,
3027
storage_proof.key.into(),
3128
)?;
3229

33-
let value = match raw_value {
34-
Some(unwrapped_raw_value) => {
35-
let proof_value = storage_proof.value.to_be_bytes_vec();
36-
if proof_value != unwrapped_raw_value {
37-
return Err(EthereumIBCError::StoredValueMistmatch {
38-
expected: unwrapped_raw_value,
39-
actual: proof_value,
40-
});
41-
}
42-
Some(encode_fixed_size(&storage_proof.value).to_vec())
30+
ensure!(
31+
storage_proof.value.to_be_bytes_vec() == raw_value,
32+
EthereumIBCError::StoredValueMistmatch {
33+
expected: raw_value,
34+
actual: storage_proof.value.to_be_bytes_vec(),
4335
}
44-
None => None,
45-
};
36+
);
37+
38+
let rlp_value = alloy_rlp::encode_fixed_size(&storage_proof.value);
39+
verify_storage_inclusion_proof(
40+
&trusted_consensus_state.storage_root,
41+
&storage_proof.key,
42+
&rlp_value,
43+
storage_proof.proof.iter(),
44+
)
45+
.map_err(|err| EthereumIBCError::VerifyStorageProof(err.to_string()))
46+
}
47+
48+
/// Verifies the non-membership of a key in the storage trie.
49+
/// # Errors
50+
/// Returns an error if the proof cannot be verified.
51+
#[allow(clippy::module_name_repetitions, clippy::needless_pass_by_value)]
52+
pub fn verify_non_membership(
53+
trusted_consensus_state: ConsensusState,
54+
client_state: ClientState,
55+
proof: Vec<u8>,
56+
path: Vec<Vec<u8>>,
57+
) -> Result<(), EthereumIBCError> {
58+
let storage_proof: StorageProof = serde_json::from_slice(proof.as_slice())
59+
.map_err(|_| EthereumIBCError::StorageProofDecode)?;
4660

47-
let proof: Vec<&Bytes> = storage_proof.proof.iter().collect();
61+
check_commitment_path(
62+
&path,
63+
client_state.ibc_commitment_slot,
64+
storage_proof.key.into(),
65+
)?;
66+
67+
ensure!(
68+
storage_proof.value.is_zero(),
69+
EthereumIBCError::StoredValueMistmatch {
70+
expected: vec![0],
71+
actual: storage_proof.value.to_be_bytes_vec(),
72+
}
73+
);
4874

49-
verify_proof::<Vec<&Bytes>>(
50-
trusted_consensus_state.storage_root,
51-
Nibbles::unpack(keccak256(storage_proof.key)),
52-
value,
53-
proof,
75+
verify_storage_exclusion_proof(
76+
&trusted_consensus_state.storage_root,
77+
&storage_proof.key,
78+
storage_proof.proof.iter(),
5479
)
5580
.map_err(|err| EthereumIBCError::VerifyStorageProof(err.to_string()))
5681
}
5782

58-
fn check_commitment_key(
59-
path: Vec<u8>,
83+
fn check_commitment_path(
84+
path: &[Vec<u8>],
6085
ibc_commitment_slot: U256,
6186
key: U256,
6287
) -> Result<(), EthereumIBCError> {
63-
let expected_commitment_key = ibc_commitment_key_v2(path, ibc_commitment_slot);
64-
65-
// Data MUST be stored to the commitment path that is defined in ICS23.
66-
if expected_commitment_key == key {
67-
Ok(())
68-
} else {
69-
Err(EthereumIBCError::InvalidCommitmentKey(
70-
format!("0x{expected_commitment_key:x}"),
88+
ensure!(
89+
path.len() == 1,
90+
EthereumIBCError::InvalidPathLength {
91+
expected: 1,
92+
found: path.len()
93+
}
94+
);
95+
96+
let expected_commitment_path = evm_ics26_commitment_path(&path[0], ibc_commitment_slot);
97+
ensure!(
98+
expected_commitment_path == key,
99+
EthereumIBCError::InvalidCommitmentKey(
100+
format!("0x{expected_commitment_path:x}"),
71101
format!("0x{key:x}"),
72-
))
73-
}
102+
)
103+
);
104+
105+
Ok(())
74106
}
75107

76108
// TODO: Unit test
77109
/// Computes the commitment key for a given path and slot.
78-
#[must_use = "calculating the commitment key has no effect"]
79-
pub fn ibc_commitment_key_v2(path: Vec<u8>, slot: U256) -> U256 {
80-
let path_hash = keccak256(path);
110+
#[must_use = "calculating the commitment path has no effect"]
111+
pub fn evm_ics26_commitment_path(ibc_path: &[u8], slot: U256) -> U256 {
112+
let path_hash = keccak256(ibc_path);
81113

82114
let mut hasher = Keccak256::new();
83115
hasher.update(path_hash);
@@ -105,7 +137,7 @@ mod test {
105137

106138
use prost::Message;
107139

108-
use super::verify_membership;
140+
use super::{verify_membership, verify_non_membership};
109141

110142
#[test]
111143
fn test_with_fixture() {
@@ -148,7 +180,7 @@ mod test {
148180
client_state,
149181
storage_proof,
150182
vec![path],
151-
Some(value),
183+
value,
152184
)
153185
.unwrap();
154186
}
@@ -199,7 +231,7 @@ mod test {
199231
client_state.clone(),
200232
storage_proof_bz,
201233
path.clone(),
202-
Some(value.to_be_bytes_vec()),
234+
value.to_be_bytes_vec(),
203235
)
204236
.unwrap();
205237

@@ -208,7 +240,7 @@ mod test {
208240
let storage_proof = StorageProof { key, value, proof };
209241
let storage_proof_bz = serde_json::to_vec(&storage_proof).unwrap();
210242

211-
verify_membership(consensus_state, client_state, storage_proof_bz, path, None).unwrap_err();
243+
verify_non_membership(consensus_state, client_state, storage_proof_bz, path).unwrap_err();
212244
}
213245

214246
#[test]
@@ -246,12 +278,11 @@ mod test {
246278
let proof = StorageProof { key, value, proof };
247279
let proof_bz = serde_json::to_vec(&proof).unwrap();
248280

249-
verify_membership(
281+
verify_non_membership(
250282
consensus_state.clone(),
251283
client_state.clone(),
252284
proof_bz.clone(),
253285
path.clone(),
254-
None,
255286
)
256287
.unwrap();
257288

@@ -261,7 +292,7 @@ mod test {
261292
client_state,
262293
proof_bz,
263294
path,
264-
Some(value.to_be_bytes_vec()),
295+
value.to_be_bytes_vec(),
265296
)
266297
.unwrap_err();
267298
}

packages/ethereum/ethereum-light-client/src/test_utils/fixtures.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,8 @@ impl RelayerMessages {
9797
/// A tuple with the commitment path and value
9898
#[must_use]
9999
pub fn get_packet_proof(packet: Packet) -> (Vec<u8>, Vec<u8>) {
100-
let mut path = Vec::new();
101-
path.extend_from_slice(packet.source_client.as_bytes());
102-
path.push(1_u8);
103-
path.extend_from_slice(&packet.sequence.to_be_bytes());
104-
105100
let ics26_packet: IICS26RouterMsgs::Packet = packet.into();
106-
let value = ics26_packet.commit_packet();
107-
108-
(path, value)
101+
(ics26_packet.commitment_path(), ics26_packet.commitment())
109102
}
110103

111104
impl StepsFixture {

packages/ethereum/ethereum-trie-db/src/error.rs

+3
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ pub enum TrieDBError {
1818

1919
#[error("proof is invalid due to missing value: {v}", v = hex::encode(value))]
2020
ValueMissing { value: Vec<u8> },
21+
22+
#[error("proof is invalid due to unexpected value: {v}", v = hex::encode(value))]
23+
ValueShouldBeMissing { value: Vec<u8> },
2124
}

packages/ethereum/ethereum-trie-db/src/trie_db.rs

+52-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,56 @@ pub struct Account {
2525
pub code_hash: H256,
2626
}
2727

28+
/// Verifies against `root`, if the `expected_value` is stored at `key` by using `proof`.
29+
///
30+
/// * `root`: Storage root of a contract.
31+
/// * `key`: Padded slot number that the `expected_value` should be stored at.
32+
/// * `expected_value`: Expected stored value.
33+
/// * `proof`: Proof that is generated to prove the storage.
34+
///
35+
/// NOTE: You must not trust the `root` unless you verified it by calling [`verify_account_storage_root`].
36+
///
37+
/// # Errors
38+
/// Returns an error if the verification fails.
39+
pub fn verify_storage_inclusion_proof(
40+
root: &[u8; 32],
41+
key: &[u8; 32],
42+
expected_value: &[u8],
43+
proof: impl IntoIterator<Item = impl AsRef<[u8]>>,
44+
) -> Result<(), TrieDBError> {
45+
match get_node(H256(*root), key, proof)? {
46+
Some(value) if value == expected_value => Ok(()),
47+
Some(value) => Err(TrieDBError::ValueMismatch {
48+
expected: expected_value.into(),
49+
actual: value,
50+
})?,
51+
None => Err(TrieDBError::ValueMissing {
52+
value: expected_value.into(),
53+
})?,
54+
}
55+
}
56+
57+
/// Verifies against `root`, that no value is stored at `key` by using `proof`.
58+
///
59+
/// * `root`: Storage root of a contract.
60+
/// * `key`: Padded slot number that the `expected_value` should be stored at.
61+
/// * `proof`: Proof that is generated to prove the storage.
62+
///
63+
/// NOTE: You must not trust the `root` unless you verified it by calling [`verify_account_storage_root`].
64+
///
65+
/// # Errors
66+
/// Returns an error if the verification fails.
67+
pub fn verify_storage_exclusion_proof(
68+
root: &[u8; 32],
69+
key: &[u8; 32],
70+
proof: impl IntoIterator<Item = impl AsRef<[u8]>>,
71+
) -> Result<(), TrieDBError> {
72+
match get_node(H256(*root), key, proof)? {
73+
Some(value) => Err(TrieDBError::ValueShouldBeMissing { value })?,
74+
None => Ok(()),
75+
}
76+
}
77+
2878
/// Verifies if the `storage_root` of a contract can be verified against the state `root`.
2979
///
3080
/// * `root`: Light client update's (attested/finalized) execution block's state root.
@@ -44,7 +94,7 @@ pub fn verify_account_storage_root(
4494
let storage_root: H256 = H256(storage_root.into());
4595
let address: H160 = H160(address.into());
4696

47-
match get_node(root, address.as_ref(), proof)? {
97+
match get_node(H256(root.into()), address.as_ref(), proof)? {
4898
Some(account) => {
4999
let account =
50100
rlp::decode::<Account>(account.as_ref()).map_err(TrieDBError::RlpDecode)?;
@@ -63,7 +113,7 @@ pub fn verify_account_storage_root(
63113
}
64114

65115
fn get_node(
66-
root: B256,
116+
root: H256,
67117
key: impl AsRef<[u8]>,
68118
proof: impl IntoIterator<Item = impl AsRef<[u8]>>,
69119
) -> Result<Option<Vec<u8>>, TrieDBError> {
@@ -72,10 +122,7 @@ fn get_node(
72122
db.insert(hash_db::EMPTY_PREFIX, n.as_ref());
73123
});
74124

75-
let root: H256 = H256(root.into());
76-
77125
let trie = TrieDBBuilder::<EthLayout>::new(&db, &root).build();
78-
79126
trie.get(&keccak_256(key.as_ref()))
80127
.map_err(|e| TrieDBError::GetTrieNodeFailed(e.to_string()))
81128
}

0 commit comments

Comments
 (0)