1use ark_ff::{BigInteger, PrimeField};
4use ethers_core::types::U256;
5
6use crate::bjj::{PublicKey, BASE8};
7use crate::error::CryptoError;
8use crate::field::{fr_to_u256, poseidon_hash, random_field, u256_to_fr};
9
10type BjjFr = ark_ed_on_bn254::Fr;
12
13pub struct RawDleqProof {
14 pub recipient_b: (U256, U256),
15 pub recipient_p: (U256, U256),
16 pub u: (U256, U256),
17 pub v: (U256, U256),
18 pub z: U256,
19}
20
21pub fn generate_dleq_proof(
23 recipient_sk: U256,
24 compliance_pk: (U256, U256),
25) -> Result<RawDleqProof, CryptoError> {
26 let mut b_bytes_be = [0u8; 32];
27 recipient_sk.to_big_endian(&mut b_bytes_be);
28 let b_fr = BjjFr::from_be_bytes_mod_order(&b_bytes_be);
29 let b_le = b_fr.into_bigint().to_bytes_le();
30
31 let b_point = BASE8.mul_scalar(&b_le)?;
33 let b_result = point_to_u256_pair(&b_point);
34
35 let c_point =
37 PublicKey::from_coordinates(u256_to_fr(compliance_pk.0), u256_to_fr(compliance_pk.1))?;
38 let p_point = c_point.mul_scalar(&b_le)?;
39 let p_result = point_to_u256_pair(&p_point);
40
41 let r_fr = {
42 let r_u256 = random_field();
43 let mut r_bytes = [0u8; 32];
44 r_u256.to_big_endian(&mut r_bytes);
45 BjjFr::from_be_bytes_mod_order(&r_bytes)
46 };
47 let r_le = r_fr.into_bigint().to_bytes_le();
48
49 let u_point = BASE8.mul_scalar(&r_le)?;
51 let u_result = point_to_u256_pair(&u_point);
52
53 let v_point = c_point.mul_scalar(&r_le)?;
55 let v_result = point_to_u256_pair(&v_point);
56
57 let g_x = fr_to_u256(BASE8.x());
58 let g_y = fr_to_u256(BASE8.y());
59
60 let e_u256 = poseidon_hash(&[
61 u_result.0,
62 u_result.1,
63 v_result.0,
64 v_result.1,
65 g_x,
66 g_y,
67 compliance_pk.0,
68 compliance_pk.1,
69 b_result.0,
70 b_result.1,
71 p_result.0,
72 p_result.1,
73 ]);
74
75 let mut e_bytes = [0u8; 32];
76 e_u256.to_big_endian(&mut e_bytes);
77 let e_fr = BjjFr::from_be_bytes_mod_order(&e_bytes);
78
79 let z_fr = r_fr + e_fr * b_fr;
80 let z_be = z_fr.into_bigint().to_bytes_be();
81 let z_u256 = U256::from_big_endian(&z_be);
82
83 Ok(RawDleqProof {
84 recipient_b: b_result,
85 recipient_p: p_result,
86 u: u_result,
87 v: v_result,
88 z: z_u256,
89 })
90}
91
92fn point_to_u256_pair(point: &PublicKey) -> (U256, U256) {
93 (fr_to_u256(point.x()), fr_to_u256(point.y()))
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99 use crate::bjj::SecretKey;
100
101 fn bjj_fr_to_le_bytes(fr: &BjjFr) -> Vec<u8> {
102 fr.into_bigint().to_bytes_le()
103 }
104
105 #[test]
106 fn test_dleq_proof_generation_and_verification() {
107 let mut rng = rand::thread_rng();
108
109 let compliance_sk = SecretKey::generate(&mut rng);
110 let compliance_pk = compliance_sk.public_key().expect("compliance pk");
111 let compliance_pk_tuple = (fr_to_u256(compliance_pk.x()), fr_to_u256(compliance_pk.y()));
112
113 let recipient_sk_val = random_field();
114
115 let proof = generate_dleq_proof(recipient_sk_val, compliance_pk_tuple)
116 .expect("DLEQ proof generation should succeed");
117
118 let g_x = fr_to_u256(BASE8.x());
120 let g_y = fr_to_u256(BASE8.y());
121 let e_u256 = poseidon_hash(&[
122 proof.u.0,
123 proof.u.1,
124 proof.v.0,
125 proof.v.1,
126 g_x,
127 g_y,
128 compliance_pk_tuple.0,
129 compliance_pk_tuple.1,
130 proof.recipient_b.0,
131 proof.recipient_b.1,
132 proof.recipient_p.0,
133 proof.recipient_p.1,
134 ]);
135
136 let mut e_bytes = [0u8; 32];
137 e_u256.to_big_endian(&mut e_bytes);
138 let e_fr = BjjFr::from_be_bytes_mod_order(&e_bytes);
139 let e_le = bjj_fr_to_le_bytes(&e_fr);
140
141 let mut z_bytes = [0u8; 32];
142 proof.z.to_big_endian(&mut z_bytes);
143 let z_fr = BjjFr::from_be_bytes_mod_order(&z_bytes);
144 let z_le = bjj_fr_to_le_bytes(&z_fr);
145 let z_g = BASE8.mul_scalar(&z_le).expect("[z]*G");
146 let b_point = PublicKey::new_unchecked(
147 u256_to_fr(proof.recipient_b.0),
148 u256_to_fr(proof.recipient_b.1),
149 );
150 let e_b = b_point.mul_scalar(&e_le).expect("[e]*B");
151 let u_point = PublicKey::new_unchecked(u256_to_fr(proof.u.0), u256_to_fr(proof.u.1));
152 let u_plus_eb = u_point.add(&e_b).expect("U + [e]*B");
153 assert_eq!(
154 z_g.x(),
155 u_plus_eb.x(),
156 "DLEQ check 1 failed: [z]*G.x != (U + [e]*B).x"
157 );
158 assert_eq!(
159 z_g.y(),
160 u_plus_eb.y(),
161 "DLEQ check 1 failed: [z]*G.y != (U + [e]*B).y"
162 );
163
164 let c_point = PublicKey::new_unchecked(
165 u256_to_fr(compliance_pk_tuple.0),
166 u256_to_fr(compliance_pk_tuple.1),
167 );
168 let z_c = c_point.mul_scalar(&z_le).expect("[z]*C");
169 let p_point = PublicKey::new_unchecked(
170 u256_to_fr(proof.recipient_p.0),
171 u256_to_fr(proof.recipient_p.1),
172 );
173 let e_p = p_point.mul_scalar(&e_le).expect("[e]*P");
174 let v_point = PublicKey::new_unchecked(u256_to_fr(proof.v.0), u256_to_fr(proof.v.1));
175 let v_plus_ep = v_point.add(&e_p).expect("V + [e]*P");
176 assert_eq!(
177 z_c.x(),
178 v_plus_ep.x(),
179 "DLEQ check 2 failed: [z]*C.x != (V + [e]*P).x"
180 );
181 assert_eq!(
182 z_c.y(),
183 v_plus_ep.y(),
184 "DLEQ check 2 failed: [z]*C.y != (V + [e]*P).y"
185 );
186 }
187
188 #[test]
189 fn test_dleq_wrong_witness_fails() {
190 let mut rng = rand::thread_rng();
191
192 let compliance_sk = SecretKey::generate(&mut rng);
193 let compliance_pk = compliance_sk.public_key().expect("compliance pk");
194 let compliance_pk_tuple = (fr_to_u256(compliance_pk.x()), fr_to_u256(compliance_pk.y()));
195
196 let recipient_sk_val = random_field();
197 let proof = generate_dleq_proof(recipient_sk_val, compliance_pk_tuple)
198 .expect("proof generation must succeed");
199
200 let tampered_z = proof.z + ethers_core::types::U256::from(1u64);
201 let g_x = fr_to_u256(BASE8.x());
202 let g_y = fr_to_u256(BASE8.y());
203 let e_u256 = poseidon_hash(&[
204 proof.u.0,
205 proof.u.1,
206 proof.v.0,
207 proof.v.1,
208 g_x,
209 g_y,
210 compliance_pk_tuple.0,
211 compliance_pk_tuple.1,
212 proof.recipient_b.0,
213 proof.recipient_b.1,
214 proof.recipient_p.0,
215 proof.recipient_p.1,
216 ]);
217
218 let mut e_bytes = [0u8; 32];
219 e_u256.to_big_endian(&mut e_bytes);
220 let e_fr = BjjFr::from_be_bytes_mod_order(&e_bytes);
221 let e_le = bjj_fr_to_le_bytes(&e_fr);
222
223 let mut tz_bytes = [0u8; 32];
224 tampered_z.to_big_endian(&mut tz_bytes);
225 let tz_fr = BjjFr::from_be_bytes_mod_order(&tz_bytes);
226 let tz_le = bjj_fr_to_le_bytes(&tz_fr);
227
228 let zp_g = BASE8.mul_scalar(&tz_le).expect("[z']*G");
229 let b_point = PublicKey::new_unchecked(
230 u256_to_fr(proof.recipient_b.0),
231 u256_to_fr(proof.recipient_b.1),
232 );
233 let e_b = b_point.mul_scalar(&e_le).expect("[e]*B");
234 let u_point = PublicKey::new_unchecked(u256_to_fr(proof.u.0), u256_to_fr(proof.u.1));
235 let u_plus_eb = u_point.add(&e_b).expect("U + [e]*B");
236
237 assert!(
238 zp_g.x() != u_plus_eb.x() || zp_g.y() != u_plus_eb.y(),
239 "Tampered z must fail DLEQ verification -- soundness violation!"
240 );
241 }
242}