Skip to main content

dcrypt_algorithms/ec/p256/
scalar.rs

1//! P-256 scalar arithmetic operations
2
3use crate::ec::p256::constants::P256_SCALAR_SIZE;
4use crate::error::{validate, Error, Result};
5use dcrypt_common::security::SecretBuffer;
6use dcrypt_params::traditional::ecdsa::NIST_P256;
7use subtle::{Choice, ConditionallySelectable};
8use zeroize::{Zeroize, ZeroizeOnDrop};
9
10/// P-256 scalar value for use in elliptic curve operations
11///
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<P256_SCALAR_SIZE>);
16
17impl Scalar {
18    /// Create a canonical non-zero scalar from raw bytes.
19    ///
20    /// Private keys, nonces, and serialized signature components must be in
21    /// the interval `1..n`; out-of-range inputs are rejected rather than
22    /// reduced. Use [`Self::from_bytes_reduced`] only for mathematical
23    /// intermediates whose specification explicitly requires reduction.
24    pub fn new(data: [u8; P256_SCALAR_SIZE]) -> Result<Self> {
25        Self::validate_canonical_nonzero(&data)?;
26        Ok(Scalar(SecretBuffer::new(data)))
27    }
28
29    /// Interpret a 256-bit integer modulo the group order, including zero.
30    ///
31    /// This is for ECDSA hash and x-coordinate conversion, where FIPS 186
32    /// requires reduction rather than private-scalar validation.
33    pub fn from_bytes_reduced(mut data: [u8; P256_SCALAR_SIZE]) -> Self {
34        Self::reduce_scalar_bytes_allow_zero(&mut data);
35        Self::from_bytes_unchecked(data)
36    }
37
38    /// Internal constructor that allows zero values
39    ///
40    /// Used for intermediate arithmetic operations where zero is a valid result.
41    /// Should NOT be used for secret keys, nonces, or final signature components.
42    fn from_bytes_unchecked(bytes: [u8; P256_SCALAR_SIZE]) -> Self {
43        Scalar(SecretBuffer::new(bytes))
44    }
45
46    /// Create a scalar from an existing SecretBuffer
47    ///
48    /// Performs the same canonical validation as `new()` but starts
49    /// from a SecretBuffer instead of a raw byte array.
50    pub fn from_secret_buffer(buffer: SecretBuffer<P256_SCALAR_SIZE>) -> Result<Self> {
51        let mut bytes = [0u8; P256_SCALAR_SIZE];
52        bytes.copy_from_slice(buffer.as_ref());
53
54        Self::validate_canonical_nonzero(&bytes)?;
55        Ok(Scalar(SecretBuffer::new(bytes)))
56    }
57
58    /// Access the underlying SecretBuffer containing the scalar value
59    pub fn as_secret_buffer(&self) -> &SecretBuffer<P256_SCALAR_SIZE> {
60        &self.0
61    }
62
63    /// Serialize the scalar to a byte array
64    ///
65    /// Returns the scalar in big-endian byte representation.
66    /// The output is suitable for storage or transmission.
67    pub fn serialize(&self) -> [u8; P256_SCALAR_SIZE] {
68        let mut result = [0u8; P256_SCALAR_SIZE];
69        result.copy_from_slice(self.0.as_ref());
70        result
71    }
72
73    /// Deserialize a scalar from bytes with validation
74    ///
75    /// Parses bytes as a big-endian scalar value and ensures it's
76    /// in the valid range for P-256 operations.
77    pub fn deserialize(bytes: &[u8]) -> Result<Self> {
78        validate::length("P-256 Scalar", bytes.len(), P256_SCALAR_SIZE)?;
79
80        let mut scalar_bytes = [0u8; P256_SCALAR_SIZE];
81        scalar_bytes.copy_from_slice(bytes);
82
83        Self::new(scalar_bytes)
84    }
85
86    /// Check if the scalar represents zero
87    ///
88    /// Constant-time check to determine if the scalar is the
89    /// additive identity (which is invalid for most cryptographic operations).
90    pub fn is_zero(&self) -> bool {
91        self.0.as_ref().iter().all(|&b| b == 0)
92    }
93
94    /// Convert big-endian bytes to little-endian limbs
95    /// Properly extracts 4-byte chunks from BE array and converts to LE limbs
96    #[inline(always)]
97    fn to_le_limbs(bytes_be: &[u8; 32]) -> [u32; 8] {
98        let mut limbs = [0u32; 8];
99
100        // limb-0 must hold the 4 least-significant bytes, limb-7 the 4 most-significant
101        #[allow(clippy::needless_range_loop)] // Index used for offset calculation
102        for i in 0..8 {
103            let start = 28 - i * 4; // index of the MS-byte of this limb
104            limbs[i] = u32::from_le_bytes([
105                bytes_be[start + 3],
106                bytes_be[start + 2],
107                bytes_be[start + 1],
108                bytes_be[start],
109            ]);
110        }
111        limbs
112    }
113
114    /// Add two scalars modulo the curve order n
115    pub fn add_mod_n(&self, other: &Self) -> Result<Self> {
116        let self_limbs = Self::to_le_limbs(&self.serialize());
117        let other_limbs = Self::to_le_limbs(&other.serialize());
118
119        let mut r = [0u32; 8];
120        let mut carry = 0u64;
121
122        // Plain 256-bit add
123        #[allow(clippy::needless_range_loop)] // Index used for multiple arrays
124        for i in 0..8 {
125            let tmp = self_limbs[i] as u64 + other_limbs[i] as u64 + carry;
126            r[i] = tmp as u32;
127            carry = tmp >> 32;
128        }
129
130        let unreduced = Self::from_bytes_unchecked(Self::limbs_to_be(&r));
131        let mut reduced = r;
132        let borrow = Self::sub_in_place(&mut reduced, &Self::N_LIMBS);
133        let need_reduce = Choice::from((carry as u8) | ((borrow ^ 1) as u8));
134
135        Ok(Self::conditional_select(
136            &unreduced,
137            &Self::from_bytes_unchecked(Self::limbs_to_be(&reduced)),
138            need_reduce,
139        ))
140    }
141
142    /// Subtract two scalars modulo the curve order n
143    pub fn sub_mod_n(&self, other: &Self) -> Result<Self> {
144        let self_limbs = Self::to_le_limbs(&self.serialize());
145        let other_limbs = Self::to_le_limbs(&other.serialize());
146
147        let mut r = [0u32; 8];
148        let mut borrow = 0u64;
149
150        #[allow(clippy::needless_range_loop)] // Index used for multiple arrays
151        for i in 0..8 {
152            let tmp = (self_limbs[i] as u64)
153                .wrapping_sub(other_limbs[i] as u64)
154                .wrapping_sub(borrow);
155            r[i] = tmp as u32;
156            borrow = (tmp >> 63) & 1;
157        }
158
159        let unreduced = Self::from_bytes_unchecked(Self::limbs_to_be(&r));
160        let mut reduced = r;
161        let mut carry = 0u64;
162        #[allow(clippy::needless_range_loop)] // Index used for multiple arrays
163        for i in 0..8 {
164            let tmp = reduced[i] as u64 + Self::N_LIMBS[i] as u64 + carry;
165            reduced[i] = tmp as u32;
166            carry = tmp >> 32;
167        }
168
169        Ok(Self::conditional_select(
170            &unreduced,
171            &Self::from_bytes_unchecked(Self::limbs_to_be(&reduced)),
172            Choice::from(borrow as u8),
173        ))
174    }
175
176    /// Multiply two scalars modulo the curve order n
177    ///
178    /// Uses constant-time double-and-add algorithm for correctness and security.
179    /// Processes bits from MSB to LSB to ensure correct powers of 2.
180    pub fn mul_mod_n(&self, other: &Self) -> Result<Self> {
181        // Start with zero (additive identity)
182        let mut acc = Self::from_bytes_unchecked([0u8; P256_SCALAR_SIZE]);
183
184        // Process each bit from MSB to LSB
185        for byte in other.serialize() {
186            for i in (0..8).rev() {
187                // MSB first within each byte
188                // Double the accumulator: acc = acc * 2 (mod n)
189                acc = acc.add_mod_n(&acc)?;
190
191                let acc_plus_self = acc.add_mod_n(self)?;
192                let choice = Choice::from((byte >> i) & 1);
193                acc = Self::conditional_select(&acc, &acc_plus_self, choice);
194            }
195        }
196
197        Ok(acc)
198    }
199
200    /// Compute multiplicative inverse modulo n using Fermat's little theorem
201    /// a^(-1) ≡ a^(n-2) (mod n).  Left-to-right binary exponentiation.
202    pub fn inv_mod_n(&self) -> Result<Self> {
203        // zero has no inverse
204        if self.is_zero() {
205            return Err(Error::param("P-256 Scalar", "Cannot invert zero scalar"));
206        }
207
208        // Step 1: form exponent = n-2
209        let mut exp = NIST_P256.n; // big-endian [u8;32]
210                                   // subtract 2 with borrow
211        let mut borrow = 2u16;
212        for i in (0..P256_SCALAR_SIZE).rev() {
213            let v = exp[i] as i16 - (borrow as i16);
214            if v < 0 {
215                exp[i] = (v + 256) as u8;
216                borrow = 1;
217            } else {
218                exp[i] = v as u8;
219                borrow = 0;
220            }
221        }
222
223        // Step 2: binary exponentiation, left-to-right:
224        //    result = 1
225        //    for each bit of exp from MSB to LSB:
226        //        result = result^2 mod n
227        //        if bit == 1 { result = result * a mod n }
228        let mut result = {
229            let mut one = [0u8; P256_SCALAR_SIZE];
230            one[P256_SCALAR_SIZE - 1] = 1;
231            // from_bytes_unchecked is fine here because 1 < n
232            Self::from_bytes_unchecked(one)
233        };
234        let base = self.clone();
235
236        for byte in exp {
237            for bit in (0..8).rev() {
238                // square
239                result = result.mul_mod_n(&result)?;
240                // multiply if this exp-bit is 1
241                if (byte >> bit) & 1 == 1 {
242                    result = result.mul_mod_n(&base)?;
243                }
244            }
245        }
246
247        Ok(result)
248    }
249
250    /// Compute the additive inverse (negation) modulo n
251    ///
252    /// Returns -self mod n, which is equivalent to n - self when self != 0
253    /// Returns 0 when self is 0
254    pub fn negate(&self) -> Self {
255        // If self is zero, return zero
256        if self.is_zero() {
257            return Self::from_bytes_unchecked([0u8; P256_SCALAR_SIZE]);
258        }
259
260        // Otherwise compute n - self
261        let n_limbs = Self::N_LIMBS;
262        let self_limbs = Self::to_le_limbs(&self.serialize());
263        let mut res = [0u32; 8];
264
265        // Subtract self from n
266        let mut borrow = 0i64;
267        #[allow(clippy::needless_range_loop)] // Index used for multiple arrays
268        for i in 0..8 {
269            let tmp = n_limbs[i] as i64 - self_limbs[i] as i64 - borrow;
270            if tmp < 0 {
271                res[i] = (tmp + (1i64 << 32)) as u32;
272                borrow = 1;
273            } else {
274                res[i] = tmp as u32;
275                borrow = 0;
276            }
277        }
278
279        // No borrow should occur since self < n
280        debug_assert_eq!(borrow, 0);
281
282        Self::from_bytes_unchecked(Self::limbs_to_be(&res))
283    }
284
285    // Private helper methods
286
287    /// Reduce scalar modulo the curve order n using constant-time arithmetic
288    ///
289    /// The curve order n for P-256 is:
290    /// n = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551
291    ///
292    /// The input is at most 2^256-1 and the order is greater than 2^255,
293    /// so at most one subtraction is required. Zero is intentionally allowed.
294    fn reduce_scalar_bytes_allow_zero(bytes: &mut [u8; P256_SCALAR_SIZE]) {
295        let order = &NIST_P256.n;
296
297        // Constant-time comparison with curve order
298        // We want to check: is bytes >= order?
299        let mut gt = 0u8; // set if bytes > order
300        let mut lt = 0u8; // set if bytes < order
301
302        for i in 0..P256_SCALAR_SIZE {
303            let x = bytes[i];
304            let y = order[i];
305            gt |= ((x > y) as u8) & (!lt);
306            lt |= ((x < y) as u8) & (!gt);
307        }
308        let ge = gt | ((!lt) & 1); // ge = gt || eq (if not less, then greater or equal)
309
310        if ge == 1 {
311            // If scalar >= order, perform modular reduction
312            let mut borrow = 0u16;
313            let mut temp_bytes = *bytes;
314
315            for i in (0..P256_SCALAR_SIZE).rev() {
316                let diff = (temp_bytes[i] as i16) - (order[i] as i16) - (borrow as i16);
317                if diff < 0 {
318                    temp_bytes[i] = (diff + 256) as u8;
319                    borrow = 1;
320                } else {
321                    temp_bytes[i] = diff as u8;
322                    borrow = 0;
323                }
324            }
325
326            *bytes = temp_bytes;
327        }
328    }
329
330    fn validate_canonical_nonzero(bytes: &[u8; P256_SCALAR_SIZE]) -> Result<()> {
331        if bytes.iter().all(|&byte| byte == 0) {
332            return Err(Error::param("P-256 Scalar", "Scalar cannot be zero"));
333        }
334
335        let order = &NIST_P256.n;
336        for (&byte, &order_byte) in bytes.iter().zip(order) {
337            if byte < order_byte {
338                return Ok(());
339            }
340            if byte > order_byte {
341                return Err(Error::param(
342                    "P-256 Scalar",
343                    "Scalar must be less than the group order",
344                ));
345            }
346        }
347
348        Err(Error::param(
349            "P-256 Scalar",
350            "Scalar must be less than the group order",
351        ))
352    }
353
354    // Helper constants - stored in little-endian limb order
355    const N_LIMBS: [u32; 8] = [
356        0xFC63_2551,
357        0xF3B9_CAC2,
358        0xA717_9E84,
359        0xBCE6_FAAD,
360        0xFFFF_FFFF,
361        0xFFFF_FFFF,
362        0x0000_0000,
363        0xFFFF_FFFF,
364    ];
365
366    #[inline(always)]
367    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
368        let a_bytes = a.serialize();
369        let b_bytes = b.serialize();
370        let mut out = [0u8; P256_SCALAR_SIZE];
371        for i in 0..P256_SCALAR_SIZE {
372            out[i] = u8::conditional_select(&a_bytes[i], &b_bytes[i], choice);
373        }
374        Self::from_bytes_unchecked(out)
375    }
376
377    /// Compare two limb arrays for greater-than-or-equal
378    #[inline(always)]
379    fn geq(a: &[u32; 8], b: &[u32; 8]) -> bool {
380        for i in (0..8).rev() {
381            if a[i] > b[i] {
382                return true;
383            }
384            if a[i] < b[i] {
385                return false;
386            }
387        }
388        true // equal
389    }
390
391    /// Subtract b from a in-place
392    #[inline(always)]
393    fn sub_in_place(a: &mut [u32; 8], b: &[u32; 8]) -> u64 {
394        let mut borrow = 0u64;
395        #[allow(clippy::needless_range_loop)] // Index used for multiple arrays
396        for i in 0..8 {
397            let tmp = (a[i] as u64).wrapping_sub(b[i] as u64).wrapping_sub(borrow);
398            a[i] = tmp as u32;
399            borrow = (tmp >> 63) & 1; // 1 if we wrapped
400        }
401        borrow
402    }
403
404    /// Convert little-endian limbs to big-endian bytes
405    /// The inverse of to_le_limbs
406    #[inline(always)]
407    fn limbs_to_be(limbs: &[u32; 8]) -> [u8; 32] {
408        let mut out = [0u8; 32];
409        for (i, &w) in limbs.iter().enumerate() {
410            let be = w.to_le_bytes(); // limb itself is little-endian
411            let start = 28 - i * 4;
412            out[start] = be[3];
413            out[start + 1] = be[2];
414            out[start + 2] = be[1];
415            out[start + 3] = be[0];
416        }
417        out
418    }
419}