dcrypt_algorithms/ec/p384/field.rs
1//! P-384 field arithmetic implementation
2
3use crate::ec::p384::constants::P384_FIELD_ELEMENT_SIZE;
4use crate::error::{Error, Result};
5use dcrypt_internal::constant_time::{Choice, ConditionallySelectable};
6use dcrypt_internal::zeroing::{Zeroize, Zeroizing};
7
8/// P-384 field element representing values in F_p
9///
10/// Internally stored as 12 little-endian 32-bit limbs for efficient arithmetic.
11/// All operations maintain the invariant that values are reduced modulo p.
12// `Copy` is retained because the owned constant-time selection trait requires
13// it. Copies and single-word carry values may transiently exist in registers,
14// which safe Rust cannot guarantee are erased. Every explicit aggregate
15// byte/limb buffer and field-element temporary is owned by `Zeroizing` and
16// wiped at its lexical boundary.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18pub struct FieldElement(pub(crate) [u32; 12]);
19
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
32impl ConditionallySelectable for FieldElement {
33 #[inline(always)]
34 fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
35 Self::select_limbs(&a.0, &b.0, choice)
36 }
37}
38
39impl FieldElement {
40 /* -------------------------------------------------------------------- */
41 /* NIST P-384 Field Constants (stored as little-endian 32-bit limbs) */
42 /* -------------------------------------------------------------------- */
43
44 /// The NIST P-384 prime modulus: p = 2^384 - 2^128 - 2^96 + 2^32 - 1
45 /// Stored as 12 little-endian 32-bit limbs where limbs[0] is least significant
46 pub(crate) const MOD_LIMBS: [u32; 12] = [
47 0xFFFF_FFFF, // 2⁰ … 2³¹
48 0x0000_0000, // 2³² … 2⁶³
49 0x0000_0000, // 2⁶⁴ … 2⁹⁵
50 0xFFFF_FFFF, // 2⁹⁶ … 2¹²⁷
51 0xFFFF_FFFE, // 2¹²⁸ … 2¹⁵⁹
52 0xFFFF_FFFF, // 2¹⁶⁰ … 2¹⁹¹
53 0xFFFF_FFFF, // 2¹⁹² … 2²²³
54 0xFFFF_FFFF, // 2²²⁴ … 2²⁵⁵
55 0xFFFF_FFFF, // 2²⁵⁶ … 2²⁸⁷
56 0xFFFF_FFFF, // 2²⁸⁸ … 2³¹⁹
57 0xFFFF_FFFF, // 2³²⁰ … 2³⁵¹
58 0xFFFF_FFFF, // 2³⁵² … 2³⁸³
59 ];
60
61 /// The curve parameter a = -3 mod p, used in the curve equation y² = x³ + ax + b
62 /// For P-384: a = p - 3
63 pub(crate) const A_M3: [u32; 12] = [
64 0xFFFF_FFFC, // (2³² - 1) - 3 = 2³² - 4
65 0x0000_0000,
66 0x0000_0000,
67 0xFFFF_FFFF,
68 0xFFFF_FFFE,
69 0xFFFF_FFFF,
70 0xFFFF_FFFF,
71 0xFFFF_FFFF,
72 0xFFFF_FFFF,
73 0xFFFF_FFFF,
74 0xFFFF_FFFF,
75 0xFFFF_FFFF, // Most significant limb
76 ];
77
78 /// The additive identity element: 0
79 pub fn zero() -> Self {
80 FieldElement([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
81 }
82
83 /// The multiplicative identity element: 1
84 pub fn one() -> Self {
85 FieldElement([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
86 }
87
88 /// Create a field element from big-endian byte representation
89 ///
90 /// Validates that the input represents a value less than the field modulus p.
91 /// Returns an error if the value is >= p.
92 pub fn from_bytes(bytes: &[u8; P384_FIELD_ELEMENT_SIZE]) -> Result<Self> {
93 let mut limbs = Zeroizing::new([0u32; 12]);
94
95 // Convert from big-endian bytes to little-endian limbs
96 // limbs[0] = least-significant 4 bytes (bytes[44..48])
97 // limbs[11] = most-significant 4 bytes (bytes[0..4])
98 for (i, limb) in limbs.iter_mut().enumerate() {
99 let offset = (11 - i) * 4; // Byte offset: 44, 40, 36, ..., 0
100 *limb = ((bytes[offset] as u32) << 24)
101 | ((bytes[offset + 1] as u32) << 16)
102 | ((bytes[offset + 2] as u32) << 8)
103 | bytes[offset + 3] as u32;
104 }
105
106 // Validate that the value is in the field (< p)
107 let fe = Zeroizing::new(FieldElement(*limbs));
108 if !fe.is_valid() {
109 return Err(Error::param(
110 "FieldElement",
111 "Value must be less than the field modulus",
112 ));
113 }
114
115 Ok(fe.into_inner())
116 }
117
118 /// Convert field element to its deliberately exposed big-endian form.
119 pub fn to_bytes(&self) -> [u8; P384_FIELD_ELEMENT_SIZE] {
120 let mut bytes = [0u8; P384_FIELD_ELEMENT_SIZE];
121 self.write_bytes(&mut bytes);
122 bytes
123 }
124
125 pub(crate) fn write_bytes(&self, bytes: &mut [u8]) {
126 debug_assert_eq!(bytes.len(), P384_FIELD_ELEMENT_SIZE);
127 // Convert from little-endian limbs to big-endian bytes
128 for (i, &limb) in self.0.iter().enumerate() {
129 let offset = (11 - i) * 4; // Byte offset: 44, 40, 36, ..., 0
130 bytes[offset] = (limb >> 24) as u8;
131 bytes[offset + 1] = (limb >> 16) as u8;
132 bytes[offset + 2] = (limb >> 8) as u8;
133 bytes[offset + 3] = limb as u8;
134 }
135 }
136
137 /// Constant-time validation that the field element is in canonical form (< p)
138 ///
139 /// Uses constant-time subtraction to check if self < p without branching.
140 /// Returns true if the element is valid (< p), false otherwise.
141 #[inline(always)]
142 pub fn is_valid(&self) -> bool {
143 // Attempt to subtract p from self
144 // If subtraction requires a borrow, then self < p (valid)
145 let (_difference, borrow) = Self::sbb12(&self.0, &Self::MOD_LIMBS);
146 borrow == 1
147 }
148
149 /// Constant-time field addition: (self + other) mod p
150 ///
151 /// Algorithm:
152 /// 1. Perform full 384-bit addition with carry detection
153 /// 2. Conditionally subtract p if result >= p
154 /// 3. Ensure result is in canonical form
155 #[inline(always)]
156 pub fn add(&self, other: &Self) -> Self {
157 // Step 1: Full 384-bit addition
158 let (sum, carry) = Self::adc12(&self.0, &other.0);
159
160 // Step 2: Attempt conditional reduction by subtracting p
161 let (sum_minus_p, borrow) = Self::sbb12(&sum, &Self::MOD_LIMBS);
162
163 // Step 3: Choose reduced value if:
164 // - Addition overflowed (carry == 1), OR
165 // - Subtraction didn't borrow (borrow == 0), meaning sum >= p
166 let need_reduce = (carry | (borrow ^ 1)) & 1;
167 let reduced = Zeroizing::new(Self::select_limbs(
168 &sum,
169 &sum_minus_p,
170 Choice::from(need_reduce as u8),
171 ));
172
173 // Step 4: Final canonical reduction
174 reduced.conditional_sub_p()
175 }
176
177 /// Constant-time field subtraction: (self - other) mod p
178 ///
179 /// Algorithm:
180 /// 1. Perform limb-wise subtraction
181 /// 2. If subtraction borrows, add p to get the correct positive result
182 pub fn sub(&self, other: &Self) -> Self {
183 // Step 1: Raw subtraction
184 let (diff, borrow) = Self::sbb12(&self.0, &other.0);
185
186 // Step 2: If we borrowed, add p to get the correct positive result
187 let (candidate, _carry) = Self::adc12(&diff, &Self::MOD_LIMBS);
188
189 // Step 3: Constant-time select based on borrow flag
190 Self::select_limbs(&diff, &candidate, Choice::from(borrow as u8))
191 }
192
193 /// Field multiplication: (self * other) mod p
194 ///
195 /// Algorithm:
196 /// 1. Compute the full 768-bit product using schoolbook multiplication
197 /// 2. Perform carry propagation to get proper limb representation
198 /// 3. Apply Barrett reduction for P-384
199 pub fn mul(&self, other: &Self) -> Self {
200 // Phase 1: Accumulate partial products in 128-bit temporaries
201 // This prevents overflow during the schoolbook multiplication
202 let mut t = Zeroizing::new([0u128; 24]);
203 for i in 0..12 {
204 for j in 0..12 {
205 t[i + j] += (self.0[i] as u128) * (other.0[j] as u128);
206 }
207 }
208
209 // Phase 2: Carry propagation to convert to 32-bit limb representation
210 let mut prod = Zeroizing::new([0u32; 24]);
211 let mut carry: u128 = 0;
212 for i in 0..24 {
213 let v = t[i] + carry;
214 prod[i] = (v & 0xffff_ffff) as u32;
215 carry = v >> 32;
216 }
217
218 // Phase 3: Apply P-384 reduction
219 Self::reduce_wide(&prod)
220 }
221
222 /// Field squaring: self² mod p
223 ///
224 /// Optimized version of multiplication for the case where both operands
225 /// are the same. Currently implemented as self.mul(self) but could be
226 /// optimized further with dedicated squaring algorithms.
227 #[inline(always)]
228 pub fn square(&self) -> Self {
229 self.mul(self)
230 }
231
232 /// Compute the modular multiplicative inverse using Fermat's Little Theorem
233 ///
234 /// For prime fields, a^(p-1) ≡ 1 (mod p), so a^(p-2) ≡ a^(-1) (mod p).
235 /// Uses binary exponentiation (square-and-multiply) for efficiency.
236 ///
237 /// Returns an error if attempting to invert zero (which has no inverse).
238 pub fn invert(&self) -> Result<Self> {
239 if self.is_zero() {
240 return Err(Error::param(
241 "FieldElement",
242 "Inversion of zero is undefined",
243 ));
244 }
245
246 // The exponent p-2 for NIST P-384 in big-endian byte format
247 const P_MINUS_2: [u8; 48] = [
248 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
249 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
250 0xFF, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
251 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFD,
252 ];
253
254 // Binary exponentiation: compute self^(p-2) mod p
255 let mut result = Zeroizing::new(FieldElement::one());
256 let mut base = Zeroizing::new(*self);
257
258 // Process each bit of the exponent from least to most significant
259 for &byte in P_MINUS_2.iter().rev() {
260 for bit in 0..8 {
261 if (byte >> bit) & 1 == 1 {
262 let next = Zeroizing::new(result.mul(&base));
263 result.zeroize();
264 *result = next.into_inner();
265 }
266 let squared = Zeroizing::new(base.square());
267 base.zeroize();
268 *base = squared.into_inner();
269 }
270 }
271
272 Ok(result.into_inner())
273 }
274
275 /// Check if the field element represents zero
276 ///
277 /// Constant-time check across all limbs to determine if the
278 /// field element is the additive identity.
279 pub fn is_zero(&self) -> bool {
280 let mut any = 0u32;
281 for &limb in &self.0 {
282 any |= limb;
283 }
284 any == 0
285 }
286
287 /// Return `true` when the element is odd (LSB set)
288 ///
289 /// Used for point compression to determine the sign of the y-coordinate.
290 /// The parity is determined by the least significant bit of the canonical
291 /// representation.
292 pub fn is_odd(&self) -> bool {
293 (self.0[0] & 1) == 1
294 }
295
296 /// Modular square root using the (p+1)/4 shortcut (p ≡ 3 mod 4).
297 ///
298 /// Because the P-384 prime satisfies p ≡ 3 (mod 4), we can compute
299 /// sqrt(a) = a^((p+1)/4) mod p. This is more efficient than the
300 /// general Tonelli-Shanks algorithm.
301 ///
302 /// Returns `None` when the input is a quadratic non-residue (i.e.,
303 /// when no square root exists in the field).
304 ///
305 /// # Algorithm
306 /// For p ≡ 3 (mod 4), if a has a square root, then:
307 /// - sqrt(a) = ±a^((p+1)/4) mod p
308 /// - We return the principal square root (the smaller of the two)
309 pub fn sqrt(&self) -> Option<Self> {
310 if self.is_zero() {
311 return Some(Self::zero());
312 }
313
314 // (p + 1) / 4 for P-384 as big-endian bytes
315 // p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff
316 // (p + 1) / 4 = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbffffffffc000000000000000400000000
317 const EXP: [u8; 48] = [
318 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
319 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
320 0xFF, 0xFF, 0xFF, 0xFF, 0xBF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00,
321 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
322 ];
323
324 let mut result = Zeroizing::new(FieldElement::one());
325 let mut base = Zeroizing::new(*self);
326
327 // Binary exponentiation from LSB to MSB
328 for &byte in EXP.iter().rev() {
329 for bit in 0..8 {
330 if ((byte >> bit) & 1) == 1 {
331 let next = Zeroizing::new(result.mul(&base));
332 result.zeroize();
333 *result = next.into_inner();
334 }
335 let squared = Zeroizing::new(base.square());
336 base.zeroize();
337 *base = squared.into_inner();
338 }
339 }
340
341 // Verify that result^2 = self (constant-time check)
342 let verification = Zeroizing::new(result.square());
343 if *verification == *self {
344 Some(result.into_inner())
345 } else {
346 None
347 }
348 }
349
350 // Private helper methods
351
352 /// Constant-time conditional selection between two limb arrays
353 ///
354 /// Returns a if flag == 0, returns b if flag == 1
355 /// Used to avoid source-level branches in reviewed conditional selections.
356 #[inline(never)]
357 fn select_limbs(a: &[u32; 12], b: &[u32; 12], flag: Choice) -> Self {
358 let mut out = Zeroizing::new([0u32; 12]);
359 for i in 0..12 {
360 out[i] = u32::conditional_select(&a[i], &b[i], flag);
361 }
362 FieldElement(*out)
363 }
364
365 /// 12-limb addition with carry propagation
366 ///
367 /// Performs full-width addition across all limbs, returning both
368 /// the sum and the final carry bit for overflow detection.
369 #[inline(always)]
370 fn adc12(a: &[u32; 12], b: &[u32; 12]) -> (Zeroizing<[u32; 12]>, u32) {
371 let mut r = Zeroizing::new([0u32; 12]);
372 let mut carry = 0;
373
374 for i in 0..12 {
375 // Add corresponding limbs plus carry from previous iteration
376 let (sum1, carry1) = a[i].overflowing_add(b[i]);
377 let (sum2, carry2) = sum1.overflowing_add(carry);
378
379 r[i] = sum2;
380 carry = (carry1 as u32) | (carry2 as u32);
381 }
382
383 (r, carry)
384 }
385
386 /// 12-limb subtraction with borrow propagation
387 ///
388 /// Performs full-width subtraction across all limbs, returning both
389 /// the difference and the final borrow bit for underflow detection.
390 #[inline(always)]
391 fn sbb12(a: &[u32; 12], b: &[u32; 12]) -> (Zeroizing<[u32; 12]>, u32) {
392 let mut r = Zeroizing::new([0u32; 12]);
393 let mut borrow = 0;
394
395 for i in 0..12 {
396 // Subtract corresponding limbs minus borrow from previous iteration
397 let (diff1, borrow1) = a[i].overflowing_sub(b[i]);
398 let (diff2, borrow2) = diff1.overflowing_sub(borrow);
399
400 r[i] = diff2;
401 borrow = (borrow1 as u32) | (borrow2 as u32);
402 }
403 (r, borrow)
404 }
405
406 /// Conditionally subtract p if the current value is >= p
407 ///
408 /// Ensures the field element is in canonical reduced form.
409 /// Used as a final step in arithmetic operations.
410 fn conditional_sub_p(&self) -> Self {
411 let needs_sub = Choice::from((!self.is_valid() as u8) & 1);
412 Self::conditional_sub(&self.0, needs_sub)
413 }
414
415 /// Conditionally subtract the field modulus p based on a boolean condition
416 ///
417 /// Uses constant-time selection to avoid branching while maintaining
418 /// the option to perform the subtraction.
419 fn conditional_sub(limbs: &[u32; 12], condition: Choice) -> Self {
420 let (diff, _borrow) = Self::sbb12(limbs, &Self::MOD_LIMBS);
421 Self::select_limbs(limbs, &diff, condition)
422 }
423
424 /// NIST P-384 specific reduction for 768-bit values using Solinas method
425 /// Fully constant-time Solinas reduction with two carry-folds.
426 /// For P-384: 2^384 ≡ 2^128 + 2^96 - 2^32 + 1 (mod p)
427 pub(crate) fn reduce_wide(t: &[u32; 24]) -> FieldElement {
428 // 1) load into signed 128-bit
429 let mut s = Zeroizing::new([0i128; 24]);
430 for i in 0..24 {
431 s[i] = t[i] as i128;
432 }
433
434 // 2) fold high limbs 12..23 into 0..11 via
435 // 2^384 ≡ 2^128 + 2^96 - 2^32 + 1 (mod p)
436 for i in (12..24).rev() {
437 let v = s[i];
438 s[i] = 0;
439 s[i - 12] = s[i - 12].wrapping_add(v); // +1 (2^0 term)
440 s[i - 11] = s[i - 11].wrapping_sub(v); // -2^32 term
441 s[i - 9] = s[i - 9].wrapping_add(v); // +2^96 term
442 s[i - 8] = s[i - 8].wrapping_add(v); // +2^128 term
443 }
444
445 // 2b) the previous step can leave non-zero words in slots 12..15
446 // (it happens when i = 20..23). Fold them once more so that
447 // all non-zero limbs are now in 0..11.
448 for i in (12..16).rev() {
449 let v = s[i];
450 s[i] = 0;
451 s[i - 12] = s[i - 12].wrapping_add(v); // +1
452 s[i - 11] = s[i - 11].wrapping_sub(v); // -2^32
453 s[i - 9] = s[i - 9].wrapping_add(v); // +2^96
454 s[i - 8] = s[i - 8].wrapping_add(v); // +2^128
455 }
456
457 // 3) first signed carry-propagate
458 let mut carry1: i128 = 0;
459 for limb in s.iter_mut().take(12) {
460 let tmp = *limb + carry1;
461 *limb = tmp & 0xffff_ffff;
462 carry1 = tmp >> 32; // arithmetic shift
463 }
464
465 // 4) fold carry1 back down using same relation
466 let c1 = carry1;
467 s[0] = s[0].wrapping_add(c1); // +1
468 s[1] = s[1].wrapping_sub(c1); // -2^32
469 s[3] = s[3].wrapping_add(c1); // +2^96
470 s[4] = s[4].wrapping_add(c1); // +2^128
471
472 // 5) second signed carry-propagate
473 let mut carry2: i128 = 0;
474 for limb in s.iter_mut().take(12) {
475 let tmp = *limb + carry2;
476 *limb = tmp & 0xffff_ffff;
477 carry2 = tmp >> 32;
478 }
479
480 // 6) fold carry2 back down
481 let c2 = carry2;
482 s[0] = s[0].wrapping_add(c2);
483 s[1] = s[1].wrapping_sub(c2);
484 s[3] = s[3].wrapping_add(c2);
485 s[4] = s[4].wrapping_add(c2);
486
487 // 7) final signed carry-propagate into 32-bit limbs
488 let mut out = Zeroizing::new([0u32; 12]);
489 let mut carry3: i128 = 0;
490 for i in 0..12 {
491 let tmp = s[i] + carry3;
492 out[i] = (tmp & 0xffff_ffff) as u32;
493 carry3 = tmp >> 32;
494 }
495
496 // 8) one last constant-time subtract if ≥ p
497 let (subbed, borrow) = Self::sbb12(&out, &Self::MOD_LIMBS);
498 let need_sub = Choice::from((borrow ^ 1) as u8); // borrow==0 ⇒ out>=p
499 Self::select_limbs(&out, &subbed, need_sub)
500 }
501
502 /// Get the field modulus p as a FieldElement
503 ///
504 /// Returns the NIST P-384 prime modulus for use in reduction operations.
505 pub(crate) fn get_modulus() -> Self {
506 FieldElement(Self::MOD_LIMBS)
507 }
508}