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