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).
This commit is contained in:
Jack Grigg 2021-09-20 17:44:11 +01:00
parent 8fabb44ad4
commit 9999964d17
10 changed files with 216 additions and 40 deletions

View file

@ -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"]

View file

@ -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$.

View file

@ -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<Affine = <Self as CurveExt>::AffineExt>
+ group::Group<Scalar = <Self as CurveExt>::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 = <Self as CurveAffine>::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<C: CurveAffine> {
pub(crate) x: C::Base,
pub(crate) y: C::Base,
}
#[cfg(feature = "std")]
impl<C: CurveAffine> Coordinates<C> {
/// Returns the x-coordinate.
///
@ -171,6 +185,7 @@ impl<C: CurveAffine> Coordinates<C> {
}
}
#[cfg(feature = "std")]
impl<C: CurveAffine> ConditionallySelectable for Coordinates<C> {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Coordinates {

View file

@ -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::<usize>() >= 4);
/// This trait is a common interface for dealing with elements of a finite
/// field.
#[cfg(feature = "std")]
pub trait FieldExt: ff::PrimeField + From<bool> + Ord + Group<Scalar = Self> {
/// 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<bool> + Ord + Group<Scalar = Self> {
}
}
/// 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: ff::PrimeField, S: AsRef<[u64]>>(
f: &F,
tm1d2: S,
) -> CtOption<F> {
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<F: FieldExt> {
hash_xor: u32,
@ -138,6 +197,7 @@ struct SqrtHasher<F: FieldExt> {
marker: PhantomData<F>,
}
#[cfg(feature = "std")]
impl<F: FieldExt> SqrtHasher<F> {
/// Returns a perfect hash of x for use with SqrtTables::inv.
fn hash(&self, x: &F) -> usize {
@ -150,6 +210,7 @@ impl<F: FieldExt> SqrtHasher<F> {
}
/// Tables used for square root computation.
#[cfg(feature = "std")]
#[derive(Debug)]
pub struct SqrtTables<F: FieldExt> {
hasher: SqrtHasher<F>,
@ -160,9 +221,12 @@ pub struct SqrtTables<F: FieldExt> {
g3: Box<[F; 129]>,
}
#[cfg(feature = "std")]
impl<F: FieldExt> SqrtTables<F> {
/// 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,

View file

@ -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<dyn Fn(&[u8]) -> Self + 'a> {

View file

@ -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<std::cmp::Ordering> {
impl core::cmp::PartialOrd for Fp {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
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<Self> {
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<Fp> = 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]));

View file

@ -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<std::cmp::Ordering> {
impl core::cmp::PartialOrd for Fq {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
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<Self> {
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<Fq> = 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]));

View file

@ -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};

View file

@ -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;

View file

@ -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;