Skip to main content

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}