elastic_elgamal/proofs/
range.rs

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