Skip to main content

tecdsa_core/arith/
sbig.rs

1//! Signed big integers over `BoxedUint` (sign + magnitude), constant-time where it matters.
2use alloc::vec::Vec;
3use crypto_bigint::{
4    BoxedUint, Choice, ConcatenatingMul, NonZero, RandomBits, RandomMod, Resize,
5    ctutils::{CtEq, CtSelect},
6    rand_core::Rng,
7};
8use zeroize::Zeroize;
9
10use crate::error::{Error, Result};
11
12/// Signed integer `(-1)^neg · mag`. Zero is always stored with `neg = false`.
13#[derive(Clone)]
14pub struct SBig {
15    neg: Choice,
16    mag: BoxedUint,
17}
18
19impl PartialEq for SBig {
20    fn eq(&self, other: &Self) -> bool {
21        bool::from(self.neg.ct_eq(&other.neg))
22            && resized(&self.mag, other.mag.bits_precision())
23                == resized(&other.mag, self.mag.bits_precision())
24    }
25}
26impl Eq for SBig {}
27
28impl core::fmt::Debug for SBig {
29    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
30        f.write_str("SBig(***)")
31    }
32}
33
34impl Zeroize for SBig {
35    fn zeroize(&mut self) {
36        self.mag.zeroize();
37        self.neg = Choice::FALSE;
38    }
39}
40
41/// Interval `±2^n = {−2^{n−1}+1, …, 2^{n−1}}` (spec §10.2).
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub struct SRange {
44    bits: u32,
45}
46
47impl SRange {
48    /// The interval `±2^n`.
49    pub const fn pm_pow2(n: u32) -> Self {
50        Self { bits: n }
51    }
52    pub const fn bits(&self) -> u32 {
53        self.bits
54    }
55}
56
57impl SBig {
58    pub fn zero(bits: u32) -> Self {
59        Self {
60            neg: Choice::FALSE,
61            mag: BoxedUint::zero_with_precision(bits.max(1)),
62        }
63    }
64
65    /// Non-negative value.
66    pub fn from_uint(mag: BoxedUint) -> Self {
67        Self {
68            neg: Choice::FALSE,
69            mag,
70        }
71    }
72
73    pub fn from_i64(v: i64) -> Self {
74        let mag = BoxedUint::from(v.unsigned_abs());
75        Self {
76            neg: neg_flag(v),
77            mag,
78        }
79    }
80
81    pub fn is_neg(&self) -> Choice {
82        self.neg
83    }
84
85    pub fn mag(&self) -> &BoxedUint {
86        &self.mag
87    }
88
89    pub fn is_zero(&self) -> Choice {
90        self.mag.is_zero()
91    }
92
93    pub fn bits_precision(&self) -> u32 {
94        self.mag.bits_precision()
95    }
96
97    fn widen(&self, bits: u32) -> BoxedUint {
98        resized(&self.mag, bits)
99    }
100
101    fn from_parts(neg: Choice, mag: BoxedUint) -> Self {
102        // Canonical zero: never negative.
103        let neg = neg.and(mag.is_zero().not());
104        Self { neg, mag }
105    }
106
107    pub fn negate(&self) -> Self {
108        Self::from_parts(self.neg.not(), self.mag.clone())
109    }
110
111    pub fn add(&self, rhs: &Self) -> Self {
112        let bits = self.mag.bits_precision().max(rhs.mag.bits_precision()) + 64;
113        let a = self.widen(bits);
114        let b = rhs.widen(bits);
115        let same = self.neg.ct_eq(&rhs.neg);
116        let sum = a.wrapping_add(&b);
117        let (d_ab, a_lt_b) = a.underflowing_sub(&b);
118        let d_ba = b.wrapping_sub(&a);
119        // Opposite signs: |a| ≥ |b| keeps the sign of a, otherwise sign of b.
120        let diff = d_ab.ct_select(&d_ba, a_lt_b);
121        let diff_neg = self.neg.ct_select(&rhs.neg, a_lt_b);
122        let mag = diff.ct_select(&sum, same);
123        let neg = diff_neg.ct_select(&self.neg, same);
124        Self::from_parts(neg, mag)
125    }
126
127    pub fn sub(&self, rhs: &Self) -> Self {
128        self.add(&rhs.negate())
129    }
130
131    pub fn mul(&self, rhs: &Self) -> Self {
132        let mag = self.mag.concatenating_mul(&rhs.mag);
133        Self::from_parts(self.neg.ct_ne(&rhs.neg), mag)
134    }
135
136    /// Euclidean remainder in `[0, m)`.
137    pub fn rem_euclid(&self, m: &NonZero<BoxedUint>) -> BoxedUint {
138        let bits = self.mag.bits_precision().max(m.bits_precision());
139        let wm = Option::<NonZero<BoxedUint>>::from(resized(m.as_ref(), bits).to_nz())
140            .unwrap_or_else(|| m.clone());
141        let r = self.widen(bits).rem(&wm);
142        let neg_r = r.neg_mod(&wm);
143        let out = r.ct_select(&neg_r, self.neg);
144        out.resize(m.bits_precision())
145    }
146
147    /// Checks `|z| ≤ 2^{n−1}` (public data).
148    pub fn in_range(&self, range: SRange) -> bool {
149        if range.bits == 0 {
150            return bool::from(self.is_zero());
151        }
152        let bound = BoxedUint::one_with_precision(range.bits.max(self.mag.bits_precision()) + 1)
153            .shl(range.bits - 1);
154        let m = self.widen(bound.bits_precision());
155        // positive: |z| ≤ 2^{n−1}; negative: |z| ≤ 2^{n−1}−1
156        if bool::from(self.neg) {
157            m.cmp_vartime(&bound) == core::cmp::Ordering::Less
158        } else {
159            m.cmp_vartime(&bound) != core::cmp::Ordering::Greater
160        }
161    }
162
163    /// Uniform sample from `±2^n`: `u − 2^{n−1} + 1` with `u ← [0, 2^n)`.
164    pub fn random_in<R: Rng + ?Sized>(rng: &mut R, range: SRange) -> Self {
165        let n = range.bits.max(1);
166        let prec = n + 64;
167        let u = match BoxedUint::try_random_bits_with_precision(rng, n, prec) {
168            Ok(u) => u,
169            Err(_) => BoxedUint::zero_with_precision(prec),
170        };
171        let shift = BoxedUint::one_with_precision(prec).shl(n - 1);
172        let one = BoxedUint::one_with_precision(prec);
173        // v = u + 1 − 2^{n−1}; v ∈ [−2^{n−1}+1, 2^{n−1}]
174        let up = u.wrapping_add(&one);
175        let (d, under) = up.underflowing_sub(&shift);
176        let d_neg = shift.wrapping_sub(&up);
177        let mag = d.ct_select(&d_neg, under);
178        Self::from_parts(under, mag)
179    }
180
181    /// Uniform sample from an interval of `size` consecutive integers `{−size/2+1, …, size/2}`
182    /// (`size` even, at least 2), i.e. the paper's `±size`.
183    pub fn random_in_size<R: Rng + ?Sized>(rng: &mut R, size: &BoxedUint) -> Result<Self> {
184        let prec = size.bits_precision() + 64;
185        let size_w = resized(size, prec);
186        let nz = Option::<NonZero<BoxedUint>>::from(size_w.to_nz()).ok_or(Error::OutOfRange)?;
187        let u = BoxedUint::random_mod_vartime(rng, &nz);
188        let half = size_w.shr_vartime(1).ok_or(Error::OutOfRange)?;
189        Ok(Self::from_uint(u)
190            .add(&Self::from_i64(1))
191            .sub(&Self::from_uint(half)))
192    }
193
194    /// Encodes as sign byte `0x00/0x01` followed by `⌈(n−1)/8⌉` big-endian magnitude bytes (spec §10.2).
195    pub fn to_bytes(&self, range: SRange) -> Result<Vec<u8>> {
196        if !self.in_range(range) {
197            return Err(Error::OutOfRange);
198        }
199        let len = (range.bits.saturating_sub(1) as usize).div_ceil(8).max(1);
200        let be = self.mag.to_be_bytes();
201        let mut out = Vec::with_capacity(1 + len);
202        out.push(u8::from(bool::from(self.neg)));
203        if be.len() >= len {
204            out.extend_from_slice(&be[be.len() - len..]);
205        } else {
206            out.resize(1 + len - be.len(), 0);
207            out.extend_from_slice(&be);
208        }
209        Ok(out)
210    }
211
212    pub fn from_bytes(bytes: &[u8], range: SRange) -> Result<Self> {
213        let len = (range.bits.saturating_sub(1) as usize).div_ceil(8).max(1);
214        if bytes.len() != 1 + len {
215            return Err(Error::InvalidEncoding);
216        }
217        let neg = match bytes[0] {
218            0 => false,
219            1 => true,
220            _ => return Err(Error::InvalidEncoding),
221        };
222        let mag = BoxedUint::from_be_slice(&bytes[1..], (len as u32 * 8).max(64))
223            .map_err(|_| Error::InvalidEncoding)?;
224        if neg && bool::from(mag.is_zero()) {
225            return Err(Error::InvalidEncoding);
226        }
227        let v = Self {
228            neg: Choice::from_u8_lsb(u8::from(neg)),
229            mag,
230        };
231        if !v.in_range(range) {
232            return Err(Error::OutOfRange);
233        }
234        Ok(v)
235    }
236}
237
238fn neg_flag(v: i64) -> Choice {
239    Choice::from_u8_lsb(u8::from(v < 0))
240}
241
242/// Copy of `x` with at least `bits` of precision (never narrows).
243pub(crate) fn resized(x: &BoxedUint, bits: u32) -> BoxedUint {
244    if x.bits_precision() >= bits {
245        x.clone()
246    } else {
247        Resize::resize(x.clone(), bits)
248    }
249}