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