Skip to main content

tecdsa_core/zk/
dlenc.rs

1//! Π_dlenc (paper Protocol 7): the Paillier ciphertext `E = (1+N)^x · ρ^λ mod N²` encrypts the
2//! discrete log `x` of `X = g^x` (secp256k1), with `x ∈ ±2^{n_x}`, `λ ∈ ±2^{n_λ}`.
3//!
4//! The client's ephemeral DF tuple `(M̂, v, u1, u2)` (roles `N̂→M̂`, `t→v`, `s1→u1`, `s2→u2`) is
5//! used for the auxiliary commitment `P̃ = u1^x · u2^λ · v^µ mod M̂`, `µ ← ±2^{n_client+ν}`.
6//!
7//! Compact proof `(P̃, e, z1, z2, z3)`; the verifier reconstructs `A = g^{z1} X^{-e}`,
8//! `B̃ = u1^{z1} u2^{z2} v^{z3} P̃^{-e}` and `D = (1+N)^{z1} ρ^{z2} E^{-e}`.
9//! Ranges: `z1 ∈ ±2^{n_x+ε}`, `z2 ∈ ±2^{n_λ+ε}`, `z3 ∈ ±2^{n_client+ν+ε}` (the mask `µ'` range;
10//! honest responses leave these ranges with probability ≈ 2^-ν and the prover then restarts).
11use alloc::vec::Vec;
12use crypto_bigint::BoxedUint;
13use k256::{ProjectivePoint, Scalar};
14use rand_core::CryptoRng;
15use zeroize::{Zeroize, ZeroizeOnDrop};
16
17use super::df::DfStatement;
18use crate::{
19    arith::{
20        monty::Residue,
21        multiexp::multiexp_vartime,
22        sbig::{SBig, SRange, resized},
23    },
24    curve::{encode_point, is_identity},
25    error::{Error, Result},
26    paillier::PaillierPk,
27    params::Params,
28    transcript::{Transcript, uint_to_be},
29};
30
31const MAX_ATTEMPTS: u32 = 64;
32
33/// Public statement.
34pub struct DlencStatement<'a> {
35    /// `X = g^x`.
36    pub x_pub: &'a ProjectivePoint,
37    /// `E = enc_N(x; ρ^λ)` as a residue mod `N²`.
38    pub e_ct: &'a Residue,
39    pub pk: &'a PaillierPk,
40    /// The client's ephemeral DF tuple `(M̂, v, u1, u2)`.
41    pub client: &'a DfStatement,
42}
43
44/// Secret witness `(x, β)` (paper) i.e. `(x, λ)`.
45#[derive(ZeroizeOnDrop)]
46pub struct DlencWitness {
47    pub x: SBig,
48    pub beta: SBig,
49}
50
51impl core::fmt::Debug for DlencWitness {
52    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53        f.write_str("DlencWitness(***)")
54    }
55}
56
57impl DlencWitness {
58    pub fn new(x: SBig, beta: SBig) -> Self {
59        Self { x, beta }
60    }
61}
62
63/// Compact proof.
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct DlencProof {
66    /// `P̃ ∈ Z*_M̂`, canonical (`< M̂`).
67    pub p_tilde: BoxedUint,
68    pub e: SBig,
69    pub z1: SBig,
70    pub z2: SBig,
71    pub z3: SBig,
72}
73
74fn e_range(p: &Params) -> SRange {
75    SRange::pm_pow2(p.ell)
76}
77fn z1_range(p: &Params) -> SRange {
78    SRange::pm_pow2(p.n_x() + p.eps())
79}
80fn z2_range(p: &Params) -> SRange {
81    SRange::pm_pow2(p.n_lambda() + p.eps())
82}
83fn mu_range(p: &Params) -> SRange {
84    SRange::pm_pow2(p.n_client + p.nu)
85}
86fn z3_range(p: &Params) -> SRange {
87    SRange::pm_pow2(p.n_client + p.nu + p.eps())
88}
89fn sbig_len(r: SRange) -> usize {
90    1 + (r.bits().saturating_sub(1) as usize).div_ceil(8).max(1)
91}
92fn p_len(p: &Params) -> usize {
93    (p.n_client as usize).div_ceil(8)
94}
95
96fn check_params(p: &Params) -> Result<()> {
97    // |e| ≤ 2^{ℓ−1} must be below the group order.
98    if p.ell == 0 || p.ell > 200 || p.n_client == 0 {
99        return Err(Error::OutOfRange);
100    }
101    Ok(())
102}
103
104/// Signed integer reduced into the scalar field (constant time in the value).
105fn sbig_to_scalar(s: &SBig) -> Scalar {
106    let mut acc = Scalar::ZERO;
107    let c256 = Scalar::from(256u64);
108    for b in s.mag().to_be_bytes().iter() {
109        acc = acc * c256 + Scalar::from(u64::from(*b));
110    }
111    if bool::from(s.is_neg()) { -acc } else { acc }
112}
113
114fn lt(a: &BoxedUint, b: &BoxedUint) -> bool {
115    let bits = a.bits_precision().max(b.bits_precision());
116    resized(a, bits).cmp_vartime(resized(b, bits)) == core::cmp::Ordering::Less
117}
118
119fn base_tx(tx: &Transcript, st: &DlencStatement<'_>, params: &Params) -> Result<Transcript> {
120    let mut t = tx.clone();
121    t.append("zk", b"dlenc");
122    t.append_u64("n_x", u64::from(params.n_x()));
123    t.append_u64("n_l", u64::from(params.n_lambda()));
124    t.append("X", &encode_point(st.x_pub));
125    let n = st.pk.n();
126    t.append_uint("N", n, (n.bits() as usize).div_ceil(8))?;
127    t.append_residue("rho", st.pk.mod_n2(), st.pk.rho())?;
128    t.append_residue("E", st.pk.mod_n2(), st.e_ct)?;
129    st.client.append_to(&mut t)?;
130    Ok(t)
131}
132
133fn derive_e(
134    t: &Transcript,
135    st: &DlencStatement<'_>,
136    p_tilde: &Residue,
137    a: &ProjectivePoint,
138    b: &Residue,
139    d: &Residue,
140    params: &Params,
141) -> Result<SBig> {
142    let mut t = t.clone();
143    t.append_residue("P", &st.client.n_hat, p_tilde)?;
144    t.append("A", &encode_point(a));
145    t.append_residue("B", &st.client.n_hat, b)?;
146    t.append_residue("D", st.pk.mod_n2(), d)?;
147    Ok(t.challenge("e").pm_pow2(params.ell))
148}
149
150/// Common checks: client modulus size and ciphertext membership.
151fn check_statement(st: &DlencStatement<'_>, params: &Params) -> Result<()> {
152    check_params(params)?;
153    if is_identity(st.x_pub) || st.client.n_hat.modulus().bits() > params.n_client {
154        return Err(Error::OutOfRange);
155    }
156    st.pk.check_ciphertext(&st.e_ct.retrieve())?;
157    Ok(())
158}
159
160impl DlencProof {
161    pub fn prove<R: CryptoRng + ?Sized>(
162        rng: &mut R,
163        tx: &Transcript,
164        params: &Params,
165        st: &DlencStatement<'_>,
166        w: &DlencWitness,
167    ) -> Result<Self> {
168        check_statement(st, params)?;
169        if !w.x.in_range(SRange::pm_pow2(params.n_x()))
170            || !w.beta.in_range(SRange::pm_pow2(params.n_lambda()))
171        {
172            return Err(Error::OutOfRange);
173        }
174        let m = &st.client.n_hat;
175        let (u1, u2, v) = (&st.client.s1, &st.client.s2, &st.client.t);
176        let t0 = base_tx(tx, st, params)?;
177        let (r1, r2, r3) = (z1_range(params), z2_range(params), z3_range(params));
178        let mut mu = SBig::random_in(rng, mu_range(params));
179        let p_tilde = m
180            .pow_ct(u1, &w.x, params.n_x())
181            .and_then(|a| {
182                let b = m.pow_ct(u2, &w.beta, params.n_lambda())?;
183                let c = m.pow_ct(v, &mu, mu_range(params).bits())?;
184                Ok(a.mul(&b).mul(&c))
185            })
186            .inspect_err(|_| mu.zeroize())?;
187
188        for _ in 0..MAX_ATTEMPTS {
189            let mut alpha = SBig::random_in(rng, r1);
190            let mut lam = SBig::random_in(rng, r2);
191            let mut mu2 = SBig::random_in(rng, r3);
192            let first = (|| -> Result<(ProjectivePoint, Residue, Residue)> {
193                let a = ProjectivePoint::GENERATOR * sbig_to_scalar(&alpha);
194                let b = m
195                    .pow_ct(u1, &alpha, r1.bits())?
196                    .mul(&m.pow_ct(u2, &lam, r2.bits())?)
197                    .mul(&m.pow_ct(v, &mu2, r3.bits())?);
198                let d = st.pk.enc(&alpha, &lam, r2.bits())?;
199                Ok((a, b, d))
200            })();
201            let (a, b, d) = match first {
202                Ok(f) => f,
203                Err(e) => {
204                    alpha.zeroize();
205                    lam.zeroize();
206                    mu2.zeroize();
207                    mu.zeroize();
208                    return Err(e);
209                }
210            };
211            let e = derive_e(&t0, st, &p_tilde, &a, &b, &d, params)?;
212            let z1 = alpha.add(&e.mul(&w.x));
213            let z2 = lam.add(&e.mul(&w.beta));
214            let z3 = mu2.add(&e.mul(&mu));
215            alpha.zeroize();
216            lam.zeroize();
217            mu2.zeroize();
218            if z1.in_range(r1) && z2.in_range(r2) && z3.in_range(r3) {
219                mu.zeroize();
220                return Ok(Self {
221                    p_tilde: p_tilde.retrieve(),
222                    e,
223                    z1,
224                    z2,
225                    z3,
226                });
227            }
228        }
229        mu.zeroize();
230        Err(Error::OutOfRange)
231    }
232
233    pub fn verify(&self, tx: &Transcript, params: &Params, st: &DlencStatement<'_>) -> Result<()> {
234        check_statement(st, params).map_err(|_| Error::VerificationFailed)?;
235        if !self.e.in_range(e_range(params))
236            || !self.z1.in_range(z1_range(params))
237            || !self.z2.in_range(z2_range(params))
238            || !self.z3.in_range(z3_range(params))
239        {
240            return Err(Error::OutOfRange);
241        }
242        let m = &st.client.n_hat;
243        if !lt(&self.p_tilde, m.modulus().as_ref()) {
244            return Err(Error::OutOfRange);
245        }
246        let p_t = m.residue(&self.p_tilde);
247        m.invert(&p_t).map_err(|_| Error::VerificationFailed)?;
248        let neg_e = self.e.negate();
249        // A = g^{z1} · X^{-e}
250        let a = ProjectivePoint::GENERATOR * sbig_to_scalar(&self.z1)
251            - *st.x_pub * sbig_to_scalar(&self.e);
252        if is_identity(&a) {
253            return Err(Error::VerificationFailed);
254        }
255        // B̃ = u1^{z1} u2^{z2} v^{z3} P̃^{-e}
256        let b = multiexp_vartime(m, &[(&st.client.s1, &self.z1), (&st.client.s2, &self.z2)])?.mul(
257            &multiexp_vartime(m, &[(&st.client.t, &self.z3), (&p_t, &neg_e)])?,
258        );
259        // D = (1+N)^{z1} · ρ^{z2} · E^{-e}
260        let d = st.pk.g_pow(&self.z1).mul(&multiexp_vartime(
261            st.pk.mod_n2(),
262            &[(st.pk.rho(), &self.z2), (st.e_ct, &neg_e)],
263        )?);
264        let t0 = base_tx(tx, st, params)?;
265        if derive_e(&t0, st, &p_t, &a, &b, &d, params)? == self.e {
266            Ok(())
267        } else {
268            Err(Error::VerificationFailed)
269        }
270    }
271
272    pub fn encoded_len(params: &Params) -> usize {
273        p_len(params)
274            + sbig_len(e_range(params))
275            + sbig_len(z1_range(params))
276            + sbig_len(z2_range(params))
277            + sbig_len(z3_range(params))
278    }
279
280    /// `P̃` (⌈n_client/8⌉ bytes, big-endian) ‖ `e` ‖ `z1` ‖ `z2` ‖ `z3` (spec §10.2 signed format).
281    pub fn encode(&self, params: &Params) -> Result<Vec<u8>> {
282        check_params(params)?;
283        let mut out = uint_to_be(&self.p_tilde, p_len(params))?;
284        out.extend_from_slice(&self.e.to_bytes(e_range(params))?);
285        out.extend_from_slice(&self.z1.to_bytes(z1_range(params))?);
286        out.extend_from_slice(&self.z2.to_bytes(z2_range(params))?);
287        out.extend_from_slice(&self.z3.to_bytes(z3_range(params))?);
288        Ok(out)
289    }
290
291    pub fn decode(bytes: &[u8], params: &Params) -> Result<Self> {
292        check_params(params)?;
293        if bytes.len() != Self::encoded_len(params) {
294            return Err(Error::InvalidEncoding);
295        }
296        let (pb, rest) = bytes.split_at(p_len(params));
297        let p_tilde = crate::transcript::uint_from_be(pb, params.n_client)?;
298        let (eb, rest) = rest.split_at(sbig_len(e_range(params)));
299        let (z1b, rest) = rest.split_at(sbig_len(z1_range(params)));
300        let (z2b, z3b) = rest.split_at(sbig_len(z2_range(params)));
301        Ok(Self {
302            p_tilde,
303            e: SBig::from_bytes(eb, e_range(params))?,
304            z1: SBig::from_bytes(z1b, z1_range(params))?,
305            z2: SBig::from_bytes(z2b, z2_range(params))?,
306            z3: SBig::from_bytes(z3b, z3_range(params))?,
307        })
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    #![allow(clippy::unwrap_used)]
314    extern crate std;
315    use super::*;
316    use crate::{
317        arith::monty::Modulus,
318        params::{PARAMS_128, ParamsId},
319        rsa::tough::{ToughSpec, sample_df_with, sample_rsa_with},
320    };
321    use crypto_bigint::RandomBits;
322    use rand_chacha::ChaCha8Rng;
323    use rand_core::SeedableRng;
324
325    fn small_params() -> Params {
326        Params {
327            id: ParamsId::P128,
328            ell: 16,
329            nu: 16,
330            nu_leak: 16,
331            n_client: 512,
332            ..PARAMS_128
333        }
334    }
335
336    struct Fx {
337        p: Params,
338        pk: PaillierPk,
339        client: DfStatement,
340        x_pub: ProjectivePoint,
341        e_ct: Residue,
342        w: DlencWitness,
343    }
344
345    fn make_pk(rng: &mut ChaCha8Rng, n: BoxedUint) -> PaillierPk {
346        let n2 = crypto_bigint::ConcatenatingMul::concatenating_mul(&n, &n);
347        let m2 = Modulus::new(n2).unwrap();
348        let r = m2.residue(&BoxedUint::random_bits(rng, 1000));
349        PaillierPk::new(n, &r.square().retrieve()).unwrap()
350    }
351
352    fn fixture(
353        seed: u64,
354        p: Params,
355        n_bits: u32,
356        client: Option<DfStatement>,
357        neg: bool,
358    ) -> (Fx, ChaCha8Rng) {
359        let mut rng = ChaCha8Rng::seed_from_u64(seed);
360        let spec = ToughSpec { count: 4, bits: 64 };
361        let client = client.unwrap_or_else(|| {
362            let d = sample_df_with(&mut rng, p.n_client, spec, p.ell).unwrap();
363            DfStatement::from_uints(&d.n_hat, &d.t, &d.s1, &d.s2).unwrap()
364        });
365        let spec_n = if n_bits == p.n_rsa {
366            ToughSpec::from_params(&p)
367        } else {
368            spec
369        };
370        let n = sample_rsa_with(&mut rng, n_bits, spec_n).unwrap().n;
371        let pk = make_pk(&mut rng, n);
372        let x = SBig::random_in(&mut rng, SRange::pm_pow2(p.n_x()));
373        let x = if neg && !bool::from(x.is_neg()) {
374            x.negate()
375        } else {
376            x
377        };
378        let beta = SBig::random_in(&mut rng, SRange::pm_pow2(p.n_lambda()));
379        let x_pub = ProjectivePoint::GENERATOR * sbig_to_scalar(&x);
380        let e_ct = pk.enc(&x, &beta, p.n_lambda()).unwrap();
381        (
382            Fx {
383                p,
384                pk,
385                client,
386                x_pub,
387                e_ct,
388                w: DlencWitness::new(x, beta),
389            },
390            rng,
391        )
392    }
393
394    impl Fx {
395        fn st(&self) -> DlencStatement<'_> {
396            DlencStatement {
397                x_pub: &self.x_pub,
398                e_ct: &self.e_ct,
399                pk: &self.pk,
400                client: &self.client,
401            }
402        }
403    }
404
405    fn small(seed: u64) -> (Fx, ChaCha8Rng) {
406        fixture(seed, small_params(), 512, None, false)
407    }
408
409    #[test]
410    fn honest_roundtrip() {
411        for (seed, neg) in [(1u64, false), (2, true)] {
412            let (f, mut rng) = fixture(seed, small_params(), 512, None, neg);
413            let tx = Transcript::new("test");
414            for _ in 0..4 {
415                let pr = DlencProof::prove(&mut rng, &tx, &f.p, &f.st(), &f.w).unwrap();
416                pr.verify(&tx, &f.p, &f.st()).unwrap();
417                let enc = pr.encode(&f.p).unwrap();
418                assert_eq!(enc.len(), DlencProof::encoded_len(&f.p));
419                let q = DlencProof::decode(&enc, &f.p).unwrap();
420                assert_eq!(pr, q);
421                q.verify(&tx, &f.p, &f.st()).unwrap();
422                assert!(DlencProof::decode(&enc[1..], &f.p).is_err());
423                let mut longer = enc.clone();
424                longer.push(0);
425                assert!(DlencProof::decode(&longer, &f.p).is_err());
426            }
427        }
428    }
429
430    #[test]
431    fn tampering_fails() {
432        let (f, mut rng) = small(3);
433        let tx = Transcript::new("test");
434        let pr = DlencProof::prove(&mut rng, &tx, &f.p, &f.st(), &f.w).unwrap();
435        let one = SBig::from_i64(1);
436        for d in [one.clone(), one.negate()] {
437            let mut b = pr.clone();
438            b.e = b.e.add(&d);
439            assert!(b.verify(&tx, &f.p, &f.st()).is_err());
440            let mut b = pr.clone();
441            b.z1 = b.z1.add(&d);
442            assert!(b.verify(&tx, &f.p, &f.st()).is_err());
443            let mut b = pr.clone();
444            b.z2 = b.z2.add(&d);
445            assert!(b.verify(&tx, &f.p, &f.st()).is_err());
446            let mut b = pr.clone();
447            b.z3 = b.z3.add(&d);
448            assert!(b.verify(&tx, &f.p, &f.st()).is_err());
449        }
450        // P̃ modified / non-unit / non-canonical
451        let mut b = pr.clone();
452        b.p_tilde = b
453            .p_tilde
454            .wrapping_add(BoxedUint::one_with_precision(b.p_tilde.bits_precision()));
455        assert!(b.verify(&tx, &f.p, &f.st()).is_err());
456        let mut b = pr.clone();
457        b.p_tilde = BoxedUint::zero_with_precision(64);
458        assert!(b.verify(&tx, &f.p, &f.st()).is_err());
459        let mut b = pr.clone();
460        b.p_tilde = pr.p_tilde.wrapping_add(f.client.n_hat.modulus().as_ref());
461        assert_eq!(b.verify(&tx, &f.p, &f.st()), Err(Error::OutOfRange));
462        // boundaries: 2^{n-1} passes the range check (fails equation), 2^{n-1}+1 is out of range
463        for which in 0..3 {
464            let n = match which {
465                0 => f.p.n_x() + f.p.eps(),
466                1 => f.p.n_lambda() + f.p.eps(),
467                _ => f.p.n_client + f.p.nu + f.p.eps(),
468            };
469            let edge = SBig::from_uint(BoxedUint::one_with_precision(n + 64).shl(n - 1));
470            let mut b = pr.clone();
471            match which {
472                0 => b.z1 = edge.clone(),
473                1 => b.z2 = edge.clone(),
474                _ => b.z3 = edge.clone(),
475            }
476            assert_eq!(b.verify(&tx, &f.p, &f.st()), Err(Error::VerificationFailed));
477            match which {
478                0 => b.z1 = edge.add(&one),
479                1 => b.z2 = edge.add(&one),
480                _ => b.z3 = edge.add(&one),
481            }
482            assert_eq!(b.verify(&tx, &f.p, &f.st()), Err(Error::OutOfRange));
483        }
484        let mut b = pr.clone();
485        b.e = SBig::from_uint(BoxedUint::one_with_precision(128).shl(f.p.ell - 1)).add(&one);
486        assert_eq!(b.verify(&tx, &f.p, &f.st()), Err(Error::OutOfRange));
487        // statement fields
488        let other = f.x_pub + ProjectivePoint::GENERATOR;
489        let st = DlencStatement {
490            x_pub: &other,
491            ..f.st()
492        };
493        assert!(pr.verify(&tx, &f.p, &st).is_err());
494        let ident = ProjectivePoint::IDENTITY;
495        let st = DlencStatement {
496            x_pub: &ident,
497            ..f.st()
498        };
499        assert!(pr.verify(&tx, &f.p, &st).is_err());
500        let e2 = f.e_ct.mul(&f.pk.g_pow(&one));
501        let st = DlencStatement {
502            e_ct: &e2,
503            ..f.st()
504        };
505        assert!(pr.verify(&tx, &f.p, &st).is_err());
506        // different client tuple fields
507        for which in 0..4 {
508            let mut c = f.client.clone();
509            match which {
510                0 => c.t = c.t.mul(&c.t),
511                1 => c.s1 = c.s1.mul(&c.t),
512                2 => c.s2 = c.s2.mul(&c.t),
513                _ => c.s1 = c.s2.clone(),
514            }
515            let st = DlencStatement {
516                client: &c,
517                ..f.st()
518            };
519            assert!(pr.verify(&tx, &f.p, &st).is_err());
520        }
521        // different rho
522        let pk2 = make_pk(&mut rng, f.pk.n().clone());
523        let st = DlencStatement { pk: &pk2, ..f.st() };
524        assert!(pr.verify(&tx, &f.p, &st).is_err());
525        // transcript
526        let mut tx2 = Transcript::new("test");
527        tx2.append("x", b"y");
528        assert!(pr.verify(&tx2, &f.p, &f.st()).is_err());
529    }
530
531    #[test]
532    fn wrong_witness_fails() {
533        let (f, mut rng) = small(4);
534        let tx = Transcript::new("test");
535        let one = SBig::from_i64(1);
536        let bad = DlencWitness::new(f.w.x.add(&one), f.w.beta.clone());
537        let pr = DlencProof::prove(&mut rng, &tx, &f.p, &f.st(), &bad).unwrap();
538        assert!(pr.verify(&tx, &f.p, &f.st()).is_err());
539        let bad = DlencWitness::new(f.w.x.clone(), f.w.beta.add(&one));
540        let pr = DlencProof::prove(&mut rng, &tx, &f.p, &f.st(), &bad).unwrap();
541        assert!(pr.verify(&tx, &f.p, &f.st()).is_err());
542        // witness out of range
543        let big = SBig::from_uint(BoxedUint::one_with_precision(512).shl(f.p.n_x()));
544        let bad = DlencWitness::new(big, f.w.beta.clone());
545        assert!(DlencProof::prove(&mut rng, &tx, &f.p, &f.st(), &bad).is_err());
546    }
547
548    #[test]
549    fn bad_ciphertext_and_rho() {
550        let (f, mut rng) = small(5);
551        let tx = Transcript::new("test");
552        let pr = DlencProof::prove(&mut rng, &tx, &f.p, &f.st(), &f.w).unwrap();
553        // E from a different ρ
554        let pk2 = make_pk(&mut rng, f.pk.n().clone());
555        let e2 = pk2.enc(&f.w.x, &f.w.beta, f.p.n_lambda()).unwrap();
556        let st = DlencStatement {
557            e_ct: &e2,
558            ..f.st()
559        };
560        assert!(pr.verify(&tx, &f.p, &st).is_err());
561        assert!(
562            DlencProof::prove(&mut rng, &tx, &f.p, &st, &f.w)
563                .unwrap()
564                .verify(&tx, &f.p, &st)
565                .is_err()
566        );
567        // E not coprime to N: a multiple of N
568        let e3 = f.pk.mod_n2().residue(f.pk.n());
569        let st = DlencStatement {
570            e_ct: &e3,
571            ..f.st()
572        };
573        assert!(pr.verify(&tx, &f.p, &st).is_err());
574        assert!(DlencProof::prove(&mut rng, &tx, &f.p, &st, &f.w).is_err());
575        let e4 = f.pk.mod_n2().residue(&BoxedUint::zero_with_precision(64));
576        let st = DlencStatement {
577            e_ct: &e4,
578            ..f.st()
579        };
580        assert!(pr.verify(&tx, &f.p, &st).is_err());
581    }
582
583    #[test]
584    fn decode_rejects_bad() {
585        let (f, mut rng) = small(6);
586        let tx = Transcript::new("test");
587        let p = f.p;
588        let enc = DlencProof::prove(&mut rng, &tx, &p, &f.st(), &f.w)
589            .unwrap()
590            .encode(&p)
591            .unwrap();
592        let o = p_len(&p);
593        // bad sign byte of e
594        let mut b = enc.clone();
595        b[o] = 2;
596        assert!(DlencProof::decode(&b, &p).is_err());
597        // negative zero e
598        let el = sbig_len(e_range(&p));
599        let mut b = enc.clone();
600        b[o] = 1;
601        for v in b[o + 1..o + el].iter_mut() {
602            *v = 0;
603        }
604        assert!(DlencProof::decode(&b, &p).is_err());
605        // z1 magnitude too large
606        let mut b = enc.clone();
607        b[o + el] = 0;
608        b[o + el + 1] = 0xff;
609        assert!(DlencProof::decode(&b, &p).is_err());
610    }
611
612    #[test]
613    #[ignore]
614    fn full_size_timing() {
615        let p = PARAMS_128;
616        let mut rng = ChaCha8Rng::seed_from_u64(9);
617        let d = crate::rsa::tough::sample_df_client(&mut rng, &p).unwrap();
618        let client = DfStatement::client(&d.n_hat, &d.t, &d.s1, &d.s2).unwrap();
619        let (f, mut rng) = fixture(10, p, p.n_rsa, Some(client), true);
620        let tx = Transcript::new("test");
621        let t0 = std::time::Instant::now();
622        let pr = DlencProof::prove(&mut rng, &tx, &p, &f.st(), &f.w).unwrap();
623        let t1 = std::time::Instant::now();
624        pr.verify(&tx, &p, &f.st()).unwrap();
625        let t2 = std::time::Instant::now();
626        let enc = pr.encode(&p).unwrap();
627        std::println!(
628            "dlenc prove {:?} verify {:?} size {} (P {} e {} z1 {} z2 {} z3 {})",
629            t1 - t0,
630            t2 - t1,
631            enc.len(),
632            p_len(&p),
633            sbig_len(e_range(&p)),
634            sbig_len(z1_range(&p)),
635            sbig_len(z2_range(&p)),
636            sbig_len(z3_range(&p)),
637        );
638    }
639}