1use crate::{
2 fawkes_crypto::{
3 ff_uint::{Num, seedbox::{SeedboxChaCha20, SeedBox, SeedBoxGen}},
4 borsh::{BorshSerialize, BorshDeserialize},
5 native::ecc::{EdwardsPoint},
6
7 },
8 native::{
9 account::Account,
10 note::Note,
11 params::PoolParams,
12 key::{derive_key_a, derive_key_p_d}
13 },
14 constants::{self, SHARED_SECRETS_HEAPLESS_SIZE, ACCOUNT_HEAPLESS_SIZE, NOTE_HEAPLESS_SIZE}
15};
16
17use sha3::{Digest, Keccak256};
18
19use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce, aead::AeadMutInPlace};
20use chacha20poly1305::aead::{Aead, NewAead};
21use chacha20poly1305::aead::heapless::Vec as HeaplessVec;
22
23enum Buffer<T, const N: usize> {
25 HeapBuffer(Vec<T>),
26 HeaplessBuffer(HeaplessVec<T, N>)
27}
28
29impl<T, const N: usize> Buffer<T, N> {
30 fn as_slice(&self) -> &[T] {
31 match self {
32 Self::HeapBuffer(vec) => vec.as_slice(),
33 Self::HeaplessBuffer(heapless_vec) => heapless_vec.as_slice()
34 }
35 }
36}
37
38fn keccak256(data:&[u8])->[u8;constants::U256_SIZE] {
39 let mut hasher = Keccak256::new();
40 hasher.update(data);
41 let mut res = [0u8;constants::U256_SIZE];
42 res.iter_mut().zip(hasher.finalize().into_iter()).for_each(|(l,r)| *l=r);
43 res
44}
45
46fn symcipher_encode(key:&[u8], data:&[u8])->Vec<u8> {
48 assert!(key.len()==constants::U256_SIZE);
49 let nonce = Nonce::from_slice(&constants::ENCRYPTION_NONCE);
50 let cipher = ChaCha20Poly1305::new(Key::from_slice(key));
51 cipher.encrypt(nonce, data.as_ref()).unwrap()
52}
53
54fn symcipher_decode<const N: usize>(key: &[u8], ciphertext: &[u8]) -> Option<Buffer<u8, N>> {
57 assert!(key.len()==constants::U256_SIZE);
58 let nonce = Nonce::from_slice(&constants::ENCRYPTION_NONCE);
59 let mut cipher = ChaCha20Poly1305::new(Key::from_slice(key));
60
61 if ciphertext.len() <= N {
62 let mut buffer = HeaplessVec::<u8, N>::from_slice(ciphertext).ok()?;
63 cipher.decrypt_in_place(nonce, b"", &mut buffer).ok()?;
64 Some(Buffer::HeaplessBuffer(buffer))
65 } else {
66 let plain = cipher.decrypt(nonce, ciphertext).ok()?;
67 Some(Buffer::HeapBuffer(plain))
68 }
69}
70
71pub fn encrypt<P: PoolParams>(
72 entropy: &[u8],
73 eta:Num<P::Fr>,
74 account: Account<P::Fr>,
75 note: &[Note<P::Fr>],
76 params:&P
77) -> Vec<u8> {
78 let nozero_notes_num = note.len();
79 let nozero_items_num = nozero_notes_num+1;
80
81
82 let mut sb = SeedboxChaCha20::new_with_salt(entropy);
83
84 let account_data = {
85 let mut account_key = [0u8;constants::U256_SIZE];
86 sb.fill_bytes(&mut account_key);
87 let account_ciphertext = symcipher_encode(&account_key, &account.try_to_vec().unwrap());
88 (account_key, account_ciphertext)
89 };
90
91
92 let notes_data = note.iter().map(|e|{
93 let a:Num<P::Fs> = sb.gen();
94 let p_d = EdwardsPoint::subgroup_decompress(e.p_d, params.jubjub()).unwrap();
95 let ecdh = p_d.mul(a, params.jubjub());
96 let key = keccak256(&ecdh.x.try_to_vec().unwrap());
97 let ciphertext = symcipher_encode(&key, &e.try_to_vec().unwrap());
98 let a_pub = derive_key_p_d(e.d.to_num(), a, params);
99 (a_pub.x, key, ciphertext)
100
101 }).collect::<Vec<_>>();
102
103 let shared_secret_data = {
104 let a_p_pub = derive_key_a(sb.gen(), params);
105 let ecdh = a_p_pub.mul(eta.to_other_reduced(), params.jubjub());
106 let key = keccak256(&ecdh.x.try_to_vec().unwrap());
107 let text:Vec<u8> = core::iter::once(&account_data.0[..]).chain(notes_data.iter().map(|e| &e.1[..])).collect::<Vec<_>>().concat();
108 let ciphertext = symcipher_encode(&key, &text);
109 (a_p_pub.x, ciphertext)
110 };
111
112 let mut res = vec![];
113
114 (nozero_items_num as u32).serialize(&mut res).unwrap();
115 account.hash(params).serialize(&mut res).unwrap();
116
117 for e in note.iter() {
118 e.hash(params).serialize(&mut res).unwrap();
119 }
120 shared_secret_data.0.serialize(&mut res).unwrap();
121 res.extend(&shared_secret_data.1);
122
123 res.extend(&account_data.1);
124
125 notes_data.iter().for_each(|nd|{
126 nd.0.serialize(&mut res).unwrap();
127 res.extend(&nd.2);
128 });
129
130 res
131}
132
133
134fn buf_take<'a>(memo: &mut &'a[u8], size:usize) -> Option<&'a[u8]> {
135 if memo.len() < size {
136 None
137 } else {
138 let res = &memo[0..size];
139 *memo = &memo[size..];
140 Some(res)
141 }
142}
143
144pub fn decrypt_out<P: PoolParams>(eta:Num<P::Fr>, mut memo:&[u8], params:&P)->Option<(Account<P::Fr>, Vec<Note<P::Fr>>)> {
145 let num_size = constants::num_size_bits::<P::Fr>()/8;
146 let account_size = constants::account_size_bits::<P::Fr>()/8;
147 let note_size = constants::note_size_bits::<P::Fr>()/8;
148
149
150 let nozero_items_num = u32::deserialize(&mut memo).ok()? as usize;
151 if nozero_items_num == 0 {
152 return None;
153 }
154
155 let nozero_notes_num = nozero_items_num - 1;
156 let shared_secret_ciphertext_size = nozero_items_num * constants::U256_SIZE + constants::POLY_1305_TAG_SIZE;
157
158 let account_hash = Num::deserialize(&mut memo).ok()?;
159 let note_hashes = buf_take(&mut memo, nozero_notes_num * num_size)?;
160
161 let shared_secret_text = decrypt_ecdh::<P, SHARED_SECRETS_HEAPLESS_SIZE>(eta, &mut memo, shared_secret_ciphertext_size, params)?;
162
163 let mut shared_secret_text_ptr = shared_secret_text.as_slice();
164
165 let account_key= <[u8;constants::U256_SIZE]>::deserialize(&mut shared_secret_text_ptr).ok()?;
166 let note_key = (0..nozero_notes_num).map(|_| <[u8;constants::U256_SIZE]>::deserialize(&mut shared_secret_text_ptr)).collect::<Result<Vec<_>,_>>().ok()?;
167
168 let account_ciphertext = buf_take(&mut memo, account_size+constants::POLY_1305_TAG_SIZE)?;
169 let account = decrypt_account(&account_key, account_ciphertext, account_hash, params)?;
170
171 let note = (0..nozero_notes_num).map(|i| {
172 buf_take(&mut memo, num_size)?;
173 let note_hash = {
174 let note_hash = &mut ¬e_hashes[i * num_size..(i + 1) * num_size];
175 Num::deserialize(note_hash).ok()?
176 };
177
178 let ciphertext = buf_take(&mut memo, note_size+constants::POLY_1305_TAG_SIZE)?;
179
180 decrypt_note(¬e_key[i], ciphertext, note_hash, params)
181 }).collect::<Option<Vec<_>>>()?;
182
183 Some((account, note))
184}
185
186fn decrypt_ecdh<P: PoolParams, const N: usize>(eta:Num<P::Fr>, buf:&mut &[u8], size:usize, params:&P) -> Option<Buffer<u8, N>> {
187 let a_p = EdwardsPoint::subgroup_decompress(Num::deserialize(buf).ok()?, params.jubjub())?;
188 let ecdh = a_p.mul(eta.to_other_reduced(), params.jubjub());
189 let key = {
190 let mut x: [u8; 32] = [0; 32];
191 ecdh.x.serialize(&mut &mut x[..]).unwrap();
192 keccak256(&x)
193 };
194 let ciphertext = buf_take(buf, size)?;
195
196 symcipher_decode::<N>(&key, ciphertext)
197}
198
199fn _decrypt_in<P: PoolParams>(eta:Num<P::Fr>, mut memo:&[u8], params:&P)->Option<Vec<Option<Note<P::Fr>>>> {
200 let num_size = constants::num_size_bits::<P::Fr>()/8;
201 let account_size = constants::account_size_bits::<P::Fr>()/8;
202 let note_size = constants::note_size_bits::<P::Fr>()/8;
203
204
205 let nozero_items_num = u32::deserialize(&mut memo).ok()? as usize;
206 if nozero_items_num == 0 {
207 return None;
208 }
209
210 let nozero_notes_num = nozero_items_num - 1;
211 let shared_secret_ciphertext_size = nozero_items_num * constants::U256_SIZE + constants::POLY_1305_TAG_SIZE;
212
213 buf_take(&mut memo, num_size)?;
214 let note_hashes = buf_take(&mut memo, nozero_notes_num * num_size)?;
215
216 buf_take(&mut memo, num_size)?;
217 buf_take(&mut memo, shared_secret_ciphertext_size)?;
218 buf_take(&mut memo, account_size+constants::POLY_1305_TAG_SIZE)?;
219
220
221 let note = (0..nozero_notes_num).map(|i| {
222 let a_pub = EdwardsPoint::subgroup_decompress(Num::deserialize(&mut memo).ok()?, params.jubjub())?;
223 let ecdh = a_pub.mul(eta.to_other_reduced(), params.jubjub());
224
225 let key = {
226 let mut x: [u8; 32] = [0; 32];
227 ecdh.x.serialize(&mut &mut x[..]).unwrap();
228 keccak256(&x)
229 };
230
231 let note_hash = {
232 let note_hash = &mut ¬e_hashes[i * num_size..(i + 1) * num_size];
233 Num::deserialize(note_hash).ok()?
234 };
235
236 let ciphertext = buf_take(&mut memo, note_size+constants::POLY_1305_TAG_SIZE)?;
237
238 decrypt_note(&key, ciphertext, note_hash, params)
239 }).collect::<Vec<Option<_>>>();
240
241 Some(note)
242}
243
244pub fn decrypt_in<P: PoolParams>(eta:Num<P::Fr>, memo:&[u8], params:&P)->Vec<Option<Note<P::Fr>>> {
245 if let Some(res) = _decrypt_in(eta, memo, params) {
246 res
247 } else {
248 vec![]
249 }
250}
251
252pub fn symcipher_decryption_keys<P: PoolParams>(eta:Num<P::Fr>, mut memo:&[u8], params:&P) -> Option<Vec<(u64, Vec<u8>, Vec<u8>)>> {
256 let num_size = constants::num_size_bits::<P::Fr>()/8;
257 let account_size = constants::account_size_bits::<P::Fr>()/8;
258 let note_size = constants::note_size_bits::<P::Fr>()/8;
259
260 let nozero_items_num = u32::deserialize(&mut memo).ok()? as usize;
261 if nozero_items_num == 0 {
262 return None;
263 }
264
265 let nozero_notes_num = nozero_items_num - 1;
266 let shared_secret_ciphertext_size = nozero_items_num * constants::U256_SIZE + constants::POLY_1305_TAG_SIZE;
267
268 let account_hash = Num::deserialize(&mut memo).ok()?;
269 let note_hashes = buf_take(&mut memo, nozero_notes_num * num_size)?;
270
271 let shared_secret_text = decrypt_ecdh::<P, SHARED_SECRETS_HEAPLESS_SIZE>(eta, &mut memo, shared_secret_ciphertext_size, params);
272
273 if let Some(shared_secret_text) = shared_secret_text {
274 let mut shared_secret_text_ptr = shared_secret_text.as_slice();
276
277 let account_key= <[u8;constants::U256_SIZE]>::deserialize(&mut shared_secret_text_ptr).ok()?;
278 let note_key = (0..nozero_notes_num).map(|_| <[u8;constants::U256_SIZE]>::deserialize(&mut shared_secret_text_ptr)).collect::<Result<Vec<_>,_>>().ok()?;
279
280 let account_ciphertext = buf_take(&mut memo, account_size+constants::POLY_1305_TAG_SIZE)?;
281 let _ = decrypt_account(&account_key, account_ciphertext, account_hash, params)?;
282
283 let account_tuple = (0 as u64, account_ciphertext.to_vec(), account_key.to_vec());
284 let result = Some(account_tuple)
285 .into_iter()
286 .chain(
287 (0..nozero_notes_num).filter_map(|i| {
288 buf_take(&mut memo, num_size)?;
289
290 let note_hash = {
291 let note_hash = &mut ¬e_hashes[i * num_size..(i + 1) * num_size];
292 Num::deserialize(note_hash).ok()?
293 };
294
295 let ciphertext = buf_take(&mut memo, note_size+constants::POLY_1305_TAG_SIZE)?;
296 match decrypt_note(¬e_key[i], ciphertext, note_hash, params) {
297 Some(_) => Some((i as u64 + 1, ciphertext.to_vec(), note_key[i].to_vec())),
298 _ => None,
299 }
300 })
301 ).collect::<Vec<_>>();
302
303 Some(result)
304 } else {
305 buf_take(&mut memo, account_size+constants::POLY_1305_TAG_SIZE)?; let notes = (0..nozero_notes_num).filter_map(|i| {
308 let a_pub = EdwardsPoint::subgroup_decompress(Num::deserialize(&mut memo).ok()?, params.jubjub())?;
309 let ecdh = a_pub.mul(eta.to_other_reduced(), params.jubjub());
310
311 let key = {
312 let mut x: [u8; 32] = [0; 32];
313 ecdh.x.serialize(&mut &mut x[..]).unwrap();
314 keccak256(&x)
315 };
316
317 let note_hash = {
318 let note_hash = &mut ¬e_hashes[i * num_size..(i + 1) * num_size];
319 Num::deserialize(note_hash).ok()?
320 };
321
322 let ciphertext = buf_take(&mut memo, note_size+constants::POLY_1305_TAG_SIZE)?;
323 match decrypt_note(&key, ciphertext, note_hash, params) {
324 Some(_) => Some((i as u64 + 1, ciphertext.to_vec(), key.to_vec())),
325 _ => None,
326 }
327 })
328 .collect();
329
330 Some(notes)
331 }
332}
333
334pub fn decrypt_account<P: PoolParams>(symkey: &[u8], ciphertext: &[u8], hash: Num<P::Fr>, params: &P) -> Option<Account<P::Fr>> {
335 match decrypt_account_no_validate(symkey, ciphertext, params) {
336 Some(acc) if acc.hash(params) == hash => Some(acc),
337 _ => None,
338 }
339}
340
341pub fn decrypt_account_no_validate<P: PoolParams>(symkey: &[u8], ciphertext: &[u8], _: &P) -> Option<Account<P::Fr>> {
342 let plain = symcipher_decode::<ACCOUNT_HEAPLESS_SIZE>(&symkey, ciphertext)?;
343 Account::try_from_slice(plain.as_slice()).ok()
344}
345
346pub fn decrypt_note<P: PoolParams>(symkey: &[u8], ciphertext: &[u8], hash: Num<P::Fr>, params: &P) -> Option<Note<P::Fr>> {
347 match decrypt_note_no_validate(symkey, ciphertext, params) {
348 Some(note) if note.hash(params) == hash => Some(note),
349 _ => None,
350 }
351}
352
353pub fn decrypt_note_no_validate<P: PoolParams>(symkey: &[u8], ciphertext: &[u8], _: &P) -> Option<Note<P::Fr>> {
354 let plain = symcipher_decode::<NOTE_HEAPLESS_SIZE>(&symkey, ciphertext)?;
355 Note::try_from_slice(plain.as_slice()).ok()
356}
357
358#[cfg(test)]
359mod tests {
360 use test_case::test_case;
361
362 use crate::native::cipher::{symcipher_decryption_keys, decrypt_account, decrypt_note};
363 use crate::native::note::Note;
364 use crate::{POOL_PARAMS, native::boundednum::BoundedNum};
365 use crate::native::account::Account;
366 use fawkes_crypto::ff_uint::Num;
367 use fawkes_crypto::{rand::Rng, engines::bn256::Fr};
368 use fawkes_crypto::rand::rngs::OsRng;
369 use crate::native::key::{derive_key_a, derive_key_eta, derive_key_p_d};
370
371 use super::{symcipher_encode, symcipher_decode, encrypt, decrypt_out, decrypt_in};
372
373 #[test_case(0)]
374 #[test_case(1)]
375 #[test_case(100)]
376 #[test_case(128)]
377 #[test_case(1024)]
378 fn test_symcipher(buf_len: usize) {
379 let mut rng = OsRng::default();
380
381 let key: [u8; 32] = rng.gen();
382 let plaintext: Vec<u8> = (0..buf_len).map(|_| { rng.gen() }).collect();
383 let ciphertext = symcipher_encode(&key, &plaintext.as_slice());
384 let decrypted = symcipher_decode::<0>(&key, &ciphertext.as_slice()).unwrap();
385
386 assert_eq!(plaintext.len(), decrypted.as_slice().len());
387 assert_eq!(plaintext.as_slice(), decrypted.as_slice());
388
389 }
390
391 #[test_case(0, 0.0)]
392 #[test_case(1, 0.0)]
393 #[test_case(1, 1.0)]
394 #[test_case(5, 0.8)]
395 #[test_case(15, 0.0)]
396 #[test_case(15, 1.0)]
397 #[test_case(20, 0.5)]
398 #[test_case(30, 0.7)]
399 #[test_case(42, 0.5)]
400 fn test_decrypt_in_out(notes_count: u32, note_probability: f64) {
401 let params = &POOL_PARAMS.clone();
402 let mut rng = OsRng::default();
403
404 let eta1 = derive_key_eta(derive_key_a(rng.gen(), params).x, params);
406 let eta2 = derive_key_eta(derive_key_a(rng.gen(), params).x, params);
408
409 let mut account: Account<Fr> = Account::sample(&mut rng, params);
411 account.b = BoundedNum::new(Num::from(10000000000 as u64));
412 account.e = BoundedNum::new(Num::from(12345 as u64));
413 account.i = BoundedNum::new(Num::from(128 as u32));
414 account.p_d = derive_key_p_d(account.d.to_num(), eta1, params).x;
415
416 let mut dst_notes_num: usize = 0;
418 let notes: Vec<Note<Fr>> = (0..notes_count as u64).map(|_| {
419 let mut a_note = Note::sample(&mut rng, params);
420 a_note.b = BoundedNum::new(Num::from(500000000 as u64));
421 if rng.gen_bool(note_probability) {
422 a_note.p_d = derive_key_p_d(a_note.d.to_num(), eta2, params).x;
424 dst_notes_num += 1;
425 } else {
426 a_note.p_d = derive_key_p_d(a_note.d.to_num(), eta1, params).x;
428 }
429 a_note
430 }).collect();
431
432 let entropy: [u8; 32] = rng.gen();
434 let mut encrypted = encrypt(&entropy, eta1, account, notes.as_slice(), params);
435
436 let decrypted_in = decrypt_in(eta2, encrypted.as_mut_slice(), params);
438 assert_eq!(decrypted_in.len(), notes.len());
439 let in_notes: Vec<_> = decrypted_in
440 .into_iter()
441 .enumerate()
442 .filter_map(|(i, note)| {
443 match note {
444 Some(note) => { assert_eq!(¬e, notes.get(i).unwrap());
446 Some(note)
447 }
448 _ => None,
449 }
450 })
451 .collect();
452 assert_eq!(in_notes.len(), dst_notes_num);
453
454 let decrypted_out = decrypt_out(eta1, encrypted.as_mut_slice(), params);
456 let decrypted_acc = decrypted_out.as_ref().unwrap().0;
457 let decrypted_notes = &decrypted_out.as_ref().unwrap().1;
458 assert_eq!(decrypted_acc, account);
459 assert_eq!(decrypted_notes.len(), notes.len());
460 (0..notes.len()).for_each(|i: usize| {
461 let src = notes.get(i).unwrap();
462 let recovered = decrypted_notes.get(i).unwrap();
463 assert_eq!(src, recovered);
464 });
465 }
466
467 #[test_case(0, 0.0)]
468 #[test_case(1, 0.0)]
469 #[test_case(1, 1.0)]
470 #[test_case(3, 0.5)]
471 #[test_case(10, 0.5)]
472 #[test_case(15, 0.0)]
473 #[test_case(30, 1.0)]
474 #[test_case(42, 0.5)]
475 fn test_compliance(notes_count: u32, note_probability: f64) {
476 let params = &POOL_PARAMS.clone();
477 let mut rng = OsRng::default();
478
479 let eta1 = derive_key_eta(derive_key_a(rng.gen(), params).x, params);
481 let eta2 = derive_key_eta(derive_key_a(rng.gen(), params).x, params);
483 let eta3 = derive_key_eta(derive_key_a(rng.gen(), params).x, params);
485
486 let mut account: Account<Fr> = Account::sample(&mut rng, params);
488 account.b = BoundedNum::new(Num::from(10000000000 as u64));
489 account.e = BoundedNum::new(Num::from(12345 as u64));
490 account.i = BoundedNum::new(Num::from(128 as u32));
491 account.p_d = derive_key_p_d(account.d.to_num(), eta1, params).x;
492
493 let mut dst_notes_num: usize = 0;
495 let notes: Vec<Note<Fr>> = (0..notes_count as u64).map(|_| {
496 let mut a_note = Note::sample(&mut rng, params);
497 a_note.b = BoundedNum::new(Num::from(500000000 as u64));
498 if rng.gen_bool(note_probability) {
499 a_note.p_d = derive_key_p_d(a_note.d.to_num(), eta2, params).x;
501 dst_notes_num += 1;
502 } else {
503 a_note.p_d = derive_key_p_d(a_note.d.to_num(), eta1, params).x;
505 }
506 a_note
507 }).collect();
508
509 let entropy: [u8; 32] = rng.gen();
511 let encrypted = encrypt(&entropy, eta1, account, notes.as_slice(), params);
512
513 let sender_restored = symcipher_decryption_keys(eta1, encrypted.as_slice(), params).unwrap();
515 assert!(sender_restored.len() == notes.len() + 1);
516 sender_restored.iter().for_each(|(index, chunk, key)| {
517 if *index == 0 {
518 let decrypt_acc = decrypt_account(key.as_slice(), chunk.as_slice(), account.hash(params), params).unwrap();
520 assert_eq!(decrypt_acc, account);
521 } else {
522 let orig_note = notes.get((index - 1) as usize).unwrap();
524 let decrypt_note = decrypt_note(key.as_slice(), chunk.as_slice(), orig_note.hash(params), params).unwrap();
525 assert_eq!(decrypt_note, *orig_note);
526 }
527 });
528
529 let receiver_restored = symcipher_decryption_keys(eta2, encrypted.as_slice(), params).unwrap();
531 assert!(receiver_restored.len() == dst_notes_num);
532 receiver_restored.iter().for_each(|(index, chunk, key)| {
533 assert_ne!(*index, 0); let orig_note = notes.get((index - 1) as usize).unwrap();
536 let decrypt_note = decrypt_note(key.as_slice(), chunk.as_slice(), orig_note.hash(params), params).unwrap();
537 assert_eq!(decrypt_note, *orig_note);
538 });
539
540 let thirdparty_restored = symcipher_decryption_keys(eta3, encrypted.as_slice(), params).unwrap();
542 assert_eq!(thirdparty_restored.len(), 0);
543 }
544}