Use CtOption from subtle crate

Closes #20.
This commit is contained in:
Jack Grigg 2019-05-04 20:58:19 +01:00
parent d2e5f79b14
commit cb57a647b5
No known key found for this signature in database
GPG Key ID: 9E8255172BBF9898
5 changed files with 4 additions and 266 deletions

View File

@ -14,7 +14,7 @@ version = "1"
default-features = false
[dependencies.subtle]
version = "2"
version = "2.1"
default-features = false
[dev-dependencies.rand_core]

View File

@ -1,257 +0,0 @@
//! This module provides a "CtOption" abstraction as a constant-time
//! alternative for APIs that want to return optional values.
//! Ideally, this would be merged into upstream `subtle` at some
//! point.
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
/// The `CtOption<T>` type represents an optional value similar to the
/// [`Option<T>`](core::option::Option) type but is intended for
/// use in constant time APIs. Any given `CtOption<T>` is either
/// `Some` or `None`, but unlike `Option<T>` these variants are
/// not exposed. The `is_some()` method is used to determine if the
/// value is `Some`, and `unwrap_or`/`unwrap_or_else` methods are
/// provided to access the underlying value. The value can also be
/// obtained with `unwrap()` but this will panic if it is None.
///
/// Functions that are intended to be constant time may not produce
/// valid results for all inputs, such as square root and inversion
/// operations in finite field arithmetic. Returning an `Option<T>`
/// from these functions makes it difficult for the caller to reason
/// about the result in constant time, and returning an incorrect
/// value burdens the caller and increases the chance of bugs.
#[derive(Clone, Copy, Debug)]
pub struct CtOption<T> {
value: T,
is_some: Choice,
}
impl<T> CtOption<T> {
/// This method is used to construct a new `CtOption<T>` and takes
/// a value of type `T`, and a `Choice` that determines whether
/// the optional value should be `Some` or not. If `is_some` is
/// false, the value will still be stored but its value is never
/// exposed.
#[inline]
pub fn new(value: T, is_some: Choice) -> CtOption<T> {
CtOption { value, is_some }
}
/// This returns the underlying value but panics if it
/// is not `Some`.
#[inline]
pub fn unwrap(self) -> T {
assert_eq!(self.is_some.unwrap_u8(), 1);
self.value
}
/// This returns the underlying value if it is `Some`
/// or the provided value otherwise.
#[inline]
pub fn unwrap_or(self, def: T) -> T
where
T: ConditionallySelectable,
{
T::conditional_select(&def, &self.value, self.is_some)
}
/// This returns the underlying value if it is `Some`
/// or the value produced by the provided closure otherwise.
#[inline]
pub fn unwrap_or_else<F>(self, f: F) -> T
where
T: ConditionallySelectable,
F: FnOnce() -> T,
{
T::conditional_select(&f(), &self.value, self.is_some)
}
/// Returns a true `Choice` if this value is `Some`.
#[inline]
pub fn is_some(&self) -> Choice {
self.is_some
}
/// Returns a true `Choice` if this value is `None`.
#[inline]
pub fn is_none(&self) -> Choice {
!self.is_some
}
/// Returns a `None` value if the option is `None`, otherwise
/// returns a `CtOption` enclosing the value of the provided closure.
/// The closure is given the enclosed value or, if the option is
/// `None`, it is provided a dummy value computed using
/// `Default::default()`.
///
/// This operates in constant time, because the provided closure
/// is always called.
#[inline]
pub fn map<U, F>(self, f: F) -> CtOption<U>
where
T: Default + ConditionallySelectable,
F: FnOnce(T) -> U,
{
CtOption::new(
f(T::conditional_select(
&T::default(),
&self.value,
self.is_some,
)),
self.is_some,
)
}
/// Returns a `None` value if the option is `None`, otherwise
/// returns the result of the provided closure. The closure is
/// given the enclosed value or, if the option is `None`, it
/// is provided a dummy value computed using `Default::default()`.
///
/// This operates in constant time, because the provided closure
/// is always called.
#[inline]
pub fn and_then<U, F>(self, f: F) -> CtOption<U>
where
T: Default + ConditionallySelectable,
F: FnOnce(T) -> CtOption<U>,
{
let mut tmp = f(T::conditional_select(
&T::default(),
&self.value,
self.is_some,
));
tmp.is_some &= self.is_some;
tmp
}
}
impl<T: ConditionallySelectable> ConditionallySelectable for CtOption<T> {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
CtOption::new(
T::conditional_select(&a.value, &b.value, choice),
// TODO: subtle crate currently doesn't implement ConditionallySelectable
// for Choice so we must unwrap these manually.
Choice::from(u8::conditional_select(
&a.is_some.unwrap_u8(),
&b.is_some.unwrap_u8(),
choice,
)),
)
}
}
impl<T: ConstantTimeEq> ConstantTimeEq for CtOption<T> {
/// Two `CtOption<T>`s are equal if they are both `Some` and
/// their values are equal, or both `None`.
#[inline]
fn ct_eq(&self, rhs: &CtOption<T>) -> Choice {
let a = self.is_some();
let b = rhs.is_some();
(a & b & self.value.ct_eq(&rhs.value)) | (!a & !b)
}
}
#[test]
fn test_ctoption() {
let a = CtOption::new(10, Choice::from(1));
let b = CtOption::new(9, Choice::from(1));
let c = CtOption::new(10, Choice::from(0));
let d = CtOption::new(9, Choice::from(0));
// Test is_some / is_none
assert!(bool::from(a.is_some()));
assert!(bool::from(!a.is_none()));
assert!(bool::from(b.is_some()));
assert!(bool::from(!b.is_none()));
assert!(bool::from(!c.is_some()));
assert!(bool::from(c.is_none()));
assert!(bool::from(!d.is_some()));
assert!(bool::from(d.is_none()));
// Test unwrap for Some
assert_eq!(a.unwrap(), 10);
assert_eq!(b.unwrap(), 9);
// Test equality
assert!(bool::from(a.ct_eq(&a)));
assert!(bool::from(!a.ct_eq(&b)));
assert!(bool::from(!a.ct_eq(&c)));
assert!(bool::from(!a.ct_eq(&d)));
// Test equality of None with different
// dummy value
assert!(bool::from(c.ct_eq(&d)));
// Test unwrap_or
assert_eq!(CtOption::new(1, Choice::from(1)).unwrap_or(2), 1);
assert_eq!(CtOption::new(1, Choice::from(0)).unwrap_or(2), 2);
// Test unwrap_or_else
assert_eq!(CtOption::new(1, Choice::from(1)).unwrap_or_else(|| 2), 1);
assert_eq!(CtOption::new(1, Choice::from(0)).unwrap_or_else(|| 2), 2);
// Test map
assert_eq!(
CtOption::new(1, Choice::from(1))
.map(|v| {
assert_eq!(v, 1);
2
})
.unwrap(),
2
);
assert_eq!(
CtOption::new(1, Choice::from(0))
.map(|_| 2)
.is_none()
.unwrap_u8(),
1
);
// Test and_then
assert_eq!(
CtOption::new(1, Choice::from(1))
.and_then(|v| {
assert_eq!(v, 1);
CtOption::new(2, Choice::from(0))
})
.is_none()
.unwrap_u8(),
1
);
assert_eq!(
CtOption::new(1, Choice::from(1))
.and_then(|v| {
assert_eq!(v, 1);
CtOption::new(2, Choice::from(1))
})
.unwrap(),
2
);
assert_eq!(
CtOption::new(1, Choice::from(0))
.and_then(|_| CtOption::new(2, Choice::from(0)))
.is_none()
.unwrap_u8(),
1
);
assert_eq!(
CtOption::new(1, Choice::from(0))
.and_then(|_| CtOption::new(2, Choice::from(1)))
.is_none()
.unwrap_u8(),
1
);
}
#[test]
#[should_panic]
fn unwrap_none_ctoption() {
// This test might fail (in release mode?) if the
// compiler decides to optimize it away.
CtOption::new(10, Choice::from(0)).unwrap();
}

View File

@ -2,10 +2,9 @@ use core::fmt;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use byteorder::{ByteOrder, LittleEndian};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use crate::util::{adc, mac, sbb};
use crate::CtOption;
/// Represents an element of `GF(q)`.
// The internal representation of this type is four 64-bit unsigned

View File

@ -2,10 +2,9 @@ use core::fmt;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use byteorder::{ByteOrder, LittleEndian};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use crate::util::{adc, mac, sbb};
use crate::CtOption;
/// Represents an element of `GF(r)`.
// The internal representation of this type is four 64-bit unsigned

View File

@ -36,14 +36,11 @@
extern crate std;
use core::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
#[macro_use]
mod util;
mod ctoption;
pub use ctoption::CtOption;
mod fq;
mod fr;
pub use fq::Fq;