1use confium_tc::paillier::{
27 PaillierError, PaillierKeypair, PaillierPrivateKey, PaillierPublicKey, add as paillier_add,
28 decrypt as paillier_decrypt, encrypt as paillier_encrypt, scalar_mul as paillier_scalar_mul,
29};
30use num_bigint::{BigUint, RandBigInt};
31use num_traits::Zero;
32use rand::rngs::OsRng;
33
34use crate::mta_proofs::CommitmentKey;
35use crate::mta_proofs::RangeProof;
36use crate::mta_proofs::RespondentProof;
37use crate::mta_proofs::prove_range;
38use crate::mta_proofs::prove_respondent;
39use crate::mta_proofs::verify_range;
40use crate::mta_proofs::verify_respondent;
41
42#[derive(Debug, Clone)]
46pub struct ProvedMessage1 {
47 pub ciphertext: BigUint,
49 pub range_proof: RangeProof,
51}
52
53#[derive(Debug, Clone)]
55pub struct ProvedMessage2 {
56 pub ciphertext: BigUint,
58 pub respondent_proof: RespondentProof,
61 pub beta: BigUint,
63}
64
65#[derive(Debug)]
67pub enum MtaProofError {
68 Paillier(PaillierError),
70 InvalidProof,
73}
74
75impl std::fmt::Display for MtaProofError {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 match self {
78 Self::Paillier(e) => write!(f, "paillier error: {e}"),
79 Self::InvalidProof => write!(f, "MtA ZK proof failed verification"),
80 }
81 }
82}
83
84impl std::error::Error for MtaProofError {}
85
86impl From<PaillierError> for MtaProofError {
87 fn from(e: PaillierError) -> Self {
88 Self::Paillier(e)
89 }
90}
91
92pub fn party_i_init_proved(
99 i_public: &PaillierPublicKey,
100 ck_j: &CommitmentKey,
101 q: &BigUint,
102 k_i: &BigUint,
103) -> Result<ProvedMessage1, MtaProofError> {
104 let r = random_below(&i_public.n);
105 let ciphertext = paillier_encrypt(i_public, k_i, &r)?;
106 let range_proof = prove_range(q, i_public, ck_j, &ciphertext, k_i, &r);
107 Ok(ProvedMessage1 {
108 ciphertext,
109 range_proof,
110 })
111}
112
113pub fn party_j_respond_proved(
123 i_public: &PaillierPublicKey,
124 ck_i: &CommitmentKey,
125 ck_j: &CommitmentKey,
126 q: &BigUint,
127 msg: &ProvedMessage1,
128 x_j: &BigUint,
129) -> Result<(ProvedMessage2, BigUint), MtaProofError> {
130 if !verify_range(q, i_public, ck_j, &msg.ciphertext, &msg.range_proof) {
131 return Err(MtaProofError::InvalidProof);
132 }
133
134 let q5 = {
137 let q2 = q * q;
138 &q2 * &q2 * q
139 };
140 let mut rng = OsRng;
141 let beta_prime = rng.gen_biguint_range(&BigUint::zero(), &q5);
142
143 let c_mul = paillier_scalar_mul(i_public, &msg.ciphertext, x_j);
145 let r_prime = random_below(&i_public.n);
146 let c_beta = paillier_encrypt(i_public, &beta_prime, &r_prime)?;
147 let c_prime = paillier_add(i_public, &c_mul, &c_beta);
148
149 let respondent_proof = prove_respondent(
150 q,
151 i_public,
152 ck_i,
153 &msg.ciphertext,
154 &c_prime,
155 x_j,
156 &beta_prime,
157 &r_prime,
158 );
159
160 Ok((
161 ProvedMessage2 {
162 ciphertext: c_prime,
163 respondent_proof,
164 beta: beta_prime.clone(),
165 },
166 beta_prime,
167 ))
168}
169
170pub fn party_i_finish_proved(
178 i_public: &PaillierPublicKey,
179 ck_i: &CommitmentKey,
180 q: &BigUint,
181 msg1_ciphertext: &BigUint,
182 i_private: &PaillierPrivateKey,
183 msg: &ProvedMessage2,
184) -> Result<BigUint, MtaProofError> {
185 if !verify_respondent(
186 q,
187 i_public,
188 ck_i,
189 msg1_ciphertext,
190 &msg.ciphertext,
191 &msg.respondent_proof,
192 ) {
193 return Err(MtaProofError::InvalidProof);
194 }
195 let alpha = paillier_decrypt(i_private, i_public, &msg.ciphertext)?;
196 Ok(alpha)
197}
198
199pub fn full_mta_proved(
208 i_keypair: &PaillierKeypair,
209 ck_i: &CommitmentKey,
210 ck_j: &CommitmentKey,
211 q: &BigUint,
212 k_i: &BigUint,
213 x_j: &BigUint,
214) -> Result<(BigUint, BigUint), MtaProofError> {
215 let msg1 = party_i_init_proved(&i_keypair.public, ck_j, q, k_i)?;
216 let (msg2, beta) = party_j_respond_proved(&i_keypair.public, ck_i, ck_j, q, &msg1, x_j)?;
217 let alpha = party_i_finish_proved(
218 &i_keypair.public,
219 ck_i,
220 q,
221 &msg1.ciphertext,
222 &i_keypair.private,
223 &msg2,
224 )?;
225 Ok((alpha, beta))
226}
227
228fn random_below(n: &BigUint) -> BigUint {
229 let mut rng = OsRng;
230 loop {
231 let r = rng.gen_biguint(n.bits());
232 if r < *n && !r.is_zero() {
233 return r;
234 }
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use confium_tc::paillier::generate_keypair;
242
243 pub(super) fn shared_fixtures()
248 -> &'static (PaillierKeypair, CommitmentKey, CommitmentKey, BigUint) {
249 use std::sync::OnceLock;
250 static FIX: OnceLock<(PaillierKeypair, CommitmentKey, CommitmentKey, BigUint)> =
251 OnceLock::new();
252 FIX.get_or_init(|| {
253 let kp = generate_keypair(642);
254 let ck_i = crate::mta_proofs::generate_commitment_key(64);
257 let ck_j = crate::mta_proofs::generate_commitment_key(64);
258 let q = BigUint::parse_bytes(
260 b"ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
261 16,
262 )
263 .unwrap();
264 (kp, ck_i, ck_j, q)
265 })
266 }
267
268 #[test]
269 fn proved_mta_shares_subtract_to_product() {
270 let (kp, ck_i, ck_j, q) = shared_fixtures();
271 let k_i = BigUint::from(42u32);
272 let x_j = BigUint::from(17u32);
273 let (alpha, beta) = full_mta_proved(kp, ck_i, ck_j, q, &k_i, &x_j).unwrap();
274 assert_eq!((&alpha - &beta) % q, (&k_i * &x_j) % q);
276 }
277
278 #[test]
279 fn proved_mta_no_modular_wrap() {
280 let (kp, ck_i, ck_j, q) = shared_fixtures();
282 let k_i = q - BigUint::from(1u32);
283 let x_j = q - BigUint::from(2u32);
284 let (alpha, beta) = full_mta_proved(kp, ck_i, ck_j, q, &k_i, &x_j).unwrap();
285 assert!(alpha < kp.public.n);
286 assert_eq!((&alpha - &beta) % q, (&k_i * &x_j) % q);
287 }
288
289 #[test]
290 fn proved_mta_multiple_pairs() {
291 let (kp, ck_i, ck_j, q) = shared_fixtures();
292 for (k, x) in [(10u32, 20u32), (100u32, 50u32), (7u32, 13u32)] {
293 let k_i = BigUint::from(k);
294 let x_j = BigUint::from(x);
295 let (alpha, beta) = full_mta_proved(kp, ck_i, ck_j, q, &k_i, &x_j).unwrap();
296 assert_eq!((&alpha - &beta) % q, (&k_i * &x_j) % q, "pair ({k}, {x})");
297 }
298 }
299
300 #[test]
301 fn proved_mta_shares_neither_reveals_product() {
302 let (kp, ck_i, ck_j, q) = shared_fixtures();
303 let k_i = BigUint::from(42u32);
304 let x_j = BigUint::from(17u32);
305 let (alpha, beta) = full_mta_proved(kp, ck_i, ck_j, q, &k_i, &x_j).unwrap();
306 let product = &k_i * &x_j;
307 assert_ne!(alpha, product);
308 assert_ne!(beta, product);
309 }
310
311 #[test]
312 fn proved_mta_beta_is_random() {
313 let (kp, ck_i, ck_j, q) = shared_fixtures();
314 let k_i = BigUint::from(42u32);
315 let x_j = BigUint::from(17u32);
316 let (_, beta1) = full_mta_proved(kp, ck_i, ck_j, q, &k_i, &x_j).unwrap();
317 let (_, beta2) = full_mta_proved(kp, ck_i, ck_j, q, &k_i, &x_j).unwrap();
318 assert_ne!(beta1, beta2);
319 }
320}
321
322#[cfg(test)]
323mod adversarial_tests {
324 use super::*;
327
328 fn shared_fixtures() -> &'static (PaillierKeypair, CommitmentKey, CommitmentKey, BigUint) {
329 crate::paillier_mta::tests::shared_fixtures()
330 }
331
332 #[test]
333 fn responder_rejects_tampered_ciphertext() {
334 let (kp, ck_i, ck_j, q) = shared_fixtures();
337 let k_i = BigUint::from(42u32);
338 let x_j = BigUint::from(17u32);
339 let msg1 = party_i_init_proved(&kp.public, ck_j, q, &k_i).unwrap();
340 let (mut msg2, _) = party_j_respond_proved(&kp.public, ck_i, ck_j, q, &msg1, &x_j).unwrap();
341 msg2.ciphertext = &msg2.ciphertext * BigUint::from(2u32) % &kp.public.n_squared;
342 let err = party_i_finish_proved(&kp.public, ck_i, q, &msg1.ciphertext, &kp.private, &msg2);
343 assert!(matches!(err, Err(MtaProofError::InvalidProof)));
344 }
345
346 #[test]
347 fn responder_rejects_proof_for_different_statement() {
348 let (kp, ck_i, ck_j, q) = shared_fixtures();
351 let k_i = BigUint::from(42u32);
352 let x_j = BigUint::from(17u32);
353 let msg1 = party_i_init_proved(&kp.public, ck_j, q, &k_i).unwrap();
354 let (msg2, _) = party_j_respond_proved(&kp.public, ck_i, ck_j, q, &msg1, &x_j).unwrap();
355 let other_c1 = party_i_init_proved(&kp.public, ck_j, q, &BigUint::from(7u32))
356 .unwrap()
357 .ciphertext;
358 let err = party_i_finish_proved(&kp.public, ck_i, q, &other_c1, &kp.private, &msg2);
359 assert!(matches!(err, Err(MtaProofError::InvalidProof)));
360 }
361
362 #[test]
363 fn responder_rejects_tampered_response() {
364 let (kp, ck_i, ck_j, q) = shared_fixtures();
365 let k_i = BigUint::from(42u32);
366 let x_j = BigUint::from(17u32);
367 let msg1 = party_i_init_proved(&kp.public, ck_j, q, &k_i).unwrap();
368 let (mut msg2, _) = party_j_respond_proved(&kp.public, ck_i, ck_j, q, &msg1, &x_j).unwrap();
369 msg2.respondent_proof.s1 += BigUint::from(1u32);
370 let err = party_i_finish_proved(&kp.public, ck_i, q, &msg1.ciphertext, &kp.private, &msg2);
371 assert!(matches!(err, Err(MtaProofError::InvalidProof)));
372 }
373
374 #[test]
375 fn responder_rejects_secret_above_bound() {
376 let (kp, ck_i, _ck_j, q) = shared_fixtures();
381 let k_i = BigUint::from(42u32);
382 let x_j = {
383 let q2 = q * q;
384 &q2 * &q2 * q
385 };
386 let msg1 = party_i_init_proved(&kp.public, ck_i, q, &k_i).unwrap();
387
388 let mut rng = OsRng;
389 let beta_prime = rng.gen_biguint_range(&BigUint::zero(), &(q * q));
390 let r_prime = random_below(&kp.public.n);
391 let c_mul = paillier_scalar_mul(&kp.public, &msg1.ciphertext, &x_j);
392 let c_beta = paillier_encrypt(&kp.public, &beta_prime, &r_prime).unwrap();
393 let c_prime = paillier_add(&kp.public, &c_mul, &c_beta);
394 let proof = prove_respondent(
395 q,
396 &kp.public,
397 ck_i,
398 &msg1.ciphertext,
399 &c_prime,
400 &x_j,
401 &beta_prime,
402 &r_prime,
403 );
404 let msg2 = ProvedMessage2 {
405 ciphertext: c_prime,
406 respondent_proof: proof,
407 beta: beta_prime,
408 };
409 let err = party_i_finish_proved(&kp.public, ck_i, q, &msg1.ciphertext, &kp.private, &msg2);
410 assert!(matches!(err, Err(MtaProofError::InvalidProof)));
411 }
412
413 #[test]
414 fn initiator_rejects_out_of_range_plaintext() {
415 let (kp, _ck_i, ck_j, q) = shared_fixtures();
418 let q5 = {
419 let q2 = q * q;
420 &q2 * &q2 * q
421 };
422 let r = random_below(&kp.public.n);
423 let c = paillier_encrypt(&kp.public, &q5, &r).unwrap();
424 let proof = prove_range(q, &kp.public, ck_j, &c, &q5, &r);
425 assert!(!verify_range(q, &kp.public, ck_j, &c, &proof));
426 }
427
428 #[test]
429 fn initiator_rejects_proof_for_a_different_ciphertext() {
430 let (kp, _ck_i, ck_j, q) = shared_fixtures();
431 let m = BigUint::from(12345u32);
432 let r = random_below(&kp.public.n);
433 let c = paillier_encrypt(&kp.public, &m, &r).unwrap();
434 let proof = prove_range(q, &kp.public, ck_j, &c, &m, &r);
435 let other_c = paillier_encrypt(&kp.public, &m, &random_below(&kp.public.n)).unwrap();
436 assert!(!verify_range(q, &kp.public, ck_j, &other_c, &proof));
437 }
438
439 #[test]
440 fn initiator_rejects_wrong_commitment_key() {
441 let (kp, _ck_i, ck_j, q) = shared_fixtures();
444 let m = BigUint::from(12345u32);
445 let r = random_below(&kp.public.n);
446 let c = paillier_encrypt(&kp.public, &m, &r).unwrap();
447 let proof = prove_range(q, &kp.public, ck_j, &c, &m, &r);
448 let wrong_ck = crate::mta_proofs::generate_commitment_key(64);
449 assert!(!verify_range(q, &kp.public, &wrong_ck, &c, &proof));
450 }
451}