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}