Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add merkle proof module #1101

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0bc1ca0
feat: add main logic
ericnordelo Aug 13, 2024
3199da1
feat: add hashes tests
ericnordelo Aug 14, 2024
c345375
feat: add some tests and update hashes
ericnordelo Aug 15, 2024
c564b66
feat: add tests for multi proofs
ericnordelo Aug 15, 2024
69f1ef3
feat: format files
ericnordelo Aug 15, 2024
1b4b8ec
feat: add more tests
ericnordelo Aug 15, 2024
d67535c
feat: finish poseidon tests
ericnordelo Aug 16, 2024
5a63fe6
Merge branch 'main' of github.com:OpenZeppelin/cairo-contracts into f…
ericnordelo Aug 16, 2024
9e70146
fix: version
ericnordelo Aug 16, 2024
a76ef71
fix: typo
ericnordelo Aug 16, 2024
4c3068d
refactor: remove unnecessary check
ericnordelo Aug 21, 2024
2b2a035
refactor: merkle_tree into a separated package
ericnordelo Aug 21, 2024
a6155ad
feat: update CHANGELOG
ericnordelo Aug 21, 2024
1732282
feat: remove common module from utils
ericnordelo Aug 22, 2024
1d15ee0
Merge branch 'main' of github.com:OpenZeppelin/cairo-contracts into f…
ericnordelo Aug 22, 2024
8cd2b51
docs: add page for merkle tree
ericnordelo Aug 22, 2024
9020139
feat: format files
ericnordelo Aug 22, 2024
11cb1cc
feat: update index from scarb bump
ericnordelo Aug 22, 2024
31cdc10
feat: add typos config file
ericnordelo Aug 22, 2024
ec11531
refactor: add empty line
ericnordelo Aug 22, 2024
34664b3
Update packages/utils/src/lib.cairo
ericnordelo Aug 23, 2024
7976a2d
Update packages/merkle_tree/src/hashes.cairo
ericnordelo Aug 23, 2024
65c67cc
feat: apple review updates
ericnordelo Aug 23, 2024
c599582
Merge branch 'feat/merkle-proof-verifier-#936' of github.com:ericnord…
ericnordelo Aug 23, 2024
e4758fd
feat: update test
ericnordelo Aug 26, 2024
198918c
Update docs/modules/ROOT/pages/api/merkle-tree.adoc
ericnordelo Aug 27, 2024
b2a8fd6
Update packages/merkle_tree/src/tests/merkle_proof/test_with_poseidon…
ericnordelo Aug 27, 2024
de1a13d
Update packages/merkle_tree/src/tests/merkle_proof/test_with_poseidon…
ericnordelo Aug 27, 2024
63928f9
Update docs/modules/ROOT/pages/api/merkle-tree.adoc
ericnordelo Aug 28, 2024
56f8491
Update docs/modules/ROOT/pages/api/merkle-tree.adoc
ericnordelo Aug 28, 2024
f9cae0e
feat: apply review updates
ericnordelo Aug 29, 2024
f7b839c
feat: format files
ericnordelo Aug 29, 2024
013aa57
Merge branch 'feat/merkle-proof-verifier-#936' of github.com:ericnord…
ericnordelo Aug 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- ERC2981 (NFT Royalty Standard) component (#1091)
- `merkle_tree` package with utilities to verify proofs and multi proofs (#1101)

### Changed

Expand All @@ -22,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Changed ABI suffix to Trait in dual case account and eth account modules (#1096).
- `DualCaseAccountABI` renamed to `DualCaseAccountTrait`
- `DualCaseEthAccountABI` renamed to `DualCaseEthAccountTrait`
- Bump scarb to v2.7.1 (#1025)
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

## 0.15.1 (2024-08-13)

Expand Down
8 changes: 8 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dependencies = [
"openzeppelin_account",
"openzeppelin_governance",
"openzeppelin_introspection",
"openzeppelin_merkle_tree",
"openzeppelin_presets",
"openzeppelin_security",
"openzeppelin_test_common",
Expand Down Expand Up @@ -58,6 +59,13 @@ dependencies = [
"snforge_std",
]

[[package]]
name = "openzeppelin_merkle_tree"
version = "0.15.1"
dependencies = [
"snforge_std",
]

[[package]]
name = "openzeppelin_presets"
version = "0.15.1"
Expand Down
10 changes: 6 additions & 4 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"packages/account",
"packages/governance",
"packages/introspection",
"packages/merkle_tree",
"packages/presets",
"packages/security",
"packages/token",
Expand All @@ -20,8 +21,8 @@ version.workspace = true
[workspace.package]
version = "0.15.1"
edition = "2023_11"
cairo-version = "2.7.0"
scarb-version = "2.7.0"
cairo-version = "2.7.1"
scarb-version = "2.7.1"
authors = ["OpenZeppelin Community <[email protected]>"]
description = "OpenZeppelin Contracts written in Cairo for Starknet, a decentralized ZK Rollup"
documentation = "https://docs.openzeppelin.com/contracts-cairo"
Expand All @@ -34,11 +35,11 @@ keywords = [
"cairo",
"contracts",
"security",
"standards",
"standards"
]

[workspace.dependencies]
starknet = "2.7.0"
starknet = "2.7.1"
snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.27.0" }

[dependencies]
Expand All @@ -47,6 +48,7 @@ openzeppelin_access = { path = "packages/access" }
openzeppelin_account = { path = "packages/account" }
openzeppelin_governance = { path = "packages/governance" }
openzeppelin_introspection = { path = "packages/introspection" }
openzeppelin_merkle_tree = { path = "packages/merkle_tree" }
openzeppelin_presets = { path = "packages/presets" }
openzeppelin_security = { path = "packages/security" }
openzeppelin_token = { path = "packages/token" }
Expand Down
4 changes: 4 additions & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[default]
extend-ignore-identifiers-re = [
"e288874ba",
]
2 changes: 2 additions & 0 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
*** xref:/guides/src5-migration.adoc[Migrating ERC165 to SRC5]
*** xref:/api/introspection.adoc[API Reference]

** xref:/api/merkle-tree.adoc[Merkle Tree]

** xref:security.adoc[Security]
*** xref:/api/security.adoc[API Reference]

Expand Down
208 changes: 208 additions & 0 deletions docs/modules/ROOT/pages/api/merkle-tree.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
:github-icon: pass:[<svg class="icon"><use href="#github-icon"/></svg>]
:strk-merkle-tree: https://github.com/ericnordelo/strk-merkle-tree[JavaScript library]
:verify: xref:#merkle_proof-verify[verify]
:verify_pedersen: xref:#merkle_proof-verify_perdersen[verify_pedersen]
:verify_poseidon: xref:#merkle_proof-verify_poseidon[verify_poseidon]
:verify_multi_proof: xref:#merkle_proof-verify_multi_proof[verify_multi_proof]
:process_multi_proof: xref:#merkle_proof-process_multi_proof[process_multi_proof]

= Merkle Tree

OpenZeppelin Contracts for Cairo provides a `merkle_tree` package with a set of utilities for verifying Merkle Tree proofs on-chain. The tree and the proofs can be generated using this {strk-merkle-tree}.

This module provides:

- `{verify}` - can prove that some value is part of a Merkle tree.

- `{verify_multi_proof}` - can prove multiple values are part of a Merkle tree.

NOTE: `openzeppelin_merkle_tree` doesn't have dependencies outside of `corelib`, and can be used in projects that are not Starknet-related.

[TIP]
====
To use it as a standalone package, you can add it in your `Scarb.toml` as follows:

`openzeppelin_merkle_tree = { git = "https://github.com/openzeppelin/cairo-contracts.git", tag = "v0.X.X" }`
====

== Modules

[.contract]
[[merkle_proof]]
=== `++merkle_proof++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/packages/merkle_tree/src/merkle_proof.cairo[{github-icon},role=heading-link]

```cairo
use openzeppelin_merkle_tree::merkle_proof;
```

These functions deal with verification of Merkle Tree proofs.

The tree and the proofs can be generated using this {strk-merkle-tree}. You will find a quickstart guide in the readme.

WARNING: You should avoid using leaf values that are two felt252 long prior to hashing, or use a hash function
other than the one used to hash internal nodes for hashing leaves. This is because the concatenation of a sorted pair
of internal nodes in the Merkle tree could be reinterpreted as a leaf value. The JavaScript library generates Merkle
trees that are safe against this attack out of the box.

[.contract-index]
.Functions
--
* xref:#merkle_proof-verify[`++verify<Hasher>(proof, root, leaf)++`]
* xref:#merkle_proof-verify_pedersen[`++verify_pedersen(proof, root, leaf)++`]
* xref:#merkle_proof-verify_poseidon[`++verify_poseidon(proof, root, leaf)++`]
* xref:#merkle_proof-process_proof[`++process_proof<Hasher>(proof, leaf)++`]
* xref:#merkle_proof-verify_multi_proof[`++verify_multi_proof<Hasher>(proof, proof_flags, root, leaves)++`]
* xref:#merkle_proof-process_multi_proof[`++process_multi_proof<Hasher>(proof, proof_flags, leaf)++`]
--

[#merkle_proof-Functions]
==== Functions

[.contract-item]
[[merkle_proof-verify]]
==== `[.contract-item-name]#++verify<+CommutativeHasher>++#++(proof: Span<felt252>, root: felt252, leaf: felt252) → bool++` [.item-kind]#public#
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look great with the current formatting IMO. I do get that we should include +CommutativeHasher though

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but couldn't find a better solution. I spent some time on that. Suggestions are more than appreciated.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah, I was playing around with this as well to offer a suggestion. I think an ideal solution would be to stack parameters vertically like we did in the last iteration of Nile (if I recall correctly). It's not worth blocking this, but it's a very nice-to-have feature


Returns true if a `leaf` can be proved to be a part of a Merkle tree defined by `root`.

For this, a `proof` must be provided, containing sibling hashes on the branch from the leaf to the root of the tree.

Each pair of leaves and each pair of pre-images are assumed to be sorted.

[NOTE]
====
This function expects a `CommutativeHasher` implementation. See xref:#hashes-CommutativeHasher[hashes::CommutativeHasher] for more information.

`{verify_pedersen}` and `{verify_poseidon}` already include the corresponding `Hasher` implementations.
====

[.contract-item]
[[merkle_proof-verify_pedersen]]
==== `[.contract-item-name]#++verify_pedersen++#++(proof: Span<felt252>, root: felt252, leaf: felt252) → bool++` [.item-kind]#public#

Version of `{verify}` using Perdersen as the hashing function.

[.contract-item]
[[merkle_proof-verify_poseidon]]
==== `[.contract-item-name]#++verify_poseidon++#++(proof: Span<felt252>, root: felt252, leaf: felt252) → bool++` [.item-kind]#public#

Version of `{verify}` using Poseidon as the hashing function.

[.contract-item]
[[merkle_proof-process_proof]]
==== `[.contract-item-name]#++process_proof<+CommutativeHasher>++#++(proof: Span<felt252>, leaf: felt252) → felt252++` [.item-kind]#public#

Returns the rebuilt hash obtained by traversing a Merkle tree up from `leaf` using `proof`.

A `proof` is valid if and only if the rebuilt hash matches the root of the tree.

When processing the proof, the pairs of leaves & pre-images are assumed to be sorted.

NOTE: This function expects a `CommutativeHasher` implementation. See xref:#hashes-CommutativeHasher[hashes::CommutativeHasher] for more information.

[.contract-item]
[[merkle_proof-verify_multi_proof]]
==== `[.contract-item-name]#++verify_multi_proof<+CommutativeHasher>++#++(proof: Span<felt252>, proof_flags: Span<bool>, root: felt252, leaves: Span<felt252>) → bool++` [.item-kind]#public#

Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined
by `root`, according to `proof` and `proof_flags` as described in `{process_multi_proof}`.

The `leaves` must be validated independently.

CAUTION: Not all Merkle trees admit multiproofs. See `{process_multi_proof}` for details.

NOTE: Consider the case where `root == proof.at(0) && leaves.len() == 0` as it will return `true`.

NOTE: This function expects a `CommutativeHasher` implementation. See xref:#hashes-CommutativeHasher[hashes::CommutativeHasher] for more information.

[.contract-item]
[[merkle_proof-process_multi_proof]]
==== `[.contract-item-name]#++process_multi_proof<+CommutativeHasher>++#++(proof: Span<felt252>, proof_flags: Span<bool>, leaves: Span<felt252>) → felt252++` [.item-kind]#public#

Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`.

The reconstruction proceeds by incrementally reconstructing all inner nodes by combining a
leaf/inner node with either another leaf/inner node or a proof sibling node, depending on
whether each `proof_flags` item is true or false respectively.

[CAUTION]
====
Not all Merkle trees admit multiproofs.
To use multiproofs, it is sufficient to ensure that:

1. The tree is complete (but not necessarily perfect).
2. The leaves to be proven are in the opposite order they are in the tree.
(i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
====

NOTE: The _empty set_ (i.e. the case where `proof.len() == 1 && leaves.len() == 0`) is
considered a no-op, and therefore a valid multiproof (i.e. it returns `proof.at(0)`). Consider
disallowing this case if you're not validating the leaves elsewhere.

NOTE: This function expects a `CommutativeHasher` implementation. See xref:#hashes-CommutativeHasher[hashes::CommutativeHasher] for more information.


[.contract]
[[hashes]]
=== `++hashes++` link:https://github.com/OpenZeppelin/cairo-contracts/blob/release-v0.15.1/packages/merkle_tree/src/hashes.cairo[{github-icon},role=heading-link]

```cairo
use openzeppelin_merkle_tree::hashes;
```

:pedersen-hasher: xref:#hashes-PedersenCHasher[PedersenCHasher]
:poseidon-hasher: xref:#hashes-PoseidonCHasher[PoseidonCHasher]

Module providing the trait and default implementations for the commutative hash functions used in
xref:#merkle_proof[`merkle_proof`].

NOTE: The `{pedersen-hasher}` implementation matches the default node hashing function used in the {strk-merkle-tree}.

[.contract-index]
.Traits
--
* xref:#hashes-CommutativeHasher[`++CommutativeHasher++`]
--

[.contract-index]
.Impls
--
* xref:#hashes-PedersenCHasher[`++PedersenCHasher++`]
* xref:#hashes-PoseidonCHasher[`++PoseidonCHasher++`]
--

[#hashes-Traits]
==== Traits

[.contract-item]
[[hashes-CommutativeHasher]]
==== `[.contract-item-name]#++CommutativeHasher++#` [.item-kind]#trait#

Declares a commutative hash function with the following signature:

`commutative_hash(a: felt252, b: felt252) -> felt252;`

which computes a commutative hash of a sorted pair of `felt252`.

This is usually implemented as an extension of a non-commutative hash function, like
Pedersen or Poseidon, returning the hash of the concatenation of the two values by first
sorting them.

Frequently used when working with merkle proofs.

NOTE: The `commutative_hash` function MUST follow the invariant that `commutative_hash(a, b) == commutative_hash(b, a)`.

[#hashes-Impls]
==== Impls

[.contract-item]
[[hashes-PedersenCHasher]]
==== `[.contract-item-name]#++PedersenCHasher++#` [.item-kind]#impl#

Implementation of the `CommutativeHasher` trait which computes the Pedersen hash of chaining the two input values
with the len (2), sorting the pair first.

[.contract-item]
[[hashes-PoseidonCHasher]]
==== `[.contract-item-name]#++PoseidonCHasher++#` [.item-kind]#impl#

Implementation of the `CommutativeHasher` trait which computes the Poseidon hash of the concatenation of two values, sorting the pair first.
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/guides/snip12.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ mod CustomERC20 {
let is_valid_signature_felt = DualCaseAccount { contract_address: owner }
.is_valid_signature(hash, signature);

// Check either 'VALID' or True for backwards compatibility
// Check either 'VALID' or true for backwards compatibility
let is_valid_signature = is_valid_signature_felt == starknet::VALIDATED
|| is_valid_signature_felt == 1;
assert(is_valid_signature, 'Invalid signature');
Expand Down
4 changes: 2 additions & 2 deletions docs/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ before proceeding, and run the following command to check that the installation
----
$ scarb --version

scarb 2.7.0 (e9a2b8716 2024-08-01)
cairo: 2.7.0 (https://crates.io/crates/cairo-lang-compiler/2.7.0)
scarb 2.7.1 (e288874ba 2024-08-13)
cairo: 2.7.1 (https://crates.io/crates/cairo-lang-compiler/2.7.1)
sierra: 1.6.0
----

Expand Down
21 changes: 21 additions & 0 deletions packages/merkle_tree/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

[package]
name = "openzeppelin_merkle_tree"
version.workspace = true
edition.workspace = true
cairo-version.workspace = true
scarb-version.workspace = true
authors.workspace = true
description.workspace = true
documentation.workspace = true
readme.workspace = true
repository.workspace = true
license-file.workspace = true
keywords.workspace = true

[tool]
fmt.workspace = true

[dev-dependencies]
starknet.workspace = true
snforge_std.workspace = true
Loading