Skip to main content

protocol/
scalar.rs

1//! Fixed-width secret scalar container.
2//!
3//! Protocol code should avoid passing variable-length scalar slices for secret
4//! material, because the length itself can become observable. This wrapper
5//! keeps scalar width fixed at the type level.
6
7use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
8
9/// Fixed-width scalar represented as little-endian `u64` limbs.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct SecretScalar<const LIMBS: usize> {
12    limbs: [u64; LIMBS],
13}
14
15impl<const LIMBS: usize> SecretScalar<LIMBS> {
16    /// Construct a scalar from its little-endian limb representation.
17    pub const fn new(limbs: [u64; LIMBS]) -> Self {
18        Self { limbs }
19    }
20
21    /// Borrow the underlying little-endian limbs.
22    pub fn as_limbs(&self) -> &[u64; LIMBS] {
23        &self.limbs
24    }
25
26    /// Consume the scalar and return the underlying limbs.
27    pub fn to_limbs(self) -> [u64; LIMBS] {
28        self.limbs
29    }
30
31    /// Total bit width of the scalar container.
32    pub const fn bit_len() -> usize {
33        64 * LIMBS
34    }
35
36    /// Return the bit at absolute big-endian index `i`.
37    ///
38    /// `i = 0` is the most-significant bit of the highest limb and
39    /// `i = bit_len() - 1` is the least-significant bit of limb 0.
40    pub fn bit_be(&self, i: usize) -> Choice {
41        debug_assert!(i < Self::bit_len());
42
43        let limb_from_msb = i / 64;
44        let bit_in_limb = 63 - (i % 64);
45        let limb_index = LIMBS - 1 - limb_from_msb;
46        Choice::from(((self.limbs[limb_index] >> bit_in_limb) & 1) as u8)
47    }
48}
49
50impl<const LIMBS: usize> Default for SecretScalar<LIMBS> {
51    fn default() -> Self {
52        Self { limbs: [0u64; LIMBS] }
53    }
54}
55
56impl<const LIMBS: usize> ConditionallySelectable for SecretScalar<LIMBS> {
57    fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
58        let mut limbs = [0u64; LIMBS];
59        for i in 0..LIMBS {
60            limbs[i] = u64::conditional_select(&a.limbs[i], &b.limbs[i], choice);
61        }
62        Self { limbs }
63    }
64
65    fn conditional_assign(&mut self, other: &Self, choice: Choice) {
66        for i in 0..LIMBS {
67            self.limbs[i].conditional_assign(&other.limbs[i], choice);
68        }
69    }
70
71    fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
72        for i in 0..LIMBS {
73            u64::conditional_swap(&mut a.limbs[i], &mut b.limbs[i], choice);
74        }
75    }
76}
77
78impl<const LIMBS: usize> ConstantTimeEq for SecretScalar<LIMBS> {
79    fn ct_eq(&self, other: &Self) -> Choice {
80        let mut acc = Choice::from(1u8);
81        for i in 0..LIMBS {
82            acc = acc & self.limbs[i].ct_eq(&other.limbs[i]);
83        }
84        acc
85    }
86}