Skip to content

Commit 3cf10a9

Browse files
jayz22sisureshleighmcculloch
authored
Add bn254 support (#1615)
### What This is the same PR as #1613, but onto `p25-preview` branch instead of `master`, so that it doesn't interfere with any potential p24 patch work. All comments have been addressed in the original PR. --------- Co-authored-by: Siddharth Suresh <siddharth@stellar.org> Co-authored-by: Leigh <351529+leighmcculloch@users.noreply.github.com>
1 parent 0738452 commit 3cf10a9

File tree

160 files changed

+2774
-247
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

160 files changed

+2774
-247
lines changed

Cargo.lock

Lines changed: 251 additions & 50 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,29 @@ soroban-token-spec = { version = "23.1.0", path = "soroban-token-spec" }
3030
stellar-asset-spec = { version = "23.1.0", path = "stellar-asset-spec" }
3131

3232
[workspace.dependencies.soroban-env-common]
33-
version = "=23.0.1"
34-
#git = "https://github.com/stellar/rs-soroban-env"
35-
#rev = "bd0c80a1fe171e75f8d745f17975a73927d44ecd"
33+
version = "=25.0.0"
34+
git = "https://github.com/stellar/rs-soroban-env"
35+
rev = "0a0c2df704edeb3cdafabfcc759eddccd6775337"
3636

3737
[workspace.dependencies.soroban-env-guest]
38-
version = "=23.0.1"
39-
#git = "https://github.com/stellar/rs-soroban-env"
40-
#rev = "bd0c80a1fe171e75f8d745f17975a73927d44ecd"
38+
version = "=25.0.0"
39+
git = "https://github.com/stellar/rs-soroban-env"
40+
rev = "0a0c2df704edeb3cdafabfcc759eddccd6775337"
4141

4242
[workspace.dependencies.soroban-env-host]
43-
version = "=23.0.1"
44-
#git = "https://github.com/stellar/rs-soroban-env"
45-
#rev = "bd0c80a1fe171e75f8d745f17975a73927d44ecd"
43+
version = "=25.0.0"
44+
git = "https://github.com/stellar/rs-soroban-env"
45+
rev = "0a0c2df704edeb3cdafabfcc759eddccd6775337"
4646

4747
[workspace.dependencies.stellar-strkey]
4848
version = "=0.0.13"
4949

5050
[workspace.dependencies.stellar-xdr]
51-
version = "=23.0.0"
51+
version = "=24.0.1"
5252
default-features = false
5353
features = ["curr"]
54-
#git = "https://github.com/stellar/rs-stellar-xdr"
55-
#rev = "67be5955a15f1d3a4df83fe86e6ae107f687141b"
54+
git = "https://github.com/stellar/rs-stellar-xdr"
55+
rev = "89cc1cbadf1b9a16843826954dede7fec514d8e7"
5656

5757
#[patch.crates-io]
5858
#soroban-env-common = { path = "../rs-soroban-env/soroban-env-common" }

soroban-ledger-snapshot/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ impl LedgerSnapshot {
166166
impl Default for LedgerSnapshot {
167167
fn default() -> Self {
168168
Self {
169-
protocol_version: 23,
169+
protocol_version: 25,
170170
sequence_number: Default::default(),
171171
timestamp: Default::default(),
172172
network_id: Default::default(),

soroban-sdk/src/crypto.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
};
88

99
pub mod bls12_381;
10+
pub mod bn254;
1011
/// A `BytesN<N>` generated by a cryptographic hash function.
1112
///
1213
/// The `Hash<N>` type contains a `BytesN<N>` and can only be constructed in
@@ -182,6 +183,12 @@ impl Crypto {
182183
pub fn bls12_381(&self) -> bls12_381::Bls12_381 {
183184
bls12_381::Bls12_381::new(self.env())
184185
}
186+
187+
/// Get a [Bn254][bn254::Bn254] for accessing the bn254
188+
/// functions.
189+
pub fn bn254(&self) -> bn254::Bn254 {
190+
bn254::Bn254::new(self.env())
191+
}
185192
}
186193

187194
/// # ⚠️ Hazardous Materials

soroban-sdk/src/crypto/bn254.rs

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
#[cfg(not(target_family = "wasm"))]
2+
use crate::xdr::ScVal;
3+
use crate::{
4+
env::internal::{self, BytesObject, U256Val},
5+
impl_bytesn_repr,
6+
unwrap::{UnwrapInfallible, UnwrapOptimized},
7+
Bytes, BytesN, ConversionError, Env, IntoVal, TryFromVal, Val, Vec, U256,
8+
};
9+
use core::{
10+
cmp::Ordering,
11+
fmt::Debug,
12+
ops::{Add, Mul},
13+
};
14+
15+
const FP_SERIALIZED_SIZE: usize = 32; // Size in bytes of a serialized Fp element in BN254. The field modulus is 254 bits, requiring 32 bytes (256 bits).
16+
pub const G1_SERIALIZED_SIZE: usize = FP_SERIALIZED_SIZE * 2; // Size in bytes of a serialized G1 element in BN254. Each coordinate (X, Y) is 32 bytes.
17+
pub const G2_SERIALIZED_SIZE: usize = G1_SERIALIZED_SIZE * 2; // Size in bytes of a serialized G2 element in BN254. Each coordinate (X, Y) is 64 bytes (2 Fp elements per coordinate).
18+
19+
/// Bn254 provides access to curve and pairing operations on the BN254
20+
/// (also known as alt_bn128) curve.
21+
pub struct Bn254 {
22+
env: Env,
23+
}
24+
25+
/// `G1Affine` 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+
/// # Serialization:
31+
/// - The 64 bytes represent the **uncompressed encoding** of a point in G1. The
32+
/// bytes consist of `be_bytes(X) || be_bytes(Y)` (`||` is concatenation),
33+
/// where 'X' and 'Y' are the two coordinates, each being a base field element
34+
/// `Fp` (32 bytes each).
35+
#[derive(Clone)]
36+
#[repr(transparent)]
37+
pub struct G1Affine(BytesN<G1_SERIALIZED_SIZE>);
38+
39+
/// `G2Affine` is a point in the G2 group (subgroup defined over the quadratic
40+
/// extension field `Fq2`) of the BN254 elliptic curve
41+
///
42+
/// # Serialization:
43+
/// - The 128 bytes represent the **uncompressed encoding** of a point in G2.
44+
/// The bytes consist of `be_bytes(X_im) || be_bytes(X_re) || be_bytes(Y_im)
45+
/// || be_bytes(Y_re)` (`||` is concatenation), where 'X' and 'Y' are the two
46+
/// coordinates, each being an extension field element `Fp2`. Each component
47+
/// (real and imaginary parts) is an `Fp` element (32 bytes each).
48+
#[derive(Clone)]
49+
#[repr(transparent)]
50+
pub struct G2Affine(BytesN<G2_SERIALIZED_SIZE>);
51+
52+
/// `Fr` represents an element in the BN254 scalar field, which is a prime field
53+
/// of order `r =
54+
/// 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001`. The
55+
/// struct is internally represented with a `U256`, all arithmetic operations
56+
/// follow modulo `r`.
57+
#[derive(Clone)]
58+
#[repr(transparent)]
59+
pub struct Fr(U256);
60+
61+
impl_bytesn_repr!(G1Affine, G1_SERIALIZED_SIZE);
62+
impl_bytesn_repr!(G2Affine, G2_SERIALIZED_SIZE);
63+
64+
impl G1Affine {
65+
pub fn env(&self) -> &Env {
66+
self.0.env()
67+
}
68+
}
69+
70+
impl Add for G1Affine {
71+
type Output = G1Affine;
72+
73+
fn add(self, rhs: Self) -> Self::Output {
74+
self.env().crypto().bn254().g1_add(&self, &rhs)
75+
}
76+
}
77+
78+
impl Mul<Fr> for G1Affine {
79+
type Output = G1Affine;
80+
81+
fn mul(self, rhs: Fr) -> Self::Output {
82+
self.env().crypto().bn254().g1_mul(&self, &rhs)
83+
}
84+
}
85+
86+
impl G2Affine {
87+
pub fn env(&self) -> &Env {
88+
self.0.env()
89+
}
90+
}
91+
92+
impl Fr {
93+
pub fn env(&self) -> &Env {
94+
self.0.env()
95+
}
96+
97+
pub fn from_u256(value: U256) -> Self {
98+
value.into()
99+
}
100+
101+
pub fn to_u256(&self) -> U256 {
102+
self.0.clone()
103+
}
104+
105+
pub fn as_u256(&self) -> &U256 {
106+
&self.0
107+
}
108+
109+
pub fn from_bytes(bytes: BytesN<32>) -> Self {
110+
U256::from_be_bytes(bytes.env(), bytes.as_ref()).into()
111+
}
112+
113+
pub fn to_bytes(&self) -> BytesN<32> {
114+
self.as_u256().to_be_bytes().try_into().unwrap_optimized()
115+
}
116+
117+
pub fn as_val(&self) -> &Val {
118+
self.0.as_val()
119+
}
120+
121+
pub fn to_val(&self) -> Val {
122+
self.0.to_val()
123+
}
124+
}
125+
126+
impl From<U256> for Fr {
127+
fn from(value: U256) -> Self {
128+
Self(value)
129+
}
130+
}
131+
132+
impl From<&Fr> for U256Val {
133+
fn from(value: &Fr) -> Self {
134+
value.as_u256().into()
135+
}
136+
}
137+
138+
impl TryFromVal<Env, Val> for Fr {
139+
type Error = ConversionError;
140+
141+
fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
142+
let u = U256::try_from_val(env, val)?;
143+
Ok(Fr(u))
144+
}
145+
}
146+
147+
impl TryFromVal<Env, Fr> for Val {
148+
type Error = ConversionError;
149+
150+
fn try_from_val(_env: &Env, fr: &Fr) -> Result<Self, Self::Error> {
151+
Ok(fr.to_val())
152+
}
153+
}
154+
155+
impl TryFromVal<Env, &Fr> for Val {
156+
type Error = ConversionError;
157+
158+
fn try_from_val(_env: &Env, fr: &&Fr) -> Result<Self, Self::Error> {
159+
Ok(fr.to_val())
160+
}
161+
}
162+
163+
#[cfg(not(target_family = "wasm"))]
164+
impl From<&Fr> for ScVal {
165+
fn from(v: &Fr) -> Self {
166+
Self::from(&v.0)
167+
}
168+
}
169+
170+
#[cfg(not(target_family = "wasm"))]
171+
impl From<Fr> for ScVal {
172+
fn from(v: Fr) -> Self {
173+
(&v).into()
174+
}
175+
}
176+
177+
impl Eq for Fr {}
178+
179+
impl PartialEq for Fr {
180+
fn eq(&self, other: &Self) -> bool {
181+
self.as_u256().partial_cmp(other.as_u256()) == Some(core::cmp::Ordering::Equal)
182+
}
183+
}
184+
185+
impl Debug for Fr {
186+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
187+
write!(f, "Fr({:?})", self.as_u256())
188+
}
189+
}
190+
191+
impl Bn254 {
192+
pub(crate) fn new(env: &Env) -> Bn254 {
193+
Bn254 { env: env.clone() }
194+
}
195+
196+
pub fn env(&self) -> &Env {
197+
&self.env
198+
}
199+
200+
/// Adds two points `p0` and `p1` in G1.
201+
pub fn g1_add(&self, p0: &G1Affine, p1: &G1Affine) -> G1Affine {
202+
let env = self.env();
203+
let bin =
204+
internal::Env::bn254_g1_add(env, p0.to_object(), p1.to_object()).unwrap_infallible();
205+
unsafe { G1Affine::from_bytes(BytesN::unchecked_new(env.clone(), bin)) }
206+
}
207+
208+
/// Multiplies a point `p0` in G1 by a scalar.
209+
pub fn g1_mul(&self, p0: &G1Affine, scalar: &Fr) -> G1Affine {
210+
let env = self.env();
211+
let bin =
212+
internal::Env::bn254_g1_mul(env, p0.to_object(), scalar.into()).unwrap_infallible();
213+
unsafe { G1Affine::from_bytes(BytesN::unchecked_new(env.clone(), bin)) }
214+
}
215+
216+
// pairing
217+
218+
/// Performs a multi-pairing check between vectors of points in G1 and G2.
219+
///
220+
/// This function computes the pairing for each pair of points in the
221+
/// provided vectors `vp1` (G1 points) and `vp2` (G2 points) and verifies if
222+
/// the product of all pairings is equal to 1 in the target group Fq12.
223+
///
224+
/// # Returns:
225+
/// - `true` if the pairing check holds (i.e., the product of pairings equals 1),
226+
/// otherwise `false`.
227+
///
228+
/// # Panics:
229+
/// - If the lengths of `vp1` and `vp2` are not equal or if they are empty.
230+
pub fn pairing_check(&self, vp1: Vec<G1Affine>, vp2: Vec<G2Affine>) -> bool {
231+
let env = self.env();
232+
internal::Env::bn254_multi_pairing_check(env, vp1.into(), vp2.into())
233+
.unwrap_infallible()
234+
.into()
235+
}
236+
}
237+
238+
#[cfg(test)]
239+
mod test {
240+
use super::*;
241+
242+
#[test]
243+
fn test_g1affine_to_val() {
244+
let env = Env::default();
245+
246+
let g1 = G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
247+
let val: Val = g1.clone().into_val(&env);
248+
let rt: G1Affine = val.into_val(&env);
249+
250+
assert_eq!(g1, rt);
251+
}
252+
253+
#[test]
254+
fn test_ref_g1affine_to_val() {
255+
let env = Env::default();
256+
257+
let g1 = G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
258+
let val: Val = (&g1).into_val(&env);
259+
let rt: G1Affine = val.into_val(&env);
260+
261+
assert_eq!(g1, rt);
262+
}
263+
264+
#[test]
265+
fn test_double_ref_g1affine_to_val() {
266+
let env = Env::default();
267+
268+
let g1 = G1Affine::from_bytes(BytesN::from_array(&env, &[1; 64]));
269+
let val: Val = (&&g1).into_val(&env);
270+
let rt: G1Affine = val.into_val(&env);
271+
272+
assert_eq!(g1, rt);
273+
}
274+
275+
#[test]
276+
fn test_fr_to_val() {
277+
let env = Env::default();
278+
279+
let fr = Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
280+
let val: Val = fr.clone().into_val(&env);
281+
let rt: Fr = val.into_val(&env);
282+
283+
assert_eq!(fr, rt);
284+
}
285+
286+
#[test]
287+
fn test_ref_fr_to_val() {
288+
let env = Env::default();
289+
290+
let fr = Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
291+
let val: Val = (&fr).into_val(&env);
292+
let rt: Fr = val.into_val(&env);
293+
294+
assert_eq!(fr, rt);
295+
}
296+
297+
#[test]
298+
fn test_double_ref_fr_to_val() {
299+
let env = Env::default();
300+
301+
let fr = Fr::from_bytes(BytesN::from_array(&env, &[1; 32]));
302+
let val: Val = (&&fr).into_val(&env);
303+
let rt: Fr = val.into_val(&env);
304+
305+
assert_eq!(fr, rt);
306+
}
307+
}

soroban-sdk/src/env.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ impl Env {
514514

515515
let rf = Rc::new(EmptySnapshotSource());
516516
let info = internal::LedgerInfo {
517-
protocol_version: 23,
517+
protocol_version: 25,
518518
sequence_number: 0,
519519
timestamp: 0,
520520
network_id: [0; 32],

0 commit comments

Comments
 (0)