From 87488ec1f6de0634c31b61939c7eea8959f74fa9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Wed, 3 Mar 2021 20:37:51 +0000 Subject: [PATCH 1/5] CI: Add no-std build check --- .github/workflows/ci.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2566161..dc5ba6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,3 +188,26 @@ jobs: with: command: fmt args: -- --check + + no-std: + name: Check no-std target ${{ matrix.target }} + runs-on: ubuntu-latest + strategy: + matrix: + target: + - thumbv6m-none-eabi + - wasm32-unknown-unknown + - wasm32-wasi + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - run: rustup target add ${{ matrix.target }} + - name: Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --target ${{ matrix.target }} --no-default-features From 6a47700b1da7a251d3629e1e36ab1dcb0d7168f7 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 20 Sep 2021 16:34:28 +0100 Subject: [PATCH 2/5] fields: Ensure that trait impl dependencies match trait bounds The `FieldExt` trait was originally the only trait implemented in this crate. When we added `ff` support, we reworked `FieldExt` to be an extension trait on top of `ff::PrimeField`, but left the existing impls in `FieldExt`. This resulted in some circular dependencies that prevent us from making `FieldExt` conditional (e.g. for no-std support). This commit removes the cycles like so: - `ff::PrimeField::{from_repr, to_repr}` were implemented as calls to `FieldExt::{from_bytes, to_bytes}`. The field encoding/decoding logic is moved into the `ff::PrimeField` trait impl, and `FieldExt` now calls into `ff::PrimeField`. - `ff::Field::sqrt` was implemented in terms of `FieldExt::sqrt_alt`. Given that the latter is a trivial wrapper around the `SqrtTables` implementation, we duplicate the call to eliminate the cycle. - `ff::Field::random` used `FieldExt::from_bytes_wide`, which wraps either `Fp::from_u512` or `Fq::from_u512`. We now use these internal methods directly. --- src/fields/fp.rs | 96 ++++++++++++++++++++++++------------------------ src/fields/fq.rs | 96 ++++++++++++++++++++++++------------------------ 2 files changed, 98 insertions(+), 94 deletions(-) diff --git a/src/fields/fp.rs b/src/fields/fp.rs index eb943b0..065a453 100644 --- a/src/fields/fp.rs +++ b/src/fields/fp.rs @@ -466,10 +466,16 @@ impl Group for Fp { impl ff::Field for Fp { fn random(mut rng: impl RngCore) -> Self { - let mut random_bytes = [0; 64]; - rng.fill_bytes(&mut random_bytes[..]); - - Self::from_bytes_wide(&random_bytes) + Self::from_u512([ + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + ]) } fn zero() -> Self { @@ -491,7 +497,7 @@ impl ff::Field for Fp { /// Computes the square root of this element, if it exists. fn sqrt(&self) -> CtOption { - let (is_square, res) = self.sqrt_alt(); + let (is_square, res) = FP_TABLES.sqrt_alt(self); CtOption::new(res, is_square) } @@ -535,15 +541,47 @@ impl ff::PrimeField for Fp { const S: u32 = S; fn from_repr(repr: Self::Repr) -> CtOption { - Self::from_bytes(&repr) + let mut tmp = Fp([0, 0, 0, 0]); + + tmp.0[0] = u64::from_le_bytes(repr[0..8].try_into().unwrap()); + tmp.0[1] = u64::from_le_bytes(repr[8..16].try_into().unwrap()); + tmp.0[2] = u64::from_le_bytes(repr[16..24].try_into().unwrap()); + tmp.0[3] = u64::from_le_bytes(repr[24..32].try_into().unwrap()); + + // Try to subtract the modulus + let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0); + let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow); + let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow); + let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow); + + // 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 = (borrow as u8) & 1; + + // Convert to Montgomery form by computing + // (a.R^0 * R^2) / R = a.R + tmp *= &R2; + + CtOption::new(tmp, Choice::from(is_some)) } fn to_repr(&self) -> Self::Repr { - self.to_bytes() + // Turn into canonical form by computing + // (a.R) / R = a + let tmp = Fp::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); + + let mut res = [0; 32]; + res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + + res } fn is_odd(&self) -> Choice { - Choice::from(self.to_bytes()[0] & 1) + Choice::from(self.to_repr()[0] & 1) } fn multiplicative_generator() -> Self { @@ -551,7 +589,7 @@ impl ff::PrimeField for Fp { } fn root_of_unity() -> Self { - Self::ROOT_OF_UNITY + ROOT_OF_UNITY } } @@ -660,48 +698,12 @@ impl FieldExt for Fp { Fp::from_raw([v as u64, (v >> 64) as u64, 0, 0]) } - /// Attempts to convert a little-endian byte representation of - /// a scalar into a `Fp`, failing if the input is not canonical. fn from_bytes(bytes: &[u8; 32]) -> CtOption { - let mut tmp = Fp([0, 0, 0, 0]); - - tmp.0[0] = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); - tmp.0[1] = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); - tmp.0[2] = u64::from_le_bytes(bytes[16..24].try_into().unwrap()); - tmp.0[3] = u64::from_le_bytes(bytes[24..32].try_into().unwrap()); - - // Try to subtract the modulus - let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0); - let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow); - let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow); - let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow); - - // 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 = (borrow as u8) & 1; - - // Convert to Montgomery form by computing - // (a.R^0 * R^2) / R = a.R - tmp *= &R2; - - CtOption::new(tmp, Choice::from(is_some)) + ::from_repr(*bytes) } - /// Converts an element of `Fp` into a byte representation in - /// little-endian byte order. fn to_bytes(&self) -> [u8; 32] { - // Turn into canonical form by computing - // (a.R) / R = a - let tmp = Fp::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); - - let mut res = [0; 32]; - res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); - res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); - res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); - res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); - - res + ::to_repr(self) } /// Converts a 512-bit little endian integer into diff --git a/src/fields/fq.rs b/src/fields/fq.rs index b624a67..c8be606 100644 --- a/src/fields/fq.rs +++ b/src/fields/fq.rs @@ -466,10 +466,16 @@ impl Group for Fq { impl ff::Field for Fq { fn random(mut rng: impl RngCore) -> Self { - let mut random_bytes = [0; 64]; - rng.fill_bytes(&mut random_bytes[..]); - - Self::from_bytes_wide(&random_bytes) + Self::from_u512([ + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + rng.next_u64(), + ]) } fn zero() -> Self { @@ -491,7 +497,7 @@ impl ff::Field for Fq { /// Computes the square root of this element, if it exists. fn sqrt(&self) -> CtOption { - let (is_square, res) = self.sqrt_alt(); + let (is_square, res) = FQ_TABLES.sqrt_alt(self); CtOption::new(res, is_square) } @@ -535,15 +541,47 @@ impl ff::PrimeField for Fq { const S: u32 = S; fn from_repr(repr: Self::Repr) -> CtOption { - Self::from_bytes(&repr) + let mut tmp = Fq([0, 0, 0, 0]); + + tmp.0[0] = u64::from_le_bytes(repr[0..8].try_into().unwrap()); + tmp.0[1] = u64::from_le_bytes(repr[8..16].try_into().unwrap()); + tmp.0[2] = u64::from_le_bytes(repr[16..24].try_into().unwrap()); + tmp.0[3] = u64::from_le_bytes(repr[24..32].try_into().unwrap()); + + // Try to subtract the modulus + let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0); + let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow); + let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow); + let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow); + + // 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 = (borrow as u8) & 1; + + // Convert to Montgomery form by computing + // (a.R^0 * R^2) / R = a.R + tmp *= &R2; + + CtOption::new(tmp, Choice::from(is_some)) } fn to_repr(&self) -> Self::Repr { - self.to_bytes() + // Turn into canonical form by computing + // (a.R) / R = a + let tmp = Fq::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); + + let mut res = [0; 32]; + res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); + res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); + res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); + res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); + + res } fn is_odd(&self) -> Choice { - Choice::from(self.to_bytes()[0] & 1) + Choice::from(self.to_repr()[0] & 1) } fn multiplicative_generator() -> Self { @@ -551,7 +589,7 @@ impl ff::PrimeField for Fq { } fn root_of_unity() -> Self { - Self::ROOT_OF_UNITY + ROOT_OF_UNITY } } @@ -660,48 +698,12 @@ impl FieldExt for Fq { Fq::from_raw([v as u64, (v >> 64) as u64, 0, 0]) } - /// Attempts to convert a little-endian byte representation of - /// a scalar into a `Fq`, failing if the input is not canonical. fn from_bytes(bytes: &[u8; 32]) -> CtOption { - let mut tmp = Fq([0, 0, 0, 0]); - - tmp.0[0] = u64::from_le_bytes(bytes[0..8].try_into().unwrap()); - tmp.0[1] = u64::from_le_bytes(bytes[8..16].try_into().unwrap()); - tmp.0[2] = u64::from_le_bytes(bytes[16..24].try_into().unwrap()); - tmp.0[3] = u64::from_le_bytes(bytes[24..32].try_into().unwrap()); - - // Try to subtract the modulus - let (_, borrow) = sbb(tmp.0[0], MODULUS.0[0], 0); - let (_, borrow) = sbb(tmp.0[1], MODULUS.0[1], borrow); - let (_, borrow) = sbb(tmp.0[2], MODULUS.0[2], borrow); - let (_, borrow) = sbb(tmp.0[3], MODULUS.0[3], borrow); - - // 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 = (borrow as u8) & 1; - - // Convert to Montgomery form by computing - // (a.R^0 * R^2) / R = a.R - tmp *= &R2; - - CtOption::new(tmp, Choice::from(is_some)) + ::from_repr(*bytes) } - /// Converts an element of `Fq` into a byte representation in - /// little-endian byte order. fn to_bytes(&self) -> [u8; 32] { - // Turn into canonical form by computing - // (a.R) / R = a - let tmp = Fq::montgomery_reduce(self.0[0], self.0[1], self.0[2], self.0[3], 0, 0, 0, 0); - - let mut res = [0; 32]; - res[0..8].copy_from_slice(&tmp.0[0].to_le_bytes()); - res[8..16].copy_from_slice(&tmp.0[1].to_le_bytes()); - res[16..24].copy_from_slice(&tmp.0[2].to_le_bytes()); - res[24..32].copy_from_slice(&tmp.0[3].to_le_bytes()); - - res + ::to_repr(self) } /// Converts a 512-bit little endian integer into From 8fabb44ad4d2bfb2ee309838e52c3834b06be2e0 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 20 Sep 2021 17:26:34 +0100 Subject: [PATCH 3/5] fields: Use `ff::PrimeField` instead of `FieldExt` where possible --- src/curves.rs | 17 +++++++++-------- src/fields/fp.rs | 14 ++++++++------ src/fields/fq.rs | 14 ++++++++------ 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/curves.rs b/src/curves.rs index 93778e2..dc01128 100644 --- a/src/curves.rs +++ b/src/curves.rs @@ -5,7 +5,8 @@ use core::cmp; use core::fmt::Debug; use core::iter::Sum; use core::ops::{Add, Mul, Neg, Sub}; -use ff::Field; + +use ff::{Field, PrimeField}; use group::{ cofactor::{CofactorCurve, CofactorGroup}, prime::{PrimeCurve, PrimeCurveAffine, PrimeGroup}, @@ -68,7 +69,7 @@ macro_rules! new_curve_impl { let x3 = x.square() * x; let y = (x3 + $name::curve_constant_b()).sqrt(); if let Some(y) = Option::<$base>::from(y) { - let sign = y.to_bytes()[0] & 1; + let sign = y.is_odd().unwrap_u8(); let y = if ysign ^ sign == 0 { y } else { -y }; let p = $name_affine { @@ -465,7 +466,7 @@ macro_rules! new_curve_impl { // // NOTE: We skip the leading bit because it's always unset. for bit in other - .to_bytes() + .to_repr() .iter() .rev() .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) @@ -576,7 +577,7 @@ macro_rules! new_curve_impl { // // NOTE: We skip the leading bit because it's always unset. for bit in other - .to_bytes() + .to_repr() .iter() .rev() .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) @@ -646,11 +647,11 @@ macro_rules! new_curve_impl { let ysign = Choice::from(tmp[31] >> 7); tmp[31] &= 0b0111_1111; - $base::from_bytes(&tmp).and_then(|x| { + $base::from_repr(tmp).and_then(|x| { CtOption::new(Self::identity(), x.is_zero() & (!ysign)).or_else(|| { let x3 = x.square() * x; (x3 + $name::curve_constant_b()).sqrt().and_then(|y| { - let sign = Choice::from(y.to_bytes()[0] & 1); + let sign = y.is_odd(); let y = $base::conditional_select(&y, &-y, ysign ^ sign); @@ -678,8 +679,8 @@ macro_rules! new_curve_impl { [0; 32] } else { let (x, y) = (self.x, self.y); - let sign = (y.to_bytes()[0] & 1) << 7; - let mut xbytes = x.to_bytes(); + let sign = y.is_odd().unwrap_u8() << 7; + let mut xbytes = x.to_repr(); xbytes[31] |= sign; xbytes } diff --git a/src/fields/fp.rs b/src/fields/fp.rs index 065a453..b8573c1 100644 --- a/src/fields/fp.rs +++ b/src/fields/fp.rs @@ -2,6 +2,8 @@ use core::convert::TryInto; use core::fmt; use core::ops::{Add, Mul, Neg, Sub}; use lazy_static::lazy_static; + +use ff::PrimeField; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -23,7 +25,7 @@ pub struct Fp(pub(crate) [u64; 4]); impl fmt::Debug for Fp { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let tmp = self.to_bytes(); + let tmp = self.to_repr(); write!(f, "0x")?; for &b in tmp.iter().rev() { write!(f, "{:02x}", b)?; @@ -66,8 +68,8 @@ impl PartialEq for Fp { impl std::cmp::Ord for Fp { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let left = self.to_bytes(); - let right = other.to_bytes(); + let left = self.to_repr(); + let right = other.to_repr(); left.iter() .zip(right.iter()) .rev() @@ -437,13 +439,13 @@ impl Fp { impl From for [u8; 32] { fn from(value: Fp) -> [u8; 32] { - value.to_bytes() + value.to_repr() } } impl<'a> From<&'a Fp> for [u8; 32] { fn from(value: &'a Fp) -> [u8; 32] { - value.to_bytes() + value.to_repr() } } @@ -767,7 +769,7 @@ impl FieldExt for Fp { } #[cfg(test)] -use ff::{Field, PrimeField}; +use ff::Field; #[test] fn test_inv() { diff --git a/src/fields/fq.rs b/src/fields/fq.rs index c8be606..6c2518f 100644 --- a/src/fields/fq.rs +++ b/src/fields/fq.rs @@ -2,6 +2,8 @@ use core::convert::TryInto; use core::fmt; use core::ops::{Add, Mul, Neg, Sub}; use lazy_static::lazy_static; + +use ff::PrimeField; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -23,7 +25,7 @@ pub struct Fq(pub(crate) [u64; 4]); impl fmt::Debug for Fq { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let tmp = self.to_bytes(); + let tmp = self.to_repr(); write!(f, "0x")?; for &b in tmp.iter().rev() { write!(f, "{:02x}", b)?; @@ -66,8 +68,8 @@ impl PartialEq for Fq { impl std::cmp::Ord for Fq { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - let left = self.to_bytes(); - let right = other.to_bytes(); + let left = self.to_repr(); + let right = other.to_repr(); left.iter() .zip(right.iter()) .rev() @@ -437,13 +439,13 @@ impl Fq { impl From for [u8; 32] { fn from(value: Fq) -> [u8; 32] { - value.to_bytes() + value.to_repr() } } impl<'a> From<&'a Fq> for [u8; 32] { fn from(value: &'a Fq) -> [u8; 32] { - value.to_bytes() + value.to_repr() } } @@ -767,7 +769,7 @@ impl FieldExt for Fq { } #[cfg(test)] -use ff::{Field, PrimeField}; +use ff::Field; #[test] fn test_inv() { From 9999964d17a5aafbe9aa811ff47310f57a852bf9 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 20 Sep 2021 17:44:11 +0100 Subject: [PATCH 4/5] Add no-std support We re-introduce the Tonelli-Shank square root algoritm that was removed in zcash/halo2#120, to use in no-std mode (the table-based impl requires allocations, and also uses 29kiB of memory which is a problem for constrained environments that typically need no-std). --- Cargo.toml | 19 +++++++---- src/arithmetic.rs | 4 +++ src/arithmetic/curves.rs | 21 ++++++++++-- src/arithmetic/fields.rs | 72 +++++++++++++++++++++++++++++++++++++--- src/curves.rs | 16 +++++++-- src/fields/fp.rs | 54 ++++++++++++++++++++++++------ src/fields/fq.rs | 54 ++++++++++++++++++++++++------ src/lib.rs | 10 +++++- src/pallas.rs | 4 +++ src/vesta.rs | 2 ++ 10 files changed, 216 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d9593d7..50b86c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ rand_xorshift = "0.3" [[bench]] name = "hashtocurve" harness = false +required-features = ["std"] [[bench]] name = "fp" @@ -37,16 +38,20 @@ harness = false [[bench]] name = "point" harness = false +required-features = ["std"] [dependencies] -subtle = "2.3" -ff = "0.11" -group = "0.11" -rand = "0.8" -blake2b_simd = "0.5" -lazy_static = "1.4.0" +blake2b_simd = { version = "0.5", default-features = false } +ff = { version = "0.11", default-features = false } +group = { version = "0.11", default-features = false } +rand = { version = "0.8", default-features = false } static_assertions = "1.1.0" +subtle = { version = "2.3", default-features = false } + +# std dependencies +lazy_static = { version = "1.4.0", optional = true } [features] -default = ["bits"] +default = ["bits", "std"] bits = ["ff/bits"] +std = ["group/alloc", "lazy_static", "rand/getrandom"] diff --git a/src/arithmetic.rs b/src/arithmetic.rs index c2b802c..bb5fb1d 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -9,12 +9,16 @@ pub use ff::Field; mod curves; mod fields; +pub(crate) use fields::*; + pub use curves::*; +#[cfg(feature = "std")] pub use fields::*; /// This represents an element of a group with basic operations that can be /// performed. This allows an FFT implementation (for example) to operate /// generically over either a field or elliptic curve group. +#[cfg(feature = "std")] pub trait Group: Copy + Clone + Send + Sync + 'static { /// The group is assumed to be of prime order $p$. `Scalar` is the /// associated scalar field of size $p$. diff --git a/src/arithmetic/curves.rs b/src/arithmetic/curves.rs index a0b34ed..0088945 100644 --- a/src/arithmetic/curves.rs +++ b/src/arithmetic/curves.rs @@ -1,18 +1,29 @@ //! This module contains the `Curve`/`CurveAffine` abstractions that allow us to //! write code that generalizes over a pair of groups. -use core::cmp; -use core::ops::{Add, Mul, Sub}; +#[cfg(feature = "std")] use group::prime::{PrimeCurve, PrimeCurveAffine}; +#[cfg(feature = "std")] use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; +#[cfg(feature = "std")] use super::{FieldExt, Group}; -use std::io::{self, Read, Write}; +#[cfg(feature = "std")] +use std::{ + boxed::Box, + cmp, + io::{self, Read, Write}, + ops::{Add, Mul, Sub}, +}; /// This trait is a common interface for dealing with elements of an elliptic /// curve group in a "projective" form, where that arithmetic is usually more /// efficient. +/// +/// Currently requires the `std` feature flag because of `hash_to_curve`, and +/// `CurveAffine::{read, write}`. +#[cfg(feature = "std")] pub trait CurveExt: PrimeCurve::AffineExt> + group::Group::ScalarExt> @@ -81,6 +92,7 @@ pub trait CurveExt: /// This trait is the affine counterpart to `Curve` and is used for /// serialization, storage in memory, and inspection of $x$ and $y$ coordinates. +#[cfg(feature = "std")] pub trait CurveAffine: PrimeCurveAffine< Scalar = ::ScalarExt, @@ -135,12 +147,14 @@ pub trait CurveAffine: } /// The affine coordinates of a point on an elliptic curve. +#[cfg(feature = "std")] #[derive(Clone, Copy, Debug, Default)] pub struct Coordinates { pub(crate) x: C::Base, pub(crate) y: C::Base, } +#[cfg(feature = "std")] impl Coordinates { /// Returns the x-coordinate. /// @@ -171,6 +185,7 @@ impl Coordinates { } } +#[cfg(feature = "std")] impl ConditionallySelectable for Coordinates { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { Coordinates { diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index e9caea2..d755c9d 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -2,20 +2,28 @@ //! code that generalizes over a pair of fields. use core::mem::size_of; + use static_assertions::const_assert; -use std::assert; -use std::convert::TryInto; -use std::marker::PhantomData; use subtle::{Choice, CtOption}; +#[cfg(feature = "std")] use super::Group; -use std::io::{self, Read, Write}; +#[cfg(feature = "std")] +use std::{ + assert, + boxed::Box, + convert::TryInto, + io::{self, Read, Write}, + marker::PhantomData, + vec::Vec, +}; const_assert!(size_of::() >= 4); /// This trait is a common interface for dealing with elements of a finite /// field. +#[cfg(feature = "std")] pub trait FieldExt: ff::PrimeField + From + Ord + Group { /// Modulus of the field written as a string for display purposes const MODULUS: &'static str; @@ -130,7 +138,58 @@ pub trait FieldExt: ff::PrimeField + From + Ord + Group { } } +/// Tonelli-Shank's square-root algorithm for `p mod 16 = 1`. +/// +/// https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) +/// +/// `tm1d2` should be set to `(t - 1) // 2`, where `t = (modulus - 1) >> F::S`. +#[cfg(not(feature = "std"))] +pub(crate) fn sqrt_tonelli_shank>( + f: &F, + tm1d2: S, +) -> CtOption { + use subtle::{ConditionallySelectable, ConstantTimeEq}; + + // w = self^((t - 1) // 2) + let w = f.pow_vartime(tm1d2); + + let mut v = F::S; + let mut x = w * f; + let mut b = x * w; + + // Initialize z as the 2^S root of unity. + let mut z = F::root_of_unity(); + + for max_v in (1..=F::S).rev() { + let mut k = 1; + let mut tmp = b.square(); + let mut j_less_than_v: Choice = 1.into(); + + for j in 2..max_v { + let tmp_is_one = tmp.ct_eq(&F::one()); + let squared = F::conditional_select(&tmp, &z, tmp_is_one).square(); + tmp = F::conditional_select(&squared, &tmp, tmp_is_one); + let new_z = F::conditional_select(&z, &squared, tmp_is_one); + j_less_than_v &= !j.ct_eq(&v); + k = u32::conditional_select(&j, &k, tmp_is_one); + z = F::conditional_select(&z, &new_z, j_less_than_v); + } + + let result = x * z; + x = F::conditional_select(&result, &x, b.ct_eq(&F::one())); + z = z.square(); + b *= z; + v = k; + } + + CtOption::new( + x, + (x * x).ct_eq(f), // Only return Some if it's the square root. + ) +} + /// Parameters for a perfect hash function used in square root computation. +#[cfg(feature = "std")] #[derive(Debug)] struct SqrtHasher { hash_xor: u32, @@ -138,6 +197,7 @@ struct SqrtHasher { marker: PhantomData, } +#[cfg(feature = "std")] impl SqrtHasher { /// Returns a perfect hash of x for use with SqrtTables::inv. fn hash(&self, x: &F) -> usize { @@ -150,6 +210,7 @@ impl SqrtHasher { } /// Tables used for square root computation. +#[cfg(feature = "std")] #[derive(Debug)] pub struct SqrtTables { hasher: SqrtHasher, @@ -160,9 +221,12 @@ pub struct SqrtTables { g3: Box<[F; 129]>, } +#[cfg(feature = "std")] impl SqrtTables { /// Build tables given parameters for the perfect hash. pub fn new(hash_xor: u32, hash_mod: usize) -> Self { + use std::vec; + let hasher = SqrtHasher { hash_xor, hash_mod, diff --git a/src/curves.rs b/src/curves.rs index dc01128..d402de5 100644 --- a/src/curves.rs +++ b/src/curves.rs @@ -2,10 +2,13 @@ //! groups. use core::cmp; -use core::fmt::Debug; +use core::fmt; use core::iter::Sum; use core::ops::{Add, Mul, Neg, Sub}; +#[cfg(feature = "std")] +use std::boxed::Box; + use ff::{Field, PrimeField}; use group::{ cofactor::{CofactorCurve, CofactorGroup}, @@ -16,6 +19,8 @@ use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; use super::{Fp, Fq}; + +#[cfg(feature = "std")] use crate::arithmetic::{Coordinates, CurveAffine, CurveExt, FieldExt, Group}; macro_rules! new_curve_impl { @@ -48,8 +53,8 @@ macro_rules! new_curve_impl { infinity: Choice, } - impl std::fmt::Debug for $name_affine { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + impl fmt::Debug for $name_affine { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { if self.infinity.into() { write!(f, "Infinity") } else { @@ -97,6 +102,7 @@ macro_rules! new_curve_impl { } } + #[cfg(feature = "std")] impl group::WnafGroup for $name { fn recommended_wnaf_for_num_scalars(num_scalars: usize) -> usize { // Copied from bls12_381::g1, should be updated. @@ -116,6 +122,7 @@ macro_rules! new_curve_impl { } } + #[cfg(feature = "std")] impl CurveExt for $name { type ScalarExt = $scalar; type Base = $base; @@ -687,6 +694,7 @@ macro_rules! new_curve_impl { } } + #[cfg(feature = "std")] impl CurveAffine for $name_affine { type ScalarExt = $scalar; type Base = $base; @@ -770,6 +778,7 @@ macro_rules! new_curve_impl { impl_binops_multiplicative!($name, $scalar); impl_binops_multiplicative_mixed!($name_affine, $scalar, $name); + #[cfg(feature = "std")] impl Group for $name { type Scalar = $scalar; @@ -870,6 +879,7 @@ macro_rules! impl_projective_curve_specific { }; } +#[cfg(feature = "std")] macro_rules! impl_projective_curve_ext { ($name:ident, $iso:ident, $base:ident, special_a0_b5) => { fn hash_to_curve<'a>(domain_prefix: &'a str) -> Box Self + 'a> { diff --git a/src/fields/fp.rs b/src/fields/fp.rs index b8573c1..200e20d 100644 --- a/src/fields/fp.rs +++ b/src/fields/fp.rs @@ -1,16 +1,21 @@ use core::convert::TryInto; use core::fmt; use core::ops::{Add, Mul, Neg, Sub}; -use lazy_static::lazy_static; use ff::PrimeField; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; +#[cfg(feature = "std")] +use lazy_static::lazy_static; + #[cfg(feature = "bits")] use ff::{FieldBits, PrimeFieldBits}; -use crate::arithmetic::{adc, mac, sbb, FieldExt, Group, SqrtTables}; +use crate::arithmetic::{adc, mac, sbb}; + +#[cfg(feature = "std")] +use crate::arithmetic::{FieldExt, Group, SqrtTables}; /// This represents an element of $\mathbb{F}_p$ where /// @@ -66,23 +71,23 @@ impl PartialEq for Fp { } } -impl std::cmp::Ord for Fp { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { +impl core::cmp::Ord for Fp { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { let left = self.to_repr(); let right = other.to_repr(); left.iter() .zip(right.iter()) .rev() .find_map(|(left_byte, right_byte)| match left_byte.cmp(right_byte) { - std::cmp::Ordering::Equal => None, + core::cmp::Ordering::Equal => None, res => Some(res), }) - .unwrap_or(std::cmp::Ordering::Equal) + .unwrap_or(core::cmp::Ordering::Equal) } } -impl std::cmp::PartialOrd for Fp { - fn partial_cmp(&self, other: &Self) -> Option { +impl core::cmp::PartialOrd for Fp { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } @@ -219,6 +224,7 @@ const ROOT_OF_UNITY: Fp = Fp::from_raw([ /// GENERATOR^{2^s} where t * 2^s + 1 = p /// with t odd. In other words, this /// is a t root of unity. +#[cfg(feature = "std")] const DELTA: Fp = Fp::from_raw([ 0x6a6ccd20dd7b9ba2, 0xf5e4f3f13eee5636, @@ -449,6 +455,7 @@ impl<'a> From<&'a Fp> for [u8; 32] { } } +#[cfg(feature = "std")] impl Group for Fp { type Scalar = Fp; @@ -499,8 +506,22 @@ impl ff::Field for Fp { /// Computes the square root of this element, if it exists. fn sqrt(&self) -> CtOption { - let (is_square, res) = FP_TABLES.sqrt_alt(self); - CtOption::new(res, is_square) + #[cfg(feature = "std")] + { + let (is_square, res) = FP_TABLES.sqrt_alt(self); + CtOption::new(res, is_square) + } + + #[cfg(not(feature = "std"))] + crate::arithmetic::sqrt_tonelli_shank( + self, + &[ + 0x04a6_7c8d_cc96_9876, + 0x0000_0000_1123_4c7e, + 0x0000_0000_0000_0000, + 0x0000_0000_2000_0000, + ], + ) } /// Computes the multiplicative inverse of this element, @@ -642,11 +663,13 @@ impl PrimeFieldBits for Fp { } } +#[cfg(feature = "std")] lazy_static! { // The perfect hash parameters are found by `squareroottab.sage` in zcash/pasta. static ref FP_TABLES: SqrtTables = SqrtTables::new(0x11BE, 1098); } +#[cfg(feature = "std")] impl FieldExt for Fp { const MODULUS: &'static str = "0x40000000000000000000000000000000224698fc094cf91b992d30ed00000001"; @@ -768,7 +791,7 @@ impl FieldExt for Fp { } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] use ff::Field; #[test] @@ -786,6 +809,7 @@ fn test_inv() { assert_eq!(inv, INV); } +#[cfg(feature = "std")] #[test] fn test_rescue() { // NB: TWO_INV is standing in as a "random" field element @@ -797,6 +821,7 @@ fn test_rescue() { ); } +#[cfg(feature = "std")] #[test] fn test_sqrt() { // NB: TWO_INV is standing in as a "random" field element @@ -804,6 +829,7 @@ fn test_sqrt() { assert!(v == Fp::TWO_INV || (-v) == Fp::TWO_INV); } +#[cfg(feature = "std")] #[test] fn test_pow_by_t_minus1_over2() { // NB: TWO_INV is standing in as a "random" field element @@ -811,6 +837,7 @@ fn test_pow_by_t_minus1_over2() { assert!(v == ff::Field::pow_vartime(&Fp::TWO_INV, &Fp::T_MINUS1_OVER2)); } +#[cfg(feature = "std")] #[test] fn test_sqrt_ratio_and_alt() { // (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field @@ -857,6 +884,7 @@ fn test_sqrt_ratio_and_alt() { assert!(v == expected); } +#[cfg(feature = "std")] #[test] fn test_zeta() { assert_eq!( @@ -872,6 +900,7 @@ fn test_zeta() { assert!(c == Fp::one()); } +#[cfg(feature = "std")] #[test] fn test_root_of_unity() { assert_eq!( @@ -880,16 +909,19 @@ fn test_root_of_unity() { ); } +#[cfg(feature = "std")] #[test] fn test_inv_root_of_unity() { assert_eq!(Fp::ROOT_OF_UNITY_INV, Fp::ROOT_OF_UNITY.invert().unwrap()); } +#[cfg(feature = "std")] #[test] fn test_inv_2() { assert_eq!(Fp::TWO_INV, Fp::from(2).invert().unwrap()); } +#[cfg(feature = "std")] #[test] fn test_delta() { assert_eq!(Fp::DELTA, GENERATOR.pow(&[1u64 << Fp::S, 0, 0, 0])); diff --git a/src/fields/fq.rs b/src/fields/fq.rs index 6c2518f..95fed38 100644 --- a/src/fields/fq.rs +++ b/src/fields/fq.rs @@ -1,16 +1,21 @@ use core::convert::TryInto; use core::fmt; use core::ops::{Add, Mul, Neg, Sub}; -use lazy_static::lazy_static; use ff::PrimeField; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; +#[cfg(feature = "std")] +use lazy_static::lazy_static; + #[cfg(feature = "bits")] use ff::{FieldBits, PrimeFieldBits}; -use crate::arithmetic::{adc, mac, sbb, FieldExt, Group, SqrtTables}; +use crate::arithmetic::{adc, mac, sbb}; + +#[cfg(feature = "std")] +use crate::arithmetic::{FieldExt, Group, SqrtTables}; /// This represents an element of $\mathbb{F}_q$ where /// @@ -66,23 +71,23 @@ impl PartialEq for Fq { } } -impl std::cmp::Ord for Fq { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { +impl core::cmp::Ord for Fq { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { let left = self.to_repr(); let right = other.to_repr(); left.iter() .zip(right.iter()) .rev() .find_map(|(left_byte, right_byte)| match left_byte.cmp(right_byte) { - std::cmp::Ordering::Equal => None, + core::cmp::Ordering::Equal => None, res => Some(res), }) - .unwrap_or(std::cmp::Ordering::Equal) + .unwrap_or(core::cmp::Ordering::Equal) } } -impl std::cmp::PartialOrd for Fq { - fn partial_cmp(&self, other: &Self) -> Option { +impl core::cmp::PartialOrd for Fq { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } @@ -219,6 +224,7 @@ const ROOT_OF_UNITY: Fq = Fq::from_raw([ /// GENERATOR^{2^s} where t * 2^s + 1 = q /// with t odd. In other words, this /// is a t root of unity. +#[cfg(feature = "std")] const DELTA: Fq = Fq::from_raw([ 0x8494392472d1683c, 0xe3ac3376541d1140, @@ -449,6 +455,7 @@ impl<'a> From<&'a Fq> for [u8; 32] { } } +#[cfg(feature = "std")] impl Group for Fq { type Scalar = Fq; @@ -499,8 +506,22 @@ impl ff::Field for Fq { /// Computes the square root of this element, if it exists. fn sqrt(&self) -> CtOption { - let (is_square, res) = FQ_TABLES.sqrt_alt(self); - CtOption::new(res, is_square) + #[cfg(feature = "std")] + { + let (is_square, res) = FQ_TABLES.sqrt_alt(self); + CtOption::new(res, is_square) + } + + #[cfg(not(feature = "std"))] + crate::arithmetic::sqrt_tonelli_shank( + self, + &[ + 0x04ca_546e_c623_7590, + 0x0000_0000_1123_4c7e, + 0x0000_0000_0000_0000, + 0x0000_0000_2000_0000, + ], + ) } /// Computes the multiplicative inverse of this element, @@ -642,11 +663,13 @@ impl PrimeFieldBits for Fq { } } +#[cfg(feature = "std")] lazy_static! { // The perfect hash parameters are found by `squareroottab.sage` in zcash/pasta. static ref FQ_TABLES: SqrtTables = SqrtTables::new(0x116A9E, 1206); } +#[cfg(feature = "std")] impl FieldExt for Fq { const MODULUS: &'static str = "0x40000000000000000000000000000000224698fc0994a8dd8c46eb2100000001"; @@ -768,7 +791,7 @@ impl FieldExt for Fq { } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] use ff::Field; #[test] @@ -786,6 +809,7 @@ fn test_inv() { assert_eq!(inv, INV); } +#[cfg(feature = "std")] #[test] fn test_rescue() { // NB: TWO_INV is standing in as a "random" field element @@ -797,6 +821,7 @@ fn test_rescue() { ); } +#[cfg(feature = "std")] #[test] fn test_sqrt() { // NB: TWO_INV is standing in as a "random" field element @@ -804,6 +829,7 @@ fn test_sqrt() { assert!(v == Fq::TWO_INV || (-v) == Fq::TWO_INV); } +#[cfg(feature = "std")] #[test] fn test_pow_by_t_minus1_over2() { // NB: TWO_INV is standing in as a "random" field element @@ -811,6 +837,7 @@ fn test_pow_by_t_minus1_over2() { assert!(v == ff::Field::pow_vartime(&Fq::TWO_INV, &Fq::T_MINUS1_OVER2)); } +#[cfg(feature = "std")] #[test] fn test_sqrt_ratio_and_alt() { // (true, sqrt(num/div)), if num and div are nonzero and num/div is a square in the field @@ -857,6 +884,7 @@ fn test_sqrt_ratio_and_alt() { assert!(v == expected); } +#[cfg(feature = "std")] #[test] fn test_zeta() { assert_eq!( @@ -871,6 +899,7 @@ fn test_zeta() { assert!(c == Fq::one()); } +#[cfg(feature = "std")] #[test] fn test_root_of_unity() { assert_eq!( @@ -879,16 +908,19 @@ fn test_root_of_unity() { ); } +#[cfg(feature = "std")] #[test] fn test_inv_root_of_unity() { assert_eq!(Fq::ROOT_OF_UNITY_INV, Fq::ROOT_OF_UNITY.invert().unwrap()); } +#[cfg(feature = "std")] #[test] fn test_inv_2() { assert_eq!(Fq::TWO_INV, Fq::from(2).invert().unwrap()); } +#[cfg(feature = "std")] #[test] fn test_delta() { assert_eq!(Fq::DELTA, GENERATOR.pow(&[1u64 << Fq::S, 0, 0, 0])); diff --git a/src/lib.rs b/src/lib.rs index 5cb9de9..add7d5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ //! Implementation of the Pallas / Vesta curve cycle. +#![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] #![allow(unknown_lints)] #![allow(clippy::op_ref, clippy::same_item_push, clippy::upper_case_acronyms)] @@ -8,21 +9,28 @@ #![deny(missing_docs)] #![deny(unsafe_code)] +#[cfg(any(feature = "std", test))] +#[macro_use] +extern crate std; + #[macro_use] mod macros; mod curves; mod fields; pub mod arithmetic; -mod hashtocurve; pub mod pallas; pub mod vesta; +#[cfg(feature = "std")] +mod hashtocurve; + pub use curves::*; pub use fields::*; pub extern crate group; +#[cfg(feature = "std")] #[test] fn test_endo_consistency() { use crate::arithmetic::{CurveExt, FieldExt}; diff --git a/src/pallas.rs b/src/pallas.rs index 4c00a99..c9754ee 100644 --- a/src/pallas.rs +++ b/src/pallas.rs @@ -14,6 +14,7 @@ pub type Point = Ep; /// A Pallas point in the affine coordinate space (or the point at infinity). pub type Affine = EpAffine; +#[cfg(feature = "std")] #[test] #[allow(clippy::many_single_char_names)] fn test_iso_map() { @@ -64,6 +65,7 @@ fn test_iso_map() { assert!(p2 == p.double()); } +#[cfg(feature = "std")] #[test] fn test_iso_map_identity() { use crate::arithmetic::CurveExt; @@ -98,6 +100,7 @@ fn test_iso_map_identity() { assert!(bool::from(p.is_identity())); } +#[cfg(feature = "std")] #[test] fn test_map_to_curve_simple_swu() { use crate::arithmetic::CurveExt; @@ -132,6 +135,7 @@ fn test_map_to_curve_simple_swu() { ); } +#[cfg(feature = "std")] #[test] fn test_hash_to_curve() { use crate::arithmetic::CurveExt; diff --git a/src/vesta.rs b/src/vesta.rs index e6dddb1..cafee9e 100644 --- a/src/vesta.rs +++ b/src/vesta.rs @@ -14,6 +14,7 @@ pub type Point = Eq; /// A Vesta point in the affine coordinate space (or the point at infinity). pub type Affine = EqAffine; +#[cfg(feature = "std")] #[test] fn test_map_to_curve_simple_swu() { use crate::arithmetic::CurveExt; @@ -48,6 +49,7 @@ fn test_map_to_curve_simple_swu() { ); } +#[cfg(feature = "std")] #[test] fn test_hash_to_curve() { use crate::arithmetic::CurveExt; From 2b350118b0c51dab5aed48e218d1ffeceb44f1ff Mon Sep 17 00:00:00 2001 From: str4d Date: Tue, 21 Sep 2021 10:44:07 +1200 Subject: [PATCH 5/5] Fix naming of Tonelli-Shanks Co-authored-by: Daira Hopwood --- src/arithmetic/fields.rs | 4 ++-- src/fields/fp.rs | 2 +- src/fields/fq.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/arithmetic/fields.rs b/src/arithmetic/fields.rs index d755c9d..53554fc 100644 --- a/src/arithmetic/fields.rs +++ b/src/arithmetic/fields.rs @@ -138,13 +138,13 @@ pub trait FieldExt: ff::PrimeField + From + Ord + Group { } } -/// Tonelli-Shank's square-root algorithm for `p mod 16 = 1`. +/// Tonelli–Shanks' square-root algorithm for `p mod 16 = 1`. /// /// https://eprint.iacr.org/2012/685.pdf (page 12, algorithm 5) /// /// `tm1d2` should be set to `(t - 1) // 2`, where `t = (modulus - 1) >> F::S`. #[cfg(not(feature = "std"))] -pub(crate) fn sqrt_tonelli_shank>( +pub(crate) fn sqrt_tonelli_shanks>( f: &F, tm1d2: S, ) -> CtOption { diff --git a/src/fields/fp.rs b/src/fields/fp.rs index 200e20d..0650751 100644 --- a/src/fields/fp.rs +++ b/src/fields/fp.rs @@ -513,7 +513,7 @@ impl ff::Field for Fp { } #[cfg(not(feature = "std"))] - crate::arithmetic::sqrt_tonelli_shank( + crate::arithmetic::sqrt_tonelli_shanks( self, &[ 0x04a6_7c8d_cc96_9876, diff --git a/src/fields/fq.rs b/src/fields/fq.rs index 95fed38..1a137fc 100644 --- a/src/fields/fq.rs +++ b/src/fields/fq.rs @@ -513,7 +513,7 @@ impl ff::Field for Fq { } #[cfg(not(feature = "std"))] - crate::arithmetic::sqrt_tonelli_shank( + crate::arithmetic::sqrt_tonelli_shanks( self, &[ 0x04ca_546e_c623_7590,