Add implementation of secq256k1

This commit is contained in:
Luke Parker
2024-07-22 19:21:51 -04:00
parent 49a183194d
commit 772d033bb2
9 changed files with 851 additions and 0 deletions

19
Cargo.lock generated
View File

@@ -7901,6 +7901,25 @@ dependencies = [
"cc",
]
[[package]]
name = "secq256k1"
version = "0.1.0"
dependencies = [
"crypto-bigint",
"ec-divisors",
"ff",
"ff-group-tests",
"generic-array 0.14.7",
"group",
"hex",
"hex-literal",
"k256",
"rand_core",
"rustversion",
"subtle",
"zeroize",
]
[[package]]
name = "secrecy"
version = "0.8.0"

View File

@@ -33,6 +33,7 @@ members = [
"crypto/schnorr",
"crypto/dleq",
"crypto/evrf/secq256k1",
"crypto/evrf/generalized-bulletproofs",
"crypto/evrf/circuit-abstraction",
"crypto/evrf/divisors",

View File

@@ -0,0 +1,39 @@
[package]
name = "secq256k1"
version = "0.1.0"
description = "An implementation of the curve secp256k1 cycles with"
license = "MIT"
repository = "https://github.com/serai-dex/serai/tree/develop/crypto/evrf/secq256k1"
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
keywords = ["secp256k1", "secq256k1", "group"]
edition = "2021"
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
rustversion = "1"
hex-literal = { version = "0.4", default-features = false }
rand_core = { version = "0.6", default-features = false, features = ["std"] }
zeroize = { version = "^1.5", default-features = false, features = ["std", "zeroize_derive"] }
subtle = { version = "^2.4", default-features = false, features = ["std"] }
ff = { version = "0.13", default-features = false, features = ["std", "bits"] }
group = { version = "0.13", default-features = false }
generic-array = { version = "0.14", default-features = false }
crypto-bigint = { version = "0.5", default-features = false, features = ["zeroize"] }
k256 = { version = "0.13", default-features = false, features = ["arithmetic"] }
ec-divisors = { path = "../divisors" }
[dev-dependencies]
hex = "0.4"
rand_core = { version = "0.6", features = ["std"] }
ff-group-tests = { path = "../../ff-group-tests" }

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022-2024 Luke Parker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,3 @@
# secq256k1
An implementation of the curve secp256k1 cycles with.

View File

@@ -0,0 +1,290 @@
use zeroize::Zeroize;
// Use black_box when possible
#[rustversion::since(1.66)]
use core::hint::black_box;
#[rustversion::before(1.66)]
fn black_box<T>(val: T) -> T {
val
}
pub(crate) fn u8_from_bool(bit_ref: &mut bool) -> u8 {
let bit_ref = black_box(bit_ref);
let mut bit = black_box(*bit_ref);
let res = black_box(bit as u8);
bit.zeroize();
debug_assert!((res | 1) == 1);
bit_ref.zeroize();
res
}
macro_rules! math_op {
(
$Value: ident,
$Other: ident,
$Op: ident,
$op_fn: ident,
$Assign: ident,
$assign_fn: ident,
$function: expr
) => {
impl $Op<$Other> for $Value {
type Output = $Value;
fn $op_fn(self, other: $Other) -> Self::Output {
Self($function(self.0, other.0))
}
}
impl $Assign<$Other> for $Value {
fn $assign_fn(&mut self, other: $Other) {
self.0 = $function(self.0, other.0);
}
}
impl<'a> $Op<&'a $Other> for $Value {
type Output = $Value;
fn $op_fn(self, other: &'a $Other) -> Self::Output {
Self($function(self.0, other.0))
}
}
impl<'a> $Assign<&'a $Other> for $Value {
fn $assign_fn(&mut self, other: &'a $Other) {
self.0 = $function(self.0, other.0);
}
}
};
}
macro_rules! from_wrapper {
($wrapper: ident, $inner: ident, $uint: ident) => {
impl From<$uint> for $wrapper {
fn from(a: $uint) -> $wrapper {
Self(Residue::new(&$inner::from(a)))
}
}
};
}
macro_rules! field {
(
$FieldName: ident,
$ResidueType: ident,
$MODULUS_STR: ident,
$MODULUS: ident,
$WIDE_MODULUS: ident,
$MULTIPLICATIVE_GENERATOR: literal,
$S: literal,
$ROOT_OF_UNITY: literal,
$DELTA: literal,
) => {
use core::{
ops::{DerefMut, Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign},
iter::{Sum, Product},
};
use subtle::{Choice, CtOption, ConstantTimeEq, ConstantTimeLess, ConditionallySelectable};
use rand_core::RngCore;
use crypto_bigint::{Integer, NonZero, Encoding, impl_modulus};
use ff::{Field, PrimeField, FieldBits, PrimeFieldBits, helpers::sqrt_ratio_generic};
use $crate::backend::u8_from_bool;
fn reduce(x: U512) -> U256 {
U256::from_le_slice(&x.rem(&NonZero::new($WIDE_MODULUS).unwrap()).to_le_bytes()[.. 32])
}
impl ConstantTimeEq for $FieldName {
fn ct_eq(&self, other: &Self) -> Choice {
self.0.ct_eq(&other.0)
}
}
impl ConditionallySelectable for $FieldName {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
$FieldName(Residue::conditional_select(&a.0, &b.0, choice))
}
}
math_op!($FieldName, $FieldName, Add, add, AddAssign, add_assign, |x: $ResidueType, y| x
.add(&y));
math_op!($FieldName, $FieldName, Sub, sub, SubAssign, sub_assign, |x: $ResidueType, y| x
.sub(&y));
math_op!($FieldName, $FieldName, Mul, mul, MulAssign, mul_assign, |x: $ResidueType, y| x
.mul(&y));
from_wrapper!($FieldName, U256, u8);
from_wrapper!($FieldName, U256, u16);
from_wrapper!($FieldName, U256, u32);
from_wrapper!($FieldName, U256, u64);
from_wrapper!($FieldName, U256, u128);
impl Neg for $FieldName {
type Output = $FieldName;
fn neg(self) -> $FieldName {
Self(self.0.neg())
}
}
impl<'a> Neg for &'a $FieldName {
type Output = $FieldName;
fn neg(self) -> Self::Output {
(*self).neg()
}
}
impl $FieldName {
/// Perform an exponentation.
pub fn pow(&self, other: $FieldName) -> $FieldName {
let mut table = [Self(Residue::ONE); 16];
table[1] = *self;
for i in 2 .. 16 {
table[i] = table[i - 1] * self;
}
let mut res = Self(Residue::ONE);
let mut bits = 0;
for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
bits <<= 1;
let mut bit = u8_from_bool(bit.deref_mut());
bits |= bit;
bit.zeroize();
if ((i + 1) % 4) == 0 {
if i != 3 {
for _ in 0 .. 4 {
res *= res;
}
}
let mut factor = table[0];
for (j, candidate) in table[1 ..].iter().enumerate() {
let j = j + 1;
factor = Self::conditional_select(&factor, &candidate, usize::from(bits).ct_eq(&j));
}
res *= factor;
bits = 0;
}
}
res
}
}
impl Field for $FieldName {
const ZERO: Self = Self(Residue::ZERO);
const ONE: Self = Self(Residue::ONE);
fn random(mut rng: impl RngCore) -> Self {
let mut bytes = [0; 64];
rng.fill_bytes(&mut bytes);
$FieldName(Residue::new(&reduce(U512::from_le_slice(bytes.as_ref()))))
}
fn square(&self) -> Self {
Self(self.0.square())
}
fn double(&self) -> Self {
*self + self
}
fn invert(&self) -> CtOption<Self> {
let res = self.0.invert();
CtOption::new(Self(res.0), res.1.into())
}
fn sqrt(&self) -> CtOption<Self> {
// (p + 1) // 4, as valid since p % 4 == 3
let mod_plus_one_div_four = $MODULUS.saturating_add(&U256::ONE).wrapping_div(&(4u8.into()));
let res = self.pow(Self($ResidueType::new_checked(&mod_plus_one_div_four).unwrap()));
CtOption::new(res, res.square().ct_eq(self))
}
fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) {
sqrt_ratio_generic(num, div)
}
}
impl PrimeField for $FieldName {
type Repr = [u8; 32];
const MODULUS: &'static str = $MODULUS_STR;
const NUM_BITS: u32 = 256;
const CAPACITY: u32 = 255;
const TWO_INV: Self = $FieldName($ResidueType::new(&U256::from_u8(2)).invert().0);
const MULTIPLICATIVE_GENERATOR: Self =
Self(Residue::new(&U256::from_u8($MULTIPLICATIVE_GENERATOR)));
const S: u32 = $S;
const ROOT_OF_UNITY: Self = $FieldName(Residue::new(&U256::from_be_hex($ROOT_OF_UNITY)));
const ROOT_OF_UNITY_INV: Self = Self(Self::ROOT_OF_UNITY.0.invert().0);
const DELTA: Self = $FieldName(Residue::new(&U256::from_be_hex($DELTA)));
fn from_repr(bytes: Self::Repr) -> CtOption<Self> {
let res = U256::from_le_slice(&bytes);
CtOption::new($FieldName(Residue::new(&res)), res.ct_lt(&$MODULUS))
}
fn to_repr(&self) -> Self::Repr {
let mut repr = [0; 32];
repr.copy_from_slice(&self.0.retrieve().to_le_bytes());
repr
}
fn is_odd(&self) -> Choice {
self.0.retrieve().is_odd()
}
}
impl PrimeFieldBits for $FieldName {
type ReprBits = [u8; 32];
fn to_le_bits(&self) -> FieldBits<Self::ReprBits> {
self.to_repr().into()
}
fn char_le_bits() -> FieldBits<Self::ReprBits> {
let mut repr = [0; 32];
repr.copy_from_slice(&MODULUS.to_le_bytes());
repr.into()
}
}
impl Sum<$FieldName> for $FieldName {
fn sum<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ZERO;
for item in iter {
res += item;
}
res
}
}
impl<'a> Sum<&'a $FieldName> for $FieldName {
fn sum<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().sum()
}
}
impl Product<$FieldName> for $FieldName {
fn product<I: Iterator<Item = $FieldName>>(iter: I) -> $FieldName {
let mut res = $FieldName::ONE;
for item in iter {
res *= item;
}
res
}
}
impl<'a> Product<&'a $FieldName> for $FieldName {
fn product<I: Iterator<Item = &'a $FieldName>>(iter: I) -> $FieldName {
iter.cloned().product()
}
}
};
}

View File

@@ -0,0 +1,13 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc = include_str!("../README.md")]
#[macro_use]
mod backend;
mod scalar;
pub use scalar::Scalar;
pub use k256::Scalar as FieldElement;
mod point;
pub use point::Point;

View File

@@ -0,0 +1,414 @@
use core::{
ops::{DerefMut, Add, AddAssign, Neg, Sub, SubAssign, Mul, MulAssign},
iter::Sum,
};
use rand_core::RngCore;
use zeroize::Zeroize;
use subtle::{Choice, CtOption, ConstantTimeEq, ConditionallySelectable, ConditionallyNegatable};
use generic_array::{typenum::U33, GenericArray};
use group::{
ff::{Field, PrimeField, PrimeFieldBits},
Group, GroupEncoding,
prime::PrimeGroup,
};
use crate::{backend::u8_from_bool, Scalar, FieldElement};
fn recover_y(x: FieldElement) -> CtOption<FieldElement> {
// x**3 + B since a = 0
((x.square() * x) + FieldElement::from(7u64)).sqrt()
}
/// Point.
#[derive(Clone, Copy, Debug, Zeroize)]
#[repr(C)]
pub struct Point {
x: FieldElement, // / Z
y: FieldElement, // / Z
z: FieldElement,
}
impl ConstantTimeEq for Point {
fn ct_eq(&self, other: &Self) -> Choice {
let x1 = self.x * other.z;
let x2 = other.x * self.z;
let y1 = self.y * other.z;
let y2 = other.y * self.z;
(self.x.is_zero() & other.x.is_zero()) | (x1.ct_eq(&x2) & y1.ct_eq(&y2))
}
}
impl PartialEq for Point {
fn eq(&self, other: &Point) -> bool {
self.ct_eq(other).into()
}
}
impl Eq for Point {}
impl ConditionallySelectable for Point {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Point {
x: FieldElement::conditional_select(&a.x, &b.x, choice),
y: FieldElement::conditional_select(&a.y, &b.y, choice),
z: FieldElement::conditional_select(&a.z, &b.z, choice),
}
}
}
impl Add for Point {
type Output = Point;
#[allow(non_snake_case)]
fn add(self, other: Self) -> Self {
// add-2015-rcb
let a = FieldElement::ZERO;
let B = FieldElement::from(7u64);
let b3 = B + B + B;
let X1 = self.x;
let Y1 = self.y;
let Z1 = self.z;
let X2 = other.x;
let Y2 = other.y;
let Z2 = other.z;
let t0 = X1 * X2;
let t1 = Y1 * Y2;
let t2 = Z1 * Z2;
let t3 = X1 + Y1;
let t4 = X2 + Y2;
let t3 = t3 * t4;
let t4 = t0 + t1;
let t3 = t3 - t4;
let t4 = X1 + Z1;
let t5 = X2 + Z2;
let t4 = t4 * t5;
let t5 = t0 + t2;
let t4 = t4 - t5;
let t5 = Y1 + Z1;
let X3 = Y2 + Z2;
let t5 = t5 * X3;
let X3 = t1 + t2;
let t5 = t5 - X3;
let Z3 = a * t4;
let X3 = b3 * t2;
let Z3 = X3 + Z3;
let X3 = t1 - Z3;
let Z3 = t1 + Z3;
let Y3 = X3 * Z3;
let t1 = t0 + t0;
let t1 = t1 + t0;
let t2 = a * t2;
let t4 = b3 * t4;
let t1 = t1 + t2;
let t2 = t0 - t2;
let t2 = a * t2;
let t4 = t4 + t2;
let t0 = t1 * t4;
let Y3 = Y3 + t0;
let t0 = t5 * t4;
let X3 = t3 * X3;
let X3 = X3 - t0;
let t0 = t3 * t1;
let Z3 = t5 * Z3;
let Z3 = Z3 + t0;
Point { x: X3, y: Y3, z: Z3 }
}
}
impl AddAssign for Point {
fn add_assign(&mut self, other: Point) {
*self = *self + other;
}
}
impl Add<&Point> for Point {
type Output = Point;
fn add(self, other: &Point) -> Point {
self + *other
}
}
impl AddAssign<&Point> for Point {
fn add_assign(&mut self, other: &Point) {
*self += *other;
}
}
impl Neg for Point {
type Output = Point;
fn neg(self) -> Self {
Point { x: self.x, y: -self.y, z: self.z }
}
}
impl Sub for Point {
type Output = Point;
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, other: Self) -> Self {
self + other.neg()
}
}
impl SubAssign for Point {
fn sub_assign(&mut self, other: Point) {
*self = *self - other;
}
}
impl Sub<&Point> for Point {
type Output = Point;
fn sub(self, other: &Point) -> Point {
self - *other
}
}
impl SubAssign<&Point> for Point {
fn sub_assign(&mut self, other: &Point) {
*self -= *other;
}
}
impl Group for Point {
type Scalar = Scalar;
fn random(mut rng: impl RngCore) -> Self {
loop {
let mut bytes = GenericArray::default();
rng.fill_bytes(bytes.as_mut());
let opt = Self::from_bytes(&bytes);
if opt.is_some().into() {
return opt.unwrap();
}
}
}
fn identity() -> Self {
Point { x: FieldElement::ZERO, y: FieldElement::ONE, z: FieldElement::ZERO }
}
fn generator() -> Self {
Point {
x: FieldElement::from_repr(
hex_literal::hex!("0000000000000000000000000000000000000000000000000000000000000001")
.into(),
)
.unwrap(),
y: FieldElement::from_repr(
hex_literal::hex!("0C7C97045A2074634909ABDF82C9BD0248916189041F2AF0C1B800D1FFC278C0")
.into(),
)
.unwrap(),
z: FieldElement::ONE,
}
}
fn is_identity(&self) -> Choice {
self.z.ct_eq(&FieldElement::ZERO)
}
#[allow(non_snake_case)]
fn double(&self) -> Self {
// dbl-2007-bl
let a = FieldElement::ZERO;
let X1 = self.x;
let Y1 = self.y;
let Z1 = self.z;
let XX = X1 * X1;
let ZZ = Z1 * Z1;
let w = (a * ZZ) + XX.double() + XX;
let s = (Y1 * Z1).double();
let ss = s * s;
let sss = s * ss;
let R = Y1 * s;
let RR = R * R;
let B = X1 + R;
let B = (B * B) - XX - RR;
let h = (w * w) - B.double();
let X3 = h * s;
let Y3 = w * (B - h) - RR.double();
let Z3 = sss;
let res = Self { x: X3, y: Y3, z: Z3 };
// If self is identity, res will not be well-formed
// Accordingly, we return self if self was the identity
Self::conditional_select(&res, self, self.is_identity())
}
}
impl Sum<Point> for Point {
fn sum<I: Iterator<Item = Point>>(iter: I) -> Point {
let mut res = Self::identity();
for i in iter {
res += i;
}
res
}
}
impl<'a> Sum<&'a Point> for Point {
fn sum<I: Iterator<Item = &'a Point>>(iter: I) -> Point {
Point::sum(iter.cloned())
}
}
impl Mul<Scalar> for Point {
type Output = Point;
fn mul(self, mut other: Scalar) -> Point {
// Precompute the optimal amount that's a multiple of 2
let mut table = [Point::identity(); 16];
table[1] = self;
for i in 2 .. 16 {
table[i] = table[i - 1] + self;
}
let mut res = Self::identity();
let mut bits = 0;
for (i, mut bit) in other.to_le_bits().iter_mut().rev().enumerate() {
bits <<= 1;
let mut bit = u8_from_bool(bit.deref_mut());
bits |= bit;
bit.zeroize();
if ((i + 1) % 4) == 0 {
if i != 3 {
for _ in 0 .. 4 {
res = res.double();
}
}
let mut term = table[0];
for (j, candidate) in table[1 ..].iter().enumerate() {
let j = j + 1;
term = Self::conditional_select(&term, candidate, usize::from(bits).ct_eq(&j));
}
res += term;
bits = 0;
}
}
other.zeroize();
res
}
}
impl MulAssign<Scalar> for Point {
fn mul_assign(&mut self, other: Scalar) {
*self = *self * other;
}
}
impl Mul<&Scalar> for Point {
type Output = Point;
fn mul(self, other: &Scalar) -> Point {
self * *other
}
}
impl MulAssign<&Scalar> for Point {
fn mul_assign(&mut self, other: &Scalar) {
*self *= *other;
}
}
impl GroupEncoding for Point {
type Repr = GenericArray<u8, U33>;
fn from_bytes(bytes: &Self::Repr) -> CtOption<Self> {
// Extract and clear the sign bit
let sign = Choice::from(bytes[0] & 1);
// Parse x, recover y
FieldElement::from_repr(*GenericArray::from_slice(&bytes[1 ..])).and_then(|x| {
let is_identity = x.is_zero();
let y = recover_y(x).map(|mut y| {
y.conditional_negate(y.is_odd().ct_eq(&!sign));
y
});
// If this the identity, set y to 1
let y =
CtOption::conditional_select(&y, &CtOption::new(FieldElement::ONE, 1.into()), is_identity);
// Create the point if we have a y solution
let point = y.map(|y| Point { x, y, z: FieldElement::ONE });
let not_negative_zero = !(is_identity & sign);
// Only return the point if it isn't -0 and the sign byte wasn't malleated
CtOption::conditional_select(
&CtOption::new(Point::identity(), 0.into()),
&point,
not_negative_zero & ((bytes[0] & 1).ct_eq(&bytes[0])),
)
})
}
fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption<Self> {
Point::from_bytes(bytes)
}
fn to_bytes(&self) -> Self::Repr {
let Some(z) = Option::<FieldElement>::from(self.z.invert()) else {
return *GenericArray::from_slice(&[0; 33]);
};
let x = self.x * z;
let y = self.y * z;
let mut res = *GenericArray::from_slice(&[0; 33]);
res[1 ..].as_mut().copy_from_slice(&x.to_repr());
// The following conditional select normalizes the sign to 0 when x is 0
let y_sign = u8::conditional_select(&y.is_odd().unwrap_u8(), &0, x.ct_eq(&FieldElement::ZERO));
res[0] |= y_sign;
res
}
}
impl PrimeGroup for Point {}
impl ec_divisors::DivisorCurve for Point {
type FieldElement = FieldElement;
fn a() -> Self::FieldElement {
FieldElement::from(0u64)
}
fn b() -> Self::FieldElement {
FieldElement::from(7u64)
}
fn to_xy(point: Self) -> Option<(Self::FieldElement, Self::FieldElement)> {
let z: Self::FieldElement = Option::from(point.z.invert())?;
Some((point.x * z, point.y * z))
}
}
#[test]
fn test_curve() {
ff_group_tests::group::test_prime_group_bits::<_, Point>(&mut rand_core::OsRng);
}
#[test]
fn generator() {
assert_eq!(
Point::generator(),
Point::from_bytes(GenericArray::from_slice(&hex_literal::hex!(
"000000000000000000000000000000000000000000000000000000000000000001"
)))
.unwrap()
);
}
#[test]
fn zero_x_is_invalid() {
assert!(Option::<FieldElement>::from(recover_y(FieldElement::ZERO)).is_none());
}
// Checks random won't infinitely loop
#[test]
fn random() {
Point::random(&mut rand_core::OsRng);
}

View File

@@ -0,0 +1,51 @@
use zeroize::{DefaultIsZeroes, Zeroize};
use crypto_bigint::{
U256, U512,
modular::constant_mod::{ResidueParams, Residue},
};
const MODULUS_STR: &str = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F";
impl_modulus!(HelioseleneQ, U256, MODULUS_STR);
type ResidueType = Residue<HelioseleneQ, { HelioseleneQ::LIMBS }>;
/// The Scalar field of secq256k1.
///
/// This is equivalent to the field secp256k1 is defined over.
#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)]
#[repr(C)]
pub struct Scalar(pub(crate) ResidueType);
impl DefaultIsZeroes for Scalar {}
pub(crate) const MODULUS: U256 = U256::from_be_hex(MODULUS_STR);
const WIDE_MODULUS: U512 = U512::from_be_hex(concat!(
"0000000000000000000000000000000000000000000000000000000000000000",
"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F",
));
field!(
Scalar,
ResidueType,
MODULUS_STR,
MODULUS,
WIDE_MODULUS,
3,
1,
"fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2e",
"0000000000000000000000000000000000000000000000000000000000000009",
);
impl Scalar {
/// Perform a wide reduction, presumably to obtain a non-biased Scalar field element.
pub fn wide_reduce(bytes: [u8; 64]) -> Scalar {
Scalar(Residue::new(&reduce(U512::from_le_slice(bytes.as_ref()))))
}
}
#[test]
fn test_scalar_field() {
ff_group_tests::prime_field::test_prime_field_bits::<_, Scalar>(&mut rand_core::OsRng);
}