1use ethers::types::U256;
5
6pub use darkpool_crypto::{
8 address_to_field, aes128_decrypt, aes128_encrypt, bjj_is_on_curve, bjj_scalar_mul,
9 derive_public_key_from_sk, derive_shared_secret_bjj, field_to_address, fr_to_u256,
10 from_noir_hex, kdf_to_aes_key_iv, poseidon_hash, random_bjj_scalar, random_field, string_to_fr,
11 to_noir_decimal, to_noir_hex, u256_to_fr, CryptoError,
12};
13
14use crate::proof_inputs::NotePlaintext;
15
16#[must_use]
18pub fn derive_nullifier_path_a(note_nullifier: U256) -> U256 {
19 poseidon_hash(&[note_nullifier])
20}
21
22#[must_use]
24pub fn derive_nullifier_path_b(shared_secret: U256, commitment: U256, leaf_index: u64) -> U256 {
25 poseidon_hash(&[shared_secret, commitment, U256::from(leaf_index)])
26}
27
28#[must_use]
29pub fn calculate_public_memo_id(
30 value: U256,
31 asset_id: U256,
32 timelock: U256,
33 owner_x: U256,
34 owner_y: U256,
35 salt: U256,
36) -> U256 {
37 poseidon_hash(&[value, asset_id, timelock, owner_x, owner_y, salt])
38}
39
40#[must_use]
42pub fn pack_note_plaintext(note: &NotePlaintext) -> [u8; 192] {
43 let mut buf = [0u8; 192];
44 let mut offset = 0;
45
46 for field in [
47 note.asset_id,
48 note.value,
49 note.secret,
50 note.nullifier,
51 note.timelock,
52 note.hashlock,
53 ] {
54 field.to_big_endian(&mut buf[offset..offset + 32]);
55 offset += 32;
56 }
57
58 buf
59}
60
61#[must_use]
62pub fn unpack_note_plaintext(bytes: &[u8; 192]) -> NotePlaintext {
63 let asset_id = U256::from_big_endian(&bytes[0..32]);
64 let value = U256::from_big_endian(&bytes[32..64]);
65 let secret = U256::from_big_endian(&bytes[64..96]);
66 let nullifier = U256::from_big_endian(&bytes[96..128]);
67 let timelock = U256::from_big_endian(&bytes[128..160]);
68 let hashlock = U256::from_big_endian(&bytes[160..192]);
69
70 NotePlaintext {
71 value,
72 asset_id,
73 secret,
74 nullifier,
75 timelock,
76 hashlock,
77 }
78}
79
80#[must_use]
82pub fn pack_ciphertext_to_fields(ciphertext: &[u8; 208]) -> [U256; 7] {
83 let mut fields = [U256::zero(); 7];
84 let mut idx = 0;
85
86 for (p, field) in fields.iter_mut().enumerate() {
87 let bytes_in_this = if p < 6 { 31 } else { 22 };
88 let mut val = U256::zero();
89
90 for i in (0..bytes_in_this).rev() {
91 val <<= 8;
92 val += U256::from(ciphertext[idx + i]);
93 }
94
95 *field = val;
96 idx += bytes_in_this;
97 }
98
99 fields
100}
101
102#[must_use]
103pub fn unpack_ciphertext_from_fields(packed: &[U256; 7]) -> [u8; 208] {
104 let mut ciphertext = [0u8; 208];
105 let mut idx = 0;
106
107 for (p, &val) in packed.iter().enumerate() {
108 let mut val = val;
109 let bytes_in_this = if p < 6 { 31 } else { 22 };
110
111 for _ in 0..bytes_in_this {
112 ciphertext[idx] = (val % U256::from(256)).as_u32() as u8;
113 val /= U256::from(256);
114 idx += 1;
115 }
116 }
117
118 ciphertext
119}
120
121pub fn encrypt_note_for_deposit_aes(
123 ephemeral_sk: U256,
124 compliance_pk: (U256, U256),
125 note: &NotePlaintext,
126) -> Result<([U256; 7], (U256, U256)), CryptoError> {
127 let shared_x = derive_shared_secret_bjj(ephemeral_sk, compliance_pk)?;
128 let (key, iv) = kdf_to_aes_key_iv(shared_x);
129 let plaintext = pack_note_plaintext(note);
130 let ciphertext = aes128_encrypt(&plaintext, &key, &iv);
131 let fields = pack_ciphertext_to_fields(&ciphertext);
132 let epk = derive_public_key_from_sk(ephemeral_sk)?;
133 Ok((fields, epk))
134}
135
136pub struct MemoEncryptionResult {
137 pub packed_ciphertext: [U256; 7],
138 pub ephemeral_pk: (U256, U256),
139 pub int_bob: (U256, U256),
141 pub int_carol: (U256, U256),
143}
144
145pub fn encrypt_memo_note_3party(
148 ephemeral_sk: U256,
149 recipient_p: (U256, U256), recipient_b: (U256, U256), compliance_pk: (U256, U256), note: &NotePlaintext,
153) -> Result<MemoEncryptionResult, CryptoError> {
154 let s_point = bjj_scalar_mul(ephemeral_sk, recipient_p)?;
155 let shared_secret = s_point.0;
156
157 let (key, iv) = kdf_to_aes_key_iv(shared_secret);
158 let plaintext = pack_note_plaintext(note);
159 let ciphertext = aes128_encrypt(&plaintext, &key, &iv);
160 let packed = pack_ciphertext_to_fields(&ciphertext);
161
162 let epk = derive_public_key_from_sk(ephemeral_sk)?;
163 let int_bob = bjj_scalar_mul(ephemeral_sk, compliance_pk)?;
164 let int_carol = bjj_scalar_mul(ephemeral_sk, recipient_b)?;
165
166 Ok(MemoEncryptionResult {
167 packed_ciphertext: packed,
168 ephemeral_pk: epk,
169 int_bob,
170 int_carol,
171 })
172}
173
174pub fn decrypt_note_from_fields(
175 packed: &[U256; 7],
176 ephemeral_sk: U256,
177 compliance_pk: (U256, U256),
178) -> Result<NotePlaintext, CryptoError> {
179 let ciphertext = unpack_ciphertext_from_fields(packed);
180 let shared_x = derive_shared_secret_bjj(ephemeral_sk, compliance_pk)?;
181 let (key, iv) = kdf_to_aes_key_iv(shared_x);
182 let plaintext = aes128_decrypt(&ciphertext, &key, &iv)?;
183 Ok(unpack_note_plaintext(&plaintext))
184}
185
186pub struct DleqResult {
187 pub recipient_b: (U256, U256),
188 pub recipient_p: (U256, U256),
189 pub proof: crate::proof_inputs::DLEQProof,
190}
191
192pub fn generate_dleq_proof(
193 recipient_sk: U256,
194 compliance_pk: (U256, U256),
195) -> Result<DleqResult, CryptoError> {
196 let raw = darkpool_crypto::generate_dleq_proof(recipient_sk, compliance_pk)?;
197 Ok(DleqResult {
198 recipient_b: raw.recipient_b,
199 recipient_p: raw.recipient_p,
200 proof: crate::proof_inputs::DLEQProof {
201 u: raw.u,
202 v: raw.v,
203 z: raw.z,
204 },
205 })
206}
207
208pub fn recipient_decrypt_3party(
210 recipient_sk: U256,
211 intermediate_point: (U256, U256),
212 packed_ciphertext: &[U256; 7],
213) -> Result<(NotePlaintext, U256), CryptoError> {
214 let shared_point = bjj_scalar_mul(recipient_sk, intermediate_point)?;
215 let shared_secret = shared_point.0;
216 let (key, iv) = kdf_to_aes_key_iv(shared_secret);
217 let ciphertext = unpack_ciphertext_from_fields(packed_ciphertext);
218 let plaintext = aes128_decrypt(&ciphertext, &key, &iv)?;
219 Ok((unpack_note_plaintext(&plaintext), shared_secret))
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_nullifier_derivation() {
228 let nullifier = U256::from(12345);
229 let hash_a = derive_nullifier_path_a(nullifier);
230 assert!(!hash_a.is_zero());
231
232 let shared = U256::from(111);
233 let commitment = U256::from(222);
234 let hash_b = derive_nullifier_path_b(shared, commitment, 5);
235 assert!(!hash_b.is_zero());
236 assert_ne!(hash_a, hash_b);
237 }
238
239 #[test]
240 fn test_note_plaintext_packing_roundtrip() {
241 let note = NotePlaintext {
242 asset_id: U256::from(1),
243 value: U256::from(1000),
244 secret: U256::from(12345),
245 nullifier: U256::from(67890),
246 timelock: U256::from(0),
247 hashlock: U256::from(0),
248 };
249
250 let packed = pack_note_plaintext(¬e);
251 assert_eq!(packed.len(), 192);
252
253 let unpacked = unpack_note_plaintext(&packed);
254 assert_eq!(unpacked.asset_id, note.asset_id);
255 assert_eq!(unpacked.value, note.value);
256 assert_eq!(unpacked.secret, note.secret);
257 assert_eq!(unpacked.nullifier, note.nullifier);
258 assert_eq!(unpacked.timelock, note.timelock);
259 assert_eq!(unpacked.hashlock, note.hashlock);
260 }
261
262 #[test]
263 fn test_ciphertext_field_packing_roundtrip() {
264 let mut ciphertext = [0u8; 208];
266 for (i, byte) in ciphertext.iter_mut().enumerate() {
267 *byte = (i % 256) as u8;
268 }
269
270 let fields = pack_ciphertext_to_fields(&ciphertext);
271 assert_eq!(fields.len(), 7);
272
273 let unpacked = unpack_ciphertext_from_fields(&fields);
274 assert_eq!(unpacked, ciphertext);
275 }
276
277 #[test]
278 fn test_ciphertext_field_sizes() {
279 let ciphertext = [0xffu8; 208];
280 let fields = pack_ciphertext_to_fields(&ciphertext);
281
282 for (i, field) in fields[..6].iter().enumerate() {
285 assert!(
286 !field.is_zero(),
287 "Field {} should not be zero for 0xff input",
288 i
289 );
290 }
291 assert!(!fields[6].is_zero());
292 }
293
294 #[test]
295 fn test_full_note_encryption_decryption() {
296 use darkpool_crypto::BASE8;
297
298 let compliance_sk = U256::from(987654321u64);
300 let mut sk_bytes = [0u8; 32];
301 compliance_sk.to_big_endian(&mut sk_bytes);
302 sk_bytes.reverse();
303 let compliance_pk_point = BASE8.mul_scalar(&sk_bytes).expect("valid test key");
304 let compliance_pk = (
305 fr_to_u256(compliance_pk_point.x()),
306 fr_to_u256(compliance_pk_point.y()),
307 );
308
309 let note = NotePlaintext {
311 asset_id: U256::from(0x123456789abcdef0u64),
312 value: U256::from(1_000_000_000_000_000_000u64), secret: random_field(),
314 nullifier: random_field(),
315 timelock: U256::zero(),
316 hashlock: U256::zero(),
317 };
318
319 let ephemeral_sk = U256::from(12345678u64);
321
322 let (packed_fields, epk) = encrypt_note_for_deposit_aes(ephemeral_sk, compliance_pk, ¬e)
324 .expect("encryption should succeed");
325
326 assert_eq!(packed_fields.len(), 7);
327 assert!(!epk.0.is_zero() || !epk.1.is_zero());
328
329 let decrypted = decrypt_note_from_fields(&packed_fields, ephemeral_sk, compliance_pk)
331 .expect("decryption should succeed");
332
333 assert_eq!(decrypted.asset_id, note.asset_id);
334 assert_eq!(decrypted.value, note.value);
335 assert_eq!(decrypted.secret, note.secret);
336 assert_eq!(decrypted.nullifier, note.nullifier);
337 assert_eq!(decrypted.timelock, note.timelock);
338 assert_eq!(decrypted.hashlock, note.hashlock);
339 }
340}