Skip to content

Commit d938567

Browse files
jotabulaciosnicole-grausColoCarlettidiegokingston
authored
Update Readme (#987)
* update kzg Readme * KZG: make sure example is ok * Add Readme for Merkle Tree * Add Readme for Circle * fix circle docs * fix circle readme * fix typo, remove unused test and fix markdown style * changes in circle readme * fix circle readme * fix clippy * fix clippy spaces --------- Co-authored-by: Nicole <[email protected]> Co-authored-by: Joaquin Carletti <[email protected]> Co-authored-by: Diego K <[email protected]>
1 parent 3046f0e commit d938567

File tree

4 files changed

+562
-5
lines changed

4 files changed

+562
-5
lines changed

crates/crypto/src/commitments/README.md

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,136 @@ $e( P - y g_1 , - g_2 ) \times e( Q , [\tau]_2 - z g_2 ) \equiv 1 \pmod{r}$
6060

6161
This is more efficient, since we can compute the pairing using two Miller loops but just one final exponentiation.
6262

63+
64+
## Implementation
65+
66+
The implementation in this codebase includes:
67+
68+
- `StructuredReferenceString`: Stores the powers of a secret value in both G1 and G2 groups
69+
- `KateZaveruchaGoldberg`: The main implementation of the KZG commitment scheme
70+
- Support for both single and batch openings/verifications
71+
72+
## API Usage
73+
74+
KZG commitments can be used to commit to polynomials and later prove evaluations at specific points. Here's how to use the KZG implementation in lambdaworks:
75+
76+
### Creating a KZG Instance
77+
78+
First, you need to load or create a Structured Reference String (SRS) and initialize the KZG instance:
79+
80+
```rust
81+
use lambdaworks_crypto::commitments::kzg::{KateZaveruchaGoldberg, StructuredReferenceString};
82+
use lambdaworks_crypto::commitments::traits::IsCommitmentScheme;
83+
use lambdaworks_math::elliptic_curve::short_weierstrass::curves::bls12_381::{
84+
curve::BLS12381Curve,
85+
default_types::{FrElement, FrField},
86+
pairing::BLS12381AtePairing,
87+
twist::BLS12381TwistCurve,
88+
};
89+
use lambdaworks_math::elliptic_curve::short_weierstrass::point::ShortWeierstrassProjectivePoint;
90+
91+
// Load SRS from a file
92+
let srs_file = "path/to/srs.bin";
93+
let srs = StructuredReferenceString::from_file(srs_file).unwrap();
94+
95+
// Create a KZG instance
96+
let kzg = KateZaveruchaGoldberg::<FrField, BLS12381AtePairing>::new(srs);
97+
```
98+
99+
### Committing to a Polynomial
100+
101+
To commit to a polynomial, you first create the polynomial and then use the `commit` method:
102+
103+
```rust
104+
use lambdaworks_math::field::element::FieldElement;
105+
use lambdaworks_math::polynomial::Polynomial;
106+
107+
// Create a polynomial p(x) = x + 1
108+
let p = Polynomial::<FrElement>::new(&[FieldElement::one(), FieldElement::one()]);
109+
110+
// Commit to the polynomial
111+
let commitment = kzg.commit(&p);
112+
```
113+
114+
### Generating and Verifying Proofs
115+
116+
To prove that a polynomial evaluates to a specific value at a specific point:
117+
118+
```rust
119+
// Choose a point to evaluate the polynomial
120+
let x = -FieldElement::one();
121+
122+
// Compute the evaluation
123+
let y = p.evaluate(&x); // Should be 0 for p(x) = x + 1 when x = -1
124+
125+
// Generate a proof for this evaluation
126+
let proof = kzg.open(&x, &y, &p);
127+
128+
// Verify the proof
129+
let is_valid = kzg.verify(&x, &y, &commitment, &proof);
130+
assert!(is_valid, "Proof verification failed");
131+
```
132+
133+
### Batch Operations
134+
135+
KZG supports batch operations for more efficient verification of multiple polynomial evaluations:
136+
137+
```rust
138+
// Create polynomials
139+
let p0 = Polynomial::<FrElement>::new(&[FieldElement::from(9000)]); // Constant polynomial
140+
let p1 = Polynomial::<FrElement>::new(&[
141+
FieldElement::from(1),
142+
FieldElement::from(2),
143+
-FieldElement::from(1),
144+
]); // p(x) = 1 + 2x - x²
145+
146+
// Commit to the polynomials
147+
let p0_commitment = kzg.commit(&p0);
148+
let p1_commitment = kzg.commit(&p1);
149+
150+
// Choose a point to evaluate the polynomials
151+
let x = FieldElement::from(3);
152+
153+
// Compute the evaluations
154+
let y0 = p0.evaluate(&x); // 9000
155+
let y1 = p1.evaluate(&x); // 1 + 2*3 - 3² = 1 + 6 - 9 = -2
156+
157+
// Generate a random field element for the batch proof
158+
let upsilon = &FieldElement::from(1); // In practice, use a random value
159+
160+
// Generate batch proof
161+
let proof = kzg.open_batch(&x, &[y0.clone(), y1.clone()], &[p0, p1], upsilon);
162+
163+
// Verify batch proof
164+
let is_valid = kzg.verify_batch(
165+
&x,
166+
&[y0, y1],
167+
&[p0_commitment, p1_commitment],
168+
&proof,
169+
upsilon
170+
);
171+
assert!(is_valid, "Batch proof verification failed");
172+
```
173+
174+
### Serialization and Deserialization
175+
176+
The SRS can be serialized and deserialized for storage and transmission:
177+
178+
```rust
179+
// Serialize the SRS
180+
let bytes = srs.as_bytes();
181+
182+
// Deserialize the SRS
183+
let deserialized_srs = StructuredReferenceString::<
184+
ShortWeierstrassProjectivePoint<BLS12381Curve>,
185+
ShortWeierstrassProjectivePoint<BLS12381TwistCurve>,
186+
>::deserialize(&bytes).unwrap();
187+
```
188+
63189
## References
64190

65191
- [Constantine](https://github.com/mratsim/constantine/blob/master/constantine/commitments/kzg.nim)
66192
- [EIP-4844](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-4844.md)
67193
- [KZG proof verification](https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.1/specs/deneb/polynomial-commitments.md#verify_kzg_proof_batch)
68194
- [Multiproofs KZG](https://dankradfeist.de/ethereum/2021/06/18/pcs-multiproofs.html)
69-
- [Fast amortized KZG proofs](https://eprint.iacr.org/2023/033)
195+
- [Fast amortized KZG proofs](https://eprint.iacr.org/2023/033)

crates/crypto/src/commitments/kzg.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -376,15 +376,15 @@ mod tests {
376376

377377
let x = FieldElement::from(3);
378378

379-
let p0 = Polynomial::<FrElement>::new(&[FieldElement::from(9000)]);
379+
let p0 = Polynomial::<FrElement>::new(&[FieldElement::from(9000)]); // Constant polynomial
380380
let p0_commitment: <BLS12381AtePairing as IsPairing>::G1Point = kzg.commit(&p0);
381381
let y0 = FieldElement::from(9000);
382382

383383
let p1 = Polynomial::<FrElement>::new(&[
384384
FieldElement::from(1),
385385
FieldElement::from(2),
386386
-FieldElement::from(1),
387-
]);
387+
]); // p(x) = 1 + 2x - x²
388388
let p1_commitment: <BLS12381AtePairing as IsPairing>::G1Point = kzg.commit(&p1);
389389
let y1 = p1.evaluate(&x);
390390

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
# Merkle Trees
2+
3+
A Merkle tree is a binary tree data structure where each leaf node contains the hash of a data block, and each non-leaf node contains the hash of its two child nodes. This structure allows for efficient and secure verification of content in large data structures.
4+
5+
## What is a Merkle Tree?
6+
7+
Merkle trees provide a way to efficiently verify the integrity of data. Here's how they work:
8+
9+
1. **Leaf Nodes**: Start by hashing each piece of data (e.g., files, transactions) to create the leaf nodes
10+
2. **Internal Nodes**: Each internal node is created by hashing the concatenation of its two child nodes
11+
3. **Root Hash**: The hash at the top of the tree (root) represents a cryptographic summary of all the data
12+
13+
For example, with 8 files (f₁, f₂, ..., f₈):
14+
- First, hash each file: h₁ = H(f₁), h₂ = H(f₂), ..., h₈ = H(f₈)
15+
- Then hash pairs: h₁₂ = H(h₁, h₂), h₃₄ = H(h₃, h₄), etc.
16+
- Continue until you reach the root: h₁₋₈ = H(h₁₋₄, h₅₋₈)
17+
18+
The power of Merkle trees comes from their ability to generate compact proofs. A **Merkle proof** for a specific piece of data consists of the minimal set of hashes needed to recompute the root hash. This allows verification that a piece of data belongs to the original set without needing all the data.
19+
20+
## Overview
21+
22+
The Merkle tree implementation in Lambdaworks provides:
23+
24+
- A generic `MerkleTree` structure that can work with different hash functions and data types
25+
- Support for generating and verifying inclusion proofs
26+
- Multiple backend implementations for different use cases
27+
- Serialization and deserialization of proofs
28+
- Optional parallel processing for improved performance
29+
30+
## Implementation
31+
32+
The implementation in this codebase includes:
33+
34+
- `MerkleTree`: The main Merkle tree data structure
35+
- `Proof`: Represents a Merkle proof for verifying inclusion of data
36+
- `IsMerkleTreeBackend`: A trait for implementing different backend strategies
37+
- Several backend implementations:
38+
- `FieldElementBackend`: For hashing field elements using various hash functions
39+
- `FieldElementVectorBackend`: For hashing vectors of field elements
40+
- `BatchPoseidonTree`: For batch hashing with Poseidon
41+
42+
## API Usage
43+
44+
### Creating a Merkle Tree
45+
46+
Here's a basic example of creating a Merkle tree with field elements:
47+
48+
```rust
49+
use lambdaworks_crypto::merkle_tree::{
50+
merkle::MerkleTree,
51+
backends::field_element::FieldElementBackend,
52+
};
53+
use lambdaworks_math::field::{
54+
element::FieldElement,
55+
fields::fft_friendly::stark_252_prime_field::Stark252PrimeField,
56+
};
57+
use sha3::Keccak256;
58+
59+
// Define the types we'll use
60+
type F = Stark252PrimeField;
61+
type FE = FieldElement<F>;
62+
63+
// Create some data
64+
let values: Vec<FE> = (1..6).map(FE::from).collect();
65+
66+
// Build the Merkle tree using Keccak256 as the hash function
67+
let merkle_tree = MerkleTree::<FieldElementBackend<F, Keccak256, 32>>::build(&values).unwrap();
68+
```
69+
70+
### Using BatchPoseidonTree for Efficient Hashing
71+
72+
The `BatchPoseidonTree` backend is specifically designed for efficient batch hashing using the Poseidon hash function, which is particularly useful in zero-knowledge proof systems. This backend provides optimized performance for vectors of field elements.
73+
74+
```rust
75+
use lambdaworks_crypto::merkle_tree::{
76+
merkle::MerkleTree,
77+
backends::field_element_vector::BatchPoseidonTree,
78+
};
79+
use lambdaworks_crypto::hash::poseidon::starknet::PoseidonCairoStark252;
80+
use lambdaworks_math::field::{
81+
element::FieldElement,
82+
fields::fft_friendly::stark_252_prime_field::Stark252PrimeField,
83+
};
84+
85+
// Define the types we'll use
86+
type F = Stark252PrimeField;
87+
type FE = FieldElement<F>;
88+
89+
// Create some data (vectors of field elements)
90+
let values: Vec<Vec<FE>> = vec![
91+
vec![FE::from(1), FE::from(2)],
92+
vec![FE::from(3), FE::from(4)],
93+
vec![FE::from(5), FE::from(6)],
94+
vec![FE::from(7), FE::from(8)],
95+
];
96+
97+
// Build the Merkle tree using Poseidon hash function
98+
let merkle_tree = MerkleTree::<BatchPoseidonTree<PoseidonCairoStark252>>::build(&values).unwrap();
99+
100+
// Generate a proof for a specific element
101+
let proof = merkle_tree.get_proof_by_pos(1).unwrap();
102+
103+
// Verify the proof
104+
let is_valid = proof.verify::<BatchPoseidonTree<PoseidonCairoStark252>>(
105+
&merkle_tree.root,
106+
1,
107+
&values[1]
108+
);
109+
110+
assert!(is_valid, "Proof verification failed");
111+
```
112+
113+
Key features of `BatchPoseidonTree`:
114+
115+
1. **Optimized for ZK Systems**: Poseidon is designed to be efficient in zero-knowledge proof systems, making this backend ideal for ZK applications.
116+
117+
2. **Batch Hashing**: The `hash_many` function efficiently processes multiple field elements at once.
118+
119+
3. **Field Element Compatibility**: Works with vectors of field elements, which is common in cryptographic protocols.
120+
121+
4. **Performance**: Poseidon offers better performance than traditional hash functions when working with field elements in ZK contexts.
122+
123+
### Generating Proofs
124+
125+
To generate a proof for a specific leaf:
126+
127+
```rust
128+
// Generate a proof for the first element (index 0)
129+
let proof = merkle_tree.get_proof_by_pos(0).unwrap();
130+
```
131+
132+
### Verifying Proofs
133+
134+
To verify that a value is included in the tree:
135+
136+
```rust
137+
// Verify the proof
138+
let is_valid = proof.verify::<FieldElementBackend<F, Keccak256, 32>>(
139+
&merkle_tree.root, // The Merkle root
140+
0, // The position of the leaf
141+
&values[0] // The value to verify
142+
);
143+
144+
assert!(is_valid, "Proof verification failed");
145+
```
146+
147+
### Working with Vectors of Field Elements
148+
149+
If you need to hash vectors of field elements:
150+
151+
```rust
152+
use lambdaworks_crypto::merkle_tree::{
153+
merkle::MerkleTree,
154+
backends::field_element_vector::FieldElementVectorBackend,
155+
};
156+
use lambdaworks_math::field::{
157+
element::FieldElement,
158+
fields::fft_friendly::stark_252_prime_field::Stark252PrimeField,
159+
};
160+
use sha3::Keccak256;
161+
162+
// Define the types we'll use
163+
type F = Stark252PrimeField;
164+
type FE = FieldElement<F>;
165+
166+
// Create some data (vectors of field elements)
167+
let values: Vec<Vec<FE>> = vec![
168+
vec![FE::from(1), FE::from(2)],
169+
vec![FE::from(3), FE::from(4)],
170+
vec![FE::from(5), FE::from(6)],
171+
];
172+
173+
// Build the Merkle tree
174+
let merkle_tree = MerkleTree::<FieldElementVectorBackend<F, Keccak256, 32>>::build(&values).unwrap();
175+
176+
// Generate a proof
177+
let proof = merkle_tree.get_proof_by_pos(0).unwrap();
178+
179+
// Verify the proof
180+
let is_valid = proof.verify::<FieldElementVectorBackend<F, Keccak256, 32>>(
181+
&merkle_tree.root,
182+
0,
183+
&values[0]
184+
);
185+
186+
assert!(is_valid, "Proof verification failed");
187+
```
188+
189+
## Serialization and Deserialization
190+
191+
Proofs can be serialized and deserialized for storage or transmission. Note that serialization requires the `alloc` feature to be enabled:
192+
193+
```rust
194+
// This requires the 'alloc' feature to be enabled
195+
use lambdaworks_crypto::merkle_tree::{
196+
merkle::MerkleTree,
197+
proof::Proof,
198+
// For testing, you might use a simpler backend like TestBackend
199+
};
200+
use lambdaworks_math::traits::{Deserializable, Serializable};
201+
202+
// Serialize the proof
203+
let serialized_proof = proof.serialize();
204+
205+
// Deserialize the proof
206+
let deserialized_proof = Proof::deserialize(&serialized_proof).unwrap();
207+
208+
// Verify the deserialized proof
209+
let is_valid = deserialized_proof.verify(
210+
&merkle_tree.root,
211+
0,
212+
&values[0]
213+
);
214+
215+
assert!(is_valid, "Deserialized proof verification failed");
216+
```
217+
218+
Note: The serialization example assumes that the type used for the Merkle tree nodes implements both `Serializable` and `Deserializable` traits.
219+
220+
## Performance Considerations
221+
222+
- The Merkle tree implementation automatically pads the input data to the next power of 2, which is required for a balanced binary tree
223+
- For large datasets, enable the `parallel` feature to use parallel processing for improved performance
224+
- Choose an appropriate backend based on your security and performance requirements:
225+
- Standard cryptographic hash functions (SHA-3, Keccak) provide strong security guarantees
226+
227+
228+
## References
229+
230+
- [Merkle Tree - Wikipedia](https://en.wikipedia.org/wiki/Merkle_tree)
231+
- [What is a Merkle Tree?](https://decentralizedthoughts.github.io/2020-12-22-what-is-a-merkle-tree/) - A comprehensive explanation of Merkle trees, proofs, and applications

0 commit comments

Comments
 (0)