diff --git a/issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md b/issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md index 4a8f4b2fbe..224501006c 100644 --- a/issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md +++ b/issues/toyos-crypto-is-in-the-tree-on-par-with-ring-and-secure.md @@ -423,8 +423,14 @@ Not consumers: ## Weaknesses until the exits hold -- Owned secret storage is wiped on drop. Compiler copies, registers, spills - and caller-held bytes are not wiped. +- Erasure is what `lib/crypto/src/secret.rs`'s contract states: secret + owners, each key agreement's shared value and named workspaces are + erased. Since the parity work's 5x51 field (#992), 2^255 - 19 field + elements, NIST residues and curve points are not erased one by one. The + owner approved that on condition of a measured benefit (M1: 0.80–0.94 of + per-temporary erasure on the T14, 0.79–0.88 on the AMD bench) and a + reviewed contract. Compiler + copies, registers, spills and caller-held bytes are not wiped. - **Owner**: the orchestrator. **Exit**: stage 4 states what the shipped targets keep, and a consumer holding a long-lived key stores it under a wiping owner or a Key Locker handle. diff --git a/lib/crypto/src/ed25519.rs b/lib/crypto/src/ed25519.rs index 019709acaf..69e1b8ea43 100644 --- a/lib/crypto/src/ed25519.rs +++ b/lib/crypto/src/ed25519.rs @@ -4,19 +4,24 @@ //! Signing (feature `sign`) uses fixed-schedule field/scalar arithmetic and //! complete Edwards addition; neither tables nor branches depend on secrets. -use crate::{Error, ct, field25519::F, hash::Sha512, scalar25519::S}; +use crate::{ + Error, ct, + field25519::{Multiply, Tight}, + hash::Sha512, + scalar25519::S, +}; #[cfg(feature = "sign")] use crate::{Random, secret::Secret}; /// 2d for the curve's d = -121665/121666 (RFC 8032 section 5.1). -const D2: F = F::from_bytes(&[ +const D2: Tight = Tight::from_bytes(&[ 0x59, 0xf1, 0xb2, 0x26, 0x94, 0x9b, 0xd6, 0xeb, 0x56, 0xb1, 0x83, 0x82, 0x9a, 0x14, 0xe0, 0x00, 0x30, 0xd1, 0xf3, 0xee, 0xf2, 0x80, 0x8e, 0x19, 0xe7, 0xfc, 0xdf, 0x56, 0xdc, 0xd9, 0x06, 0x24, ]); -const D: F = F::from_bytes(&[ +const D: Tight = Tight::from_bytes(&[ 0xa3, 0x78, 0x59, 0x13, 0xca, 0x4d, 0xeb, 0x75, 0xab, 0xd8, 0x41, 0x41, 0x4d, 0x0a, 0x70, 0x00, 0x98, 0xe8, 0x79, 0x77, 0x79, 0x40, 0xc7, 0x8c, 0x73, 0xfe, 0x6f, 0x2b, 0xee, 0x6c, 0x03, 0x52, ]); -const SQRT_M1: F = F::from_bytes(&[ +const SQRT_M1: Tight = Tight::from_bytes(&[ 0xb0, 0xa0, 0x0e, 0x4a, 0x27, 0x1b, 0xee, 0xc4, 0x78, 0xe4, 0x2f, 0xad, 0x06, 0x18, 0x43, 0x2f, 0xa7, 0xd7, 0xfb, 0x3d, 0x99, 0x00, 0x4d, 0x2b, 0x0b, 0xdf, 0xc1, 0x4f, 0x80, 0x24, 0x83, 0x2b, ]); @@ -25,66 +30,66 @@ const B_ENCODED: [u8; 32] = [ 0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, ]; -const B_X: F = F::from_bytes(&[ +const B_X: Tight = Tight::from_bytes(&[ 0x1a, 0xd5, 0x25, 0x8f, 0x60, 0x2d, 0x56, 0xc9, 0xb2, 0xa7, 0x25, 0x95, 0x60, 0xc7, 0x2c, 0x69, 0x5c, 0xdc, 0xd6, 0xfd, 0x31, 0xe2, 0xa4, 0xc0, 0xfe, 0x53, 0x6e, 0xcd, 0xd3, 0x36, 0x69, 0x21, ]); -const B_Y: F = F::from_bytes(&B_ENCODED); +const B_Y: Tight = Tight::from_bytes(&B_ENCODED); /// x * y of B. -const B_T: F = F::from_bytes(&[ +const B_T: Tight = Tight::from_bytes(&[ 0xa3, 0xdd, 0xb7, 0xa5, 0xb3, 0x8a, 0xde, 0x6d, 0xf5, 0x52, 0x51, 0x77, 0x80, 0x9f, 0xf0, 0x20, 0x7d, 0xe3, 0xab, 0x64, 0x8e, 0x4e, 0xea, 0x66, 0x65, 0x76, 0x8b, 0xd7, 0x0f, 0x5f, 0x87, 0x67, ]); +/// Extended coordinates (X : Y : Z : T) with x = X/Z, y = Y/Z, xy = T/Z. +#[derive(Clone, Copy)] struct Point { - x: F, - y: F, - z: F, - t: F, + x: Tight, + y: Tight, + z: Tight, + t: Tight, } impl Point { fn identity() -> Self { Self { - x: F::zero(), - y: F::one(), - z: F::one(), - t: F::zero(), + x: Tight::ZERO, + y: Tight::ONE, + z: Tight::ONE, + t: Tight::ZERO, } } fn base() -> Self { Self { x: B_X, y: B_Y, - z: F::one(), + z: Tight::ONE, t: B_T, } } + /// RFC 8032 section 5.1.3. Refusal branches depend on the public encoding. fn decode(input: &[u8; 32]) -> Result { let mut b = *input; let sign = u64::from(b[31] >> 7); b[31] &= 127; - let y = F::from_bytes(&b); + let y = Tight::from_bytes(&b); if !ct::equal(&b, &y.bytes()) { return Err(Error::InvalidPoint); } let y2 = y.square(); - let u = y2.sub(&F::one()); - let v = D.mul(&y2).add(&F::one()); + let u = y2.sub(&Tight::ONE); + let v = D.mul(&y2).add(&Tight::ONE).carry(); let q = u.mul(&v.invert()); - let mut exponent = [0xff; 32]; - exponent[0] = 0xfe; - exponent[31] = 0x0f; - let root = q.pow(&exponent, 252); + let root = q.mul(&q.pow_p58()); let alt = root.mul(&SQRT_M1); - let root = F::select(&alt, &root, root.square().eq(&q)); - if root.square().eq(&q) == 0 || (root.eq(&F::zero()) == 1 && sign == 1) { + let root = Tight::select(&alt, &root, root.square().eq(&q)); + if root.square().eq(&q) == 0 || (root.is_zero() == 1 && sign == 1) { return Err(Error::InvalidPoint); } - let x = F::select(&root, &root.neg(), u64::from(root.bytes()[0] & 1) ^ sign); + let x = Tight::select(&root, &root.neg().carry(), root.is_negative() ^ sign); let t = x.mul(&y); Ok(Self { x, y, - z: F::one(), + z: Tight::ONE, t, }) } @@ -93,15 +98,15 @@ impl Point { let inverse = self.z.invert(); let x = self.x.mul(&inverse); let mut y = self.y.mul(&inverse).bytes(); - y[31] |= (x.bytes()[0] & 1) << 7; + y[31] |= (x.is_negative() as u8) << 7; y } + /// RFC 8032 section 5.1.4's addition, complete on the curve. fn add(&self, q: &Self) -> Self { let a = self.y.sub(&self.x).mul(&q.y.sub(&q.x)); let b = self.y.add(&self.x).mul(&q.y.add(&q.x)); let c = self.t.mul(&q.t).mul(&D2); - let zz = self.z.mul(&q.z); - let d = zz.add(&zz); + let d = self.z.mul(&q.z.add(&q.z)); let e = b.sub(&a); let f = d.sub(&c); let g = d.add(&c); @@ -113,18 +118,35 @@ impl Point { z: f.mul(&g), } } + /// RFC 8032 section 5.1.4's doubling. + fn double(&self) -> Self { + let a = self.x.square(); + let b = self.y.square(); + let c = self.z.square().mul_small(2); + let h = a.add(&b).carry(); + let e = h.sub(&self.x.add(&self.y).square()); + let g = a.sub(&b).carry(); + let f = c.add(&g); + Self { + x: e.mul(&f), + y: g.mul(&h), + t: e.mul(&h), + z: f.mul(&g), + } + } fn select(a: &Self, b: &Self, bit: u64) -> Self { Self { - x: F::select(&a.x, &b.x, bit), - y: F::select(&a.y, &b.y, bit), - z: F::select(&a.z, &b.z, bit), - t: F::select(&a.t, &b.t, bit), + x: Tight::select(&a.x, &b.x, bit), + y: Tight::select(&a.y, &b.y, bit), + z: Tight::select(&a.z, &b.z, bit), + t: Tight::select(&a.t, &b.t, bit), } } + /// scalar * self, on a fixed schedule. fn mul(&self, scalar: &[u8; 32]) -> Self { let mut r = Self::identity(); for i in (0..256).rev() { - let double = r.add(&r); + let double = r.double(); let added = double.add(self); r = Self::select(&double, &added, u64::from((scalar[i / 8] >> (i % 8)) & 1)); } @@ -134,12 +156,11 @@ impl Point { self.x.mul(&q.z).eq(&q.x.mul(&self.z)) & self.y.mul(&q.z).eq(&q.y.mul(&self.z)) } fn small_order(&self) -> bool { - let p = self.add(self); - let p = p.add(&p); - let p = p.add(&p); - p.eq(&Self::identity()) == 1 + self.double().double().double().eq(&Self::identity()) == 1 } } + + pub struct PublicKey { encoded: [u8; 32], point: Point, @@ -245,9 +266,14 @@ mod tests { for (a, b) in [(&decoded.x, &base.x), (&decoded.y, &base.y), (&decoded.t, &base.t)] { assert_eq!(a.bytes(), b.bytes()); } - assert_eq!(D.add(&D).bytes(), D2.bytes()); - let d = F::small(121665).neg().mul(&F::small(121666).invert()); + assert_eq!(D.add(&D).carry().bytes(), D2.bytes()); + let small = |x: u32| { + let mut b = [0; 32]; + b[..4].copy_from_slice(&x.to_le_bytes()); + Tight::from_bytes(&b) + }; + let d = small(121665).neg().mul(&small(121666).invert()); assert_eq!(d.bytes(), D.bytes()); - assert_eq!(SQRT_M1.square().bytes(), F::one().neg().bytes()); + assert_eq!(SQRT_M1.square().bytes(), Tight::ONE.neg().carry().bytes()); } } diff --git a/lib/crypto/src/field25519.rs b/lib/crypto/src/field25519.rs index 0e25ee987b..f4d10d708b 100644 --- a/lib/crypto/src/field25519.rs +++ b/lib/crypto/src/field25519.rs @@ -1,125 +1,303 @@ -//! Arithmetic modulo 2^255-19. Five radix-2^51 limbs; products fit u128. -//! Every operation has a fixed schedule. Encodings and equality are public at -//! verification boundaries. Arithmetic storage is erased on drop. +//! Arithmetic modulo p = 2^255 - 19 in five radix-2^51 limbs with u128 +//! products (Bernstein, "Curve25519: new Diffie-Hellman speed records", PKC +//! 2006). The limb bounds are types: a [`Tight`] element is what a +//! multiplication or a carry leaves, a [`Loose`] one the sum or difference of +//! two tight ones. Multiplication takes either; addition and subtraction take +//! tight operands. Every operation has a fixed schedule and no branch or +//! address that depends on an element. Encodings and equality are public at +//! verification boundaries. +//! +//! Elements are `Copy` values and are not erased: an owner of secret state +//! erases its own storage (see `secret`). -use crate::{ct, secret::wipe64}; -const M: u64 = (1 << 51) - 1; -pub(crate) struct F([u64; 5]); -impl Drop for F { - fn drop(&mut self) { - wipe64(&mut self.0) - } +use crate::ct; + +const MASK: u64 = (1 << 51) - 1; + +/// Limbs 0, 2, 3 and 4 below 2^51, limb 1 below 2^51 + 2^19. +#[derive(Clone, Copy)] +pub(crate) struct Tight([u64; 5]); + +/// Every limb below 2^53. +#[derive(Clone, Copy)] +pub(crate) struct Loose([u64; 5]); + +/// A multiplication operand: either bound. +pub(crate) trait Operand { + fn limbs(&self) -> &[u64; 5]; } -impl F { - pub(crate) fn zero() -> Self { - Self([0; 5]) - } - pub(crate) fn one() -> Self { - Self([1, 0, 0, 0, 0]) + +impl Operand for Tight { + #[inline(always)] + fn limbs(&self) -> &[u64; 5] { + &self.0 } - pub(crate) fn small(x: u32) -> Self { - Self([u64::from(x), 0, 0, 0, 0]) +} + +impl Operand for Loose { + #[inline(always)] + fn limbs(&self) -> &[u64; 5] { + &self.0 } +} + +/// 2p, limb by limb: above every tight limb, so a subtraction never borrows. +const TWO_P: [u64; 5] = [2 * (MASK - 18), 2 * MASK, 2 * MASK, 2 * MASK, 2 * MASK]; + +#[inline(always)] +fn wide(a: u64, b: u64) -> u128 { + u128::from(a).wrapping_mul(u128::from(b)) +} + +/// A column of products. The limb bounds keep every sum below 2^115, so the +/// wrapping additions are exact and carry no overflow check. +macro_rules! column { + ($first:expr $(, $rest:expr)*) => { $first $(.wrapping_add($rest))* }; +} + +/// Columns below 2^115 into tight limbs: one carry pass, the top carry times +/// 19 into limb 0 (2^255 = 19 mod p), and one more carry into limb 1. +#[inline(always)] +fn carry(v: [u128; 5]) -> Tight { + let [v0, v1, v2, v3, v4] = v; + let v1 = v1.wrapping_add(v0 >> 51); + let v2 = v2.wrapping_add(v1 >> 51); + let v3 = v3.wrapping_add(v2 >> 51); + let v4 = v4.wrapping_add(v3 >> 51); + let r0 = u128::from(v0 as u64 & MASK).wrapping_add((v4 >> 51).wrapping_mul(19)); + let r1 = (v1 as u64 & MASK).wrapping_add((r0 >> 51) as u64); + Tight([ + r0 as u64 & MASK, + r1, + v2 as u64 & MASK, + v3 as u64 & MASK, + v4 as u64 & MASK, + ]) +} + +impl Tight { + pub(crate) const ZERO: Self = Self([0; 5]); + pub(crate) const ONE: Self = Self([1, 0, 0, 0, 0]); + /// The low 255 bits of a little-endian encoding. pub(crate) const fn from_bytes(b: &[u8; 32]) -> Self { - let mut v = [0u64; 5]; + let mut w = [0u64; 4]; let mut i = 0; - while i < 255 { - v[i / 51] |= (((b[i / 8] >> (i % 8)) & 1) as u64) << (i % 51); + while i < 32 { + w[i / 8] |= (b[i] as u64) << (8 * (i % 8)); i += 1; } - Self(v) - } - fn carry(v: &mut [u128; 5]) -> Self { - // Products start at <=77*(2^51-1)^2; three passes leave every limb <2^51. - for _ in 0..3 { - for i in 0..4 { - let c = v[i] >> 51; - v[i] &= u128::from(M); - v[i + 1] += c; - } - let c = v[4] >> 51; - v[4] &= u128::from(M); - v[0] += c * 19; - } - let out = Self(core::array::from_fn(|i| v[i] as u64)); - // Volatile erasure covers the explicit wide product workspace. - for x in v { - unsafe { core::ptr::write_volatile(x, 0) } - } - core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); - out + Self([ + w[0] & MASK, + (w[0] >> 51 | w[1] << 13) & MASK, + (w[1] >> 38 | w[2] << 26) & MASK, + (w[2] >> 25 | w[3] << 39) & MASK, + (w[3] >> 12) & MASK, + ]) } - pub(crate) fn add(&self, b: &Self) -> Self { - Self::carry(&mut core::array::from_fn(|i| { - u128::from(self.0[i]) + u128::from(b.0[i]) - })) + + #[cfg(test)] + pub(crate) fn from_limbs(limbs: [u64; 5]) -> Self { + Self(limbs) } - pub(crate) fn sub(&self, b: &Self) -> Self { - Self::carry(&mut core::array::from_fn(|i| { - u128::from(self.0[i]) + 2 * u128::from(if i == 0 { M - 18 } else { M }) - - u128::from(b.0[i]) - })) + + pub(crate) fn add(&self, b: &Tight) -> Loose { + let (a, b) = (&self.0, &b.0); + Loose([ + a[0].wrapping_add(b[0]), + a[1].wrapping_add(b[1]), + a[2].wrapping_add(b[2]), + a[3].wrapping_add(b[3]), + a[4].wrapping_add(b[4]), + ]) } - pub(crate) fn neg(&self) -> Self { - Self::zero().sub(self) + + /// self + 2p - b, limb by limb. + pub(crate) fn sub(&self, b: &Tight) -> Loose { + let (a, b) = (&self.0, &b.0); + Loose([ + a[0].wrapping_add(TWO_P[0]).wrapping_sub(b[0]), + a[1].wrapping_add(TWO_P[1]).wrapping_sub(b[1]), + a[2].wrapping_add(TWO_P[2]).wrapping_sub(b[2]), + a[3].wrapping_add(TWO_P[3]).wrapping_sub(b[3]), + a[4].wrapping_add(TWO_P[4]).wrapping_sub(b[4]), + ]) } - pub(crate) fn mul(&self, b: &Self) -> Self { - let mut v = [0u128; 5]; - for i in 0..5 { - for j in 0..5 { - let k = i + j; - v[k % 5] += - u128::from(self.0[i]) * u128::from(b.0[j]) * if k >= 5 { 19 } else { 1 }; - } + + pub(crate) fn neg(&self) -> Loose { + Self::ZERO.sub(self) + } + + /// self^(2^k), for k >= 1. + pub(crate) fn square_times(&self, k: u32) -> Tight { + debug_assert!(k >= 1); + let mut r = self.square(); + for _ in 1..k { + r = r.square(); } - Self::carry(&mut v) + r + } + + /// self^(2^250 - 1) and self^11, the shared head of the inversion and + /// square-root chains (Bernstein 2006, section 5). + fn pow_2_250_1(&self) -> (Tight, Tight) { + let z2 = self.square(); + let z9 = z2.square_times(2).mul(self); + let z11 = z9.mul(&z2); + let z_5_0 = z11.square().mul(&z9); + let z_10_0 = z_5_0.square_times(5).mul(&z_5_0); + let z_20_0 = z_10_0.square_times(10).mul(&z_10_0); + let z_40_0 = z_20_0.square_times(20).mul(&z_20_0); + let z_50_0 = z_40_0.square_times(10).mul(&z_10_0); + let z_100_0 = z_50_0.square_times(50).mul(&z_50_0); + let z_200_0 = z_100_0.square_times(100).mul(&z_100_0); + let z_250_0 = z_200_0.square_times(50).mul(&z_50_0); + (z_250_0, z11) + } + + /// self^(p - 2) = self^(2^255 - 21): 254 squarings and 11 multiplications. + pub(crate) fn invert(&self) -> Tight { + let (z_250_0, z11) = self.pow_2_250_1(); + z_250_0.square_times(5).mul(&z11) } - pub(crate) fn square(&self) -> Self { - self.mul(self) + + /// self^((p - 5) / 8) = self^(2^252 - 3), for square roots. + pub(crate) fn pow_p58(&self) -> Tight { + let (z_250_0, _) = self.pow_2_250_1(); + z_250_0.square_times(2).mul(self) } + pub(crate) fn select(a: &Self, b: &Self, bit: u64) -> Self { - Self(core::array::from_fn(|i| ct::select(a.0[i], b.0[i], bit))) - } - pub(crate) fn pow(&self, e: &[u8; 32], bits: usize) -> Self { - let mut r = Self::one(); - for i in (0..bits).rev() { - let square = r.square(); - let product = square.mul(self); - r = Self::select(&square, &product, u64::from((e[i / 8] >> (i % 8)) & 1)); - } - r + let m = ct::mask(bit); + let (a, b) = (&a.0, &b.0); + Self([ + a[0] ^ ((a[0] ^ b[0]) & m), + a[1] ^ ((a[1] ^ b[1]) & m), + a[2] ^ ((a[2] ^ b[2]) & m), + a[3] ^ ((a[3] ^ b[3]) & m), + a[4] ^ ((a[4] ^ b[4]) & m), + ]) } - pub(crate) fn invert(&self) -> Self { - let mut e = [0xff; 32]; - e[0] = 0xeb; - e[31] = 0x7f; - self.pow(&e, 255) + + /// Exchanges a and b under a mask of all ones, and leaves them under zero. + pub(crate) fn swap(a: &mut Self, b: &mut Self, mask: u64) { + for i in 0..5 { + let t = (a.0[i] ^ b.0[i]) & mask; + a.0[i] ^= t; + b.0[i] ^= t; + } } + + /// The canonical little-endian encoding. pub(crate) fn bytes(&self) -> [u8; 32] { - let n = Self::carry(&mut self.0.map(u128::from)); - let mut t = [0u64; 5]; - let mut borrow = 0; - for (i, word) in t.iter_mut().enumerate() { - let p = if i == 0 { M - 18 } else { M }; - let d = (1u128 << 51) + u128::from(n.0[i]) - u128::from(p) - u128::from(borrow); - *word = d as u64 & M; - borrow = 1 - ((d >> 51) as u64); - } - let reduced = Self(t); - let value = Self::select(&reduced, &n, borrow); + let h = &self.0; + // q = floor((h + 19) / 2^255), which is 1 exactly when h >= p for a + // tight h < 2p. + let mut q = h[0].wrapping_add(19) >> 51; + q = h[1].wrapping_add(q) >> 51; + q = h[2].wrapping_add(q) >> 51; + q = h[3].wrapping_add(q) >> 51; + q = h[4].wrapping_add(q) >> 51; + // h - q p = h + 19 q - q 2^255, with bit 255 dropped. + let h0 = h[0].wrapping_add(q.wrapping_mul(19)); + let h1 = h[1].wrapping_add(h0 >> 51); + let h2 = h[2].wrapping_add(h1 >> 51); + let h3 = h[3].wrapping_add(h2 >> 51); + let h4 = h[4].wrapping_add(h3 >> 51); + let l = [h0 & MASK, h1 & MASK, h2 & MASK, h3 & MASK, h4 & MASK]; + let words = [ + l[0] | l[1] << 51, + l[1] >> 13 | l[2] << 38, + l[2] >> 26 | l[3] << 25, + l[3] >> 39 | l[4] << 12, + ]; let mut out = [0; 32]; - for i in 0..255 { - out[i / 8] |= ((value.0[i / 51] >> (i % 51)) as u8 & 1) << (i % 8) + for (i, word) in words.iter().enumerate() { + out[8 * i..8 * i + 8].copy_from_slice(&word.to_le_bytes()); } out } + + /// One when the canonical encoding's low bit is set. + pub(crate) fn is_negative(&self) -> u64 { + u64::from(self.bytes()[0] & 1) + } + + /// One when both encode the same element. pub(crate) fn eq(&self, b: &Self) -> u64 { - let mut a = self.bytes(); - let mut bb = b.bytes(); - let result = u64::from(ct::equal(&a, &bb)); - crate::secret::wipe(&mut a); - crate::secret::wipe(&mut bb); - result + u64::from(ct::equal(&self.bytes(), &b.bytes())) + } + + pub(crate) fn is_zero(&self) -> u64 { + self.eq(&Self::ZERO) + } +} + +impl Loose { + /// The same element, carried tight. + pub(crate) fn carry(&self) -> Tight { + let v = &self.0; + carry([v[0].into(), v[1].into(), v[2].into(), v[3].into(), v[4].into()]) } } + +/// Multiplications, each leaving a tight element. +pub(crate) trait Multiply: Operand { + /// The 25 products, with b's limbs that wrap past 2^255 premultiplied by + /// 19. + #[inline(always)] + fn mul(&self, b: &impl Operand) -> Tight { + let (a, b) = (self.limbs(), b.limbs()); + let b1 = b[1].wrapping_mul(19); + let b2 = b[2].wrapping_mul(19); + let b3 = b[3].wrapping_mul(19); + let b4 = b[4].wrapping_mul(19); + let v = [ + column!(wide(a[0], b[0]), wide(a[1], b4), wide(a[2], b3), wide(a[3], b2), wide(a[4], b1)), + column!(wide(a[0], b[1]), wide(a[1], b[0]), wide(a[2], b4), wide(a[3], b3), wide(a[4], b2)), + column!(wide(a[0], b[2]), wide(a[1], b[1]), wide(a[2], b[0]), wide(a[3], b4), wide(a[4], b3)), + column!(wide(a[0], b[3]), wide(a[1], b[2]), wide(a[2], b[1]), wide(a[3], b[0]), wide(a[4], b4)), + column!(wide(a[0], b[4]), wide(a[1], b[3]), wide(a[2], b[2]), wide(a[3], b[1]), wide(a[4], b[0])), + ]; + carry(v) + } + + #[inline(always)] + fn square(&self) -> Tight { + square(self.limbs()) + } + + /// self * k for k < 2^17. + #[inline(always)] + fn mul_small(&self, k: u32) -> Tight { + let a = self.limbs(); + let k = u64::from(k); + let v = [wide(a[0], k), wide(a[1], k), wide(a[2], k), wide(a[3], k), wide(a[4], k)]; + carry(v) + } +} + +impl Multiply for Tight {} +impl Multiply for Loose {} + +/// The 15 distinct products of a square. +#[inline(always)] +fn square(a: &[u64; 5]) -> Tight { + let d0 = a[0].wrapping_mul(2); + let d1 = a[1].wrapping_mul(2); + let d2 = a[2].wrapping_mul(2); + let d3 = a[3].wrapping_mul(2); + let a3_19 = a[3].wrapping_mul(19); + let a4_19 = a[4].wrapping_mul(19); + let v = [ + column!(wide(a[0], a[0]), wide(d1, a4_19), wide(d2, a3_19)), + column!(wide(d0, a[1]), wide(a[3], a3_19), wide(d2, a4_19)), + column!(wide(d0, a[2]), wide(a[1], a[1]), wide(d3, a4_19)), + column!(wide(d0, a[3]), wide(d1, a[2]), wide(a[4], a4_19)), + column!(wide(d0, a[4]), wide(d1, a[3]), wide(a[2], a[2])), + ]; + carry(v) +} + +// Five words: zero is a valid element. +unsafe impl crate::secret::Zeroable for Tight {} diff --git a/lib/crypto/src/lib.rs b/lib/crypto/src/lib.rs index 848946d4f0..d7ec89880e 100644 --- a/lib/crypto/src/lib.rs +++ b/lib/crypto/src/lib.rs @@ -6,10 +6,10 @@ //! algorithm choices and CPU capabilities are public. Module contracts identify //! operations permitted to depend on public data. //! -//! Owned secret storage is overwritten with volatile writes on drop. This does -//! not promise erasure of compiler-created copies, registers or abandoned -//! storage after process termination. Constant-time source structure requires -//! validation of the compiler and hardware used to deploy it. +//! Secret owners and secret operations' workspaces are erased as `secret`'s +//! contract states; arithmetic intermediates, registers and compiler-made +//! copies are not. Constant-time source structure requires validation of the +//! compiler and hardware used to deploy it. #![no_std] #![deny(unsafe_op_in_unsafe_fn)] diff --git a/lib/crypto/src/montgomery.rs b/lib/crypto/src/montgomery.rs index c91dd67c76..71934f8a4d 100644 --- a/lib/crypto/src/montgomery.rs +++ b/lib/crypto/src/montgomery.rs @@ -2,12 +2,12 @@ //! words. Each modulus is a type, so a residue modulo one cannot meet a residue //! modulo another. Every operation on a residue has a fixed schedule and a //! masked final subtraction; inversion's exponent is public. Residues are -//! erased on drop and workspaces after use. +//! `Copy` intermediates, not erased (`secret`'s contract). #![allow( clippy::needless_range_loop, reason = "carry chains index parallel fixed-size arrays; iterator adaptors stay out of line in the shipped profile" )] -use crate::{ct, secret::wipe64}; +use crate::ct; use core::marker::PhantomData; /// An odd modulus p with 2^(64N-1) < p < 2^(64N), and its Montgomery @@ -30,11 +30,14 @@ pub(crate) const fn encode(x: &[u64; N], p: &[u64; N]) -> [u64; /// A residue modulo `M` in Montgomery form, below `M::P`. pub(crate) struct Residue, const N: usize>([u64; N], PhantomData); -impl, const N: usize> Drop for Residue { - fn drop(&mut self) { - wipe64(&mut self.0) +impl, const N: usize> Clone for Residue { + fn clone(&self) -> Self { + *self } } +impl, const N: usize> Copy for Residue {} +// Words and a zero-sized marker: zero is a valid residue. +unsafe impl, const N: usize> crate::secret::Zeroable for Residue {} impl, const N: usize> Residue { /// Words already in Montgomery form and below p: a compile-time constant @@ -58,16 +61,13 @@ impl, const N: usize> Residue { } /// An integer x < 2^(64N) < 2p, reduced once. pub(crate) fn from_wide(x: &[u64; N]) -> Self { - let mut v = *x; - let reduced = Self(reduce(&v, 0, &M::P), PhantomData); - wipe64(&mut v); - reduced.mul(&Self(M::R2, PhantomData)) + Self(reduce(x, 0, &M::P), PhantomData).mul(&Self(M::R2, PhantomData)) } fn from_reduced(x: &[u64; N]) -> Self { Self(*x, PhantomData).mul(&Self(M::R2, PhantomData)) } /// The canonical integer this residue represents. - pub(crate) fn to_integer(&self) -> [u64; N] { + pub(crate) fn integer(&self) -> [u64; N] { let one = Self(one(), PhantomData); self.mul(&one).0 } @@ -77,9 +77,7 @@ impl, const N: usize> Residue { for i in 0..N { (v[i], carry) = carrying_add(self.0[i], b.0[i], carry); } - let result = Self(reduce(&v, u64::from(carry), &M::P), PhantomData); - wipe64(&mut v); - result + Self(reduce(&v, u64::from(carry), &M::P), PhantomData) } pub(crate) fn double(&self) -> Self { self.add(self) @@ -119,9 +117,7 @@ impl, const N: usize> Residue { // t < 2p after each step, so at most one of the two is set. high = u64::from(overflow) | u64::from(carry); } - let result = Self(reduce(&t, high, &M::P), PhantomData); - wipe64(&mut t); - result + Self(reduce(&t, high, &M::P), PhantomData) } pub(crate) fn square(&self) -> Self { self.mul(self) @@ -209,7 +205,6 @@ fn reduce(v: &[u64; N], high: u64, p: &[u64; N]) -> [u64; N] { for i in 0..N { out[i] = (v[i] & !m) | (d[i] & m); } - wipe64(&mut d); out } diff --git a/lib/crypto/src/nist.rs b/lib/crypto/src/nist.rs index 008b10abcc..f068049d49 100644 --- a/lib/crypto/src/nist.rs +++ b/lib/crypto/src/nist.rs @@ -10,7 +10,7 @@ use crate::{ Error, montgomery::{self, Modulus, Residue, below, from_be, to_be}, - secret::wipe64, + secret::{Workspace, wipe64}, }; /// A short Weierstrass curve y^2 = x^3 - 3x + b of prime order and cofactor @@ -85,6 +85,14 @@ pub(crate) struct Point, const N: usize> { y: Coord, z: Coord, } +impl, const N: usize> Clone for Point { + fn clone(&self) -> Self { + *self + } +} +impl, const N: usize> Copy for Point {} +// Three residues: zero is a valid point representation. +unsafe impl, const N: usize> crate::secret::Zeroable for Point {} impl, const N: usize> Point { fn identity() -> Self { Self { @@ -143,6 +151,7 @@ impl, const N: usize> Point { let out = Self::select(&out, self, q.z.is_zero()); Self::select(&out, q, self.z.is_zero()) } + /// scalar * self on a fixed schedule. pub(crate) fn mul(&self, scalar: &Scalar) -> Self { let mut r = Self::identity(); for i in (0..N * 64).rev() { @@ -152,28 +161,27 @@ impl, const N: usize> Point { } r } - fn affine(&self) -> Result<([u64; N], [u64; N]), Error> { + /// The affine coordinates as integers, into the caller's arrays. + fn affine(&self, x: &mut [u64; N], y: &mut [u64; N]) -> Result<(), Error> { if self.z.is_zero() == 1 { return Err(Error::InvalidPoint); } let inverse = self.z.invert(); let inv2 = inverse.square(); - Ok(( - self.x.mul(&inv2).to_integer(), - self.y.mul(&inv2.mul(&inverse)).to_integer(), - )) + *x = self.x.mul(&inv2).integer(); + *y = self.y.mul(&inv2.mul(&inverse)).integer(); + Ok(()) } /// The uncompressed SEC 1 encoding, 1 + 16N bytes. pub(crate) fn encode(&self, out: &mut [u8]) -> Result<(), Error> { if out.len() != 1 + N * 16 { return Err(Error::Length); } - let (mut x, mut y) = self.affine()?; + let (mut x, mut y) = ([0; N], [0; N]); + self.affine(&mut x, &mut y)?; out[0] = 4; to_be(&x, &mut out[1..1 + N * 8]); to_be(&y, &mut out[1 + N * 8..]); - wipe64(&mut x); - wipe64(&mut y); Ok(()) } /// An uncompressed SEC 1 point on the curve. @@ -203,17 +211,20 @@ impl, const N: usize> Point { z: Residue::one(), }) } - /// The x-coordinate of scalar * self into `output`, 8N bytes. + /// The x-coordinate of scalar * self into `output`, 8N bytes. The product + /// and its affine coordinates are the shared secret's, erased here once + /// `output` is written. pub(crate) fn agree(&self, scalar: &Scalar, output: &mut [u8]) { + let product = Workspace(self.mul(scalar)); + let mut coordinates = Workspace([[0; N]; 2]); + let [x, y] = &mut coordinates.0; // Both curves have prime order and cofactor one; validated nonzero // scalars and finite peers cannot multiply to infinity. - let (mut x, mut y) = self - .mul(scalar) - .affine() + product + .0 + .affine(x, y) .expect("validated prime-order point and nonzero scalar"); - to_be(&x, output); - wipe64(&mut x); - wipe64(&mut y); + to_be(x, output); } /// ECDSA verification of a DER signature over an 8N-byte digest. All /// inputs are public. @@ -226,11 +237,12 @@ impl, const N: usize> Point { let s = Scalar::::from_bytes(&s[..N * 8]).map_err(|_| Error::InvalidSignature)?; let z = ModN::::from_wide(&from_be(digest)); let inverse = ModN::::from_wide(&s.0).invert(); - let u = Scalar(z.mul(&inverse).to_integer(), core::marker::PhantomData); - let v = Scalar(ModN::::from_wide(&r.0).mul(&inverse).to_integer(), core::marker::PhantomData); + let u = Scalar(z.mul(&inverse).integer(), core::marker::PhantomData); + let v = Scalar(ModN::::from_wide(&r.0).mul(&inverse).integer(), core::marker::PhantomData); let result = Self::base().mul(&u).add(&self.mul(&v)); - let (x, _) = result.affine().map_err(|_| Error::InvalidSignature)?; - let x = ModN::::from_wide(&x).to_integer(); + let (mut x, mut y) = ([0; N], [0; N]); + result.affine(&mut x, &mut y).map_err(|_| Error::InvalidSignature)?; + let x = ModN::::from_wide(&x).integer(); if x == r.0 { Ok(()) } else { diff --git a/lib/crypto/src/secret.rs b/lib/crypto/src/secret.rs index 41ce17ef82..c6b47e736c 100644 --- a/lib/crypto/src/secret.rs +++ b/lib/crypto/src/secret.rs @@ -1,4 +1,36 @@ -//! Owned secrets. Volatile stores erase this allocation, not prior moves. +//! Erasure of secret storage. The crate's erasure contract: +//! +//! - **Erased: owners.** A [`Secret`], and every type that owns a key, seed, +//! expanded key, key schedule, scalar or shared secret, erases its own +//! storage when it drops. So do the hash, HMAC, Keccak, SHA-1 and Poly1305 +//! states. +//! - **Erased: key-agreement values.** Each key agreement writes its output +//! bytes into a `Secret` it owns, then erases the values they came from: +//! X25519's x2 / z2, and P-256/P-384's product point and affine +//! coordinates, each held in a [`Workspace`]. The output `Secret` is moved +//! into the returned `SharedSecret`, an owner. Not erased: the copies of +//! the output bytes that move leaves in the agreement's finished frame, and +//! the computation's arithmetic intermediates (below). +//! - **Erased: workspaces.** A secret operation's explicitly managed state is +//! a [`Workspace`], dropped when the operation returns, on every path out of +//! it, early returns and `?` included. The X25519 ladder's (x2, z2, x3, z3) +//! is one. A workspace erases its own storage; the copies the computation +//! works on in registers and other slots are not erased. +//! - **Erased per value, for now.** Ed25519 scalars (`scalar25519::S`, until +//! its reduction is rewritten) and ML-KEM polynomials (`mlkem::Poly`) still +//! erase every temporary on drop. +//! - **How.** Volatile stores of zero over the value's own storage, then a +//! sequentially consistent compiler fence. They erase that location and no +//! other. +//! - **Not erased.** Other arithmetic intermediates: 2^255 - 19 field +//! elements, NIST residues and both curves' points are `Copy` values. +//! Registers; spill slots and every other copy the compiler makes, by-value +//! moves and returns included; a moved value's earlier location; finished +//! stack frames; the caller's buffers and public outputs. These remain +//! until something overwrites them. +//! - **Panics.** An unwinding panic drops, and so erases, every owner and +//! workspace on the unwound frames. Under `panic = "abort"` nothing runs +//! after the panic. use core::sync::atomic::{Ordering, compiler_fence}; @@ -27,21 +59,26 @@ pub(crate) fn wipe64(words: &mut [u64]) { compiler_fence(Ordering::SeqCst); } -/// Element types whose all-zero bytes are a valid value. -pub(crate) trait Zeroable: Copy {} -impl Zeroable for u8 {} -impl Zeroable for u32 {} -impl Zeroable for u64 {} -impl Zeroable for u128 {} -impl Zeroable for [T; N] {} +/// Types whose all-zero bytes are a valid value. +/// +/// # Safety +/// The all-zero bit pattern must be a valid `Self`: no references, +/// `NonZero` fields or enums without a zero discriminant. +pub(crate) unsafe trait Zeroable: Copy {} +// Integers and arrays of zeroable elements are valid at zero. +unsafe impl Zeroable for u8 {} +unsafe impl Zeroable for u32 {} +unsafe impl Zeroable for u64 {} +unsafe impl Zeroable for u128 {} +unsafe impl Zeroable for [T; N] {} -/// Erases a whole array, 16 bytes per volatile store. A single volatile store +/// Erases a whole value, 16 bytes per volatile store. A single volatile store /// of a large array is lowered through a stack copy, a `memcpy` call. -pub(crate) fn erase(array: &mut [T; N]) { - let bytes = core::mem::size_of::<[T; N]>(); - let base = (array as *mut [T; N]).cast::(); +pub(crate) fn erase(value: &mut T) { + let bytes = core::mem::size_of::(); + let base = (value as *mut T).cast::(); for i in 0..bytes / 16 { - // The 16 bytes lie inside the live, exclusively borrowed array, and + // The 16 bytes lie inside the live, exclusively borrowed value, and // zero bytes are a valid `T`. unsafe { core::ptr::write_volatile(base.add(16 * i).cast::<[u8; 16]>(), [0; 16]) }; } @@ -60,3 +97,13 @@ impl Drop for Secret { erase(&mut self.0); } } + +/// An operation's secret working state, erased when the operation ends, on +/// every return path. +pub(crate) struct Workspace(pub(crate) T); + +impl Drop for Workspace { + fn drop(&mut self) { + erase(&mut self.0); + } +} diff --git a/lib/crypto/src/test_arithmetic.rs b/lib/crypto/src/test_arithmetic.rs index 3e68a557ee..f21d0a970f 100644 --- a/lib/crypto/src/test_arithmetic.rs +++ b/lib/crypto/src/test_arithmetic.rs @@ -1,6 +1,6 @@ //! Integer-oracle checks of reductions, carry chains and representation changes. use crate::{ - field25519::F, + field25519::{Multiply, Tight}, montgomery::{Modulus, Residue}, nist::{P256Field, P256Order, P384Field, P384Order}, scalar25519::S, @@ -42,27 +42,60 @@ fn field25519_carries_and_noncanonical_inputs_match_integer_oracle() { let p = (BigUint::from(1u8) << 255) - 19u8; let mut values = fixtures(&p, 32); values.extend([p.clone(), &p + 1u8, (BigUint::from(1u8) << 255) - 1u8]); + let int = |x: &Tight| BigUint::from_bytes_le(&x.bytes()); + let int_loose = |x: &crate::field25519::Loose| BigUint::from_bytes_le(&x.carry().bytes()); for (i, a) in values.iter().enumerate() { - let a_field = F::from_bytes(&fixed(a)); - assert_eq!(BigUint::from_bytes_le(&a_field.bytes()), a % &p); + let x = Tight::from_bytes(&fixed(a)); + let a = a % &p; + assert_eq!(int(&x), a); + assert_eq!(int(&x.square()), (&a * &a) % &p); + assert_eq!(int(&x.mul_small(121665)), (&a * 121665u32) % &p); + assert_eq!(int_loose(&x.neg()), (&p - &a) % &p); + if a != BigUint::from(0u8) { + assert_eq!(int(&x.mul(&x.invert())), BigUint::from(1u8)); + let e = (&p - 5u8) / 8u8; + assert_eq!(int(&x.pow_p58()), a.modpow(&e, &p)); + } for b in [&values[(i * 17 + 3) % values.len()], &values[2]] { - let b_field = F::from_bytes(&fixed(b)); - assert_eq!( - BigUint::from_bytes_le(&a_field.add(&b_field).bytes()), - (a + b) % &p - ); - assert_eq!( - BigUint::from_bytes_le(&a_field.sub(&b_field).bytes()), - ((a % &p) + &p - (b % &p)) % &p - ); - assert_eq!( - BigUint::from_bytes_le(&a_field.mul(&b_field).bytes()), - (a * b) % &p - ); + let y = Tight::from_bytes(&fixed(b)); + let b = b % &p; + let sum = x.add(&y); + let difference = x.sub(&y); + assert_eq!(int_loose(&sum), (&a + &b) % &p); + assert_eq!(int_loose(&difference), (&a + &p - &b) % &p); + assert_eq!(int(&x.mul(&y)), (&a * &b) % &p); + // Loose operands, at their largest, into every multiplication. + assert_eq!(int(&sum.mul(&difference)), ((&a + &b) * (&a + &p - &b)) % &p); + assert_eq!(int(&sum.square()), ((&a + &b) * (&a + &b)) % &p); + assert_eq!(int(&difference.mul_small(121665)), ((&a + &p - &b) * 121665u32) % &p); } } } +/// The largest limbs the operations reach, through every operation: the +/// bounds the types state are enough for the carries. +#[test] +fn field25519_largest_reachable_limbs_stay_within_their_bounds() { + let p = (BigUint::from(1u8) << 255) - 19u8; + let tight = Tight::from_limbs([(1 << 51) - 1, (1 << 51) + (1 << 19) - 1, (1 << 51) - 1, (1 << 51) - 1, (1 << 51) - 1]); + let value = |l: &[u64; 5]| l.iter().enumerate().fold(BigUint::from(0u8), |v, (i, &x)| v + (BigUint::from(x) << (51 * i))); + let t = value(crate::field25519::Operand::limbs(&tight)) % &p; + let loose = tight.add(&tight); + let l = value(crate::field25519::Operand::limbs(&loose)) % &p; + assert_eq!(BigUint::from_bytes_le(&tight.bytes()), t); + assert_eq!(BigUint::from_bytes_le(&loose.mul(&loose).bytes()), (&l * &l) % &p); + assert_eq!(BigUint::from_bytes_le(&loose.square().bytes()), (&l * &l) % &p); + assert_eq!(BigUint::from_bytes_le(&tight.sub(&tight).carry().bytes()), BigUint::from(0u8)); + assert_eq!(BigUint::from_bytes_le(&loose.mul_small(121665).bytes()), (&l * 121665u32) % &p); + let widest = tight.sub(&Tight::ZERO); + let w = value(crate::field25519::Operand::limbs(&widest)) % &p; + assert_eq!(BigUint::from_bytes_le(&widest.mul(&loose).bytes()), (&w * &l) % &p); + assert_eq!(BigUint::from_bytes_le(&widest.square().bytes()), (&w * &w) % &p); + for limb in crate::field25519::Operand::limbs(&loose.mul(&loose)) { + assert!(*limb < (1 << 51) + (1 << 19)); + } +} + #[test] fn scalar25519_reduction_and_signing_arithmetic_match_integer_oracle() { let order = BigUint::parse_bytes( @@ -111,21 +144,21 @@ fn check_montgomery, const N: usize>() { }; let values = fixtures(&p, N * 8); assert!(Residue::::from_integer(&M::P).is_none()); - assert_eq!(integer(&Residue::::one().to_integer()), BigUint::from(1u8)); + assert_eq!(integer(&Residue::::one().integer()), BigUint::from(1u8)); let all_ones = (BigUint::from(1u8) << (64 * N)) - 1u8; - assert_eq!(integer(&Residue::::from_wide(&words(&all_ones)).to_integer()), &all_ones % &p); + assert_eq!(integer(&Residue::::from_wide(&words(&all_ones)).integer()), &all_ones % &p); for (i, a) in values.iter().enumerate() { let a_encoded = Residue::::from_integer(&words(a)).unwrap(); - assert_eq!(&integer(&a_encoded.to_integer()), a); + assert_eq!(&integer(&a_encoded.integer()), a); for b in [&values[(i * 17 + 3) % values.len()], &values[2]] { let b_encoded = Residue::::from_integer(&words(b)).unwrap(); let sum = a_encoded.add(&b_encoded); let difference = a_encoded.sub(&b_encoded); let product = a_encoded.mul(&b_encoded); - assert_eq!(integer(&sum.to_integer()), (a + b) % &p); - assert_eq!(integer(&difference.to_integer()), (a + &p - b) % &p); - assert_eq!(integer(&product.to_integer()), (a * b) % &p); - assert_eq!(integer(&a_encoded.square().to_integer()), (a * a) % &p); + assert_eq!(integer(&sum.integer()), (a + b) % &p); + assert_eq!(integer(&difference.integer()), (a + &p - b) % &p); + assert_eq!(integer(&product.integer()), (a * b) % &p); + assert_eq!(integer(&a_encoded.square().integer()), (a * a) % &p); } if (1..=4).contains(&i) { assert_eq!(a_encoded.mul(&a_encoded.invert()).eq(&Residue::one()), 1); diff --git a/lib/crypto/src/x25519.rs b/lib/crypto/src/x25519.rs index 803db1cdab..f49534858a 100644 --- a/lib/crypto/src/x25519.rs +++ b/lib/crypto/src/x25519.rs @@ -3,7 +3,11 @@ //! RFC's noncanonical field encodings. All-zero shared secrets are refused. //! The caller supplies entropy; this module never queries the CPU for it. -use crate::{Error, Random, ct, field25519::F, secret::Secret}; +use crate::{ + Error, Random, ct, + field25519::{Multiply, Tight}, + secret::{Secret, Workspace}, +}; #[derive(Clone, Copy, Eq, PartialEq)] pub struct PublicKey(pub [u8; 32]); pub struct SecretKey(Secret<32>); @@ -27,10 +31,13 @@ impl SecretKey { pub fn public_key(&self) -> PublicKey { let mut base = [0; 32]; base[0] = 9; - PublicKey(multiply(&self.0.0, &base).0) + let mut out = [0; 32]; + multiply(&self.0.0, &base, &mut out); + PublicKey(out) } pub fn agree(self, peer: &PublicKey) -> Result { - let result = multiply(&self.0.0, &peer.0); + let mut result = Secret([0; 32]); + multiply(&self.0.0, &peer.0, &mut result.0); if ct::equal(&result.0, &[0; 32]) { Err(Error::InvalidPoint) } else { @@ -38,38 +45,36 @@ impl SecretKey { } } } -fn multiply(scalar: &[u8; 32], point: &[u8; 32]) -> Secret<32> { - let x1 = F::from_bytes(point); - let mut x2 = F::one(); - let mut z2 = F::zero(); - let mut x3 = F::from_bytes(point); - let mut z3 = F::one(); +/// The u-coordinate of scalar * u into `out`. The ladder's (x2, z2, x3, z3) +/// and the shared value x2 / z2 are erased when it returns. +fn multiply(scalar: &[u8; 32], point: &[u8; 32], out: &mut [u8; 32]) { + let x1 = Tight::from_bytes(point); + let mut state = Workspace([Tight::ONE, Tight::ZERO, x1, Tight::ONE]); + let [x2, z2, x3, z3] = &mut state.0; let mut swap = 0; for i in (0..255).rev() { let bit = u64::from((scalar[i / 8] >> (i % 8)) & 1); - swap ^= bit; - let xx = F::select(&x2, &x3, swap); - x3 = F::select(&x3, &x2, swap); - x2 = xx; - let zz = F::select(&z2, &z3, swap); - z3 = F::select(&z3, &z2, swap); - z2 = zz; + let mask = ct::mask(swap ^ bit); + Tight::swap(x2, x3, mask); + Tight::swap(z2, z3, mask); swap = bit; - let a = x2.add(&z2); + let a = x2.add(z2); let aa = a.square(); - let b = x2.sub(&z2); + let b = x2.sub(z2); let bb = b.square(); let e = aa.sub(&bb); - let c = x3.add(&z3); - let d = x3.sub(&z3); + let c = x3.add(z3); + let d = x3.sub(z3); let da = d.mul(&a); let cb = c.mul(&b); - x3 = da.add(&cb).square(); - z3 = x1.mul(&da.sub(&cb).square()); - x2 = aa.mul(&bb); - z2 = e.mul(&aa.add(&F::small(121665).mul(&e))); + *x3 = da.add(&cb).square(); + *z3 = x1.mul(&da.sub(&cb).square()); + *x2 = aa.mul(&bb); + *z2 = e.mul(&aa.add(&e.mul_small(121665))); } - x2 = F::select(&x2, &x3, swap); - z2 = F::select(&z2, &z3, swap); - Secret(x2.mul(&z2.invert()).bytes()) + let mask = ct::mask(swap); + Tight::swap(x2, x3, mask); + Tight::swap(z2, z3, mask); + let shared = Workspace(x2.mul(&z2.invert())); + *out = shared.0.bytes(); }