Skip to content

Commit ed22930

Browse files
authored
fix: audit fixes (#59)
* chore: update secp256k1 in verify-quorum example * fix: return an error if no logs were prefetched * fix: enforce length of branch * fix: remove state_requests * fix: forbid blockNumber >= block.number * fix: move BeaconBlockField to the host crate * fix: add EIP-4788 specification checks * fix: better handle reverts * fix: always perform anchor checks * fix: execute_and_commit() * fix: uniformize AnchorType * fix: include active hard fork in public values * fix: utility to verify chain config onchain * fix: validate chain config in Uniswap example * fix: Uniswap example tests * fix: panic in execute_and_commit if execution fail * fix: check the beacon anchor id type * chore: add audit report
1 parent 6ccad76 commit ed22930

File tree

18 files changed

+316
-190
lines changed

18 files changed

+316
-190
lines changed

Cargo.lock

Lines changed: 8 additions & 26 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Veridise_audit.pdf

565 KB
Binary file not shown.

contracts/src/ContractCall.sol

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ struct ContractPublicValues {
66
uint256 id;
77
bytes32 anchorHash;
88
AnchorType anchorType;
9-
bytes32 genesisHash;
9+
bytes32 chainConfigHash;
1010
address callerAddress;
1111
address contractAddress;
1212
bytes contractCalldata;
@@ -16,7 +16,8 @@ struct ContractPublicValues {
1616
/// @notice The type of the anchor.
1717
enum AnchorType {
1818
BlockHash,
19-
BeaconRoot
19+
Timestamp,
20+
Slot
2021
}
2122

2223
/// @notice The anchor is too old and can no longer be validated.
@@ -39,7 +40,7 @@ library ContractCall {
3940
if (publicValues.anchorType == AnchorType.BlockHash) {
4041
return verifyBlockAnchor(publicValues.id, publicValues.anchorHash);
4142
}
42-
if (publicValues.anchorType == AnchorType.BeaconRoot) {
43+
if (publicValues.anchorType == AnchorType.Timestamp) {
4344
return verifyBeaconAnchor(publicValues.id, publicValues.anchorHash);
4445
}
4546

@@ -48,7 +49,7 @@ library ContractCall {
4849

4950
/// @notice Verify if the provided block hash matches the one of the given block number.
5051
function verifyBlockAnchor(uint256 blockNumber, bytes32 blockHash) internal view {
51-
if (block.number - blockNumber > 256) {
52+
if (blockNumber >= block.number || block.number - blockNumber > 256) {
5253
revert ExpiredAnchor();
5354
}
5455

@@ -72,4 +73,9 @@ library ContractCall {
7273
revert AnchorMismatch();
7374
}
7475
}
76+
77+
/// @notice Verify if the provided block root matches the one of the given timestamp.
78+
function verifiyChainConfig(ContractPublicValues memory publicValues, string memory activeForkName) internal view {
79+
assert(publicValues.chainConfigHash == keccak256(abi.encodePacked(block.chainid, activeForkName)));
80+
}
7581
}

crates/client-executor/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ sha2.workspace = true
1515
serde_with = "3.12.0"
1616
thiserror.workspace = true
1717

18+
# sp1
19+
sp1-zkvm.workspace = true
20+
1821
# rsp
1922
rsp-witness-db.workspace = true
2023
rsp-primitives.workspace = true

crates/client-executor/src/anchor.rs

Lines changed: 45 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{collections::HashMap, fmt::Display};
1+
use std::collections::HashMap;
22

33
use alloy_consensus::Header;
44
use alloy_eips::eip4788::BEACON_ROOTS_ADDRESS;
@@ -15,9 +15,9 @@ use crate::AnchorType;
1515
// https://eips.ethereum.org/EIPS/eip-4788
1616
pub const HISTORY_BUFFER_LENGTH: U256 = uint!(8191_U256);
1717
/// The generalized Merkle tree index of the `block_hash` field in the `BeaconBlock`.
18-
const BLOCK_HASH_LEAF_INDEX: usize = 6444;
18+
pub const BLOCK_HASH_LEAF_INDEX: usize = 6444;
1919
/// The generalized Merkle tree index of the `state_root` field in the `BeaconBlock`.
20-
const STATE_ROOT_LEAF_INDEX: usize = 6434;
20+
pub const STATE_ROOT_LEAF_INDEX: usize = 6434;
2121

2222
/// Ethereum anchoring system for verifying block execution against beacon chain roots.
2323
///
@@ -58,12 +58,17 @@ impl Anchor {
5858
Anchor::Header(header_anchor) => ResolvedAnchor {
5959
id: U256::from(header_anchor.header.number),
6060
hash: header_anchor.header.hash_slow(),
61+
ty: AnchorType::BlockHash,
6162
},
62-
Anchor::Eip4788(beacon_anchor) | Anchor::Consensus(beacon_anchor) => {
63+
Anchor::Eip4788(beacon_anchor) => {
6364
let block_hash = beacon_anchor.inner.header.hash_slow();
6465
let hash = beacon_anchor.anchor.beacon_root(block_hash, BLOCK_HASH_LEAF_INDEX);
6566

66-
ResolvedAnchor { id: beacon_anchor.id().into(), hash }
67+
ResolvedAnchor {
68+
id: U256::from(beacon_anchor.id().as_timestamp().unwrap()),
69+
hash,
70+
ty: AnchorType::Timestamp,
71+
}
6772
}
6873
Anchor::ChainedEip4788(chained_anchor) => {
6974
// Retrieve the execution block beacon root and timestamp
@@ -88,7 +93,17 @@ impl Anchor {
8893

8994
// If the full chain is valid, return the resolved anchor containing
9095
// the reference block beacon root and timestamp
91-
ResolvedAnchor { id: timestamp, hash: beacon_root }
96+
ResolvedAnchor { id: timestamp, hash: beacon_root, ty: AnchorType::Timestamp }
97+
}
98+
Anchor::Consensus(beacon_anchor) => {
99+
let block_hash = beacon_anchor.inner.header.hash_slow();
100+
let hash = beacon_anchor.anchor.beacon_root(block_hash, BLOCK_HASH_LEAF_INDEX);
101+
102+
ResolvedAnchor {
103+
id: U256::from(beacon_anchor.id().as_slot().unwrap()),
104+
hash,
105+
ty: AnchorType::Slot,
106+
}
92107
}
93108
}
94109
}
@@ -97,8 +112,8 @@ impl Anchor {
97112
pub fn ty(&self) -> AnchorType {
98113
match self {
99114
Anchor::Header(_) => AnchorType::BlockHash,
100-
Anchor::Eip4788(_) | Anchor::ChainedEip4788(_) => AnchorType::Eip4788,
101-
Anchor::Consensus(_) => AnchorType::Consensus,
115+
Anchor::Eip4788(_) | Anchor::ChainedEip4788(_) => AnchorType::Timestamp,
116+
Anchor::Consensus(_) => AnchorType::Slot,
102117
}
103118
}
104119
}
@@ -114,10 +129,11 @@ impl From<Header> for Anchor {
114129
/// This structure represents the result of processing an anchor through its
115130
/// verification chain, yielding a canonical identifier and cryptographic hash
116131
/// that can be used for block validation.
117-
#[derive(Debug, Clone, Copy)]
132+
#[derive(Debug, Clone)]
118133
pub struct ResolvedAnchor {
119134
pub id: U256,
120135
pub hash: B256,
136+
pub ty: AnchorType,
121137
}
122138

123139
/// A simple anchor that directly references an Ethereum execution block header.
@@ -214,18 +230,19 @@ impl BeaconAnchor {
214230
}
215231
}
216232

217-
/// Identifier for a beacon chain anchor, specifying how to locate the anchor in beacon chain history.
233+
/// Identifier for a beacon chain anchor, specifying how to locate the anchor in beacon chain
234+
/// history.
218235
///
219236
/// The beacon chain stores historical roots that can be accessed either by timestamp
220237
/// (for EIP-4788 verification) or by slot number (for direct beacon chain verification).
221238
/// This enum allows anchors to specify which indexing method should be used.
222239
///
223240
/// # Variants
224241
///
225-
/// - **Timestamp**: References a beacon root by its timestamp, used with EIP-4788
226-
/// where beacon roots are stored in the execution layer indexed by timestamp
227-
/// - **Slot**: References a beacon root by its slot number, used for direct beacon
228-
/// chain verification where data is indexed by consensus slots
242+
/// - **Timestamp**: References a beacon root by its timestamp, used with EIP-4788 where beacon
243+
/// roots are stored in the execution layer indexed by timestamp
244+
/// - **Slot**: References a beacon root by its slot number, used for direct beacon chain
245+
/// verification where data is indexed by consensus slots
229246
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
230247
pub enum BeaconAnchorId {
231248
Timestamp(u64),
@@ -240,13 +257,12 @@ impl BeaconAnchorId {
240257
BeaconAnchorId::Slot(_) => None,
241258
}
242259
}
243-
}
244260

245-
impl From<&BeaconAnchorId> for U256 {
246-
fn from(value: &BeaconAnchorId) -> Self {
247-
match value {
248-
BeaconAnchorId::Timestamp(t) => U256::from(*t),
249-
BeaconAnchorId::Slot(s) => U256::from(*s),
261+
/// Returns timestamp if this is a Timestamp variant, None otherwise.
262+
pub fn as_slot(&self) -> Option<u64> {
263+
match self {
264+
BeaconAnchorId::Timestamp(_) => None,
265+
BeaconAnchorId::Slot(s) => Some(*s),
250266
}
251267
}
252268
}
@@ -277,13 +293,15 @@ pub struct ChainedBeaconAnchor {
277293
}
278294

279295
impl ChainedBeaconAnchor {
280-
/// Creates a new chained beacon anchor linking an execution block through multiple state transitions.
296+
/// Creates a new chained beacon anchor linking an execution block through multiple state
297+
/// transitions.
281298
pub fn new(inner: BeaconWithHeaderAnchor, state_anchors: Vec<BeaconStateAnchor>) -> Self {
282299
Self { inner, state_anchors }
283300
}
284301
}
285302

286-
/// An anchor that combines beacon chain state with cryptographic proof for state transition verification.
303+
/// An anchor that combines beacon chain state with cryptographic proof for state transition
304+
/// verification.
287305
///
288306
/// This structure represents a single link in a chained verification process, containing
289307
/// both a beacon chain state and the cryptographic proof needed to verify that state's
@@ -311,43 +329,6 @@ impl BeaconStateAnchor {
311329
}
312330
}
313331

314-
/// A field identifier for beacon block components that can be verified via Merkle proofs.
315-
///
316-
/// This enum specifies which field of a beacon block should be used as the leaf value
317-
/// in Merkle proof verification. Different anchor types require verification of different
318-
/// beacon block fields to establish the cryptographic link between execution and consensus layers.
319-
#[derive(Debug, Clone, Copy)]
320-
pub enum BeaconBlockField {
321-
BlockHash,
322-
StateRoot,
323-
}
324-
325-
impl Display for BeaconBlockField {
326-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327-
match self {
328-
BeaconBlockField::BlockHash => write!(f, "block_hash"),
329-
BeaconBlockField::StateRoot => write!(f, "state_root"),
330-
}
331-
}
332-
}
333-
334-
impl PartialEq<BeaconBlockField> for usize {
335-
fn eq(&self, other: &BeaconBlockField) -> bool {
336-
let other = usize::from(other);
337-
338-
*self == other
339-
}
340-
}
341-
342-
impl From<&BeaconBlockField> for usize {
343-
fn from(value: &BeaconBlockField) -> Self {
344-
match value {
345-
BeaconBlockField::BlockHash => BLOCK_HASH_LEAF_INDEX,
346-
BeaconBlockField::StateRoot => STATE_ROOT_LEAF_INDEX,
347-
}
348-
}
349-
}
350-
351332
/// Rebuilds a Merkle tree root from a leaf value and its branch proof.
352333
///
353334
/// Given a leaf value, its generalized index in the tree, and the sibling hashes
@@ -369,6 +350,8 @@ pub fn rebuild_merkle_root(leaf: B256, generalized_index: usize, branch: &[B256]
369350
let mut index = generalized_index - (1 << depth);
370351
let mut hasher = Sha256::new();
371352

353+
assert_eq!(branch.len() as u32, depth);
354+
372355
for sibling in branch {
373356
// Determine if the current node is a left or right child
374357
let is_left = index % 2 == 0;
@@ -407,9 +390,12 @@ pub fn rebuild_merkle_root(leaf: B256, generalized_index: usize, branch: &[B256]
407390
///
408391
/// The beacon root hash stored at the given timestamp
409392
pub fn get_beacon_root_from_state(state: &EthereumState, timestamp: U256) -> B256 {
393+
assert!(!timestamp.is_zero());
410394
let db = TrieDB::new(state, HashMap::default(), HashMap::default());
411395
let timestamp_idx = timestamp % HISTORY_BUFFER_LENGTH;
412396
let root_idx = timestamp_idx + HISTORY_BUFFER_LENGTH;
397+
let timestamp_in_storage = db.storage_ref(BEACON_ROOTS_ADDRESS, timestamp_idx).unwrap();
398+
assert_eq!(timestamp, timestamp_in_storage);
413399

414400
let root = db.storage_ref(BEACON_ROOTS_ADDRESS, root_idx).unwrap();
415401

crates/client-executor/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ use thiserror::Error;
55
pub enum ClientError {
66
#[error("ABI error: {0}")]
77
ABI(#[from] alloy_sol_types::Error),
8+
89
#[error("RSP error: {0}")]
910
RSP(#[from] rsp_client_executor::error::ClientError),
11+
12+
#[error("The logs weren't prefetched")]
13+
LogsNotPrefetched,
14+
15+
#[error("The provided chain config is invalid")]
16+
InvalidChainConfig,
1017
}

0 commit comments

Comments
 (0)