Skip to main content

rings_node/extension/snark/
protocol.rs

1#![warn(missing_docs)]
2//! The SNARK extension: the pure [`SnarkProtocol`] router and its `SnarkShell`
3//! interpreter, which owns the task store and runs the proving/verification crypto.
4//!
5//! Proving/verification is SNARK's **own** effect (`SnarkEffect::Prove`/`Verify`), not a
6//! core capability — it is feature-gated and a node need not enable it. The shell runs the
7//! heavy crypto and re-injects the result as a self-event, so `step` stays pure.
8
9use std::sync::Arc;
10
11use bytes::Bytes;
12use rings_core::dht::Did;
13use serde::Deserialize;
14use serde::Serialize;
15
16use super::SNARKBehaviour;
17use super::SNARKTaskManager;
18use super::TaskId;
19use super::NAMESPACE;
20use crate::error::Error;
21use crate::extension::ext::Ctx;
22use crate::extension::ext::Interpret;
23use crate::extension::ext::Protocol;
24use crate::extension::ext::Reject;
25use crate::extension::ext::Scope;
26use crate::extension::ext::Transition;
27use crate::extension::ext::Wire;
28use crate::extension::types::snark::SNARKProofTask;
29use crate::extension::types::snark::SNARKTask;
30use crate::extension::types::snark::SNARKTaskMessage;
31use crate::extension::types::snark::SNARKVerifyTask;
32
33/// The result of a SNARK compute job, re-injected as a self-event for the pure `step`.
34#[derive(Serialize, Deserialize)]
35pub enum ComputeResult {
36    /// A proof was produced; `step` sends the verify task back to `reply_to`.
37    Proved {
38        /// Task id.
39        task_id: TaskId,
40        /// DID to reply to.
41        reply_to: Did,
42        /// The produced verify task.
43        verify_task: SNARKVerifyTask,
44    },
45    /// Verification finished; the boolean is already stored in the task manager.
46    Verified {
47        /// Task id.
48        task_id: TaskId,
49    },
50}
51
52/// SNARK's typed input: a self re-injected [`ComputeResult`], or a network task message.
53pub enum SnarkEvent {
54    /// A compute result fed back into the router (provenance = self).
55    Result(ComputeResult),
56    /// A task message from an authenticated peer.
57    Task {
58        /// Authenticated sender (the prover to reply to, for a proof request).
59        from: Did,
60        /// The task message.
61        msg: SNARKTaskMessage,
62    },
63}
64
65/// SNARK's own effect algebra (run by `SnarkShell`).
66pub enum SnarkEffect {
67    /// Send a (verify) task message to a peer over the overlay.
68    SendTask {
69        /// Destination.
70        to: Did,
71        /// The message.
72        msg: SNARKTaskMessage,
73    },
74    /// Prove `task` (heavy crypto), then reply the verify task to `reply_to`.
75    Prove {
76        /// Task id.
77        task_id: TaskId,
78        /// DID to reply to.
79        reply_to: Did,
80        /// Proof task (boxed: far larger than the other variants).
81        task: Box<SNARKProofTask>,
82    },
83    /// Verify `verify_task` against the locally-stored proof task.
84    Verify {
85        /// Task id (used to look up the stored proof task).
86        task_id: TaskId,
87        /// Verify task received from the prover.
88        verify_task: SNARKVerifyTask,
89    },
90}
91
92/// SNARK relay protocol: a pure router over the `snark` namespace.
93///
94/// ```text
95///   step (Ctx (), Result(Proved id to vt)) ↦ ((), [SendTask to (Verify id vt)])
96///   step (Ctx (), Result(Verified id))     ↦ ((), ε)
97///   step (Ctx (), Task(from, Proof t))     ↦ ((), [Prove id from t])
98///   step (Ctx (), Task(from, Verify vt))   ↦ ((), [Verify id vt])
99/// ```
100///
101/// The heavy proving/verification is SNARK's own `SnarkEffect`, performed by
102/// `SnarkShell`; `step` stays pure.
103#[derive(Clone)]
104pub struct SnarkProtocol;
105
106impl Protocol for SnarkProtocol {
107    type State = ();
108    type Event = SnarkEvent;
109    type Effect = SnarkEffect;
110
111    fn namespace(&self) -> &str {
112        NAMESPACE
113    }
114
115    fn init(&self) {}
116
117    fn decode(&self, wire: Wire<'_>) -> Result<SnarkEvent, Reject> {
118        if wire.from == wire.me {
119            let result = bincode::deserialize::<ComputeResult>(wire.payload)
120                .map_err(|e| Reject(format!("bad snark result: {e}")))?;
121            Ok(SnarkEvent::Result(result))
122        } else {
123            let msg = bincode::deserialize::<SNARKTaskMessage>(wire.payload)
124                .map_err(|e| Reject(format!("bad snark task: {e}")))?;
125            Ok(SnarkEvent::Task {
126                from: wire.from,
127                msg,
128            })
129        }
130    }
131
132    fn step(&self, _ctx: Ctx<'_, ()>, event: SnarkEvent) -> Transition<(), SnarkEffect> {
133        match event {
134            SnarkEvent::Result(ComputeResult::Proved {
135                task_id,
136                reply_to,
137                verify_task,
138            }) => Transition::with((), vec![SnarkEffect::SendTask {
139                to: reply_to,
140                msg: SNARKTaskMessage {
141                    task_id,
142                    task: SNARKTask::SNARKVerify(verify_task),
143                },
144            }]),
145            SnarkEvent::Result(ComputeResult::Verified { .. }) => Transition::pure(()),
146            SnarkEvent::Task { from, msg } => match msg.task {
147                SNARKTask::SNARKProof(task) => Transition::with((), vec![SnarkEffect::Prove {
148                    task_id: msg.task_id,
149                    reply_to: from,
150                    task,
151                }]),
152                SNARKTask::SNARKVerify(verify_task) => {
153                    Transition::with((), vec![SnarkEffect::Verify {
154                        task_id: msg.task_id,
155                        verify_task,
156                    }])
157                }
158            },
159        }
160    }
161}
162
163/// SNARK's interpreter: owns the task store and runs the proving/verification crypto.
164pub struct SnarkShell {
165    manager: Arc<SNARKTaskManager>,
166}
167
168impl SnarkShell {
169    /// Build over a shared task store.
170    pub fn new(manager: Arc<SNARKTaskManager>) -> Self {
171        Self { manager }
172    }
173
174    /// Serialize a [`ComputeResult`] for re-injection as a self-event for the pure `step`. The
175    /// router re-delivers it to this same namespace with `from = this node`.
176    fn reinject(&self, result: &ComputeResult) -> crate::error::Result<Vec<Bytes>> {
177        let payload = bincode::serialize(result).map_err(|_| Error::EncodeError)?;
178        Ok(vec![Bytes::from(payload)])
179    }
180}
181
182#[cfg_attr(feature = "browser", async_trait::async_trait(?Send))]
183#[cfg_attr(not(feature = "browser"), async_trait::async_trait)]
184impl Interpret for SnarkShell {
185    type Effect = SnarkEffect;
186
187    async fn run(&self, scope: &Scope, effect: SnarkEffect) -> crate::error::Result<Vec<Bytes>> {
188        match effect {
189            SnarkEffect::SendTask { to, msg } => {
190                let payload = bincode::serialize(&msg).map_err(|_| Error::EncodeError)?;
191                scope.send(to, Bytes::from(payload)).await?;
192                Ok(Vec::new())
193            }
194            SnarkEffect::Prove {
195                task_id,
196                reply_to,
197                task,
198            } => {
199                let verify_task = SNARKBehaviour::handle_snark_proof_task(task.as_ref())?;
200                self.reinject(&ComputeResult::Proved {
201                    task_id,
202                    reply_to,
203                    verify_task,
204                })
205            }
206            SnarkEffect::Verify {
207                task_id,
208                verify_task,
209            } => {
210                let Some(task) = self.manager.task.get(&task_id) else {
211                    return Err(Error::ExtensionError(format!(
212                        "no pending SNARK proof task for {task_id}; cannot verify"
213                    )));
214                };
215                let verified =
216                    SNARKBehaviour::handle_snark_verify_task(&verify_task, task.value())?;
217                drop(task);
218                self.manager.verified.insert(task_id, verified);
219                self.reinject(&ComputeResult::Verified { task_id })
220            }
221        }
222    }
223}