class_groups/crypto_bigint/encoding/compressed/partial_xgcd.rs
1//! A partial extended Euclidean algorithm
2//!
3//! This is derived from `PartialXGCD`, Algorithm 1 of
4//! [Trustless unknown-order groups](https://eprint.iacr.org/2020/196)
5//! by Samuel Dobson, Steven Galbraith, and Benjamin Smith. It's been adapted to more closely
6//! resemble the binary extended Euclidean algorithm for performance reasons and to simplify
7//! implementation.
8//!
9//! Implementations of this function MUST return the singular canonical value consistent with the
10//! specification. Other partial extended Euclidean algorithms MUST NOT be used UNLESS they yield
11//! identical results. It's for this reason that the described algorithm is the efficient binary
12//! adaptation, rather than the existing one which was already proven.
13//!
14//! The pseudocode denotes what we would discuss as `s'` as `s_apo`, instead of the more
15//! traditional `s_prime`. This is to avoid confusion on if this variable is notably considered
16//! (co)prime.
17//!
18//! We assume the existence of a `floor_sqrt` function, which for `floor_sqrt(x)` where `x` is a
19//! positive unsigned integer, yields `y` such that $y^2 \le x, x < (y + 1)^2$. We also assume the
20//! existence of a `max` function which for `max(x, y)` yields `x` if $x \ge y$ else `y`.
21//!
22//! `**` is used to notate exponentation.
23//!
24//! ```py
25//! fn t(a, b) {
26//! ceil_sqrt_a = floor_sqrt(a)
27//! if (ceil_sqrt_a * ceil_sqrt_a) != a {
28//! ceil_sqrt_a += 1
29//! }
30//!
31//! (s, s_apo, t, t_apo) = (b, a, 1, 0)
32//! while s >= ceil_sqrt_a {
33//! log_2_q = max(floor(log_2(s_apo)) - floor(log_2(s)) - 1, 0)
34//! q = 2**log_2_q
35//!
36//! # Note `t_apo` is a _signed_ integer operation, though we do bound `s_apo >= 0`
37//! (s_apo, t_apo, u_apo) = (s_apo - q * s, t_apo - q * t)
38//! if s_apo < s {
39//! (s, s_apo, t, t_apo, u, u_apo) = (s_apo, s, t_apo, t)
40//! }
41//! }
42//!
43//! # Convert `t` from signed to unsigned (with sign bit)
44//! t_positive = t >= 0
45//! if t < 0 {
46//! t = -t
47//! }
48//!
49//! (t_positive, t_abs)
50//! }
51//! ```
52//!
53//! For input $0 < a$, $0 \le b \le a$, the function `t` yields `(t_positive, t_abs)` representing
54//! a number `t` satisfying $s \cong t * b \mod a$, where $0 \le s < sqrt(a)$ and
55//! $0 < t \le sqrt(a)$. Its loop terminates within
56//! $2 * \lfloor log_2(a) \rfloor + 1 + \lfloor log_2(b) \rfloor + 1$ iterations.
57//!
58//! $s \le s'$ is an invariant of our loop, as holds true at initialization ($s, s' = b, a$ where
59//! $b \le a$) and at the end of each iteration of our loop (as if $s' < s$, the two are swapped).
60//!
61//! $q s \le s'$ holds from the choice of
62//! $q = 2^{max(\lfloor log_2(s_apo) \rfloor - \lfloor log_2(s) \rfloor - 1, 0)}$. When $s'$ has
63//! bit-length exceeding $s$, $q s$ has bit-length less than $s'$. When $s'$ has bit-length equal
64//! to $s$, $q = 1$ and $q s = s$ where $s \le s'$ is an invariant of our loop.
65//!
66//! `s` is initialized to `b` and only updated when swapped with `s'`, which only happens when
67//! `s' < s`. This allows us to bound `s` as strictly decreasing and never of size exceeding `b`.
68//!
69//! `s'` is initialized to `a` and with each iteration, updated to `s' = s' - q s` where
70//! $q s \le s'$ has already been proven to hold. This lets us bound $0 \le s' \le a$ (and
71//! therefore $0 \le s$).
72//!
73//! `s'` is reduced by at least one bit every two iterations, effecting our termination bound of
74//! $2 * \lfloor log_2(a) \rfloor + 1 + \lfloor log_2(b) \rfloor + 1$. This is as $2 q s$ has
75//! bit-length equal to or greater than $s'$ and the difference of two numbers with equal
76//! bit-length has itself a smaller bit-length. This does have a factor of `2` compared to the
77//! traditional bound on the termination of the binary extended Euclidean algorithm, which is
78//! required to ensure we iterate over the `s, t` pair for which our bounds on the output are
79//! satisfied. A tighter bound is still possible as we terminate when $s < sqrt(a)$ yet the
80//! described bound would be for if we terminated when $s = 1$ (assuming $a, b$ coprime). As this
81//! current bound on termination is sufficient, we do not put in the work on further analysis here
82//! as it's simply unnecessary.
83//!
84//! The function only terminates once $s < sqrt(a)$, so therefore our bounds on the result that
85//! $0 \le s < sqrt(a)$ have been proven.
86//!
87//! To satisfy $0 < t$, we prove $t = 0$ never occurs. $t$ is initialized to $1$ and will be one if
88//! the loop doesn't iterate. If the loop does iterate, on its first iteration, we have
89//! $t' = t' - q t$ where $t' = 0$ and therefore $t' = -q t$. As $0 < q$, this first iteration of
90//! assigns $t'$ to have the opposite sign of $t$. This is preserved throughout further iterations
91//! as the swap of $t, t'$ does not change how they have opposing signs and $t' = t' - q t$ causes
92//! $t'$ to maintain its existing sign (as the difference of numbers with opposite signs is the sum
93//! of their absolute value with the sign of the number subtracted from). Having established that
94//! if the loop iterates, $t, t'$ have distinct signs, we note it's impossible for $t'$ to be set
95//! to $0$ (except when initialized) as that would require $t' = t' - q t = (q t) - q t = 0$ (when
96//! we have proven $t \ne t'$). In turn, this makes it impossible for $t$ to be set to $0$, as $t$
97//! is only updated when swapped with $t'$.
98//!
99//! We continue to specifically prove the invariant $s' |t| \le a$ by proving the invariant
100//! $s' |t| + s |t'| = a$. This invariant, via this second invariant, was proven as part of
101//! Mathematics of Public Key Cryptography by Steven Galbraith, Lemma 2.3.3, but we provide the
102//! proof here for completeness and as it doesn't immediately apply (due to explicitly being for
103//! their described Euclidean algorithm which does not match our described Euclidean algorithm). At
104//! initialization, we have `(s, s', t, t') = (b, a, 1, 0)`, which upholds the invariant. It's
105//! clear the swap preserves the invariant, leaving us to prove the updates
106//! (`s' = s' - q s, t' = t' - q s`) do. By expansion,
107//! $s' |t| + s |t'| = a = (s' - q s) |t| + s |(t' - q t)|$ requires $(-q s) |t| + s |q t| = 0$,
108//! which is clear.
109//!
110//! As $|s' t| \le a$, we can therefore bound $t \le sqrt(a)$ by proving $s' \ge sqrt(a)$. The loop
111//! terminates when $s < sqrt(a)$. As $s'$ _was_ the $s$ value prior to this final swap, if the
112//! loop ever iterated, but the loop did not terminate, $s'$ must have been greater than or equal
113//! to $sqrt(a)$. If the loop never iterated, then we have $s' = a$ (as initialized) where
114//! $a \ge sqrt(a)$ as $0 < a$.
115//!
116//! $s \cong t * b \mod a$ is a result of two invariants:
117//! - $s \cong b t \mod a$
118//! - $s' \cong b t' \mod a$
119//!
120//! At initialization, we have $s, s', t, t' = b, a, 1, 0$, for which these are upheld. During the
121//! loop, we update:
122//! - `s' = s' - q s`
123//! - `t' = t' - q t`
124//!
125//! For the relevant invariant which requires $(s' - q s) \cong b (t' - qt ) \mod a$, we may expand
126//! this as $b t' - q b t \cong b (t' - q t) \mod a$, which is clearly correct. The only other
127//! updates during the loop are the potential simultaneously swap of $s', t'$ with $s, t$, also
128//! upholding the invariants.
129
130use crypto_bigint::{Choice, NonZero, Resize as _, ConcatenatingSquare as _, BoxedUint};
131
132/// The implementation of the `t` function as described in the specification.
133///
134/// This function runs in time variable to the input. While a constant-time variant is feasible, as
135/// the binary GCD is itself feasible to implement in constant-time and we have a bound on
136/// termination, there's not currently value to such an implementation when compression as a whole
137/// is currently posited in variable-time.
138pub(super) fn t(a: NonZero<BoxedUint>, b: BoxedUint) -> (Choice, NonZero<BoxedUint>) {
139 let ceil_sqrt_a = {
140 let floor_sqrt_a = a.as_ref().floor_sqrt_vartime();
141 if floor_sqrt_a.concatenating_square() == a.as_ref() {
142 floor_sqrt_a
143 } else {
144 floor_sqrt_a + BoxedUint::one()
145 }
146 };
147
148 let precision = a.bits_precision();
149 #[cfg(debug_assertions)]
150 let original_a = a.clone();
151 #[cfg(debug_assertions)]
152 let original_b = b.clone();
153 let (mut s, mut s_apo, mut t, mut t_apo) = (
154 b.resize(precision),
155 a.get(),
156 (Choice::TRUE, BoxedUint::one_with_precision(precision)),
157 (Choice::TRUE, BoxedUint::zero_with_precision(precision)),
158 );
159
160 let mut i = 0;
161 while s >= ceil_sqrt_a {
162 #[cfg(debug_assertions)]
163 {
164 use crypto_bigint::CtSelect as _;
165 let t = <_>::ct_select(&t.1.neg_mod(&original_a), &t.1, t.0);
166 let t_apo = <_>::ct_select(&t_apo.1.neg_mod(&original_a), &t_apo.1, t_apo.0);
167 debug_assert_eq!(original_b.mul_mod(&t, &original_a), s.rem(&original_a));
168 debug_assert_eq!(original_b.mul_mod(&t_apo, &original_a), s_apo.rem(&original_a));
169 }
170
171 let log_2_q = (s_apo.bits_vartime() - s.bits_vartime()).saturating_sub(1);
172
173 {
174 let qs = s.clone() << log_2_q;
175 // $q |s| < s'$
176 s_apo = if s_apo < qs { qs - s_apo } else { s_apo - qs };
177 }
178
179 {
180 let qt = t.1.clone() << log_2_q;
181 if i == 0 {
182 // `t' = t' - qt`, optimized with the knowledge `t' = 0`
183 t_apo = (!t_apo.0, qt);
184 } else {
185 // `t' = t' - q t`, optimized with the knowledge $sgn(t') \ne sgn(t)$
186 t_apo = (t_apo.0, t_apo.1 + qt);
187 }
188 }
189
190 if s_apo < s {
191 (s, s_apo, t, t_apo) = (s_apo, s, t_apo, t);
192 }
193
194 i += 1;
195 }
196
197 let (t_positive, t_abs) = t;
198 (t_positive, NonZero::new(t_abs).expect("`t` proven to be non-zero"))
199}
200
201#[test]
202fn test() {
203 use rand::Rng as _;
204 use crypto_bigint::{CtSelect as _, RandomBits as _, RandomMod as _};
205
206 let mut rng = rand::rand_core::UnwrapErr(rand::rngs::SysRng);
207
208 let non_zero_a = |rng: &mut _| {
209 // Sample a non-zero `a`
210 let mut a;
211 while {
212 const BITS: u32 = 512;
213 a = NonZero::new(BoxedUint::random_bits(&mut *rng, BITS));
214 bool::from(a.is_none())
215 } {}
216 a.unwrap()
217 };
218
219 // Test `(0, 1)`
220 {
221 let a = NonZero::new(BoxedUint::one()).unwrap();
222 let b = BoxedUint::zero();
223 let (t_positive, t_abs) = t(a, b);
224 assert!(bool::from(t_positive));
225 assert_eq!(t_abs, NonZero::new(BoxedUint::one()).unwrap());
226 }
227
228 // Test `(1, 1)`
229 {
230 let a = NonZero::new(BoxedUint::one()).unwrap();
231 let b = BoxedUint::one();
232 let (t_positive, t_abs) = t(a, b);
233 assert!(bool::from(!t_positive));
234 assert_eq!(t_abs, NonZero::new(BoxedUint::one()).unwrap());
235 }
236
237 // Test `(0, rand())`
238 {
239 let a = non_zero_a(&mut rng);
240 let b = BoxedUint::zero();
241 let (t_positive, t_abs) = t(a, b);
242 assert!(bool::from(t_positive));
243 assert_eq!(t_abs, NonZero::new(BoxedUint::one()).unwrap());
244 }
245
246 for _ in 0 .. 1024 {
247 // Sample a non-zero `a`
248 let a = non_zero_a(&mut rng);
249
250 // Sample `0 <= b <= a`
251 let mut b = {
252 let a_plus_one =
253 NonZero::new(a.as_ref().clone().concatenating_add(BoxedUint::one())).unwrap();
254 BoxedUint::random_mod_vartime(&mut rng, &a_plus_one)
255 };
256
257 // Ensure `b == a` a fourth of the time
258 if (rng.next_u64() % 4) == 0 {
259 b = a.as_ref().clone();
260 }
261
262 let (t_positive, t_abs) = t(a.clone(), b.clone());
263 let t_abs = t_abs.get();
264
265 // $t * b \cong s \mod a$
266 let s_abs = t_abs.mul_mod(&b, &a);
267 let s = <_>::ct_select(&s_abs.neg_mod(&a), &s_abs, t_positive);
268
269 let floor_sqrt_a = a.floor_sqrt_vartime();
270 assert!(s < floor_sqrt_a);
271 assert!(t_abs <= floor_sqrt_a);
272 }
273}