dcrypt_algorithms/ec/p521/
scalar.rs

1//! P-521 scalar arithmetic operations
2
3use crate::ec::p521::constants::{p521_bytes_to_limbs, p521_limbs_to_bytes, P521_SCALAR_SIZE};
4use crate::ec::p521::field::FieldElement;
5use crate::error::{validate, Error, Result};
6use dcrypt_common::security::SecretBuffer;
7use dcrypt_params::traditional::ecdsa::NIST_P521;
8use zeroize::{Zeroize, ZeroizeOnDrop};
9
10/// P-521 scalar value for use in elliptic curve operations.
11/// Represents integers modulo the curve order n. Used for private keys
12/// and scalar multiplication. Automatically zeroized on drop for security.
13#[derive(Clone, Zeroize, ZeroizeOnDrop, Debug)]
14pub struct Scalar(SecretBuffer<P521_SCALAR_SIZE>);
15
16impl Scalar {
17    /// Create a scalar from raw bytes with modular reduction.
18    /// Ensures the scalar is in the valid range [1, n-1] where n is the curve order.
19    /// Performs modular reduction if the input is >= n.
20    /// Returns an error if the result would be zero (invalid for cryptographic use).
21    pub fn new(mut data: [u8; P521_SCALAR_SIZE]) -> Result<Self> {
22        Self::reduce_scalar_bytes(&mut data)?;
23        Ok(Scalar(SecretBuffer::new(data)))
24    }
25
26    /// Internal constructor that allows zero values.
27    /// Used for intermediate arithmetic operations where zero is a valid result.
28    /// Should NOT be used for secret keys, nonces, or final signature components.
29    pub(super) fn from_bytes_unchecked(bytes: [u8; P521_SCALAR_SIZE]) -> Self {
30        Scalar(SecretBuffer::new(bytes))
31    }
32
33    /// Create a scalar from an existing SecretBuffer.
34    /// Performs the same validation and reduction as `new()` but starts
35    /// from a SecretBuffer instead of a raw byte array.
36    pub fn from_secret_buffer(buffer: SecretBuffer<P521_SCALAR_SIZE>) -> Result<Self> {
37        let mut bytes = [0u8; P521_SCALAR_SIZE];
38        bytes.copy_from_slice(buffer.as_ref());
39
40        Self::reduce_scalar_bytes(&mut bytes)?;
41        Ok(Scalar(SecretBuffer::new(bytes)))
42    }
43
44    /// Access the underlying SecretBuffer containing the scalar value
45    pub fn as_secret_buffer(&self) -> &SecretBuffer<P521_SCALAR_SIZE> {
46        &self.0
47    }
48
49    /// Serialize the scalar to a byte array.
50    /// Returns the scalar in big-endian byte representation.
51    /// The output is suitable for storage or transmission.
52    pub fn serialize(&self) -> [u8; P521_SCALAR_SIZE] {
53        let mut result = [0u8; P521_SCALAR_SIZE];
54        result.copy_from_slice(self.0.as_ref());
55        result
56    }
57
58    /// Deserialize a scalar from bytes with validation.
59    /// Parses bytes as a big-endian scalar value and ensures it's
60    /// in the valid range for P-521 operations.
61    pub fn deserialize(bytes: &[u8]) -> Result<Self> {
62        validate::length("P-521 Scalar", bytes.len(), P521_SCALAR_SIZE)?;
63
64        let mut scalar_bytes = [0u8; P521_SCALAR_SIZE];
65        scalar_bytes.copy_from_slice(bytes);
66
67        Self::new(scalar_bytes)
68    }
69
70    /// Check if the scalar represents zero.
71    /// Constant-time check to determine if the scalar is the
72    /// additive identity (which is invalid for most cryptographic operations).
73    pub fn is_zero(&self) -> bool {
74        self.0.as_ref().iter().all(|&b| b == 0)
75    }
76
77    /// Convert big-endian 66-byte array to 17 little-endian u32 limbs
78    #[inline(always)]
79    fn to_le_limbs(bytes_be: &[u8; 66]) -> [u32; 17] {
80        p521_bytes_to_limbs(bytes_be)
81    }
82
83    /// Convert 17 little-endian limbs back to big-endian 66-byte array
84    #[inline(always)]
85    fn limbs_to_be(limbs: &[u32; 17]) -> [u8; 66] {
86        p521_limbs_to_bytes(limbs)
87    }
88
89    /// Add two scalars modulo the curve order n
90    pub fn add_mod_n(&self, other: &Self) -> Result<Self> {
91        let a = Self::to_le_limbs(&self.serialize());
92        let b = Self::to_le_limbs(&other.serialize());
93
94        let (mut r, carry) = FieldElement::adc_n(a, b);
95
96        // if overflowed OR r >= n ⇒ subtract n once
97        if carry == 1 || Self::geq(&r, &Self::N_LIMBS) {
98            Self::sub_in_place(&mut r, &Self::N_LIMBS);
99        }
100
101        Ok(Self::from_bytes_unchecked(Self::limbs_to_be(&r)))
102    }
103
104    /// Subtract two scalars modulo the curve order n
105    pub fn sub_mod_n(&self, other: &Self) -> Result<Self> {
106        let a = Self::to_le_limbs(&self.serialize());
107        let b = Self::to_le_limbs(&other.serialize());
108
109        let (mut r, borrow) = FieldElement::sbb_n(a, b);
110
111        // if negative ⇒ add n back
112        if borrow == 1 {
113            let (sum, _) = FieldElement::adc_n(r, Self::N_LIMBS);
114            r = sum;
115        }
116
117        Ok(Self::from_bytes_unchecked(Self::limbs_to_be(&r)))
118    }
119
120    /// Multiply two scalars modulo the curve order n.
121    /// Uses constant-time double-and-add algorithm for correctness and security.
122    /// Processes bits from MSB to LSB to ensure correct powers of 2.
123    pub fn mul_mod_n(&self, other: &Self) -> Result<Self> {
124        // Start with zero (additive identity)
125        let mut acc = Self::from_bytes_unchecked([0u8; P521_SCALAR_SIZE]);
126
127        // Process each bit from MSB to LSB
128        for byte in other.serialize() {
129            for i in (0..8).rev() {
130                // MSB first within each byte
131                // Double the accumulator: acc = acc * 2 (mod n)
132                acc = acc.add_mod_n(&acc)?;
133
134                // If bit is set, add self: acc = acc + self (mod n)
135                if (byte >> i) & 1 == 1 {
136                    acc = acc.add_mod_n(self)?;
137                }
138            }
139        }
140
141        Ok(acc)
142    }
143
144    /// Compute multiplicative inverse modulo n using Fermat's little theorem
145    /// a^(-1) ≡ a^(n-2) (mod n). Left-to-right binary exponentiation.
146    pub fn inv_mod_n(&self) -> Result<Self> {
147        // zero has no inverse
148        if self.is_zero() {
149            return Err(Error::param("P-521 Scalar", "Cannot invert zero scalar"));
150        }
151
152        // Step 1: form exponent = n-2
153        let mut exp = NIST_P521.n; // big-endian [u8;66]
154                                   // subtract 2 with borrow
155        let mut borrow = 2u16;
156        for i in (0..P521_SCALAR_SIZE).rev() {
157            let v = exp[i] as i16 - (borrow as i16);
158            if v < 0 {
159                exp[i] = (v + 256) as u8;
160                borrow = 1;
161            } else {
162                exp[i] = v as u8;
163                borrow = 0;
164            }
165        }
166
167        // Step 2: binary exponentiation, left-to-right:
168        let mut result = {
169            let mut one = [0u8; P521_SCALAR_SIZE];
170            one[P521_SCALAR_SIZE - 1] = 1;
171            Self::from_bytes_unchecked(one)
172        };
173        let base = self.clone();
174
175        for byte in exp {
176            for bit in (0..8).rev() {
177                // square
178                result = result.mul_mod_n(&result)?;
179                // multiply if this exp-bit is 1
180                if (byte >> bit) & 1 == 1 {
181                    result = result.mul_mod_n(&base)?;
182                }
183            }
184        }
185
186        Ok(result)
187    }
188
189    /// Compute the additive inverse (negation) modulo n
190    /// Returns -self mod n, which is equivalent to n - self when self != 0
191    /// Returns 0 when self is 0
192    pub fn negate(&self) -> Self {
193        // If self is zero, return zero
194        if self.is_zero() {
195            return Self::from_bytes_unchecked([0u8; P521_SCALAR_SIZE]);
196        }
197
198        // Otherwise compute n - self
199        let n_limbs = Self::N_LIMBS;
200        let self_limbs = Self::to_le_limbs(&self.serialize());
201        let mut res = [0u32; 17];
202
203        // Subtract self from n
204        let mut borrow = 0i64;
205        for i in 0..17 {
206            let tmp = n_limbs[i] as i64 - self_limbs[i] as i64 - borrow;
207            if tmp < 0 {
208                res[i] = (tmp + (1i64 << 32)) as u32;
209                borrow = 1;
210            } else {
211                res[i] = tmp as u32;
212                borrow = 0;
213            }
214        }
215
216        // No borrow should occur since self < n
217        debug_assert_eq!(borrow, 0);
218
219        Self::from_bytes_unchecked(Self::limbs_to_be(&res))
220    }
221
222    // Private helper methods
223
224    /// Reduce scalar modulo the curve order n using constant-time arithmetic.
225    /// The curve order n for P-521 is:
226    /// n = 0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409
227    ///
228    /// Algorithm:
229    /// 1. Check if input is zero (invalid)
230    /// 2. Compare with curve order using constant-time comparison
231    /// 3. Conditionally subtract n if input >= n
232    /// 4. Verify result is still non-zero
233    ///
234    /// Constant-time "a ≥ b" test on 66-byte big-endian values
235    #[inline(always)]
236    fn ge_be(a: &[u8; P521_SCALAR_SIZE], b: &[u8; P521_SCALAR_SIZE]) -> bool {
237        let mut gt = 0u8;
238        let mut lt = 0u8;
239
240        for i in 0..P521_SCALAR_SIZE {
241            gt |= ((a[i] > b[i]) as u8) & (!lt);
242            lt |= ((a[i] < b[i]) as u8) & (!gt);
243        }
244        // true when a > b  OR  a == b
245        gt == 1 || (gt == 0 && lt == 0)
246    }
247
248    /* ---------- patched reducer ---------- */
249
250    fn reduce_scalar_bytes(bytes: &mut [u8; P521_SCALAR_SIZE]) -> Result<()> {
251        let order = &NIST_P521.n;
252
253        // reject zero
254        if bytes.iter().all(|&b| b == 0) {
255            return Err(Error::param("P-521 Scalar", "Scalar cannot be zero"));
256        }
257
258        // keep subtracting until bytes < order   (constant-time loop)
259        while Self::ge_be(bytes, order) {
260            let mut borrow = 0u16;
261            for i in (0..P521_SCALAR_SIZE).rev() {
262                let diff = bytes[i] as i16 - order[i] as i16 - borrow as i16;
263                if diff < 0 {
264                    bytes[i] = (diff + 256) as u8;
265                    borrow = 1;
266                } else {
267                    bytes[i] = diff as u8;
268                    borrow = 0;
269                }
270            }
271        }
272        Ok(())
273    }
274
275    /// n (group order) in 17 little-endian 32-bit limbs
276    const N_LIMBS: [u32; 17] = [
277        0x9138_6409, // limb 0  – least-significant
278        0xBB6F_B71E, // limb 1
279        0x899C_47AE, // limb 2
280        0x3BB5_C9B8, // limb 3
281        0xF709_A5D0, // limb 4
282        0x7FCC_0148, // limb 5
283        0xBF2F_966B, // limb 6
284        0x5186_8783, // limb 7
285        0xFFFF_FFFA, // limb 8
286        0xFFFF_FFFF, // limb 9
287        0xFFFF_FFFF, // limb 10
288        0xFFFF_FFFF, // limb 11
289        0xFFFF_FFFF, // limb 12
290        0xFFFF_FFFF, // limb 13
291        0xFFFF_FFFF, // limb 14
292        0xFFFF_FFFF, // limb 15
293        0x0000_01FF, // limb 16 – most-significant 9 bits
294    ];
295
296    /// Compare two limb arrays for greater-than-or-equal
297    #[inline(always)]
298    fn geq(a: &[u32; 17], b: &[u32; 17]) -> bool {
299        for i in (0..17).rev() {
300            if a[i] > b[i] {
301                return true;
302            }
303            if a[i] < b[i] {
304                return false;
305            }
306        }
307        true // equal
308    }
309
310    /// Subtract b from a in-place
311    #[inline(always)]
312    fn sub_in_place(a: &mut [u32; 17], b: &[u32; 17]) {
313        let mut borrow = 0u64;
314        for i in 0..17 {
315            let tmp = (a[i] as u64).wrapping_sub(b[i] as u64).wrapping_sub(borrow);
316            a[i] = tmp as u32;
317            borrow = (tmp >> 63) & 1; // 1 if we wrapped
318        }
319    }
320}