we (web engine): Experimental web browser project to understand the limits of Claude
1//! Arbitrary-precision unsigned integer arithmetic for RSA.
2//!
3//! Provides a `BigUint` type backed by little-endian `u64` limbs, with
4//! modular exponentiation (square-and-multiply) for up to 4096-bit keys.
5
6use core::cmp::Ordering;
7
8/// Arbitrary-precision unsigned integer stored as little-endian `u64` limbs.
9#[derive(Clone, Debug)]
10pub struct BigUint {
11 /// Limbs in little-endian order (limbs[0] is the least significant).
12 limbs: Vec<u64>,
13}
14
15impl BigUint {
16 /// The number zero.
17 pub fn zero() -> Self {
18 Self { limbs: vec![0] }
19 }
20
21 /// The number one.
22 pub fn one() -> Self {
23 Self { limbs: vec![1] }
24 }
25
26 /// Create from a single u64.
27 pub fn from_u64(v: u64) -> Self {
28 Self { limbs: vec![v] }
29 }
30
31 /// Create from big-endian bytes (as used in DER INTEGER encoding).
32 pub fn from_be_bytes(bytes: &[u8]) -> Self {
33 if bytes.is_empty() {
34 return Self::zero();
35 }
36
37 // Convert big-endian bytes to little-endian u64 limbs.
38 let mut limbs = Vec::new();
39 let mut i = bytes.len();
40 while i > 0 {
41 let start = i.saturating_sub(8);
42 let chunk = &bytes[start..i];
43 let mut val = 0u64;
44 for &b in chunk {
45 val = (val << 8) | b as u64;
46 }
47 limbs.push(val);
48 i = start;
49 }
50
51 let mut result = Self { limbs };
52 result.normalize();
53 result
54 }
55
56 /// Export to big-endian bytes.
57 pub fn to_be_bytes(&self) -> Vec<u8> {
58 if self.is_zero() {
59 return vec![0];
60 }
61
62 let mut bytes = Vec::new();
63 // Start from the most significant limb.
64 let mut started = false;
65 for &limb in self.limbs.iter().rev() {
66 for shift in (0..8).rev() {
67 let byte = (limb >> (shift * 8)) as u8;
68 if !started && byte == 0 {
69 continue;
70 }
71 started = true;
72 bytes.push(byte);
73 }
74 }
75
76 if bytes.is_empty() {
77 bytes.push(0);
78 }
79 bytes
80 }
81
82 /// Export to big-endian bytes, zero-padded to exactly `len` bytes.
83 pub fn to_be_bytes_padded(&self, len: usize) -> Vec<u8> {
84 let raw = self.to_be_bytes();
85 if raw.len() >= len {
86 // Take the last `len` bytes (truncate leading bytes).
87 return raw[raw.len() - len..].to_vec();
88 }
89 let mut out = vec![0u8; len - raw.len()];
90 out.extend_from_slice(&raw);
91 out
92 }
93
94 /// True if the value is zero.
95 pub fn is_zero(&self) -> bool {
96 self.limbs.iter().all(|&l| l == 0)
97 }
98
99 /// Number of significant bits.
100 pub fn bit_len(&self) -> usize {
101 if self.is_zero() {
102 return 0;
103 }
104 let top = self.limbs.len() - 1;
105 let top_bits = 64 - self.limbs[top].leading_zeros() as usize;
106 top * 64 + top_bits
107 }
108
109 /// Get bit at position `i` (0-indexed from LSB).
110 pub fn bit(&self, i: usize) -> bool {
111 let limb_idx = i / 64;
112 let bit_idx = i % 64;
113 if limb_idx >= self.limbs.len() {
114 return false;
115 }
116 (self.limbs[limb_idx] >> bit_idx) & 1 == 1
117 }
118
119 /// Remove trailing zero limbs (keep at least one limb).
120 fn normalize(&mut self) {
121 while self.limbs.len() > 1 && *self.limbs.last().unwrap() == 0 {
122 self.limbs.pop();
123 }
124 }
125
126 /// Addition: self + other.
127 pub fn add(&self, other: &Self) -> Self {
128 let max_len = self.limbs.len().max(other.limbs.len());
129 let mut result = Vec::with_capacity(max_len + 1);
130 let mut carry = 0u64;
131
132 for i in 0..max_len {
133 let a = if i < self.limbs.len() {
134 self.limbs[i]
135 } else {
136 0
137 };
138 let b = if i < other.limbs.len() {
139 other.limbs[i]
140 } else {
141 0
142 };
143 let (sum1, c1) = a.overflowing_add(b);
144 let (sum2, c2) = sum1.overflowing_add(carry);
145 result.push(sum2);
146 carry = (c1 as u64) + (c2 as u64);
147 }
148
149 if carry > 0 {
150 result.push(carry);
151 }
152
153 let mut r = Self { limbs: result };
154 r.normalize();
155 r
156 }
157
158 /// Subtraction: self - other. Panics if other > self.
159 pub fn sub(&self, other: &Self) -> Self {
160 debug_assert!(self.cmp(other) != Ordering::Less);
161 let mut result = Vec::with_capacity(self.limbs.len());
162 let mut borrow = 0u64;
163
164 for i in 0..self.limbs.len() {
165 let a = self.limbs[i];
166 let b = if i < other.limbs.len() {
167 other.limbs[i]
168 } else {
169 0
170 };
171 let (diff1, b1) = a.overflowing_sub(b);
172 let (diff2, b2) = diff1.overflowing_sub(borrow);
173 result.push(diff2);
174 borrow = (b1 as u64) + (b2 as u64);
175 }
176
177 let mut r = Self { limbs: result };
178 r.normalize();
179 r
180 }
181
182 /// Multiplication: self * other.
183 pub fn mul(&self, other: &Self) -> Self {
184 let n = self.limbs.len();
185 let m = other.limbs.len();
186 let mut result = vec![0u64; n + m];
187
188 for i in 0..n {
189 let mut carry = 0u128;
190 for j in 0..m {
191 let prod = (self.limbs[i] as u128) * (other.limbs[j] as u128)
192 + result[i + j] as u128
193 + carry;
194 result[i + j] = prod as u64;
195 carry = prod >> 64;
196 }
197 result[i + m] = carry as u64;
198 }
199
200 let mut r = Self { limbs: result };
201 r.normalize();
202 r
203 }
204
205 /// Division with remainder: returns (quotient, remainder).
206 pub fn div_rem(&self, divisor: &Self) -> (Self, Self) {
207 assert!(!divisor.is_zero(), "division by zero");
208
209 if self.cmp(divisor) == Ordering::Less {
210 return (Self::zero(), self.clone());
211 }
212
213 if divisor.limbs.len() == 1 {
214 return self.div_rem_single(divisor.limbs[0]);
215 }
216
217 self.div_rem_knuth(divisor)
218 }
219
220 /// Single-limb division.
221 fn div_rem_single(&self, d: u64) -> (Self, Self) {
222 let mut quotient = vec![0u64; self.limbs.len()];
223 let mut rem = 0u128;
224
225 for i in (0..self.limbs.len()).rev() {
226 rem = (rem << 64) | self.limbs[i] as u128;
227 quotient[i] = (rem / d as u128) as u64;
228 rem %= d as u128;
229 }
230
231 let mut q = Self { limbs: quotient };
232 q.normalize();
233 (q, Self::from_u64(rem as u64))
234 }
235
236 /// Multi-limb division using Knuth's Algorithm D.
237 fn div_rem_knuth(&self, divisor: &Self) -> (Self, Self) {
238 let n = divisor.limbs.len();
239 let m = self.limbs.len() - n;
240
241 // Normalize: shift so that the top bit of divisor is set.
242 let shift = divisor.limbs[n - 1].leading_zeros();
243 let u = self.shl_bits(shift);
244 let v = divisor.shl_bits(shift);
245
246 let mut u_limbs = u.limbs.clone();
247 // Ensure u has m + n + 1 limbs.
248 while u_limbs.len() <= m + n {
249 u_limbs.push(0);
250 }
251
252 let mut q = vec![0u64; m + 1];
253
254 for j in (0..=m).rev() {
255 // Estimate q_hat.
256 let u_hi = ((u_limbs[j + n] as u128) << 64) | u_limbs[j + n - 1] as u128;
257 let mut q_hat = u_hi / v.limbs[n - 1] as u128;
258 let mut r_hat = u_hi % v.limbs[n - 1] as u128;
259
260 // Refine estimate.
261 loop {
262 if q_hat >= (1u128 << 64)
263 || (q_hat * v.limbs[n - 2] as u128
264 > ((r_hat << 64) | u_limbs[j + n - 2] as u128))
265 {
266 q_hat -= 1;
267 r_hat += v.limbs[n - 1] as u128;
268 if r_hat < (1u128 << 64) {
269 continue;
270 }
271 }
272 break;
273 }
274
275 // Multiply and subtract.
276 let mut borrow: i128 = 0;
277 for i in 0..n {
278 let prod = q_hat * v.limbs[i] as u128;
279 let diff = u_limbs[j + i] as i128 - borrow - (prod as u64) as i128;
280 u_limbs[j + i] = diff as u64;
281 borrow = (prod >> 64) as i128 - (diff >> 64);
282 }
283 let diff = u_limbs[j + n] as i128 - borrow;
284 u_limbs[j + n] = diff as u64;
285
286 q[j] = q_hat as u64;
287
288 // If we subtracted too much, add back.
289 if diff < 0 {
290 q[j] -= 1;
291 let mut carry = 0u64;
292 for i in 0..n {
293 let sum = u_limbs[j + i] as u128 + v.limbs[i] as u128 + carry as u128;
294 u_limbs[j + i] = sum as u64;
295 carry = (sum >> 64) as u64;
296 }
297 u_limbs[j + n] = u_limbs[j + n].wrapping_add(carry);
298 }
299 }
300
301 // Remainder: unnormalize.
302 u_limbs.truncate(n);
303 let rem = Self { limbs: u_limbs }.shr_bits(shift);
304
305 let mut quotient = Self { limbs: q };
306 quotient.normalize();
307
308 (quotient, rem)
309 }
310
311 /// Left shift by `bits` positions (bits < 64).
312 fn shl_bits(&self, bits: u32) -> Self {
313 if bits == 0 {
314 return self.clone();
315 }
316 let mut result = Vec::with_capacity(self.limbs.len() + 1);
317 let mut carry = 0u64;
318 for &limb in &self.limbs {
319 result.push((limb << bits) | carry);
320 carry = limb >> (64 - bits);
321 }
322 if carry > 0 {
323 result.push(carry);
324 }
325 let mut r = Self { limbs: result };
326 r.normalize();
327 r
328 }
329
330 /// Right shift by `bits` positions (bits < 64).
331 fn shr_bits(&self, bits: u32) -> Self {
332 if bits == 0 {
333 return self.clone();
334 }
335 let mut result = Vec::with_capacity(self.limbs.len());
336 let mut carry = 0u64;
337 for &limb in self.limbs.iter().rev() {
338 result.push((limb >> bits) | carry);
339 carry = limb << (64 - bits);
340 }
341 result.reverse();
342 let mut r = Self { limbs: result };
343 r.normalize();
344 r
345 }
346
347 /// Modular exponentiation: self^exp mod modulus.
348 /// Uses left-to-right binary method (square-and-multiply).
349 pub fn modpow(&self, exp: &Self, modulus: &Self) -> Self {
350 assert!(!modulus.is_zero(), "modulus must be non-zero");
351
352 if modulus.limbs == [1] {
353 return Self::zero();
354 }
355
356 // Montgomery multiplication requires an odd modulus.
357 // RSA moduli are always odd, but handle even case with simple modpow.
358 if modulus.limbs[0] & 1 == 0 {
359 return simple_modpow(self, exp, modulus);
360 }
361
362 montgomery_modpow(self, exp, modulus)
363 }
364
365 /// Simple modular reduction: self mod modulus.
366 pub fn modulo(&self, modulus: &Self) -> Self {
367 self.div_rem(modulus).1
368 }
369
370 /// Number of limbs.
371 pub fn num_limbs(&self) -> usize {
372 self.limbs.len()
373 }
374}
375
376impl Ord for BigUint {
377 fn cmp(&self, other: &Self) -> Ordering {
378 let a_len = self.limbs.len();
379 let b_len = other.limbs.len();
380 let max_len = a_len.max(b_len);
381 for i in (0..max_len).rev() {
382 let a = if i < a_len { self.limbs[i] } else { 0 };
383 let b = if i < b_len { other.limbs[i] } else { 0 };
384 match a.cmp(&b) {
385 Ordering::Equal => continue,
386 ord => return ord,
387 }
388 }
389 Ordering::Equal
390 }
391}
392
393impl PartialOrd for BigUint {
394 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
395 Some(self.cmp(other))
396 }
397}
398
399impl PartialEq for BigUint {
400 fn eq(&self, other: &Self) -> bool {
401 self.cmp(other) == Ordering::Equal
402 }
403}
404
405impl Eq for BigUint {}
406
407// ---------------------------------------------------------------------------
408// Montgomery multiplication for efficient modular exponentiation
409// ---------------------------------------------------------------------------
410
411/// Montgomery context for a given modulus.
412struct Montgomery {
413 /// The modulus N.
414 n: BigUint,
415 /// Number of limbs in N.
416 num_limbs: usize,
417 /// R = 2^(64*num_limbs) (not stored, implicit).
418 /// R^2 mod N — used to convert into Montgomery form.
419 r_squared: BigUint,
420 /// n0_inv = -N^(-1) mod 2^64.
421 n0_inv: u64,
422}
423
424impl Montgomery {
425 fn new(n: &BigUint) -> Self {
426 let num_limbs = n.limbs.len();
427
428 // Compute n0_inv = -N^(-1) mod 2^64 using Newton's method.
429 // Start with x = 1 (since n is odd for RSA moduli).
430 let n0 = n.limbs[0];
431 let mut inv = 1u64;
432 for _ in 0..63 {
433 inv = inv.wrapping_mul(2u64.wrapping_sub(n0.wrapping_mul(inv)));
434 }
435 let n0_inv = inv.wrapping_neg(); // -N^(-1) mod 2^64
436
437 // Compute R^2 mod N where R = 2^(64*num_limbs).
438 // Start with R mod N, then square it.
439 let r_mod_n = {
440 // R = 2^(64*num_limbs). We compute R mod N by creating R and reducing.
441 let mut r_limbs = vec![0u64; num_limbs + 1];
442 r_limbs[num_limbs] = 1;
443 let r = BigUint { limbs: r_limbs };
444 r.div_rem(n).1
445 };
446 let r_squared = r_mod_n.mul(&r_mod_n).div_rem(n).1;
447
448 Self {
449 n: n.clone(),
450 num_limbs,
451 r_squared,
452 n0_inv,
453 }
454 }
455
456 /// Convert a value into Montgomery form: aR mod N.
457 fn to_montgomery(&self, a: &BigUint) -> BigUint {
458 self.montgomery_mul(a, &self.r_squared)
459 }
460
461 /// Convert from Montgomery form back to normal: a * R^(-1) mod N.
462 fn reduce(&self, a: &BigUint) -> BigUint {
463 self.montgomery_mul(a, &BigUint::one())
464 }
465
466 /// Montgomery multiplication: computes a * b * R^(-1) mod N.
467 fn montgomery_mul(&self, a: &BigUint, b: &BigUint) -> BigUint {
468 let n = self.num_limbs;
469 let mut t = vec![0u64; 2 * n + 2];
470
471 for i in 0..n {
472 // t = t + a[i] * b
473 let ai = if i < a.limbs.len() { a.limbs[i] } else { 0 };
474 let mut carry = 0u128;
475 for j in 0..n {
476 let bj = if j < b.limbs.len() { b.limbs[j] } else { 0 };
477 let sum = t[i + j] as u128 + (ai as u128) * (bj as u128) + carry;
478 t[i + j] = sum as u64;
479 carry = sum >> 64;
480 }
481 // Propagate carry.
482 let mut k = i + n;
483 while carry > 0 {
484 let sum = t[k] as u128 + carry;
485 t[k] = sum as u64;
486 carry = sum >> 64;
487 k += 1;
488 }
489
490 // Montgomery reduction step.
491 let m = t[i].wrapping_mul(self.n0_inv);
492 carry = 0u128;
493 for j in 0..n {
494 let sum = t[i + j] as u128 + (m as u128) * (self.n.limbs[j] as u128) + carry;
495 t[i + j] = sum as u64;
496 carry = sum >> 64;
497 }
498 let mut k = i + n;
499 while carry > 0 {
500 let sum = t[k] as u128 + carry;
501 t[k] = sum as u64;
502 carry = sum >> 64;
503 k += 1;
504 }
505 }
506
507 // Result is t[n..2n].
508 let result_limbs: Vec<u64> = t[n..2 * n + 1].to_vec();
509 let mut result = BigUint {
510 limbs: result_limbs,
511 };
512 result.normalize();
513
514 // Final subtraction if result >= N.
515 if result.cmp(&self.n) != Ordering::Less {
516 result = result.sub(&self.n);
517 }
518
519 result
520 }
521}
522
523/// Simple modular exponentiation (for even moduli where Montgomery doesn't work).
524fn simple_modpow(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
525 let base_reduced = base.modulo(modulus);
526 let mut result = BigUint::one();
527 let bits = exp.bit_len();
528 for i in (0..bits).rev() {
529 result = result.mul(&result).modulo(modulus);
530 if exp.bit(i) {
531 result = result.mul(&base_reduced).modulo(modulus);
532 }
533 }
534 result
535}
536
537/// Modular exponentiation using Montgomery multiplication.
538fn montgomery_modpow(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
539 let mont = Montgomery::new(modulus);
540
541 // Convert base to Montgomery form.
542 let base_reduced = base.modulo(modulus);
543 let mut result = mont.to_montgomery(&BigUint::one()); // 1 in Montgomery form = R mod N
544 let base_mont = mont.to_montgomery(&base_reduced);
545
546 // Left-to-right binary method.
547 let bits = exp.bit_len();
548 for i in (0..bits).rev() {
549 result = mont.montgomery_mul(&result, &result); // square
550 if exp.bit(i) {
551 result = mont.montgomery_mul(&result, &base_mont); // multiply
552 }
553 }
554
555 mont.reduce(&result)
556}
557
558// ---------------------------------------------------------------------------
559// Tests
560// ---------------------------------------------------------------------------
561
562#[cfg(test)]
563mod tests {
564 use super::*;
565
566 fn hex(bytes: &[u8]) -> String {
567 bytes.iter().map(|b| format!("{b:02x}")).collect()
568 }
569
570 fn from_hex(s: &str) -> Vec<u8> {
571 let s = s.replace(' ', "");
572 (0..s.len())
573 .step_by(2)
574 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
575 .collect()
576 }
577
578 #[test]
579 fn zero_and_one() {
580 assert!(BigUint::zero().is_zero());
581 assert!(!BigUint::one().is_zero());
582 assert_eq!(BigUint::zero().bit_len(), 0);
583 assert_eq!(BigUint::one().bit_len(), 1);
584 }
585
586 #[test]
587 fn from_be_bytes_roundtrip() {
588 let bytes = from_hex("0123456789abcdef");
589 let n = BigUint::from_be_bytes(&bytes);
590 assert_eq!(hex(&n.to_be_bytes()), "0123456789abcdef");
591 }
592
593 #[test]
594 fn from_be_bytes_large() {
595 // 128-bit number.
596 let bytes = from_hex("ffffffffffffffffffffffffffffffff");
597 let n = BigUint::from_be_bytes(&bytes);
598 assert_eq!(hex(&n.to_be_bytes()), "ffffffffffffffffffffffffffffffff");
599 }
600
601 #[test]
602 fn addition() {
603 let a = BigUint::from_be_bytes(&from_hex("ffffffffffffffff"));
604 let b = BigUint::from_u64(1);
605 let c = a.add(&b);
606 assert_eq!(hex(&c.to_be_bytes()), "010000000000000000");
607 }
608
609 #[test]
610 fn subtraction() {
611 let a = BigUint::from_be_bytes(&from_hex("010000000000000000"));
612 let b = BigUint::from_u64(1);
613 let c = a.sub(&b);
614 assert_eq!(hex(&c.to_be_bytes()), "ffffffffffffffff");
615 }
616
617 #[test]
618 fn multiplication() {
619 let a = BigUint::from_u64(0xFFFFFFFF);
620 let b = BigUint::from_u64(0xFFFFFFFF);
621 let c = a.mul(&b);
622 // 0xFFFFFFFF * 0xFFFFFFFF = 0xFFFFFFFE00000001
623 assert_eq!(hex(&c.to_be_bytes()), "fffffffe00000001");
624 }
625
626 #[test]
627 fn division() {
628 let a = BigUint::from_be_bytes(&from_hex("fffffffe00000001"));
629 let b = BigUint::from_u64(0xFFFFFFFF);
630 let (q, r) = a.div_rem(&b);
631 assert_eq!(hex(&q.to_be_bytes()), "ffffffff");
632 assert!(r.is_zero());
633 }
634
635 #[test]
636 fn division_with_remainder() {
637 let a = BigUint::from_u64(17);
638 let b = BigUint::from_u64(5);
639 let (q, r) = a.div_rem(&b);
640 assert_eq!(q, BigUint::from_u64(3));
641 assert_eq!(r, BigUint::from_u64(2));
642 }
643
644 #[test]
645 fn modpow_small() {
646 // 2^10 mod 1000 = 1024 mod 1000 = 24
647 let base = BigUint::from_u64(2);
648 let exp = BigUint::from_u64(10);
649 let modulus = BigUint::from_u64(1000);
650 let result = base.modpow(&exp, &modulus);
651 assert_eq!(result, BigUint::from_u64(24));
652 }
653
654 #[test]
655 fn modpow_fermat() {
656 // Fermat's little theorem: a^(p-1) ≡ 1 (mod p) for prime p, gcd(a,p)=1.
657 // p = 65537 (prime), a = 12345.
658 let a = BigUint::from_u64(12345);
659 let p = BigUint::from_u64(65537);
660 let exp = BigUint::from_u64(65536); // p-1
661 let result = a.modpow(&exp, &p);
662 assert_eq!(result, BigUint::one());
663 }
664
665 #[test]
666 fn modpow_rsa_sized() {
667 // Test with a larger modulus to verify it works at scale.
668 // 3^17 mod 2^128+1 — verify with known computation.
669 let base = BigUint::from_u64(3);
670 let exp = BigUint::from_u64(17);
671 // 2^128 + 1
672 let mut mod_bytes = vec![0u8; 17];
673 mod_bytes[0] = 1;
674 mod_bytes[16] = 1;
675 let modulus = BigUint::from_be_bytes(&mod_bytes);
676 let result = base.modpow(&exp, &modulus);
677 // 3^17 = 129140163. This is < 2^128+1, so result = 129140163.
678 assert_eq!(result, BigUint::from_u64(129140163));
679 }
680
681 #[test]
682 fn to_be_bytes_padded() {
683 let n = BigUint::from_u64(0xFF);
684 let padded = n.to_be_bytes_padded(4);
685 assert_eq!(padded, vec![0, 0, 0, 0xFF]);
686 }
687
688 #[test]
689 fn comparison() {
690 let a = BigUint::from_u64(100);
691 let b = BigUint::from_u64(200);
692 assert_eq!(a.cmp(&b), Ordering::Less);
693 assert_eq!(b.cmp(&a), Ordering::Greater);
694 assert_eq!(a.cmp(&a), Ordering::Equal);
695 }
696
697 #[test]
698 fn multi_limb_division() {
699 // (2^128 - 1) / (2^64 - 1) = 2^64 + 1
700 let a = BigUint::from_be_bytes(&from_hex("ffffffffffffffffffffffffffffffff"));
701 let b = BigUint::from_be_bytes(&from_hex("ffffffffffffffff"));
702 let (q, r) = a.div_rem(&b);
703 assert_eq!(hex(&q.to_be_bytes()), "010000000000000001");
704 assert!(r.is_zero());
705 }
706
707 #[test]
708 fn modpow_256bit() {
709 // Verify modpow with 256-bit numbers.
710 // base = 2, exp = 255, mod = 2^256 - 189 (a prime near 2^256)
711 // 2^255 mod (2^256 - 189)
712 let base = BigUint::from_u64(2);
713 let exp = BigUint::from_u64(255);
714
715 // mod = 2^256 - 189
716 let mut mod_bytes = vec![0xFF; 32];
717 // 2^256 - 189 = 0xFFFFFF...FF43
718 mod_bytes[31] = 0x43; // 0xFF - 188 = 0x43
719
720 let modulus = BigUint::from_be_bytes(&mod_bytes);
721 let result = base.modpow(&exp, &modulus);
722
723 // Verify: result^2 * 2 should equal 2^256 mod (2^256 - 189)
724 // 2^256 mod (2^256 - 189) = 189
725 // So result * 2 mod m should equal... let's just verify result < modulus.
726 assert_eq!(result.cmp(&modulus), Ordering::Less);
727
728 // Double-check: result should be 2^255.
729 // 2^255 < 2^256 - 189, so result = 2^255 exactly.
730 let expected = {
731 let mut b = vec![0u8; 32];
732 b[0] = 0x80;
733 BigUint::from_be_bytes(&b)
734 };
735 assert_eq!(result, expected);
736 }
737}