Skip to main content

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