Skip to main content

darkpool_client/
proof_inputs.rs

1//! Typed structs for ZK proof generation, formatted for `NoirProver`.
2
3use crate::crypto_helpers::to_noir_hex;
4use ethers::types::{Address, U256};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8pub trait ProverInput {
9    /// Flat map of field name -> hex value for Prover.toml generation.
10    fn to_prover_map(&self) -> HashMap<String, String>;
11}
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct NotePlaintext {
15    pub value: U256,
16    pub asset_id: U256,
17    pub secret: U256,
18    pub nullifier: U256,
19    pub timelock: U256,
20    pub hashlock: U256,
21}
22
23impl NotePlaintext {
24    #[must_use]
25    #[allow(clippy::expect_used)]
26    pub fn random(value: U256, asset: Address) -> Self {
27        use rand::RngCore;
28        let mut rng = rand::rngs::OsRng;
29
30        let modulus = U256::from_dec_str(
31            "21888242871839275222246405745257275088548364400416034343698204186575808495617",
32        )
33        .expect("BN254 scalar field modulus is a valid decimal string");
34
35        let mut secret_bytes = [0u8; 32];
36        rng.fill_bytes(&mut secret_bytes);
37        let secret = U256::from_big_endian(&secret_bytes) % modulus;
38
39        let mut nullifier_bytes = [0u8; 32];
40        rng.fill_bytes(&mut nullifier_bytes);
41        let nullifier = U256::from_big_endian(&nullifier_bytes) % modulus;
42
43        Self {
44            value,
45            asset_id: address_to_field(asset),
46            secret,
47            nullifier,
48            timelock: U256::zero(),
49            hashlock: U256::zero(),
50        }
51    }
52
53    pub fn add_to_map(&self, map: &mut HashMap<String, String>, prefix: &str) {
54        map.insert(format!("{prefix}.value"), to_noir_hex(self.value));
55        map.insert(format!("{prefix}.asset_id"), to_noir_hex(self.asset_id));
56        map.insert(format!("{prefix}.secret"), to_noir_hex(self.secret));
57        map.insert(format!("{prefix}.nullifier"), to_noir_hex(self.nullifier));
58        map.insert(format!("{prefix}.timelock"), to_noir_hex(self.timelock));
59        map.insert(format!("{prefix}.hashlock"), to_noir_hex(self.hashlock));
60    }
61}
62
63pub type CompliancePk = (U256, U256);
64
65#[derive(Debug, Clone)]
66pub struct DepositInputs {
67    pub note_plaintext: NotePlaintext,
68    pub ephemeral_sk: U256,
69    pub compliance_pk: CompliancePk,
70}
71
72impl DepositInputs {
73    #[must_use]
74    pub fn new(note: NotePlaintext, ephemeral_sk: U256, compliance_pk: CompliancePk) -> Self {
75        Self {
76            note_plaintext: note,
77            ephemeral_sk,
78            compliance_pk,
79        }
80    }
81}
82
83impl ProverInput for DepositInputs {
84    fn to_prover_map(&self) -> HashMap<String, String> {
85        let mut map = HashMap::new();
86
87        map.insert("ephemeral_sk".into(), to_noir_hex(self.ephemeral_sk));
88        self.note_plaintext.add_to_map(&mut map, "note_plaintext");
89
90        map.insert(
91            "compliance_pubkey_x".into(),
92            to_noir_hex(self.compliance_pk.0),
93        );
94        map.insert(
95            "compliance_pubkey_y".into(),
96            to_noir_hex(self.compliance_pk.1),
97        );
98
99        map
100    }
101}
102
103#[derive(Debug, Clone)]
104pub struct WithdrawInputs {
105    pub withdraw_value: U256,
106    pub recipient: Address,
107    pub merkle_root: U256,
108    pub current_timestamp: u64,
109    pub intent_hash: U256,
110    pub compliance_pk: CompliancePk,
111
112    // Old note being spent
113    pub old_note: NotePlaintext,
114    pub old_shared_secret: U256,
115    pub old_note_index: u64,
116    pub old_note_path: Vec<U256>,
117    pub hashlock_preimage: U256,
118
119    // Change note
120    pub change_note: NotePlaintext,
121    pub change_ephemeral_sk: U256,
122}
123
124impl ProverInput for WithdrawInputs {
125    fn to_prover_map(&self) -> HashMap<String, String> {
126        let mut map = HashMap::new();
127
128        map.insert("withdraw_value".into(), to_noir_hex(self.withdraw_value));
129        map.insert(
130            "_recipient".into(),
131            to_noir_hex(address_to_field(self.recipient)),
132        );
133        map.insert("merkle_root".into(), to_noir_hex(self.merkle_root));
134        map.insert(
135            "current_timestamp".into(),
136            to_noir_hex(U256::from(self.current_timestamp)),
137        );
138        map.insert("_intent_hash".into(), to_noir_hex(self.intent_hash));
139        map.insert(
140            "compliance_pubkey_x".into(),
141            to_noir_hex(self.compliance_pk.0),
142        );
143        map.insert(
144            "compliance_pubkey_y".into(),
145            to_noir_hex(self.compliance_pk.1),
146        );
147
148        self.old_note.add_to_map(&mut map, "old_note");
149        map.insert(
150            "old_shared_secret".into(),
151            to_noir_hex(self.old_shared_secret),
152        );
153        map.insert(
154            "old_note_index".into(),
155            to_noir_hex(U256::from(self.old_note_index)),
156        );
157        map.insert(
158            "old_note_path".into(),
159            format_path_array(&self.old_note_path),
160        );
161        map.insert(
162            "hashlock_preimage".into(),
163            to_noir_hex(self.hashlock_preimage),
164        );
165
166        self.change_note.add_to_map(&mut map, "change_note");
167        map.insert(
168            "change_ephemeral_sk".into(),
169            to_noir_hex(self.change_ephemeral_sk),
170        );
171
172        map
173    }
174}
175
176#[derive(Debug, Clone)]
177pub struct DLEQProof {
178    pub u: (U256, U256),
179    pub v: (U256, U256),
180    pub z: U256,
181}
182
183impl DLEQProof {
184    pub fn add_to_map(&self, map: &mut HashMap<String, String>, prefix: &str) {
185        map.insert(format!("{prefix}.U.x"), to_noir_hex(self.u.0));
186        map.insert(format!("{prefix}.U.y"), to_noir_hex(self.u.1));
187        map.insert(format!("{prefix}.V.x"), to_noir_hex(self.v.0));
188        map.insert(format!("{prefix}.V.y"), to_noir_hex(self.v.1));
189        map.insert(format!("{prefix}.z"), to_noir_hex(self.z));
190    }
191}
192
193#[derive(Debug, Clone)]
194pub struct TransferInputs {
195    pub merkle_root: U256,
196    pub current_timestamp: u64,
197    pub compliance_pk: CompliancePk,
198
199    // Recipient BJJ points
200    pub recipient_b: (U256, U256), // BabyJubJub Public Key
201    pub recipient_p: (U256, U256), // Public Key on scalar field
202    pub recipient_proof: DLEQProof,
203
204    // Old note being spent
205    pub old_note: NotePlaintext,
206    pub old_shared_secret: U256,
207    pub old_note_index: u64,
208    pub old_note_path: Vec<U256>,
209    pub hashlock_preimage: U256,
210
211    // Memo note (to recipient)
212    pub memo_note: NotePlaintext,
213    pub memo_ephemeral_sk: U256,
214
215    // Change note (to self)
216    pub change_note: NotePlaintext,
217    pub change_ephemeral_sk: U256,
218}
219
220impl ProverInput for TransferInputs {
221    fn to_prover_map(&self) -> HashMap<String, String> {
222        let mut map = HashMap::new();
223
224        map.insert("merkle_root".into(), to_noir_hex(self.merkle_root));
225        map.insert(
226            "current_timestamp".into(),
227            to_noir_hex(U256::from(self.current_timestamp)),
228        );
229        map.insert(
230            "compliance_pubkey_x".into(),
231            to_noir_hex(self.compliance_pk.0),
232        );
233        map.insert(
234            "compliance_pubkey_y".into(),
235            to_noir_hex(self.compliance_pk.1),
236        );
237
238        map.insert("recipient_B.x".into(), to_noir_hex(self.recipient_b.0));
239        map.insert("recipient_B.y".into(), to_noir_hex(self.recipient_b.1));
240        map.insert("recipient_P.x".into(), to_noir_hex(self.recipient_p.0));
241        map.insert("recipient_P.y".into(), to_noir_hex(self.recipient_p.1));
242
243        self.recipient_proof.add_to_map(&mut map, "recipient_proof");
244
245        self.old_note.add_to_map(&mut map, "old_note");
246        map.insert(
247            "old_shared_secret".into(),
248            to_noir_hex(self.old_shared_secret),
249        );
250        map.insert(
251            "old_note_index".into(),
252            to_noir_hex(U256::from(self.old_note_index)),
253        );
254        map.insert(
255            "old_note_path".into(),
256            format_path_array(&self.old_note_path),
257        );
258        map.insert(
259            "hashlock_preimage".into(),
260            to_noir_hex(self.hashlock_preimage),
261        );
262
263        self.memo_note.add_to_map(&mut map, "memo_note");
264        map.insert(
265            "memo_ephemeral_sk".into(),
266            to_noir_hex(self.memo_ephemeral_sk),
267        );
268
269        self.change_note.add_to_map(&mut map, "change_note");
270        map.insert(
271            "change_ephemeral_sk".into(),
272            to_noir_hex(self.change_ephemeral_sk),
273        );
274
275        map
276    }
277}
278
279#[derive(Debug, Clone)]
280pub struct GasPaymentInputs {
281    // Public inputs
282    pub merkle_root: U256,
283    pub current_timestamp: u64,
284    pub payment_value: U256,
285    pub payment_asset_id: U256,
286    pub relayer_address: Address,
287    pub execution_hash: U256,
288    pub compliance_pk: CompliancePk,
289
290    // Private inputs - Note being spent for gas
291    pub old_note: NotePlaintext,
292    pub old_shared_secret: U256,
293    pub old_note_index: u64,
294    pub old_note_path: Vec<U256>,
295    pub hashlock_preimage: U256,
296
297    // Private inputs - Change note
298    pub change_note: NotePlaintext,
299    pub change_ephemeral_sk: U256,
300}
301
302impl ProverInput for GasPaymentInputs {
303    fn to_prover_map(&self) -> HashMap<String, String> {
304        let mut map = HashMap::new();
305
306        map.insert("merkle_root".into(), to_noir_hex(self.merkle_root));
307        map.insert(
308            "current_timestamp".into(),
309            to_noir_hex(U256::from(self.current_timestamp)),
310        );
311        map.insert("payment_value".into(), to_noir_hex(self.payment_value));
312        map.insert(
313            "payment_asset_id".into(),
314            to_noir_hex(self.payment_asset_id),
315        );
316        map.insert(
317            "_relayer_address".into(),
318            to_noir_hex(address_to_field(self.relayer_address)),
319        );
320        map.insert("_execution_hash".into(), to_noir_hex(self.execution_hash));
321        map.insert(
322            "compliance_pubkey_x".into(),
323            to_noir_hex(self.compliance_pk.0),
324        );
325        map.insert(
326            "compliance_pubkey_y".into(),
327            to_noir_hex(self.compliance_pk.1),
328        );
329
330        self.old_note.add_to_map(&mut map, "old_note");
331        map.insert(
332            "old_shared_secret".into(),
333            to_noir_hex(self.old_shared_secret),
334        );
335        map.insert(
336            "old_note_index".into(),
337            to_noir_hex(U256::from(self.old_note_index)),
338        );
339        map.insert(
340            "old_note_path".into(),
341            format_path_array(&self.old_note_path),
342        );
343        map.insert(
344            "hashlock_preimage".into(),
345            to_noir_hex(self.hashlock_preimage),
346        );
347
348        self.change_note.add_to_map(&mut map, "change_note");
349        map.insert(
350            "change_ephemeral_sk".into(),
351            to_noir_hex(self.change_ephemeral_sk),
352        );
353
354        map
355    }
356}
357
358#[derive(Debug, Clone)]
359pub struct JoinInputs {
360    pub merkle_root: U256,
361    pub current_timestamp: u64,
362    pub compliance_pk: CompliancePk,
363
364    // First input note
365    pub note_a: NotePlaintext,
366    pub secret_a: U256,
367    pub index_a: u64,
368    pub path_a: Vec<U256>,
369    pub preimage_a: U256,
370
371    // Second input note
372    pub note_b: NotePlaintext,
373    pub secret_b: U256,
374    pub index_b: u64,
375    pub path_b: Vec<U256>,
376    pub preimage_b: U256,
377
378    // Output note
379    pub note_out: NotePlaintext,
380    pub sk_out: U256,
381}
382
383impl ProverInput for JoinInputs {
384    fn to_prover_map(&self) -> HashMap<String, String> {
385        let mut map = HashMap::new();
386
387        map.insert("merkle_root".into(), to_noir_hex(self.merkle_root));
388        map.insert(
389            "current_timestamp".into(),
390            to_noir_hex(U256::from(self.current_timestamp)),
391        );
392        map.insert(
393            "compliance_pubkey_x".into(),
394            to_noir_hex(self.compliance_pk.0),
395        );
396        map.insert(
397            "compliance_pubkey_y".into(),
398            to_noir_hex(self.compliance_pk.1),
399        );
400
401        self.note_a.add_to_map(&mut map, "note_a");
402        map.insert("secret_a".into(), to_noir_hex(self.secret_a));
403        map.insert("index_a".into(), to_noir_hex(U256::from(self.index_a)));
404        map.insert("path_a".into(), format_path_array(&self.path_a));
405        map.insert("preimage_a".into(), to_noir_hex(self.preimage_a));
406
407        self.note_b.add_to_map(&mut map, "note_b");
408        map.insert("secret_b".into(), to_noir_hex(self.secret_b));
409        map.insert("index_b".into(), to_noir_hex(U256::from(self.index_b)));
410        map.insert("path_b".into(), format_path_array(&self.path_b));
411        map.insert("preimage_b".into(), to_noir_hex(self.preimage_b));
412
413        self.note_out.add_to_map(&mut map, "note_out");
414        map.insert("sk_out".into(), to_noir_hex(self.sk_out));
415
416        map
417    }
418}
419
420#[derive(Debug, Clone)]
421pub struct SplitInputs {
422    pub merkle_root: U256,
423    pub current_timestamp: u64,
424    pub compliance_pk: CompliancePk,
425
426    // Input note
427    pub note_in: NotePlaintext,
428    pub secret_in: U256,
429    pub index_in: u64,
430    pub path_in: Vec<U256>,
431    pub preimage_in: U256,
432
433    // Output notes (Noir uses underscore: note_out_1, note_out_2)
434    pub note_out_1: NotePlaintext,
435    pub sk_out_1: U256,
436    pub note_out_2: NotePlaintext,
437    pub sk_out_2: U256,
438}
439
440impl ProverInput for SplitInputs {
441    fn to_prover_map(&self) -> HashMap<String, String> {
442        let mut map = HashMap::new();
443
444        map.insert("merkle_root".into(), to_noir_hex(self.merkle_root));
445        map.insert(
446            "current_timestamp".into(),
447            to_noir_hex(U256::from(self.current_timestamp)),
448        );
449        map.insert(
450            "compliance_pubkey_x".into(),
451            to_noir_hex(self.compliance_pk.0),
452        );
453        map.insert(
454            "compliance_pubkey_y".into(),
455            to_noir_hex(self.compliance_pk.1),
456        );
457
458        self.note_in.add_to_map(&mut map, "note_in");
459        map.insert("secret_in".into(), to_noir_hex(self.secret_in));
460        map.insert("index_in".into(), to_noir_hex(U256::from(self.index_in)));
461        map.insert("path_in".into(), format_path_array(&self.path_in));
462        map.insert("preimage_in".into(), to_noir_hex(self.preimage_in));
463
464        self.note_out_1.add_to_map(&mut map, "note_out_1");
465        map.insert("sk_out_1".into(), to_noir_hex(self.sk_out_1));
466        self.note_out_2.add_to_map(&mut map, "note_out_2");
467        map.insert("sk_out_2".into(), to_noir_hex(self.sk_out_2));
468
469        map
470    }
471}
472
473#[derive(Debug, Clone)]
474pub struct PublicClaimInputs {
475    pub memo_id: U256,
476    pub compliance_pk: CompliancePk,
477
478    pub val: U256,
479    pub asset_id: U256,
480    pub timelock: U256,
481    pub owner_x: U256,
482    pub owner_y: U256,
483    pub salt: U256,
484
485    pub recipient_sk: U256,
486    pub note_out: NotePlaintext,
487    pub sk_out: U256,
488}
489
490impl ProverInput for PublicClaimInputs {
491    fn to_prover_map(&self) -> HashMap<String, String> {
492        let mut map = HashMap::new();
493
494        map.insert("memo_id".into(), to_noir_hex(self.memo_id));
495        map.insert(
496            "compliance_pubkey_x".into(),
497            to_noir_hex(self.compliance_pk.0),
498        );
499        map.insert(
500            "compliance_pubkey_y".into(),
501            to_noir_hex(self.compliance_pk.1),
502        );
503
504        map.insert("val".into(), to_noir_hex(self.val));
505        map.insert("asset_id".into(), to_noir_hex(self.asset_id));
506        map.insert("timelock".into(), to_noir_hex(self.timelock));
507        map.insert("owner_x".into(), to_noir_hex(self.owner_x));
508        map.insert("owner_y".into(), to_noir_hex(self.owner_y));
509        map.insert("salt".into(), to_noir_hex(self.salt));
510
511        map.insert("recipient_sk".into(), to_noir_hex(self.recipient_sk));
512        self.note_out.add_to_map(&mut map, "note_out");
513        map.insert("sk_out".into(), to_noir_hex(self.sk_out));
514
515        map
516    }
517}
518
519#[must_use]
520pub fn address_to_field(addr: Address) -> U256 {
521    U256::from_big_endian(addr.as_bytes())
522}
523
524fn format_path_array(path: &[U256]) -> String {
525    let items: Vec<_> = path
526        .iter()
527        .map(|p| format!("\"{}\"", to_noir_hex(*p)))
528        .collect();
529    format!("[{}]", items.join(", "))
530}
531
532#[cfg(test)]
533mod tests {
534    use super::*;
535
536    #[test]
537    fn test_note_plaintext_random() {
538        let note = NotePlaintext::random(U256::from(1000), Address::zero());
539        assert!(!note.secret.is_zero());
540        assert!(!note.nullifier.is_zero());
541    }
542
543    #[test]
544    fn test_deposit_inputs_to_map() {
545        let note = NotePlaintext::random(U256::from(1000), Address::zero());
546        let inputs = DepositInputs::new(note, U256::from(12345), (U256::from(1), U256::from(2)));
547
548        let map = inputs.to_prover_map();
549        assert!(map.contains_key("note_plaintext.value"));
550        assert!(map.contains_key("ephemeral_sk"));
551
552        let value = map.get("note_plaintext.value").unwrap();
553        assert!(value.starts_with("0x"));
554        assert_eq!(value.len(), 66); // 0x + 64 hex chars
555    }
556
557    #[test]
558    fn test_format_path_array() {
559        let path = vec![U256::from(1)];
560        let formatted = format_path_array(&path);
561        assert!(formatted.starts_with("[\"0x"));
562    }
563
564    #[test]
565    fn test_proof_inputs_field_element_bounds() {
566        let modulus = U256::from_dec_str(
567            "21888242871839275222246405745257275088548364400416034343698204186575808495617",
568        )
569        .unwrap();
570
571        let note = NotePlaintext::random(U256::from(999), Address::zero());
572        let inputs = DepositInputs::new(note, U256::from(12345), (U256::from(7), U256::from(11)));
573
574        let map = inputs.to_prover_map();
575
576        for (key, hex_val) in &map {
577            if let Some(stripped) = hex_val.strip_prefix("0x") {
578                let num = U256::from_str_radix(stripped, 16)
579                    .unwrap_or_else(|_| panic!("key {key} has non-hex value: {hex_val}"));
580                assert!(num < modulus, "key {key} value {num} >= BN254 modulus");
581            }
582        }
583    }
584
585    #[test]
586    fn test_deposit_inputs_map_has_required_keys() {
587        let note = NotePlaintext::random(U256::from(1), Address::zero());
588        let inputs = DepositInputs::new(note, U256::from(99), (U256::from(1), U256::from(2)));
589        let map = inputs.to_prover_map();
590
591        let required = [
592            "note_plaintext.value",
593            "note_plaintext.secret",
594            "note_plaintext.nullifier",
595            "ephemeral_sk",
596            "compliance_pubkey_x",
597            "compliance_pubkey_y",
598        ];
599        for key in required {
600            assert!(map.contains_key(key), "missing required key: {key}");
601        }
602    }
603}