Skip to content

Commit e360ec3

Browse files
committed
feat: submit zisk endpoint in gateway
1 parent 5f000bf commit e360ec3

5 files changed

Lines changed: 245 additions & 3 deletions

File tree

aggregation_mode/gateway/src/http.rs

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ use crate::{
2525
db::Db,
2626
helpers::get_time_left_day_formatted,
2727
metrics::GatewayMetrics,
28-
types::{GetReceiptsResponse, SubmitProofRequestRisc0, SubmitProofRequestSP1},
29-
verifiers::{verify_sp1_proof, VerificationError},
28+
types::{GetReceiptsResponse, SubmitProofRequestRisc0, SubmitProofRequestSP1, SubmitProofRequestZisk},
29+
verifiers::{verify_sp1_proof, verify_zisk_proof, VerificationError},
3030
};
3131

3232
#[derive(Clone, Debug)]
@@ -78,6 +78,7 @@ impl GatewayServer {
7878
.route("/receipts", web::get().to(Self::get_receipts))
7979
.route("/proof/sp1", web::post().to(Self::post_proof_sp1))
8080
.route("/proof/risc0", web::post().to(Self::post_proof_risc0))
81+
.route("/proof/zisk", web::post().to(Self::post_proof_zisk))
8182
.route("/quotas/{address}", web::get().to(Self::get_quotas))
8283
})
8384
.bind((self.config.ip.as_str(), port))
@@ -285,6 +286,142 @@ impl GatewayServer {
285286
HttpResponse::Ok().json(AppResponse::new_sucessfull(serde_json::json!({})))
286287
}
287288

289+
// Posts a Zisk proof to the gateway, recovering the address from the signature
290+
async fn post_proof_zisk(
291+
req: HttpRequest,
292+
MultipartForm(data): MultipartForm<SubmitProofRequestZisk>,
293+
) -> impl Responder {
294+
let Some(state) = req.app_data::<Data<GatewayServer>>() else {
295+
return HttpResponse::InternalServerError()
296+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
297+
};
298+
299+
let state = state.get_ref();
300+
let Ok(signature) = Signature::from_str(&data.signature_hex.0) else {
301+
return HttpResponse::InternalServerError()
302+
.json(AppResponse::new_unsucessfull("Invalid signature", 500));
303+
};
304+
305+
let Ok(proof_content) = tokio::fs::read(data.proof.file.path()).await else {
306+
return HttpResponse::InternalServerError()
307+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
308+
};
309+
310+
// reconstruct message and recover address
311+
let msg = agg_mode_sdk::gateway::types::SubmitZiskProofMessage::new(
312+
data.nonce.0,
313+
proof_content.clone(),
314+
);
315+
let Ok(recovered_address) =
316+
signature.recover_address_from_prehash(&msg.eip712_hash(&state.network).into())
317+
else {
318+
return HttpResponse::InternalServerError()
319+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
320+
};
321+
let recovered_address = recovered_address.to_string().to_lowercase();
322+
323+
// Checking if this address has submited more proofs than the ones allowed per day
324+
let Ok(daily_tasks_by_address) = state
325+
.db
326+
.get_daily_tasks_by_address(&recovered_address)
327+
.await
328+
else {
329+
return HttpResponse::InternalServerError()
330+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
331+
};
332+
333+
if daily_tasks_by_address >= state.config.max_daily_proofs_per_user {
334+
let formatted_time_left = get_time_left_day_formatted();
335+
336+
return HttpResponse::BadRequest().json(AppResponse::new_unsucessfull(
337+
format!(
338+
"Request denied: Query limit exceeded. Quotas renew in {formatted_time_left}"
339+
)
340+
.as_str(),
341+
400,
342+
));
343+
}
344+
345+
let Ok(count) = state.db.count_tasks_by_address(&recovered_address).await else {
346+
return HttpResponse::InternalServerError()
347+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
348+
};
349+
350+
if data.nonce.0 != (count as u64) {
351+
return HttpResponse::BadRequest().json(AppResponse::new_unsucessfull(
352+
&format!("Invalid nonce, expected nonce = {count}"),
353+
400,
354+
));
355+
}
356+
357+
let now_epoch = match SystemTime::now().duration_since(UNIX_EPOCH) {
358+
Ok(duration) => duration.as_secs(),
359+
Err(_) => {
360+
return HttpResponse::InternalServerError()
361+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
362+
}
363+
};
364+
365+
let has_payment = match state
366+
.db
367+
.has_active_payment_event(
368+
&recovered_address,
369+
BigDecimal::from_str(&now_epoch.to_string()).unwrap(),
370+
)
371+
.await
372+
{
373+
Ok(result) => result,
374+
Err(_) => {
375+
return HttpResponse::InternalServerError()
376+
.json(AppResponse::new_unsucessfull("Internal server error", 500));
377+
}
378+
};
379+
380+
if !has_payment {
381+
return HttpResponse::BadRequest().json(AppResponse::new_unsucessfull(
382+
"You have to pay before submitting a proof",
383+
400,
384+
));
385+
}
386+
387+
if let Err(e) = verify_zisk_proof(&proof_content) {
388+
let message = match e {
389+
VerificationError::InvalidProof => "Proof verification failed",
390+
VerificationError::UnsupportedProof => "Unsupported proof",
391+
};
392+
393+
return HttpResponse::BadRequest().json(AppResponse::new_unsucessfull(message, 400));
394+
};
395+
396+
let query_started_at = Instant::now();
397+
398+
match state
399+
.db
400+
.insert_task(
401+
&recovered_address,
402+
AggregationModeProvingSystem::ZISK.as_u16() as i32,
403+
&proof_content,
404+
&[], // Zisk proofs don't have a separate vk file
405+
None,
406+
data.nonce.0 as i64,
407+
)
408+
.await
409+
{
410+
Ok(task_id) => {
411+
let time_elapsed_db_call = query_started_at.elapsed();
412+
state
413+
.metrics
414+
.register_db_response_time_post("zisk-post", time_elapsed_db_call.as_secs_f64());
415+
416+
HttpResponse::Ok().json(AppResponse::new_sucessfull(
417+
serde_json::json!({ "task_id": task_id.to_string() }),
418+
))
419+
}
420+
Err(_) => HttpResponse::InternalServerError()
421+
.json(AppResponse::new_unsucessfull("Internal server error", 500)),
422+
}
423+
}
424+
288425
// Returns the last 100 receipt merkle proofs for the address received in the URL.
289426
// In case of also receiving a nonce on the query param, it returns only the merkle proof for that nonce.
290427
async fn get_receipts(

aggregation_mode/gateway/src/types.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,13 @@ pub(super) struct SubmitProofRequestRisc0 {
5454
pub _signature_hex: Text<String>,
5555
}
5656

57+
#[derive(Debug, MultipartForm)]
58+
pub(super) struct SubmitProofRequestZisk {
59+
pub nonce: Text<u64>,
60+
pub proof: TempFile,
61+
pub signature_hex: Text<String>,
62+
}
63+
5764
#[derive(Debug, Clone, sqlx::FromRow, sqlx::Type, serde::Serialize)]
5865
pub struct GetReceiptsResponse {
5966
pub status: TaskStatus,

aggregation_mode/gateway/src/verifiers.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,8 @@ pub fn verify_sp1_proof(
2525

2626
Ok(())
2727
}
28+
29+
/// TODO: implement Zisk proof verification
30+
pub fn verify_zisk_proof(_proof: &[u8]) -> Result<(), VerificationError> {
31+
Ok(())
32+
}

aggregation_mode/sdk/src/gateway/provider.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use sp1_sdk::{SP1ProofWithPublicValues, SP1VerifyingKey};
66
use crate::{
77
gateway::types::{
88
EmptyDataResponse, GatewayResponse, NonceResponse, ReceiptsQueryParams, ReceiptsResponse,
9-
SubmitProofResponse, SubmitSP1ProofMessage,
9+
SubmitProofResponse, SubmitSP1ProofMessage, SubmitZiskProofMessage,
1010
},
1111
types::Network,
1212
};
@@ -120,6 +120,36 @@ impl<S: Signer> AggregationModeGatewayProvider<S> {
120120
self.send_request(request).await
121121
}
122122

123+
pub async fn submit_zisk_proof(
124+
&self,
125+
proof: &[u8],
126+
) -> Result<GatewayResponse<SubmitProofResponse>, GatewayError> {
127+
let Some(signer) = &self.signer else {
128+
return Err(GatewayError::SignerNotConfigured);
129+
};
130+
let signer_address = signer.address().to_string();
131+
let nonce_response = self.get_nonce_for(signer_address).await?;
132+
let message = SubmitZiskProofMessage::new(nonce_response.data.nonce, proof.to_vec())
133+
.sign(signer, &self.network)
134+
.await
135+
.map_err(GatewayError::MessageSignature)?;
136+
137+
let form = multipart::Form::new()
138+
.text("nonce", message.nonce.to_string())
139+
.part(
140+
"proof",
141+
multipart::Part::bytes(message.proof).file_name("proof.bin"),
142+
)
143+
.text("signature_hex", hex::encode(message.signature));
144+
145+
let request = self
146+
.http_client
147+
.post(format!("{}/proof/zisk", self.gateway_url))
148+
.multipart(form);
149+
150+
self.send_request(request).await
151+
}
152+
123153
// TODO: verify proof from receipt merkle path
124154

125155
async fn send_request<T: DeserializeOwned>(

aggregation_mode/sdk/src/gateway/types.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,66 @@ impl SubmitSP1ProofMessage {
110110
Ok(self)
111111
}
112112
}
113+
114+
/// Message for submitting a Zisk proof.
115+
/// The proof bytes contain the rom_vk and public inputs.
116+
#[derive(Debug, Clone, Deserialize)]
117+
pub struct SubmitZiskProofMessage {
118+
pub nonce: u64,
119+
pub proof: Vec<u8>,
120+
pub signature: Vec<u8>,
121+
}
122+
123+
impl SubmitZiskProofMessage {
124+
pub fn new(nonce: u64, proof: Vec<u8>) -> Self {
125+
Self {
126+
nonce,
127+
proof,
128+
signature: vec![],
129+
}
130+
}
131+
132+
fn hash_msg(&self) -> [u8; 32] {
133+
let mut hasher = Keccak256::new();
134+
let mut output = [0u8; 32];
135+
136+
let nonce_bytes: [u8; 32] = U256::from_be_slice(&self.nonce.to_be_bytes()).to_be_bytes();
137+
138+
hasher.update(nonce_bytes);
139+
hasher.update(&self.proof);
140+
hasher.finalize_into_array(&mut output);
141+
output
142+
}
143+
144+
pub fn eip712_hash(&self, network: &Network) -> [u8; 32] {
145+
let domain_value = DynSolValue::Tuple(vec![
146+
DynSolValue::String("Aligned".to_string()),
147+
DynSolValue::String("1".to_string()),
148+
DynSolValue::Uint(U256::from(network.chain_id()), 256),
149+
]);
150+
151+
let message_value = DynSolValue::Tuple(vec![
152+
DynSolValue::FixedBytes(self.hash_msg().into(), 32),
153+
DynSolValue::Uint(U256::from(self.nonce), 256),
154+
]);
155+
156+
let encoded_domain = domain_value.abi_encode();
157+
let encoded_message = message_value.abi_encode();
158+
159+
let domain_separator = keccak256(&encoded_domain);
160+
let message_hash = keccak256(&encoded_message);
161+
162+
keccak256([&[0x19, 0x01], &domain_separator[..], &message_hash[..]].concat()).0
163+
}
164+
165+
pub async fn sign<S: Signer>(mut self, signer: &S, network: &Network) -> Result<Self, String> {
166+
let signature = signer
167+
.sign_hash(&self.eip712_hash(network).into())
168+
.await
169+
.map_err(|e| e.to_string())?;
170+
171+
self.signature = signature.as_bytes().to_vec();
172+
173+
Ok(self)
174+
}
175+
}

0 commit comments

Comments
 (0)