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