1use confium_tc::paillier::PaillierPublicKey;
39use confium_tc::paillier::generate_prime;
40use confium_tc::paillier::miller_rabin;
41use num_bigint::BigUint;
42use num_bigint::RandBigInt;
43use num_traits::One;
44use num_traits::Zero;
45use rand::rngs::OsRng;
46use sha2::Digest;
47use sha2::Sha256;
48
49#[derive(Debug, Clone)]
51pub struct CommitmentKey {
52 pub n_tilde: BigUint,
54 pub h1: BigUint,
56 pub h2: BigUint,
58}
59
60pub fn generate_commitment_key(prime_bits: u32) -> CommitmentKey {
67 let p_tilde = generate_safe_prime(prime_bits);
68 let q_tilde = loop {
69 let candidate = generate_safe_prime(prime_bits);
70 if candidate != p_tilde {
71 break candidate;
72 }
73 };
74 let n_tilde = &p_tilde * &q_tilde;
75
76 let mut rng = OsRng;
77 let h1 = {
80 let x = rng.gen_biguint_range(&BigUint::one(), &n_tilde);
81 (&x * &x) % &n_tilde
82 };
83 let w = rng.gen_biguint_range(&BigUint::one(), &n_tilde);
86 let h2 = h1.modpow(&w, &n_tilde);
87
88 CommitmentKey { n_tilde, h1, h2 }
89}
90
91pub fn p256_order() -> num_bigint::BigUint {
93 num_bigint::BigUint::parse_bytes(
94 b"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
95 16,
96 )
97 .expect("static hex parses")
98}
99
100fn generate_safe_prime(prime_bits: u32) -> BigUint {
101 loop {
102 let p = generate_prime(prime_bits);
103 let candidate = (&p << 1u32) + BigUint::one();
104 if miller_rabin(&candidate, 20) {
105 return candidate;
106 }
107 }
108}
109
110#[derive(Debug, Clone)]
113pub struct RangeProof {
114 pub z: BigUint,
116 pub u: BigUint,
118 pub w: BigUint,
120 pub s: BigUint,
122 pub s1: BigUint,
124 pub s2: BigUint,
126}
127
128pub fn prove_range(
132 q: &BigUint,
133 paillier: &PaillierPublicKey,
134 ck: &CommitmentKey,
135 c: &BigUint,
136 m: &BigUint,
137 r: &BigUint,
138) -> RangeProof {
139 let q3 = q * q * q;
140 let q2 = q * q;
141 let q_n_tilde = q * &ck.n_tilde;
142 let q3_n_tilde = &q3 * &ck.n_tilde;
143
144 let mut rng = OsRng;
145 let alpha = rng.gen_biguint_range(&BigUint::zero(), &(&q3 - &q2));
148 let beta = rng.gen_biguint_range(&BigUint::one(), &paillier.n);
149 let gamma = rng.gen_biguint_range(&BigUint::zero(), &(&q3_n_tilde - &q_n_tilde));
150 let rho = rng.gen_biguint_range(&BigUint::zero(), &q_n_tilde);
151
152 let z = (&ck.h1.modpow(m, &ck.n_tilde) * &ck.h2.modpow(&rho, &ck.n_tilde)) % &ck.n_tilde;
153 let u = (&paillier.g.modpow(&alpha, &paillier.n_squared)
154 * &beta.modpow(&paillier.n, &paillier.n_squared))
155 % &paillier.n_squared;
156 let w = (&ck.h1.modpow(&alpha, &ck.n_tilde) * &ck.h2.modpow(&gamma, &ck.n_tilde)) % &ck.n_tilde;
157
158 let e = range_challenge(q, paillier, ck, c, &z, &u, &w);
159
160 let s = (r.modpow(&e, &paillier.n) * &beta) % &paillier.n;
161 let s1 = &e * m + α
162 let s2 = &e * &rho + γ
163
164 RangeProof { z, u, w, s, s1, s2 }
165}
166
167pub fn verify_range(
169 q: &BigUint,
170 paillier: &PaillierPublicKey,
171 ck: &CommitmentKey,
172 c: &BigUint,
173 proof: &RangeProof,
174) -> bool {
175 let q3 = q * q * q;
176 if proof.s1 > q3 {
179 return false;
180 }
181 if proof.s.is_zero() || proof.s >= paillier.n || proof.s1.is_zero() {
182 return false;
183 }
184
185 let e = range_challenge(q, paillier, ck, c, &proof.z, &proof.u, &proof.w);
186
187 let c_inv = match modinv(&to_bigint_helper(c), &to_bigint_helper(&paillier.n_squared)) {
189 Some(inv) => inv.to_biguint().unwrap(),
190 None => return false,
191 };
192 let lhs = (&paillier.g.modpow(&proof.s1, &paillier.n_squared)
193 * &proof.s.modpow(&paillier.n, &paillier.n_squared)
194 % &paillier.n_squared
195 * &c_inv.modpow(&e, &paillier.n_squared))
196 % &paillier.n_squared;
197 if lhs != proof.u {
198 return false;
199 }
200
201 let z_inv = match modinv(&to_bigint_helper(&proof.z), &to_bigint_helper(&ck.n_tilde)) {
203 Some(inv) => inv.to_biguint().unwrap(),
204 None => return false,
205 };
206 let lhs2 = (&ck.h1.modpow(&proof.s1, &ck.n_tilde) * &ck.h2.modpow(&proof.s2, &ck.n_tilde)
207 % &ck.n_tilde
208 * &z_inv.modpow(&e, &ck.n_tilde))
209 % &ck.n_tilde;
210 lhs2 == proof.w
211}
212
213#[derive(Debug, Clone)]
216pub struct RespondentProof {
217 pub z: BigUint,
219 pub z_prime: BigUint,
221 pub t: BigUint,
223 pub v: BigUint,
225 pub w: BigUint,
227 pub s: BigUint,
229 pub s1: BigUint,
231 pub s2: BigUint,
233 pub t1: BigUint,
235 pub t2: BigUint,
237}
238
239#[allow(clippy::too_many_arguments)]
244pub fn prove_respondent(
245 q: &BigUint,
246 paillier: &PaillierPublicKey,
247 ck: &CommitmentKey,
248 c1: &BigUint,
249 c2: &BigUint,
250 x: &BigUint,
251 y: &BigUint,
252 r: &BigUint,
253) -> RespondentProof {
254 let q3 = q * q * q;
255 let q7 = &q3 * &q3 * q;
256 let q6 = &q3 * &q3;
257 let q2 = q * q;
258 let q_n_tilde = q * &ck.n_tilde;
259 let q3_n_tilde = &q3 * &ck.n_tilde;
260
261 let mut rng = OsRng;
262 let alpha = rng.gen_biguint_range(&BigUint::zero(), &(&q3 - &q2));
263 let rho = rng.gen_biguint_range(&BigUint::zero(), &q_n_tilde);
264 let rho_prime = rng.gen_biguint_range(&BigUint::zero(), &(&q3_n_tilde - &q_n_tilde));
265 let sigma = rng.gen_biguint_range(&BigUint::zero(), &q_n_tilde);
266 let beta = rng.gen_biguint_range(&BigUint::one(), &paillier.n);
267 let gamma = rng.gen_biguint_range(&BigUint::zero(), &(&q7 - &q6));
269 let tau = rng.gen_biguint_range(&BigUint::zero(), &(&q3_n_tilde - &q_n_tilde));
270
271 let n_tilde = &ck.n_tilde;
272 let h_pow = |a: &BigUint, b: &BigUint| ck.h1.modpow(a, n_tilde) * &ck.h2.modpow(b, n_tilde);
273
274 let z = h_pow(x, &rho) % n_tilde;
275 let z_prime = h_pow(&alpha, &rho_prime) % n_tilde;
276 let t = h_pow(y, &sigma) % n_tilde;
277 let v = (&c1.modpow(&alpha, &paillier.n_squared)
278 * &paillier.g.modpow(&gamma, &paillier.n_squared)
279 % &paillier.n_squared
280 * &beta.modpow(&paillier.n, &paillier.n_squared))
281 % &paillier.n_squared;
282 let w = h_pow(&gamma, &tau) % n_tilde;
283
284 let e = respondent_challenge(q, paillier, ck, c1, c2, &z, &z_prime, &t, &v, &w);
285
286 let s = (r.modpow(&e, &paillier.n) * &beta) % &paillier.n;
287 let s1 = &e * x + α
288 let s2 = &e * &rho + &rho_prime;
289 let t1 = &e * y + γ
290 let t2 = &e * &sigma + τ
291
292 RespondentProof {
293 z,
294 z_prime,
295 t,
296 v,
297 w,
298 s,
299 s1,
300 s2,
301 t1,
302 t2,
303 }
304}
305
306#[allow(clippy::too_many_arguments)]
308pub fn verify_respondent(
309 q: &BigUint,
310 paillier: &PaillierPublicKey,
311 ck: &CommitmentKey,
312 c1: &BigUint,
313 c2: &BigUint,
314 proof: &RespondentProof,
315) -> bool {
316 let q3 = q * q * q;
317 let q7 = &q3 * &q3 * q;
318 if proof.s1 > q3 || proof.t1 > q7 {
319 return false;
320 }
321 if proof.s.is_zero() || proof.s >= paillier.n || proof.s1.is_zero() || proof.t1.is_zero() {
322 return false;
323 }
324
325 let e = respondent_challenge(
326 q,
327 paillier,
328 ck,
329 c1,
330 c2,
331 &proof.z,
332 &proof.z_prime,
333 &proof.t,
334 &proof.v,
335 &proof.w,
336 );
337
338 let n_tilde = &ck.n_tilde;
340 let z_inv = match modinv(&to_bigint_helper(&proof.z), &to_bigint_helper(n_tilde)) {
341 Some(inv) => inv.to_biguint().unwrap(),
342 None => return false,
343 };
344 let lhs1 = (&ck.h1.modpow(&proof.s1, n_tilde) * &ck.h2.modpow(&proof.s2, n_tilde) % n_tilde
345 * &z_inv.modpow(&e, n_tilde))
346 % n_tilde;
347 if lhs1 != proof.z_prime {
348 return false;
349 }
350
351 let t_inv = match modinv(&to_bigint_helper(&proof.t), &to_bigint_helper(n_tilde)) {
353 Some(inv) => inv.to_biguint().unwrap(),
354 None => return false,
355 };
356 let lhs2 = (&ck.h1.modpow(&proof.t1, n_tilde) * &ck.h2.modpow(&proof.t2, n_tilde) % n_tilde
357 * &t_inv.modpow(&e, n_tilde))
358 % n_tilde;
359 if lhs2 != proof.w {
360 return false;
361 }
362
363 let c2_inv = match modinv(
365 &to_bigint_helper(c2),
366 &to_bigint_helper(&paillier.n_squared),
367 ) {
368 Some(inv) => inv.to_biguint().unwrap(),
369 None => return false,
370 };
371 let lhs3 = (&c1.modpow(&proof.s1, &paillier.n_squared)
372 * &proof.s.modpow(&paillier.n, &paillier.n_squared)
373 % &paillier.n_squared
374 * &paillier.g.modpow(&proof.t1, &paillier.n_squared)
375 % &paillier.n_squared
376 * &c2_inv.modpow(&e, &paillier.n_squared))
377 % &paillier.n_squared;
378 lhs3 == proof.v
379}
380
381fn range_challenge(
384 q: &BigUint,
385 paillier: &PaillierPublicKey,
386 ck: &CommitmentKey,
387 c: &BigUint,
388 z: &BigUint,
389 u: &BigUint,
390 w: &BigUint,
391) -> BigUint {
392 let mut hasher = Sha256::new();
393 hasher.update(b"confium-mta-range-v1");
394 hasher.update(q.to_bytes_be());
395 hasher.update(paillier.n.to_bytes_be());
396 hasher.update(paillier.g.to_bytes_be());
397 hasher.update(ck.n_tilde.to_bytes_be());
398 hasher.update(ck.h1.to_bytes_be());
399 hasher.update(ck.h2.to_bytes_be());
400 hasher.update(c.to_bytes_be());
401 hasher.update(z.to_bytes_be());
402 hasher.update(u.to_bytes_be());
403 hasher.update(w.to_bytes_be());
404 challenge_from_hash(hasher, q)
405}
406
407#[allow(clippy::too_many_arguments)]
410fn respondent_challenge(
411 q: &BigUint,
412 paillier: &PaillierPublicKey,
413 ck: &CommitmentKey,
414 c1: &BigUint,
415 c2: &BigUint,
416 z: &BigUint,
417 z_prime: &BigUint,
418 t: &BigUint,
419 v: &BigUint,
420 w: &BigUint,
421) -> BigUint {
422 let mut hasher = Sha256::new();
423 hasher.update(b"confium-mta-respondent-v1");
424 hasher.update(q.to_bytes_be());
425 hasher.update(paillier.n.to_bytes_be());
426 hasher.update(paillier.g.to_bytes_be());
427 hasher.update(ck.n_tilde.to_bytes_be());
428 hasher.update(ck.h1.to_bytes_be());
429 hasher.update(ck.h2.to_bytes_be());
430 hasher.update(c1.to_bytes_be());
431 hasher.update(c2.to_bytes_be());
432 hasher.update(z.to_bytes_be());
433 hasher.update(z_prime.to_bytes_be());
434 hasher.update(t.to_bytes_be());
435 hasher.update(v.to_bytes_be());
436 hasher.update(w.to_bytes_be());
437 challenge_from_hash(hasher, q)
438}
439
440fn challenge_from_hash(hasher: Sha256, q: &BigUint) -> BigUint {
443 let mut current: Vec<u8> = hasher.finalize().to_vec();
446 loop {
447 let candidate = BigUint::from_bytes_be(¤t);
448 if &candidate < q {
449 return candidate;
450 }
451 let mut h = Sha256::new();
452 h.update(b"confium-mta-challenge-reduce-v1");
453 h.update(¤t);
454 current = h.finalize().to_vec();
455 }
456}
457
458fn to_bigint_helper(x: &BigUint) -> num_bigint::BigInt {
460 use num_bigint::ToBigInt;
461 x.to_bigint().unwrap_or_default()
462}
463
464fn modinv(a: &num_bigint::BigInt, m: &num_bigint::BigInt) -> Option<num_bigint::BigInt> {
465 use num_bigint::BigInt;
466 let (g, x, _) = extended_gcd(a, m);
467 if g != BigInt::one() {
468 return None;
469 }
470 let r = &x % m;
471 if r.sign() == num_bigint::Sign::Minus {
472 Some(r + m)
473 } else {
474 Some(r)
475 }
476}
477
478fn extended_gcd(
479 a: &num_bigint::BigInt,
480 b: &num_bigint::BigInt,
481) -> (num_bigint::BigInt, num_bigint::BigInt, num_bigint::BigInt) {
482 use num_bigint::BigInt;
483 if b.is_zero() {
484 return (a.clone(), BigInt::one(), BigInt::zero());
485 }
486 let (g, x, y) = extended_gcd(b, &(a % b));
487 (g, y.clone(), x - (a / b) * y)
488}