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},
6    impl_bytesn_repr,
7    unwrap::{UnwrapInfallible, UnwrapOptimized},
8    Bytes, BytesN, ConversionError, Env, IntoVal, TryFromVal, Val, Vec, U256,
9};
10use core::{
11    cmp::Ordering,
12    fmt::Debug,
13    ops::{Add, Mul, Neg},
14};
15
16pub 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).
17pub 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.
18pub 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).
19
20/// Bn254 provides access to curve and pairing operations on the BN254
21/// (also known as alt_bn128) curve.
22pub struct Bn254 {
23    env: Env,
24}
25
26/// `Bn254G1Affine` is a point in the G1 group (subgroup defined over the base field
27/// `Fq` with prime order `q =
28/// 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47`) of the
29/// BN254 elliptic curve
30///
31/// # Serialization (Ethereum-compatible format):
32/// - The 64 bytes represent the **uncompressed encoding** of a point in G1
33/// - Format: `be_bytes(X) || be_bytes(Y)` where `||` denotes concatenation
34/// - X and Y are curve coordinates, each a 32-byte big-endian Bn254Fp field element
35/// - The two flag bits (bits 0x80 and 0x40 of the first byte) must be unset
36/// - The point at infinity is encoded as 64 zero bytes
37/// - Points must be on the curve (no subgroup check required for G1)
38#[derive(Clone)]
39#[repr(transparent)]
40pub struct Bn254G1Affine(BytesN<BN254_G1_SERIALIZED_SIZE>);
41
42/// `Bn254G2Affine` is a point in the G2 group (subgroup defined over the quadratic
43/// extension field `Fq2`) of the BN254 elliptic curve
44///
45/// # Serialization (Ethereum-compatible format):
46/// - The 128 bytes represent the **uncompressed encoding** of a point in G2
47/// - Format: `be_bytes(X) || be_bytes(Y)` where each coordinate is an Fp2
48/// element (64 bytes) - Fp2 element encoding: `be_bytes(c1) || be_bytes(c0)`
49/// where:
50///   - c0 is the real component (32-byte big-endian Bn254Fp element)
51///   - c1 is the imaginary component (32-byte big-endian Bn254Fp element)
52/// - The two flag bits (bits 0x80 and 0x40 of the first byte) must be unset
53/// - The point at infinity is encoded as 128 zero bytes
54/// - Points must be on the curve AND in the correct subgroup
55#[derive(Clone)]
56#[repr(transparent)]
57pub struct Bn254G2Affine(BytesN<BN254_G2_SERIALIZED_SIZE>);
58
59/// `Fr` represents an element in the BN254 scalar field, which is a prime field
60/// of order `r =
61/// 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001`. The
62/// struct is internally represented with a `U256`, all arithmetic operations
63/// follow modulo `r`.
64#[derive(Clone)]
65#[repr(transparent)]
66pub struct Fr(U256);
67
68/// `Bn254Fp` represents an element of the base field `Bn254Fp` of the BN254 elliptic curve
69///
70/// # Serialization:
71/// - The 32 bytes represent the **big-endian encoding** of an element in the
72///   field `Bn254Fp`. The value is serialized as a big-endian integer.
73#[derive(Clone)]
74#[repr(transparent)]
75pub struct Bn254Fp(BytesN<BN254_FP_SERIALIZED_SIZE>);
76
77impl_bytesn_repr!(Bn254G1Affine, BN254_G1_SERIALIZED_SIZE);
78impl_bytesn_repr!(Bn254G2Affine, BN254_G2_SERIALIZED_SIZE);
79impl_bytesn_repr!(Bn254Fp, BN254_FP_SERIALIZED_SIZE);
80
81impl Bn254G1Affine {
82    pub fn env(&self) -> &Env {
83        self.0.env()
84    }
85}
86
87impl Bn254Fp {
88    pub fn env(&self) -> &Env {
89        self.0.env()
90    }
91
92    // `Bn254Fp` represents an element in the base field of the BN254 elliptic curve.
93    // For an element a ∈ Bn254Fp, its negation `-a` is defined as:
94    //   a + (-a) = 0 (mod p)
95    // where `p` is the field modulus, and to make a valid point coordinate on the
96    // curve, `a` also must be within the field range (i.e., 0 ≤ a < p).
97    fn checked_neg(&self) -> Option<Bn254Fp> {
98        let fq_bigint: BigInt<4> = (&self.0).into();
99        if fq_bigint.is_zero() {
100            return Some(self.clone());
101        }
102
103        //BN254 base field modulus
104        const BN254_MODULUS: [u64; 4] = [
105            4332616871279656263,
106            10917124144477883021,
107            13281191951274694749,
108            3486998266802970665,
109        ];
110        let mut res = BigInt(BN254_MODULUS);
111
112        // Compute modulus - value
113        let borrow = res.sub_with_borrow(&fq_bigint);
114        if borrow {
115            return None;
116        }
117
118        let mut bytes = [0u8; BN254_FP_SERIALIZED_SIZE];
119        res.copy_into_array(&mut bytes);
120        Some(Bn254Fp::from_array(self.env(), &bytes))
121    }
122}
123
124impl Neg for &Bn254Fp {
125    type Output = Bn254Fp;
126
127    fn neg(self) -> Self::Output {
128        match self.checked_neg() {
129            Some(v) => v,
130            None => sdk_panic!("invalid input - Bn254Fp is larger than the field modulus"),
131        }
132    }
133}
134
135impl Neg for Bn254Fp {
136    type Output = Bn254Fp;
137
138    fn neg(self) -> Self::Output {
139        (&self).neg()
140    }
141}
142
143impl Add for Bn254G1Affine {
144    type Output = Bn254G1Affine;
145
146    fn add(self, rhs: Self) -> Self::Output {
147        self.env().crypto().bn254().g1_add(&self, &rhs)
148    }
149}
150
151impl Mul<Fr> for Bn254G1Affine {
152    type Output = Bn254G1Affine;
153
154    fn mul(self, rhs: Fr) -> Self::Output {
155        self.env().crypto().bn254().g1_mul(&self, &rhs)
156    }
157}
158
159// Bn254G1Affine represents a point (X, Y) on the BN254 curve where X, Y ∈ Fr
160// Negation of (X, Y) is defined as (X, -Y)
161impl Neg for &Bn254G1Affine {
162    type Output = Bn254G1Affine;
163
164    fn neg(self) -> Self::Output {
165        let mut inner: Bytes = (&self.0).into();
166        let y = Bn254Fp::try_from_val(
167            inner.env(),
168            inner.slice(BN254_FP_SERIALIZED_SIZE as u32..).as_val(),
169        )
170        .unwrap_optimized();
171        let neg_y = -y;
172        inner.copy_from_slice(BN254_FP_SERIALIZED_SIZE as u32, &neg_y.to_array());
173        Bn254G1Affine::from_bytes(
174            BytesN::try_from_val(inner.env(), inner.as_val()).unwrap_optimized(),
175        )
176    }
177}
178
179impl Neg for Bn254G1Affine {
180    type Output = Bn254G1Affine;
181
182    fn neg(self) -> Self::Output {
183        (&self).neg()
184    }
185}
186
187impl Bn254G2Affine {
188    pub fn env(&self) -> &Env {
189        self.0.env()
190    }
191}
192
193impl Fr {
194    pub fn env(&self) -> &Env {
195        self.0.env()
196    }
197
198    pub fn from_u256(value: U256) -> Self {
199        value.into()
200    }
201
202    pub fn to_u256(&self) -> U256 {
203        self.0.clone()
204    }
205
206    pub fn as_u256(&self) -> &U256 {
207        &self.0
208    }
209
210    pub fn from_bytes(bytes: BytesN<32>) -> Self {
211        U256::from_be_bytes(bytes.env(), bytes.as_ref()).into()
212    }
213
214    pub fn to_bytes(&self) -> BytesN<32> {
215        self.as_u256().to_be_bytes().try_into().unwrap_optimized()
216    }
217
218    pub fn as_val(&self) -> &Val {
219        self.0.as_val()
220    }
221
222    pub fn to_val(&self) -> Val {
223        self.0.to_val()
224    }
225}
226
227impl From<U256> for Fr {
228    fn from(value: U256) -> Self {
229        Self(value)
230    }
231}
232
233impl From<&Fr> for U256Val {
234    fn from(value: &Fr) -> Self {
235        value.as_u256().into()
236    }
237}
238
239impl TryFromVal<Env, Val> for Fr {
240    type Error = ConversionError;
241
242    fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
243        let u = U256::try_from_val(env, val)?;
244        Ok(Fr(u))
245    }
246}
247
248impl TryFromVal<Env, Fr> for Val {
249    type Error = ConversionError;
250
251    fn try_from_val(_env: &Env, fr: &Fr) -> Result<Self, Self::Error> {
252        Ok(fr.to_val())
253    }
254}
255
256impl TryFromVal<Env, &Fr> for Val {
257    type Error = ConversionError;
258
259    fn try_from_val(_env: &Env, fr: &&Fr) -> Result<Self, Self::Error> {
260        Ok(fr.to_val())
261    }
262}
263
264#[cfg(not(target_family = "wasm"))]
265impl From<&Fr> for ScVal {
266    fn from(v: &Fr) -> Self {
267        Self::from(&v.0)
268    }
269}
270
271#[cfg(not(target_family = "wasm"))]
272impl From<Fr> for ScVal {
273    fn from(v: Fr) -> Self {
274        (&v).into()
275    }
276}
277
278impl Eq for Fr {}
279
280impl PartialEq for Fr {
281    fn eq(&self, other: &Self) -> bool {
282        self.as_u256().partial_cmp(other.as_u256()) == Some(core::cmp::Ordering::Equal)
283    }
284}
285
286impl Debug for Fr {
287    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
288        write!(f, "Fr({:?})", self.as_u256())
289    }
290}
291
292impl Bn254 {
293    pub(crate) fn new(env: &Env) -> Bn254 {
294        Bn254 { env: env.clone() }
295    }
296
297    pub fn env(&self) -> &Env {
298        &self.env
299    }
300
301    /// Adds two points `p0` and `p1` in G1.
302    pub fn g1_add(&self, p0: &Bn254G1Affine, p1: &Bn254G1Affine) -> Bn254G1Affine {
303        let env = self.env();
304        let bin =
305            internal::Env::bn254_g1_add(env, p0.to_object(), p1.to_object()).unwrap_infallible();
306        unsafe { Bn254G1Affine::from_bytes(BytesN::unchecked_new(env.clone(), bin)) }
307    }
308
309    /// Multiplies a point `p0` in G1 by a scalar.
310    pub fn g1_mul(&self, p0: &Bn254G1Affine, scalar: &Fr) -> Bn254G1Affine {
311        let env = self.env();
312        let bin =
313            internal::Env::bn254_g1_mul(env, p0.to_object(), scalar.into()).unwrap_infallible();
314        unsafe { Bn254G1Affine::from_bytes(BytesN::unchecked_new(env.clone(), bin)) }
315    }
316
317    // pairing
318
319    /// Performs a multi-pairing check between vectors of points in G1 and G2.
320    ///
321    /// This function computes the pairing for each pair of points in the
322    /// provided vectors `vp1` (G1 points) and `vp2` (G2 points) and verifies if
323    /// the product of all pairings is equal to 1 in the target group Bn254Fp.
324    ///
325    /// # Returns:
326    /// - `true` if the pairing check holds (i.e., the product of pairings equals 1),
327    ///   otherwise `false`.
328    ///
329    /// # Panics:
330    /// - If the lengths of `vp1` and `vp2` are not equal or if they are empty.
331    pub fn pairing_check(&self, vp1: Vec<Bn254G1Affine>, vp2: Vec<Bn254G2Affine>) -> bool {
332        let env = self.env();
333        internal::Env::bn254_multi_pairing_check(env, vp1.into(), vp2.into())
334            .unwrap_infallible()
335            .into()
336    }
337}
338
339#[cfg(test)]
340mod test {
341    use super::*;
342
343    #[test]
344    fn test_g1affine_to_val() {
345        let env = Env::default();
346
347        let g1 = Bn254G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
348        let val: Val = g1.clone().into_val(&env);
349        let rt: Bn254G1Affine = val.into_val(&env);
350
351        assert_eq!(g1, rt);
352    }
353
354    #[test]
355    fn test_ref_g1affine_to_val() {
356        let env = Env::default();
357
358        let g1 = Bn254G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
359        let val: Val = (&g1).into_val(&env);
360        let rt: Bn254G1Affine = val.into_val(&env);
361
362        assert_eq!(g1, rt);
363    }
364
365    #[test]
366    fn test_double_ref_g1affine_to_val() {
367        let env = Env::default();
368
369        let g1 = Bn254G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
370        let val: Val = (&&g1).into_val(&env);
371        let rt: Bn254G1Affine = val.into_val(&env);
372
373        assert_eq!(g1, rt);
374    }
375
376    #[test]
377    fn test_fr_to_val() {
378        let env = Env::default();
379
380        let fr = Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
381        let val: Val = fr.clone().into_val(&env);
382        let rt: Fr = val.into_val(&env);
383
384        assert_eq!(fr, rt);
385    }
386
387    #[test]
388    fn test_ref_fr_to_val() {
389        let env = Env::default();
390
391        let fr = Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
392        let val: Val = (&fr).into_val(&env);
393        let rt: Fr = val.into_val(&env);
394
395        assert_eq!(fr, rt);
396    }
397
398    #[test]
399    fn test_double_ref_fr_to_val() {
400        let env = Env::default();
401
402        let fr = Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
403        let val: Val = (&&fr).into_val(&env);
404        let rt: Fr = val.into_val(&env);
405
406        assert_eq!(fr, rt);
407    }
408}