Skip to content

Commit 2328b88

Browse files
authored
Implement more efficient squaring operation (#133)
Translates the implementation originally from: https://github.com/ucbrise/jedi-pairing/blob/c4bf151/include/core/bigint.hpp#L410 Permission from the original author has been given to relicense the resulting translation as Apache 2.0 + MIT
1 parent 6cb2ac9 commit 2328b88

1 file changed

Lines changed: 80 additions & 3 deletions

File tree

src/uint/mul.rs

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! [`Uint`] addition operations.
22
3-
use crate::{Checked, CheckedMul, Concat, Limb, Uint, Wrapping, Zero};
3+
use crate::{Checked, CheckedMul, Concat, Limb, Uint, WideWord, Word, Wrapping, Zero};
44
use core::ops::{Mul, MulAssign};
55
use subtle::CtOption;
66

@@ -86,7 +86,76 @@ impl<const LIMBS: usize> Uint<LIMBS> {
8686

8787
/// Square self, returning a "wide" result in two parts as (lo, hi).
8888
pub const fn square_wide(&self) -> (Self, Self) {
89-
self.mul_wide(self)
89+
// Translated from https://github.com/ucbrise/jedi-pairing/blob/c4bf151/include/core/bigint.hpp#L410
90+
//
91+
// Permission to relicense the resulting translation as Apache 2.0 + MIT was given
92+
// by the original author Sam Kumar: https://github.com/RustCrypto/crypto-bigint/pull/133#discussion_r1056870411
93+
let mut lo = Self::ZERO;
94+
let mut hi = Self::ZERO;
95+
96+
// Schoolbook multiplication, but only considering half of the multiplication grid
97+
let mut i = 1;
98+
while i < LIMBS {
99+
let mut j = 0;
100+
let mut carry = Limb::ZERO;
101+
102+
while j < i {
103+
let k = i + j;
104+
105+
if k >= LIMBS {
106+
let (n, c) = hi.limbs[k - LIMBS].mac(self.limbs[i], self.limbs[j], carry);
107+
hi.limbs[k - LIMBS] = n;
108+
carry = c;
109+
} else {
110+
let (n, c) = lo.limbs[k].mac(self.limbs[i], self.limbs[j], carry);
111+
lo.limbs[k] = n;
112+
carry = c;
113+
}
114+
115+
j += 1;
116+
}
117+
118+
if (2 * i) < LIMBS {
119+
lo.limbs[2 * i] = carry;
120+
} else {
121+
hi.limbs[2 * i - LIMBS] = carry;
122+
}
123+
124+
i += 1;
125+
}
126+
127+
// Double the current result, this accounts for the other half of the multiplication grid.
128+
// TODO: The top word is empty so we can also use a special purpose shl.
129+
(lo, hi) = Self::shl_vartime_wide((lo, hi), 1);
130+
131+
// Handle the diagonal of the multiplication grid, which finishes the multiplication grid.
132+
let mut carry = Limb::ZERO;
133+
let mut i = 0;
134+
while i < LIMBS {
135+
if (i * 2) < LIMBS {
136+
let (n, c) = lo.limbs[i * 2].mac(self.limbs[i], self.limbs[i], carry);
137+
lo.limbs[i * 2] = n;
138+
carry = c;
139+
} else {
140+
let (n, c) = hi.limbs[i * 2 - LIMBS].mac(self.limbs[i], self.limbs[i], carry);
141+
hi.limbs[i * 2 - LIMBS] = n;
142+
carry = c;
143+
}
144+
145+
if (i * 2 + 1) < LIMBS {
146+
let n = lo.limbs[i * 2 + 1].0 as WideWord + carry.0 as WideWord;
147+
lo.limbs[i * 2 + 1] = Limb(n as Word);
148+
carry = Limb((n >> Word::BITS) as Word);
149+
} else {
150+
let n = hi.limbs[i * 2 + 1 - LIMBS].0 as WideWord + carry.0 as WideWord;
151+
hi.limbs[i * 2 + 1 - LIMBS] = Limb(n as Word);
152+
carry = Limb((n >> Word::BITS) as Word);
153+
}
154+
155+
i += 1;
156+
}
157+
158+
(lo, hi)
90159
}
91160
}
92161

@@ -189,7 +258,7 @@ impl<const LIMBS: usize> MulAssign<&Checked<Uint<LIMBS>>> for Checked<Uint<LIMBS
189258

190259
#[cfg(test)]
191260
mod tests {
192-
use crate::{CheckedMul, Zero, U64};
261+
use crate::{CheckedMul, Zero, U256, U64};
193262

194263
#[test]
195264
fn mul_wide_zero_and_one() {
@@ -248,4 +317,12 @@ mod tests {
248317
assert_eq!(lo, U64::from_u64(1));
249318
assert_eq!(hi, U64::from_u64(0xffff_ffff_ffff_fffe));
250319
}
320+
321+
#[test]
322+
fn square_larger() {
323+
let n = U256::MAX;
324+
let (hi, lo) = n.square().split();
325+
assert_eq!(lo, U256::ONE);
326+
assert_eq!(hi, U256::MAX.wrapping_sub(&U256::ONE));
327+
}
251328
}

0 commit comments

Comments
 (0)