Skip to main content

dcrypt_algorithms/ec/p521/
field.rs

1//! P-521 field arithmetic implementation (Fₚ for p = 2^521 − 1)
2//!
3//! This file implements the heavy-weight primitives for P-521 field arithmetic:
4//! full-width multiplication, squaring, modular inversion and modular square-root.
5//! The design philosophy matches our existing P-256 / P-384 field modules:
6//!   * pure Rust, constant-time where it matters.
7//!   * 32-bit little-endian limbs stored in `[u32; 17]` (544 bits, only the
8//!     lower 521 are used).
9//!   * reduction uses the Mersenne trick for p = 2^521 − 1:
10//!     (H · 2^521 + L)  ≡  H + L   (mod p)
11
12use crate::ec::p521::constants::{P521_FIELD_ELEMENT_SIZE, P521_LIMBS};
13use crate::error::{Error, Result};
14use dcrypt_internal::constant_time::{Choice, ConditionallySelectable};
15use dcrypt_internal::zeroing::{Zeroize, Zeroizing};
16
17/// P-521 field element representing values in Fₚ (p = 2^521 − 1).
18/// Internally stored as 17 little-endian 32-bit limbs; only the low 9 bits
19/// of limb 16 are significant.
20// The owned type is deliberately non-`Copy`. Single-word carry values can
21// still transiently reside in registers, which safe Rust cannot guarantee are
22// erased; all explicit aggregate byte/limb buffers and field-element
23// temporaries are therefore owned by `Zeroizing`.
24#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct FieldElement(pub(crate) [u32; P521_LIMBS]);
26
27impl Default for FieldElement {
28    fn default() -> Self {
29        Self::zero()
30    }
31}
32
33impl Zeroize for FieldElement {
34    fn zeroize(&mut self) {
35        self.0.zeroize();
36    }
37}
38
39/* ========================================================================== */
40/*  Constants                                                                 */
41/* ========================================================================== */
42
43impl FieldElement {
44    /// p = 2^521 − 1  (little-endian limbs).
45    pub(crate) const MOD_LIMBS: [u32; P521_LIMBS] = [
46        0xFFFF_FFFF,
47        0xFFFF_FFFF,
48        0xFFFF_FFFF,
49        0xFFFF_FFFF,
50        0xFFFF_FFFF,
51        0xFFFF_FFFF,
52        0xFFFF_FFFF,
53        0xFFFF_FFFF,
54        0xFFFF_FFFF,
55        0xFFFF_FFFF,
56        0xFFFF_FFFF,
57        0xFFFF_FFFF,
58        0xFFFF_FFFF,
59        0xFFFF_FFFF,
60        0xFFFF_FFFF,
61        0xFFFF_FFFF,
62        0x0000_01FF, // limb 16 (only 9 bits used)
63    ];
64
65    /// a = −3 mod p  = 2^521 − 4  (little-endian limbs)
66    pub(crate) const A_M3: [u32; P521_LIMBS] = [
67        0xFFFF_FFFC,
68        0xFFFF_FFFF,
69        0xFFFF_FFFF,
70        0xFFFF_FFFF,
71        0xFFFF_FFFF,
72        0xFFFF_FFFF,
73        0xFFFF_FFFF,
74        0xFFFF_FFFF,
75        0xFFFF_FFFF,
76        0xFFFF_FFFF,
77        0xFFFF_FFFF,
78        0xFFFF_FFFF,
79        0xFFFF_FFFF,
80        0xFFFF_FFFF,
81        0xFFFF_FFFF,
82        0xFFFF_FFFF,
83        0x0000_01FF,
84    ];
85
86    /// The additive identity element: 0
87    #[inline]
88    pub fn zero() -> Self {
89        FieldElement([0u32; P521_LIMBS])
90    }
91
92    /// The multiplicative identity element: 1
93    #[inline]
94    pub fn one() -> Self {
95        let mut limbs = Zeroizing::new([0u32; P521_LIMBS]);
96        limbs[0] = 1;
97        Self(*limbs)
98    }
99}
100
101/* ========================================================================== */
102/*  (De)Serialisation                                                         */
103/* ========================================================================== */
104
105impl FieldElement {
106    /// Create a field element from big-endian byte representation.
107    ///
108    /// Validates that the input represents a value less than the field modulus p.
109    /// Returns an error if the value is >= p.
110    pub fn from_bytes(bytes: &[u8; P521_FIELD_ELEMENT_SIZE]) -> Result<Self> {
111        let mut limbs = Zeroizing::new([0u32; P521_LIMBS]);
112        for i in 0..16 {
113            let offset = P521_FIELD_ELEMENT_SIZE - 4 - i * 4;
114            limbs[i] = ((bytes[offset] as u32) << 24)
115                | ((bytes[offset + 1] as u32) << 16)
116                | ((bytes[offset + 2] as u32) << 8)
117                | bytes[offset + 3] as u32;
118        }
119        limbs[16] = ((bytes[0] as u32) << 8) | bytes[1] as u32;
120
121        let fe = Zeroizing::new(FieldElement(*limbs));
122        if !fe.is_valid() {
123            return Err(Error::param("FieldElement P-521", "Value >= modulus"));
124        }
125        Ok(fe.into_inner())
126    }
127
128    /// Convert field element to its deliberately exposed big-endian form.
129    pub fn to_bytes(&self) -> [u8; P521_FIELD_ELEMENT_SIZE] {
130        let mut bytes = [0u8; P521_FIELD_ELEMENT_SIZE];
131        self.write_bytes(&mut bytes);
132        bytes
133    }
134
135    pub(crate) fn write_bytes(&self, bytes: &mut [u8]) {
136        debug_assert_eq!(bytes.len(), P521_FIELD_ELEMENT_SIZE);
137        for (i, &limb) in self.0.iter().take(16).enumerate() {
138            let offset = P521_FIELD_ELEMENT_SIZE - 4 - i * 4;
139            bytes[offset] = (limb >> 24) as u8;
140            bytes[offset + 1] = (limb >> 16) as u8;
141            bytes[offset + 2] = (limb >> 8) as u8;
142            bytes[offset + 3] = limb as u8;
143        }
144        let most_significant = self.0[16] & 0x1ff;
145        bytes[0] = (most_significant >> 8) as u8;
146        bytes[1] = most_significant as u8;
147    }
148
149    /// Check if the field element represents zero
150    #[inline(always)]
151    pub fn is_zero(&self) -> bool {
152        let mut any = 0u32;
153        for &limb in &self.0 {
154            any |= limb;
155        }
156        any == 0
157    }
158
159    /// Return `true` if the field element is odd (least-significant bit set)
160    #[inline(always)]
161    pub fn is_odd(&self) -> bool {
162        (self.0[0] & 1) == 1
163    }
164
165    /// self < p ?   (constant-time)
166    #[inline(always)]
167    pub fn is_valid(&self) -> bool {
168        let (_difference, borrow) = Self::sbb_n(&self.0, &Self::MOD_LIMBS);
169        borrow == 1 // borrow = 1  ⇒  self < p
170    }
171}
172
173/* ========================================================================== */
174/*  Core helpers: limb add / sub                                              */
175/* ========================================================================== */
176
177impl FieldElement {
178    /// N-limb addition with carry.
179    #[inline(always)]
180    pub(crate) fn adc_n<const N: usize>(a: &[u32; N], b: &[u32; N]) -> (Zeroizing<[u32; N]>, u32) {
181        let mut out = Zeroizing::new([0u32; N]);
182        let mut carry = 0u64;
183        for i in 0..N {
184            let t = a[i] as u64 + b[i] as u64 + carry;
185            out[i] = t as u32;
186            carry = t >> 32;
187        }
188        (out, carry as u32)
189    }
190
191    /// N-limb subtraction with borrow.
192    #[inline(always)]
193    pub(crate) fn sbb_n<const N: usize>(a: &[u32; N], b: &[u32; N]) -> (Zeroizing<[u32; N]>, u32) {
194        let mut out = Zeroizing::new([0u32; N]);
195        let mut borrow = 0i64;
196        for i in 0..N {
197            let t = a[i] as i64 - b[i] as i64 - borrow;
198            out[i] = t as u32;
199            borrow = (t >> 63) & 1; // 1 if negative
200        }
201        (out, borrow as u32)
202    }
203
204    /// Conditionally select (`flag` = 0 ⇒ *a*, `flag` = 1 ⇒ *b*).
205    #[inline(always)]
206    pub(crate) fn conditional_select(a: &Self, b: &Self, flag: Choice) -> Self {
207        Self::select_limbs(&a.0, &b.0, flag)
208    }
209
210    #[inline(never)]
211    fn select_limbs(a: &[u32; P521_LIMBS], b: &[u32; P521_LIMBS], flag: Choice) -> Self {
212        let mut out = Zeroizing::new([0u32; P521_LIMBS]);
213        for i in 0..P521_LIMBS {
214            out[i] = u32::conditional_select(&a[i], &b[i], flag);
215        }
216        FieldElement(*out)
217    }
218
219    /// Constant-time conditional swap
220    ///
221    /// Swaps the two field elements if choice is 1, leaves them unchanged if choice is 0.
222    /// This operation is performed in constant time to prevent timing attacks.
223    #[inline(always)]
224    pub fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
225        for i in 0..P521_LIMBS {
226            let mut tmp = u32::conditional_select(&a.0[i], &b.0[i], choice);
227            b.0[i] = u32::conditional_select(&b.0[i], &a.0[i], choice);
228            a.0[i] = tmp;
229            tmp.zeroize();
230        }
231    }
232}
233
234/* ========================================================================== */
235/*  P-521 reduction helper                                                    */
236/* ========================================================================== */
237
238impl FieldElement {
239    /// Reduce a 34-limb value (little-endian u32) modulo
240    /// p = 2²⁵²¹ − 1.  Runs in constant time.
241    fn reduce_wide(t: &[u32; 34]) -> Self {
242        // Split exactly at bit 521 and use 2^521 == 1 (mod p).  The high
243        // half spans 18 limbs because the product is at most 1088 bits.
244        let mut first = Zeroizing::new([0u32; 18]);
245        let mut carry = 0u64;
246        for i in 0..16 {
247            let high = ((t[i + 16] >> 9) | (t[i + 17] << 23)) as u64;
248            let value = t[i] as u64 + high + carry;
249            first[i] = value as u32;
250            carry = value >> 32;
251        }
252        let high_16 = ((t[32] >> 9) | (t[33] << 23)) as u64;
253        let value_16 = (t[16] & 0x1ff) as u64 + high_16 + carry;
254        first[16] = value_16 as u32;
255        carry = value_16 >> 32;
256
257        let value_17 = ((t[33] as u64) >> 9) + carry;
258        first[17] = value_17 as u32;
259
260        // Fold the at-most-47-bit remainder above bit 521 back into the low
261        // limbs.  Carry propagation always traverses every limb.
262        let extra = ((first[16] >> 9) as u64) | ((first[17] as u64) << 23);
263        let mut limbs = Zeroizing::new([0u32; P521_LIMBS]);
264        carry = extra;
265        for i in 0..P521_LIMBS {
266            let low = if i == 16 { first[i] & 0x1ff } else { first[i] };
267            let value = low as u64 + carry;
268            limbs[i] = value as u32;
269            carry = value >> 32;
270        }
271
272        // The previous fold yields a value below 2^521 + 2.  One conditional
273        // subtraction therefore produces the unique canonical representative.
274
275        let (sub, borrow) = Self::sbb_n(&limbs, &Self::MOD_LIMBS);
276        Self::select_limbs(&limbs, &sub, Choice::from((borrow ^ 1) as u8))
277    }
278}
279
280/* ========================================================================== */
281/*  Public API: add / sub / mul / square / invert / sqrt                      */
282/* ========================================================================== */
283
284impl FieldElement {
285    /// Constant-time addition modulo p
286    pub fn add(&self, other: &Self) -> Self {
287        let (sum, carry) = Self::adc_n(&self.0, &other.0);
288        // If there was a carry OR the sum ≥ p  ⇒ subtract once.
289        let (sub, borrow) = Self::sbb_n(&sum, &Self::MOD_LIMBS);
290        let need_sub = Choice::from(((carry | (borrow ^ 1)) & 1) as u8);
291        Self::select_limbs(&sum, &sub, need_sub)
292    }
293
294    /// Constant-time subtraction modulo p
295    pub fn sub(&self, other: &Self) -> Self {
296        let (diff, borrow) = Self::sbb_n(&self.0, &other.0);
297        // If we borrowed ⇒ add p back.
298        let (sum, _carry) = Self::adc_n(&diff, &Self::MOD_LIMBS);
299        Self::select_limbs(&diff, &sum, Choice::from(borrow as u8))
300    }
301
302    /// Field multiplication using school-book multiply + Mersenne reduction.
303    pub fn mul(&self, other: &Self) -> Self {
304        // ── 1. 17×17 → 34 partial products (128-bit accumulator) ----------
305        let mut wide = Zeroizing::new([0u128; 34]);
306        for i in 0..17 {
307            for j in 0..17 {
308                wide[i + j] += (self.0[i] as u128) * (other.0[j] as u128);
309            }
310        }
311
312        // ── 2. Carry-propagate 128-bit → 34 × 32-bit limbs -----------------
313        let mut limbs = Zeroizing::new([0u32; 34]);
314        let mut carry: u128 = 0;
315        for i in 0..34 {
316            let v = wide[i] + carry;
317            limbs[i] = (v & 0xFFFF_FFFF) as u32;
318            carry = v >> 32;
319        }
320        // Index 33 is an explicit empty carry limb (the largest product term
321        // is at index 32), so the final carry is zero by construction.
322        let _ = carry;
323
324        // ── 3. Reduce back to 17 limbs -------------------------------------
325        Self::reduce_wide(&limbs)
326    }
327
328    /// Field squaring – just a specialised multiplication.
329    #[inline(always)]
330    pub fn square(&self) -> Self {
331        self.mul(self)
332    }
333
334    /// Fermat-inversion  a^(p−2)  via left-to-right square-and-multiply.
335    pub fn invert(&self) -> Result<Self> {
336        if self.is_zero() {
337            return Err(Error::param("FieldElement P-521", "Inverse of zero"));
338        }
339
340        // Prepare exponent  p−2  =  (2^521 − 1) − 2  =  2^521 − 3
341        //   p  in bytes is   0x01 | 0xFF * 65
342        let mut exp = Zeroizing::new([0u8; P521_FIELD_ELEMENT_SIZE]);
343        exp[0] = 0x01;
344        for byte in exp.iter_mut().skip(1) {
345            *byte = 0xFF;
346        }
347        // subtract 2                                      (big-endian)
348        let mut borrow = 2u16;
349        for i in (0..66).rev() {
350            let v = exp[i] as i16 - borrow as i16;
351            exp[i] = if v < 0 { (v + 256) as u8 } else { v as u8 };
352            borrow = if v < 0 { 1 } else { 0 };
353        }
354
355        // Left-to-right binary exponentiation
356        let mut result = Zeroizing::new(FieldElement::one());
357        let base = Zeroizing::new(self.clone());
358        for byte in exp.iter() {
359            for bit in (0..8).rev() {
360                let squared = Zeroizing::new(result.square());
361                result.zeroize();
362                *result = squared.into_inner();
363                if (byte >> bit) & 1 == 1 {
364                    let next = Zeroizing::new(result.mul(&base));
365                    result.zeroize();
366                    *result = next.into_inner();
367                }
368            }
369        }
370        Ok(result.into_inner())
371    }
372
373    /// Square-root via  a^{(p+1)/4}  (because p ≡ 3 mod 4).
374    /// (p+1)/4 = 2^519.
375    pub fn sqrt(&self) -> Option<Self> {
376        if self.is_zero() {
377            return Some(Self::zero());
378        }
379        // a^{2^519}
380        let mut res = Zeroizing::new(self.clone());
381        for _ in 0..519 {
382            let squared = Zeroizing::new(res.square());
383            res.zeroize();
384            *res = squared.into_inner();
385        }
386        // verify
387        let verification = Zeroizing::new(res.square());
388        if *verification == *self {
389            Some(res.into_inner())
390        } else {
391            None
392        }
393    }
394
395    /// Get the field modulus p as a FieldElement
396    pub(crate) fn get_modulus() -> Self {
397        FieldElement(Self::MOD_LIMBS)
398    }
399}