use core::{fmt, str::FromStr};
use ark_ff::{BigInteger, PrimeField};
use num_bigint::BigUint;
pub use ark_bn254::Fr;
pub const FIELD_MODULUS_DEC: &str =
"21888242871839275222246405745257275088548364400416034343698204186575808495617";
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Bn254Fr(Fr);
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Bn254FrError {
InvalidDecimal,
NonCanonicalDecimal,
OutOfRange,
}
impl fmt::Display for Bn254FrError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidDecimal => f.write_str("invalid unsigned decimal field element"),
Self::NonCanonicalDecimal => f.write_str("non-canonical decimal field element"),
Self::OutOfRange => {
f.write_str("field element is greater than or equal to the BN254 modulus")
}
}
}
}
impl std::error::Error for Bn254FrError {}
impl Bn254Fr {
pub fn try_from_dec(s: &str) -> Result<Self, Bn254FrError> {
if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
return Err(Bn254FrError::InvalidDecimal);
}
if s.len() > 1 && s.starts_with('0') {
return Err(Bn254FrError::NonCanonicalDecimal);
}
let value = BigUint::parse_bytes(s.as_bytes(), 10).ok_or(Bn254FrError::InvalidDecimal)?;
let modulus =
BigUint::parse_bytes(FIELD_MODULUS_DEC.as_bytes(), 10).expect("valid BN254 modulus");
if value >= modulus {
return Err(Bn254FrError::OutOfRange);
}
Ok(Self(fr_from_biguint(&value)))
}
#[inline]
pub fn from_fr(value: Fr) -> Self {
Self(value)
}
#[inline]
pub fn as_fr(&self) -> &Fr {
&self.0
}
#[inline]
pub fn into_inner(self) -> Fr {
self.0
}
pub fn to_dec(self) -> String {
fr_to_dec(&self.0)
}
pub fn to_le_32(self) -> [u8; 32] {
let bytes = fr_to_biguint(&self.0).to_bytes_le();
let mut out = [0u8; 32];
out[..bytes.len()].copy_from_slice(&bytes);
out
}
}
pub fn fr_from_dec(s: &str) -> Fr {
Fr::from_str(s).unwrap_or_else(|_| panic!("invalid field decimal: {s:?}"))
}
pub fn fr_from_biguint(v: &BigUint) -> Fr {
Fr::from_be_bytes_mod_order(&v.to_bytes_be())
}
pub fn fr_to_dec(x: &Fr) -> String {
fr_to_biguint(x).to_str_radix(10)
}
pub fn fr_to_biguint(x: &Fr) -> BigUint {
BigUint::from_bytes_be(&x.into_bigint().to_bytes_be())
}
pub fn fr_to_be_32(x: &Fr) -> [u8; 32] {
let be = x.into_bigint().to_bytes_be(); debug_assert_eq!(be.len(), 32);
let mut out = [0u8; 32];
out.copy_from_slice(&be);
out
}
pub fn fr_from_be_bytes_mod(bytes: &[u8]) -> Fr {
Fr::from_be_bytes_mod_order(bytes)
}
pub fn fr_from_be_32_checked(bytes: &[u8]) -> Option<Fr> {
if bytes.len() != 32 {
return None;
}
let value = Fr::from_be_bytes_mod_order(bytes);
(fr_to_be_32(&value).as_slice() == bytes).then_some(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decimal_roundtrip() {
for s in [
"0",
"1",
"42",
"21888242871839275222246405745257275088548364400416034343698204186575808495616",
] {
assert_eq!(fr_to_dec(&fr_from_dec(s)), s);
}
}
#[test]
fn from_dec_reduces_out_of_range() {
let p_plus_5 =
"21888242871839275222246405745257275088548364400416034343698204186575808495622";
assert_eq!(fr_from_dec(p_plus_5), Fr::from(5u64));
assert_eq!(fr_from_dec("-1"), -Fr::from(1u64));
}
#[test]
fn field_modulus_constant_matches_arkworks() {
let be = <Fr as PrimeField>::MODULUS.to_bytes_be();
let dec = BigUint::from_bytes_be(&be).to_str_radix(10);
assert_eq!(dec, FIELD_MODULUS_DEC);
}
#[test]
fn canonical_binary_decoder_rejects_reduction_and_wrong_lengths() {
let five = fr_to_be_32(&Fr::from(5u64));
assert_eq!(fr_from_be_32_checked(&five), Some(Fr::from(5u64)));
assert_eq!(fr_from_be_32_checked(&five[..31]), None);
let modulus = <Fr as PrimeField>::MODULUS.to_bytes_be();
assert_eq!(fr_from_be_32_checked(&modulus), None);
}
#[test]
fn checked_field_parser_rejects_reduction_and_noncanonical_decimal() {
assert_eq!(Bn254Fr::try_from_dec("42").unwrap().to_dec(), "42");
assert_eq!(
Bn254Fr::try_from_dec("00"),
Err(Bn254FrError::NonCanonicalDecimal)
);
assert_eq!(
Bn254Fr::try_from_dec("-1"),
Err(Bn254FrError::InvalidDecimal)
);
assert_eq!(
Bn254Fr::try_from_dec(FIELD_MODULUS_DEC),
Err(Bn254FrError::OutOfRange)
);
}
}