Skip to content

Commit 25fc6ac

Browse files
cryptoAtwillraulk
andauthored
chore: vendor our merkle-tree-rs fork (#941)
Co-authored-by: raulk <raul@protocol.ai>
1 parent 9dbf467 commit 25fc6ac

10 files changed

Lines changed: 1305 additions & 7 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ resolver = "2"
33
members = [
44
# contracts
55
"contracts/binding",
6+
7+
# merkle
8+
"ext/merkle-tree-rs",
9+
610
# ipc
711
"ipc/cli",
812
"ipc/wallet",
@@ -108,7 +112,6 @@ libsecp256k1 = "0.7"
108112
literally = "0.1.3"
109113
log = "0.4"
110114
lru_time_cache = "0.11"
111-
merkle-tree-rs = "0.1.0"
112115
multiaddr = "0.18"
113116
multihash = { version = "0.18.1", default-features = false, features = [
114117
"sha2",
@@ -227,8 +230,6 @@ tendermint-proto = { version = "0.31" }
227230
[patch.crates-io]
228231
# Use stable-only features.
229232
gcra = { git = "https://github.com/consensus-shipyard/gcra-rs.git", branch = "main" }
230-
# Contains some API changes that the upstream has not merged.
231-
merkle-tree-rs = { git = "https://github.com/consensus-shipyard/merkle-tree-rs.git", branch = "dev" }
232233

233234
[profile.wasm]
234235
inherits = "release"

ext/merkle-tree-rs/Cargo.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[package]
2+
name = "merkle-tree-rs"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MIT"
6+
rust-version = "1.65.0"
7+
authors = ["Ahiara Ikechukwu Marvellous <https://github.com/literallymarvellous>"]
8+
readme = "README.md"
9+
repository = "https://github.com/literallymarvellous/merkle-tree-rs"
10+
description = "A Rust library to generate merkle trees and merkle proofs."
11+
12+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13+
14+
[dependencies]
15+
serde = "1.0.147"
16+
serde_json = "1.0"
17+
anyhow = "1.0"
18+
thiserror = "1.0.24"
19+
render-tree = "0.1.1"
20+
ethers = "2.0"

ext/merkle-tree-rs/README.md

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
> Forked from [literallymarvellous/merkle-tree-rs](https://github.com/literallymarvellous/merkle-tree-rs) (via [consensus-shipyard/merkle-tree-rs](https://github.com/consensus-shipyard/merkle-tree-rs)) with adaptations, including: type flexibility, perf improvements, ethers dependency upgrade, output formatting, and type safety (using the specialized H256 for hashes, and preserving Bytes for raw bytes). The original upstream was labelled as MIT licensed in its Cargo.toml, so we preserve that license for this crate.
2+
3+
**A Rust library to generate merkle trees and merkle proofs.**
4+
5+
This is based on [@openzeppelin/merkle-tree](https://github.com/OpenZeppelin/merkle-tree) implementation of merkle-trees and it's well suited for airdrops and similar mechanisms in combination with OpenZeppelin Contracts [`MerkleProof`] utilities.
6+
7+
[`merkleproof`]: https://docs.openzeppelin.com/contracts/4.x/api/utils#MerkleProof
8+
9+
## Quick Start
10+
11+
Add merkle-tree-rs to your repository, also serde and serde_json for json.
12+
13+
```
14+
[dependencies]
15+
16+
merkle-tree-rs = "0.1.0"
17+
serde = "1.0.147"
18+
serde_json = "1.0"
19+
```
20+
21+
### Building a Tree
22+
23+
```rust
24+
use merkle_tree_rs::standard::StandardMerkleTree;
25+
use std::fs;
26+
27+
fn main() {
28+
let values = vec![
29+
vec![
30+
"0x1111111111111111111111111111111111111111",
31+
"5000000000000000000",
32+
],
33+
vec![
34+
"0x2222222222222222222222222222222222222222",
35+
"2500000000000000000",
36+
],
37+
];
38+
39+
let tree = StandardMerkleTree::of(values, &["address", "uint256"]);
40+
41+
let root = tree.root();
42+
43+
println!("Merkle root: {}", root);
44+
45+
let tree_json = serde_json::to_string(&tree.dump()).unwrap();
46+
47+
fs::write("tree.json", tree_json).unwrap();
48+
}
49+
```
50+
51+
1. Get the values to include in the tree. (Note: Consider reading them from a file.)
52+
2. Build the merkle tree. Set the encoding to match the values.
53+
3. Print the merkle root. You will probably publish this value on chain in a smart contract.
54+
4. Write a file that describes the tree. You will distribute this to users so they can generate proofs for values in the tree.
55+
56+
### Obtaining a Proof
57+
58+
Assume we're looking to generate a proof for the entry that corresponds to address `0x11...11`.
59+
60+
```rust
61+
use merkle_tree_rs::standard::StandardMerkleTree;
62+
use std::fs;
63+
64+
fn main() {
65+
let tree_json = fs::read_to_string("tree.json").unwrap();
66+
67+
let tree_data: StandardMerkleTreeData = serde_json::from_str(&tree_json).unwrap();
68+
69+
let tree = StandardMerkleTree::load(tree_data).unwrap();
70+
71+
for (i, v) in tree.clone().enumerate() {
72+
if v[0] == "0x1111111111111111111111111111111111111111" {
73+
let proof = tree.get_proof(LeafType::Number(i));
74+
println!("Value : {:?}", v);
75+
println!("Proof : {:?}", proof);
76+
}
77+
}
78+
}
79+
```
80+
81+
1. Load the tree from the description that was generated previously.
82+
2. Loop through the entries to find the one you're interested in.
83+
3. Generate the proof using the index of the entry.
84+
85+
In practice this might be done in a frontend application prior to submitting the proof on-chain, with the address looked up being that of the connected wallet.
86+
87+
See [`MerkleProof`] for documentation on how to validate the proof in Solidity.
88+
89+
## Standard Merkle Trees
90+
91+
This library works on "standard" merkle trees designed for Ethereum smart contracts. We have defined them with a few characteristics that make them secure and good for on-chain verification.
92+
93+
- The tree is shaped as a [complete binary tree](https://xlinux.nist.gov/dads/HTML/completeBinaryTree.html).
94+
- The leaves are sorted.
95+
- The leaves are the result of ABI encoding a series of values.
96+
- The hash used is Keccak256.
97+
- The leaves are double-hashed to prevent [second preimage attacks].
98+
99+
[second preimage attacks]: https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/
100+
101+
From the last three points we get that the hash of a leaf in the tree with value `[addr, amount]` can be computed in Solidity as follows:
102+
103+
```solidity
104+
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(addr, amount))));
105+
```
106+
107+
This is an opinionated design that we believe will offer the best out of the box experience for most users. We may introduce options for customization in the future based on user requests.
108+
109+
## API & Examples
110+
111+
### `StandardMerkleTree`
112+
113+
```rust
114+
use merkle_tree_rs::standard::StandardMerkleTree,
115+
```
116+
117+
### `StandardMerkleTree.of`
118+
119+
Types currently supported for encoding includes address, uint, uint256 and string.
120+
121+
```rust
122+
let values = vec![
123+
vec![
124+
"0x1111111111111111111111111111111111111111",
125+
"5000000000000000000",
126+
],
127+
vec![
128+
"0x2222222222222222222222222222222222222222",
129+
"2500000000000000000",
130+
],
131+
];
132+
let encoding = ["address", "uint256"];
133+
let tree = StandardMerkleTree::of(values, &encoding).unwrap();
134+
```
135+
136+
Creates a standard merkle tree out of an array of the elements in the tree, along with their types for ABI encoding.
137+
138+
> **Note**
139+
> Consider reading the array of elements from a CSV file for easy interoperability with spreadsheets or other data processing pipelines.
140+
141+
### `tree.root`
142+
143+
```rust
144+
println!("{}", tree.root());
145+
```
146+
147+
The root of the tree is a commitment on the values of the tree. It can be published (e.g., in a smart contract) to later prove that its values are part of the tree.
148+
149+
### `tree.dump`
150+
151+
```rust
152+
let tree_json = serde_json::to_string(&tree.dump()).unwrap();
153+
154+
fs::write("tree.json", tree_json).unwrap();
155+
```
156+
157+
Returns a description of the merkle tree for distribution. It contains all the necessary information to reproduce the tree, find the relevant leaves, and generate proofs. You should distribute this to users in a web application or command line interface so they can generate proofs for their leaves of interest.
158+
159+
### `StandardMerkleTree.load`
160+
161+
```rust
162+
let tree_json = fs::read_to_string("tree.json").unwrap();
163+
let tree_data: StandardMerkleTreeData = serde_json::from_str(&tree_json).unwrap();
164+
165+
let tree = StandardMerkleTree::load(tree_data).unwrap();
166+
```
167+
168+
Loads the tree from a description previously returned by `dump`.
169+
170+
### `tree.getProof`
171+
172+
```rust
173+
let proof = tree.get_proof(LeafType::Number(i)).unwrap();
174+
```
175+
176+
Returns a proof for the `i`th value in the tree. Indices refer to the position of the values in the array from which the tree was constructed.
177+
178+
It is wrapped in a `LeafType` enum of `Number(usize)` for indices and `LeafBytes(Vec<string>)` for values. Using value is less efficient cause it will fail if the value is not found in the tree.
179+
180+
```rust
181+
let proof = tree.get_proof(LeafType::LeafBytes([alice, "100"])).unwrap();
182+
```
183+
184+
### `tree.getMultiProof`
185+
186+
```rust
187+
let multi_proof = tree.get_multi_proof([LeafType::Number(i0), LeafType::Number(i1), ...]).unwrap();
188+
```
189+
190+
Returns a multiproof strcut containing {proof, prooflags, leaves} for the values at indices `i0, i1, ...`. Indices refer to the position of the values in the array from which the tree was constructed.
191+
192+
The multiproof returned contains an array with the leaves that are being proven. This array may be in a different order than that given by `i0, i1, ...`! The order returned is significant, as it is that in which the leaves must be submitted for verification (e.g., in a smart contract).
193+
194+
Also accepts values instead of indices, but this will be less efficient. It will fail if any of the values is not found in the tree.
195+
196+
### Interating over the tree
197+
198+
```rust
199+
for (i, v) in tree.clone().enumerate {
200+
console.log("value: {:?}", v);
201+
console.log("proof: {:?}", tree.getProof(LeafType::Number(i)).unwrap());
202+
}
203+
```
204+
205+
Lists the values in the tree along with their indices, which can be used to obtain proofs.
206+
207+
### `tree.render`
208+
209+
```rust
210+
println!("{:?}", tree.render().unwrap());
211+
```
212+
213+
Returns a visual representation of the tree that can be useful for debugging.
214+
215+
### `tree.leafHash`
216+
217+
```rust
218+
let leaf = tree.leaf_hash(["alice".to_string(), "100".to_string()]).unwrap();
219+
```
220+
221+
Returns the leaf hash of the value, as defined in [Standard Merkle Trees](#standard-merkle-trees).
222+
223+
Corresponds to the following expression in Solidity:
224+
225+
```solidity
226+
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(alice, 100))));
227+
```
228+
229+
Attributions
230+
231+
- [@openzeppelin/merkle-tree](https://github.com/OpenZeppelin/merkle-tree)

0 commit comments

Comments
 (0)