dcrypt_algorithms/ec/p224/field.rs
1//! P-224 field arithmetic implementation
2
3use crate::ec::p224::constants::P224_FIELD_ELEMENT_SIZE;
4use crate::error::{Error, Result};
5use subtle::{Choice, ConditionallySelectable};
6
7/// P-224 field element representing values in F_p
8///
9/// Internally stored as 7 little-endian 32-bit limbs for efficient arithmetic.
10/// All operations maintain the invariant that values are reduced modulo p.
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct FieldElement(pub(crate) [u32; 7]);
13
14// put this right after `#[derive(...)]` in field.rs –
15// it's only needed for ad-hoc tests, not for production code:
16impl From<[u32; 7]> for FieldElement {
17 fn from(limbs: [u32; 7]) -> Self {
18 FieldElement(limbs)
19 }
20}
21
22impl FieldElement {
23 /* -------------------------------------------------------------------- */
24 /* NIST P-224 Field Constants (stored as little-endian 32-bit limbs) */
25 /* -------------------------------------------------------------------- */
26
27 /// The NIST P-224 prime modulus: p = 2^224 - 2^96 + 1
28 /// Stored as 7 little-endian 32-bit limbs where limbs[0] is least significant
29 pub(crate) const MOD_LIMBS: [u32; 7] = [
30 0x0000_0001, // 2⁰ … 2³¹
31 0x0000_0000, // 2³² … 2⁶³
32 0x0000_0000, // 2⁶⁴ … 2⁹⁵
33 0xFFFF_FFFF, // 2⁹⁶ … 2¹²⁷
34 0xFFFF_FFFF, // 2¹²⁸ … 2¹⁵⁹
35 0xFFFF_FFFF, // 2¹⁶⁰ … 2¹⁹¹
36 0xFFFF_FFFF, // 2¹⁹² … 2²²³
37 ];
38
39 /// The curve parameter a = -3 mod p, used in the curve equation y² = x³ + ax + b
40 /// For P-224: a = p - 3
41 pub(crate) const A_M3: [u32; 7] = [
42 0xFFFF_FFFE, // (2³² - 1) - 2 = 2³² - 3 (with borrow from p)
43 0xFFFF_FFFF,
44 0xFFFF_FFFF,
45 0xFFFF_FFFE,
46 0xFFFF_FFFF,
47 0xFFFF_FFFF,
48 0xFFFF_FFFF,
49 ];
50
51 /// The additive identity element: 0
52 pub fn zero() -> Self {
53 FieldElement([0, 0, 0, 0, 0, 0, 0])
54 }
55
56 /// The multiplicative identity element: 1
57 pub fn one() -> Self {
58 FieldElement([1, 0, 0, 0, 0, 0, 0])
59 }
60
61 /// Create a field element from big-endian byte representation
62 ///
63 /// Validates that the input represents a value less than the field modulus p.
64 /// Returns an error if the value is >= p.
65 pub fn from_bytes(bytes: &[u8; P224_FIELD_ELEMENT_SIZE]) -> Result<Self> {
66 let mut limbs = [0u32; 7];
67
68 // Convert from big-endian bytes to little-endian limbs
69 // limbs[0] = least-significant 4 bytes (bytes[24..28])
70 // limbs[6] = most-significant 4 bytes (bytes[0..4])
71 for (i, limb) in limbs.iter_mut().enumerate() {
72 let offset = (6 - i) * 4; // Byte offset: 24, 20, 16, ..., 0
73 *limb = u32::from_be_bytes([
74 bytes[offset],
75 bytes[offset + 1],
76 bytes[offset + 2],
77 bytes[offset + 3],
78 ]);
79 }
80
81 // Validate that the value is in the field (< p)
82 let fe = FieldElement(limbs);
83 if !fe.is_valid() {
84 return Err(Error::param(
85 "FieldElement",
86 "Value must be less than the field modulus",
87 ));
88 }
89
90 Ok(fe)
91 }
92
93 /// Convert field element to big-endian byte representation
94 pub fn to_bytes(&self) -> [u8; P224_FIELD_ELEMENT_SIZE] {
95 let mut bytes = [0u8; P224_FIELD_ELEMENT_SIZE];
96
97 // Convert from little-endian limbs to big-endian bytes
98 for i in 0..7 {
99 let limb_bytes = self.0[i].to_be_bytes();
100 let offset = (6 - i) * 4; // Byte offset: 24, 20, 16, ..., 0
101 bytes[offset..offset + 4].copy_from_slice(&limb_bytes);
102 }
103 bytes
104 }
105
106 /// Constant-time validation that the field element is in canonical form (< p)
107 ///
108 /// Uses constant-time subtraction to check if self < p without branching.
109 /// Returns true if the element is valid (< p), false otherwise.
110 #[inline(always)]
111 pub fn is_valid(&self) -> bool {
112 // Attempt to subtract p from self
113 // If subtraction requires a borrow, then self < p (valid)
114 let (_, borrow) = Self::sbb7(self.0, Self::MOD_LIMBS);
115 borrow == 1
116 }
117
118 /// Constant-time field addition: (self + other) mod p
119 ///
120 /// Algorithm:
121 /// 1. Perform full 224-bit addition with carry detection
122 /// 2. Conditionally subtract p if result >= p
123 /// 3. Ensure result is in canonical form
124 #[inline(always)]
125 pub fn add(&self, other: &Self) -> Self {
126 // Step 1: Full 224-bit addition
127 let (sum, carry) = Self::adc7(self.0, other.0);
128
129 // Step 2: Attempt conditional reduction by subtracting p
130 let (sum_minus_p, borrow) = Self::sbb7(sum, Self::MOD_LIMBS);
131
132 // Step 3: Choose reduced value if:
133 // - Addition overflowed (carry == 1), OR
134 // - Subtraction didn't borrow (borrow == 0), meaning sum >= p
135 let need_reduce = (carry | (borrow ^ 1)) & 1;
136 let reduced = Self::conditional_select(&sum, &sum_minus_p, Choice::from(need_reduce as u8));
137
138 // Step 4: Final canonical reduction
139 reduced.conditional_sub_p()
140 }
141
142 /// Constant-time field subtraction: (self - other) mod p
143 ///
144 /// Algorithm:
145 /// 1. Perform limb-wise subtraction
146 /// 2. If subtraction borrows, add p to get the correct positive result
147 pub fn sub(&self, other: &Self) -> Self {
148 // Step 1: Raw subtraction
149 let (diff, borrow) = Self::sbb7(self.0, other.0);
150
151 // Step 2: If we borrowed, add p to get the correct positive result
152 let (candidate, _) = Self::adc7(diff, Self::MOD_LIMBS);
153
154 // Step 3: Constant-time select based on borrow flag
155 Self::conditional_select(&diff, &candidate, Choice::from(borrow as u8))
156 }
157
158 /// Field multiplication: (self * other) mod p
159 ///
160 /// Algorithm:
161 /// 1. Compute the full 448-bit product using schoolbook multiplication
162 /// 2. Perform carry propagation to get proper limb representation
163 /// 3. Apply NIST P-224 specific fast reduction
164 pub fn mul(&self, other: &Self) -> Self {
165 // Phase 1: Accumulate partial products in 128-bit temporaries
166 // This prevents overflow during the schoolbook multiplication
167 let mut t = [0u128; 14];
168 for i in 0..7 {
169 for j in 0..7 {
170 t[i + j] += (self.0[i] as u128) * (other.0[j] as u128);
171 }
172 }
173
174 // Phase 2: Carry propagation to convert to 32-bit limb representation
175 let mut prod = [0u32; 14];
176 let mut carry: u128 = 0;
177 for i in 0..14 {
178 let v = t[i] + carry;
179 prod[i] = (v & 0xffff_ffff) as u32;
180 carry = v >> 32;
181 }
182
183 // Phase 3: Apply NIST P-224 fast reduction
184 Self::reduce_wide(prod)
185 }
186
187 /// Field squaring: self² mod p
188 ///
189 /// Optimized version of multiplication for the case where both operands
190 /// are the same. Currently implemented as self.mul(self) but could be
191 /// optimized further with dedicated squaring algorithms.
192 #[inline(always)]
193 pub fn square(&self) -> Self {
194 self.mul(self)
195 }
196
197 /// Compute the modular multiplicative inverse using Fermat's Little Theorem
198 ///
199 /// For prime fields, a^(p-1) ≡ 1 (mod p), so a^(p-2) ≡ a^(-1) (mod p).
200 /// Uses binary exponentiation (square-and-multiply) for efficiency.
201 ///
202 /// Returns an error if attempting to invert zero (which has no inverse).
203 pub fn invert(&self) -> Result<Self> {
204 if self.is_zero() {
205 return Err(Error::param(
206 "FieldElement",
207 "Inversion of zero is undefined",
208 ));
209 }
210
211 // The exponent p-2 for NIST P-224 in big-endian byte format
212 const P_MINUS_2: [u8; 28] = [
213 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
214 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
215 ];
216
217 // Binary exponentiation: compute self^(p-2) mod p
218 let mut result = FieldElement::one();
219 let mut base = self.clone();
220
221 // Process each bit of the exponent from least to most significant
222 for &byte in P_MINUS_2.iter().rev() {
223 for bit in 0..8 {
224 if (byte >> bit) & 1 == 1 {
225 result = result.mul(&base);
226 }
227 base = base.square();
228 }
229 }
230
231 Ok(result)
232 }
233
234 /// Check if the field element represents zero
235 ///
236 /// Constant-time check across all limbs to determine if the
237 /// field element is the additive identity.
238 pub fn is_zero(&self) -> bool {
239 for limb in self.0.iter() {
240 if *limb != 0 {
241 return false;
242 }
243 }
244 true
245 }
246
247 /// Return `true` if the field element is odd (least-significant bit set)
248 ///
249 /// Used for point compression to determine the sign of the y-coordinate.
250 /// The parity is determined by the least significant bit of the canonical
251 /// representation.
252 pub fn is_odd(&self) -> bool {
253 (self.0[0] & 1) == 1
254 }
255
256 /// Compute the modular square root using Tonelli‑Shanks.
257 ///
258 /// Because the P‑224 prime satisfies **p ≡ 1 (mod 4)**, we cannot use the
259 /// simple `(p+1)/4` exponent trick. Instead we implement the general
260 /// Tonelli‑Shanks algorithm which works for any odd prime.
261 ///
262 /// ‑ Returns `Some(root)` with *any* square‑root of `self` when the element
263 /// is a quadratic residue.
264 /// ‑ Returns `None` when no square‑root exists.
265 pub fn sqrt(&self) -> Option<Self> {
266 // 0 and 1 are their own square roots – handle these fast.
267 if self.is_zero() {
268 return Some(Self::zero());
269 }
270 if *self == Self::one() {
271 return Some(Self::one());
272 }
273
274 /* ------------------------------------------------------------------
275 * 1. Check quadratic‑residue with Euler's criterion
276 * ------------------------------------------------------------------ */
277 // (p‑1)/2 = 2²²³ − 2⁹⁵
278 // In hex: one 0x7F, fifteen 0xFF, one 0x80, eleven 0x00
279 const LEGENDRE_EXP: [u8; 28] = [
280 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
281 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
282 ];
283
284 if self.pow(&LEGENDRE_EXP) != Self::one() {
285 return None; // not a quadratic residue
286 }
287
288 /* ------------------------------------------------------------------
289 * 2. Tonelli‑Shanks setup (p‑1 = q · 2^s with q odd)
290 * ------------------------------------------------------------------ */
291 // For P‑224 s = 96, q = 2¹²⁸ − 1.
292 const S: usize = 96;
293 // Constant q in 28‑byte BE: 12 leading zeros + 16 × 0xFF.
294 const Q: [u8; 28] = [
295 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 12 × 0
296 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
297 0xFF, 0xFF,
298 ];
299 // (q+1)/2 = 2¹²⁷ → 0x80 followed by 15 × 0, plus the 12 zero prefix.
300 const QPLUS1_OVER2: [u8; 28] = [
301 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00,
302 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
303 ];
304
305 /* r = self^{(q+1)/2}; t = self^q; c = z^q */
306 // We still need a non‑residue z. Trial small integers 2,3,4…
307 let mut z = Self::one().add(&Self::one()); // 2
308 loop {
309 // Use the same corrected LEGENDRE_EXP for consistency
310 if z.pow(&LEGENDRE_EXP) != Self::one() {
311 break;
312 }
313 z = z.add(&Self::one()); // next integer
314 }
315
316 let mut c = z.pow(&Q);
317 let mut t = self.pow(&Q);
318 let mut r = self.pow(&QPLUS1_OVER2);
319 let mut m = S;
320
321 /* ------------------------------------------------------------------
322 * 3. Main Tonelli‑Shanks loop
323 * ------------------------------------------------------------------ */
324 while t != Self::one() {
325 // Find least i (0 < i < m) s.t. t^{2^i} = 1.
326 let mut i = 1usize;
327 let mut t2i = t.square();
328 while i < m {
329 if t2i == Self::one() {
330 break;
331 }
332 t2i = t2i.square();
333 i += 1;
334 }
335
336 // If we didn't find such i, something is inconsistent.
337 if i == m {
338 return None;
339 }
340
341 // b = c^{2^{m‑i‑1}}
342 let mut b = c.clone();
343 for _ in 0..(m - i - 1) {
344 b = b.square();
345 }
346
347 // Updates
348 r = r.mul(&b);
349 let b2 = b.square();
350 t = t.mul(&b2);
351 c = b2;
352 m = i;
353 }
354
355 Some(r)
356 }
357
358 /* ----------------------------------------------------------------------
359 * Small helper: generic square‑and‑multiply (base^exp) where exp is a
360 * big‑endian byte slice.
361 * ------------------------------------------------------------------ */
362 fn pow(&self, exp_be: &[u8]) -> Self {
363 let mut result = Self::one();
364 let mut base = self.clone();
365
366 // Iterate bits from LSB → MSB (rev bytes, then 0..7)
367 for &byte in exp_be.iter().rev() {
368 let mut b = byte;
369 for _ in 0..8 {
370 if (b & 1) == 1 {
371 result = result.mul(&base);
372 }
373 base = base.square();
374 b >>= 1;
375 }
376 }
377
378 result
379 }
380
381 // Private helper methods
382
383 /// Constant-time conditional selection between two limb arrays
384 ///
385 /// Returns a if flag == 0, returns b if flag == 1
386 /// Used for branchless operations to maintain constant-time guarantees.
387 fn conditional_select(a: &[u32; 7], b: &[u32; 7], flag: Choice) -> Self {
388 let mut out = [0u32; 7];
389 for (i, out_elem) in out.iter_mut().enumerate() {
390 *out_elem = u32::conditional_select(&a[i], &b[i], flag);
391 }
392 FieldElement(out)
393 }
394
395 /// 7-limb addition with carry propagation
396 ///
397 /// Performs full-width addition across all limbs, returning both
398 /// the sum and the final carry bit for overflow detection.
399 #[inline(always)]
400 fn adc7(a: [u32; 7], b: [u32; 7]) -> ([u32; 7], u32) {
401 let mut r = [0u32; 7];
402 let mut carry = 0;
403
404 for (i, r_elem) in r.iter_mut().enumerate() {
405 // Add corresponding limbs plus carry from previous iteration
406 let (sum1, carry1) = a[i].overflowing_add(b[i]);
407 let (sum2, carry2) = sum1.overflowing_add(carry);
408
409 *r_elem = sum2;
410 carry = (carry1 as u32) | (carry2 as u32);
411 }
412
413 (r, carry)
414 }
415
416 /// 7-limb subtraction with borrow propagation
417 ///
418 /// Performs full-width subtraction across all limbs, returning both
419 /// the difference and the final borrow bit for underflow detection.
420 #[inline(always)]
421 fn sbb7(a: [u32; 7], b: [u32; 7]) -> ([u32; 7], u32) {
422 let mut r = [0u32; 7];
423 let mut borrow = 0;
424
425 for (i, r_elem) in r.iter_mut().enumerate() {
426 // Subtract corresponding limbs minus borrow from previous iteration
427 let (diff1, borrow1) = a[i].overflowing_sub(b[i]);
428 let (diff2, borrow2) = diff1.overflowing_sub(borrow);
429
430 *r_elem = diff2;
431 borrow = (borrow1 as u32) | (borrow2 as u32);
432 }
433 (r, borrow)
434 }
435
436 /// Conditionally subtract p if the current value is >= p
437 ///
438 /// Ensures the field element is in canonical reduced form.
439 /// Used as a final step in arithmetic operations.
440 fn conditional_sub_p(&self) -> Self {
441 let needs_sub = Choice::from((!self.is_valid() as u8) & 1);
442 Self::conditional_sub(self.0, needs_sub)
443 }
444
445 /// Conditionally subtract the field modulus p based on a boolean condition
446 ///
447 /// Uses constant-time selection to avoid branching while maintaining
448 /// the option to perform the subtraction.
449 fn conditional_sub(limbs: [u32; 7], condition: Choice) -> Self {
450 let mut result = [0u32; 7];
451 let (diff, _) = Self::sbb7(limbs, Self::MOD_LIMBS);
452
453 // Constant-time select between original limbs and difference
454 for (i, result_elem) in result.iter_mut().enumerate() {
455 *result_elem = u32::conditional_select(&limbs[i], &diff[i], condition);
456 }
457
458 Self(result)
459 }
460
461 /// Reduce a 448-bit value (14 little-endian `u32` limbs) modulo
462 /// p = 2²²⁴ − 2⁹⁶ + 1 (NIST P-224).
463 ///
464 /// Strategy (constant-time Solinas):
465 /// 1. Fold limbs 7‥13 back into 0‥6 with
466 /// 2²²⁴ ≡ 2⁹⁶ − 1 (mod p)
467 /// → `s[i-4] += v` and `s[i-7] -= v`.
468 /// A single top-down pass is enough because we process the new "middle"
469 /// limbs (indices 7-9) later in the same loop.
470 /// 2. Signed carry-propagate over the 7 low limbs.
471 /// 3. Whatever carry leaked beyond bit 224 is one more "2²²⁴"; fold it
472 /// again with the *same* relation (add to limb 3, subtract from limb 0).
473 /// 4. A second carry sweep + one more tiny fold guarantee canonical limbs.
474 /// 5. Final constant-time subtract of p if the value is ≥ p.
475 #[inline(always)]
476 pub(crate) fn reduce_wide(t: [u32; 14]) -> FieldElement {
477 /* ── 1. load into signed 128-bit ─────────────────────────────────── */
478 let mut s = [0i128; 14];
479 for (i, s_elem) in s.iter_mut().enumerate().take(14) {
480 *s_elem = t[i] as i128;
481 }
482
483 /* ── 2. main folding pass (7‥13 → 0‥6) ──────────────────────────── */
484 for i in (7..14).rev() {
485 let v = s[i];
486 if v == 0 {
487 continue;
488 }
489 s[i] = 0;
490 s[i - 7] = s[i - 7].wrapping_sub(v); // −v · 2^(32(i-7))
491 s[i - 4] = s[i - 4].wrapping_add(v); // +v · 2^(32(i-4))
492 }
493
494 /* ── 3. first signed carry sweep over the 7 low limbs ────────────── */
495 let mut carry: i128 = 0;
496 for elem in s.iter_mut().take(7) {
497 let tmp = *elem + carry;
498 *elem = tmp & 0xffff_ffff;
499 carry = tmp >> 32; // arithmetic shift
500 }
501
502 /* ── 4. fold that carry (k · 2²²⁴) once more: +k→limb3 −k→limb0 */
503 if carry != 0 {
504 s[3] = s[3].wrapping_add(carry);
505 s[0] = s[0].wrapping_sub(carry);
506 }
507
508 /* ── 5. second signed carry sweep ────────────────────────────────── */
509 carry = 0;
510 for elem in s.iter_mut().take(7) {
511 let tmp = *elem + carry;
512 *elem = tmp & 0xffff_ffff;
513 carry = tmp >> 32;
514 }
515
516 /* ── 6. (tiny) second carry fold, identical indices ─────────────── */
517 if carry != 0 {
518 s[3] = s[3].wrapping_add(carry);
519 s[0] = s[0].wrapping_sub(carry);
520 }
521
522 /* ── 7. final carry sweep into ordinary u32 limbs ────────────────── */
523 let mut out = [0u32; 7];
524 carry = 0;
525 for (i, out_elem) in out.iter_mut().enumerate() {
526 let tmp = s[i] + carry;
527 *out_elem = (tmp & 0xffff_ffff) as u32;
528 carry = tmp >> 32;
529 }
530 debug_assert!(carry == 0); // everything folded
531
532 /* ── 8. last conditional subtract if ≥ p ─────────────────────────── */
533 let (sub, borrow) = Self::sbb7(out, Self::MOD_LIMBS);
534 let need_sub = Choice::from((borrow ^ 1) as u8); // borrow==0 ⇒ out≥p
535 Self::conditional_select(&out, &sub, need_sub)
536 }
537
538 /// Get the field modulus p as a FieldElement
539 ///
540 /// Returns the NIST P-224 prime modulus for use in reduction operations.
541 pub(crate) fn get_modulus() -> Self {
542 FieldElement(Self::MOD_LIMBS)
543 }
544}