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::*;