Skip to main content

class_groups/crypto_bigint/encoding/
mod.rs

1//! Compressed and uncompressed encoding of primitive reduced positive definite binary quadratic
2//! forms of negative discriminants (even or odd). While this is of greater scope than this library
3//! (which restricts itself to odd discriminants), it ensures the wire format allows the library to
4//! expand to include also implementing binary quadratic forms of even discriminants (without
5//! requiring a distinct encoding specification for those, when the time comes).
6//!
7//! Each module contains a documented, exact technical specification as to allow portable
8//! interoperability with any other implementation which also implements this specification.
9//!
10//! All algorithms are presented in a pseudo-code which should be clear to any developer but is not
11//! guaranteed to coincide with any specific language or standard. When decoding, we generally use
12//! `bytestream.next_byte()` to signify reading the next byte from some (error-free, non-empty,
13//! ideal) stream of bytes. This is done to denote how the encodings (and associated decode
14//! methods) _do not_ require any encapsulating and are self-contained. They additionally do not
15//! explicitly require the ability to look ahead or any external buffers.
16//!
17//! Our encoding method is _canonical_, only yielding a single encoding for any binary quadratic
18//! form. When decoding, we require encodings be canonical. While the simplest check would be to
19//! decode, reduce, and re-encode, before checking the equality of the resulting bytes with the
20//! input bytes, we provide more cost-effective methods which perform the canonicity checks during
21//! the decoding process itself. While this requirement does increase the cost of decoding, we do
22//! not find it a considerable portion of a larger context's runtime, but do find malleability too
23//! often a footgun to be worth the performance benefits.
24//!
25//! The following function is used to validate decoded binary quadratic forms as primitive and
26//! reduced. It yields `(a, b_positive, b_abs, c)` for valid forms.
27//!
28//! We assume the existence of a `gcd` function, which for `gcd(x, y)` returns the greatest common
29//! divisor of `x, y`.
30//!
31//! `//` is used to represent floor division.
32//!
33//! ```py
34//! fn validate_binary_quadratic_form(a, b_positive, b_abs, discriminant) {
35//!   c = ((b_abs * b_abs) - discriminant) // (4 * a)
36//!
37//!   # Assert the form is of this discriminant
38//!   assert ((b_abs * b_abs) - (4 * a * c)) == discriminant
39//!
40//!   # Assert the form is reduced
41//!   assert b_abs <= a
42//!   assert a <= c
43//!   # Assert _neither_ `|b| == a, a == c` _or_ that `b_positive == true`
44//!   # (as required for a reduced form, when such a equality exists)
45//!   assert (!((b_abs == a) || (a == c))) || b_positive
46//!   # Assert the form is primitive
47//!   assert gcd(a, b_abs, c) == 1
48//!
49//!   return (a, b_positive, b_abs, c)
50//! }
51//! ```
52
53use crypto_bigint::{Choice, CtOption, CtEq as _, CtLt as _, CtAssign, Zero, One, NonZero, Limb};
54
55/// An error encountered while decoding.
56#[derive(Clone, Copy, Debug)]
57pub enum Error {
58  /// The input stream unexpectedly ended.
59  UnexpectedEof,
60  /// The value overflowed the intended buffer.
61  Overflow,
62  /// The value was not canonically encoded.
63  NonCanonical,
64  /// The value was incorrect.
65  Incorrect,
66}
67
68/// The required view over a collection of limbs to validate a binary quadratic form.
69pub(super) trait Limbs:
70  From<u8> + CtAssign + Zero + One + super::composition::Limbs
71{
72  /// Divide a wide value by a thin value, yielding the quotient and remainder.
73  fn wide_div_rem_thin(wide: Self::Wide, thin: &NonZero<Self>) -> (Self::Wide, Self);
74  /// Return if the values are coprime.
75  fn coprime(a: Self, b_abs: Self, c: Self::Wide) -> Choice;
76}
77
78/// Validate a positive definite binary quadratic form (of negative discriminant) as primitive and
79/// reduced.
80///
81/// This function runs in constant time.
82#[expect(clippy::type_complexity)]
83pub(super) fn validate_binary_quadratic_form<U: Limbs>(
84  a: NonZero<U>,
85  (b_positive, b_abs): (Choice, U),
86  discriminant_abs: &U::Wide,
87) -> CtOption<(NonZero<U>, (Choice, U), U::Wide)> {
88  let (c, c_is_correct) = {
89    let mut b_square = b_abs.clone().square();
90
91    // This is correct as the discriminant is bound to be negative
92    let mut carry = Limb::ZERO;
93    for (b_square, discriminant_abs) in <_ as AsMut<[Limb]>>::as_mut(&mut b_square)
94      .iter_mut()
95      .zip(discriminant_abs.as_ref().iter().chain(core::iter::repeat(&Limb::ZERO)))
96    {
97      let new_limb;
98      (new_limb, carry) = b_square.carrying_add(*discriminant_abs, carry);
99      *b_square = new_limb;
100    }
101
102    let mut four_ac = b_square;
103
104    {
105      let four_ac = <_ as AsMut<[Limb]>>::as_mut(&mut four_ac);
106      for i in (0 .. four_ac.len()).rev() {
107        let new_limb = (four_ac[i] >> 2) | (carry << (Limb::BITS - 2));
108        carry = four_ac[i] & Limb::from(0b11u8);
109        four_ac[i] = new_limb;
110      }
111    }
112    let ac = four_ac;
113
114    let (c, rem) = U::wide_div_rem_thin(ac, &a);
115
116    (c, carry.is_zero() & rem.is_zero())
117  };
118
119  let reduced_absolute_values;
120  let reduced_sign;
121  {
122    let a = crypto_bigint::UintRef::new(a.as_ref().as_ref());
123    let b_abs = crypto_bigint::UintRef::new(b_abs.as_ref());
124    let c = crypto_bigint::UintRef::new(c.as_ref());
125
126    // Check `b <= a <= c`
127    reduced_absolute_values = (b_abs.ct_lt(a) | b_abs.ct_eq(a)) & (a.ct_lt(c) | a.ct_eq(c));
128    // Check the sign of `b` is positive if `a == b` or `a == c`
129    reduced_sign = {
130      let has_equality = b_abs.ct_eq(a) | a.ct_eq(c);
131      (!has_equality) | b_positive
132    };
133  }
134  // Check it's primitive
135  let primitive = U::coprime(a.as_ref().clone(), b_abs.clone(), c.clone());
136
137  CtOption::new(
138    (a, (b_positive, b_abs), c),
139    c_is_correct & reduced_absolute_values & reduced_sign & primitive,
140  )
141}
142
143mod uncompressed;
144
145#[cfg(feature = "alloc")]
146mod compressed;
147#[cfg(feature = "alloc")]
148pub(crate) use compressed::*;