Skip to content

Commit 5afbf67

Browse files
committed
feat: split aggregation in two programs
1 parent 4f778ad commit 5afbf67

File tree

11 files changed

+195
-161
lines changed

11 files changed

+195
-161
lines changed

aggregation_mode/aggregation_programs/risc0/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] }
1515
path = "./src/lib.rs"
1616

1717
[[bin]]
18-
name = "risc0_aggregator_program"
19-
path = "./src/main.rs"
18+
name = "risc0_chunk_aggregator_program"
19+
path = "./bin/chunk_aggregator.rs"
20+
21+
[[bin]]
22+
name = "risc0_root_aggregator_program"
23+
path = "./bin/root_aggregator.rs"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#![no_main]
2+
3+
use risc0_aggregation_program::{compute_merkle_root, Input, Risc0ImageIdAndPubInputs};
4+
use risc0_zkvm::guest::env;
5+
6+
risc0_zkvm::guest::entry!(main);
7+
8+
fn main() {
9+
let input = env::read::<Input>();
10+
11+
let mut proofs_hash: Vec<[u8; 32]> = vec![];
12+
13+
for proof in &input.proofs_image_id_and_pub_inputs {
14+
proofs_hash.push(proof.commitment());
15+
env::verify(proof.image_id.clone(), &proof.public_inputs)
16+
.expect("proof to be verified correctly");
17+
}
18+
19+
let merkle_root = compute_merkle_root(proofs_hash);
20+
21+
env::commit_slice(&merkle_root);
22+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![no_main]
2+
3+
use risc0_aggregation_program::{compute_merkle_root, Input, Risc0ImageIdAndPubInputs};
4+
use risc0_zkvm::guest::env;
5+
6+
risc0_zkvm::guest::entry!(main);
7+
8+
fn main() {
9+
let input = env::read::<Input>();
10+
11+
let mut chunks_merkle_root: Vec<[u8; 32]> = vec![];
12+
13+
for Risc0ImageIdAndPubInputs {
14+
image_id,
15+
public_inputs,
16+
} in &input.proofs_image_id_and_pub_inputs
17+
{
18+
let merkle_root: [u8; 32] = public_inputs
19+
.clone()
20+
.try_into()
21+
.expect("Public input to be the chunk merkle root");
22+
chunks_merkle_root.push(merkle_root);
23+
env::verify(image_id.clone(), &public_inputs).expect("proof to be verified correctly");
24+
}
25+
26+
let merkle_root = compute_merkle_root(chunks_merkle_root);
27+
28+
env::commit_slice(&merkle_root);
29+
}

aggregation_mode/aggregation_programs/risc0/src/lib.rs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,46 @@ pub struct Risc0ImageIdAndPubInputs {
88
}
99

1010
impl Risc0ImageIdAndPubInputs {
11-
pub fn commitment(&self, is_aggregated_chunk: bool) -> [u8; 32] {
12-
// If the proof is the aggregation of a chunk
13-
// the commitment is simply the public input (the merkle root of the chunk)
14-
if is_aggregated_chunk {
15-
self.public_inputs.clone().try_into().unwrap()
16-
} else {
17-
let mut hasher = Keccak::v256();
18-
for &word in &self.image_id {
19-
hasher.update(&word.to_be_bytes());
20-
}
21-
hasher.update(&self.public_inputs);
22-
23-
let mut hash = [0u8; 32];
24-
hasher.finalize(&mut hash);
25-
hash
11+
pub fn commitment(&self) -> [u8; 32] {
12+
let mut hasher = Keccak::v256();
13+
for &word in &self.image_id {
14+
hasher.update(&word.to_be_bytes());
2615
}
16+
hasher.update(&self.public_inputs);
17+
18+
let mut hash = [0u8; 32];
19+
hasher.finalize(&mut hash);
20+
hash
2721
}
2822
}
2923

3024
#[derive(Serialize, Deserialize)]
3125
pub struct Input {
3226
pub proofs_image_id_and_pub_inputs: Vec<Risc0ImageIdAndPubInputs>,
33-
pub is_aggregated_chunk: bool,
27+
}
28+
29+
fn combine_hashes(hash_a: &[u8; 32], hash_b: &[u8; 32]) -> [u8; 32] {
30+
let mut hasher = Keccak::v256();
31+
hasher.update(hash_a);
32+
hasher.update(hash_b);
33+
34+
let mut hash = [0u8; 32];
35+
hasher.finalize(&mut hash);
36+
hash
37+
}
38+
39+
/// Computes the merkle root for the given proofs
40+
pub fn compute_merkle_root(mut leaves: Vec<[u8; 32]>) -> [u8; 32] {
41+
while leaves.len() > 1 {
42+
leaves = leaves
43+
.chunks(2)
44+
.map(|chunk| match chunk {
45+
[a, b] => combine_hashes(&a, &b),
46+
[a] => combine_hashes(&a, &a),
47+
_ => panic!("Unexpected chunk size in leaves"),
48+
})
49+
.collect()
50+
}
51+
52+
leaves[0]
3453
}

aggregation_mode/aggregation_programs/risc0/src/main.rs

Lines changed: 0 additions & 67 deletions
This file was deleted.

aggregation_mode/aggregation_programs/sp1/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@ serde_json = "1.0.117"
1515
path = "./src/lib.rs"
1616

1717
[[bin]]
18-
name = "sp1_aggregator_program"
19-
path = "./src/main.rs"
18+
name = "sp1_chunk_aggregator_program"
19+
path = "./bin/chunk_aggregator.rs"
20+
21+
[[bin]]
22+
name = "sp1_root_aggregator_program"
23+
path = "./bin/root_aggregator.rs"
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#![no_main]
2+
sp1_zkvm::entrypoint!(main);
3+
4+
use sha2::{Digest, Sha256};
5+
use sp1_aggregation_program::{compute_merkle_root, Input};
6+
7+
pub fn main() {
8+
let input = sp1_zkvm::io::read::<Input>();
9+
10+
let mut proofs_commitment: Vec<[u8; 32]> = vec![];
11+
12+
// Verify the proofs.
13+
for proof in input.proofs_vk_and_pub_inputs.iter() {
14+
let vkey = proof.vk;
15+
let public_values = &proof.public_inputs;
16+
let public_values_digest = Sha256::digest(public_values);
17+
18+
proofs_commitment.push(proof.commitment());
19+
20+
sp1_zkvm::lib::verify::verify_sp1_proof(&vkey, &public_values_digest.into());
21+
}
22+
23+
let merkle_root = compute_merkle_root(proofs_commitment);
24+
25+
sp1_zkvm::io::commit_slice(&merkle_root);
26+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#![no_main]
2+
sp1_zkvm::entrypoint!(main);
3+
4+
use sha2::{Digest, Sha256};
5+
use sp1_aggregation_program::{compute_merkle_root, Input};
6+
7+
pub const LEAVES_AGG_PROGRAM_VK_HASH: [u32; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
8+
9+
pub fn main() {
10+
let input = sp1_zkvm::io::read::<Input>();
11+
12+
let mut proofs_hash: Vec<[u8; 32]> = vec![];
13+
14+
// Verify the proofs.
15+
for proof in input.proofs_vk_and_pub_inputs.iter() {
16+
let vkey = proof.vk;
17+
let public_values_digest = Sha256::digest(&proof.public_inputs);
18+
19+
// Ensure the aggregated chunk originates from the L1 aggregation program.
20+
// This validation step guarantees that the proof was genuinely verified
21+
// by this program. Without this check, a different program using the
22+
// same public inputs could bypass verification.
23+
assert!(proof.vk == LEAVES_AGG_PROGRAM_VK_HASH);
24+
25+
let merkle_root: [u8; 32] = proof
26+
.public_inputs
27+
.clone()
28+
.try_into()
29+
.expect("Public input to be the hash of the chunk tree");
30+
proofs_hash.push(merkle_root);
31+
32+
sp1_zkvm::lib::verify::verify_sp1_proof(&vkey, &public_values_digest.into());
33+
}
34+
35+
let merkle_root = compute_merkle_root(proofs_hash);
36+
37+
sp1_zkvm::io::commit_slice(&merkle_root);
38+
}

aggregation_mode/aggregation_programs/sp1/src/lib.rs

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,40 @@ pub struct SP1VkAndPubInputs {
88
}
99

1010
impl SP1VkAndPubInputs {
11-
pub fn commitment(&self, is_aggregated_chunk: bool) -> [u8; 32] {
12-
// If the proof is the aggregation of a chunk
13-
// the commitment is simply the public input (the merkle root of the chunk)
14-
if is_aggregated_chunk {
15-
self.public_inputs.clone().try_into().unwrap()
16-
} else {
17-
let mut hasher = Keccak256::new();
18-
for &word in &self.vk {
19-
hasher.update(word.to_be_bytes());
20-
}
21-
hasher.update(&self.public_inputs);
22-
hasher.finalize().into()
11+
pub fn commitment(&self) -> [u8; 32] {
12+
let mut hasher = Keccak256::new();
13+
for &word in &self.vk {
14+
hasher.update(word.to_be_bytes());
2315
}
16+
hasher.update(&self.public_inputs);
17+
hasher.finalize().into()
2418
}
2519
}
2620

2721
#[derive(Serialize, Deserialize)]
2822
pub struct Input {
2923
pub proofs_vk_and_pub_inputs: Vec<SP1VkAndPubInputs>,
30-
pub is_aggregated_chunk: bool,
24+
}
25+
26+
fn combine_hashes(hash_a: &[u8; 32], hash_b: &[u8; 32]) -> [u8; 32] {
27+
let mut hasher = Keccak256::new();
28+
hasher.update(hash_a);
29+
hasher.update(hash_b);
30+
hasher.finalize().into()
31+
}
32+
33+
/// Computes the merkle root for the given proofs
34+
pub fn compute_merkle_root(mut leaves_hash: Vec<[u8; 32]>) -> [u8; 32] {
35+
while leaves_hash.len() > 1 {
36+
leaves_hash = leaves_hash
37+
.chunks(2)
38+
.map(|chunk| match chunk {
39+
[a, b] => combine_hashes(&a, &b),
40+
[a] => combine_hashes(&a, &a),
41+
_ => panic!("Unexpected chunk size in leaves"),
42+
})
43+
.collect()
44+
}
45+
46+
leaves_hash[0]
3147
}

aggregation_mode/aggregation_programs/sp1/src/main.rs

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)