1use fast_paillier::backend::Integer;
89use fast_paillier::{AnyEncryptionKey, Ciphertext, Nonce};
90
91#[cfg(feature = "serde")]
92use serde::{Deserialize, Serialize};
93
94pub use crate::common::Aux;
95pub use crate::common::InvalidProof;
96
97#[derive(Debug, Clone, udigest::Digestable)]
100#[udigest(bound = "")]
101pub struct SecurityParams {
102 pub l: usize,
105 pub epsilon: usize,
107 #[udigest(as = crate::common::encoding::Integer)]
111 pub q: Integer,
112}
113
114#[derive(Debug, Clone, Copy, udigest::Digestable)]
116pub struct Data<'a> {
117 #[udigest(as = crate::common::encoding::AnyEncryptionKey)]
119 pub key: &'a dyn AnyEncryptionKey,
120 #[udigest(as = &crate::common::encoding::Integer)]
122 pub ciphertext: &'a Ciphertext,
123}
124
125#[derive(Clone, Copy)]
127pub struct PrivateData<'a> {
128 pub plaintext: &'a Integer,
130 pub nonce: &'a Nonce,
132}
133
134#[derive(Debug, Clone, udigest::Digestable)]
137#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
138pub struct Commitment {
139 #[udigest(as = crate::common::encoding::Integer)]
140 pub s: Integer,
141 #[udigest(as = crate::common::encoding::Integer)]
142 pub a: Integer,
143 #[udigest(as = crate::common::encoding::Integer)]
144 pub c: Integer,
145}
146
147#[derive(Clone)]
150pub struct PrivateCommitment {
151 pub alpha: Integer,
152 pub mu: Integer,
153 pub r: Integer,
154 pub gamma: Integer,
155}
156
157pub type Challenge = Integer;
160
161#[derive(Debug, Clone)]
164#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
165pub struct Proof {
166 pub z1: Integer,
167 pub z2: Integer,
168 pub z3: Integer,
169}
170
171#[derive(Debug, Clone)]
174#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
175pub struct NiProof {
176 pub commitment: Commitment,
177 pub proof: Proof,
178}
179
180pub mod interactive {
184 use fast_paillier::backend::Integer;
185
186 use crate::{
187 common::{fail_if, fail_if_ne, InvalidProofReason},
188 BadExponent, Error,
189 };
190
191 use crate::common::{IntegerExt, InvalidProof};
192
193 use super::{
194 Aux, Challenge, Commitment, Data, PrivateCommitment, PrivateData, Proof, SecurityParams,
195 };
196
197 pub fn commit(
199 aux: &Aux,
200 data: Data,
201 pdata: PrivateData,
202 security: &SecurityParams,
203 rng: &mut impl rand_core::RngCore,
204 ) -> Result<(Commitment, PrivateCommitment), Error> {
205 let two_to_l_plus_e = Integer::one() << (security.l + security.epsilon);
206 let hat_n_at_two_to_l = (Integer::one() << security.l) * &aux.rsa_modulo;
207 let hat_n_at_two_to_l_plus_e =
208 (Integer::one() << (security.l + security.epsilon)) * &aux.rsa_modulo;
209
210 let alpha = Integer::from_rng_half_pm(rng, &two_to_l_plus_e);
211 let mu = Integer::from_rng_half_pm(rng, &hat_n_at_two_to_l);
212 let r = Integer::sample_in_mult_group_of(rng, data.key.n());
213 let gamma = Integer::from_rng_half_pm(rng, &hat_n_at_two_to_l_plus_e);
214
215 let s = aux.combine(pdata.plaintext, &mu)?;
216 let a = data.key.encrypt_with(&alpha, &r)?;
217 let c = aux.combine(&alpha, &gamma)?;
218
219 Ok((
220 Commitment { s, a, c },
221 PrivateCommitment {
222 alpha,
223 mu,
224 r,
225 gamma,
226 },
227 ))
228 }
229
230 pub fn prove(
232 data: Data,
233 pdata: PrivateData,
234 private_commitment: &PrivateCommitment,
235 challenge: &Challenge,
236 ) -> Result<Proof, Error> {
237 let z1 = &private_commitment.alpha + (challenge * pdata.plaintext);
238 let nonce_to_challenge_mod_n: Integer = pdata
239 .nonce
240 .pow_mod_ref(challenge, data.key.n())
241 .ok_or(BadExponent::undefined())?;
242 let z2 = (&private_commitment.r * nonce_to_challenge_mod_n).modulo(data.key.n());
243 let z3 = &private_commitment.gamma + (challenge * &private_commitment.mu);
244 Ok(Proof { z1, z2, z3 })
245 }
246
247 pub fn verify(
249 aux: &Aux,
250 data: Data,
251 commitment: &Commitment,
252 security: &SecurityParams,
253 challenge: &Challenge,
254 proof: &Proof,
255 ) -> Result<(), InvalidProof> {
256 fail_if(
257 InvalidProofReason::RangeCheck(1),
258 data.ciphertext.in_mult_group_of(data.key.nn()),
259 )?;
260 fail_if(
261 InvalidProofReason::RangeCheck(2),
262 aux.is_in_mult_group(&commitment.s),
263 )?;
264 fail_if(
265 InvalidProofReason::RangeCheck(3),
266 commitment.a.in_mult_group_of(data.key.nn()),
267 )?;
268 fail_if(
269 InvalidProofReason::RangeCheck(4),
270 aux.is_in_mult_group(&commitment.c),
271 )?;
272
273 fail_if(
274 InvalidProofReason::RangeCheck(5),
275 proof
276 .z1
277 .is_in_half_pm(&(Integer::one() << (security.l + security.epsilon))),
278 )?;
279 fail_if(
280 InvalidProofReason::RangeCheck(6),
281 proof.z3.is_in_half_pm(
282 &(&aux.rsa_modulo * (Integer::one() << (security.l + security.epsilon + 1))),
283 ),
284 )?;
285
286 {
287 let lhs = data
288 .key
289 .encrypt_with(&proof.z1, &proof.z2)
290 .map_err(|_| InvalidProofReason::PaillierEnc)?;
291 let rhs = {
292 let e_at_k = data
293 .key
294 .omul(challenge, data.ciphertext)
295 .map_err(|_| InvalidProofReason::PaillierOp)?;
296 data.key
297 .oadd(&commitment.a, &e_at_k)
298 .map_err(|_| InvalidProofReason::PaillierOp)?
299 };
300 fail_if_ne(InvalidProofReason::EqualityCheck(2), lhs, rhs)?;
301 }
302
303 {
304 let lhs = aux.combine(&proof.z1, &proof.z3)?;
305 let s_to_e = aux.pow_mod(&commitment.s, challenge)?;
306 let rhs = (&commitment.c * s_to_e).modulo(&aux.rsa_modulo);
307 fail_if_ne(InvalidProofReason::EqualityCheck(3), lhs, rhs)?;
308 }
309
310 Ok(())
311 }
312
313 pub fn challenge(rng: &mut impl rand_core::RngCore, security: &SecurityParams) -> Challenge {
317 Integer::from_rng_half_pm(rng, &security.q)
318 }
319}
320
321pub mod non_interactive {
324 use digest::Digest;
325
326 use crate::{Error, InvalidProof};
327
328 use super::{Aux, Challenge, Commitment, Data, NiProof, PrivateData, SecurityParams};
329
330 pub fn prove<D: Digest>(
335 shared_state: &impl udigest::Digestable,
336 aux: &Aux,
337 data: Data,
338 pdata: PrivateData,
339 security: &SecurityParams,
340 rng: &mut impl rand_core::RngCore,
341 ) -> Result<NiProof, Error> {
342 let (commitment, pcomm) = super::interactive::commit(aux, data, pdata, security, rng)?;
343 let challenge = challenge::<D>(shared_state, aux, data, &commitment, security);
344 let proof = super::interactive::prove(data, pdata, &pcomm, &challenge)?;
345 Ok(NiProof { commitment, proof })
346 }
347
348 pub fn challenge<D: Digest>(
350 shared_state: &impl udigest::Digestable,
351 aux: &Aux,
352 data: Data,
353 commitment: &Commitment,
354 security: &SecurityParams,
355 ) -> Challenge {
356 let tag = "paillier_zk.encryption_in_range.ni_challenge";
357 let seed = udigest::inline_struct!(tag {
358 security,
359 shared_state,
360 aux: aux.digest_public_data(),
361 data,
362 commitment,
363 });
364 let mut rng = rand_hash::HashRng::<D, _>::from_seed(seed);
365 super::interactive::challenge(&mut rng, security)
366 }
367
368 pub fn verify<D: Digest>(
370 shared_state: &impl udigest::Digestable,
371 aux: &Aux,
372 data: Data,
373 security: &SecurityParams,
374 proof: &NiProof,
375 ) -> Result<(), InvalidProof> {
376 let challenge = challenge::<D>(shared_state, aux, data, &proof.commitment, security);
377 super::interactive::verify(
378 aux,
379 data,
380 &proof.commitment,
381 security,
382 &challenge,
383 &proof.proof,
384 )
385 }
386}
387
388#[cfg(test)]
389mod test {
390 use fast_paillier::backend::Integer;
391 use sha2::Digest;
392
393 use crate::common::{IntegerExt, InvalidProofReason};
394
395 fn run_with<D: Digest>(
396 mut rng: &mut impl rand_core::CryptoRngCore,
397 security: super::SecurityParams,
398 plaintext: Integer,
399 ) -> Result<(), crate::common::InvalidProof> {
400 let aux = crate::common::test::aux(&mut rng);
401 let private_key = crate::common::test::random_key(&mut rng).unwrap();
402 let key = private_key.encryption_key();
403 let (ciphertext, nonce) = key.encrypt_with_random(&mut rng, &plaintext).unwrap();
404 let data = super::Data {
405 key,
406 ciphertext: &ciphertext,
407 };
408 let pdata = super::PrivateData {
409 plaintext: &plaintext,
410 nonce: &nonce,
411 };
412
413 let shared_state = "shared state";
414 let proof =
415 super::non_interactive::prove::<D>(&shared_state, &aux, data, pdata, &security, rng)
416 .unwrap();
417 super::non_interactive::verify::<D>(&shared_state, &aux, data, &security, &proof)
418 }
419
420 #[test]
421 fn passing() {
422 let mut rng = rand_dev::DevRng::new();
423 let security = super::SecurityParams {
424 l: 256,
425 epsilon: 512,
426 q: Integer::curve_order::<generic_ec::curves::Secp256k1>(),
427 };
428 let plaintext = Integer::from_rng_half_pm(&mut rng, &(Integer::one() << security.l));
429 let r = run_with::<sha2::Sha256>(&mut rng, security, plaintext);
430 match r {
431 Ok(()) => (),
432 Err(e) => panic!("{e:?}"),
433 }
434 }
435 #[test]
436 fn failing() {
437 let mut rng = rand_dev::DevRng::new();
438 let security = super::SecurityParams {
439 l: 256,
440 epsilon: 512,
441 q: Integer::curve_order::<generic_ec::curves::Secp256k1>(),
442 };
443 let plaintext = (Integer::one() << (security.l + security.epsilon)) + 1;
444 let r = run_with::<sha2::Sha256>(&mut rng, security, plaintext);
445 match r.map_err(|e| e.reason()) {
446 Ok(()) => panic!("proof should not pass"),
447 Err(InvalidProofReason::RangeCheck(_)) => (),
448 Err(e) => panic!("proof should not fail with {e:?}"),
449 }
450 }
451}