Skip to content

Commit e516750

Browse files
Oghmasisuresh
andauthored
Feature/bn254 neg trait (#1630)
Implement `Neg` trait for `G1Affine`. This is useful for verifying Groth16 proofs. ### What - Introduce an `Fq` newtype wrapping `BytesN<FP_SERIALIZED_SIZE>` as the BN254 base field element - Implement `Neg` for `Fq` - Implement `Neg` for `G1Affine` by: ### Why - We need this operation to make cheaper groth16 verification on the bn254 curve. BLS has implemented this feature as well. ### Known limitations - Negation is implemented only for G1Affine; G2Affine and other types (e.g. projective representations) do not yet have Neg implementations. --------- Co-authored-by: Siddharth Suresh <siddharth@stellar.org>
1 parent d64ed4c commit e516750

File tree

5 files changed

+176
-63
lines changed

5 files changed

+176
-63
lines changed

soroban-sdk/src/crypto.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub(crate) mod poseidon2_params;
1212
pub mod poseidon2_sponge;
1313
pub(crate) mod poseidon_params;
1414
pub mod poseidon_sponge;
15+
pub(crate) mod utils;
1516
pub use bn254::Fr as BnScalar;
1617
pub use poseidon2_sponge::Poseidon2Sponge;
1718
pub use poseidon_sponge::PoseidonSponge;

soroban-sdk/src/crypto/bls12_381.rs

Lines changed: 1 addition & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[cfg(not(target_family = "wasm"))]
22
use crate::xdr::ScVal;
33
use crate::{
4+
crypto::utils::BigInt,
45
env::internal::{self, BytesObject, U256Val, U64Val},
56
impl_bytesn_repr,
67
unwrap::{UnwrapInfallible, UnwrapOptimized},
@@ -23,65 +24,6 @@ pub struct Bls12_381 {
2324
env: Env,
2425
}
2526

26-
// This routine was copied with slight modification from the arkworks library:
27-
// https://github.com/arkworks-rs/algebra/blob/bf1c9b22b30325ef4df4f701dedcb6dea904c587/ff/src/biginteger/arithmetic.rs#L66-L79
28-
fn sbb_for_sub_with_borrow(a: &mut u64, b: u64, borrow: u8) -> u8 {
29-
let tmp = (1u128 << 64) + u128::from(*a) - u128::from(b) - u128::from(borrow);
30-
// casting is safe here because `tmp` can only exceed u64 by a single
31-
// (borrow) bit, which we capture in the next line.
32-
*a = tmp as u64;
33-
u8::from(tmp >> 64 == 0)
34-
}
35-
36-
#[derive(Debug)]
37-
pub(crate) struct BigInt<const N: usize>(pub [u64; N]);
38-
39-
impl<const N: usize> BigInt<N> {
40-
pub fn sub_with_borrow(&mut self, other: &Self) -> bool {
41-
let mut borrow = 0;
42-
for i in 0..N {
43-
borrow = sbb_for_sub_with_borrow(&mut self.0[i], other.0[i], borrow);
44-
}
45-
borrow != 0
46-
}
47-
48-
pub fn copy_into_array<const M: usize>(&self, slice: &mut [u8; M]) {
49-
const {
50-
if M != N * 8 {
51-
panic!("BigInt::copy_into_array with mismatched array length")
52-
}
53-
}
54-
55-
for i in 0..N {
56-
let limb_bytes = self.0[N - 1 - i].to_be_bytes();
57-
slice[i * 8..(i + 1) * 8].copy_from_slice(&limb_bytes);
58-
}
59-
}
60-
61-
pub fn is_zero(&self) -> bool {
62-
self.0 == [0; N]
63-
}
64-
}
65-
66-
impl<const N: usize, const M: usize> From<&BytesN<M>> for BigInt<N> {
67-
fn from(bytes: &BytesN<M>) -> Self {
68-
if M != N * 8 {
69-
panic!("BytesN::Into<BigInt> - length mismatch")
70-
}
71-
72-
let array = bytes.to_array();
73-
let mut limbs = [0u64; N];
74-
for i in 0..N {
75-
let start = i * 8;
76-
let end = start + 8;
77-
let mut chunk = [0u8; 8];
78-
chunk.copy_from_slice(&array[start..end]);
79-
limbs[N - 1 - i] = u64::from_be_bytes(chunk);
80-
}
81-
BigInt(limbs)
82-
}
83-
}
84-
8527
/// `G1Affine` is a point in the G1 group (subgroup defined over the base field
8628
/// `Fq`) of the BLS12-381 elliptic curve
8729
///

soroban-sdk/src/crypto/bn254.rs

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[cfg(not(target_family = "wasm"))]
22
use crate::xdr::ScVal;
33
use crate::{
4+
crypto::utils::BigInt,
45
env::internal::{self, BytesObject, U256Val},
56
impl_bytesn_repr,
67
unwrap::{UnwrapInfallible, UnwrapOptimized},
@@ -9,7 +10,7 @@ use crate::{
910
use core::{
1011
cmp::Ordering,
1112
fmt::Debug,
12-
ops::{Add, Mul},
13+
ops::{Add, Mul, Neg},
1314
};
1415

1516
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).
@@ -23,7 +24,7 @@ pub struct Bn254 {
2324
}
2425

2526
/// `G1Affine` is a point in the G1 group (subgroup defined over the base field
26-
/// `Fq` with prime order `q =
27+
/// `Fp` with prime order `q =
2728
/// 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47`) of the
2829
/// BN254 elliptic curve
2930
///
@@ -37,7 +38,7 @@ pub struct Bn254 {
3738
pub struct G1Affine(BytesN<G1_SERIALIZED_SIZE>);
3839

3940
/// `G2Affine` is a point in the G2 group (subgroup defined over the quadratic
40-
/// extension field `Fq2`) of the BN254 elliptic curve
41+
/// extension field `Fp2`) of the BN254 elliptic curve
4142
///
4243
/// # Serialization:
4344
/// - The 128 bytes represent the **uncompressed encoding** of a point in G2.
@@ -58,15 +59,81 @@ pub struct G2Affine(BytesN<G2_SERIALIZED_SIZE>);
5859
#[repr(transparent)]
5960
pub struct Fr(U256);
6061

62+
/// `Fp` represents an element of the base field `Fp` of the BN254 elliptic curve
63+
///
64+
/// # Serialization:
65+
/// - The 32 bytes represent the **big-endian encoding** of an element in the
66+
/// field `Fp`. The value is serialized as a big-endian integer.
67+
#[derive(Clone)]
68+
#[repr(transparent)]
69+
pub struct Fp(BytesN<FP_SERIALIZED_SIZE>);
70+
6171
impl_bytesn_repr!(G1Affine, G1_SERIALIZED_SIZE);
6272
impl_bytesn_repr!(G2Affine, G2_SERIALIZED_SIZE);
73+
impl_bytesn_repr!(Fp, FP_SERIALIZED_SIZE);
6374

6475
impl G1Affine {
6576
pub fn env(&self) -> &Env {
6677
self.0.env()
6778
}
6879
}
6980

81+
impl Fp {
82+
pub fn env(&self) -> &Env {
83+
self.0.env()
84+
}
85+
86+
// `Fp` represents an element in the base field of the BN254 elliptic curve.
87+
// For an element a ∈ Fp, its negation `-a` is defined as:
88+
// a + (-a) = 0 (mod p)
89+
// where `p` is the field modulus, and to make a valid point coordinate on the
90+
// curve, `a` also must be within the field range (i.e., 0 ≤ a < p).
91+
fn checked_neg(&self) -> Option<Fp> {
92+
let fq_bigint: BigInt<4> = (&self.0).into();
93+
if fq_bigint.is_zero() {
94+
return Some(self.clone());
95+
}
96+
97+
//BN254 base field modulus
98+
const BN254_MODULUS: [u64; 4] = [
99+
4332616871279656263,
100+
10917124144477883021,
101+
13281191951274694749,
102+
3486998266802970665,
103+
];
104+
let mut res = BigInt(BN254_MODULUS);
105+
106+
// Compute modulus - value
107+
let borrow = res.sub_with_borrow(&fq_bigint);
108+
if borrow {
109+
return None;
110+
}
111+
112+
let mut bytes = [0u8; FP_SERIALIZED_SIZE];
113+
res.copy_into_array(&mut bytes);
114+
Some(Fp::from_array(self.env(), &bytes))
115+
}
116+
}
117+
118+
impl Neg for &Fp {
119+
type Output = Fp;
120+
121+
fn neg(self) -> Self::Output {
122+
match self.checked_neg() {
123+
Some(v) => v,
124+
None => sdk_panic!("invalid input - Fp is larger than the field modulus"),
125+
}
126+
}
127+
}
128+
129+
impl Neg for Fp {
130+
type Output = Fp;
131+
132+
fn neg(self) -> Self::Output {
133+
(&self).neg()
134+
}
135+
}
136+
70137
impl Add for G1Affine {
71138
type Output = G1Affine;
72139

@@ -83,6 +150,32 @@ impl Mul<Fr> for G1Affine {
83150
}
84151
}
85152

153+
// G1Affine represents a point (X, Y) on the BN254 curve where X, Y ∈ Fr
154+
// Negation of (X, Y) is defined as (X, -Y)
155+
impl Neg for &G1Affine {
156+
type Output = G1Affine;
157+
158+
fn neg(self) -> Self::Output {
159+
let mut inner: Bytes = (&self.0).into();
160+
let y = Fp::try_from_val(
161+
inner.env(),
162+
inner.slice(FP_SERIALIZED_SIZE as u32..).as_val(),
163+
)
164+
.unwrap_optimized();
165+
let neg_y = -y;
166+
inner.copy_from_slice(FP_SERIALIZED_SIZE as u32, &neg_y.to_array());
167+
G1Affine::from_bytes(BytesN::try_from_val(inner.env(), inner.as_val()).unwrap_optimized())
168+
}
169+
}
170+
171+
impl Neg for G1Affine {
172+
type Output = G1Affine;
173+
174+
fn neg(self) -> Self::Output {
175+
(&self).neg()
176+
}
177+
}
178+
86179
impl G2Affine {
87180
pub fn env(&self) -> &Env {
88181
self.0.env()
@@ -219,7 +312,7 @@ impl Bn254 {
219312
///
220313
/// This function computes the pairing for each pair of points in the
221314
/// 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.
315+
/// the product of all pairings is equal to 1 in the target group Fp.
223316
///
224317
/// # Returns:
225318
/// - `true` if the pairing check holds (i.e., the product of pairings equals 1),

soroban-sdk/src/crypto/utils.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use crate::BytesN;
2+
3+
// This routine was copied with slight modification from the arkworks library:
4+
// https://github.com/arkworks-rs/algebra/blob/bf1c9b22b30325ef4df4f701dedcb6dea904c587/ff/src/biginteger/arithmetic.rs#L66-L79
5+
fn sbb_for_sub_with_borrow(a: &mut u64, b: u64, borrow: u8) -> u8 {
6+
let tmp = (1u128 << 64) + u128::from(*a) - u128::from(b) - u128::from(borrow);
7+
// casting is safe here because `tmp` can only exceed u64 by a single
8+
// (borrow) bit, which we capture in the next line.
9+
*a = tmp as u64;
10+
u8::from(tmp >> 64 == 0)
11+
}
12+
13+
#[derive(Debug)]
14+
pub(crate) struct BigInt<const N: usize>(pub [u64; N]);
15+
16+
impl<const N: usize> BigInt<N> {
17+
pub fn sub_with_borrow(&mut self, other: &Self) -> bool {
18+
let mut borrow = 0;
19+
for i in 0..N {
20+
borrow = sbb_for_sub_with_borrow(&mut self.0[i], other.0[i], borrow);
21+
}
22+
borrow != 0
23+
}
24+
25+
pub fn copy_into_array<const M: usize>(&self, slice: &mut [u8; M]) {
26+
const {
27+
if M != N * 8 {
28+
panic!("BigInt::copy_into_array with mismatched array length")
29+
}
30+
}
31+
32+
for i in 0..N {
33+
let limb_bytes = self.0[N - 1 - i].to_be_bytes();
34+
slice[i * 8..(i + 1) * 8].copy_from_slice(&limb_bytes);
35+
}
36+
}
37+
38+
pub fn is_zero(&self) -> bool {
39+
self.0 == [0; N]
40+
}
41+
}
42+
43+
impl<const N: usize, const M: usize> From<&BytesN<M>> for BigInt<N> {
44+
fn from(bytes: &BytesN<M>) -> Self {
45+
if M != N * 8 {
46+
panic!("BytesN::Into<BigInt> - length mismatch")
47+
}
48+
49+
let array = bytes.to_array();
50+
let mut limbs = [0u64; N];
51+
for i in 0..N {
52+
let start = i * 8;
53+
let end = start + 8;
54+
let mut chunk = [0u8; 8];
55+
chunk.copy_from_slice(&array[start..end]);
56+
limbs[N - 1 - i] = u64::from_be_bytes(chunk);
57+
}
58+
BigInt(limbs)
59+
}
60+
}

tests/bn254/src/lib.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,21 @@ mod test {
126126
// This should return true for valid pairing
127127
assert!(client.verify_pairing(&proof));
128128
}
129+
130+
#[test]
131+
fn test_g1_negation() {
132+
let env = Env::default();
133+
134+
let negated_input = "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000130644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd45";
135+
let bytes = hex::decode(negated_input).unwrap();
136+
assert_eq!(bytes.len(), 128);
137+
138+
let g1_bytes: [u8; 64] = bytes[0..64].try_into().unwrap();
139+
let g1_negaed_bytes: [u8; 64] = bytes[64..128].try_into().unwrap();
140+
141+
let g1 = G1Affine::from_array(&env, &g1_bytes);
142+
let g1_negated = G1Affine::from_array(&env, &g1_negaed_bytes);
143+
144+
assert_eq!(-g1, g1_negated);
145+
}
129146
}

0 commit comments

Comments
 (0)