dcrypt_algorithms/ec/p384/
scalar.rs

1//! P-384 scalar arithmetic operations
2
3use crate::ec::p384::constants::P384_SCALAR_SIZE;
4use crate::error::{validate, Error, Result};
5use dcrypt_common::security::SecretBuffer;
6use dcrypt_params::traditional::ecdsa::NIST_P384;
7use zeroize::{Zeroize, ZeroizeOnDrop};
8
9/// P-384 scalar value for use in elliptic curve operations
10///
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<P384_SCALAR_SIZE>);
15
16impl Scalar {
17    /// Create a scalar from raw bytes with modular reduction
18    ///
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; P384_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    ///
29    /// Used for intermediate arithmetic operations where zero is a valid result.
30    /// Should NOT be used for secret keys, nonces, or final signature components.
31    fn from_bytes_unchecked(bytes: [u8; P384_SCALAR_SIZE]) -> Self {
32        Scalar(SecretBuffer::new(bytes))
33    }
34
35    /// Create a scalar from an existing SecretBuffer
36    ///
37    /// Performs the same validation and reduction as `new()` but starts
38    /// from a SecretBuffer instead of a raw byte array.
39    pub fn from_secret_buffer(buffer: SecretBuffer<P384_SCALAR_SIZE>) -> Result<Self> {
40        let mut bytes = [0u8; P384_SCALAR_SIZE];
41        bytes.copy_from_slice(buffer.as_ref());
42
43        Self::reduce_scalar_bytes(&mut bytes)?;
44        Ok(Scalar(SecretBuffer::new(bytes)))
45    }
46
47    /// Access the underlying SecretBuffer containing the scalar value
48    pub fn as_secret_buffer(&self) -> &SecretBuffer<P384_SCALAR_SIZE> {
49        &self.0
50    }
51
52    /// Serialize the scalar to a byte array
53    ///
54    /// Returns the scalar in big-endian byte representation.
55    /// The output is suitable for storage or transmission.
56    pub fn serialize(&self) -> [u8; P384_SCALAR_SIZE] {
57        let mut result = [0u8; P384_SCALAR_SIZE];
58        result.copy_from_slice(self.0.as_ref());
59        result
60    }
61
62    /// Deserialize a scalar from bytes with validation
63    ///
64    /// Parses bytes as a big-endian scalar value and ensures it's
65    /// in the valid range for P-384 operations.
66    pub fn deserialize(bytes: &[u8]) -> Result<Self> {
67        validate::length("P-384 Scalar", bytes.len(), P384_SCALAR_SIZE)?;
68
69        let mut scalar_bytes = [0u8; P384_SCALAR_SIZE];
70        scalar_bytes.copy_from_slice(bytes);
71
72        Self::new(scalar_bytes)
73    }
74
75    /// Check if the scalar represents zero
76    ///
77    /// Constant-time check to determine if the scalar is the
78    /// additive identity (which is invalid for most cryptographic operations).
79    pub fn is_zero(&self) -> bool {
80        self.0.as_ref().iter().all(|&b| b == 0)
81    }
82
83    /// Convert big-endian 48-byte array → 12 little-endian u32 limbs
84    #[inline(always)]
85    fn to_le_limbs(bytes_be: &[u8; 48]) -> [u32; 12] {
86        let mut limbs = [0u32; 12];
87        for (i, limb) in limbs.iter_mut().enumerate() {
88            // MS limb first ⇒ start index counts back from the end
89            let start = 44 - i * 4;
90            *limb = u32::from_le_bytes([
91                bytes_be[start + 3],
92                bytes_be[start + 2],
93                bytes_be[start + 1],
94                bytes_be[start],
95            ]);
96        }
97        limbs
98    }
99
100    /// Convert 12 little-endian limbs back to big-endian 48-byte array  
101    /// (inverse of `to_le_limbs`)
102    #[inline(always)]
103    fn limbs_to_be(limbs: &[u32; 12]) -> [u8; 48] {
104        let mut out = [0u8; 48];
105        for (i, &w) in limbs.iter().enumerate() {
106            let le = w.to_le_bytes();
107            let start = 44 - i * 4;
108            out[start] = le[3];
109            out[start + 1] = le[2];
110            out[start + 2] = le[1];
111            out[start + 3] = le[0];
112        }
113        out
114    }
115
116    /// Add two scalars modulo the curve order n
117    pub fn add_mod_n(&self, other: &Self) -> Result<Self> {
118        let a = Self::to_le_limbs(&self.serialize());
119        let b = Self::to_le_limbs(&other.serialize());
120
121        let mut r = [0u32; 12];
122        let mut carry = 0u64;
123
124        // plain 384-bit addition
125        for i in 0..12 {
126            let tmp = a[i] as u64 + b[i] as u64 + carry;
127            r[i] = tmp as u32;
128            carry = tmp >> 32;
129        }
130
131        // if overflowed OR r ≥ n  ⇒ subtract n once
132        if carry == 1 || Self::geq(&r, &Self::N_LIMBS) {
133            Self::sub_in_place(&mut r, &Self::N_LIMBS);
134        }
135
136        Ok(Self::from_bytes_unchecked(Self::limbs_to_be(&r)))
137    }
138
139    /// Subtract two scalars modulo the curve order n
140    pub fn sub_mod_n(&self, other: &Self) -> Result<Self> {
141        let a = Self::to_le_limbs(&self.serialize());
142        let b = Self::to_le_limbs(&other.serialize());
143
144        let mut r = [0u32; 12];
145        let mut borrow = 0i64;
146
147        for (i, r_limb) in r.iter_mut().enumerate() {
148            let tmp = a[i] as i64 - b[i] as i64 - borrow;
149            if tmp < 0 {
150                *r_limb = (tmp + (1i64 << 32)) as u32;
151                borrow = 1;
152            } else {
153                *r_limb = tmp as u32;
154                borrow = 0;
155            }
156        }
157
158        // if negative ⇒ add n back
159        if borrow == 1 {
160            let mut c = 0u64;
161            for (i, r_limb) in r.iter_mut().enumerate() {
162                let tmp = *r_limb as u64 + Self::N_LIMBS[i] as u64 + c;
163                *r_limb = tmp as u32;
164                c = tmp >> 32;
165            }
166        }
167
168        Ok(Self::from_bytes_unchecked(Self::limbs_to_be(&r)))
169    }
170
171    /// Multiply two scalars modulo the curve order n
172    ///
173    /// Uses constant-time double-and-add algorithm for correctness and security.
174    /// Processes bits from MSB to LSB to ensure correct powers of 2.
175    pub fn mul_mod_n(&self, other: &Self) -> Result<Self> {
176        // Start with zero (additive identity)
177        let mut acc = Self::from_bytes_unchecked([0u8; P384_SCALAR_SIZE]);
178
179        // Process each bit from MSB to LSB
180        for byte in other.serialize() {
181            for i in (0..8).rev() {
182                // MSB first within each byte
183                // Double the accumulator: acc = acc * 2 (mod n)
184                acc = acc.add_mod_n(&acc)?;
185
186                // If bit is set, add self: acc = acc + self (mod n)
187                if (byte >> i) & 1 == 1 {
188                    acc = acc.add_mod_n(self)?;
189                }
190            }
191        }
192
193        Ok(acc)
194    }
195
196    /// Compute multiplicative inverse modulo n using Fermat's little theorem
197    pub fn inv_mod_n(&self) -> Result<Self> {
198        // Fast fail on zero - no multiplicative inverse exists
199        if self.is_zero() {
200            return Err(Error::param("P-384 Scalar", "Cannot invert zero scalar"));
201        }
202
203        // n-2 for P-384 in big-endian
204        // n = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973
205        // n-2 = FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52971
206        const N_MINUS_2: [u8; 48] = [
207            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
208            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x63, 0x4D, 0x81,
209            0xF4, 0x37, 0x2D, 0xDF, 0x58, 0x1A, 0x0D, 0xB2, 0x48, 0xB0, 0xA7, 0x7A, 0xEC, 0xEC,
210            0x19, 0x6A, 0xCC, 0xC5, 0x29, 0x71,
211        ];
212
213        let mut one_bytes = [0x00; 48];
214        one_bytes[47] = 0x01;
215        let mut result = Self::new(one_bytes)?;
216        let base = self.clone();
217
218        for byte in N_MINUS_2 {
219            for bit in (0..8).rev() {
220                result = result.mul_mod_n(&result)?;
221                if (byte >> bit) & 1 == 1 {
222                    result = result.mul_mod_n(&base)?;
223                }
224            }
225        }
226
227        Ok(result)
228    }
229
230    /// Compute the additive inverse (negation) modulo n
231    ///
232    /// Returns -self mod n, which is equivalent to n - self when self != 0
233    /// Returns 0 when self is 0
234    pub fn negate(&self) -> Self {
235        // If self is zero, return zero
236        if self.is_zero() {
237            return Self::from_bytes_unchecked([0u8; P384_SCALAR_SIZE]);
238        }
239
240        // Otherwise compute n - self
241        let n_limbs = Self::N_LIMBS;
242        let self_limbs = Self::to_le_limbs(&self.serialize());
243        let mut res = [0u32; 12];
244
245        // Subtract self from n
246        let mut borrow = 0i64;
247        for i in 0..12 {
248            let tmp = n_limbs[i] as i64 - self_limbs[i] as i64 - borrow;
249            if tmp < 0 {
250                res[i] = (tmp + (1i64 << 32)) as u32;
251                borrow = 1;
252            } else {
253                res[i] = tmp as u32;
254                borrow = 0;
255            }
256        }
257
258        // No borrow should occur since self < n
259        debug_assert_eq!(borrow, 0);
260
261        Self::from_bytes_unchecked(Self::limbs_to_be(&res))
262    }
263
264    // Private helper methods
265
266    /// Reduce scalar modulo the curve order n using constant-time arithmetic
267    ///
268    /// The curve order n for P-384 is:
269    /// n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973
270    ///
271    /// Algorithm:
272    /// 1. Check if input is zero (invalid)
273    /// 2. Compare with curve order using constant-time comparison
274    /// 3. Conditionally subtract n if input >= n
275    /// 4. Verify result is still non-zero
276    fn reduce_scalar_bytes(bytes: &mut [u8; P384_SCALAR_SIZE]) -> Result<()> {
277        let order = &NIST_P384.n;
278
279        // Reject zero scalars immediately
280        if bytes.iter().all(|&b| b == 0) {
281            return Err(Error::param("P-384 Scalar", "Scalar cannot be zero"));
282        }
283
284        // Constant-time comparison with curve order
285        // We want to check: is bytes >= order?
286        let mut gt = 0u8; // set if bytes > order
287        let mut lt = 0u8; // set if bytes < order
288
289        for i in 0..P384_SCALAR_SIZE {
290            let x = bytes[i];
291            let y = order[i];
292            gt |= ((x > y) as u8) & (!lt);
293            lt |= ((x < y) as u8) & (!gt);
294        }
295
296        if gt == 1 || (lt == 0 && gt == 0) {
297            // If scalar >= order, perform modular reduction
298            let mut borrow = 0u16;
299            let mut temp_bytes = *bytes;
300
301            for i in (0..P384_SCALAR_SIZE).rev() {
302                let diff = (temp_bytes[i] as i16) - (order[i] as i16) - (borrow as i16);
303                if diff < 0 {
304                    temp_bytes[i] = (diff + 256) as u8;
305                    borrow = 1;
306                } else {
307                    temp_bytes[i] = diff as u8;
308                    borrow = 0;
309                }
310            }
311
312            *bytes = temp_bytes;
313        }
314
315        // Check for zero after reduction
316        if bytes.iter().all(|&b| b == 0) {
317            return Err(Error::param(
318                "P-384 Scalar",
319                "Reduction resulted in zero scalar",
320            ));
321        }
322
323        Ok(())
324    }
325
326    // Helper constants
327    // The curve order n for P-384 in little-endian limbs
328    // n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973
329    const N_LIMBS: [u32; 12] = [
330        0xCCC5_2973,
331        0xECEC_196A,
332        0x48B0_A77A,
333        0x581A_0DB2,
334        0xF437_2DDF,
335        0xC763_4D81,
336        0xFFFF_FFFF,
337        0xFFFF_FFFF,
338        0xFFFF_FFFF,
339        0xFFFF_FFFF,
340        0xFFFF_FFFF,
341        0xFFFF_FFFF,
342    ];
343
344    /// constant-time compare:  a ≥ b ?
345    #[inline(always)]
346    fn geq(a: &[u32; 12], b: &[u32; 12]) -> bool {
347        for i in (0..12).rev() {
348            if a[i] > b[i] {
349                return true;
350            }
351            if a[i] < b[i] {
352                return false;
353            }
354        }
355        true // equal
356    }
357
358    /// a ← a − b   (little-endian limbs), ignores final borrow
359    #[inline(always)]
360    fn sub_in_place(a: &mut [u32; 12], b: &[u32; 12]) {
361        let mut borrow = 0u64;
362        for i in 0..12 {
363            let tmp = (a[i] as u64).wrapping_sub(b[i] as u64).wrapping_sub(borrow);
364            a[i] = tmp as u32;
365            borrow = (tmp >> 63) & 1;
366        }
367    }
368}