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;