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}