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