Skip to content

Commit ab3f85e

Browse files
committed
[WIP] primefield: generic MontyFieldElement type
The previous implementation was written entirely in terms of macros. Leveraging types from `crypto-bigint`, this provides a generic field element type with an internal Montgomery form representation.
1 parent 9b162e5 commit ab3f85e

File tree

5 files changed

+231
-1
lines changed

5 files changed

+231
-1
lines changed

p256/src/arithmetic/field.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use core::ops::Mul;
1111
use elliptic_curve::{
1212
FieldBytesEncoding,
1313
bigint::U256,
14+
consts::U32,
1415
ff::PrimeField,
1516
subtle::{Choice, ConstantTimeEq, CtOption},
1617
};
@@ -26,6 +27,19 @@ const R2: FieldElement = FieldElement(U256::from_be_hex(
2627
"00000004fffffffdfffffffffffffffefffffffbffffffff0000000000000003",
2728
));
2829

30+
primefield::monty_field_params!(
31+
MontyFieldParams,
32+
MODULUS_HEX,
33+
U256,
34+
U32,
35+
primefield::ByteOrder::BigEndian,
36+
"P-256 field modulus"
37+
);
38+
39+
/// An element in the finite field modulo p = 2^{224}(2^{32} − 1) + 2^{192} + 2^{96} − 1.
40+
#[allow(dead_code)]
41+
pub type MontyFieldElement = primefield::MontyFieldElement<MontyFieldParams, { U256::LIMBS }>;
42+
2943
/// An element in the finite field modulo p = 2^{224}(2^{32} − 1) + 2^{192} + 2^{96} − 1.
3044
///
3145
/// The internal representation is in little-endian order. Elements are always in

p256/src/arithmetic/scalar.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use core::{
1414
use elliptic_curve::{
1515
Curve,
1616
bigint::{Limb, U256, prelude::*},
17+
consts::U32,
1718
group::ff::{self, Field, PrimeField},
1819
ops::{Invert, Reduce, ReduceNonZero},
1920
rand_core::TryRngCore,
@@ -41,6 +42,19 @@ pub(crate) const MODULUS: U256 = NistP256::ORDER;
4142
/// `MODULUS / 2`
4243
const FRAC_MODULUS_2: Scalar = Scalar(MODULUS.shr_vartime(1));
4344

45+
primefield::monty_field_params!(
46+
MontyScalarParams,
47+
ORDER_HEX,
48+
U256,
49+
U32,
50+
primefield::ByteOrder::BigEndian,
51+
"P-256 scalar modulus"
52+
);
53+
54+
/// Scalars are elements in the finite field modulo n.
55+
#[allow(dead_code)]
56+
pub type MontyScalar = primefield::MontyFieldElement<MontyScalarParams, { U256::LIMBS }>;
57+
4458
/// Scalars are elements in the finite field modulo n.
4559
///
4660
/// # Trait impls

primefield/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ edition = "2024"
1414
rust-version = "1.85"
1515

1616
[dependencies]
17-
bigint = { package = "crypto-bigint", version = "=0.7.0-pre.7", default-features = false }
17+
bigint = { package = "crypto-bigint", version = "=0.7.0-pre.7", default-features = false, features = ["hybrid-array"] }
1818
ff = { version = "=0.14.0-pre.0", default-features = false }
1919
subtle = { version = "2.6", default-features = false }
2020
rand_core = { version = "0.9", default-features = false }

primefield/src/lib.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,28 @@
88
#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
99
#![doc = include_str!("../README.md")]
1010

11+
pub use array::typenum::consts;
1112
pub use bigint;
13+
pub use bigint::hybrid_array as array;
1214
pub use ff;
1315
pub use rand_core;
1416
pub use subtle;
1517
pub use zeroize;
1618

19+
pub use crate::monty::{MontyFieldElement, MontyFieldParams};
20+
1721
mod fiat;
22+
mod monty;
23+
24+
/// Byte order used when encoding/decoding field elements as bytestrings.
25+
#[derive(Debug)]
26+
pub enum ByteOrder {
27+
/// Big endian.
28+
BigEndian,
29+
30+
/// Little endian.
31+
LittleEndian,
32+
}
1833

1934
/// Implements a field element type whose internal representation is in
2035
/// Montgomery form, providing a combination of trait impls and inherent impls

primefield/src/monty.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
//! Field elements which use an internal Montgomery form representation, implemented using
2+
//! `crypto-bigint`'s [`MontyForm`].
3+
4+
use crate::ByteOrder;
5+
use bigint::{
6+
ArrayEncoding, ByteArray, Uint,
7+
hybrid_array::{Array, ArraySize, typenum::Unsigned},
8+
modular::{ConstMontyForm as MontyForm, ConstMontyParams},
9+
};
10+
use subtle::{ConstantTimeLess, CtOption};
11+
12+
/// Creates a ZST representing the Montgomery parameters for a given field modulus.
13+
///
14+
/// Accepts the following parameters:
15+
///
16+
/// - name of the ZST representing the field modulus
17+
/// - hex serialization of the modulus
18+
/// - `crypto-bigint` unsigned integer type (e.g. U256)
19+
/// - number of bytes in an encoded field element
20+
/// - byte order to use when encoding/decoding field elements
21+
/// - documentation string for the field modulus type
22+
///
23+
/// ```
24+
/// use primefield::{ByteOrder, bigint::U256, consts::U32};
25+
///
26+
/// primefield::monty_field_params!(
27+
/// FieldParams,
28+
/// "ffffffff00000001000000000000000000000000ffffffffffffffffffffffff",
29+
/// U256,
30+
/// U32,
31+
/// ByteOrder::BigEndian,
32+
/// "P-256 field modulus"
33+
/// );
34+
/// ```
35+
#[macro_export]
36+
macro_rules! monty_field_params {
37+
($name:ident, $modulus_hex:expr, $uint_type:ty, $byte_size:ty, $byte_order:expr, $doc:expr) => {
38+
$crate::bigint::const_monty_params!($name, $uint_type, $modulus_hex, $doc);
39+
40+
impl $crate::MontyFieldParams<{ <$uint_type>::LIMBS }> for $name {
41+
type ByteSize = $byte_size;
42+
const BYTE_ORDER: $crate::ByteOrder = $byte_order;
43+
}
44+
};
45+
}
46+
47+
/// Extension trait for defining additional field parameters beyond the ones provided by
48+
/// [`ConstMontyParams`].
49+
pub trait MontyFieldParams<const LIMBS: usize>: ConstMontyParams<LIMBS> {
50+
/// Size of a field element when serialized as bytes.
51+
type ByteSize: ArraySize;
52+
53+
/// Byte order to use when serializing a field element as byte.
54+
const BYTE_ORDER: ByteOrder;
55+
}
56+
57+
/// Field element type which uses an internal Montgomery form representation.
58+
#[derive(Clone, Copy)]
59+
pub struct MontyFieldElement<MOD: MontyFieldParams<LIMBS>, const LIMBS: usize>(
60+
MontyForm<MOD, LIMBS>,
61+
);
62+
63+
impl<MOD: MontyFieldParams<LIMBS>, const LIMBS: usize> MontyFieldElement<MOD, LIMBS> {
64+
/// Zero element (additive identity).
65+
pub const ZERO: Self = Self(MontyForm::ZERO);
66+
67+
/// Multiplicative identity.
68+
pub const ONE: Self = Self(MontyForm::ONE);
69+
70+
/// Number of limbs used by the internal integer representation.
71+
pub const LIMBS: usize = LIMBS;
72+
73+
/// Decode field element from a canonical bytestring representation.
74+
#[inline]
75+
pub fn from_bytes(repr: &Array<u8, MOD::ByteSize>) -> CtOption<Self>
76+
where
77+
Uint<LIMBS>: ArrayEncoding,
78+
{
79+
debug_assert!(repr.len() <= MOD::ByteSize::USIZE);
80+
let mut byte_array = ByteArray::<Uint<LIMBS>>::default();
81+
let offset = MOD::ByteSize::USIZE.saturating_sub(repr.len());
82+
83+
let uint = match MOD::BYTE_ORDER {
84+
ByteOrder::BigEndian => {
85+
byte_array[offset..].copy_from_slice(repr);
86+
Uint::from_be_byte_array(byte_array)
87+
}
88+
ByteOrder::LittleEndian => {
89+
byte_array[..offset].copy_from_slice(repr);
90+
Uint::from_le_byte_array(byte_array)
91+
}
92+
};
93+
94+
Self::from_uint(&uint)
95+
}
96+
97+
/// Decode field element from a canonical byte slice.
98+
///
99+
/// Slice is expected to be zero padded to the expected byte size.
100+
#[inline]
101+
pub fn from_slice(slice: &[u8]) -> Option<Self>
102+
where
103+
Uint<LIMBS>: ArrayEncoding,
104+
{
105+
let array = Array::try_from(slice).ok()?;
106+
Self::from_bytes(&array).into()
107+
}
108+
109+
/// Decode a field element from hex-encoded bytes.
110+
///
111+
/// Does *not* perform a check that the field element does not overflow the order.
112+
///
113+
/// This method is primarily intended for defining internal constants.
114+
pub const fn from_hex(hex: &str) -> Self {
115+
let uint = match MOD::BYTE_ORDER {
116+
ByteOrder::BigEndian => Uint::from_be_hex(hex),
117+
ByteOrder::LittleEndian => Uint::from_le_hex(hex),
118+
};
119+
120+
// TODO(tarcieri): ensure value does not overflow the modulus (RustCrypto/crypto-bigint#881)
121+
// if uint.lt(MOD::PARAMS.modulus().as_ref()).is_false_vartime() {
122+
// panic!("hex encoded field element overflows modulus");
123+
// }
124+
125+
Self::from_uint_unchecked(&uint)
126+
}
127+
128+
/// Convert [`Uint`] into [`MontyFieldElement`], first converting it into Montgomery form:
129+
///
130+
/// ```text
131+
/// w * R^2 * R^-1 mod p = wR mod p
132+
/// ```
133+
///
134+
/// Does *NOT* ensure that the input value is within range of the modulus!
135+
#[inline]
136+
pub const fn from_uint_unchecked(uint: &Uint<LIMBS>) -> Self {
137+
Self(MontyForm::new(uint))
138+
}
139+
140+
/// Convert [`Uint`] into [`MontyFieldElement`], first converting it into Montgomery form:
141+
///
142+
/// ```text
143+
/// w * R^2 * R^-1 mod p = wR mod p
144+
/// ```
145+
#[inline]
146+
pub fn from_uint(uint: &Uint<LIMBS>) -> CtOption<Self> {
147+
let is_some = uint.ct_lt(MOD::PARAMS.modulus());
148+
CtOption::new(Self::from_uint_unchecked(uint), is_some)
149+
}
150+
151+
/// Convert a `u64` into a [`MontyFieldElement`].
152+
///
153+
/// Does *NOT* ensure that the modulus is greater than 64-bits!
154+
#[inline]
155+
pub const fn from_u64(w: u64) -> Self {
156+
if MOD::PARAMS.modulus().as_ref().bits() <= 64 {
157+
panic!("modulus is too small to ensure all u64s are in range");
158+
}
159+
160+
Self::from_uint_unchecked(&Uint::from_u64(w))
161+
}
162+
163+
/// Returns the bytestring encoding of this field element.
164+
#[inline]
165+
pub fn to_bytes(self) -> Array<u8, MOD::ByteSize>
166+
where
167+
Uint<LIMBS>: ArrayEncoding,
168+
{
169+
let mut repr = Array::<u8, MOD::ByteSize>::default();
170+
debug_assert!(repr.len() <= MOD::ByteSize::USIZE);
171+
172+
let offset = MOD::ByteSize::USIZE.saturating_sub(repr.len());
173+
174+
match MOD::BYTE_ORDER {
175+
ByteOrder::BigEndian => {
176+
let padded = self.0.retrieve().to_be_byte_array();
177+
repr.copy_from_slice(&padded[offset..]);
178+
}
179+
ByteOrder::LittleEndian => {
180+
let padded = self.0.retrieve().to_le_byte_array();
181+
repr.copy_from_slice(&padded[..offset]);
182+
}
183+
}
184+
185+
repr
186+
}
187+
}

0 commit comments

Comments
 (0)