elastic_elgamal/proofs/
range.rs

1//! Range proofs for ElGamal ciphertexts.
2
3use merlin::Transcript;
4use rand_core::{CryptoRng, RngCore};
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7use subtle::{ConditionallySelectable, ConstantTimeGreater};
8use zeroize::Zeroizing;
9
10use core::{convert::TryFrom, fmt};
11
12use crate::{
13    alloc::{vec, HashMap, ToString, Vec},
14    encryption::{CiphertextWithValue, ExtendedCiphertext},
15    group::Group,
16    proofs::{RingProof, RingProofBuilder, TranscriptForGroup},
17    Ciphertext, PublicKey, VerificationError,
18};
19
20#[derive(Debug, Clone, Copy, PartialEq)]
21struct RingSpec {
22    size: u64,
23    step: u64,
24}
25
26/// Decomposition of an integer range `0..n` into one or more sub-ranges. Decomposing the range
27/// allows constructing [`RangeProof`]s with size / computational complexity `O(log n)`.
28///
29/// # Construction
30///
31/// To build efficient `RangeProof`s, we need to be able to decompose any value `x` in `0..n`
32/// into several components, with each of them being in a smaller predefined range; once we
33/// have such a decomposition, we can build a [`RingProof`] around it.
34/// To build a decomposition, we use the following generic construction:
35///
36/// ```text
37/// 0..n = 0..t_0 + k_0 * (0..t_1 + k_1 * (0..t_2 + …)),
38/// ```
39///
40/// where `t_i` and `k_i` are integers greater than 1. If `x` is a value in `0..n`,
41/// it is decomposed as
42///
43/// ```text
44/// x = x_0 + k_0 * x_1 + k_0 * k_1 * x_2 + …; x_i in 0..t_i.
45/// ```
46///
47/// For a decomposition to be valid (i.e., to represent any value in `0..n` and no other values),
48/// the following statements are sufficient:
49///
50/// - `t_i >= k_i` (no gaps in values)
51/// - `n = t_0 + k_0 * (t_1 - 1 + k_1 * …)` (exact upper bound).
52///
53/// The size of a `RingProof` is the sum of upper range bounds `t_i` (= number of responses) + 1
54/// (the common challenge). Additionally, we need a ciphertext per each sub-range `0..t_i`
55/// (i.e., for each ring in `RingProof`). In practice, proof size is logarithmic:
56///
57/// | Upper bound `n`| Optimal decomposition | Proof size |
58/// |---------------:|-----------------------|-----------:|
59/// | 5              | `0..5`                | 6 scalars  |
60/// | 10             | `0..5 * 2 + 0..2`     | 8 scalars, 2 elements |
61/// | 20             | `0..5 * 4 + 0..4`     | 10 scalars, 2 elements |
62/// | 50             | `(0..5 * 5 + 0..5) * 2 + 0..2` | 13 scalars, 4 elements |
63/// | 64             | `(0..4 * 4 + 0..4) * 4 + 0..4` | 13 scalars, 4 elements |
64/// | 100            | `(0..5 * 5 + 0..5) * 4 + 0..4` | 15 scalars, 4 elements |
65/// | 256            | `((0..4 * 4 + 0..4) * 4 + 0..4) * 4 + 0..4` | 17 scalars, 6 elements |
66/// | 1000           | `((0..8 * 5 + 0..5) * 5 + 0..5) * 5 + 0..5` | 24 scalars, 6 elements |
67///
68/// (We do not count one of sub-range ciphertexts since it can be restored from the other
69/// sub-range ciphertexts and the original ciphertext of the value.)
70///
71/// ## Notes
72///
73/// - Decomposition of some values may be non-unique, but this is fine for our purposes.
74/// - Encoding of a value in a certain base is a partial case, with all `t_i` and `k_i` equal
75///   to the base. It only works for `n` being a power of the base.
76/// - Other types of decompositions may perform better, but this one has a couple
77///   of nice properties. It works for all `n`s, and the optimal decomposition can be found
78///   recursively.
79/// - If we know how to create / verify range proofs for `0..N`, proofs for all ranges `0..n`,
80///   `n < N` can be constructed as a combination of 2 proofs: a proof that encrypted value `x`
81///   is in `0..N` and that `n - 1 - x` is in `0..N`. (The latter is proved for a ciphertext
82///   obtained by the matching linear transform of the original ciphertext of `x`.)
83///   This does not help us if proofs for `0..N` are constructed using [`RingProof`]s,
84///   but allows estimating for which `n` a [Bulletproofs]-like construction would become
85///   more efficient despite using 2 proofs. If we take `N = 2^(2^P)`
86///   and the "vanilla" Bulletproof length `2 * P + 9`, this threshold is around `n = 2000`.
87///
88/// [Bulletproofs]: https://crypto.stanford.edu/bulletproofs/
89///
90/// # Examples
91///
92/// Finding out the optimal decomposition for a certain range:
93///
94/// ```
95/// # use elastic_elgamal::RangeDecomposition;
96/// let range = RangeDecomposition::optimal(42);
97/// assert_eq!(range.to_string(), "6 * 0..7 + 0..6");
98/// assert_eq!(range.proof_size(), 16); // 14 scalars, 2 elements
99///
100/// let range = RangeDecomposition::optimal(100);
101/// assert_eq!(range.to_string(), "20 * 0..5 + 4 * 0..5 + 0..4");
102/// assert_eq!(range.proof_size(), 19); // 15 scalars, 4 elements
103/// ```
104///
105/// See [`RangeProof`] docs for an end-to-end example of usage.
106#[derive(Debug, Clone, PartialEq)]
107pub struct RangeDecomposition {
108    rings: Vec<RingSpec>,
109}
110
111impl fmt::Display for RangeDecomposition {
112    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
113        for (i, ring_spec) in self.rings.iter().enumerate() {
114            if ring_spec.step > 1 {
115                write!(formatter, "{} * ", ring_spec.step)?;
116            }
117            write!(formatter, "0..{}", ring_spec.size)?;
118
119            if i + 1 < self.rings.len() {
120                formatter.write_str(" + ")?;
121            }
122        }
123        Ok(())
124    }
125}
126
127/// `RangeDecomposition` together with optimized parameters.
128#[derive(Debug, Clone)]
129struct OptimalDecomposition {
130    decomposition: RangeDecomposition,
131    optimal_len: u64,
132}
133
134#[allow(
135    clippy::cast_possible_truncation,
136    clippy::cast_precision_loss,
137    clippy::cast_sign_loss
138)]
139impl RangeDecomposition {
140    /// Finds an optimal decomposition of the range with the given `upper_bound` in terms
141    /// of space of the range proof.
142    ///
143    /// Empirically, this method has sublinear complexity, but may work slowly for large values
144    /// of `upper_bound` (say, larger than 1 billion).
145    ///
146    /// # Panics
147    ///
148    /// Panics if `upper_bound` is less than 2.
149    pub fn optimal(upper_bound: u64) -> Self {
150        assert!(upper_bound >= 2, "`upper_bound` must be greater than 1");
151
152        let mut optimal_values = HashMap::new();
153        Self::optimize(upper_bound, &mut optimal_values).decomposition
154    }
155
156    fn just(capacity: u64) -> Self {
157        let spec = RingSpec {
158            size: capacity,
159            step: 1,
160        };
161        Self { rings: vec![spec] }
162    }
163
164    fn combine_mul(self, new_ring_size: u64, multiplier: u64) -> Self {
165        let mut combined_rings = self.rings;
166        for spec in &mut combined_rings {
167            spec.step *= multiplier;
168        }
169        combined_rings.push(RingSpec {
170            size: new_ring_size,
171            step: 1,
172        });
173
174        Self {
175            rings: combined_rings,
176        }
177    }
178
179    /// Returns the exclusive upper bound of the range presentable by this decomposition.
180    pub fn upper_bound(&self) -> u64 {
181        self.rings
182            .iter()
183            .map(|spec| (spec.size - 1) * spec.step)
184            .sum::<u64>()
185            + 1
186    }
187
188    /// Returns the total number of items in all rings.
189    fn rings_size(&self) -> u64 {
190        self.rings.iter().map(|spec| spec.size).sum::<u64>()
191    }
192
193    /// Returns the size of [`RangeProof`]s using this decomposition, measured as a total number
194    /// of scalars and group elements in the proof. Computational complexity of creating and
195    /// verifying proofs is also linear w.r.t. this number.
196    pub fn proof_size(&self) -> u64 {
197        self.rings_size() + 2 * self.rings.len() as u64 - 1
198    }
199
200    fn decompose(&self, value_indexes: &mut Vec<usize>, mut secret_value: u64) {
201        for ring_spec in &self.rings {
202            let mut value_index = secret_value / ring_spec.step;
203            let ring_max_value = ring_spec.size - 1;
204            let overflow = value_index.ct_gt(&ring_max_value);
205            value_index.conditional_assign(&ring_max_value, overflow);
206            value_indexes.push(value_index as usize);
207            secret_value -= value_index * ring_spec.step;
208        }
209
210        debug_assert_eq!(secret_value, 0, "unused secret value for {self:?}");
211    }
212
213    /// We decompose our range `0..n` as `0..t + k * 0..T`, where `t >= 2`, `T >= 2`,
214    /// `k >= 2`. For all values in the range to be presentable, we need `t >= k` (otherwise,
215    /// there will be gaps) and
216    ///
217    /// ```text
218    /// n - 1 = t - 1 + k * (T - 1) <=> n = t + k * (T - 1)
219    /// ```
220    ///
221    /// (to accurately represent the upper bound). For valid decompositions, we apply the
222    /// same decomposition recursively to `0..T`. If `P(n)` is the optimal proof length for
223    /// range `0..n`, we thus obtain
224    ///
225    /// ```text
226    /// P(n) = min_(t, k) { t + 2 + P((n - t) / k + 1) }.
227    /// ```
228    ///
229    /// Here, `t` is the number of commitments (= number of scalars for ring `0..t`), plus
230    /// 2 group elements in a partial ElGamal ciphertext corresponding to the ring.
231    ///
232    /// We additionally trim the solution space using a lower-bound estimate
233    ///
234    /// ```text
235    /// P(n) >= 3 * log2(n),
236    /// ```
237    ///
238    /// which can be proven recursively.
239    fn optimize(
240        upper_bound: u64,
241        optimal_values: &mut HashMap<u64, OptimalDecomposition>,
242    ) -> OptimalDecomposition {
243        if let Some(opt) = optimal_values.get(&upper_bound) {
244            return opt.clone();
245        }
246
247        let mut opt = OptimalDecomposition {
248            optimal_len: upper_bound + 2,
249            decomposition: RangeDecomposition::just(upper_bound),
250        };
251
252        for first_ring_size in 2_u64.. {
253            if first_ring_size + 2 > opt.optimal_len {
254                // Any further estimate will be worse than the current optimum.
255                break;
256            }
257
258            let remaining_capacity = upper_bound - first_ring_size;
259            for multiplier in 2_u64..=first_ring_size {
260                if remaining_capacity % multiplier != 0 {
261                    continue;
262                }
263                let inner_upper_bound = remaining_capacity / multiplier + 1;
264                if inner_upper_bound < 2 {
265                    // Since `inner_upper_bound` decreases w.r.t. `multiplier`, we can
266                    // break here.
267                    break;
268                }
269
270                let best_estimate =
271                    first_ring_size + 2 + Self::lower_len_estimate(inner_upper_bound);
272                if best_estimate > opt.optimal_len {
273                    continue;
274                }
275
276                let inner_opt = Self::optimize(inner_upper_bound, optimal_values);
277                let candidate_len = first_ring_size + 2 + inner_opt.optimal_len;
278                let candidate_rings = 1 + inner_opt.decomposition.rings.len();
279
280                if candidate_len < opt.optimal_len
281                    || (candidate_len == opt.optimal_len
282                        && candidate_rings < opt.decomposition.rings.len())
283                {
284                    opt.optimal_len = candidate_len;
285                    opt.decomposition = inner_opt
286                        .decomposition
287                        .combine_mul(first_ring_size, multiplier);
288                }
289            }
290        }
291
292        debug_assert!(
293            opt.optimal_len >= Self::lower_len_estimate(upper_bound),
294            "Lower len estimate {est} is invalid for {bound}: {opt:?}",
295            est = Self::lower_len_estimate(upper_bound),
296            bound = upper_bound,
297            opt = opt
298        );
299        optimal_values.insert(upper_bound, opt.clone());
300        opt
301    }
302
303    #[cfg(feature = "std")]
304    fn lower_len_estimate(upper_bound: u64) -> u64 {
305        ((upper_bound as f64).log2() * 3.0).ceil() as u64
306    }
307
308    #[cfg(not(feature = "std"))]
309    fn lower_len_estimate(upper_bound: u64) -> u64 {
310        Self::int_lower_len_estimate(upper_bound)
311    }
312
313    // We may not have floating-point arithmetics on no-std targets; thus, we use
314    // a less precise estimate.
315    #[cfg(any(test, not(feature = "std")))]
316    #[inline]
317    fn int_lower_len_estimate(upper_bound: u64) -> u64 {
318        let log2_upper_bound = if upper_bound == 0 {
319            0
320        } else {
321            63 - u64::from(upper_bound.leading_zeros()) // rounded down
322        };
323        log2_upper_bound * 3
324    }
325}
326
327/// [`RangeDecomposition`] together with values precached for creating and/or verifying
328/// [`RangeProof`]s in a certain [`Group`].
329#[derive(Debug, Clone)]
330pub struct PreparedRange<G: Group> {
331    inner: RangeDecomposition,
332    admissible_values: Vec<Vec<G::Element>>,
333}
334
335impl<G: Group> From<RangeDecomposition> for PreparedRange<G> {
336    fn from(decomposition: RangeDecomposition) -> Self {
337        Self::new(decomposition)
338    }
339}
340
341impl<G: Group> PreparedRange<G> {
342    fn new(inner: RangeDecomposition) -> Self {
343        let admissible_values = Vec::with_capacity(inner.rings.len());
344        let admissible_values = inner.rings.iter().fold(admissible_values, |mut acc, spec| {
345            let ring_values: Vec<_> = (0..spec.size)
346                .map(|i| G::vartime_mul_generator(&(i * spec.step).into()))
347                .collect();
348            acc.push(ring_values);
349            acc
350        });
351
352        Self {
353            inner,
354            admissible_values,
355        }
356    }
357
358    /// Returns a reference to the contained decomposition.
359    pub fn decomposition(&self) -> &RangeDecomposition {
360        &self.inner
361    }
362
363    /// Decomposes the provided `secret_value` into value indexes in constituent rings.
364    fn decompose(&self, secret_value: u64) -> Zeroizing<Vec<usize>> {
365        assert!(
366            secret_value < self.inner.upper_bound(),
367            "Secret value must be in range 0..{}",
368            self.inner.upper_bound()
369        );
370        // We immediately allocate the necessary capacity for `decomposition`.
371        let mut decomposition = Zeroizing::new(Vec::with_capacity(self.admissible_values.len()));
372        self.inner.decompose(&mut decomposition, secret_value);
373        decomposition
374    }
375}
376
377/// Zero-knowledge proof that an ElGamal ciphertext encrypts a value into a certain range `0..n`.
378///
379/// # Construction
380///
381/// To make the proof more compact – `O(log n)` in terms of size and proving / verification
382/// complexity – we use the same trick as for [Pedersen commitments] (used, e.g., for confidential
383/// transaction amounts in [Elements]):
384///
385/// 1. Represent the encrypted value `x` as `x = x_0 + k_0 * x_1 + k_0 * k_1 * x_2 + …`,
386///    where `0 <= x_i < t_i` is the decomposition of `x` as per the [`RangeDecomposition`],
387///    `0..t_0 + k_0 * (0..t_1 + …)`.
388///    As an example, if `n` is a power of 2, one can choose a decomposition as
389///    the base-2 presentation of `x`, i.e., `t_i = k_i = 2` for all `i`.
390///    For brevity, denote a multiplier of `x_i` in `x` decomposition as `K_i`,
391///    `K_i = k_0 * … * k_{i-1}`; `K_0 = 1` by extension.
392/// 2. Split the ciphertext: `E = E_0 + E_1 + …`, where `E_i` encrypts `K_i * x_i`.
393/// 3. Produce a [`RingProof`] that for all `i` the encrypted scalar for `E_i`
394///    is among 0, `K_i`, …, `K_i * (t_i - 1)`. The range proof consists of all `E_i` ciphertexts
395///    and this `RingProof`.
396///
397/// As with range proofs for Pedersen commitments, this construction is not optimal
398/// in terms of space or proving / verification complexity for large ranges;
399/// it is linear w.r.t. the bit length of the range.
400/// (Constructions like [Bulletproofs] are *logarithmic* w.r.t. the bit length.)
401/// Still, it can be useful for small ranges.
402///
403/// [Pedersen commitments]: https://en.wikipedia.org/wiki/Commitment_scheme
404/// [Elements]: https://elementsproject.org/features/confidential-transactions/investigation
405/// [Bulletproofs]: https://crypto.stanford.edu/bulletproofs/
406///
407/// # Examples
408///
409/// ```
410/// # use elastic_elgamal::{
411/// #     group::Ristretto, DiscreteLogTable, Keypair, RangeDecomposition, RangeProof, Ciphertext,
412/// # };
413/// # use merlin::Transcript;
414/// # use rand::thread_rng;
415/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
416/// // Generate the ciphertext receiver.
417/// let mut rng = thread_rng();
418/// let receiver = Keypair::<Ristretto>::generate(&mut rng);
419/// // Find the optimal range decomposition for our range
420/// // and specialize it for the Ristretto group.
421/// let range = RangeDecomposition::optimal(100).into();
422///
423/// let (ciphertext, proof) = RangeProof::new(
424///     receiver.public(),
425///     &range,
426///     55,
427///     &mut Transcript::new(b"test_proof"),
428///     &mut rng,
429/// );
430/// let ciphertext = Ciphertext::from(ciphertext);
431///
432/// // Check that the ciphertext is valid
433/// let lookup = DiscreteLogTable::new(0..100);
434/// assert_eq!(receiver.secret().decrypt(ciphertext, &lookup), Some(55));
435/// // ...and that the proof verifies.
436/// proof.verify(
437///     receiver.public(),
438///     &range,
439///     ciphertext,
440///     &mut Transcript::new(b"test_proof"),
441/// )?;
442/// # Ok(())
443/// # }
444/// ```
445#[derive(Debug, Clone)]
446#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
447#[cfg_attr(feature = "serde", serde(bound = ""))]
448pub struct RangeProof<G: Group> {
449    partial_ciphertexts: Vec<Ciphertext<G>>,
450    #[cfg_attr(feature = "serde", serde(flatten))]
451    inner: RingProof<G>,
452}
453
454impl<G: Group> RangeProof<G> {
455    /// Encrypts `value` for `receiver` and creates a zero-knowledge proof that the encrypted value
456    /// is in `range`.
457    ///
458    /// This is a lower-level operation; see [`PublicKey::encrypt_range()`] for a higher-level
459    /// alternative.
460    ///
461    /// # Panics
462    ///
463    /// Panics if `value` is outside the range specified by `range`.
464    pub fn new<R: RngCore + CryptoRng>(
465        receiver: &PublicKey<G>,
466        range: &PreparedRange<G>,
467        value: u64,
468        transcript: &mut Transcript,
469        rng: &mut R,
470    ) -> (CiphertextWithValue<G, u64>, Self) {
471        let ciphertext = CiphertextWithValue::new(value, receiver, rng);
472        let proof = Self::from_ciphertext(receiver, range, &ciphertext, transcript, rng);
473        (ciphertext, proof)
474    }
475
476    /// Creates a proof that a value in `ciphertext` is in the `range`.
477    ///
478    /// The caller is responsible for providing a `ciphertext` encrypted for the `receiver`;
479    /// if the ciphertext is encrypted for another public key, the resulting proof will not verify.
480    ///
481    /// # Panics
482    ///
483    /// Panics if `value` is outside the range specified by `range`.
484    pub fn from_ciphertext<R: RngCore + CryptoRng>(
485        receiver: &PublicKey<G>,
486        range: &PreparedRange<G>,
487        ciphertext: &CiphertextWithValue<G, u64>,
488        transcript: &mut Transcript,
489        rng: &mut R,
490    ) -> Self {
491        let value_indexes = range.decompose(*ciphertext.value());
492        debug_assert_eq!(value_indexes.len(), range.admissible_values.len());
493        transcript.start_proof(b"encryption_range_proof");
494        transcript.append_message(b"range", range.inner.to_string().as_bytes());
495
496        let ring_responses_size = usize::try_from(range.inner.rings_size())
497            .expect("Integer overflow when allocating ring responses");
498        let mut ring_responses = vec![G::Scalar::default(); ring_responses_size];
499
500        let mut proof_builder = RingProofBuilder::new(
501            receiver,
502            range.admissible_values.len(),
503            &mut ring_responses,
504            transcript,
505            rng,
506        );
507
508        let mut cumulative_ciphertext = ExtendedCiphertext::zero();
509        let mut it = value_indexes.iter().zip(&range.admissible_values);
510
511        let partial_ciphertexts = it
512            .by_ref()
513            .take(value_indexes.len() - 1)
514            .map(|(value_index, admissible_values)| {
515                let ciphertext = proof_builder.add_value(admissible_values, *value_index);
516                let inner = ciphertext.inner;
517                cumulative_ciphertext += ciphertext;
518                inner
519            })
520            .collect();
521
522        let last_partial_ciphertext =
523            ciphertext.extended_ciphertext().clone() - cumulative_ciphertext;
524        let (&value_index, admissible_values) = it.next().unwrap();
525        // ^ `unwrap()` is safe by construction
526        proof_builder.add_precomputed_value(
527            last_partial_ciphertext,
528            admissible_values,
529            value_index,
530        );
531
532        Self {
533            partial_ciphertexts,
534            inner: RingProof::new(proof_builder.build(), ring_responses),
535        }
536    }
537
538    /// Verifies this proof against `ciphertext` for `receiver` and the specified `range`.
539    ///
540    /// This is a lower-level operation; see [`PublicKey::verify_range()`] for a higher-level
541    /// alternative.
542    ///
543    /// For a proof to verify, all parameters must be identical to ones provided when creating
544    /// the proof. In particular, `range` must have the same decomposition.
545    ///
546    /// # Errors
547    ///
548    /// Returns an error if this proof does not verify.
549    pub fn verify(
550        &self,
551        receiver: &PublicKey<G>,
552        range: &PreparedRange<G>,
553        ciphertext: Ciphertext<G>,
554        transcript: &mut Transcript,
555    ) -> Result<(), VerificationError> {
556        // Check decomposition / proof consistency.
557        VerificationError::check_lengths(
558            "admissible values",
559            self.partial_ciphertexts.len() + 1,
560            range.admissible_values.len(),
561        )?;
562
563        transcript.start_proof(b"encryption_range_proof");
564        transcript.append_message(b"range", range.inner.to_string().as_bytes());
565
566        let ciphertext_sum = self
567            .partial_ciphertexts
568            .iter()
569            .fold(Ciphertext::zero(), |acc, ciphertext| acc + *ciphertext);
570        let ciphertexts = self
571            .partial_ciphertexts
572            .iter()
573            .copied()
574            .chain(Some(ciphertext - ciphertext_sum));
575
576        let admissible_values = range.admissible_values.iter().map(Vec::as_slice);
577        self.inner
578            .verify(receiver, admissible_values, ciphertexts, transcript)
579    }
580}
581
582#[cfg(test)]
583mod tests {
584    use rand::{thread_rng, Rng};
585    use test_casing::test_casing;
586
587    use super::*;
588    use crate::{
589        group::{ElementOps, Ristretto},
590        Keypair,
591    };
592
593    #[test]
594    fn optimal_value_small() {
595        let value = RangeDecomposition::optimal(5);
596        assert_eq!(value.rings.as_ref(), [RingSpec { size: 5, step: 1 }]);
597
598        let value = RangeDecomposition::optimal(16);
599        assert_eq!(
600            value.rings.as_ref(),
601            [RingSpec { size: 4, step: 4 }, RingSpec { size: 4, step: 1 }]
602        );
603
604        let value = RangeDecomposition::optimal(60);
605        assert_eq!(
606            value.rings.as_ref(),
607            [
608                RingSpec { size: 5, step: 12 },
609                RingSpec { size: 4, step: 3 },
610                RingSpec { size: 3, step: 1 },
611            ]
612        );
613
614        let value = RangeDecomposition::optimal(1_000);
615        assert_eq!(
616            value.to_string(),
617            "125 * 0..8 + 25 * 0..5 + 5 * 0..5 + 0..5"
618        );
619    }
620
621    #[test]
622    fn optimal_values_with_additives() {
623        let value = RangeDecomposition::optimal(17);
624        assert_eq!(
625            value.rings.as_ref(),
626            [RingSpec { size: 4, step: 4 }, RingSpec { size: 5, step: 1 }]
627        );
628
629        let value = RangeDecomposition::optimal(101);
630        assert_eq!(
631            value.rings.as_ref(),
632            [
633                RingSpec { size: 5, step: 20 },
634                RingSpec { size: 5, step: 4 },
635                RingSpec { size: 5, step: 1 }
636            ]
637        );
638    }
639
640    #[test]
641    fn large_optimal_values() {
642        let value = RangeDecomposition::optimal(12_345);
643        assert_eq!(
644            value.to_string(),
645            "2880 * 0..4 + 720 * 0..5 + 90 * 0..9 + 15 * 0..7 + 3 * 0..5 + 0..3"
646        );
647        assert_eq!(value.upper_bound(), 12_345);
648
649        let value = RangeDecomposition::optimal(777_777);
650        assert_eq!(
651            value.to_string(),
652            "125440 * 0..6 + 25088 * 0..6 + 3136 * 0..8 + 784 * 0..4 + 196 * 0..4 + \
653             49 * 0..5 + 7 * 0..7 + 0..7"
654        );
655        assert_eq!(value.upper_bound(), 777_777);
656
657        let value = RangeDecomposition::optimal(12_345_678);
658        assert_eq!(
659            value.to_string(),
660            "3072000 * 0..4 + 768000 * 0..4 + 192000 * 0..4 + 48000 * 0..5 + 9600 * 0..6 + \
661             1200 * 0..8 + 300 * 0..4 + 75 * 0..5 + 15 * 0..5 + 3 * 0..6 + 0..3"
662        );
663        assert_eq!(value.upper_bound(), 12_345_678);
664    }
665
666    #[test_casing(4, [1_000, 9_999, 12_345, 54_321])]
667    fn decomposing_for_larger_range(upper_bound: u64) {
668        let decomposition = RangeDecomposition::optimal(upper_bound);
669        let mut rng = thread_rng();
670
671        let values = (0..1_000)
672            .map(|_| rng.gen_range(0..upper_bound))
673            .chain(0..5)
674            .chain((upper_bound - 5)..upper_bound);
675
676        for secret_value in values {
677            let mut value_indexes = vec![];
678            decomposition.decompose(&mut value_indexes, secret_value);
679
680            let restored = value_indexes
681                .iter()
682                .zip(&decomposition.rings)
683                .fold(0, |acc, (&idx, spec)| acc + idx as u64 * spec.step);
684            assert_eq!(
685                restored, secret_value,
686                "Cannot restore secret value {secret_value}; decomposed as {value_indexes:?}"
687            );
688        }
689    }
690
691    #[test]
692    fn decomposing_for_small_range() {
693        let decomposition = RangeDecomposition::optimal(17);
694        assert_eq!(decomposition.to_string(), "4 * 0..4 + 0..5");
695        let mut value_indexes = vec![];
696        decomposition.decompose(&mut value_indexes, 16);
697        assert_eq!(value_indexes, [3, 4]);
698        // 3 * 4 + 4 = 16
699    }
700
701    #[test]
702    fn decomposing_for_range() {
703        let decomposition = RangeDecomposition::optimal(1_000);
704        let mut value_indexes = vec![];
705        decomposition.decompose(&mut value_indexes, 567);
706        assert_eq!(value_indexes, [4, 2, 3, 2]);
707        // 2 + 3 * 5 + 2 * 25 + 4 * 125 = 567
708    }
709
710    #[test_casing(4, [12, 15, 20, 50])]
711    fn range_proof_basics(upper_bound: u64) {
712        let decomposition = RangeDecomposition::optimal(upper_bound).into();
713
714        let mut rng = thread_rng();
715        let receiver = Keypair::<Ristretto>::generate(&mut rng);
716        let (ciphertext, proof) = RangeProof::new(
717            receiver.public(),
718            &decomposition,
719            10,
720            &mut Transcript::new(b"test"),
721            &mut rng,
722        );
723        let ciphertext = ciphertext.into();
724
725        proof
726            .verify(
727                receiver.public(),
728                &decomposition,
729                ciphertext,
730                &mut Transcript::new(b"test"),
731            )
732            .unwrap();
733
734        // Should not verify with another transcript context
735        assert!(proof
736            .verify(
737                receiver.public(),
738                &decomposition,
739                ciphertext,
740                &mut Transcript::new(b"other"),
741            )
742            .is_err());
743
744        // ...or with another receiver
745        let other_receiver = Keypair::<Ristretto>::generate(&mut rng);
746        assert!(proof
747            .verify(
748                other_receiver.public(),
749                &decomposition,
750                ciphertext,
751                &mut Transcript::new(b"test"),
752            )
753            .is_err());
754
755        // ...or with another ciphertext
756        let other_ciphertext = receiver.public().encrypt(10_u64, &mut rng);
757        assert!(proof
758            .verify(
759                receiver.public(),
760                &decomposition,
761                other_ciphertext,
762                &mut Transcript::new(b"test"),
763            )
764            .is_err());
765
766        let mut mangled_ciphertext = ciphertext;
767        mangled_ciphertext.blinded_element += Ristretto::generator();
768        assert!(proof
769            .verify(
770                receiver.public(),
771                &decomposition,
772                mangled_ciphertext,
773                &mut Transcript::new(b"test"),
774            )
775            .is_err());
776
777        // ...or with another decomposition
778        let other_decomposition = RangeDecomposition::just(15).into();
779        assert!(proof
780            .verify(
781                receiver.public(),
782                &other_decomposition,
783                ciphertext,
784                &mut Transcript::new(b"test"),
785            )
786            .is_err());
787    }
788
789    #[test]
790    #[cfg(feature = "std")]
791    fn int_lower_len_estimate_is_always_not_more_than_exact() {
792        let samples = (0..1_000).chain((1..1_000).map(|i| i * 1_000));
793        for sample in samples {
794            let floating_point_estimate = RangeDecomposition::lower_len_estimate(sample);
795            let int_estimate = RangeDecomposition::int_lower_len_estimate(sample);
796            assert!(
797                floating_point_estimate >= int_estimate,
798                "Unexpected estimates for {sample}: floating-point = {floating_point_estimate}, \
799                 int = {int_estimate}"
800            );
801        }
802    }
803}