Skip to main content

darkpool_client/
note_factory.rs

1//! Note creation with encryption, commitment computation, and nullifier derivation.
2
3use ethers::types::{Address, U256};
4use thiserror::Error;
5use tracing::debug;
6
7use crate::crypto_helpers::{
8    derive_nullifier_path_a, derive_nullifier_path_b, encrypt_memo_note_3party,
9    encrypt_note_for_deposit_aes, poseidon_hash, CryptoError,
10};
11use crate::key_repository::KeyRepository;
12use crate::merkle_tree::MerklePath;
13use crate::proof_inputs::NotePlaintext;
14use crate::utxo_store::OwnedNote;
15
16#[derive(Debug, Error)]
17pub enum NoteFactoryError {
18    #[error("Crypto error: {0}")]
19    Crypto(#[from] CryptoError),
20    #[error("Invalid parameters: {0}")]
21    InvalidParams(String),
22    #[error("Insufficient balance: need {need}, have {have}")]
23    InsufficientBalance { need: U256, have: U256 },
24}
25
26#[derive(Debug, Clone)]
27pub struct DepositNoteResult {
28    pub note: NotePlaintext,
29    pub ephemeral_sk: U256,
30    pub ephemeral_pk: (U256, U256),
31    pub packed_ciphertext: [U256; 7],
32    pub commitment: U256,
33}
34
35#[derive(Debug, Clone)]
36pub struct TransferNoteResult {
37    pub memo_note: NotePlaintext,
38    pub change_note: NotePlaintext,
39    pub memo_ephemeral_sk: U256,
40    pub memo_ephemeral_pk: (U256, U256),
41    pub memo_packed_ciphertext: [U256; 7],
42    pub memo_commitment: U256,
43    pub int_bob: (U256, U256),
44    pub int_carol: (U256, U256),
45    pub change_ephemeral_sk: U256,
46    pub change_ephemeral_pk: (U256, U256),
47    pub change_packed_ciphertext: [U256; 7],
48    pub change_commitment: U256,
49    pub transfer_tag: U256,
50}
51
52#[derive(Debug, Clone)]
53pub struct ChangeNoteResult {
54    pub note: NotePlaintext,
55    pub ephemeral_sk: U256,
56    pub ephemeral_pk: (U256, U256),
57    pub packed_ciphertext: [U256; 7],
58    pub commitment: U256,
59}
60
61pub struct NoteFactory {
62    compliance_pk: (U256, U256),
63}
64
65impl NoteFactory {
66    #[must_use]
67    pub fn new(compliance_pk: (U256, U256)) -> Self {
68        Self { compliance_pk }
69    }
70
71    pub fn create_deposit_note(
72        &self,
73        value: U256,
74        asset: Address,
75        keys: &mut KeyRepository,
76    ) -> Result<DepositNoteResult, NoteFactoryError> {
77        let note = NotePlaintext::random(value, asset);
78        let (ephemeral_sk, _nonce) = keys.next_ephemeral_params();
79        let (packed_ciphertext, ephemeral_pk) =
80            encrypt_note_for_deposit_aes(ephemeral_sk, self.compliance_pk, &note)?;
81        let commitment = poseidon_hash(&packed_ciphertext);
82
83        debug!(
84            "Created deposit note: value={}, commitment={:?}",
85            value, commitment
86        );
87
88        Ok(DepositNoteResult {
89            note,
90            ephemeral_sk,
91            ephemeral_pk,
92            packed_ciphertext,
93            commitment,
94        })
95    }
96
97    /// Create memo (to recipient via 3-party ECDH) + change (to self) notes.
98    pub fn create_transfer_notes(
99        &self,
100        memo_value: U256,
101        change_value: U256,
102        asset: Address,
103        recipient_b: (U256, U256),
104        recipient_p: (U256, U256),
105        keys: &mut KeyRepository,
106    ) -> Result<TransferNoteResult, NoteFactoryError> {
107        let memo_note = NotePlaintext::random(memo_value, asset);
108        let change_note = NotePlaintext::random(change_value, asset);
109        let (memo_ephemeral_sk, _) = keys.next_ephemeral_params();
110        let (change_ephemeral_sk, _) = keys.next_ephemeral_params();
111
112        let memo_result = encrypt_memo_note_3party(
113            memo_ephemeral_sk,
114            recipient_p,
115            recipient_b,
116            self.compliance_pk,
117            &memo_note,
118        )?;
119
120        let (change_packed_ciphertext, change_ephemeral_pk) =
121            encrypt_note_for_deposit_aes(change_ephemeral_sk, self.compliance_pk, &change_note)?;
122
123        let memo_commitment = poseidon_hash(&memo_result.packed_ciphertext);
124        let change_commitment = poseidon_hash(&change_packed_ciphertext);
125        let transfer_tag = recipient_p.0;
126
127        debug!(
128            "Created transfer notes: memo_value={}, change_value={}, tag={:?}",
129            memo_value, change_value, transfer_tag
130        );
131
132        Ok(TransferNoteResult {
133            memo_note,
134            change_note,
135            memo_ephemeral_sk,
136            memo_ephemeral_pk: memo_result.ephemeral_pk,
137            memo_packed_ciphertext: memo_result.packed_ciphertext,
138            memo_commitment,
139            int_bob: memo_result.int_bob,
140            int_carol: memo_result.int_carol,
141            change_ephemeral_sk,
142            change_ephemeral_pk,
143            change_packed_ciphertext,
144            change_commitment,
145            transfer_tag,
146        })
147    }
148
149    pub fn create_change_note(
150        &self,
151        value: U256,
152        asset: Address,
153        keys: &mut KeyRepository,
154    ) -> Result<ChangeNoteResult, NoteFactoryError> {
155        let result = self.create_deposit_note(value, asset, keys)?;
156
157        Ok(ChangeNoteResult {
158            note: result.note,
159            ephemeral_sk: result.ephemeral_sk,
160            ephemeral_pk: result.ephemeral_pk,
161            packed_ciphertext: result.packed_ciphertext,
162            commitment: result.commitment,
163        })
164    }
165
166    pub fn create_split_notes(
167        &self,
168        value_a: U256,
169        value_b: U256,
170        asset: Address,
171        keys: &mut KeyRepository,
172    ) -> Result<(ChangeNoteResult, ChangeNoteResult), NoteFactoryError> {
173        let note_a = self.create_change_note(value_a, asset, keys)?;
174        let note_b = self.create_change_note(value_b, asset, keys)?;
175        Ok((note_a, note_b))
176    }
177
178    pub fn create_join_output_note(
179        &self,
180        total_value: U256,
181        asset: Address,
182        keys: &mut KeyRepository,
183    ) -> Result<ChangeNoteResult, NoteFactoryError> {
184        self.create_change_note(total_value, asset, keys)
185    }
186
187    #[must_use]
188    pub fn derive_nullifier_deposit(&self, note: &NotePlaintext) -> U256 {
189        derive_nullifier_path_a(note.nullifier)
190    }
191
192    #[must_use]
193    pub fn derive_nullifier_transfer(
194        &self,
195        shared_secret: U256,
196        commitment: U256,
197        leaf_index: u64,
198    ) -> U256 {
199        derive_nullifier_path_b(shared_secret, commitment, leaf_index)
200    }
201}
202
203#[derive(Debug, Clone)]
204pub struct SpendingInputs {
205    pub note: NotePlaintext,
206    pub shared_secret: U256,
207    pub leaf_index: u64,
208    pub merkle_path: MerklePath,
209    pub hashlock_preimage: U256,
210    pub nullifier_hash: U256,
211}
212
213impl SpendingInputs {
214    #[must_use]
215    pub fn from_owned_note(note: &OwnedNote, merkle_path: MerklePath) -> Self {
216        let nullifier_hash = if note.is_transfer {
217            derive_nullifier_path_b(note.spending_secret, note.commitment, note.leaf_index)
218        } else {
219            derive_nullifier_path_a(note.plaintext.nullifier)
220        };
221
222        Self {
223            note: note.plaintext.clone(),
224            shared_secret: note.spending_secret,
225            leaf_index: note.leaf_index,
226            merkle_path,
227            hashlock_preimage: U256::zero(),
228            nullifier_hash,
229        }
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236    use crate::identity::DarkAccount;
237    use crate::key_repository::KeyRepository;
238
239    fn setup_test_keys() -> KeyRepository {
240        let account = DarkAccount::from_seed(b"test_seed_12345");
241        let compliance_pk = (U256::from(1), U256::from(2));
242        let mut keys = KeyRepository::new(account, compliance_pk);
243        keys.advance_ephemeral_keys(10);
244        keys
245    }
246
247    #[test]
248    fn test_create_deposit_note() {
249        let compliance_pk = (
250            U256::from_dec_str(
251                "5299619240641551281634865583518297030282874472190772894086521144482721001553",
252            )
253            .unwrap(),
254            U256::from_dec_str(
255                "16950150798460657717958625567821834550301663161624707787222815936182638968203",
256            )
257            .unwrap(),
258        );
259        let mut keys = setup_test_keys();
260        let factory = NoteFactory::new(compliance_pk);
261
262        let result = factory
263            .create_deposit_note(U256::from(1000), Address::zero(), &mut keys)
264            .unwrap();
265
266        assert_eq!(result.note.value, U256::from(1000));
267        assert!(!result.commitment.is_zero());
268        assert!(!result.ephemeral_pk.0.is_zero());
269    }
270
271    #[test]
272    fn test_nullifier_derivation() {
273        let compliance_pk = (U256::from(1), U256::from(2));
274        let factory = NoteFactory::new(compliance_pk);
275
276        let note = NotePlaintext {
277            value: U256::from(100),
278            asset_id: U256::from(1),
279            secret: U256::from(12345),
280            nullifier: U256::from(67890),
281            timelock: U256::zero(),
282            hashlock: U256::zero(),
283        };
284
285        let null_a = factory.derive_nullifier_deposit(&note);
286        assert!(!null_a.is_zero());
287
288        let null_b = factory.derive_nullifier_transfer(U256::from(111), U256::from(222), 5);
289        assert!(!null_b.is_zero());
290        assert_ne!(null_a, null_b);
291    }
292
293    #[test]
294    fn test_create_change_note() {
295        let compliance_pk = (
296            U256::from_dec_str(
297                "5299619240641551281634865583518297030282874472190772894086521144482721001553",
298            )
299            .unwrap(),
300            U256::from_dec_str(
301                "16950150798460657717958625567821834550301663161624707787222815936182638968203",
302            )
303            .unwrap(),
304        );
305        let mut keys = setup_test_keys();
306        let factory = NoteFactory::new(compliance_pk);
307
308        let result = factory
309            .create_change_note(U256::from(500), Address::zero(), &mut keys)
310            .unwrap();
311
312        assert_eq!(result.note.value, U256::from(500));
313        assert!(!result.commitment.is_zero());
314    }
315
316    #[test]
317    fn test_create_split_notes() {
318        let compliance_pk = (
319            U256::from_dec_str(
320                "5299619240641551281634865583518297030282874472190772894086521144482721001553",
321            )
322            .unwrap(),
323            U256::from_dec_str(
324                "16950150798460657717958625567821834550301663161624707787222815936182638968203",
325            )
326            .unwrap(),
327        );
328        let mut keys = setup_test_keys();
329        let factory = NoteFactory::new(compliance_pk);
330
331        let (note_a, note_b) = factory
332            .create_split_notes(U256::from(300), U256::from(200), Address::zero(), &mut keys)
333            .unwrap();
334
335        assert_eq!(note_a.note.value, U256::from(300));
336        assert_eq!(note_b.note.value, U256::from(200));
337        assert_ne!(note_a.commitment, note_b.commitment);
338    }
339
340    #[test]
341    fn test_note_factory_zero_value() {
342        let compliance_pk = (
343            U256::from_dec_str(
344                "5299619240641551281634865583518297030282874472190772894086521144482721001553",
345            )
346            .unwrap(),
347            U256::from_dec_str(
348                "16950150798460657717958625567821834550301663161624707787222815936182638968203",
349            )
350            .unwrap(),
351        );
352        let mut keys = setup_test_keys();
353        let factory = NoteFactory::new(compliance_pk);
354
355        let result = factory
356            .create_deposit_note(U256::zero(), Address::zero(), &mut keys)
357            .unwrap();
358
359        assert_eq!(result.note.value, U256::zero());
360        assert!(!result.commitment.is_zero());
361    }
362
363    #[test]
364    fn test_note_factory_same_input_different_epk() {
365        let compliance_pk = (
366            U256::from_dec_str(
367                "5299619240641551281634865583518297030282874472190772894086521144482721001553",
368            )
369            .unwrap(),
370            U256::from_dec_str(
371                "16950150798460657717958625567821834550301663161624707787222815936182638968203",
372            )
373            .unwrap(),
374        );
375        let mut keys = setup_test_keys();
376        let factory = NoteFactory::new(compliance_pk);
377
378        let r1 = factory
379            .create_deposit_note(U256::from(500), Address::zero(), &mut keys)
380            .unwrap();
381        let r2 = factory
382            .create_deposit_note(U256::from(500), Address::zero(), &mut keys)
383            .unwrap();
384
385        assert_ne!(r1.ephemeral_pk, r2.ephemeral_pk);
386        assert_ne!(r1.commitment, r2.commitment);
387    }
388}