Skip to main content

dcrypt_algorithms/ec/p224/
scalar.rs

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