dcrypt_algorithms/ec/p521/field.rs
1//! P-521 field arithmetic implementation (Fₚ for p = 2^521 − 1)
2//!
3//! This file implements the heavy-weight primitives for P-521 field arithmetic:
4//! full-width multiplication, squaring, modular inversion and modular square-root.
5//! The design philosophy matches our existing P-256 / P-384 field modules:
6//! * pure Rust, constant-time where it matters.
7//! * 32-bit little-endian limbs stored in `[u32; 17]` (544 bits, only the
8//! lower 521 are used).
9//! * reduction uses the Mersenne trick for p = 2^521 − 1:
10//! (H · 2^521 + L) ≡ H + L (mod p)
11
12use crate::ec::p521::constants::{
13 p521_bytes_to_limbs, p521_limbs_to_bytes, P521_FIELD_ELEMENT_SIZE, P521_LIMBS,
14};
15use crate::error::{Error, Result};
16use subtle::{Choice, ConditionallySelectable};
17use zeroize::Zeroize;
18
19/// P-521 field element representing values in Fₚ (p = 2^521 − 1).
20/// Internally stored as 17 little-endian 32-bit limbs; only the low 9 bits
21/// of limb 16 are significant.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct FieldElement(pub(crate) [u32; P521_LIMBS]);
24
25impl Zeroize for FieldElement {
26 fn zeroize(&mut self) {
27 self.0.zeroize();
28 }
29}
30
31/* ========================================================================== */
32/* Constants */
33/* ========================================================================== */
34
35impl FieldElement {
36 /// p = 2^521 − 1 (little-endian limbs).
37 pub(crate) const MOD_LIMBS: [u32; P521_LIMBS] = [
38 0xFFFF_FFFF,
39 0xFFFF_FFFF,
40 0xFFFF_FFFF,
41 0xFFFF_FFFF,
42 0xFFFF_FFFF,
43 0xFFFF_FFFF,
44 0xFFFF_FFFF,
45 0xFFFF_FFFF,
46 0xFFFF_FFFF,
47 0xFFFF_FFFF,
48 0xFFFF_FFFF,
49 0xFFFF_FFFF,
50 0xFFFF_FFFF,
51 0xFFFF_FFFF,
52 0xFFFF_FFFF,
53 0xFFFF_FFFF,
54 0x0000_01FF, // limb 16 (only 9 bits used)
55 ];
56
57 /// a = −3 mod p = 2^521 − 4 (little-endian limbs)
58 pub(crate) const A_M3: [u32; P521_LIMBS] = [
59 0xFFFF_FFFC,
60 0xFFFF_FFFF,
61 0xFFFF_FFFF,
62 0xFFFF_FFFF,
63 0xFFFF_FFFF,
64 0xFFFF_FFFF,
65 0xFFFF_FFFF,
66 0xFFFF_FFFF,
67 0xFFFF_FFFF,
68 0xFFFF_FFFF,
69 0xFFFF_FFFF,
70 0xFFFF_FFFF,
71 0xFFFF_FFFF,
72 0xFFFF_FFFF,
73 0xFFFF_FFFF,
74 0xFFFF_FFFF,
75 0x0000_01FF,
76 ];
77
78 /// The additive identity element: 0
79 #[inline]
80 pub fn zero() -> Self {
81 FieldElement([0u32; P521_LIMBS])
82 }
83
84 /// The multiplicative identity element: 1
85 #[inline]
86 pub fn one() -> Self {
87 let mut limbs = [0u32; P521_LIMBS];
88 limbs[0] = 1;
89 Self(limbs)
90 }
91}
92
93/* ========================================================================== */
94/* (De)Serialisation */
95/* ========================================================================== */
96
97impl FieldElement {
98 /// Create a field element from big-endian byte representation.
99 ///
100 /// Validates that the input represents a value less than the field modulus p.
101 /// Returns an error if the value is >= p.
102 pub fn from_bytes(bytes: &[u8; P521_FIELD_ELEMENT_SIZE]) -> Result<Self> {
103 let limbs = p521_bytes_to_limbs(bytes);
104 let fe = FieldElement(limbs);
105 if !fe.is_valid() {
106 return Err(Error::param("FieldElement P-521", "Value >= modulus"));
107 }
108 Ok(fe)
109 }
110
111 /// Convert field element to big-endian byte representation
112 pub fn to_bytes(&self) -> [u8; P521_FIELD_ELEMENT_SIZE] {
113 p521_limbs_to_bytes(&self.0)
114 }
115
116 /// Check if the field element represents zero
117 #[inline(always)]
118 pub fn is_zero(&self) -> bool {
119 self.0.iter().all(|&w| w == 0)
120 }
121
122 /// Return `true` if the field element is odd (least-significant bit set)
123 #[inline(always)]
124 pub fn is_odd(&self) -> bool {
125 (self.0[0] & 1) == 1
126 }
127
128 /// self < p ? (constant-time)
129 #[inline(always)]
130 pub fn is_valid(&self) -> bool {
131 let (_, borrow) = Self::sbb_n(self.0, Self::MOD_LIMBS);
132 borrow == 1 // borrow = 1 ⇒ self < p
133 }
134}
135
136/* ========================================================================== */
137/* Core helpers: limb add / sub */
138/* ========================================================================== */
139
140impl FieldElement {
141 /// N-limb addition with carry.
142 #[inline(always)]
143 pub(crate) fn adc_n<const N: usize>(a: [u32; N], b: [u32; N]) -> ([u32; N], u32) {
144 let mut out = [0u32; N];
145 let mut carry = 0u64;
146 for i in 0..N {
147 let t = a[i] as u64 + b[i] as u64 + carry;
148 out[i] = t as u32;
149 carry = t >> 32;
150 }
151 (out, carry as u32)
152 }
153
154 /// N-limb subtraction with borrow.
155 #[inline(always)]
156 pub(crate) fn sbb_n<const N: usize>(a: [u32; N], b: [u32; N]) -> ([u32; N], u32) {
157 let mut out = [0u32; N];
158 let mut borrow = 0i64;
159 for i in 0..N {
160 let t = a[i] as i64 - b[i] as i64 - borrow;
161 out[i] = t as u32;
162 borrow = (t >> 63) & 1; // 1 if negative
163 }
164 (out, borrow as u32)
165 }
166
167 /// Conditionally select (`flag` = 0 ⇒ *a*, `flag` = 1 ⇒ *b*).
168 #[inline(always)]
169 fn conditional_select(a: &[u32; P521_LIMBS], b: &[u32; P521_LIMBS], flag: Choice) -> Self {
170 let mut out = [0u32; P521_LIMBS];
171 for i in 0..P521_LIMBS {
172 out[i] = u32::conditional_select(&a[i], &b[i], flag);
173 }
174 FieldElement(out)
175 }
176
177 /// Constant-time conditional swap
178 ///
179 /// Swaps the two field elements if choice is 1, leaves them unchanged if choice is 0.
180 /// This operation is performed in constant time to prevent timing attacks.
181 #[inline(always)]
182 pub fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
183 for i in 0..P521_LIMBS {
184 let tmp = u32::conditional_select(&a.0[i], &b.0[i], choice);
185 b.0[i] = u32::conditional_select(&b.0[i], &a.0[i], choice);
186 a.0[i] = tmp;
187 }
188 }
189}
190
191/* ========================================================================== */
192/* P-521 reduction helper */
193/* ========================================================================== */
194
195impl FieldElement {
196 /// Reduce a 34-limb value (little-endian u32) modulo
197 /// p = 2²⁵²¹ − 1. Runs in constant time.
198 fn reduce_wide(t: [u32; 34]) -> Self {
199 // --------------------------------------------------------------------- //
200 // 1. Split the input in two halves: L = t[ 0..17], H = t[17..34] //
201 // and add L + (H << 23). //
202 // While doing so we fold the overflow of each limb straight away, //
203 // so we never need more than an 18-limb scratch buffer. //
204 // --------------------------------------------------------------------- //
205 let mut acc = [0u64; 18]; // 17 limbs + a possible final carry
206 let mut c: u64 = 0;
207
208 for i in 0..17 {
209 let l = t[i] as u64;
210 let h = t[17 + i] as u64;
211
212 // low 32 bits of (h << 23)
213 let lo = (h << 23) & 0xFFFF_FFFF;
214 // overflow (high bits of the same shift)
215 let hi = h >> 9; // (32 − 23) = 9
216
217 let tmp = l + lo + c;
218 acc[i] = tmp & 0xFFFF_FFFF;
219 c = (tmp >> 32) + hi; // propagate both the normal carry
220 }
221 acc[17] = c; // (can be up to 2³³ − 2)
222
223 // --------------------------------------------------------------------- //
224 // 2. Fold the 18-th limb (bit-position 544) once more: //
225 // 2²⁵⁴⁴ ≡ 2²³ (mod p) //
226 // --------------------------------------------------------------------- //
227 let top = acc[17];
228 if top != 0 {
229 // add (top << 23) to limb-0 … overflow will bubble to the right
230 let tmp = acc[0] + ((top << 23) & 0xFFFF_FFFF);
231 acc[0] = tmp & 0xFFFF_FFFF;
232 let mut c = (tmp >> 32) + (top >> 9); // carry to propagate
233
234 // propagate through all 17 limbs, *wrapping* when we fall off the end
235 for i in 1..=17 {
236 let idx = i % 17;
237 let tmp = acc[idx] + c;
238 acc[idx] = tmp & 0xFFFF_FFFF;
239 c = tmp >> 32;
240 if c == 0 {
241 break;
242 }
243 }
244 acc[17] = 0; // completely folded away
245 }
246
247 // --------------------------------------------------------------------- //
248 // 3. Limb-16 must only keep its lowest 9 bits. Everything above that //
249 // again represents k · 2²⁵²¹ and is folded back into limb-0. //
250 // --------------------------------------------------------------------- //
251 let extra = acc[16] >> 9; // at most 23 bits
252 acc[16] &= 0x1FF;
253 if extra != 0 {
254 let mut i = 0;
255 let mut c = extra;
256 loop {
257 let tmp = acc[i] + c;
258 acc[i] = tmp & 0xFFFF_FFFF;
259 c = tmp >> 32;
260 if c == 0 {
261 break;
262 }
263 i = (i + 1) % 17;
264 }
265 }
266
267 // --------------------------------------------------------------------- //
268 // 4. Conditional subtraction of the modulus. //
269 // --------------------------------------------------------------------- //
270 let mut limbs = [0u32; 17];
271 for i in 0..17 {
272 limbs[i] = acc[i] as u32;
273 }
274
275 let (sub, borrow) = Self::sbb_n(limbs, Self::MOD_LIMBS);
276 Self::conditional_select(&limbs, &sub, Choice::from((borrow ^ 1) as u8))
277 }
278}
279
280/* ========================================================================== */
281/* Public API: add / sub / mul / square / invert / sqrt */
282/* ========================================================================== */
283
284impl FieldElement {
285 /// Constant-time addition modulo p
286 pub fn add(&self, other: &Self) -> Self {
287 let (sum, carry) = Self::adc_n(self.0, other.0);
288 // If there was a carry OR the sum ≥ p ⇒ subtract once.
289 let (sub, borrow) = Self::sbb_n(sum, Self::MOD_LIMBS);
290 let need_sub = Choice::from(((carry | (borrow ^ 1)) & 1) as u8);
291 Self::conditional_select(&sum, &sub, need_sub)
292 }
293
294 /// Constant-time subtraction modulo p
295 pub fn sub(&self, other: &Self) -> Self {
296 let (diff, borrow) = Self::sbb_n(self.0, other.0);
297 // If we borrowed ⇒ add p back.
298 let (sum, _) = Self::adc_n(diff, Self::MOD_LIMBS);
299 Self::conditional_select(&diff, &sum, Choice::from(borrow as u8))
300 }
301
302 /// Field multiplication using school-book multiply + Mersenne reduction.
303 pub fn mul(&self, other: &Self) -> Self {
304 // ── 1. 17×17 → 34 partial products (128-bit accumulator) ----------
305 let mut wide = [0u128; 34];
306 for i in 0..17 {
307 for j in 0..17 {
308 wide[i + j] += (self.0[i] as u128) * (other.0[j] as u128);
309 }
310 }
311
312 // ── 2. Carry-propagate 128-bit → 34 × 32-bit limbs -----------------
313 let mut limbs = [0u32; 34];
314 let mut carry: u128 = 0;
315 for i in 0..34 {
316 let v = wide[i] + carry;
317 limbs[i] = (v & 0xFFFF_FFFF) as u32;
318 carry = v >> 32;
319 }
320 // `carry` may be non-zero (at most 2^32−1) – push it as limb 34 if so
321 if carry != 0 {
322 // This is equivalent to a limb 34 that will be folded anyway.
323 let extra = carry as u32;
324 let (new, of) = limbs[0].overflowing_add(extra);
325 limbs[0] = new;
326 if of {
327 // propagate one more carry (rare)
328 let mut k = 1;
329 while k < 17 {
330 let (n, o) = limbs[k].overflowing_add(1);
331 limbs[k] = n;
332 if !o {
333 break;
334 }
335 k += 1;
336 }
337 }
338 }
339
340 // ── 3. Reduce back to 17 limbs -------------------------------------
341 Self::reduce_wide(limbs)
342 }
343
344 /// Field squaring – just a specialised multiplication.
345 #[inline(always)]
346 pub fn square(&self) -> Self {
347 self.mul(self)
348 }
349
350 /// Fermat-inversion a^(p−2) via left-to-right square-and-multiply.
351 pub fn invert(&self) -> Result<Self> {
352 if self.is_zero() {
353 return Err(Error::param("FieldElement P-521", "Inverse of zero"));
354 }
355
356 // Prepare exponent p−2 = (2^521 − 1) − 2 = 2^521 − 3
357 // p in bytes is 0x01 | 0xFF * 65
358 let mut exp = [0u8; P521_FIELD_ELEMENT_SIZE];
359 exp[0] = 0x01;
360 for byte in exp.iter_mut().skip(1) {
361 *byte = 0xFF;
362 }
363 // subtract 2 (big-endian)
364 let mut borrow = 2u16;
365 for i in (0..66).rev() {
366 let v = exp[i] as i16 - borrow as i16;
367 exp[i] = if v < 0 { (v + 256) as u8 } else { v as u8 };
368 borrow = if v < 0 { 1 } else { 0 };
369 }
370
371 // Left-to-right binary exponentiation
372 let mut result = FieldElement::one();
373 let base = self.clone();
374 for byte in exp.iter() {
375 for bit in (0..8).rev() {
376 result = result.square();
377 if (byte >> bit) & 1 == 1 {
378 result = result.mul(&base);
379 }
380 }
381 }
382 Ok(result)
383 }
384
385 /// Square-root via a^{(p+1)/4} (because p ≡ 3 mod 4).
386 /// (p+1)/4 = 2^519.
387 pub fn sqrt(&self) -> Option<Self> {
388 if self.is_zero() {
389 return Some(Self::zero());
390 }
391 // a^{2^519}
392 let mut res = self.clone();
393 for _ in 0..519 {
394 res = res.square();
395 }
396 // verify
397 if res.square() == *self {
398 Some(res)
399 } else {
400 None
401 }
402 }
403
404 /// Get the field modulus p as a FieldElement
405 pub(crate) fn get_modulus() -> Self {
406 FieldElement(Self::MOD_LIMBS)
407 }
408}