From c46c9233e765c9accdb41cecd5b7e68ffadda4d0 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 20 Jun 2026 21:19:41 -0600 Subject: [PATCH] elliptic-curve: replace `FieldBytesEncoding` with `FIELD_ENDIANNESS` Removes the `FieldBytesEncoding` trait and replaces it with: - `Curve::FIELD_ENDIANNESS` which defaults to `ByteOrder::BigEndian` - `field::{bytes_to_uint, uint_to_bytes}` generic free functions This constant annoyingly duplicates the ones we have elsewhere, but without getting it upstream into `ff` (zkcrypto/ff#158) there is no single other convenient place to hang it but here. Ideally this functionality could be phased out completely, but there are still places that need it for now (e.g. legacy `rfc6979`, ECDSA recovery). In a future breaking release after the imminent one, we need to refactor everything so the base and scalar fields are treated completely separately. At that point, hopefully this all can and will need to go away, and we will be able to leverage an upstream endianness constant instead of having to stick (another) one in `elliptic-curve`. --- Cargo.lock | 5 +- Cargo.toml | 2 + elliptic-curve/Cargo.toml | 2 +- elliptic-curve/src/dev/mock_curve.rs | 5 +- elliptic-curve/src/field.rs | 68 +++++++++++++--------------- elliptic-curve/src/lib.rs | 11 +++-- elliptic-curve/src/scalar/value.rs | 7 +-- 7 files changed, 48 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ad84075ec..a3ba8e06b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,9 +141,8 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a0d26b245348befa0c121944541476763dcc46ede886c88f9d12e1697d27c3" +version = "0.7.4" +source = "git+https://github.com/RustCrypto/crypto-bigint#ec6615fad09d26f8518378f1fdfe96a635ddab2d" dependencies = [ "cpubits", "ctutils", diff --git a/Cargo.toml b/Cargo.toml index 79c85b5b2..90dd21ad4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,3 +57,5 @@ unused_qualifications = "warn" crypto-common = { path = "crypto-common" } digest = { path = "digest" } signature = { path = "signature" } + +crypto-bigint = { git = "https://github.com/RustCrypto/crypto-bigint" } diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index 66379b977..48b65101e 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -17,7 +17,7 @@ representing various elliptic curve forms, scalars, points, and public/secret ke [dependencies] array = { package = "hybrid-array", version = "0.4", default-features = false, features = ["zeroize"] } -bigint = { package = "crypto-bigint", version = "0.7", default-features = false, features = ["hybrid-array", "rand_core", "subtle", "zeroize"] } +bigint = { package = "crypto-bigint", version = "0.7.4", default-features = false, features = ["hybrid-array", "rand_core", "subtle", "zeroize"] } base16ct = "1" common = { package = "crypto-common", version = "0.2", features = ["rand_core"] } rand_core = { version = "0.10", default-features = false } diff --git a/elliptic-curve/src/dev/mock_curve.rs b/elliptic-curve/src/dev/mock_curve.rs index 1057517e3..5e0d70826 100644 --- a/elliptic-curve/src/dev/mock_curve.rs +++ b/elliptic-curve/src/dev/mock_curve.rs @@ -5,8 +5,7 @@ //! generically over curves without having to pull in a complete curve implementation. use crate::{ - BatchNormalize, Curve, CurveAffine, CurveArithmetic, CurveGroup, FieldBytesEncoding, Generate, - PrimeCurve, + BatchNormalize, Curve, CurveAffine, CurveArithmetic, CurveGroup, Generate, PrimeCurve, array::typenum::U32, bigint::{Limb, Odd, U256, modular::Retrieve}, ctutils, @@ -350,8 +349,6 @@ impl Retrieve for Scalar { } } -impl FieldBytesEncoding for U256 {} - impl From for Scalar { fn from(n: u64) -> Scalar { Self(n.into()) diff --git a/elliptic-curve/src/field.rs b/elliptic-curve/src/field.rs index 0e068e235..c61c90b17 100644 --- a/elliptic-curve/src/field.rs +++ b/elliptic-curve/src/field.rs @@ -1,10 +1,8 @@ -//! Field elements. +//! Field element encoding support. -use crate::{ - Curve, - bigint::{ArrayEncoding, ByteArray, Integer}, -}; +use crate::Curve; use array::{Array, typenum::Unsigned}; +use bigint::{ArrayEncoding, ByteOrder, Encoding}; /// Size of serialized field elements of this elliptic curve. pub type FieldBytesSize = ::FieldBytesSize; @@ -12,40 +10,38 @@ pub type FieldBytesSize = ::FieldBytesSize; /// Byte representation of a base/scalar field element of a given curve. pub type FieldBytes = Array>; -/// Trait for decoding/encoding `Curve::Uint` from/to [`FieldBytes`] using -/// curve-specific rules. +/// Decode the provided [`FieldBytes`] as an integer. /// -/// Namely a curve's modulus may be smaller than the big integer type used to -/// internally represent field elements (since the latter are multiples of the -/// limb size), such as in the case of curves like NIST P-224 and P-521, and so -/// it may need to be padded/truncated to the right length. +/// Note that the resulting integer is the raw representation of the given `bytes` and is not +/// reduced by any modulus. +pub fn bytes_to_uint(bytes: &FieldBytes) -> C::Uint { + C::Uint::from_slice_truncated(bytes, modulus_bits::(), C::FIELD_ENDIANNESS) +} + +/// Encode the provided integer as [`FieldBytes`]. /// -/// Additionally, different curves have different endianness conventions, also -/// captured here. -pub trait FieldBytesEncoding: ArrayEncoding + Integer -where - C: Curve, -{ - /// Decode unsigned integer from serialized field element. - /// - /// The default implementation assumes a big endian encoding. - fn decode_field_bytes(field_bytes: &FieldBytes) -> Self { - debug_assert!(field_bytes.len() <= Self::ByteSize::USIZE); - let mut byte_array = ByteArray::::default(); - let offset = Self::ByteSize::USIZE.saturating_sub(field_bytes.len()); - byte_array[offset..].copy_from_slice(field_bytes); - Self::from_be_byte_array(byte_array) +/// Note that the output may be truncated if it overflows the width of [`FieldBytes`]. +pub fn uint_to_bytes(uint: &C::Uint) -> FieldBytes { + let field_bytes_len = FieldBytesSize::::USIZE; + let uint_bytes_len = <::Uint as ArrayEncoding>::ByteSize::USIZE; + debug_assert!(field_bytes_len <= uint_bytes_len); + + let mut field_bytes = FieldBytes::::default(); + match C::FIELD_ENDIANNESS { + ByteOrder::BigEndian => { + let offset = uint_bytes_len.saturating_sub(field_bytes_len); + field_bytes.copy_from_slice(&uint.to_be_byte_array()[offset..]); + } + ByteOrder::LittleEndian => { + field_bytes.copy_from_slice(&uint.to_le_byte_array()[..field_bytes_len]); + } } - /// Encode unsigned integer into serialized field element. - /// - /// The default implementation assumes a big endian encoding. - fn encode_field_bytes(&self) -> FieldBytes { - let mut field_bytes = FieldBytes::::default(); - debug_assert!(field_bytes.len() <= Self::ByteSize::USIZE); + field_bytes +} - let offset = Self::ByteSize::USIZE.saturating_sub(field_bytes.len()); - field_bytes.copy_from_slice(&self.to_be_byte_array()[offset..]); - field_bytes - } +// TODO(tarcieri): store full bit precision of the modulus on `Curve` +#[allow(clippy::cast_possible_truncation)] +const fn modulus_bits() -> u32 { + (FieldBytesSize::::USIZE * 8) as u32 } diff --git a/elliptic-curve/src/lib.rs b/elliptic-curve/src/lib.rs index a2443fd1b..a89bb985f 100644 --- a/elliptic-curve/src/lib.rs +++ b/elliptic-curve/src/lib.rs @@ -64,6 +64,7 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; +pub mod field; pub mod point; pub mod scalar; @@ -77,7 +78,6 @@ pub mod ops; pub mod sec1; mod error; -mod field; mod macros; mod secret_key; @@ -88,14 +88,13 @@ mod public_key; pub use crate::{ error::{Error, Result}, - field::{FieldBytes, FieldBytesEncoding, FieldBytesSize}, + field::{FieldBytes, FieldBytesSize}, scalar::ScalarValue, secret_key::SecretKey, }; pub use array; pub use array::typenum::consts; -pub use bigint; -pub use bigint::ctutils; +pub use bigint::{self, ByteOrder, ctutils}; pub use common; pub use common::Generate; pub use rand_core; @@ -163,11 +162,13 @@ pub trait Curve: 'static + Copy + Clone + Debug + Default + Eq + Ord + Send + Sy + bigint::RandomMod + bigint::Unsigned + zeroize::Zeroize - + FieldBytesEncoding + ShrAssign; /// Order of this curve's prime order subgroup, i.e. number of elements in the scalar field. const ORDER: Odd; + + /// Endianness used for serializing field elements of this curve. + const FIELD_ENDIANNESS: ByteOrder = ByteOrder::BigEndian; } /// Marker trait for elliptic curves with prime order. diff --git a/elliptic-curve/src/scalar/value.rs b/elliptic-curve/src/scalar/value.rs index 1a986c658..f649da577 100644 --- a/elliptic-curve/src/scalar/value.rs +++ b/elliptic-curve/src/scalar/value.rs @@ -1,10 +1,11 @@ //! Integer values within the range of a given [`Curve`]'s scalar modulus. use crate::{ - Curve, Error, FieldBytes, FieldBytesEncoding, Result, + Curve, Error, FieldBytes, Result, array::Array, bigint::{AddMod, ConstOne, ConstZero, Integer, Limb, NegMod, Odd, RandomMod, SubMod, Zero}, ctutils::{self, CtEq, CtGt, CtLt, CtSelect}, + field, scalar::{FromUintUnchecked, IsHigh}, }; use base16ct::HexDisplay; @@ -74,7 +75,7 @@ where /// Decode [`ScalarValue`] from a serialized field element pub fn from_bytes(bytes: &FieldBytes) -> CtOption { - Self::new(C::Uint::decode_field_bytes(bytes)) + Self::new(field::bytes_to_uint::(bytes)) } /// Decode [`ScalarValue`] from a big endian byte slice. @@ -114,7 +115,7 @@ where /// Encode [`ScalarValue`] as a serialized field element. pub fn to_bytes(&self) -> FieldBytes { - self.inner.encode_field_bytes() + field::uint_to_bytes::(&self.inner) } /// Convert to a `C::Uint`.