Skip to main content

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