Skip to main content

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}