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}