Skip to content

Commit 270e57e

Browse files
Wollaccaposselenategraf
authored
WEB3-68: feat: Add EIP4788 commitments (#180)
Adds the option to commit to a Beacon (Consensus) block instead of an Execution block. --------- Co-authored-by: capossele <[email protected]> Co-authored-by: Victor Graf <[email protected]>
1 parent fb48ef7 commit 270e57e

File tree

30 files changed

+916
-350
lines changed

30 files changed

+916
-350
lines changed

.github/workflows/main.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,19 @@ jobs:
154154
- name: build erc20-Counter
155155
run: cargo build
156156
working-directory: examples/erc20-counter
157+
- name: forge test erc20-Counter
158+
run: forge test
159+
working-directory: examples/erc20-counter
160+
env:
161+
ETH_RPC_URL: https://ethereum-sepolia-rpc.publicnode.com
157162
- name: build token-stats
158163
run: cargo build
159164
working-directory: examples/token-stats
160165
- name: test erc20-Counter
161166
run: ./test-local-deployment.sh
162167
working-directory: examples/erc20-counter
168+
env:
169+
RISC0_DEV_MODE: true
163170
- run: sccache --show-stats
164171

165172
doc:

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ alloy-sol-types = { version = "0.7" }
3232
alloy = { version = "0.2.1", features = ["full"] }
3333
alloy-trie = { version = "0.4.0" }
3434

35+
# Beacon chain support
36+
beacon-api-client = { git = "https://github.com/ralexstokes/ethereum-consensus.git", rev = "cf3c404043230559660810bc0c9d6d5a8498d819" }
37+
ethereum-consensus = { git = "https://github.com/ralexstokes/ethereum-consensus.git", rev = "cf3c404043230559660810bc0c9d6d5a8498d819" }
38+
3539
anyhow = { version = "1.0" }
3640
bincode = { version = "1.3" }
3741
clap = { version = "4.5", features = ["derive", "env"] }
@@ -41,6 +45,7 @@ once_cell = "1.19"
4145
revm = { version = "13.0", default-features = false, features = ["std"] }
4246
serde = "1.0"
4347
serde_json = "1.0"
48+
sha2 = { version = "0.10" }
4449
test-log = "0.2.15"
4550
tokio = { version = "1.35" }
4651
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

contracts/src/steel/Steel.sol

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,107 @@ pragma solidity ^0.8.9;
1919
/// @title Steel Library
2020
/// @notice This library provides a collection of utilities to work with Steel commitments in Solidity.
2121
library Steel {
22-
/// @notice A Commitment struct representing a block number and its block hash.
22+
/// @notice Represents a commitment to a specific block in the blockchain.
23+
/// @dev The `blockID` encodes both the block identifier (block number or timestamp) and the version.
24+
/// @dev The `blockDigest` is the block hash or beacon block root, used for validation.
2325
struct Commitment {
24-
uint256 blockNumber; // Block number at which the commitment was made.
25-
bytes32 blockHash; // Hash of the block at the specified block number.
26+
uint256 blockID;
27+
bytes32 blockDigest;
2628
}
2729

30+
/// @notice The version of the Commitment is incorrect.
31+
error InvalidCommitmentVersion();
32+
33+
/// @notice The Commitment is too old and can no longer be validated.
34+
error CommitmentTooOld();
35+
2836
/// @notice Validates if the provided Commitment matches the block hash of the given block number.
2937
/// @param commitment The Commitment struct to validate.
30-
/// @return isValid True if the commitment's block hash matches the block hash of the block number, false otherwise.
31-
function validateCommitment(Commitment memory commitment) internal view returns (bool isValid) {
32-
return commitment.blockHash == blockhash(commitment.blockNumber);
38+
/// @return True if the commitment's block hash matches the block hash of the block number, false otherwise.
39+
function validateCommitment(Commitment memory commitment) internal view returns (bool) {
40+
(uint240 blockID, uint16 version) = Encoding.decodeVersionedID(commitment.blockID);
41+
if (version == 0) {
42+
return validateBlockCommitment(blockID, commitment.blockDigest);
43+
} else if (version == 1) {
44+
return validateBeaconCommitment(blockID, commitment.blockDigest);
45+
} else {
46+
revert InvalidCommitmentVersion();
47+
}
48+
}
49+
50+
/// @notice Validates if the provided block commitment matches the block hash of the given block number.
51+
/// @param blockNumber The block number to compare against.
52+
/// @param blockHash The block hash to validate.
53+
/// @return True if the block's block hash matches the block hash, false otherwise.
54+
function validateBlockCommitment(uint256 blockNumber, bytes32 blockHash) internal view returns (bool) {
55+
if (block.number - blockNumber > 256) {
56+
revert CommitmentTooOld();
57+
}
58+
return blockHash == blockhash(blockNumber);
59+
}
60+
61+
/// @notice Validates if the provided beacon commitment matches the block root of the given timestamp.
62+
/// @param blockTimestamp The timestamp to compare against.
63+
/// @param blockRoot The block root to validate.
64+
/// @return True if the block's block root matches the block root, false otherwise.
65+
function validateBeaconCommitment(uint256 blockTimestamp, bytes32 blockRoot) internal view returns (bool) {
66+
if (block.timestamp - blockTimestamp > 12 * 8191) {
67+
revert CommitmentTooOld();
68+
}
69+
return blockRoot == Beacon.blockRoot(blockTimestamp);
70+
}
71+
}
72+
73+
/// @title Beacon Library
74+
library Beacon {
75+
/// @notice The address of the Beacon roots contract.
76+
/// @dev https://eips.ethereum.org/EIPS/eip-4788
77+
address internal constant BEACON_ROOTS_ADDRESS = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02;
78+
79+
/// @notice The Beacon block root could not be found as the next block has not been issued yet.
80+
error NoParentBeaconBlock();
81+
82+
/// @notice Attempts to find the root of the Beacon block with the given timestamp.
83+
/// @dev Since the Beacon roots contract only returns the parent Beacon block’s root, we need to find the next
84+
/// Beacon block instead. This is done by adding the block time of 12s until a value is returned.
85+
function blockRoot(uint256 timestamp) internal view returns (bytes32 root) {
86+
uint256 blockTimestamp = block.timestamp;
87+
while (true) {
88+
timestamp += 12; // Beacon block time is 12 seconds
89+
if (timestamp > blockTimestamp) revert NoParentBeaconBlock();
90+
91+
(bool success, bytes memory result) = BEACON_ROOTS_ADDRESS.staticcall(abi.encode(timestamp));
92+
if (success) {
93+
return abi.decode(result, (bytes32));
94+
}
95+
}
96+
}
97+
}
98+
99+
/// @title Encoding Library
100+
library Encoding {
101+
/// @notice Encodes a version and ID into a single uint256 value.
102+
/// @param id The base ID to be encoded, limited by 240 bits (or the maximum value of a uint240).
103+
/// @param version The version number to be encoded, limited by 16 bits (or the maximum value of a uint16).
104+
/// @return Returns a single uint256 value that contains both the `id` and the `version` encoded into it.
105+
function encodeVersionedID(uint240 id, uint16 version) internal pure returns (uint256) {
106+
uint256 encoded;
107+
assembly {
108+
encoded := or(shl(240, version), id)
109+
}
110+
return encoded;
111+
}
112+
113+
/// @notice Decodes a version and ID from a single uint256 value.
114+
/// @param id The single uint256 value to be decoded.
115+
/// @return Returns two values: a uint240 for the original base ID and a uint16 for the version number encoded into it.
116+
function decodeVersionedID(uint256 id) internal pure returns (uint240, uint16) {
117+
uint240 decoded;
118+
uint16 version;
119+
assembly {
120+
decoded := and(id, 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)
121+
version := shr(240, id)
122+
}
123+
return (decoded, version);
33124
}
34125
}

examples/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ Explore a more advanced interaction between [Steel] and a custom Ethereum smart
1616
This example shows how the [Steel] library can be used to call multiple view functions of a contract.
1717
This example generates a proof of a [Compound] cToken's APR (Annual Percentage Rate), showcasing the potential for on-chain verification of complex financial metrics.
1818

19-
[Counter]: ./erc20-counter/contracts/Counter.sol
2019
[coprocessor]: https://www.risczero.com/news/a-guide-to-zk-coprocessors-for-scalability
2120
[Steel]: ../steel
2221
[Compound]: https://compound.finance/

examples/erc20-counter/.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ETH_RPC_URL="https://ethereum-rpc.publicnode.com"

examples/erc20-counter/.gitignore

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@ out/
1212
anvil_logs.txt
1313

1414
# Autogenerated contracts
15-
contracts/ImageID.sol
16-
contracts/Elf.sol
17-
18-
# Dotenv file
19-
.env
15+
contracts/src/ImageID.sol
16+
contracts/src/Elf.sol
2017

2118
# Cargo
2219
target/

examples/erc20-counter/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ bytemuck = { version = "1.14" }
2727
clap = { version = "4.5" }
2828
hex = { version = "0.4" }
2929
erc20-counter-methods = { path = "./methods" }
30+
log = { version = "0.4" }
3031
serde = { version = "1.0", features = ["derive", "std"] }
3132
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
3233
tokio = { version = "1.39", features = ["full"] }

examples/erc20-counter/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# RISC Zero View Call Proofs ERC20-Counter Example
1+
# ERC20-Counter Example
22

33
This example implements a counter that increments based on off-chain RISC Zero [Steel] proofs submitted to the [Counter] contract.
44
The contract interacts with ERC-20 tokens, using [Steel] proofs to verify that an account holds at least 1 token before incrementing the counter.
@@ -35,6 +35,7 @@ The contract includes functionality to query the current value of the counter at
3535
## Dependencies
3636

3737
To get started, you need to have the following installed:
38+
3839
- [Rust]
3940
- [Foundry]
4041
- [RISC Zero]
@@ -59,7 +60,6 @@ When you're ready, follow the [deployment guide] to get your application running
5960
[Groth16 SNARK proof]: https://www.risczero.com/news/on-chain-verification
6061
[RISC Zero]: https://dev.risczero.com/api/zkvm/install
6162
[Sepolia]: https://www.alchemy.com/overviews/sepolia-testnet
62-
[cargo-binstall]: https://github.com/cargo-bins/cargo-binstall#cargo-binaryinstall
6363
[deployment guide]: ./deployment-guide.md
6464
[Rust]: https://doc.rust-lang.org/cargo/getting-started/installation.html
6565
[Counter]: ./contracts/Counter.sol

examples/erc20-counter/apps/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ alloy-primitives = { workspace = true }
99
anyhow = { workspace = true }
1010
clap = { workspace = true, features = ["derive", "env"] }
1111
erc20-counter-methods = { workspace = true }
12+
log = { workspace = true }
1213
risc0-ethereum-contracts = { workspace = true }
1314
risc0-steel = { workspace = true, features = ["host"] }
1415
risc0-zkvm = { workspace = true, features = ["client"] }

examples/erc20-counter/apps/README.md

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,23 @@ cargo run --bin publisher
1515
```text
1616
$ cargo run --bin publisher -- --help
1717
18-
Usage: publisher --eth-wallet-private-key <ETH_WALLET_PRIVATE_KEY> --rpc-url <RPC_URL> --contract <CONTRACT> --token <TOKEN> --account <ACCOUNT>
18+
Usage: publisher [OPTIONS] --eth-wallet-private-key <ETH_WALLET_PRIVATE_KEY> --eth-rpc-url <ETH_RPC_URL> --counter <COUNTER> --token-contract <TOKEN_CONTRACT> --account <ACCOUNT>
1919
2020
Options:
2121
--eth-wallet-private-key <ETH_WALLET_PRIVATE_KEY>
22-
Ethereum Node endpoint [env: ETH_WALLET_PRIVATE_KEY=]
23-
--rpc-url <RPC_URL>
24-
Ethereum Node endpoint [env: RPC_URL=]
25-
--contract <CONTRACT>
26-
Counter's contract address on Ethereum
27-
--token <TOKEN>
28-
ERC20 contract address on Ethereum
22+
Private key [env: ETH_WALLET_PRIVATE_KEY=]
23+
--eth-rpc-url <ETH_RPC_URL>
24+
Ethereum RPC endpoint URL [env: ETH_RPC_URL=]
25+
--beacon-api-url <BEACON_API_URL>
26+
Beacon API endpoint URL [env: BEACON_API_URL=]
27+
--counter <COUNTER>
28+
Address of the Counter verifier
29+
--token-contract <TOKEN_CONTRACT>
30+
Address of the ERC20 token contract [env: TOKEN_CONTRACT=]
2931
--account <ACCOUNT>
30-
Account address to read the balance_of on Ethereum
32+
Address to query the token balance of
3133
-h, --help
32-
Print help
33-
-V, --version
34-
Print version
35-
```
34+
Print help```
3635
3736
[publisher]: ./src/bin/publisher.rs
3837
[Counter]: ../contracts/Counter.sol

0 commit comments

Comments
 (0)