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::{
13    p521_bytes_to_limbs, p521_limbs_to_bytes, P521_FIELD_ELEMENT_SIZE, P521_LIMBS,
14};
15use crate::error::{Error, Result};
16use subtle::{Choice, ConditionallySelectable};
17use zeroize::Zeroize;
18
19/// P-521 field element representing values in Fₚ (p = 2^521 − 1).
20/// Internally stored as 17 little-endian 32-bit limbs; only the low 9 bits
21/// of limb 16 are significant.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct FieldElement(pub(crate) [u32; P521_LIMBS]);
24
25impl Zeroize for FieldElement {
26    fn zeroize(&mut self) {
27        self.0.zeroize();
28    }
29}
30
31/* ========================================================================== */
32/*  Constants                                                                 */
33/* ========================================================================== */
34
35impl FieldElement {
36    /// p = 2^521 − 1  (little-endian limbs).
37    pub(crate) const MOD_LIMBS: [u32; P521_LIMBS] = [
38        0xFFFF_FFFF,
39        0xFFFF_FFFF,
40        0xFFFF_FFFF,
41        0xFFFF_FFFF,
42        0xFFFF_FFFF,
43        0xFFFF_FFFF,
44        0xFFFF_FFFF,
45        0xFFFF_FFFF,
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        0x0000_01FF, // limb 16 (only 9 bits used)
55    ];
56
57    /// a = −3 mod p  = 2^521 − 4  (little-endian limbs)
58    pub(crate) const A_M3: [u32; P521_LIMBS] = [
59        0xFFFF_FFFC,
60        0xFFFF_FFFF,
61        0xFFFF_FFFF,
62        0xFFFF_FFFF,
63        0xFFFF_FFFF,
64        0xFFFF_FFFF,
65        0xFFFF_FFFF,
66        0xFFFF_FFFF,
67        0xFFFF_FFFF,
68        0xFFFF_FFFF,
69        0xFFFF_FFFF,
70        0xFFFF_FFFF,
71        0xFFFF_FFFF,
72        0xFFFF_FFFF,
73        0xFFFF_FFFF,
74        0xFFFF_FFFF,
75        0x0000_01FF,
76    ];
77
78    /// The additive identity element: 0
79    #[inline]
80    pub fn zero() -> Self {
81        FieldElement([0u32; P521_LIMBS])
82    }
83
84    /// The multiplicative identity element: 1
85    #[inline]
86    pub fn one() -> Self {
87        let mut limbs = [0u32; P521_LIMBS];
88        limbs[0] = 1;
89        Self(limbs)
90    }
91}
92
93/* ========================================================================== */
94/*  (De)Serialisation                                                         */
95/* ========================================================================== */
96
97impl FieldElement {
98    /// Create a field element from big-endian byte representation.
99    ///
100    /// Validates that the input represents a value less than the field modulus p.
101    /// Returns an error if the value is >= p.
102    pub fn from_bytes(bytes: &[u8; P521_FIELD_ELEMENT_SIZE]) -> Result<Self> {
103        let limbs = p521_bytes_to_limbs(bytes);
104        let fe = FieldElement(limbs);
105        if !fe.is_valid() {
106            return Err(Error::param("FieldElement P-521", "Value >= modulus"));
107        }
108        Ok(fe)
109    }
110
111    /// Convert field element to big-endian byte representation
112    pub fn to_bytes(&self) -> [u8; P521_FIELD_ELEMENT_SIZE] {
113        p521_limbs_to_bytes(&self.0)
114    }
115
116    /// Check if the field element represents zero
117    #[inline(always)]
118    pub fn is_zero(&self) -> bool {
119        self.0.iter().all(|&w| w == 0)
120    }
121
122    /// Return `true` if the field element is odd (least-significant bit set)
123    #[inline(always)]
124    pub fn is_odd(&self) -> bool {
125        (self.0[0] & 1) == 1
126    }
127
128    /// self < p ?   (constant-time)
129    #[inline(always)]
130    pub fn is_valid(&self) -> bool {
131        let (_, borrow) = Self::sbb_n(self.0, Self::MOD_LIMBS);
132        borrow == 1 // borrow = 1  ⇒  self < p
133    }
134}
135
136/* ========================================================================== */
137/*  Core helpers: limb add / sub                                              */
138/* ========================================================================== */
139
140impl FieldElement {
141    /// N-limb addition with carry.
142    #[inline(always)]
143    pub(crate) fn adc_n<const N: usize>(a: [u32; N], b: [u32; N]) -> ([u32; N], u32) {
144        let mut out = [0u32; N];
145        let mut carry = 0u64;
146        for i in 0..N {
147            let t = a[i] as u64 + b[i] as u64 + carry;
148            out[i] = t as u32;
149            carry = t >> 32;
150        }
151        (out, carry as u32)
152    }
153
154    /// N-limb subtraction with borrow.
155    #[inline(always)]
156    pub(crate) fn sbb_n<const N: usize>(a: [u32; N], b: [u32; N]) -> ([u32; N], u32) {
157        let mut out = [0u32; N];
158        let mut borrow = 0i64;
159        for i in 0..N {
160            let t = a[i] as i64 - b[i] as i64 - borrow;
161            out[i] = t as u32;
162            borrow = (t >> 63) & 1; // 1 if negative
163        }
164        (out, borrow as u32)
165    }
166
167    /// Conditionally select (`flag` = 0 ⇒ *a*, `flag` = 1 ⇒ *b*).
168    #[inline(always)]
169    fn conditional_select(a: &[u32; P521_LIMBS], b: &[u32; P521_LIMBS], flag: Choice) -> Self {
170        let mut out = [0u32; P521_LIMBS];
171        for i in 0..P521_LIMBS {
172            out[i] = u32::conditional_select(&a[i], &b[i], flag);
173        }
174        FieldElement(out)
175    }
176
177    /// Constant-time conditional swap
178    ///
179    /// Swaps the two field elements if choice is 1, leaves them unchanged if choice is 0.
180    /// This operation is performed in constant time to prevent timing attacks.
181    #[inline(always)]
182    pub fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
183        for i in 0..P521_LIMBS {
184            let tmp = u32::conditional_select(&a.0[i], &b.0[i], choice);
185            b.0[i] = u32::conditional_select(&b.0[i], &a.0[i], choice);
186            a.0[i] = tmp;
187        }
188    }
189}
190
191/* ========================================================================== */
192/*  P-521 reduction helper                                                    */
193/* ========================================================================== */
194
195impl FieldElement {
196    /// Reduce a 34-limb value (little-endian u32) modulo
197    /// p = 2²⁵²¹ − 1.  Runs in constant time.
198    fn reduce_wide(t: [u32; 34]) -> Self {
199        // --------------------------------------------------------------------- //
200        // 1.  Split the input in two halves:  L = t[ 0..17],  H = t[17..34]     //
201        //     and add       L  +  (H << 23).                                    //
202        //     While doing so we fold the overflow of each limb straight away,   //
203        //     so we never need more than an 18-limb scratch buffer.             //
204        // --------------------------------------------------------------------- //
205        let mut acc = [0u64; 18]; // 17 limbs  + a possible final carry
206        let mut c: u64 = 0;
207
208        for i in 0..17 {
209            let l = t[i] as u64;
210            let h = t[17 + i] as u64;
211
212            // low   32 bits of (h << 23)
213            let lo = (h << 23) & 0xFFFF_FFFF;
214            // overflow  (high bits of the same shift)
215            let hi = h >> 9; // (32 − 23) = 9
216
217            let tmp = l + lo + c;
218            acc[i] = tmp & 0xFFFF_FFFF;
219            c = (tmp >> 32) + hi; // propagate both the normal carry
220        }
221        acc[17] = c; // (can be up to 2³³ − 2)
222
223        // --------------------------------------------------------------------- //
224        // 2.  Fold the 18-th limb (bit-position 544) once more:                  //
225        //         2²⁵⁴⁴  ≡  2²³   (mod p)                                        //
226        // --------------------------------------------------------------------- //
227        let top = acc[17];
228        if top != 0 {
229            // add (top << 23) to limb-0   … overflow will bubble to the right
230            let tmp = acc[0] + ((top << 23) & 0xFFFF_FFFF);
231            acc[0] = tmp & 0xFFFF_FFFF;
232            let mut c = (tmp >> 32) + (top >> 9); // carry to propagate
233
234            // propagate through all 17 limbs, *wrapping* when we fall off the end
235            for i in 1..=17 {
236                let idx = i % 17;
237                let tmp = acc[idx] + c;
238                acc[idx] = tmp & 0xFFFF_FFFF;
239                c = tmp >> 32;
240                if c == 0 {
241                    break;
242                }
243            }
244            acc[17] = 0; // completely folded away
245        }
246
247        // --------------------------------------------------------------------- //
248        // 3.  Limb-16 must only keep its lowest 9 bits.  Everything above that   //
249        //     again represents   k · 2²⁵²¹  and is folded back into limb-0.     //
250        // --------------------------------------------------------------------- //
251        let extra = acc[16] >> 9; // at most 23 bits
252        acc[16] &= 0x1FF;
253        if extra != 0 {
254            let mut i = 0;
255            let mut c = extra;
256            loop {
257                let tmp = acc[i] + c;
258                acc[i] = tmp & 0xFFFF_FFFF;
259                c = tmp >> 32;
260                if c == 0 {
261                    break;
262                }
263                i = (i + 1) % 17;
264            }
265        }
266
267        // --------------------------------------------------------------------- //
268        // 4.  Conditional subtraction of the modulus.                           //
269        // --------------------------------------------------------------------- //
270        let mut limbs = [0u32; 17];
271        for i in 0..17 {
272            limbs[i] = acc[i] as u32;
273        }
274
275        let (sub, borrow) = Self::sbb_n(limbs, Self::MOD_LIMBS);
276        Self::conditional_select(&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::conditional_select(&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, _) = Self::adc_n(diff, Self::MOD_LIMBS);
299        Self::conditional_select(&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 = [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 = [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        // `carry` may be non-zero (at most 2^32−1) – push it as limb 34 if so
321        if carry != 0 {
322            // This is equivalent to a limb 34 that will be folded anyway.
323            let extra = carry as u32;
324            let (new, of) = limbs[0].overflowing_add(extra);
325            limbs[0] = new;
326            if of {
327                // propagate one more carry (rare)
328                let mut k = 1;
329                while k < 17 {
330                    let (n, o) = limbs[k].overflowing_add(1);
331                    limbs[k] = n;
332                    if !o {
333                        break;
334                    }
335                    k += 1;
336                }
337            }
338        }
339
340        // ── 3. Reduce back to 17 limbs -------------------------------------
341        Self::reduce_wide(limbs)
342    }
343
344    /// Field squaring – just a specialised multiplication.
345    #[inline(always)]
346    pub fn square(&self) -> Self {
347        self.mul(self)
348    }
349
350    /// Fermat-inversion  a^(p−2)  via left-to-right square-and-multiply.
351    pub fn invert(&self) -> Result<Self> {
352        if self.is_zero() {
353            return Err(Error::param("FieldElement P-521", "Inverse of zero"));
354        }
355
356        // Prepare exponent  p−2  =  (2^521 − 1) − 2  =  2^521 − 3
357        //   p  in bytes is   0x01 | 0xFF * 65
358        let mut exp = [0u8; P521_FIELD_ELEMENT_SIZE];
359        exp[0] = 0x01;
360        for byte in exp.iter_mut().skip(1) {
361            *byte = 0xFF;
362        }
363        // subtract 2                                      (big-endian)
364        let mut borrow = 2u16;
365        for i in (0..66).rev() {
366            let v = exp[i] as i16 - borrow as i16;
367            exp[i] = if v < 0 { (v + 256) as u8 } else { v as u8 };
368            borrow = if v < 0 { 1 } else { 0 };
369        }
370
371        // Left-to-right binary exponentiation
372        let mut result = FieldElement::one();
373        let base = self.clone();
374        for byte in exp.iter() {
375            for bit in (0..8).rev() {
376                result = result.square();
377                if (byte >> bit) & 1 == 1 {
378                    result = result.mul(&base);
379                }
380            }
381        }
382        Ok(result)
383    }
384
385    /// Square-root via  a^{(p+1)/4}  (because p ≡ 3 mod 4).
386    /// (p+1)/4 = 2^519.
387    pub fn sqrt(&self) -> Option<Self> {
388        if self.is_zero() {
389            return Some(Self::zero());
390        }
391        // a^{2^519}
392        let mut res = self.clone();
393        for _ in 0..519 {
394            res = res.square();
395        }
396        // verify
397        if res.square() == *self {
398            Some(res)
399        } else {
400            None
401        }
402    }
403
404    /// Get the field modulus p as a FieldElement
405    pub(crate) fn get_modulus() -> Self {
406        FieldElement(Self::MOD_LIMBS)
407    }
408}