1use 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, ¬e)?;
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 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(¬e);
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}