Skip to main content

soroban_sdk/crypto/
bn254.rs

1#[cfg(not(target_family = "wasm"))]
2use crate::xdr::ScVal;
3use crate::{
4    crypto::utils::BigInt,
5    env::internal::{self, BytesObject, U256Val, U64Val},
6    unwrap::{UnwrapInfallible, UnwrapOptimized},
7    Bytes, BytesN, ConversionError, Env, IntoVal, TryFromVal, Val, Vec, U256,
8};
9use core::{
10    cmp::Ordering,
11    fmt::Debug,
12    ops::{Add, Mul, Neg, Sub},
13};
14
15pub const BN254_FP_SERIALIZED_SIZE: usize = 32; // Size in bytes of a serialized Bn254Fp element in BN254. The field modulus is 254 bits, requiring 32 bytes (256 bits).
16pub const BN254_G1_SERIALIZED_SIZE: usize = BN254_FP_SERIALIZED_SIZE * 2; // Size in bytes of a serialized G1 element in BN254. Each coordinate (X, Y) is 32 bytes.
17pub const BN254_G2_SERIALIZED_SIZE: usize = BN254_G1_SERIALIZED_SIZE * 2; // Size in bytes of a serialized G2 element in BN254. Each coordinate (X, Y) is 64 bytes (2 Bn254Fp elements per coordinate).
18
19/// Bn254 provides access to curve and pairing operations on the BN254
20/// (also known as alt_bn128) curve.
21pub struct Bn254 {
22    env: Env,
23}
24
25/// `Bn254G1Affine` is a point in the G1 group (subgroup defined over the base field
26/// `Fq` with prime order `q =
27/// 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47`) of the
28/// BN254 elliptic curve.
29///
30/// This type is a thin wrapper around `BytesN<64>`. The [`from_bytes`](Self::from_bytes)
31/// constructor does **not** validate the contents — it accepts any 64 bytes.
32/// The serialization requirements below are enforced by the Soroban host when
33/// the value is passed to a host function (e.g. `g1_add`, `g1_mul`, `pairing`).
34/// Invalid bytes will cause the host call to trap, not construction.
35///
36/// # Serialization (Ethereum-compatible format):
37/// - The 64 bytes represent the **uncompressed encoding** of a point in G1
38/// - Format: `be_bytes(X) || be_bytes(Y)` where `||` denotes concatenation
39/// - X and Y are curve coordinates, each a 32-byte big-endian Bn254Fp field element
40/// - The two flag bits (bits 0x80 and 0x40 of the first byte) must be unset
41/// - The point at infinity is encoded as 64 zero bytes
42/// - Points must be on the curve (no subgroup check required for G1)
43#[derive(Clone)]
44#[repr(transparent)]
45pub struct Bn254G1Affine(BytesN<BN254_G1_SERIALIZED_SIZE>);
46
47/// `Bn254G2Affine` is a point in the G2 group (subgroup defined over the quadratic
48/// extension field `Fq2`) of the BN254 elliptic curve.
49///
50/// This type is a thin wrapper around `BytesN<128>`. The [`from_bytes`](Self::from_bytes)
51/// constructor does **not** validate the contents — it accepts any 128 bytes.
52/// The serialization requirements below are enforced by the Soroban host when
53/// the value is passed to a host function (e.g. `g2_add`, `g2_mul`, `pairing`).
54/// Invalid bytes will cause the host call to trap, not construction.
55///
56/// # Serialization (Ethereum-compatible format):
57/// - The 128 bytes represent the **uncompressed encoding** of a point in G2
58/// - Format: `be_bytes(X) || be_bytes(Y)` where each coordinate is an Fp2
59/// element (64 bytes) - Fp2 element encoding: `be_bytes(c1) || be_bytes(c0)`
60/// where:
61///   - c0 is the real component (32-byte big-endian Bn254Fp element)
62///   - c1 is the imaginary component (32-byte big-endian Bn254Fp element)
63/// - The two flag bits (bits 0x80 and 0x40 of the first byte) must be unset
64/// - The point at infinity is encoded as 128 zero bytes
65/// - Points must be on the curve AND in the correct subgroup
66#[derive(Clone)]
67#[repr(transparent)]
68pub struct Bn254G2Affine(BytesN<BN254_G2_SERIALIZED_SIZE>);
69
70/// `Bn254Fr` represents an element in the BN254 scalar field, which is a prime
71/// field of order `r =
72/// 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001`. The
73/// struct is internally represented with a `U256`, all arithmetic operations
74/// follow modulo `r`.
75#[derive(Clone)]
76#[repr(transparent)]
77pub struct Bn254Fr(U256);
78
79/// Deprecated type alias for `Bn254Fr`.
80/// Use `Bn254Fr` to avoid ambiguity with `Bls12381Fr`.
81#[deprecated(note = "use `Bn254Fr` instead to avoid ambiguity with `Bls12381Fr`")]
82pub type Fr = Bn254Fr;
83
84/// `Bn254Fp` represents an element of the base field `Bn254Fp` of the BN254 elliptic curve
85///
86/// # Serialization:
87/// - The 32 bytes represent the **big-endian encoding** of an element in the
88///   field `Bn254Fp`. The value is serialized as a big-endian integer.
89#[derive(Clone)]
90#[repr(transparent)]
91pub struct Bn254Fp(BytesN<BN254_FP_SERIALIZED_SIZE>);
92
93impl_bytesn_repr!(Bn254G1Affine, BN254_G1_SERIALIZED_SIZE);
94impl_bytesn_repr!(Bn254G2Affine, BN254_G2_SERIALIZED_SIZE);
95impl_bytesn_repr!(Bn254Fp, BN254_FP_SERIALIZED_SIZE);
96
97// BN254 base field modulus p in big-endian bytes.
98// p = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47
99const BN254_FP_MODULUS_BE: [u8; BN254_FP_SERIALIZED_SIZE] = [
100    0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, 0x58, 0x5d,
101    0x97, 0x81, 0x6a, 0x91, 0x68, 0x71, 0xca, 0x8d, 0x3c, 0x20, 0x8c, 0x16, 0xd8, 0x7c, 0xfd, 0x47,
102];
103
104fn validate_bn254_fp(bytes: &[u8; BN254_FP_SERIALIZED_SIZE]) {
105    if bytes >= &BN254_FP_MODULUS_BE {
106        sdk_panic!("Bn254: Invalid Fp");
107    }
108}
109
110impl Bn254G1Affine {
111    /// Wraps raw bytes as a G1 point without validation.
112    /// See [`Bn254G1Affine`] for serialization requirements enforced by the host.
113    pub fn from_bytes(bytes: BytesN<BN254_G1_SERIALIZED_SIZE>) -> Self {
114        Self(bytes)
115    }
116}
117
118impl Bn254G2Affine {
119    /// Wraps raw bytes as a G2 point without validation.
120    /// See [`Bn254G2Affine`] for serialization requirements enforced by the host.
121    pub fn from_bytes(bytes: BytesN<BN254_G2_SERIALIZED_SIZE>) -> Self {
122        Self(bytes)
123    }
124}
125
126impl Bn254Fp {
127    pub fn from_bytes(bytes: BytesN<BN254_FP_SERIALIZED_SIZE>) -> Self {
128        validate_bn254_fp(&bytes.to_array());
129        Self(bytes)
130    }
131}
132
133impl Bn254G1Affine {
134    pub fn env(&self) -> &Env {
135        self.0.env()
136    }
137}
138
139impl Bn254Fp {
140    pub fn env(&self) -> &Env {
141        self.0.env()
142    }
143
144    // `Bn254Fp` represents an element in the base field of the BN254 elliptic curve.
145    // For an element a ∈ Bn254Fp, its negation `-a` is defined as:
146    //   a + (-a) = 0 (mod p)
147    // where `p` is the field modulus, and to make a valid point coordinate on the
148    // curve, `a` also must be within the field range (i.e., 0 ≤ a < p).
149    fn checked_neg(&self) -> Option<Bn254Fp> {
150        let fq_bigint: BigInt<4> = (&self.0).into();
151        if fq_bigint.is_zero() {
152            return Some(self.clone());
153        }
154
155        //BN254 base field modulus
156        const BN254_MODULUS: [u64; 4] = [
157            4332616871279656263,
158            10917124144477883021,
159            13281191951274694749,
160            3486998266802970665,
161        ];
162        let mut res = BigInt(BN254_MODULUS);
163
164        // Compute modulus - value
165        let borrow = res.sub_with_borrow(&fq_bigint);
166        if borrow {
167            return None;
168        }
169
170        let mut bytes = [0u8; BN254_FP_SERIALIZED_SIZE];
171        res.copy_into_array(&mut bytes);
172        Some(Bn254Fp::from_array(self.env(), &bytes))
173    }
174}
175
176impl Neg for &Bn254Fp {
177    type Output = Bn254Fp;
178
179    fn neg(self) -> Self::Output {
180        match self.checked_neg() {
181            Some(v) => v,
182            None => sdk_panic!("invalid input - Bn254Fp is larger than the field modulus"),
183        }
184    }
185}
186
187impl Neg for Bn254Fp {
188    type Output = Bn254Fp;
189
190    fn neg(self) -> Self::Output {
191        (&self).neg()
192    }
193}
194
195impl Add for Bn254G1Affine {
196    type Output = Bn254G1Affine;
197
198    fn add(self, rhs: Self) -> Self::Output {
199        self.env().crypto().bn254().g1_add(&self, &rhs)
200    }
201}
202
203impl Mul<Bn254Fr> for Bn254G1Affine {
204    type Output = Bn254G1Affine;
205
206    fn mul(self, rhs: Bn254Fr) -> Self::Output {
207        self.env().crypto().bn254().g1_mul(&self, &rhs)
208    }
209}
210
211// Bn254G1Affine represents a point (X, Y) on the BN254 curve where X, Y ∈ Bn254Fp
212// Negation of (X, Y) is defined as (X, -Y)
213impl Neg for &Bn254G1Affine {
214    type Output = Bn254G1Affine;
215
216    fn neg(self) -> Self::Output {
217        let mut inner: Bytes = (&self.0).into();
218        let y = Bn254Fp::try_from_val(
219            inner.env(),
220            inner.slice(BN254_FP_SERIALIZED_SIZE as u32..).as_val(),
221        )
222        .unwrap_optimized();
223        let neg_y = -y;
224        inner.copy_from_slice(BN254_FP_SERIALIZED_SIZE as u32, &neg_y.to_array());
225        Bn254G1Affine::from_bytes(
226            BytesN::try_from_val(inner.env(), inner.as_val()).unwrap_optimized(),
227        )
228    }
229}
230
231impl Neg for Bn254G1Affine {
232    type Output = Bn254G1Affine;
233
234    fn neg(self) -> Self::Output {
235        (&self).neg()
236    }
237}
238
239impl Bn254G2Affine {
240    pub fn env(&self) -> &Env {
241        self.0.env()
242    }
243}
244
245impl Bn254Fr {
246    pub fn env(&self) -> &Env {
247        self.0.env()
248    }
249
250    pub fn from_u256(value: U256) -> Self {
251        value.into()
252    }
253
254    pub fn to_u256(&self) -> U256 {
255        self.0.clone()
256    }
257
258    pub fn as_u256(&self) -> &U256 {
259        &self.0
260    }
261
262    pub fn from_bytes(bytes: BytesN<32>) -> Self {
263        U256::from_be_bytes(bytes.env(), bytes.as_ref()).into()
264    }
265
266    pub fn to_bytes(&self) -> BytesN<32> {
267        self.as_u256().to_be_bytes().try_into().unwrap_optimized()
268    }
269
270    pub fn as_val(&self) -> &Val {
271        self.0.as_val()
272    }
273
274    pub fn to_val(&self) -> Val {
275        self.0.to_val()
276    }
277
278    pub fn pow(&self, rhs: u64) -> Self {
279        self.env().crypto().bn254().fr_pow(self, rhs)
280    }
281
282    pub fn inv(&self) -> Self {
283        self.env().crypto().bn254().fr_inv(self)
284    }
285}
286
287impl Add for Bn254Fr {
288    type Output = Bn254Fr;
289
290    fn add(self, rhs: Self) -> Self::Output {
291        self.env().crypto().bn254().fr_add(&self, &rhs)
292    }
293}
294
295impl Sub for Bn254Fr {
296    type Output = Bn254Fr;
297
298    fn sub(self, rhs: Self) -> Self::Output {
299        self.env().crypto().bn254().fr_sub(&self, &rhs)
300    }
301}
302
303impl Mul for Bn254Fr {
304    type Output = Bn254Fr;
305
306    fn mul(self, rhs: Self) -> Self::Output {
307        self.env().crypto().bn254().fr_mul(&self, &rhs)
308    }
309}
310
311// BN254 scalar field modulus r in big-endian bytes.
312// r = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
313const BN254_FR_MODULUS_BE: [u8; 32] = [
314    0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, 0x58, 0x5d,
315    0x28, 0x33, 0xe8, 0x48, 0x79, 0xb9, 0x70, 0x91, 0x43, 0xe1, 0xf5, 0x93, 0xf0, 0x00, 0x00, 0x01,
316];
317
318fn fr_modulus(env: &Env) -> U256 {
319    U256::from_be_bytes(env, &Bytes::from_array(env, &BN254_FR_MODULUS_BE))
320}
321
322impl From<U256> for Bn254Fr {
323    fn from(value: U256) -> Self {
324        // Keep all Bn254Fr construction paths canonical by reducing modulo r here.
325        // Constructors and deserialization paths should route through this impl.
326        // Skip the expensive rem_euclid when value is already canonical (< r),
327        // which is always the case for host-returned arithmetic results.
328        let modulus = fr_modulus(value.env());
329        if value >= modulus {
330            Self(value.rem_euclid(&modulus))
331        } else {
332            Self(value)
333        }
334    }
335}
336
337impl From<&Bn254Fr> for U256Val {
338    fn from(value: &Bn254Fr) -> Self {
339        value.as_u256().into()
340    }
341}
342
343impl TryFromVal<Env, Val> for Bn254Fr {
344    type Error = ConversionError;
345
346    fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
347        let u = U256::try_from_val(env, val)?;
348        Ok(u.into())
349    }
350}
351
352impl TryFromVal<Env, Bn254Fr> for Val {
353    type Error = ConversionError;
354
355    fn try_from_val(_env: &Env, fr: &Bn254Fr) -> Result<Self, Self::Error> {
356        Ok(fr.to_val())
357    }
358}
359
360impl TryFromVal<Env, &Bn254Fr> for Val {
361    type Error = ConversionError;
362
363    fn try_from_val(_env: &Env, fr: &&Bn254Fr) -> Result<Self, Self::Error> {
364        Ok(fr.to_val())
365    }
366}
367
368#[cfg(not(target_family = "wasm"))]
369impl From<&Bn254Fr> for ScVal {
370    fn from(v: &Bn254Fr) -> Self {
371        Self::from(&v.0)
372    }
373}
374
375#[cfg(not(target_family = "wasm"))]
376impl From<Bn254Fr> for ScVal {
377    fn from(v: Bn254Fr) -> Self {
378        (&v).into()
379    }
380}
381
382impl Eq for Bn254Fr {}
383
384impl PartialEq for Bn254Fr {
385    fn eq(&self, other: &Self) -> bool {
386        self.as_u256().partial_cmp(other.as_u256()) == Some(core::cmp::Ordering::Equal)
387    }
388}
389
390impl Debug for Bn254Fr {
391    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
392        write!(f, "Bn254Fr({:?})", self.as_u256())
393    }
394}
395
396impl Bn254 {
397    pub(crate) fn new(env: &Env) -> Bn254 {
398        Bn254 { env: env.clone() }
399    }
400
401    pub fn env(&self) -> &Env {
402        &self.env
403    }
404
405    /// Adds two points `p0` and `p1` in G1.
406    pub fn g1_add(&self, p0: &Bn254G1Affine, p1: &Bn254G1Affine) -> Bn254G1Affine {
407        let env = self.env();
408        let bin =
409            internal::Env::bn254_g1_add(env, p0.to_object(), p1.to_object()).unwrap_infallible();
410        unsafe { Bn254G1Affine::from_bytes(BytesN::unchecked_new(env.clone(), bin)) }
411    }
412
413    /// Multiplies a point `p0` in G1 by a scalar.
414    pub fn g1_mul(&self, p0: &Bn254G1Affine, scalar: &Bn254Fr) -> Bn254G1Affine {
415        let env = self.env();
416        let bin =
417            internal::Env::bn254_g1_mul(env, p0.to_object(), scalar.into()).unwrap_infallible();
418        unsafe { Bn254G1Affine::from_bytes(BytesN::unchecked_new(env.clone(), bin)) }
419    }
420
421    // pairing
422
423    /// Performs a multi-pairing check between vectors of points in G1 and G2.
424    ///
425    /// This function computes the pairing for each pair of points in the
426    /// provided vectors `vp1` (G1 points) and `vp2` (G2 points) and verifies if
427    /// the product of all pairings is equal to 1 in the target group Bn254Fp.
428    ///
429    /// # Returns:
430    /// - `true` if the pairing check holds (i.e., the product of pairings equals 1),
431    ///   otherwise `false`.
432    ///
433    /// # Panics:
434    /// - If the lengths of `vp1` and `vp2` are not equal or if they are empty.
435    pub fn pairing_check(&self, vp1: Vec<Bn254G1Affine>, vp2: Vec<Bn254G2Affine>) -> bool {
436        let env = self.env();
437        internal::Env::bn254_multi_pairing_check(env, vp1.into(), vp2.into())
438            .unwrap_infallible()
439            .into()
440    }
441
442    /// Performs a multi-scalar multiplication (MSM) operation in G1.
443    pub fn g1_msm(&self, vp: Vec<Bn254G1Affine>, vs: Vec<Bn254Fr>) -> Bn254G1Affine {
444        let env = self.env();
445        let bin = internal::Env::bn254_g1_msm(env, vp.into(), vs.into()).unwrap_infallible();
446        unsafe { Bn254G1Affine::from_bytes(BytesN::unchecked_new(env.clone(), bin)) }
447    }
448
449    /// Checks if a G1 point is on the BN254 curve.
450    pub fn g1_is_on_curve(&self, point: &Bn254G1Affine) -> bool {
451        let env = self.env();
452        internal::Env::bn254_g1_is_on_curve(env, point.to_object())
453            .unwrap_infallible()
454            .into()
455    }
456
457    // scalar arithmetic
458
459    /// Adds two scalars in the BN254 scalar field `Bn254Fr`.
460    pub fn fr_add(&self, lhs: &Bn254Fr, rhs: &Bn254Fr) -> Bn254Fr {
461        let env = self.env();
462        let v = internal::Env::bn254_fr_add(env, lhs.into(), rhs.into()).unwrap_infallible();
463        U256::try_from_val(env, &v).unwrap_infallible().into()
464    }
465
466    /// Subtracts one scalar from another in the BN254 scalar field `Bn254Fr`.
467    pub fn fr_sub(&self, lhs: &Bn254Fr, rhs: &Bn254Fr) -> Bn254Fr {
468        let env = self.env();
469        let v = internal::Env::bn254_fr_sub(env, lhs.into(), rhs.into()).unwrap_infallible();
470        U256::try_from_val(env, &v).unwrap_infallible().into()
471    }
472
473    /// Multiplies two scalars in the BN254 scalar field `Bn254Fr`.
474    pub fn fr_mul(&self, lhs: &Bn254Fr, rhs: &Bn254Fr) -> Bn254Fr {
475        let env = self.env();
476        let v = internal::Env::bn254_fr_mul(env, lhs.into(), rhs.into()).unwrap_infallible();
477        U256::try_from_val(env, &v).unwrap_infallible().into()
478    }
479
480    /// Raises a scalar to the power of a given exponent in the BN254 scalar field `Bn254Fr`.
481    pub fn fr_pow(&self, lhs: &Bn254Fr, rhs: u64) -> Bn254Fr {
482        let env = self.env();
483        let rhs = U64Val::try_from_val(env, &rhs).unwrap_optimized();
484        let v = internal::Env::bn254_fr_pow(env, lhs.into(), rhs).unwrap_infallible();
485        U256::try_from_val(env, &v).unwrap_infallible().into()
486    }
487
488    /// Computes the multiplicative inverse of a scalar in the BN254 scalar field `Bn254Fr`.
489    pub fn fr_inv(&self, lhs: &Bn254Fr) -> Bn254Fr {
490        let env = self.env();
491        let v = internal::Env::bn254_fr_inv(env, lhs.into()).unwrap_infallible();
492        U256::try_from_val(env, &v).unwrap_infallible().into()
493    }
494}
495
496#[cfg(test)]
497mod test {
498    use super::*;
499
500    #[test]
501    fn test_g1affine_to_val() {
502        let env = Env::default();
503
504        let g1 = Bn254G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
505        let val: Val = g1.clone().into_val(&env);
506        let rt: Bn254G1Affine = val.into_val(&env);
507
508        assert_eq!(g1, rt);
509    }
510
511    #[test]
512    fn test_ref_g1affine_to_val() {
513        let env = Env::default();
514
515        let g1 = Bn254G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
516        let val: Val = (&g1).into_val(&env);
517        let rt: Bn254G1Affine = val.into_val(&env);
518
519        assert_eq!(g1, rt);
520    }
521
522    #[test]
523    fn test_double_ref_g1affine_to_val() {
524        let env = Env::default();
525
526        let g1 = Bn254G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
527        let val: Val = (&&g1).into_val(&env);
528        let rt: Bn254G1Affine = val.into_val(&env);
529
530        assert_eq!(g1, rt);
531    }
532
533    #[test]
534    fn test_fr_to_val() {
535        let env = Env::default();
536
537        let fr = Bn254Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
538        let val: Val = fr.clone().into_val(&env);
539        let rt: Bn254Fr = val.into_val(&env);
540
541        assert_eq!(fr, rt);
542    }
543
544    #[test]
545    fn test_ref_fr_to_val() {
546        let env = Env::default();
547
548        let fr = Bn254Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
549        let val: Val = (&fr).into_val(&env);
550        let rt: Bn254Fr = val.into_val(&env);
551
552        assert_eq!(fr, rt);
553    }
554
555    #[test]
556    fn test_double_ref_fr_to_val() {
557        let env = Env::default();
558
559        let fr = Bn254Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
560        let val: Val = (&&fr).into_val(&env);
561        let rt: Bn254Fr = val.into_val(&env);
562
563        assert_eq!(fr, rt);
564    }
565
566    #[test]
567    fn test_fr_eq_both_unreduced() {
568        // Both inputs are user-provided unreduced values representing the same field element
569        let env = Env::default();
570        let r = fr_modulus(&env);
571        let one = U256::from_u32(&env, 1);
572
573        let a = Bn254Fr::from_u256(r.add(&one)); // r+1 ≡ 1 (mod r)
574        let b = Bn254Fr::from_u256(one.clone()); // 1
575        assert_eq!(a, b);
576
577        // Both unreduced by different multiples of r
578        let two_r_plus_one = r.add(&r).add(&one);
579        let c = Bn254Fr::from_u256(two_r_plus_one); // 2r+1 ≡ 1 (mod r)
580        assert_eq!(a, c);
581        assert_eq!(b, c);
582    }
583
584    #[test]
585    fn test_fr_eq_unreduced_vs_zero() {
586        // value == r should reduce to 0
587        let env = Env::default();
588        let r = fr_modulus(&env);
589        let zero = U256::from_u32(&env, 0);
590
591        let a = Bn254Fr::from_u256(r);
592        let b = Bn254Fr::from_u256(zero);
593        assert_eq!(a, b);
594    }
595
596    #[test]
597    fn test_fr_reduced_value_unchanged() {
598        // value < r should be preserved as-is
599        let env = Env::default();
600        let r = fr_modulus(&env);
601        let val = r.sub(&U256::from_u32(&env, 1)); // r-1
602
603        let fr = Bn254Fr::from_u256(val.clone());
604        assert_eq!(fr.to_u256(), val);
605
606        // small values
607        let fr42 = Bn254Fr::from_u256(U256::from_u32(&env, 42));
608        assert_eq!(fr42.to_u256(), U256::from_u32(&env, 42));
609    }
610
611    #[test]
612    fn test_fr_from_bytes_reduces() {
613        // from_bytes should also reduce since it goes through From<U256>
614        let env = Env::default();
615        let one_fr = Bn254Fr::from_u256(U256::from_u32(&env, 1));
616
617        // BN254 r+1 as big-endian bytes
618        let fr_from_bytes = Bn254Fr::from_bytes(bytesn!(
619            &env,
620            0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000002
621        ));
622        assert_eq!(fr_from_bytes, one_fr);
623    }
624
625    #[test]
626    fn test_fr_try_from_val_reduces() {
627        // TryFromVal<Env, Val> path must also reduce
628        let env = Env::default();
629        let r = fr_modulus(&env);
630        let one = U256::from_u32(&env, 1);
631
632        // Create an unreduced U256 value (r+1), convert to Val, then to Fr
633        let unreduced_u256 = r.add(&one);
634        let val: Val = unreduced_u256.into_val(&env);
635        let fr_from_val: Bn254Fr = val.into_val(&env);
636        let fr_one = Bn254Fr::from_u256(one);
637        assert_eq!(fr_from_val, fr_one);
638    }
639
640    #[test]
641    fn test_fr_u256_into_reduces() {
642        // Direct From<U256>::from / .into() path must reduce
643        let env = Env::default();
644        let r = fr_modulus(&env);
645        let one = U256::from_u32(&env, 1);
646
647        let fr: Bn254Fr = r.add(&one).into(); // r+1 via .into()
648        let fr_one: Bn254Fr = one.into();
649        assert_eq!(fr, fr_one);
650    }
651
652    // Bn254Fp validation tests
653
654    #[test]
655    fn test_bn254_fp_max_valid_accepted() {
656        let env = Env::default();
657        // p - 1 (last byte 0x46 instead of 0x47)
658        let mut p_minus_1 = BN254_FP_MODULUS_BE;
659        p_minus_1[BN254_FP_SERIALIZED_SIZE - 1] -= 1;
660        let _ = Bn254Fp::from_array(&env, &p_minus_1);
661    }
662
663    #[test]
664    #[should_panic(expected = "Bn254: Invalid Fp")]
665    fn test_bn254_fp_at_modulus_panics() {
666        let env = Env::default();
667        let _ = Bn254Fp::from_array(&env, &BN254_FP_MODULUS_BE);
668    }
669
670    #[test]
671    #[should_panic(expected = "Bn254: Invalid Fp")]
672    fn test_bn254_fp_above_modulus_panics() {
673        let env = Env::default();
674        let mut above = BN254_FP_MODULUS_BE;
675        above[BN254_FP_SERIALIZED_SIZE - 1] += 1; // p + 1
676        let _ = Bn254Fp::from_array(&env, &above);
677    }
678
679    #[test]
680    fn test_bn254_fp_from_bytes_validates() {
681        let env = Env::default();
682        // Zero should be valid
683        let _ = Bn254Fp::from_bytes(BytesN::from_array(&env, &[0u8; BN254_FP_SERIALIZED_SIZE]));
684    }
685
686    #[test]
687    #[should_panic(expected = "Bn254: Invalid Fp")]
688    fn test_bn254_fp_from_bytes_rejects_modulus() {
689        let env = Env::default();
690        let _ = Bn254Fp::from_bytes(BytesN::from_array(&env, &BN254_FP_MODULUS_BE));
691    }
692
693    #[test]
694    #[should_panic(expected = "Bn254: Invalid Fp")]
695    fn test_bn254_fp_try_from_val_rejects_modulus() {
696        let env = Env::default();
697        let bytes = BytesN::from_array(&env, &BN254_FP_MODULUS_BE);
698        let val: Val = bytes.into_val(&env);
699        let _: Bn254Fp = val.into_val(&env);
700    }
701
702    #[test]
703    fn test_bn254_fp_modulus_matches_arkworks() {
704        use ark_bn254::Fq;
705        use ark_ff::{BigInteger, PrimeField};
706
707        let be_bytes = Fq::MODULUS.to_bytes_be();
708        assert_eq!(
709            be_bytes.as_slice(),
710            &BN254_FP_MODULUS_BE,
711            "BN254 Fp modulus does not match arkworks"
712        );
713    }
714
715    #[test]
716    fn test_bn254_fr_modulus_matches_arkworks() {
717        use ark_bn254::Fr as ArkFr;
718        use ark_ff::{BigInteger, PrimeField};
719
720        let be_bytes = ArkFr::MODULUS.to_bytes_be();
721        assert_eq!(
722            be_bytes.as_slice(),
723            &BN254_FR_MODULUS_BE,
724            "BN254 Fr modulus does not match arkworks"
725        );
726    }
727}