1use fast_paillier::backend::Integer;
100use fast_paillier::{AnyEncryptionKey, Ciphertext, Nonce, Plaintext};
101use generic_ec::{Curve, Point, Scalar};
102
103#[cfg(feature = "serde")]
104use serde::{Deserialize, Serialize};
105
106pub use crate::common::Aux;
107pub use crate::common::InvalidProof;
108
109#[derive(Debug, Clone, udigest::Digestable)]
112#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
113pub struct SecurityParams {
114 pub l: usize,
116 pub epsilon: usize,
118}
119
120#[derive(Debug, Clone, Copy, udigest::Digestable)]
122#[udigest(bound = "")]
123pub struct Data<'a, C: Curve> {
124 #[udigest(as = crate::common::encoding::AnyEncryptionKey)]
126 pub key: &'a dyn AnyEncryptionKey,
127 #[udigest(as = &crate::common::encoding::Integer)]
129 pub ciphertext: &'a Ciphertext,
130 pub a: &'a Point<C>,
132 pub b: &'a Point<C>,
134 pub x: &'a Point<C>,
136}
137
138#[derive(Clone, Copy)]
140pub struct PrivateData<'a, E: Curve> {
141 pub plaintext: &'a Plaintext,
143 pub nonce: &'a Nonce,
145 pub b: &'a Scalar<E>,
147}
148
149#[derive(Debug, Clone, udigest::Digestable)]
151#[udigest(bound = "")]
152#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))]
153pub struct Commitment<E: Curve> {
154 #[udigest(as = crate::common::encoding::Integer)]
155 pub s: Integer,
156 #[udigest(as = crate::common::encoding::Integer)]
157 pub t: Integer,
158 #[udigest(as = crate::common::encoding::Integer)]
159 pub d: Integer,
160 pub y: Point<E>,
161 pub z: Point<E>,
162}
163
164#[derive(Clone)]
166pub struct PrivateCommitment<E: Curve> {
167 pub alpha: Integer,
168 pub mu: Integer,
169 pub r: Integer,
170 pub beta: Scalar<E>,
171 pub gamma: Integer,
172}
173
174pub type Challenge = Integer;
177
178#[derive(Debug, Clone)]
180#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))]
181pub struct Proof<E: Curve> {
182 pub z1: Integer,
183 pub z2: Integer,
184 pub z3: Integer,
185 pub w: Scalar<E>,
186}
187
188#[derive(Debug, Clone)]
191#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))]
192pub struct NiProof<E: Curve> {
193 pub commitment: Commitment<E>,
194 pub proof: Proof<E>,
195}
196
197pub mod interactive {
201 use fast_paillier::backend::Integer;
202 use generic_ec::{Curve, Point, Scalar};
203 use rand_core::RngCore;
204
205 use crate::{
206 common::{fail_if, fail_if_ne, InvalidProofReason},
207 BadExponent, Error,
208 };
209
210 use crate::common::{IntegerExt, InvalidProof};
211
212 use super::{
213 Aux, Challenge, Commitment, Data, PrivateCommitment, PrivateData, Proof, SecurityParams,
214 };
215
216 pub fn commit<E: Curve>(
218 aux: &Aux,
219 data: Data<E>,
220 pdata: PrivateData<E>,
221 security: &SecurityParams,
222 rng: &mut impl RngCore,
223 ) -> Result<(Commitment<E>, PrivateCommitment<E>), Error> {
224 let two_to_l_plus_e = Integer::one() << (security.l + security.epsilon);
225 let n_j_at_two_to_l = (Integer::one() << security.l) * &aux.rsa_modulo;
226 let n_j_at_two_to_l_plus_e = &two_to_l_plus_e * &aux.rsa_modulo;
227
228 let alpha = Integer::from_rng_half_pm(rng, &two_to_l_plus_e);
229 let mu = Integer::from_rng_half_pm(rng, &n_j_at_two_to_l);
230 let r = Integer::sample_in_mult_group_of(rng, data.key.n());
231 let beta = Scalar::random(rng);
232 let gamma = Integer::from_rng_half_pm(rng, &n_j_at_two_to_l_plus_e);
233
234 let s = aux.combine(pdata.plaintext, &mu)?;
235 let t = aux.combine(&alpha, &gamma)?;
236 let d = data.key.encrypt_with(&alpha, &r)?;
237 let y = data.a * beta + Point::<E>::generator() * alpha.to_scalar();
238 let z = Point::<E>::generator() * beta;
239
240 Ok((
241 Commitment { s, t, d, y, z },
242 PrivateCommitment {
243 alpha,
244 mu,
245 r,
246 beta,
247 gamma,
248 },
249 ))
250 }
251
252 pub fn prove<E: Curve>(
254 data: Data<E>,
255 pdata: PrivateData<E>,
256 private_commitment: &PrivateCommitment<E>,
257 challenge: &Challenge,
258 ) -> Result<Proof<E>, Error> {
259 let z1 = &private_commitment.alpha + (challenge * pdata.plaintext);
260 let z2 = {
261 let nonce_to_challenge_mod_n: Integer = pdata
262 .nonce
263 .pow_mod_ref(challenge, data.key.n())
264 .ok_or(BadExponent::undefined())?;
265 (&private_commitment.r * nonce_to_challenge_mod_n).modulo(data.key.n())
266 };
267 let z3 = &private_commitment.gamma + (challenge * &private_commitment.mu);
268 let w = private_commitment.beta + (challenge.to_scalar() * pdata.b);
269 Ok(Proof { z1, z2, z3, w })
270 }
271
272 pub fn verify<E: Curve>(
274 aux: &Aux,
275 data: Data<E>,
276 commitment: &Commitment<E>,
277 security: &SecurityParams,
278 challenge: &Challenge,
279 proof: &Proof<E>,
280 ) -> Result<(), InvalidProof> {
281 fail_if(
283 InvalidProofReason::RangeCheck(1),
284 data.ciphertext.in_mult_group_of(data.key.nn()),
285 )?;
286 fail_if(
287 InvalidProofReason::RangeCheck(2),
288 aux.is_in_mult_group(&commitment.s),
289 )?;
290 fail_if(
291 InvalidProofReason::RangeCheck(3),
292 aux.is_in_mult_group(&commitment.t),
293 )?;
294 fail_if(
295 InvalidProofReason::RangeCheck(4),
296 commitment.d.in_mult_group_of(data.key.nn()),
297 )?;
298
299 {
301 let lhs = data
302 .key
303 .encrypt_with(&proof.z1, &proof.z2)
304 .map_err(|_| InvalidProofReason::PaillierEnc)?;
305 let rhs = {
306 let e_at_c = data
307 .key
308 .omul(challenge, data.ciphertext)
309 .map_err(|_| InvalidProofReason::PaillierOp)?;
310 data.key
311 .oadd(&commitment.d, &e_at_c)
312 .map_err(|_| InvalidProofReason::PaillierOp)?
313 };
314 fail_if_ne(InvalidProofReason::EqualityCheck(5), lhs, rhs)?;
315 }
316 {
317 let lhs = data.a * proof.w + Point::<E>::generator() * proof.z1.to_scalar();
318 let rhs = commitment.y + data.x * challenge.to_scalar();
319 fail_if_ne(InvalidProofReason::EqualityCheck(6), lhs, rhs)?;
320 }
321 {
322 let lhs = Point::<E>::generator() * proof.w;
323 let rhs = commitment.z + data.b * challenge.to_scalar();
324 fail_if_ne(InvalidProofReason::EqualityCheck(7), lhs, rhs)?;
325 }
326 {
327 let lhs = aux.combine(&proof.z1, &proof.z3)?;
328 let rhs = {
329 let s_to_e = aux.pow_mod(&commitment.s, challenge)?;
330 (&commitment.t * s_to_e).modulo(&aux.rsa_modulo)
331 };
332 fail_if_ne(InvalidProofReason::EqualityCheck(8), lhs, rhs)?;
333 }
334
335 fail_if(
336 InvalidProofReason::RangeCheck(9),
337 proof
338 .z1
339 .is_in_half_pm(&(Integer::one() << (security.l + security.epsilon))),
340 )?;
341
342 Ok(())
343 }
344
345 pub fn challenge<E: Curve>(rng: &mut impl RngCore) -> Challenge {
349 Integer::from_rng_half_pm(rng, &Integer::curve_order::<E>())
350 }
351}
352
353pub mod non_interactive {
356 use digest::Digest;
357 use generic_ec::Curve;
358
359 use crate::{Error, InvalidProof};
360
361 use super::{Aux, Challenge, Commitment, Data, NiProof, PrivateData, SecurityParams};
362
363 pub fn prove<E: Curve, D: Digest>(
368 shared_state: &impl udigest::Digestable,
369 aux: &Aux,
370 data: Data<E>,
371 pdata: PrivateData<E>,
372 security: &SecurityParams,
373 rng: &mut impl rand_core::RngCore,
374 ) -> Result<NiProof<E>, Error> {
375 let (commitment, pcomm) = super::interactive::commit(aux, data, pdata, security, rng)?;
376 let challenge = challenge::<E, D>(shared_state, aux, data, &commitment, security);
377 let proof = super::interactive::prove(data, pdata, &pcomm, &challenge)?;
378 Ok(NiProof { commitment, proof })
379 }
380
381 pub fn verify<E: Curve, D: Digest>(
383 shared_state: &impl udigest::Digestable,
384 aux: &Aux,
385 data: Data<E>,
386 proof: &NiProof<E>,
387 security: &SecurityParams,
388 ) -> Result<(), InvalidProof> {
389 let challenge = challenge::<E, D>(shared_state, aux, data, &proof.commitment, security);
390 super::interactive::verify(
391 aux,
392 data,
393 &proof.commitment,
394 security,
395 &challenge,
396 &proof.proof,
397 )
398 }
399
400 pub fn challenge<E: Curve, D: Digest>(
402 shared_state: &impl udigest::Digestable,
403 aux: &Aux,
404 data: Data<E>,
405 commitment: &Commitment<E>,
406 security: &SecurityParams,
407 ) -> Challenge {
408 let tag = "paillier_zk.encryption_in_range_with_el_gamal.ni_challenge";
409 let seed = udigest::inline_struct!(tag {
410 shared_state,
411 aux: aux.digest_public_data(),
412 security,
413 data,
414 commitment,
415 });
416 let mut rng = rand_hash::HashRng::<D, _>::from_seed(seed);
417 super::interactive::challenge::<E>(&mut rng)
418 }
419}
420
421#[cfg(test)]
422mod test {
423 use fast_paillier::backend::Integer;
424 use generic_ec::{Curve, Point, Scalar};
425 use sha2::Digest;
426
427 use crate::common::{IntegerExt, InvalidProofReason};
428
429 fn run_with<E: Curve, D: Digest>(
430 mut rng: &mut impl rand_core::CryptoRngCore,
431 security: super::SecurityParams,
432 plaintext: Integer,
433 ) -> Result<(), crate::common::InvalidProof> {
434 let aux = crate::common::test::aux(&mut rng);
435
436 let private_key = crate::common::test::random_key(&mut rng).unwrap();
437 let a = Scalar::random(rng);
438 let pdata = super::PrivateData {
439 plaintext: &plaintext,
440 nonce: &Integer::sample_in_mult_group_of(rng, private_key.n()),
441 b: &Scalar::random(rng),
442 };
443
444 let data = super::Data {
445 key: private_key.encryption_key(),
446 ciphertext: &private_key
447 .encrypt_with(pdata.plaintext, pdata.nonce)
448 .unwrap(),
449 a: &(Point::generator() * a),
450 b: &(Point::generator() * pdata.b),
451 x: &(Point::generator() * (a * pdata.b + pdata.plaintext.to_scalar())),
452 };
453
454 let shared_state = "shared state";
455 let proof =
456 super::non_interactive::prove::<E, D>(&shared_state, &aux, data, pdata, &security, rng)
457 .unwrap();
458 super::non_interactive::verify::<E, D>(&shared_state, &aux, data, &proof, &security)
459 }
460
461 fn passing_test<C: Curve, D: Digest>() {
462 let mut rng = rand_dev::DevRng::new();
463 let security = super::SecurityParams {
464 l: 256,
465 epsilon: 512,
466 };
467 let plaintext = Integer::from_rng_half_pm(&mut rng, &(Integer::one() << security.l));
468 run_with::<C, D>(&mut rng, security, plaintext).expect("proof failed");
469 }
470
471 fn failing_test<C: Curve, D: Digest>() {
472 let mut rng = rand_dev::DevRng::new();
473 let security = super::SecurityParams {
474 l: 256,
475 epsilon: 512,
476 };
477 let plaintext = (Integer::one() << (security.l + security.epsilon - 1)) + 1;
478 let r = run_with::<C, D>(&mut rng, security, plaintext).expect_err("proof should not pass");
479 match r.reason() {
480 InvalidProofReason::RangeCheck(9) => (),
481 e => panic!("proof should not fail with: {e:?}"),
482 }
483 }
484
485 #[test]
486 fn passing_p256() {
487 passing_test::<generic_ec::curves::Secp256r1, sha2::Sha256>()
488 }
489 #[test]
490 fn failing_p256_add() {
491 failing_test::<generic_ec::curves::Secp256r1, sha2::Sha256>()
492 }
493
494 #[test]
495 fn passing_million() {
496 passing_test::<crate::curve::C, sha2::Sha256>()
497 }
498 #[test]
499 fn failing_million_add() {
500 failing_test::<crate::curve::C, sha2::Sha256>()
501 }
502}