crypto_bigint/uint/ref_type/
sqrt.rs1use super::UintRef;
4use crate::{Choice, Limb, NonZero, Uint, WideWord, Word, word};
5
6impl UintRef {
7 #[inline]
8 pub(crate) const fn sqrt_assign(&mut self, buf: (&mut UintRef, &mut UintRef)) -> Choice {
9 let len = self.nlimbs();
10 assert!(buf.0.nlimbs() >= len && buf.1.nlimbs() >= len);
11
12 match len {
13 0 => Choice::TRUE,
14 1 => {
15 let rem;
16 (self.limbs[0].0, rem) = sqrt_rem_word(self.limbs[0].0);
17 Limb(rem).is_zero()
18 }
19 _ => {
20 let mut bits = self.bits();
21 let is_zero = Choice::from_u32_nz(bits).not();
22 self.limbs[0] = Limb::select(self.limbs[0], Limb::ONE, is_zero);
24 bits = is_zero.select_u32(bits, 1);
25
26 let s_shift = (self.bits_precision() - bits) >> 1;
28 self.shl_assign(s_shift * 2);
29
30 self.sqrt_rem_normalized::<false>(buf.0, buf.1);
32
33 self.copy_from(buf.0.leading(self.nlimbs()));
35 self.shr_assign(s_shift);
36
37 self.limbs[0] = Limb::select(self.limbs[0], Limb::ZERO, is_zero);
39
40 buf.1.is_zero()
42 }
43 }
44 }
45
46 #[inline]
47 pub(crate) const fn sqrt_assign_vartime(&mut self, buf: (&mut UintRef, &mut UintRef)) -> bool {
48 let bits = self.bits_vartime();
49 if bits == 0 {
50 return true;
51 }
52
53 let words = bits.div_ceil(Limb::BITS);
54 assert!(buf.0.nlimbs() >= words as usize && buf.1.nlimbs() >= words as usize);
55
56 let out = self.leading_mut(words as usize);
58
59 if words <= 2 {
60 sqrt_rem_small::<true>(out.as_limbs(), buf.0.as_mut_limbs(), buf.1.as_mut_limbs());
62 out.copy_from(buf.0.leading(out.nlimbs()));
63 } else {
64 let lz = words * Limb::BITS - bits;
66 let s_shift = lz >> 1;
67 out.shl_assign_limb_vartime(s_shift * 2);
68
69 out.sqrt_rem_normalized::<true>(buf.0, buf.1);
71
72 out.copy_from(buf.0.leading(out.nlimbs()));
74 out.shr_assign_limb_vartime(s_shift);
75 }
76
77 buf.1.is_zero_vartime()
79 }
80
81 #[allow(clippy::cast_possible_truncation)]
83 const fn sqrt_rem_normalized<const VARTIME: bool>(&self, s: &mut UintRef, r: &mut UintRef) {
84 let len = self.nlimbs();
85
86 if len < 4 {
89 sqrt_rem_small::<VARTIME>(self.as_limbs(), s.as_mut_limbs(), r.as_mut_limbs());
90 return;
91 }
92
93 let l = len >> 2;
94 let rt_len = (len - l * 2).div_ceil(2);
95 let (a_lo, a_hi) = self.split_at(l * 2);
96 let (a0, a1) = a_lo.split_at(l);
97
98 a_hi.sqrt_rem_normalized::<VARTIME>(s.trailing_mut(l), r.trailing_mut(l));
100
101 let (s, s_tail) = s.split_at_mut(l + rt_len);
103 let (s_lo, s_hi) = s.split_at_mut(l);
104 let (r, r_tail) = r.split_at_mut(l + rt_len + 1);
105
106 r.leading_mut(l).copy_from(a1);
108 let u = s_tail.leading_mut(rt_len);
110 u.copy_from(s_hi);
111
112 r.div_rem(u);
115 let r_mod2 = r.shr1_assign();
117 let u_hi = u.conditional_add_assign(s_hi, Limb::ZERO, r_mod2.is_nonzero());
118
119 let (r_lo, r_hi) = r.split_at_mut(l);
120
121 s_lo.copy_from(r_lo);
123 let q_hi = r_hi.limbs[0];
124 s_hi.add_assign_limb(q_hi);
125 debug_assert!(q_hi.0 & !1 == 0);
126
127 r_lo.copy_from(a0);
129 r_hi.leading_mut(rt_len).copy_from(u);
130 r_hi.limbs[rt_len] = u_hi;
131
132 let q2 = s_tail.leading_mut(l * 2);
134 q2.fill(Limb::ZERO);
135 s_lo.wrapping_square(q2);
136
137 let (r0, r1) = r.split_at_mut(l * 2);
139 let borrow = r0.borrowing_sub_assign(q2, Limb::ZERO);
140 let swap = r1.borrowing_sub_assign_limb(q_hi, borrow).lsb_to_choice();
141 let s_carry = Limb::select(Limb::ZERO, Limb::ONE, swap);
142
143 s.borrowing_sub_assign_limb(s_carry, Limb::ZERO);
145
146 let s_mul = s_carry.shl(1);
148 r.carrying_add_assign_mul_limb(s, s_mul, s_carry);
149
150 s_tail.fill(Limb::ZERO);
152 r_tail.fill(Limb::ZERO);
153 }
154}
155
156#[inline]
162const fn sqrt_rem_word(value: Word) -> (Word, Word) {
163 let mut m = 1 << (Word::BITS - 2);
164 let mut x = value;
165 let mut y = 0;
166 while m != 0 {
167 let b = y | m;
168 y >>= 1;
169 let mask = word::choice_to_mask(word::choice_from_le(b, x));
170 x = x.wrapping_sub(b & mask);
171 y |= m & mask;
172 m >>= 2;
173 }
174 (y, x)
175}
176
177#[allow(clippy::cast_possible_truncation)]
183#[inline]
184const fn sqrt_rem_expand_by_word(s1: Word, r1: WideWord, next: Word) -> (WideWord, WideWord) {
185 const HALF_WIDTH: u32 = Word::BITS >> 1;
186 debug_assert!((s1 >> (HALF_WIDTH - 1)) > 0);
187
188 let (a0, a1) = (
190 (next & ((1 << HALF_WIDTH) - 1)) as WideWord,
191 (next >> HALF_WIDTH) as WideWord,
192 );
193 let d = (r1 << HALF_WIDTH) | a1;
194
195 let (q, _) = Uint::<2>::from_wide_word(d).div_rem_limb(NonZero::<Limb>::new_unwrap(Limb(s1)));
197 let q = (q.limbs[0].0 >> 1) as WideWord;
199 let u = d - (q << 1) * s1 as WideWord;
201 let s = ((s1 as WideWord) << HALF_WIDTH) + q;
203
204 let r_pre = (u << HALF_WIDTH) | a0;
206 let q2 = q.pow(2);
207 let swap = word::choice_to_wide_mask(word::choice_from_wide_lt(r_pre, q2));
208
209 let s = s - (swap & 1);
211 let r = r_pre.wrapping_add(((s << 1) | 1) & swap) - q2;
213
214 (s, r)
215}
216
217#[allow(clippy::cast_possible_truncation)]
220#[inline]
221const fn sqrt_rem_small<const VARTIME: bool>(value: &[Limb], s: &mut [Limb], r: &mut [Limb]) {
222 let len = value.len();
223 assert!(len < 4, "value exceeds maximum size for sqrt_rem_small");
224
225 if len == 0 {
226 return;
227 }
228
229 if len == 1 {
230 let base = value[0].0;
231 (s[0].0, r[0].0) = if VARTIME {
232 let root = base.isqrt();
233 (root, base.wrapping_sub(root.wrapping_pow(2)))
234 } else {
235 sqrt_rem_word(base)
236 };
237 return;
238 }
239
240 let (s1, r1) = {
242 if VARTIME {
243 let base = word::join(value[len - 2].0, value[len - 1].0);
244 let root = base.isqrt();
245 (root as Word, base.wrapping_sub(root.wrapping_pow(2)))
246 } else {
247 let (s0, r0) = sqrt_rem_word(value[len - 1].0);
248 let (s1, r1) = sqrt_rem_expand_by_word(s0, r0 as WideWord, value[len - 2].0);
249 (s1 as Word, r1)
250 }
251 };
252 if len == 2 {
253 s[0].0 = s1;
254 (r[0].0, r[1].0) = word::split_wide(r1);
255 return;
256 }
257
258 let (s1, r1) = sqrt_rem_expand_by_word(s1, r1, value[len - 3].0);
260 (s[0].0, s[1].0) = word::split_wide(s1);
261 (r[0].0, r[1].0) = word::split_wide(r1);
262}
263
264#[cfg(test)]
265mod tests {
266 use super::{sqrt_rem_expand_by_word, sqrt_rem_word};
267 use crate::{WideWord, Word, word};
268
269 #[test]
270 fn sqrt_rem_word_expected() {
271 fn check(val: Word) {
272 let (root, rem) = sqrt_rem_word(val);
273 let check = root.pow(2) + rem;
274 assert_eq!(check, val, "val: {val}, root: {root}, rem: {rem}");
275 }
276
277 check(0);
278 check(1);
279 check(2);
280 check(Word::MAX);
281 }
282
283 #[test]
284 fn sqrt_rem_expand_by_word_expected() {
285 fn check(val: WideWord) {
286 let (lo, hi) = word::split_wide(val);
287 let s1 = hi.isqrt();
288 let r1 = WideWord::from(hi - s1.pow(2));
289 let (s, r) = sqrt_rem_expand_by_word(s1, r1, lo);
290 assert_eq!(s, val.isqrt());
291 assert_eq!(r, val - s.pow(2));
292 }
293
294 check(1 << (WideWord::BITS - 1));
295 check(2 << (WideWord::BITS - 2));
296 check(WideWord::MAX);
297 }
298}