From 21c9a37617e348f9e20b085a1329e78267f9c2e0 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Tue, 16 Jun 2026 17:35:37 -0600 Subject: [PATCH] primefield: add `PrimeFieldExt` trait Adds a trait for specifying the endianness of the serialized representation of a field element as a stopgap until zkcrypto/rfcs#4 is accepted. This uses the `ByteOrder` enum and replaces the previous `FIELD_REPR_IS_BE` flag on `PrimeCurveParams`. It also means `Radix16Decomposition` now "Just Works" and doesn't need an endianness parameter passed to `new`. The `primefield::monty_field_element!` macro now writes impls for this trait, sourcing the endianness information from the `MontyFieldParams`. --- Cargo.lock | 1 + bignp256/src/arithmetic.rs | 1 - bignp256/src/arithmetic/scalar.rs | 6 +++--- k256/src/arithmetic/mul.rs | 6 +++--- k256/src/arithmetic/scalar.rs | 17 +++++++++------- p256/src/arithmetic/scalar.rs | 3 +++ primefield/src/lib.rs | 2 ++ primefield/src/macros.rs | 9 ++++++--- primefield/src/traits.rs | 31 ++++++++++++++++++++++++++++++ primeorder/Cargo.toml | 1 + primeorder/src/lib.rs | 17 ++++++++-------- primeorder/src/projective.rs | 18 ++++++++--------- primeorder/src/tables/basepoint.rs | 4 ++-- primeorder/src/tables/radix16.rs | 11 ++++------- 14 files changed, 83 insertions(+), 44 deletions(-) create mode 100644 primefield/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 88afb39da..c884ce528 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1005,6 +1005,7 @@ version = "0.14.0-rc.11" dependencies = [ "elliptic-curve", "once_cell", + "primefield", "serdect", ] diff --git a/bignp256/src/arithmetic.rs b/bignp256/src/arithmetic.rs index 9a7ac8930..060ec82f6 100644 --- a/bignp256/src/arithmetic.rs +++ b/bignp256/src/arithmetic.rs @@ -49,5 +49,4 @@ impl PrimeCurveParams for BignP256 { "936A510418CF291E52F608C4663991785D83D651A3C9E45C9FD616FB3CFCF76B", ), ); - const FIELD_REPR_IS_BE: bool = false; } diff --git a/bignp256/src/arithmetic/scalar.rs b/bignp256/src/arithmetic/scalar.rs index 3c65b92df..fa3e3f561 100644 --- a/bignp256/src/arithmetic/scalar.rs +++ b/bignp256/src/arithmetic/scalar.rs @@ -10,8 +10,8 @@ use elliptic_curve::{ scalar::{FromUintUnchecked, IsHigh}, subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, CtOption}, }; +use primefield::ByteOrder; -// TODO(tarcieri): remove this when we can use `const _` to silence warnings cpubits! { 32 => { #[path = "scalar/bignp256_scalar_32.rs"] @@ -23,7 +23,7 @@ cpubits! { clippy::identity_op, clippy::needless_lifetimes, clippy::too_many_arguments, - clippy::unnecessary_cast + clippy::unnecessary_cast, )] mod scalar_impl; } @@ -52,7 +52,7 @@ primefield::monty_field_params! { name: ScalarParams, modulus: ORDER_HEX, uint: U256, - byte_order: primefield::ByteOrder::LittleEndian, + byte_order: ByteOrder::LittleEndian, multiplicative_generator: 3, doc: "Montgomery parameters for the bign-curve256v1 scalar modulus" } diff --git a/k256/src/arithmetic/mul.rs b/k256/src/arithmetic/mul.rs index 88be4df6f..cde1303a0 100644 --- a/k256/src/arithmetic/mul.rs +++ b/k256/src/arithmetic/mul.rs @@ -248,8 +248,8 @@ fn lincomb( ); digits[i] = ( - Radix16Decomposition::::new(&r1_c, true), - Radix16Decomposition::::new(&r2_c, true), + Radix16Decomposition::::new(&r1_c), + Radix16Decomposition::::new(&r2_c), ) }); @@ -355,7 +355,7 @@ impl ProjectivePoint { /// Calculates `k * G`, where `G` is the generator. #[cfg(feature = "precomputed-tables")] pub(super) fn mul_by_generator(k: &Scalar) -> ProjectivePoint { - let digits = Radix16Decomposition::::new(k, true); + let digits = Radix16Decomposition::::new(k); let table = *BASEPOINT_TABLE; let mut acc = table[32].select(digits[64]); let mut acc2 = ProjectivePoint::IDENTITY; diff --git a/k256/src/arithmetic/scalar.rs b/k256/src/arithmetic/scalar.rs index 035e57bc8..886ecbd2b 100644 --- a/k256/src/arithmetic/scalar.rs +++ b/k256/src/arithmetic/scalar.rs @@ -19,6 +19,7 @@ use elliptic_curve::{ }, zeroize::DefaultIsZeroes, }; +use primeorder::PrimeFieldExt; cpubits! { 32 => { @@ -77,12 +78,6 @@ const FRAC_MODULUS_2: U256 = ORDER.as_ref().shr_vartime(1); #[derive(Clone, Copy, Debug, Default, PartialOrd, Ord)] pub struct Scalar(pub(crate) U256); -impl AsRef for Scalar { - fn as_ref(&self) -> &Scalar { - self - } -} - impl Scalar { /// Zero scalar. pub const ZERO: Self = Self(U256::ZERO); @@ -176,6 +171,14 @@ impl Scalar { } } +impl AsRef for Scalar { + fn as_ref(&self) -> &Scalar { + self + } +} + +impl DefaultIsZeroes for Scalar {} + impl Field for Scalar { const ZERO: Self = Self::ZERO; const ONE: Self = Self::ONE; @@ -308,7 +311,7 @@ impl PrimeField for Scalar { } } -impl DefaultIsZeroes for Scalar {} +impl PrimeFieldExt for Scalar {} impl From for Scalar { fn from(k: u32) -> Self { diff --git a/p256/src/arithmetic/scalar.rs b/p256/src/arithmetic/scalar.rs index bee8b2f0f..92c21da9c 100644 --- a/p256/src/arithmetic/scalar.rs +++ b/p256/src/arithmetic/scalar.rs @@ -21,6 +21,7 @@ use elliptic_curve::{ }, zeroize::DefaultIsZeroes, }; +use primefield::PrimeFieldExt; cpubits! { 32 => { @@ -307,6 +308,8 @@ impl PrimeField for Scalar { } } +impl PrimeFieldExt for Scalar {} + impl Retrieve for Scalar { type Output = U256; diff --git a/primefield/src/lib.rs b/primefield/src/lib.rs index 277a2cc6f..8c8a22cf4 100644 --- a/primefield/src/lib.rs +++ b/primefield/src/lib.rs @@ -12,10 +12,12 @@ mod dev; mod error; mod macros; mod monty; +mod traits; pub use crate::{ error::{Error, Result}, monty::{MontyFieldBytes, MontyFieldElement, MontyFieldParams, compute_t}, + traits::PrimeFieldExt, }; pub use array::typenum::consts; pub use bigint; diff --git a/primefield/src/macros.rs b/primefield/src/macros.rs index 79b7f82e1..f316c8d97 100644 --- a/primefield/src/macros.rs +++ b/primefield/src/macros.rs @@ -314,7 +314,7 @@ macro_rules! monty_field_element { } } - impl PrimeField for $fe { + impl $crate::ff::PrimeField for $fe { type Repr = $crate::MontyFieldBytes<$params, { <$params>::LIMBS }>; const MODULUS: &'static str = @@ -352,6 +352,11 @@ macro_rules! monty_field_element { } } + impl $crate::PrimeFieldExt for $fe { + const REPR_ENDIANNESS: $crate::ByteOrder = + <$params as $crate::MontyFieldParams<{ <$params>::LIMBS }>>::BYTE_ORDER; + } + $crate::field_op!($fe, Add, add, add); $crate::field_op!($fe, Sub, sub, sub); $crate::field_op!($fe, Mul, mul, multiply); @@ -794,8 +799,6 @@ macro_rules! monty_field_element_doc { "- [`PrimeField`] represents elements of prime fields and provides:\n", " - `from_repr`/`to_repr` for converting field elements from/to big integers.\n", " - `MULTIPLICATIVE_GENERATOR` and `ROOT_OF_UNITY` constants.\n", - "- [`PrimeFieldBits`] operations over field elements represented as bits ", - " (requires `bits` feature)\n", "\n", "Please see the documentation for the relevant traits for more information.\n" ) diff --git a/primefield/src/traits.rs b/primefield/src/traits.rs new file mode 100644 index 000000000..8a4f6fdac --- /dev/null +++ b/primefield/src/traits.rs @@ -0,0 +1,31 @@ +use crate::ByteOrder; + +/// Extension trait for [`ff::PrimeField`] which enables specifying the endianness in which +/// [`ff::PrimeField::Repr`] is encoded. +// TODO(tarcieri): remove this if/whenever zkcrypto/rfcs#4 lands. See also: zkcrypto/ff#158 +pub trait PrimeFieldExt: ff::PrimeField { + /// Endianness used when encoding [`ff::PrimeField::Repr`]. + const REPR_ENDIANNESS: ByteOrder = ByteOrder::BigEndian; + + /// Encode `self` using a big endian representation. + fn to_be_repr(&self) -> Self::Repr { + let mut repr = self.to_repr(); + + if Self::REPR_ENDIANNESS == ByteOrder::LittleEndian { + repr.as_mut().reverse(); + } + + repr + } + + /// Encode `self` using a little endian representation. + fn to_le_repr(&self) -> Self::Repr { + let mut repr = self.to_repr(); + + if Self::REPR_ENDIANNESS == ByteOrder::BigEndian { + repr.as_mut().reverse(); + } + + repr + } +} diff --git a/primeorder/Cargo.toml b/primeorder/Cargo.toml index a8137cee0..fff10c3ae 100644 --- a/primeorder/Cargo.toml +++ b/primeorder/Cargo.toml @@ -19,6 +19,7 @@ rust-version = "1.85" [dependencies] elliptic-curve = { version = "0.14.0-rc.34", default-features = false, features = ["arithmetic", "sec1"] } +primefield = "0.14.0-rc.11" # optional dependencies once_cell = { version = "1.21", optional = true, default-features = false } diff --git a/primeorder/src/lib.rs b/primeorder/src/lib.rs index b94a381f5..79d6463e7 100644 --- a/primeorder/src/lib.rs +++ b/primeorder/src/lib.rs @@ -34,21 +34,26 @@ pub use crate::{ pub use elliptic_curve::{ self, Field, FieldBytes, PrimeCurve, PrimeField, Scalar, array::{self, ArraySize, sizes::U1}, - bigint::modular::Retrieve, + bigint::{ByteOrder, modular::Retrieve}, point::Double, }; +pub use primefield::PrimeFieldExt; use elliptic_curve::{Curve, CurveArithmetic, Generate, ops::Invert, sec1, subtle::CtOption}; #[cfg(feature = "basepoint-table")] pub use crate::tables::BasepointTable; -/// Parameters for elliptic curves of prime order which can be described by the short -/// Weierstrass equation. +/// Parameters for elliptic curves of prime order which can be described by the short Weierstrass +/// equation. pub trait PrimeCurveParams: Curve + PrimeCurve - + CurveArithmetic, ProjectivePoint = ProjectivePoint> + + CurveArithmetic< + AffinePoint = AffinePoint, + ProjectivePoint = ProjectivePoint, + Scalar: PrimeFieldExt, + > { /// Base field element type. type FieldElement: Generate @@ -70,10 +75,6 @@ pub trait PrimeCurveParams: /// Generator point's affine coordinates: (x, y). const GENERATOR: (Self::FieldElement, Self::FieldElement); - - /// Are field element serializations for this curve big endian? - // TODO(tarcieri): make this a property of the scalar type, e.g. zkcrypto/ff#158 - const FIELD_REPR_IS_BE: bool = true; } /// Trait for specifying a constant-time basepoint table for a given curve. diff --git a/primeorder/src/projective.rs b/primeorder/src/projective.rs index dcc56d11c..b57526cd5 100644 --- a/primeorder/src/projective.rs +++ b/primeorder/src/projective.rs @@ -108,7 +108,7 @@ where Self: Double, { let table = LookupTable::new(*self); - let digits = Radix16Decomposition::new(k, C::FIELD_REPR_IS_BE); + let digits = Radix16Decomposition::new(k); lincomb::(&[table], &[digits]) } @@ -118,7 +118,7 @@ where Self: Double, { let table = LookupTable::new(*self); - let digits = Radix16Decomposition::new(k, C::FIELD_REPR_IS_BE); + let digits = Radix16Decomposition::new(k); lincomb_vartime::(&[table], &[digits]) } } @@ -448,7 +448,7 @@ where .collect(); let digits: Vec<_> = points_and_scalars .iter() - .map(|(_, scalar)| Radix16Decomposition::new(scalar, C::FIELD_REPR_IS_BE)) + .map(|(_, scalar)| Radix16Decomposition::new(scalar)) .collect(); lincomb::(&tables, &digits) @@ -462,7 +462,7 @@ where .collect(); let digits: Vec<_> = points_and_scalars .iter() - .map(|(_, scalar)| Radix16Decomposition::new(scalar, C::FIELD_REPR_IS_BE)) + .map(|(_, scalar)| Radix16Decomposition::new(scalar)) .collect(); lincomb_vartime::(&tables, &digits) @@ -475,18 +475,16 @@ where { fn lincomb(points_and_scalars: &[(Self, Scalar); N]) -> Self { let tables: [_; N] = array::from_fn(|index| LookupTable::new(points_and_scalars[index].0)); - let digits: [_; N] = array::from_fn(|index| { - Radix16Decomposition::new(&points_and_scalars[index].1, C::FIELD_REPR_IS_BE) - }); + let digits: [_; N] = + array::from_fn(|index| Radix16Decomposition::new(&points_and_scalars[index].1)); lincomb::(&tables, &digits) } fn lincomb_vartime(points_and_scalars: &[(Self, Scalar); N]) -> Self { let tables: [_; N] = array::from_fn(|index| LookupTable::new(points_and_scalars[index].0)); - let digits: [_; N] = array::from_fn(|index| { - Radix16Decomposition::new(&points_and_scalars[index].1, C::FIELD_REPR_IS_BE) - }); + let digits: [_; N] = + array::from_fn(|index| Radix16Decomposition::new(&points_and_scalars[index].1)); lincomb_vartime::(&tables, &digits) } diff --git a/primeorder/src/tables/basepoint.rs b/primeorder/src/tables/basepoint.rs index e66b0ea84..54845ff0c 100644 --- a/primeorder/src/tables/basepoint.rs +++ b/primeorder/src/tables/basepoint.rs @@ -80,7 +80,7 @@ impl /// Multiply `Point::generator` by the given scalar in constant-time, using the precomputed /// basepoint table to accelerate the scalar multiplication. pub fn mul(&self, k: &Scalar) -> ProjectivePoint { - let digits = Radix16Decomposition::>::new(k, true); + let digits = Radix16Decomposition::>::new(k); let len = FieldBytesSize::::USIZE; let mut acc = self[len].select(digits[len * 2]); let mut acc2 = ProjectivePoint::::IDENTITY; @@ -107,7 +107,7 @@ impl /// used with them. /// pub fn mul_vartime(&self, k: &Scalar) -> ProjectivePoint { - let digits = Radix16Decomposition::>::new(k, true); + let digits = Radix16Decomposition::>::new(k); let len = FieldBytesSize::::USIZE; let mut acc = self[len].select_vartime(digits[len * 2]); let mut acc2 = ProjectivePoint::::IDENTITY; diff --git a/primeorder/src/tables/radix16.rs b/primeorder/src/tables/radix16.rs index 7f20290c6..bcad45b93 100644 --- a/primeorder/src/tables/radix16.rs +++ b/primeorder/src/tables/radix16.rs @@ -1,8 +1,9 @@ //! Radix-16 signed-digit decomposition for constant-time scalar multiplication. +use crate::PrimeFieldExt; use core::ops::{Add, Index}; use elliptic_curve::{ - FieldBytesSize, PrimeField, + FieldBytesSize, array::{Array, ArraySize, sizes::U1}, }; @@ -27,17 +28,13 @@ impl Radix16Decomposition { /// decomposition can be negative, so we need an additional byte to store it. /// /// Assumes `x < 2^(4*(digits-1))`. - // TODO(tarcieri): get rid of `is_be` flag w\ e.g. zkcrypto/ff#158 - pub fn new(scalar: &Scalar, is_be: bool) -> Self { + pub fn new(scalar: &Scalar) -> Self { // TODO(tarcieri): `debug_assert!` that `scalar < 2^(4*(digits-1))` let mut ret = Self::default(); // Step 1: change radix. // Convert from big endian radix-256 (bytes) to radix-16 (nibbles). - let mut repr = scalar.to_repr(); - if !is_be { - repr.as_mut().reverse(); - } + let repr = scalar.to_be_repr(); let bytes = repr.as_ref(); for i in 0..(Digits::USIZE - 1) / 2 {