literate_crypto/pubkey/ecc/num.rs
1use {
2 crate::{
3 ecc::{Curve, Point},
4 util,
5 },
6 docext::docext,
7 std::{cmp, iter, mem, ops},
8};
9
10/// Number used for modular arithmetic. Internally stored in little-endian
11/// (least-significant byte first) format.
12#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
13pub struct Num([u64; Self::WIDTH]);
14
15impl Num {
16 pub const ZERO: Num = Num([0, 0, 0, 0]);
17 pub const ONE: Num = Num([1, 0, 0, 0]);
18 pub const TWO: Num = Num([2, 0, 0, 0]);
19 pub const THREE: Num = Num([3, 0, 0, 0]);
20 pub const SEVEN: Num = Num([7, 0, 0, 0]);
21
22 /// The size of this number in 64-bit words.
23 pub const WIDTH: usize = 4;
24 /// The size of this number in bits.
25 pub const BITS: usize = Self::WIDTH * u64::BITS as usize;
26 /// The size of this number in bytes.
27 pub const BYTES: usize = Self::BITS / 8;
28
29 pub const fn from_le_words(n: [u64; Self::WIDTH]) -> Self {
30 Self(n)
31 }
32
33 pub fn from_le_bytes(b: [u8; Self::BYTES]) -> Self {
34 const S: usize = mem::size_of::<u64>();
35 Self::from_le_words([
36 u64::from_le_bytes(b[..S].try_into().unwrap()),
37 u64::from_le_bytes(b[S..2 * S].try_into().unwrap()),
38 u64::from_le_bytes(b[2 * S..3 * S].try_into().unwrap()),
39 u64::from_le_bytes(b[3 * S..4 * S].try_into().unwrap()),
40 ])
41 }
42
43 pub fn to_le_bytes(&self) -> [u8; Self::BYTES] {
44 let mut result = [0u8; Self::BYTES];
45 result
46 .iter_mut()
47 .zip(self.0.iter().flat_map(|n| n.to_le_bytes()))
48 .for_each(|(a, b)| *a = b);
49 result
50 }
51
52 /// Modular addition with modulus `p`.
53 #[must_use]
54 pub fn add(&self, n: Self, p: Self) -> Self {
55 let (n, carry) = add(self.0, n.0);
56 if carry.0 {
57 // To account for the carry bit, extend n by a single most significant byte
58 // equal to 1, then do the reduction.
59 let mut ext = [0; Self::WIDTH + 1];
60 ext.iter_mut()
61 .zip(n.into_iter().chain(iter::once(1)))
62 .for_each(|(a, b)| *a = b);
63 Self(reduce(ext, p.0))
64 } else {
65 Self(reduce(n, p.0))
66 }
67 }
68
69 /// Modular subtraction with modulus `p`.
70 #[must_use]
71 pub fn sub(self, n: Self, p: Self) -> Self {
72 let (n, borrow) = sub(self.0, n.0);
73 if borrow.0 {
74 // If there was a borrow, then the result is negative, so add MOD to
75 // make it positive. This addition is guaranteed to result in a carry, and the
76 // carry and borrow bits "cancel" each other out. Note that adding
77 // MOD in a prime field modulus MOD is a no-op, and also note that
78 // self and rhs are both already reduced modulus MOD before the
79 // subtraction.
80 let (add, carry) = add(n, p.0);
81 assert!(carry.0);
82 Self(add)
83 } else {
84 Self(n)
85 }
86 }
87
88 /// Modular multiplication with modulus `p`.
89 #[must_use]
90 pub fn mul(self, n: Self, p: Self) -> Self {
91 // Same as multiplication on paper.
92 let mut prod = [0; Self::WIDTH * 2];
93 for (i, a) in self.0.into_iter().enumerate() {
94 let mut carry = 0u128;
95 for (j, b) in n.0.into_iter().enumerate() {
96 let m = prod[i + j] as u128 + a as u128 * b as u128 + carry;
97 // The upper u64::BITS are the carry part.
98 carry = (m & ((u64::MAX as u128) << u64::BITS)) >> u64::BITS;
99 // The lower u64::BITS are the digit to store at i + j.
100 prod[i + j] = u64::try_from(m & u64::MAX as u128).unwrap();
101 }
102 // The final carry becomes the next digit over.
103 prod[i + Self::WIDTH] = u64::try_from(carry).unwrap();
104 }
105 Self(reduce(prod, p.0))
106 }
107
108 /// Modular equality with modulus `p`.
109 pub fn eq(self, n: Self, p: Self) -> bool {
110 reduce(self.0, p.0) == reduce(n.0, p.0)
111 }
112
113 /// Reduction modulo `p`.
114 pub fn reduce(self, p: Self) -> Self {
115 Self(reduce(self.0, p.0))
116 }
117
118 /// Get the modular multiplicative inverse of the number by using the
119 /// extended Euclidean algorithm. Returns `None` for [`Num::ZERO`],
120 /// since 0 has no inverse.
121 ///
122 /// The non-extended Euclidean algorithm computes the greatest common
123 /// divisor $gcd(a, b)$ given $a, b, a \leq b$. It relies on the following
124 /// fact: $gcd(a, b) = gcd(b - \lfloor \frac{b}{a} \rfloor a, a)$. This fact
125 /// allows the algorithm to successively reduce the values of $a$ and
126 /// $b$ until one is eventually equal to zero, and the other is equal to
127 /// the greatest common divisor. The algorithm operates as follows:
128 ///
129 /// - Set $u = a, v = b$. The algorithm maintains the invariant that $u \leq
130 /// v$.
131 /// - Iteratively update $u$ and $v$. First, get the quotient $q = \lfloor
132 /// \frac{v}{u} \rfloor$, then set the new values $v' = u, u' = v - qu$.
133 /// Note that $v - qu$ is the remainder from dividing $v$ by $u$. Call
134 /// this remainder $r$, so that $u = r$.
135 /// - Terminate when $u = 0$. $v$ is the greatest common divisor.
136 ///
137 /// To extend the algorithm above, apply Bezout's identity. This identity
138 /// states that, given two integers $a$ and $b$ with greatest common
139 /// divisor $d$, there exist integers $x$ and $y$ such that $ax + by =
140 /// d$.
141 ///
142 /// The extended algorithm will represent $u$ and $v$ as
143 ///
144 /// $$
145 /// u = x_1a + y_1b \\
146 /// v = x_2a + y_2b
147 /// $$
148 ///
149 /// Since $u$ should be initialized to $a$, and $v$ should
150 /// be initialized to $b$, the initial values for $x_{1, 2}$ and $y_{1,
151 /// 2}$ are $x_1 = 1, y_1 = 0, x_2 = 0, y_2 = 1$.
152 ///
153 /// The rest of the algorithm is exactly the same, except that apart from
154 /// updating $u$ and $v$ like the regular Euclidean algorithm, the
155 /// extended Euclidean algorithm also updates $x_{1, 2}$ and $y_{1, 2}$.
156 /// This is done as follows:
157 ///
158 /// $$
159 /// x_2' = x_1 \\
160 /// x_1' = x_2 - qx_1 \\
161 /// y_2' = y_1 \\
162 /// y_1' = y_2 - qy_1
163 /// $$
164 ///
165 /// Where $q = \lfloor \frac{v}{u} \rfloor$ is the quotient and $r = v - qu$
166 /// is the remainder, same as in the non-extended Euclidean algorithm. It is
167 /// not difficult to verify that the values for $x_{1, 2}'$ and $y_{1, 2}'$
168 /// are correct. Namely, it must be true that $v' = u$ and $u' = r$ as in
169 /// the non-extended Euclidean algorithm. This can be shown with a few
170 /// substitutions:
171 ///
172 /// $$
173 /// v' = x_2'a + y_2'b \\
174 /// v' = x_1a + y_1 b \\
175 /// v' = u
176 /// $$
177 ///
178 /// And
179 ///
180 /// $$
181 /// u' = x_1'a + y_1'b \\
182 /// u' = (x_2 - qx_1)a + (y_2 - qy_1)b \\
183 /// u' = ax_2 + by_2 - (ax_1 + by_1)q \\
184 /// u' = v - qu
185 /// $$
186 ///
187 /// So $v' = u, u' = v - qu$ as expected. The algorithm terminates when $u =
188 /// 0$, at which point $x_2$, $y_2$, and $v$ are the result of the
189 /// algorithm.
190 ///
191 /// Finally, the above can be used to get a multiplicative inverse. If $b$
192 /// (or $a$) is prime, the result of the algorithm will be $v = 1$ because
193 /// the greatest common divisor between a prime number and any other
194 /// number is 1.
195 ///
196 /// $$
197 /// v = 1 = x_2a + y_2b
198 /// $$
199 ///
200 /// If the operations are done in a prime field with order $b$, then
201 ///
202 /// $$
203 /// y_2b \equiv 0 \pmod b \implies v \equiv x_2a \equiv 1 \pmod b
204 /// $$
205 ///
206 /// This means that $x_2$ is the multiplicative inverse of $a$
207 /// in the prime field with order $b$. Finally, since $y_1$ and $y_2$ are
208 /// not used, they can be omitted from the algorithm as a small
209 /// optimization.
210 #[docext]
211 #[must_use]
212 pub fn inv(&self, p: Self) -> Option<Self> {
213 if *self == Self::ZERO {
214 return None;
215 }
216
217 let mut u = reduce(self.0, p.0);
218 let mut v = p.0;
219 let mut x1 = Self::ONE;
220 let mut x2 = Self::ZERO;
221 while u != Self::ZERO.0 {
222 let (q, r) = div(v, u);
223 v = u;
224 u = r.0;
225 let x = x2.sub(Self(q).mul(x1, p), p);
226 x2 = x1;
227 x1 = x;
228 }
229 Some(x2)
230 }
231
232 /// Get the bit at the given index. The rightmost (least significant) bit is
233 /// at index 0.
234 pub fn get_bit(&self, i: usize) -> bool {
235 get_bit(self.0, i)
236 }
237}
238
239impl cmp::PartialOrd for Num {
240 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
241 Some(self.cmp(other))
242 }
243}
244
245impl cmp::Ord for Num {
246 fn cmp(&self, other: &Self) -> cmp::Ordering {
247 // Compare the digits in most-significant-first order.
248 for (a, b) in self.0.iter().zip(other.0.iter()).rev() {
249 match a.cmp(b) {
250 cmp::Ordering::Less => return cmp::Ordering::Less,
251 cmp::Ordering::Equal => {}
252 cmp::Ordering::Greater => return cmp::Ordering::Greater,
253 }
254 }
255 cmp::Ordering::Equal
256 }
257}
258
259/// Flag to indicate if a subtraction resulted in a borrow.
260#[derive(Debug, Clone, Copy, PartialEq, Eq)]
261struct Borrow(bool);
262
263/// Flag to indicate if an addition resulted in a carry.
264#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265struct Carry(bool);
266
267/// The remainder left after a division.
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269struct Rem<const N: usize>([u64; N]);
270
271/// Subtract two numbers.
272#[must_use]
273fn sub<const N: usize>(a: [u64; N], b: [u64; N]) -> ([u64; N], Borrow) {
274 // The easiest way to understand this code is to do subtractions on paper and
275 // watch how digits are borrowed from. Some examples to go through might be:
276 // 591 - 202, where the middle digit is borrowed from;
277 // 201 - 192, where the first two digits are borrowed from;
278 // 201 - 292, where the first two digits are borrowed from and the result
279 // includes a borrow bit.
280 // The only difference is that in this implementation digits range from 0 to
281 // u64::MAX, whereas on paper they range from 0 to 9, and they are stored in
282 // LSB-first order, whereas on paper they are MSB-first.
283 let mut borrow = false;
284 let mut result = [0; N];
285 for ((a, b), r) in a.iter().zip(&b).zip(result.iter_mut()) {
286 let (sub, overflow) = a.overflowing_sub(*b);
287 *r = sub;
288 if overflow {
289 if borrow {
290 // If the subtraction overflowed, and this digit was borrowed from, then
291 // subtract the borrow from the result. It is impossible for this subtraction to
292 // overflow, because the result must be at least 1 due to the previous
293 // subtraction having overflowed.
294 *r -= 1;
295 }
296 // The subtraction overflowed, so borrow from the next digit.
297 borrow = true;
298 } else {
299 // There was no overflow. Subtract the borrow bit.
300 let (sub, overflow) = r.overflowing_sub(borrow as u64);
301 *r = sub;
302 if overflow {
303 // If subtracting the borrow bit overflowed, then the next
304 // digit must be borrowed from. Don't clear the borrow bit.
305 } else {
306 // If subtracting the borrow bit did not overflow, then the
307 // borrow bit was either already clear or it was
308 // successfully "used", and hence can be cleared.
309 borrow = false;
310 }
311 }
312 }
313 (result, Borrow(borrow))
314}
315
316/// Add two numbers.
317#[must_use]
318fn add<const N: usize>(a: [u64; N], b: [u64; N]) -> ([u64; N], Carry) {
319 // Same as addition on paper.
320 let mut carry = false;
321 let mut result = [0; N];
322 for ((a, b), r) in a.iter().zip(&b).zip(result.iter_mut()) {
323 let (add, overflow) = a.overflowing_add(*b);
324 *r = add;
325 if carry {
326 // If the carry bit is set, increment the result by one. If this operation
327 // overflows, set the carry bit. If it doesn't overflow, then the
328 // carry bit was successfully "used", so clear it.
329 let (add, overflow) = r.overflowing_add(1);
330 *r = add;
331 carry = overflow;
332 }
333 if overflow {
334 // If the original addition overflowed, there was a carry. This is true
335 // regardless of the current state of the carry bit.
336 carry = true;
337 }
338 }
339 (result, Carry(carry))
340}
341
342/// Divide two numbers.
343#[must_use]
344fn div<const N: usize>(n: [u64; N], d: [u64; N]) -> ([u64; N], Rem<N>) {
345 // This is an implementation of long division. It's the same as long division
346 // done on paper, except it's done in base 2 instead of base 10. The easiest
347 // way to understand the algorithm is to do an example on paper in base ten,
348 // e.g. 587 / 342, and see how the base 2 algorithm below corresponds to the
349 // base 10 algorithm done on paper.
350 //
351 // The long division algorithm can be roughly explained in words as follows:
352 // keep track of a running remainder. For each digit of the dividend, append
353 // the digit to the running remainder. Count how many times the divisor can
354 // be subtracted from the running remainder, do the subtractions, and append
355 // the count to the result as a single digit. Note that the count may be zero.
356 // The algorithm finishes when there are no more digits in the dividend,
357 // resulting in a quotient and a remainder.
358 let mut q = [0; N];
359 let mut r = [0; N];
360 for i in (0..N * u64::BITS as usize).rev() {
361 r = shl(r);
362 if get_bit(n, i) {
363 r = set_bit(r, 0);
364 }
365 let (sub, borrow) = sub(r, d);
366 if !borrow.0 {
367 // The subtraction didn't require a borrow, which means that r >= d, i.e. the
368 // subtraction was successful.
369 r = sub;
370 // Because this is long division in base 2, only a 1 or a 0 can be appended
371 // to the result. In case of successful division, a 1 is appended, and at most
372 // one subtraction is made to the running remainder. This is different from long
373 // division in base 10, where any digit from 0 to 9 can be appended,
374 // and at most nine subtractions could be made (although in practice
375 // a human does not subtract 9 times, instead he divides two small numbers in
376 // his head).
377 q = set_bit(q, i);
378 }
379 }
380 (q, Rem(r))
381}
382
383/// Reduce a number modulo another number.
384#[must_use]
385fn reduce<const N: usize, const P: usize>(n: [u64; N], p: [u64; P]) -> [u64; P] {
386 assert!(N >= P);
387 let (_div, rem) = div(n, util::resize(p));
388 util::resize(rem.0)
389}
390
391/// Shift all of the bits left by one.
392#[must_use]
393fn shl<const N: usize>(n: [u64; N]) -> [u64; N] {
394 let mut res = [0; N];
395 let mut msb = false;
396 for (i, digit) in n.into_iter().enumerate() {
397 res[i] = digit.wrapping_shl(1);
398 // If the most significant bit was shifted out of the previous digit, the next
399 // digit should have the least significant bit set after the shift.
400 if msb {
401 res[i] |= 1;
402 }
403 msb = digit & (1 << (u64::BITS - 1)) != 0;
404 }
405 res
406}
407
408/// Get the bit at the given index. The rightmost (least significant) bit is at
409/// index 0.
410#[must_use]
411fn get_bit<const N: usize>(n: [u64; N], i: usize) -> bool {
412 let digit = i / u64::BITS as usize;
413 let i = i % u64::BITS as usize;
414 n[digit] & (1 << i) != 0
415}
416
417/// Set the bit at the given index. Note that the rightmost bit is at index
418/// 0, the leftmost at index 255.
419#[must_use]
420fn set_bit<const N: usize>(mut n: [u64; N], i: usize) -> [u64; N] {
421 let digit = i / u64::BITS as usize;
422 let i = i % u64::BITS as usize;
423 n[digit] |= 1 << i;
424 n
425}
426
427/// Multiply the point by a scalar.
428///
429/// This uses the _square-and-multiply_ method. For example, to calculate
430/// $x^{19}$, start with $y = x$ and multiply $y$ with itself, resulting in $y =
431/// y \cdot y = x^2$. Then, multiply $y$ with itself again, resulting in
432/// $y = y \cdot y = x^4$. Repeat this until it can no longer be done, at which
433/// point $y = x^{16}$ and there have been four multiplications thus far.
434/// Finally, multiply $y$ with $x$ three more times to get the desired result.
435///
436/// With this method, $x^{19}$ was calculated in only seven multiplications,
437/// compared to the naive algorithm which would execute 19 multiplications.
438///
439/// In the case of elliptic curve points, the "square" is equivalent to
440/// doubling, and "multiply" is equivalent to addition.
441#[docext]
442impl<C: Curve> ops::Mul<Point<C>> for Num {
443 type Output = Point<C>;
444
445 fn mul(self, rhs: Point<C>) -> Self::Output {
446 rhs.scale(self)
447 }
448}