Skip to content

Commit 8019324

Browse files
feat(agg-mode): receive proofs via multipart form data instead of json in batcher (#2192)
1 parent daf0344 commit 8019324

File tree

14 files changed

+191
-33
lines changed

14 files changed

+191
-33
lines changed

Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,22 @@ agg_mode_batcher_start_local: agg_mode_run_migrations
323323
agg_mode_batcher_start_ethereum_package: agg_mode_run_migrations
324324
cargo run --manifest-path ./aggregation_mode/Cargo.toml --release --bin agg_mode_batcher -- config-files/config-agg-mode-batcher-ethereum-package.yaml
325325

326+
AGG_MODE_SENDER ?= 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
327+
agg_mode_batcher_send_payment:
328+
@cast send --value 1ether \
329+
0x922D6956C99E12DFeB3224DEA977D0939758A1Fe \
330+
--private-key 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
331+
332+
agg_mode_batcher_send_sp1_proof:
333+
@NONCE=$$(curl -s http://127.0.0.1:8089/nonce/0x70997970C51812dc3A010C7d01b50e0d17dc79C8 | jq -r '.data.nonce'); \
334+
curl -X POST \
335+
-H "Content-Type: multipart/form-data" \
336+
-F "nonce=$${NONCE}" \
337+
-F "proof=@scripts/test_files/sp1/sp1_fibonacci_5_0_0.proof" \
338+
-F "program_vk=@scripts/test_files/sp1/sp1_fibonacci_5_0_0_vk.bin" \
339+
-F "signature_hex=0x0" \
340+
http://127.0.0.1:8089/proof/sp1
341+
326342
__AGGREGATOR__: ## ____
327343

328344
aggregator_start: ## Start the Aggregator. Parameters: ENVIRONMENT=<devnet|testnet|mainnet>, AGG_CONFIG_FILE

aggregation_mode/Cargo.lock

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

aggregation_mode/batcher/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@ serde = { workspace = true }
88
serde_json = { workspace = true }
99
serde_yaml = { workspace = true }
1010
aligned-sdk = { workspace = true }
11+
sp1-sdk = { workspace = true }
1112
tracing = { version = "0.1", features = ["log"] }
1213
tracing-subscriber = { version = "0.3.0", features = ["env-filter"] }
14+
bincode = "1.3.3"
1315
actix-web = "4"
16+
actix-multipart = "0.7.2"
1417
alloy = { workspace = true }
1518
tokio = { version = "1", features = ["time"]}
1619
# TODO: enable tls

aggregation_mode/batcher/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ pub mod config;
22
pub mod db;
33
pub mod payments;
44
pub mod server;
5+
mod verifiers;

aggregation_mode/batcher/src/server/http.rs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ use std::{
33
time::{SystemTime, UNIX_EPOCH},
44
};
55

6+
use actix_multipart::form::MultipartForm;
67
use actix_web::{
78
web::{self, Data},
89
App, HttpRequest, HttpResponse, HttpServer, Responder,
910
};
1011
use aligned_sdk::aggregation_layer::AggregationModeProvingSystem;
12+
use sp1_sdk::{SP1ProofWithPublicValues, SP1VerifyingKey};
1113
use sqlx::types::BigDecimal;
1214

1315
use super::{
@@ -18,9 +20,8 @@ use super::{
1820
use crate::{
1921
config::Config,
2022
db::Db,
21-
server::types::{
22-
SubmitProofRequest, SubmitProofRequestMessageRisc0, SubmitProofRequestMessageSP1,
23-
},
23+
server::types::{SubmitProofRequestRisc0, SubmitProofRequestSP1},
24+
verifiers::{verify_sp1_proof, VerificationError},
2425
};
2526

2627
#[derive(Clone, Debug)]
@@ -62,7 +63,6 @@ impl BatcherServer {
6263
};
6364

6465
// TODO: validate valid ethereum address
65-
6666
let Some(state) = req.app_data::<Data<BatcherServer>>() else {
6767
return HttpResponse::InternalServerError()
6868
.json(AppResponse::new_unsucessfull("Internal server error", 500));
@@ -82,11 +82,8 @@ impl BatcherServer {
8282

8383
async fn post_proof_sp1(
8484
req: HttpRequest,
85-
body: web::Json<SubmitProofRequest<SubmitProofRequestMessageSP1>>,
85+
MultipartForm(data): MultipartForm<SubmitProofRequestSP1>,
8686
) -> impl Responder {
87-
let data = body.into_inner();
88-
89-
// TODO: validate signature
9087
let recovered_address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8".to_lowercase();
9188

9289
let Some(state) = req.app_data::<Data<BatcherServer>>() else {
@@ -100,7 +97,7 @@ impl BatcherServer {
10097
.json(AppResponse::new_unsucessfull("Internal server error", 500));
10198
};
10299

103-
if data.nonce != (count as u64) {
100+
if data.nonce.0 != (count as u64) {
104101
return HttpResponse::BadRequest().json(AppResponse::new_unsucessfull(
105102
&format!("Invalid nonce, expected nonce = {count}"),
106103
400,
@@ -138,15 +135,42 @@ impl BatcherServer {
138135
));
139136
}
140137

141-
// TODO: decode proof and validate it
138+
let Ok(proof_content) = tokio::fs::read(data.proof.file.path()).await else {
139+
return HttpResponse::InternalServerError()
140+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
141+
};
142+
143+
let Ok(proof) = bincode::deserialize::<SP1ProofWithPublicValues>(&proof_content) else {
144+
return HttpResponse::BadRequest()
145+
.json(AppResponse::new_unsucessfull("Invalid SP1 proof", 400));
146+
};
147+
148+
let Ok(vk_content) = tokio::fs::read(data.program_vk.file.path()).await else {
149+
return HttpResponse::InternalServerError()
150+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
151+
};
152+
153+
let Ok(vk) = bincode::deserialize::<SP1VerifyingKey>(&vk_content) else {
154+
return HttpResponse::BadRequest()
155+
.json(AppResponse::new_unsucessfull("Invalid vk", 400));
156+
};
157+
158+
if let Err(e) = verify_sp1_proof(&proof, &vk) {
159+
let message = match e {
160+
VerificationError::InvalidProof => "Proof verification failed",
161+
VerificationError::UnsupportedProof => "Unsupported proof",
162+
};
163+
164+
return HttpResponse::BadRequest().json(AppResponse::new_unsucessfull(message, 400));
165+
};
142166

143167
match state
144168
.db
145169
.insert_task(
146170
&recovered_address,
147171
AggregationModeProvingSystem::SP1.as_u16() as i32,
148-
&data.message.proof,
149-
&data.message.program_vk_commitment,
172+
&proof_content,
173+
&vk_content,
150174
None,
151175
)
152176
.await
@@ -162,7 +186,7 @@ impl BatcherServer {
162186
/// TODO: complete for risc0 (see `post_proof_sp1`)
163187
async fn post_proof_risc0(
164188
_req: HttpRequest,
165-
_body: web::Json<SubmitProofRequest<SubmitProofRequestMessageRisc0>>,
189+
MultipartForm(_): MultipartForm<SubmitProofRequestRisc0>,
166190
) -> impl Responder {
167191
HttpResponse::Ok().json(AppResponse::new_sucessfull(serde_json::json!({})))
168192
}

aggregation_mode/batcher/src/server/types.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use actix_multipart::form::{tempfile::TempFile, text::Text, MultipartForm};
12
use serde::{Deserialize, Serialize};
23
use serde_json::Value;
34

@@ -32,21 +33,18 @@ pub(super) struct GetProofMerklePathQueryParams {
3233
pub id: Option<String>,
3334
}
3435

35-
#[derive(Serialize, Deserialize, Clone, Debug)]
36-
pub(super) struct SubmitProofRequest<T> {
37-
pub nonce: u64,
38-
pub message: T,
39-
pub signature: String,
40-
}
41-
#[derive(Serialize, Deserialize, Clone, Debug)]
42-
pub(super) struct SubmitProofRequestMessageSP1 {
43-
pub proof: Vec<u8>,
44-
pub program_vk_commitment: Vec<u8>,
36+
#[derive(Debug, MultipartForm)]
37+
pub(super) struct SubmitProofRequestSP1 {
38+
pub nonce: Text<u64>,
39+
pub proof: TempFile,
40+
pub program_vk: TempFile,
41+
pub _signature_hex: Text<String>,
4542
}
4643

47-
#[derive(Serialize, Deserialize, Clone, Debug)]
48-
pub(super) struct SubmitProofRequestMessageRisc0 {
49-
pub proof: Vec<u8>,
50-
pub program_image_id: Vec<u8>,
51-
pub public_inputs: Vec<u8>,
44+
#[derive(Debug, MultipartForm)]
45+
pub(super) struct SubmitProofRequestRisc0 {
46+
pub _nonce: Text<u64>,
47+
pub _risc0_receipt: TempFile,
48+
pub _program_image_id_hex: Text<String>,
49+
pub _signature_hex: Text<String>,
5250
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use std::sync::LazyLock;
2+
3+
use sp1_sdk::{CpuProver, Prover, ProverClient, SP1ProofWithPublicValues, SP1VerifyingKey};
4+
5+
static SP1_PROVER_CLIENT_CPU: LazyLock<CpuProver> =
6+
LazyLock::new(|| ProverClient::builder().cpu().build());
7+
8+
pub enum VerificationError {
9+
InvalidProof,
10+
UnsupportedProof,
11+
}
12+
13+
pub fn verify_sp1_proof(
14+
proof: &SP1ProofWithPublicValues,
15+
vk: &SP1VerifyingKey,
16+
) -> Result<(), VerificationError> {
17+
let client = &*SP1_PROVER_CLIENT_CPU;
18+
19+
match proof.proof {
20+
sp1_sdk::SP1Proof::Compressed(_) => client
21+
.verify(proof, vk)
22+
.map_err(|_| VerificationError::InvalidProof),
23+
_ => Err(VerificationError::UnsupportedProof),
24+
}?;
25+
26+
Ok(())
27+
}

scripts/test_files/sp1/fibonacci_proof_generator/script/Cargo.lock

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

scripts/test_files/sp1/fibonacci_proof_generator/script/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = "2021"
66

77
[dependencies]
88
sp1-sdk = { git = "https://github.com/succinctlabs/sp1.git", rev = "v5.0.0" }
9+
bincode = "1.3.3"
910

1011
[build-dependencies]
1112
sp1-helper = { git = "https://github.com/succinctlabs/sp1.git", rev = "v5.0.0" }

scripts/test_files/sp1/fibonacci_proof_generator/script/src/main.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,23 @@ fn main() {
3939
let proof_file_path = format!("../../sp1_fibonacci_{}.proof", SP1_VERSION);
4040
proof.save(proof_file_path).expect("saving proof failed");
4141

42-
std::fs::write(format!("../../sp1_fibonacci_{}.pub", SP1_VERSION), proof.public_values)
43-
.expect("failed to save public inputs");
42+
std::fs::write(
43+
format!("../../sp1_fibonacci_{}.pub", SP1_VERSION),
44+
proof.public_values,
45+
)
46+
.expect("failed to save public inputs");
4447

45-
std::fs::write(format!("../../sp1_fibonacci_{}.vk", SP1_VERSION), vk.hash_bytes())
46-
.expect("failed to save vk hash");
48+
std::fs::write(
49+
format!("../../sp1_fibonacci_{}.vk", SP1_VERSION),
50+
vk.hash_bytes(),
51+
)
52+
.expect("failed to save vk hash");
53+
54+
std::fs::write(
55+
format!("../../sp1_fibonacci_{}_vk.bin", SP1_VERSION),
56+
bincode::serialize(&vk).unwrap()
57+
)
58+
.expect("failed to save vk bin");
4759

4860
let elf_file_path = format!("../../sp1_fibonacci_{}.elf", SP1_VERSION);
4961
let mut file = std::fs::File::create(elf_file_path).unwrap();

0 commit comments

Comments
 (0)