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}