Skip to content

Commit

Permalink
Merge pull request #65 from zkcrypto/64-constant-time
Browse files Browse the repository at this point in the history
Enable constant-time usage of trait methods
  • Loading branch information
str4d authored Sep 1, 2021
2 parents 6a8392c + 0c657f3 commit 9ba3f60
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 72 deletions.
17 changes: 13 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,21 @@ and this library adheres to Rust's notion of
### Added
- `subtle::ConstantTimeEq` bound on `ff::Field`
- `Copy + Send + Sync + 'static` bounds on `ff::PrimeField::Repr`
- `ff::derive` module behind the `derive` feature flag, which re-exports the crates used
by the `PrimeField` derive macro.
- `ff::derive` module behind the `derive` feature flag, containing dependencies for the
`PrimeField` derive macro:
- Re-exports of required crates.
- `adc, mac, sbb` constant-time const helper functions.
- `ff::Field::is_zero_vartime`
- `ff::PrimeField::from_repr_vartime`

### Changed
- `ff::{adc, mac_with_carry, sbb}` have been moved into the `ff::derive` module, behind
the `derive` feature flag.
- `ff::Field::is_zero` now returns `subtle::Choice`.
- `ff::PrimeField::{is_odd, is_even}` now return `subtle::Choice`.
- `ff::PrimeField::from_repr` now return `subtle::CtOption<Self>`.
- `ff::PrimeField::from_str` has been renamed to `PrimeField::from_str_vartime`.

### Removed
- `ff::{adc, mac_with_carry, sbb}` (replaced by `ff::derive::{adc, mac, sbb}`).

## [0.10.1] - 2021-08-11
### Added
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

## Disclaimers

* This library does not provide constant-time guarantees.
* This library does not provide constant-time guarantees. The traits enable downstream
users to expose constant-time logic, but `#[derive(PrimeField)]` in particular does not
generate constant-time code (even for trait methods that return constant-time-compatible
values).

## Usage

Expand Down
93 changes: 56 additions & 37 deletions ff_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,6 @@ impl ReprEndianness {
#read_repr
#name(inner)
};

if r.is_valid() {
Some(r * R2)
} else {
None
}
}
}

Expand Down Expand Up @@ -670,33 +664,26 @@ fn prime_field_impl(
let temp = get_temp(i);
gen.extend(quote! {
let k = #temp.wrapping_mul(INV);
let mut carry = 0;
::ff::derive::mac_with_carry(#temp, k, MODULUS_LIMBS.0[0], &mut carry);
let (_, carry) = ::ff::derive::mac(#temp, k, MODULUS_LIMBS.0[0], 0);
});
}

for j in 1..limbs {
let temp = get_temp(i + j);
gen.extend(quote! {
#temp = ::ff::derive::mac_with_carry(#temp, k, MODULUS_LIMBS.0[#j], &mut carry);
let (#temp, carry) = ::ff::derive::mac(#temp, k, MODULUS_LIMBS.0[#j], carry);
});
}

let temp = get_temp(i + limbs);

if i == 0 {
gen.extend(quote! {
#temp = ::ff::derive::adc(#temp, 0, &mut carry);
let (#temp, carry2) = ::ff::derive::adc(#temp, 0, carry);
});
} else {
gen.extend(quote! {
#temp = ::ff::derive::adc(#temp, carry2, &mut carry);
});
}

if i != (limbs - 1) {
gen.extend(quote! {
let carry2 = carry;
let (#temp, carry2) = ::ff::derive::adc(#temp, carry2, carry);
});
}
}
Expand All @@ -718,18 +705,18 @@ fn prime_field_impl(
if limbs > 1 {
for i in 0..(limbs - 1) {
gen.extend(quote! {
let mut carry = 0;
let carry = 0;
});

for j in (i + 1)..limbs {
let temp = get_temp(i + j);
if i == 0 {
gen.extend(quote! {
let #temp = ::ff::derive::mac_with_carry(0, #a.0[#i], #a.0[#j], &mut carry);
let (#temp, carry) = ::ff::derive::mac(0, #a.0[#i], #a.0[#j], carry);
});
} else {
gen.extend(quote! {
let #temp = ::ff::derive::mac_with_carry(#temp, #a.0[#i], #a.0[#j], &mut carry);
let (#temp, carry) = ::ff::derive::mac(#temp, #a.0[#i], #a.0[#j], carry);
});
}
}
Expand Down Expand Up @@ -766,25 +753,21 @@ fn prime_field_impl(
});
}

gen.extend(quote! {
let mut carry = 0;
});

for i in 0..limbs {
let temp0 = get_temp(i * 2);
let temp1 = get_temp(i * 2 + 1);
if i == 0 {
gen.extend(quote! {
let #temp0 = ::ff::derive::mac_with_carry(0, #a.0[#i], #a.0[#i], &mut carry);
let (#temp0, carry) = ::ff::derive::mac(0, #a.0[#i], #a.0[#i], 0);
});
} else {
gen.extend(quote! {
let #temp0 = ::ff::derive::mac_with_carry(#temp0, #a.0[#i], #a.0[#i], &mut carry);
let (#temp0, carry) = ::ff::derive::mac(#temp0, #a.0[#i], #a.0[#i], carry);
});
}

gen.extend(quote! {
let #temp1 = ::ff::derive::adc(#temp1, 0, &mut carry);
let (#temp1, carry) = ::ff::derive::adc(#temp1, 0, carry);
});
}

Expand Down Expand Up @@ -812,19 +795,19 @@ fn prime_field_impl(

for i in 0..limbs {
gen.extend(quote! {
let mut carry = 0;
let carry = 0;
});

for j in 0..limbs {
let temp = get_temp(i + j);

if i == 0 {
gen.extend(quote! {
let #temp = ::ff::derive::mac_with_carry(0, #a.0[#i], #b.0[#j], &mut carry);
let (#temp, carry) = ::ff::derive::mac(0, #a.0[#i], #b.0[#j], carry);
});
} else {
gen.extend(quote! {
let #temp = ::ff::derive::mac_with_carry(#temp, #a.0[#i], #b.0[#j], &mut carry);
let (#temp, carry) = ::ff::derive::mac(#temp, #a.0[#i], #b.0[#j], carry);
});
}
}
Expand Down Expand Up @@ -1019,7 +1002,7 @@ fn prime_field_impl(
use ::ff::Field;

let mut ret = self;
if !ret.is_zero() {
if !ret.is_zero_vartime() {
let mut tmp = MODULUS_LIMBS;
tmp.sub_noborrow(&ret);
ret = tmp;
Expand Down Expand Up @@ -1150,22 +1133,48 @@ fn prime_field_impl(
impl ::ff::PrimeField for #name {
type Repr = #repr;

fn from_repr(r: #repr) -> Option<#name> {
fn from_repr(r: #repr) -> ::ff::derive::subtle::CtOption<#name> {
#from_repr_impl

// Try to subtract the modulus
let borrow = r.0.iter().zip(MODULUS_LIMBS.0.iter()).fold(0, |borrow, (a, b)| {
::ff::derive::sbb(*a, *b, borrow).1
});

// If the element is smaller than MODULUS then the
// subtraction will underflow, producing a borrow value
// of 0xffff...ffff. Otherwise, it'll be zero.
let is_some = ::ff::derive::subtle::Choice::from((borrow as u8) & 1);

// Convert to Montgomery form by computing
// (a.R^0 * R^2) / R = a.R
::ff::derive::subtle::CtOption::new(r * &R2, is_some)
}

fn from_repr_vartime(r: #repr) -> Option<#name> {
#from_repr_impl

if r.is_valid() {
Some(r * R2)
} else {
None
}
}

fn to_repr(&self) -> #repr {
#to_repr_impl
}

#[inline(always)]
fn is_odd(&self) -> bool {
fn is_odd(&self) -> ::ff::derive::subtle::Choice {
let mut r = *self;
r.mont_reduce(
#mont_reduce_self_params
);

r.0[0] & 1 == 1
// TODO: This looks like a constant-time result, but r.mont_reduce() is
// currently implemented using variable-time code.
::ff::derive::subtle::Choice::from((r.0[0] & 1) as u8)
}

const NUM_BITS: u32 = MODULUS_BITS;
Expand Down Expand Up @@ -1227,7 +1236,13 @@ fn prime_field_impl(
}

#[inline]
fn is_zero(&self) -> bool {
fn is_zero(&self) -> ::ff::derive::subtle::Choice {
use ::ff::derive::subtle::ConstantTimeEq;
self.ct_eq(&Self::zero())
}

#[inline]
fn is_zero_vartime(&self) -> bool {
self.0.iter().all(|&e| e == 0)
}

Expand Down Expand Up @@ -1295,7 +1310,9 @@ fn prime_field_impl(
let mut carry = 0;

for (a, b) in self.0.iter_mut().zip(other.0.iter()) {
*a = ::ff::derive::adc(*a, *b, &mut carry);
let (new_a, new_carry) = ::ff::derive::adc(*a, *b, carry);
*a = new_a;
carry = new_carry;
}
}

Expand All @@ -1304,7 +1321,9 @@ fn prime_field_impl(
let mut borrow = 0;

for (a, b) in self.0.iter_mut().zip(other.0.iter()) {
*a = ::ff::derive::sbb(*a, *b, &mut borrow);
let (new_a, new_borrow) = ::ff::derive::sbb(*a, *b, borrow);
*a = new_a;
borrow = new_borrow;
}
}

Expand Down
80 changes: 50 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use bitvec::{array::BitArray, order::Lsb0};
use core::fmt;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use rand_core::RngCore;
use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};

/// Bit representation of a field element.
#[cfg(feature = "bits")]
Expand Down Expand Up @@ -69,7 +69,19 @@ pub trait Field:
fn one() -> Self;

/// Returns true iff this element is zero.
fn is_zero(&self) -> bool;
fn is_zero(&self) -> Choice {
self.ct_eq(&Self::zero())
}

/// Returns true iff this element is zero.
///
/// # Security
///
/// This method provides **no** constant-time guarantees. Implementors of the
/// `Field` trait **may** optimise this method using non-constant-time logic.
fn is_zero_vartime(&self) -> bool {
self.is_zero().into()
}

/// Squares this element.
#[must_use]
Expand Down Expand Up @@ -122,7 +134,11 @@ pub trait PrimeField: Field + From<u64> {

/// Interpret a string of numbers as a (congruent) prime field element.
/// Does not accept unnecessary leading zeroes or a blank string.
fn from_str(s: &str) -> Option<Self> {
///
/// # Security
///
/// This method provides **no** constant-time guarantees.
fn from_str_vartime(s: &str) -> Option<Self> {
if s.is_empty() {
return None;
}
Expand Down Expand Up @@ -166,7 +182,22 @@ pub trait PrimeField: Field + From<u64> {
///
/// The byte representation is interpreted with the same endianness as elements
/// returned by [`PrimeField::to_repr`].
fn from_repr(_: Self::Repr) -> Option<Self>;
fn from_repr(repr: Self::Repr) -> CtOption<Self>;

/// Attempts to convert a byte representation of a field element into an element of
/// this prime field, failing if the input is not canonical (is not smaller than the
/// field's modulus).
///
/// The byte representation is interpreted with the same endianness as elements
/// returned by [`PrimeField::to_repr`].
///
/// # Security
///
/// This method provides **no** constant-time guarantees. Implementors of the
/// `PrimeField` trait **may** optimise this method using non-constant-time logic.
fn from_repr_vartime(repr: Self::Repr) -> Option<Self> {
Self::from_repr(repr).into()
}

/// Converts an element of the prime field into the standard byte representation for
/// this field.
Expand All @@ -176,11 +207,11 @@ pub trait PrimeField: Field + From<u64> {
fn to_repr(&self) -> Self::Repr;

/// Returns true iff this element is odd.
fn is_odd(&self) -> bool;
fn is_odd(&self) -> Choice;

/// Returns true iff this element is even.
#[inline(always)]
fn is_even(&self) -> bool {
fn is_even(&self) -> Choice {
!self.is_odd()
}

Expand Down Expand Up @@ -238,37 +269,26 @@ pub mod derive {
pub use {bitvec, byteorder, rand_core, subtle};
}

#[cfg(feature = "derive")]
mod arith_impl {
/// Calculate a - b - borrow, returning the result and modifying
/// the borrow value.
/// Computes `a - (b + borrow)`, returning the result and the new borrow.
#[inline(always)]
pub fn sbb(a: u64, b: u64, borrow: &mut u64) -> u64 {
let tmp = (1u128 << 64) + u128::from(a) - u128::from(b) - u128::from(*borrow);

*borrow = if tmp >> 64 == 0 { 1 } else { 0 };

tmp as u64
pub const fn sbb(a: u64, b: u64, borrow: u64) -> (u64, u64) {
let ret = (a as u128).wrapping_sub((b as u128) + ((borrow >> 63) as u128));
(ret as u64, (ret >> 64) as u64)
}

/// Calculate a + b + carry, returning the sum and modifying the
/// carry value.
/// Computes `a + b + carry`, returning the result and the new carry over.
#[inline(always)]
pub fn adc(a: u64, b: u64, carry: &mut u64) -> u64 {
let tmp = u128::from(a) + u128::from(b) + u128::from(*carry);

*carry = (tmp >> 64) as u64;

tmp as u64
pub const fn adc(a: u64, b: u64, carry: u64) -> (u64, u64) {
let ret = (a as u128) + (b as u128) + (carry as u128);
(ret as u64, (ret >> 64) as u64)
}

/// Calculate a + (b * c) + carry, returning the least significant digit
/// and setting carry to the most significant digit.
/// Computes `a + (b * c) + carry`, returning the result and the new carry over.
#[inline(always)]
pub fn mac_with_carry(a: u64, b: u64, c: u64, carry: &mut u64) -> u64 {
let tmp = (u128::from(a)) + u128::from(b) * u128::from(c) + u128::from(*carry);

*carry = (tmp >> 64) as u64;

tmp as u64
pub const fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) {
let ret = (a as u128) + ((b as u128) * (c as u128)) + (carry as u128);
(ret as u64, (ret >> 64) as u64)
}
}

0 comments on commit 9ba3f60

Please sign in to comment.