Skip to content

Commit

Permalink
Add SimpleMerkleTree docs (#42)
Browse files Browse the repository at this point in the history
Co-authored-by: Hadrien Croubois <[email protected]>
  • Loading branch information
ernestognw and Amxx committed Feb 28, 2024
1 parent 952fd9f commit 7288854
Showing 1 changed file with 100 additions and 33 deletions.
133 changes: 100 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# `@openzeppelin/merkle-tree`


**A JavaScript library to generate merkle trees and merkle proofs.**

Well suited for airdrops and similar mechanisms in combination with OpenZeppelin Contracts [`MerkleProof`] utilities.
Expand Down Expand Up @@ -120,15 +119,39 @@ This library works on "standard" merkle trees designed for Ethereum smart contra

[second preimage attacks]: https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/

## Simple Merkle Trees

The library also supports "simple" merkle trees, which are a simplified version of the standard ones. They are designed to be more flexible and accept arbitrary `bytes32` data as leaves. It keeps the same tree shape and internal pair hashing algorithm.

As opposed to standard trees, leaves are not double-hashed. Instead they are ABI encoded to `bytes32` and hashed in pairs inside the tree. This is useful to override the leaf hashing algorithm and use a different one prior to building the tree.

Users of tooling that produced trees without double leaf hashing can use this feature to build a representation of the tree in JavaScript. We recommend this approach exclusively for trees that are already built on-chain. Otherwise the standard tree may be a better fit.

```typescript
import { SimpleMerkleTree } from '@openzeppelin/merkle-tree';
import keccak256 from '@ethersproject/keccak256';

// (1)
const tree = SimpleMerkleTree.of([keccak256('Value 1'), keccak256('Value 2')]);

// (2)
// ...
```

1. Use a custom leaf hashing algorithm to produce `bytes32` values for the tree.
2. The Simple Merkle Tree share the same API as Standard Merkle Tree.

## Advanced usage

### Leaf Hash

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:
The Standard Merkle Tree uses an opinionated double leaf hashing algorithm. For example, a leaf in the tree with value `[addr, amount]` can be computed in Solidity as follows:

```solidity
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(addr, amount))));
```

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.
This is an opinionated design that we believe will offer the best out of the box experience for most users. However, there are advanced use case where a different leaf hashing algorithm may be needed. For those, the `SimpleMerkleTree` can be used to build a tree with custom leaf hashing.

### Leaf ordering

Expand All @@ -144,73 +167,117 @@ However, some trees are constructed iteratively from unsorted data, causing the

## API & Examples

> **Note**
> Consider reading the array of elements from a CSV file for easy interoperability with spreadsheets or other data processing pipelines.
> **Note**
> By default, leaves are sorted according to their hash. This is done so that multiproof generated by the library can more easily be verified onchain. This can be disabled using the optional third argument. See the [Leaf ordering](#leaf-ordering) section for more details.
### `StandardMerkleTree`

```typescript
import { StandardMerkleTree } from "@openzeppelin/merkle-tree";
```

### `StandardMerkleTree.of`
#### `StandardMerkleTree.of`

```typescript
const tree = StandardMerkleTree.of([[alice, '100'], [bob, '200']], ['address', 'uint'], options);
```

Creates a standard merkle tree out of an array of the elements in the tree, along with their types for ABI encoding. For documentation on the syntax of the types, including how to encode structs, refer to the documentation for Ethers.js's [`AbiCoder`](https://docs.ethers.org/v5/api/utils/abi/coder/#AbiCoder-encode).

> **Note**
> Consider reading the array of elements from a CSV file for easy interoperability with spreadsheets or other data processing pipelines.
> **Note**
> By default, leaves are sorted according to their hash. This is done so that multiproof generated by the library can more easily be verified onchain. This can be disabled using the optional third argument. See the [Leaf ordering](#leaf-ordering) section for more details.
#### `StandardMerkleTree.load`

#### Options
```typescript
StandardMerkleTree.load(JSON.parse(fs.readFileSync('tree.json', 'utf8')));
```

| Option | Description | Default |
| ------------ | ----------------------------------------------------------------------------------- | ------- |
| `sortLeaves` | Enable or disable sorted leaves. Sorting is strongly recommended for multiproofs. | `true` |
Loads the tree from a description previously returned by `tree.dump`.

### `StandardMerkleTree.verify`
#### `StandardMerkleTree.verify`

```typescript
const verified = StandardMerkleTree.verify(root, ['address', 'uint'], [alice, '100'], proof);
```

Returns a boolean that is `true` when the proof verifies that the value is contained in the tree given only the proof, merkle root, and encoding.

### `StandardMerkleTree.verifyMultiProof`
#### `StandardMerkleTree.verifyMultiProof`

```typescript
const isValid = StandardMerkleTree.verifyMultiProof(root, leafEncoding, multiproof);
```

Returns a boolean that is `true` when the multiproof verifies that all the values are contained in the tree given only the multiproof, merkle root, and leaf encoding.

### `StandardMerkleTree.load`
### `SimpleMerkleTree`

```typescript
StandardMerkleTree.load(JSON.parse(fs.readFileSync('tree.json', 'utf8')));
import { SimpleMerkleTree } from '@openzeppelin/merkle-tree';
```

Loads the tree from a description previously returned by `tree.dump`.
#### `SimpleMerkleTree.of`

```typescript
const tree = SimpleMerkleTree.of([hashFn('Value 1'), hashFn('Value 2')]);
```

The `hashFn` is a custom cryptographic leaf hashing algorithm that returns `bytes32` values. The tree will be built using these values as leaves. The function should be different to the internal hashing pair algorithm used by the tree.

#### `SimpleMerkleTree.load`

```typescript
SimpleMerkleTree.load(JSON.parse(fs.readFileSync('tree.json', 'utf8')));
```

Same as `StandardMerkleTree.load`.

#### `SimpleMerkleTree.verify`

```typescript
const verified = SimpleMerkleTree.verify(root, hashFn('Value 1'), proof);
```

Same as `StandardMerkleTree.verify`, but using raw `bytes32` values.

#### `SimpleMerkleTree.verifyMultiProof`

```typescript
const isValid = SimpleMerkleTree.verifyMultiProof(root, multiproof);
```

Same as `StandardMerkleTree.verifyMultiProof`.

### Shared API

Both `StandardMerkleTree` and `SimpleMerkleTree` share the same API, defined below.

#### Options

Allows to configure the behavior of the tree. The following options are available:

| Option | Description | Default |
| ------------ | --------------------------------------------------------------------------------- | ------- |
| `sortLeaves` | Enable or disable sorted leaves. Sorting is strongly recommended for multiproofs. | `true` |

### `tree.root`
#### `tree.root`

```typescript
console.log(tree.root);
```

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.

### `tree.dump`
#### `tree.dump`

```typescript
fs.writeFileSync('tree.json', JSON.stringify(tree.dump()));
```

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.

### `tree.getProof`
#### `tree.getProof`

```typescript
const proof = tree.getProof(i);
Expand All @@ -221,10 +288,10 @@ Returns a proof for the `i`th value in the tree. Indices refer to the position o
Also accepts a value instead of an index, but this will be less efficient. It will fail if the value is not found in the tree.

```typescript
const proof = tree.getProof([alice, '100']);
const proof = tree.getProof(value); // e.g. [alice, '100']
```

### `tree.getMultiProof`
#### `tree.getMultiProof`

```typescript
const { proof, proofFlags, leaves } = tree.getMultiProof([i0, i1, ...]);
Expand All @@ -237,27 +304,27 @@ The multiproof returned contains an array with the leaves that are being proven.
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.

```typescript
const proof = tree.getProof([[alice, '100'], [bob, '200']]);
const proof = tree.getMultiProof([value1, value2]); // e.g. [[alice, '100'], [bob, '200']]
```

### `tree.verify`
#### `tree.verify`

```typescript
tree.verify(i, proof);
tree.verify([alice, '100'], proof);
tree.verify(value, proof); // e.g. [alice, '100']
```

Returns a boolean that is `true` when the proof verifies that the value is contained in the tree.

### `tree.verifyMultiProof`
#### `tree.verifyMultiProof`

```typescript
tree.verifyMultiProof({ proof, proofFlags, leaves });
```

Returns a boolean that is `true` when the multi-proof verifies that the values are contained in the tree.

### `tree.entries`
#### `tree.entries`

```typescript
for (const [i, v] of tree.entries()) {
Expand All @@ -268,23 +335,23 @@ for (const [i, v] of tree.entries()) {

Lists the values in the tree along with their indices, which can be used to obtain proofs.

### `tree.render`
#### `tree.render`

```typescript
console.log(tree.render());
```

Returns a visual representation of the tree that can be useful for debugging.

### `tree.leafHash`
#### `tree.leafHash`

```typescript
const leaf = tree.leafHash([alice, '100']);
const leaf = tree.leafHash(value); // e.g. [alice, '100']
```

Returns the leaf hash of the value, as defined in [Standard Merkle Trees](#standard-merkle-trees).
Returns the leaf hash of the value, defined per tree type.

Corresponds to the following expression in Solidity:
In case of the `StandardMerkleTree`, it corresponds to the following expression in Solidity:

```solidity
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(alice, 100))));
Expand Down

0 comments on commit 7288854

Please sign in to comment.