Skip to main content

rings_node/extension/snark/
mod.rs

1//! SNARK Backend
2//! ================
3
4use std::sync::Arc;
5
6use bytes::Bytes;
7use dashmap::DashMap;
8use rings_core::dht::Did;
9use rings_derive::wasm_export;
10use rings_snark::circuit;
11use rings_snark::prelude::nova::provider;
12use rings_snark::prelude::nova::provider::hyperkzg;
13use rings_snark::prelude::nova::provider::ipa_pc;
14use rings_snark::prelude::nova::spartan;
15use rings_snark::prelude::nova::traits::snark::RelaxedR1CSSNARKTrait;
16use rings_snark::prelude::nova::traits::Engine;
17use rings_snark::snark::CompressedSNARK;
18use rings_snark::snark::ProverKey;
19use rings_snark::snark::PublicParams;
20use rings_snark::snark::VerifierKey;
21use rings_snark::snark::SNARK;
22use serde::Deserialize;
23use serde::Serialize;
24
25use super::types::snark::SNARKProofTask;
26use super::types::snark::SNARKTask;
27use super::types::snark::SNARKTaskMessage;
28use super::types::snark::SNARKVerifyTask;
29use crate::error::Error;
30use crate::error::Result;
31use crate::provider::Provider;
32
33type TaskId = uuid::Uuid;
34
35/// Namespace under which SNARK proof/verify tasks travel.
36pub const NAMESPACE: &str = "snark";
37
38#[cfg(feature = "browser")]
39pub mod browser;
40mod builder;
41mod protocol;
42
43pub use builder::SNARKTaskBuilder;
44pub use protocol::SnarkProtocol;
45
46/// Task Manageer of SNARK provider and verifier
47#[derive(Default, Clone)]
48pub struct SNARKTaskManager {
49    /// map of task_id and task
50    task: DashMap<TaskId, SNARKProofTask>,
51    /// map of task_id and result
52    verified: DashMap<TaskId, bool>,
53}
54
55/// SNARK message handler
56#[wasm_export]
57#[derive(Default, Clone)]
58pub struct SNARKBehaviour {
59    inner: Arc<SNARKTaskManager>,
60}
61
62impl std::ops::Deref for SNARKBehaviour {
63    type Target = Arc<SNARKTaskManager>;
64    fn deref(&self) -> &Self::Target {
65        &self.inner
66    }
67}
68
69impl AsRef<SNARKProofTask> for &SNARKProofTask {
70    fn as_ref(&self) -> &SNARKProofTask {
71        self
72    }
73}
74
75impl AsRef<SNARKVerifyTask> for &SNARKVerifyTask {
76    fn as_ref(&self) -> &SNARKVerifyTask {
77        self
78    }
79}
80
81impl SNARKBehaviour {
82    /// Generate proof task
83    pub fn gen_proof_task(circuits: Vec<Circuit>) -> Result<SNARKProofTask> {
84        SNARKTaskBuilder::gen_proof_task(circuits)
85    }
86
87    /// Generate a proof task and send it to did
88    pub async fn gen_and_send_proof_task(
89        &self,
90        provider: Arc<Provider>,
91        circuits: Vec<Circuit>,
92        did: Did,
93    ) -> Result<String> {
94        let task = Self::gen_proof_task(circuits)?;
95        self.send_proof_task(provider.clone(), &task, did).await
96    }
97
98    /// send proof task to did
99    ///
100    /// Sends a [`SNARKTaskMessage`] under the [`NAMESPACE`] envelope and records the
101    /// task locally so the matching verify reply can be checked. Same code path on
102    /// native and browser ([`Provider::send`]).
103    pub async fn send_proof_task(
104        &self,
105        provider: Arc<Provider>,
106        task_ref: impl AsRef<SNARKProofTask>,
107        did: Did,
108    ) -> Result<String> {
109        let task_id = uuid::Uuid::new_v4();
110        let task = task_ref.as_ref();
111        let msg = SNARKTaskMessage {
112            task_id,
113            task: SNARKTask::SNARKProof(Box::new(task.clone())),
114        };
115        let payload = bincode::serialize(&msg).map_err(|_| Error::EncodeError)?;
116        // Record the task *before* sending, so a fast verify reply cannot arrive before
117        // the verifier has the proof task to check against. Roll back if the send fails.
118        self.task.insert(task_id, task.clone());
119        if let Err(e) = provider.send(did, NAMESPACE, Bytes::from(payload)).await {
120            self.task.remove(&task_id);
121            return Err(e);
122        }
123        tracing::info!("sent proof request");
124        Ok(task_id.to_string())
125    }
126
127    /// Register the SNARK extension on a provider: the pure [`SnarkProtocol`] router paired
128    /// with its `SnarkShell` interpreter (which owns this behaviour's task store and runs
129    /// the proving/verification crypto). After this, inbound `snark` envelopes are
130    /// dispatched automatically; results are readable via
131    /// [`SNARKBehaviour::get_task_result`].
132    pub fn register(&self, provider: &Provider) -> Result<()> {
133        provider.register_protocol(SnarkProtocol, protocol::SnarkShell::new(self.inner.clone()))
134    }
135}
136
137/// The state of a submitted proof task, from the requester's side.
138///
139/// A bare `bool` collapses two distinct outcomes into `false` — "no result yet" and "a
140/// proof came back but failed verification" — so a pending task is indistinguishable from
141/// an invalid one. This makes the distinction explicit.
142#[wasm_export]
143#[derive(Clone, Copy, PartialEq, Eq, Debug)]
144pub enum ProofResult {
145    /// No result has returned yet (still proving / in flight).
146    Pending,
147    /// A proof returned and **verified**.
148    Verified,
149    /// A proof returned but **failed verification**.
150    Invalid,
151}
152
153#[wasm_export]
154impl SNARKBehaviour {
155    /// Current state of a submitted proof task: [`ProofResult::Pending`] until a result
156    /// returns, then [`ProofResult::Verified`] / [`ProofResult::Invalid`]. Unlike a bare
157    /// bool, this separates "not yet" from "verification failed".
158    pub fn get_task_result(&self, task_id: String) -> Result<ProofResult> {
159        let task_id = uuid::Uuid::parse_str(&task_id)?;
160        Ok(match self.inner.verified.get(&task_id) {
161            Some(v) if *v.value() => ProofResult::Verified,
162            Some(_) => ProofResult::Invalid,
163            None => ProofResult::Pending,
164        })
165    }
166}
167
168/// Types for circuit
169pub enum CircuitGenerator {
170    /// Circuit based on Vesta curve
171    Vesta(circuit::WasmCircuitGenerator<<provider::VestaEngine as Engine>::Scalar>),
172    /// Circuit based on pallas curve
173    Pallas(circuit::WasmCircuitGenerator<<provider::PallasEngine as Engine>::Scalar>),
174    /// Circuit based on KZG bn256
175    Bn256KZG(circuit::WasmCircuitGenerator<<provider::Bn256EngineKZG as Engine>::Scalar>),
176}
177
178/// Supported prime field
179#[wasm_export]
180#[derive(Clone)]
181pub enum SupportedPrimeField {
182    /// field of vesta curve
183    Vesta,
184    /// field of pallas curve
185    Pallas,
186    /// bn256 with kzg
187    Bn256KZG,
188}
189
190/// Input type
191#[wasm_export]
192#[derive(Deserialize, Serialize)]
193pub struct Input(Vec<(String, Vec<Field>)>);
194
195impl From<Vec<(String, Vec<Field>)>> for Input {
196    fn from(data: Vec<(String, Vec<Field>)>) -> Self {
197        Self(data)
198    }
199}
200
201#[wasm_export]
202impl Input {
203    /// serialize Input to json
204    pub fn to_json(&self) -> Result<String> {
205        Ok(serde_json::to_string(self)?)
206    }
207
208    /// deserialize Input from json
209    pub fn from_json(s: String) -> Result<Input> {
210        Ok(serde_json::from_str(&s)?)
211    }
212}
213
214impl std::ops::Deref for Input {
215    type Target = Vec<(String, Vec<Field>)>;
216    fn deref(&self) -> &Self::Target {
217        &self.0
218    }
219}
220
221impl IntoIterator for Input {
222    type Item = (String, Vec<Field>);
223    type IntoIter = <Vec<Self::Item> as IntoIterator>::IntoIter;
224    fn into_iter(self) -> Self::IntoIter {
225        self.0.into_iter()
226    }
227}
228
229impl<'a> IntoIterator for &'a Input {
230    type Item = <&'a Vec<(String, Vec<Field>)> as IntoIterator>::Item;
231    type IntoIter = <&'a Vec<(String, Vec<Field>)> as IntoIterator>::IntoIter;
232
233    fn into_iter(self) -> Self::IntoIter {
234        self.0.iter()
235    }
236}
237
238/// Circuit, it's a typeless wrapper of rings_snark circuit
239#[wasm_export]
240#[derive(Deserialize, Serialize)]
241pub struct Circuit {
242    inner: CircuitEnum,
243}
244
245/// Types of Circuit
246#[derive(Deserialize, Serialize)]
247pub enum CircuitEnum {
248    /// Based on vesta curve
249    Vesta(circuit::Circuit<<provider::VestaEngine as Engine>::Scalar>),
250    /// Based on pallas curve
251    Pallas(circuit::Circuit<<provider::PallasEngine as Engine>::Scalar>),
252    /// based on bn256 and KZG
253    Bn256KZG(circuit::Circuit<<provider::Bn256EngineKZG as Engine>::Scalar>),
254}
255
256#[wasm_export]
257impl Circuit {
258    /// serialize circuit to json
259    pub fn to_json(&self) -> Result<String> {
260        Ok(serde_json::to_string(self)?)
261    }
262
263    /// deserialize circuit from json
264    pub fn from_json(s: String) -> Result<Circuit> {
265        Ok(serde_json::from_str(&s)?)
266    }
267}
268
269/// Field type
270#[wasm_export]
271#[derive(Deserialize, Serialize)]
272pub struct Field {
273    value: FieldEnum,
274}
275
276/// Supported prime field
277#[derive(Deserialize, Serialize)]
278pub enum FieldEnum {
279    /// field of vesta curve
280    Vesta(<provider::VestaEngine as Engine>::Scalar),
281    /// field of pallas curve
282    Pallas(<provider::PallasEngine as Engine>::Scalar),
283    /// bn256 with kzg
284    Bn256KZG(<provider::Bn256EngineKZG as Engine>::Scalar),
285}
286
287#[wasm_export]
288impl Field {
289    /// create field from u64
290    pub fn from_u64(v: u64, ty: SupportedPrimeField) -> Self {
291        match ty {
292            SupportedPrimeField::Vesta => Self {
293                value: FieldEnum::Vesta(<provider::VestaEngine as Engine>::Scalar::from(v)),
294            },
295            SupportedPrimeField::Pallas => Self {
296                value: FieldEnum::Pallas(<provider::PallasEngine as Engine>::Scalar::from(v)),
297            },
298            SupportedPrimeField::Bn256KZG => Self {
299                value: FieldEnum::Bn256KZG(<provider::Bn256EngineKZG as Engine>::Scalar::from(v)),
300            },
301        }
302    }
303}
304
305/// SNARK Proof
306#[derive(Serialize, Deserialize)]
307pub struct SNARKProof<E1, E2, S1, S2>
308where
309    S1: RelaxedR1CSSNARKTrait<E1>,
310    S2: RelaxedR1CSSNARKTrait<E2>,
311    E1: Engine<Base = <E2 as Engine>::Scalar>,
312    E2: Engine<Base = <E1 as Engine>::Scalar>,
313{
314    /// verifier key of proof
315    #[serde(
316        serialize_with = "crate::util::serialize_forward",
317        deserialize_with = "crate::util::deserialize_forward"
318    )]
319    pub vk: VerifierKey<E1, E2, S1, S2>,
320    #[serde(
321        serialize_with = "crate::util::serialize_forward",
322        deserialize_with = "crate::util::deserialize_forward"
323    )]
324    /// compressed proof
325    pub proof: CompressedSNARK<E1, E2, S1, S2>,
326}
327
328/// SNARK proof generator, including setup, proof and verify
329#[derive(Serialize, Deserialize, Debug, Clone)]
330pub struct SNARKGenerator<E1, E2>
331where
332    E1: Engine<Base = <E2 as Engine>::Scalar>,
333    E2: Engine<Base = <E1 as Engine>::Scalar>,
334{
335    snark: SNARK<E1, E2>,
336    circuits: Vec<circuit::Circuit<<E1 as Engine>::Scalar>>,
337    pp: Arc<PublicParams<E1, E2>>,
338}
339
340impl SNARKProofTask {
341    /// Make snark proof task splitable
342    pub fn split(&self, n: usize) -> Vec<SNARKProofTask> {
343        match self {
344            SNARKProofTask::PallasVasta(g) => g
345                .split(n)
346                .into_iter()
347                .map(SNARKProofTask::PallasVasta)
348                .collect(),
349            SNARKProofTask::VastaPallas(g) => g
350                .split(n)
351                .into_iter()
352                .map(SNARKProofTask::VastaPallas)
353                .collect(),
354            SNARKProofTask::Bn256KZGGrumpkin(g) => g
355                .split(n)
356                .into_iter()
357                .map(SNARKProofTask::Bn256KZGGrumpkin)
358                .collect(),
359        }
360    }
361}
362
363impl<E1, E2> SNARKGenerator<E1, E2>
364where
365    E1: Engine<Base = <E2 as Engine>::Scalar>,
366    E2: Engine<Base = <E1 as Engine>::Scalar>,
367{
368    /// Setup snark, get pk and vk, if check set to true, it will check the folding is working correct
369    pub fn fold(&mut self, check: bool) -> Result<()> {
370        self.snark.fold_all(&self.pp, &self.circuits)?;
371        if check {
372            let steps = self.circuits.len();
373            let first_input = self.circuits.first().unwrap().get_public_inputs();
374            self.snark
375                .verify(&self.pp, steps, first_input, vec![E2::Scalar::from(0)])?;
376        }
377        Ok(())
378    }
379
380    /// Split a SNARKGenerator task to multiple, by split circuits into multiple
381    pub fn split(&self, n: usize) -> Vec<Self> {
382        let SNARKGenerator {
383            snark,
384            circuits,
385            pp,
386        } = self;
387
388        let mut split = Vec::new();
389        let chunk_size = circuits.len().div_ceil(n);
390
391        for circuit_chunk in circuits.chunks(chunk_size) {
392            let new_generator = SNARKGenerator {
393                snark: snark.clone(),
394                circuits: circuit_chunk.to_vec(),
395                pp: Arc::clone(pp),
396            };
397            split.push(new_generator);
398        }
399        split
400    }
401
402    /// setup compressed snark, get (pk, vk)
403    #[allow(clippy::type_complexity)]
404    pub fn setup<S1: RelaxedR1CSSNARKTrait<E1>, S2: RelaxedR1CSSNARKTrait<E2>>(
405        &self,
406    ) -> Result<(ProverKey<E1, E2, S1, S2>, VerifierKey<E1, E2, S1, S2>)> {
407        Ok(SNARK::<E1, E2>::compress_setup(&self.pp)?)
408    }
409
410    /// gen proof for compressed snark
411    pub fn prove<S1: RelaxedR1CSSNARKTrait<E1>, S2: RelaxedR1CSSNARKTrait<E2>>(
412        &self,
413        pk: impl AsRef<ProverKey<E1, E2, S1, S2>>,
414    ) -> Result<CompressedSNARK<E1, E2, S1, S2>> {
415        Ok(self.snark.compress_prove(&self.pp, pk)?)
416    }
417
418    /// verify a proof
419    #[allow(clippy::type_complexity)]
420    pub fn verify<S1: RelaxedR1CSSNARKTrait<E1>, S2: RelaxedR1CSSNARKTrait<E2>>(
421        &self,
422        proof: impl AsRef<CompressedSNARK<E1, E2, S1, S2>>,
423        vk: impl AsRef<VerifierKey<E1, E2, S1, S2>>,
424    ) -> Result<(Vec<E1::Scalar>, Vec<E2::Scalar>)> {
425        let steps = self.circuits.len();
426        let first_input = self.circuits.first().unwrap().get_public_inputs();
427        Ok(SNARK::<E1, E2>::compress_verify(
428            proof,
429            vk,
430            steps,
431            first_input,
432        )?)
433    }
434}
435
436impl SNARKBehaviour {
437    /// Handle proof task
438    pub fn handle_snark_proof_task<T: AsRef<SNARKProofTask>>(data: T) -> Result<SNARKVerifyTask> {
439        tracing::debug!("SNARK proof start");
440        let ret = match data.as_ref() {
441            SNARKProofTask::VastaPallas(s) => {
442                type E1 = provider::VestaEngine;
443                type E2 = provider::PallasEngine;
444                type EE1 = ipa_pc::EvaluationEngine<E1>;
445                type EE2 = ipa_pc::EvaluationEngine<E2>;
446                type S1 = spartan::snark::RelaxedR1CSSNARK<E1, EE1>;
447                type S2 = spartan::snark::RelaxedR1CSSNARK<E2, EE2>;
448                let mut snark = s.clone();
449                snark.fold(false)?;
450                let (pk, vk) = snark.setup()?;
451                let compressed_proof = snark.prove::<S1, S2>(&pk)?;
452                let proof = SNARKProof::<E1, E2, S1, S2> {
453                    vk,
454                    proof: compressed_proof,
455                };
456                Ok(SNARKVerifyTask::VastaPallas(serde_json::to_string(&proof)?))
457            }
458            SNARKProofTask::PallasVasta(s) => {
459                type E1 = provider::PallasEngine;
460                type E2 = provider::VestaEngine;
461                type EE1 = ipa_pc::EvaluationEngine<E1>;
462                type EE2 = ipa_pc::EvaluationEngine<E2>;
463                type S1 = spartan::snark::RelaxedR1CSSNARK<E1, EE1>;
464                type S2 = spartan::snark::RelaxedR1CSSNARK<E2, EE2>;
465                let mut snark = s.clone();
466                snark.fold(false)?;
467                let (pk, vk) = snark.setup()?;
468                let compressed_proof = snark.prove::<S1, S2>(&pk)?;
469                let proof = SNARKProof::<E1, E2, S1, S2> {
470                    vk,
471                    proof: compressed_proof,
472                };
473                Ok(SNARKVerifyTask::PallasVasta(serde_json::to_string(&proof)?))
474            }
475            SNARKProofTask::Bn256KZGGrumpkin(s) => {
476                type E1 = provider::Bn256EngineKZG;
477                type E2 = provider::GrumpkinEngine;
478                type EE1 = hyperkzg::EvaluationEngine<E1>;
479                type EE2 = ipa_pc::EvaluationEngine<E2>;
480                type S1 = spartan::snark::RelaxedR1CSSNARK<E1, EE1>; // non-preprocessing SNARK
481                type S2 = spartan::snark::RelaxedR1CSSNARK<E2, EE2>; // non-preprocessing SNARK
482                let mut snark = s.clone();
483                snark.fold(false)?;
484                let (pk, vk) = snark.setup()?;
485                let compressed_proof = snark.prove::<S1, S2>(&pk)?;
486                let proof = SNARKProof::<E1, E2, S1, S2> {
487                    vk,
488                    proof: compressed_proof,
489                };
490                Ok(SNARKVerifyTask::Bn256KZGGrumpkin(serde_json::to_string(
491                    &proof,
492                )?))
493            }
494        };
495        tracing::debug!("SNARK proof success");
496        ret
497    }
498
499    /// Handle verify task
500    pub fn handle_snark_verify_task<T: AsRef<SNARKVerifyTask>, F: AsRef<SNARKProofTask>>(
501        data: T,
502        snark: F,
503    ) -> Result<bool> {
504        tracing::debug!("SNARK verify start");
505        let snark = snark.as_ref();
506        let ret = match data.as_ref() {
507            SNARKVerifyTask::PallasVasta(p) => {
508                type E1 = provider::PallasEngine;
509                type E2 = provider::VestaEngine;
510                type EE1 = ipa_pc::EvaluationEngine<E1>;
511                type EE2 = ipa_pc::EvaluationEngine<E2>;
512                type S1 = spartan::snark::RelaxedR1CSSNARK<E1, EE1>;
513                type S2 = spartan::snark::RelaxedR1CSSNARK<E2, EE2>;
514                let proof = serde_json::from_str::<SNARKProof<E1, E2, S1, S2>>(p)?;
515                if let SNARKProofTask::PallasVasta(t) = snark {
516                    let ret = t.verify::<S1, S2>(proof.proof, proof.vk);
517                    Ok(ret.is_ok())
518                } else {
519                    Err(Error::SNARKCurveNotMatch())
520                }
521            }
522            SNARKVerifyTask::VastaPallas(p) => {
523                type E1 = provider::VestaEngine;
524                type E2 = provider::PallasEngine;
525                type EE1 = ipa_pc::EvaluationEngine<E1>;
526                type EE2 = ipa_pc::EvaluationEngine<E2>;
527                type S1 = spartan::snark::RelaxedR1CSSNARK<E1, EE1>;
528                type S2 = spartan::snark::RelaxedR1CSSNARK<E2, EE2>;
529                let proof = serde_json::from_str::<SNARKProof<E1, E2, S1, S2>>(p)?;
530                if let SNARKProofTask::VastaPallas(t) = snark {
531                    let ret = t.verify::<S1, S2>(proof.proof, proof.vk);
532                    Ok(ret.is_ok())
533                } else {
534                    Err(Error::SNARKCurveNotMatch())
535                }
536            }
537            SNARKVerifyTask::Bn256KZGGrumpkin(p) => {
538                type E1 = provider::Bn256EngineKZG;
539                type E2 = provider::GrumpkinEngine;
540                type EE1 = hyperkzg::EvaluationEngine<E1>;
541                type EE2 = ipa_pc::EvaluationEngine<E2>;
542                type S1 = spartan::snark::RelaxedR1CSSNARK<E1, EE1>; // non-preprocessing SNARK
543                type S2 = spartan::snark::RelaxedR1CSSNARK<E2, EE2>; // non-preprocessing SNARK
544                let proof = serde_json::from_str::<SNARKProof<E1, E2, S1, S2>>(p)?;
545                if let SNARKProofTask::Bn256KZGGrumpkin(t) = snark {
546                    let ret = t.verify::<S1, S2>(proof.proof, proof.vk);
547                    Ok(ret.is_ok())
548                } else {
549                    Err(Error::SNARKCurveNotMatch())
550                }
551            }
552        };
553        tracing::debug!("SNARK verify success");
554        ret
555    }
556}
557
558impl From<SNARKGenerator<provider::PallasEngine, provider::VestaEngine>> for SNARKProofTask {
559    fn from(snark: SNARKGenerator<provider::PallasEngine, provider::VestaEngine>) -> Self {
560        Self::PallasVasta(snark)
561    }
562}
563
564impl From<SNARKGenerator<provider::VestaEngine, provider::PallasEngine>> for SNARKProofTask {
565    fn from(snark: SNARKGenerator<provider::VestaEngine, provider::PallasEngine>) -> Self {
566        Self::VastaPallas(snark)
567    }
568}
569
570impl From<SNARKGenerator<provider::Bn256EngineKZG, provider::GrumpkinEngine>> for SNARKProofTask {
571    fn from(snark: SNARKGenerator<provider::Bn256EngineKZG, provider::GrumpkinEngine>) -> Self {
572        Self::Bn256KZGGrumpkin(snark)
573    }
574}