class_groups/crypto_bigint/encoding/uncompressed.rs
1//! Uncompressed encoding of binary quadratic forms
2//!
3//! This implements uncompressed encoding of primitive reduced positive definite binary quadratic
4//! forms of negative discriminants (even or odd) in a constant
5//! $2 * \lceil \floor log_2(-discriminant) / 2 \rfloor / 8 \rceil$ bytes.
6//!
7//! This SHOULD NOT be used unless there's an explicit use-case for it (such as when strong bounds
8//! on runtime complexity or constant-time encoding is needed).
9//!
10//! `^` is used to represent the binary XOR operation. `//` is used to represent floor division.
11//!
12//! `floor_log_2(x)` is assumed to yield $\lfloor log_2(x) \rfloor$ for a positive integer `x`.
13//! Note that a number requires $\lfloor log_2(x) \rfloor + 1$ bits to be represented, not
14//! $\lceil log_2(x) \rceil$.
15//!
16//! ```py
17//! # Note `discriminant` is a _signed_ big integer, bound to be negative
18//! fn encode_uncompressed_binary_quadratic_form(a, b_positive, b_abs, discriminant) {
19//! bits_per_element = (floor_log_2(-discriminant) // 2) + 1
20//! bytes_per_element = (bits_per_element + 7) / 8
21//!
22//! result = a.to_le_bytes()
23//! while result.len() < bytes_per_element {
24//! result.push(0)
25//! }
26//!
27//! result.extend((b_abs ^ (!b_positive)).to_le_bytes())
28//! while result.len() < (2 * bytes_per_element) {
29//! result.push(0)
30//! }
31//!
32//! return result
33//! }
34//!
35//! # Note `discriminant` is a _signed_ big integer, bound to be negative
36//! fn decode_uncompressed_binary_quadratic_form(bytestream, discriminant) {
37//! bits_per_element = (floor_log_2(-discriminant) // 2) + 1
38//! bytes_per_element = (bits_per_element + 7) / 8
39//!
40//! a = []
41//! while a.len() < bytes_per_element {
42//! a.push(bytestream.next_byte())
43//! }
44//! a = BigInt::from_le_bytes(a)
45//!
46//! b_abs = []
47//! while b_abs.len() < bytes_per_element {
48//! b_abs.push(bytestream.next_byte())
49//! }
50//! b_abs = BigInt::from_le_bytes(b_abs)
51//!
52//! b_positive = (b_abs % 2) == (discriminant % 2)
53//! if !b_positive {
54//! b_abs = b_abs ^ 1
55//! }
56//!
57//! return validate_binary_quadratic_form(a, b_positive, b_abs, discriminant)
58//! }
59//! ```
60//!
61//! $\lfloor log_2(-discriminant) / 2 \rfloor + 1$ is used as an approximation for the length of
62//! the square root of the discriminant. Per Lemma 5.3.4 of
63//! A Course in Computational Number Theory by Henri Cohen, a reduced `a` coefficient is less than
64//! or equal to `sqrt(|D| / 3)`. This means our approximation is sufficient for any reduced `a`
65//! coefficient.
66//!
67//! The absolute value of a reduced `b` coefficient will also be less than or equal to the `a`
68//! coefficient, so it will fit within the bound. As for encoding its sign, we take advantage of
69//! how $b \cong discriminant \mod 2$ to encode the sign bit in place of the (known-value)
70//! least-significant bit to avoid requiring any additional bytes.
71//!
72//! These methods are described in a way which allocates, with lists to encode into and to store
73//! bytes which are decoded from. As the amount of bytes in the encoding is constant to the
74//! discriminant, a non-allocating implementation is actually quite immediate. The descriptions
75//! above are only as-is for simplicity, not for explicit intent these be implemented with
76//! allocations.
77//!
78//! We specifically align the uncompressed encoding exactly to byte boundaries, and do not remove
79//! any zero bytes, to ensure simplicity and the possibility of constant-time encoding.
80//! Constant-time encoding MAY be necessary for protocols which involve secret forms, where
81//! non-interactive multiplication (as seen in
82//! [Non-Interactive Distributed Point Functions](https://eprint.iacr.org/2025/095) by
83//! Elette Boyle, Lalita Devadas, Sacha Servan-Schreiber) is an example of a scheme with secret
84//! forms (but does not necessarily encode them). For those who want a more compact encoding,
85//! again, the compressed encoding SHOULD be used unless there's explicit reason to use this
86//! uncompressed encoding.