Skip to content

Commit 3795dbc

Browse files
authored
Compression for bls12381 (lambdaclass#904)
* Define trait Compress * Implement `Compress` for short weierstrass with BLS12381 curve The implementation was ported and adapted from https://github.com/lambdaclass/lambdaworks_kzg/blob/8f031b1f32e170c1af06029e46c73404f4c85e2e/src/compression.rs#L29. * Implement `decompress_g2_point` for BLS12381 The implementation was ported from https://github.com/lambdaclass/lambdaworks_kzg/blob/8f031b1f32e170c1af06029e46c73404f4c85e2e/src/compression.rs#L105 * Use `Compress` trait in BLS12381 benches * Fix return type `Compress` for no std * Fix no std for Compress trait * Remove alias types public visibility * Fix typo * Change `Compress` implementation from G1 point to bls12381 curve * Fix use of bls12381 compression in benches
1 parent 0c23464 commit 3795dbc

File tree

4 files changed

+167
-98
lines changed

4 files changed

+167
-98
lines changed

math/benches/elliptic_curves/bls12_381.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ use criterion::{black_box, Criterion};
22
use lambdaworks_math::{
33
cyclic_group::IsGroup,
44
elliptic_curve::{
5-
short_weierstrass::curves::bls12_381::{
6-
compression::{compress_g1_point, decompress_g1_point},
7-
curve::BLS12381Curve,
8-
pairing::BLS12381AtePairing,
9-
twist::BLS12381TwistCurve,
5+
short_weierstrass::{
6+
curves::bls12_381::{
7+
curve::BLS12381Curve, pairing::BLS12381AtePairing, twist::BLS12381TwistCurve,
8+
},
9+
traits::Compress,
1010
},
1111
traits::{IsEllipticCurve, IsPairing},
1212
},
@@ -70,13 +70,13 @@ pub fn bls12_381_elliptic_curve_benchmarks(c: &mut Criterion) {
7070

7171
// Compress_G1_point
7272
group.bench_function("Compress G1 point", |bencher| {
73-
bencher.iter(|| black_box(compress_g1_point(black_box(&a_g1))));
73+
bencher.iter(|| black_box(BLS12381Curve::compress_g1_point(black_box(&a_g1))));
7474
});
7575

7676
// Decompress_G1_point
7777
group.bench_function("Decompress G1 Point", |bencher| {
78-
let a: [u8; 48] = compress_g1_point(&a_g1).try_into().unwrap();
79-
bencher.iter(|| black_box(decompress_g1_point(&mut black_box(a))).unwrap());
78+
let a: [u8; 48] = BLS12381Curve::compress_g1_point(&a_g1).try_into().unwrap();
79+
bencher.iter(|| black_box(BLS12381Curve::decompress_g1_point(&mut black_box(a))).unwrap());
8080
});
8181

8282
// Subgroup Check G1

math/benches/elliptic_curves/iai_bls12_381.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,15 @@ use lambdaworks_math::{
44
elliptic_curve::{
55
short_weierstrass::{
66
curves::bls12_381::{
7-
compression::{compress_g1_point, decompress_g1_point},
8-
curve::BLS12381Curve,
9-
pairing::BLS12381AtePairing,
10-
twist::BLS12381TwistCurve,
7+
curve::BLS12381Curve, pairing::BLS12381AtePairing, twist::BLS12381TwistCurve,
118
},
129
point::ShortWeierstrassProjectivePoint,
10+
traits::Compress,
1311
},
1412
traits::{IsEllipticCurve, IsPairing},
1513
},
1614
};
15+
1716
use rand::{rngs::StdRng, Rng, SeedableRng};
1817
#[allow(dead_code)]
1918
type G1 = ShortWeierstrassProjectivePoint<BLS12381Curve>;
@@ -91,14 +90,14 @@ pub fn bls12_381_neg_g2() {
9190
#[allow(dead_code)]
9291
pub fn bls12_381_compress_g1() {
9392
let (a, _, _, _) = rand_points_g1();
94-
let _ = black_box(compress_g1_point(black_box(&a)));
93+
let _ = black_box(BLS12381Curve::compress_g1_point(black_box(&a)));
9594
}
9695

9796
#[allow(dead_code)]
9897
pub fn bls12_381_decompress_g1() {
9998
let (a, _, _, _) = rand_points_g1();
100-
let a: [u8; 48] = compress_g1_point(&a).try_into().unwrap();
101-
let _ = black_box(decompress_g1_point(&mut black_box(a))).unwrap();
99+
let a: [u8; 48] = BLS12381Curve::compress_g1_point(&a).try_into().unwrap();
100+
let _ = black_box(BLS12381Curve::decompress_g1_point(&mut black_box(a))).unwrap();
102101
}
103102

104103
#[allow(dead_code)]

math/src/elliptic_curve/short_weierstrass/curves/bls12_381/compression.rs

Lines changed: 139 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
use super::field_extension::BLS12381PrimeField;
1+
use super::{field_extension::BLS12381PrimeField, twist::BLS12381TwistCurve};
22
use crate::{
3-
elliptic_curve::short_weierstrass::{
4-
curves::bls12_381::curve::BLS12381Curve, point::ShortWeierstrassProjectivePoint,
3+
elliptic_curve::{
4+
short_weierstrass::{
5+
curves::bls12_381::{
6+
curve::BLS12381Curve, field_extension::Degree2ExtensionField, sqrt,
7+
},
8+
point::ShortWeierstrassProjectivePoint,
9+
traits::Compress,
10+
},
11+
traits::IsEllipticCurve,
512
},
613
field::element::FieldElement,
714
};
@@ -12,95 +19,138 @@ use crate::{
1219
traits::ByteConversion,
1320
};
1421

15-
pub type G1Point = ShortWeierstrassProjectivePoint<BLS12381Curve>;
16-
pub type BLS12381FieldElement = FieldElement<BLS12381PrimeField>;
17-
18-
pub fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result<G1Point, ByteConversionError> {
19-
let first_byte = input_bytes.first().unwrap();
20-
// We get the 3 most significant bits
21-
let prefix_bits = first_byte >> 5;
22-
let first_bit = (prefix_bits & 4_u8) >> 2;
23-
// If first bit is not 1, then the value is not compressed.
24-
if first_bit != 1 {
25-
return Err(ByteConversionError::ValueNotCompressed);
26-
}
27-
let second_bit = (prefix_bits & 2_u8) >> 1;
28-
// If the second bit is 1, then the compressed point is the
29-
// point at infinity and we return it directly.
30-
if second_bit == 1 {
31-
return Ok(G1Point::neutral_element());
22+
type G1Point = ShortWeierstrassProjectivePoint<BLS12381Curve>;
23+
type BLS12381FieldElement = FieldElement<BLS12381PrimeField>;
24+
25+
impl Compress for BLS12381Curve {
26+
type G1Point = G1Point;
27+
28+
type G2Point = <BLS12381TwistCurve as IsEllipticCurve>::PointRepresentation;
29+
30+
type Error = ByteConversionError;
31+
32+
#[cfg(feature = "alloc")]
33+
fn compress_g1_point(point: &Self::G1Point) -> alloc::vec::Vec<u8> {
34+
if *point == G1Point::neutral_element() {
35+
// point is at infinity
36+
let mut x_bytes = alloc::vec![0_u8; 48];
37+
x_bytes[0] |= 1 << 7;
38+
x_bytes[0] |= 1 << 6;
39+
x_bytes
40+
} else {
41+
// point is not at infinity
42+
let point_affine = point.to_affine();
43+
let x = point_affine.x();
44+
let y = point_affine.y();
45+
46+
let mut x_bytes = x.to_bytes_be();
47+
48+
// Set first bit to to 1 indicate this is compressed element.
49+
x_bytes[0] |= 1 << 7;
50+
51+
let y_neg = core::ops::Neg::neg(y);
52+
if y_neg.representative() < y.representative() {
53+
x_bytes[0] |= 1 << 5;
54+
}
55+
x_bytes
56+
}
3257
}
33-
let third_bit = prefix_bits & 1_u8;
34-
35-
let first_byte_without_control_bits = (first_byte << 3) >> 3;
36-
input_bytes[0] = first_byte_without_control_bits;
37-
38-
let x = BLS12381FieldElement::from_bytes_be(input_bytes)?;
39-
40-
// We apply the elliptic curve formula to know the y^2 value.
41-
let y_squared = x.pow(3_u16) + BLS12381FieldElement::from(4);
42-
43-
let (y_sqrt_1, y_sqrt_2) = &y_squared.sqrt().ok_or(ByteConversionError::InvalidValue)?;
44-
45-
// we call "negative" to the greate root,
46-
// if the third bit is 1, we take this grater value.
47-
// Otherwise, we take the second one.
48-
let y = match (
49-
y_sqrt_1.representative().cmp(&y_sqrt_2.representative()),
50-
third_bit,
51-
) {
52-
(Ordering::Greater, 0) => y_sqrt_2,
53-
(Ordering::Greater, _) => y_sqrt_1,
54-
(Ordering::Less, 0) => y_sqrt_1,
55-
(Ordering::Less, _) => y_sqrt_2,
56-
(Ordering::Equal, _) => y_sqrt_1,
57-
};
5858

59-
let point =
60-
G1Point::from_affine(x, y.clone()).map_err(|_| ByteConversionError::InvalidValue)?;
59+
fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result<Self::G1Point, Self::Error> {
60+
let first_byte = input_bytes.first().unwrap();
61+
// We get the 3 most significant bits
62+
let prefix_bits = first_byte >> 5;
63+
let first_bit = (prefix_bits & 4_u8) >> 2;
64+
// If first bit is not 1, then the value is not compressed.
65+
if first_bit != 1 {
66+
return Err(ByteConversionError::ValueNotCompressed);
67+
}
68+
let second_bit = (prefix_bits & 2_u8) >> 1;
69+
// If the second bit is 1, then the compressed point is the
70+
// point at infinity and we return it directly.
71+
if second_bit == 1 {
72+
return Ok(G1Point::neutral_element());
73+
}
74+
let third_bit = prefix_bits & 1_u8;
6175

62-
point
63-
.is_in_subgroup()
64-
.then_some(point)
65-
.ok_or(ByteConversionError::PointNotInSubgroup)
66-
}
76+
let first_byte_without_control_bits = (first_byte << 3) >> 3;
77+
input_bytes[0] = first_byte_without_control_bits;
78+
79+
let x = BLS12381FieldElement::from_bytes_be(input_bytes)?;
80+
81+
// We apply the elliptic curve formula to know the y^2 value.
82+
let y_squared = x.pow(3_u16) + BLS12381FieldElement::from(4);
83+
84+
let (y_sqrt_1, y_sqrt_2) = &y_squared.sqrt().ok_or(ByteConversionError::InvalidValue)?;
85+
86+
// we call "negative" to the greate root,
87+
// if the third bit is 1, we take this grater value.
88+
// Otherwise, we take the second one.
89+
let y = match (
90+
y_sqrt_1.representative().cmp(&y_sqrt_2.representative()),
91+
third_bit,
92+
) {
93+
(Ordering::Greater, 0) => y_sqrt_2,
94+
(Ordering::Greater, _) => y_sqrt_1,
95+
(Ordering::Less, 0) => y_sqrt_1,
96+
(Ordering::Less, _) => y_sqrt_2,
97+
(Ordering::Equal, _) => y_sqrt_1,
98+
};
99+
100+
let point =
101+
G1Point::from_affine(x, y.clone()).map_err(|_| ByteConversionError::InvalidValue)?;
102+
103+
point
104+
.is_in_subgroup()
105+
.then_some(point)
106+
.ok_or(ByteConversionError::PointNotInSubgroup)
107+
}
108+
109+
#[allow(unused)]
110+
fn decompress_g2_point(input_bytes: &mut [u8; 96]) -> Result<Self::G2Point, Self::Error> {
111+
let first_byte = input_bytes.first().unwrap();
67112

68-
#[cfg(feature = "alloc")]
69-
pub fn compress_g1_point(point: &G1Point) -> alloc::vec::Vec<u8> {
70-
if *point == G1Point::neutral_element() {
71-
// point is at infinity
72-
let mut x_bytes = alloc::vec![0_u8; 48];
73-
x_bytes[0] |= 1 << 7;
74-
x_bytes[0] |= 1 << 6;
75-
x_bytes
76-
} else {
77-
// point is not at infinity
78-
let point_affine = point.to_affine();
79-
let x = point_affine.x();
80-
let y = point_affine.y();
81-
82-
let mut x_bytes = x.to_bytes_be();
83-
84-
// Set first bit to to 1 indicate this is compressed element.
85-
x_bytes[0] |= 1 << 7;
86-
87-
let y_neg = core::ops::Neg::neg(y);
88-
if y_neg.representative() < y.representative() {
89-
x_bytes[0] |= 1 << 5;
113+
// We get the first 3 bits
114+
let prefix_bits = first_byte >> 5;
115+
let first_bit = (prefix_bits & 4_u8) >> 2;
116+
// If first bit is not 1, then the value is not compressed.
117+
if first_bit != 1 {
118+
return Err(ByteConversionError::InvalidValue);
119+
}
120+
let second_bit = (prefix_bits & 2_u8) >> 1;
121+
// If the second bit is 1, then the compressed point is the
122+
// point at infinity and we return it directly.
123+
if second_bit == 1 {
124+
return Ok(Self::G2Point::neutral_element());
90125
}
91-
x_bytes
126+
127+
let first_byte_without_control_bits = (first_byte << 3) >> 3;
128+
input_bytes[0] = first_byte_without_control_bits;
129+
130+
let input0 = &input_bytes[48..];
131+
let input1 = &input_bytes[0..48];
132+
let x0 = BLS12381FieldElement::from_bytes_be(input0).unwrap();
133+
let x1 = BLS12381FieldElement::from_bytes_be(input1).unwrap();
134+
let x: FieldElement<Degree2ExtensionField> = FieldElement::new([x0, x1]);
135+
136+
const VALUE: BLS12381FieldElement = BLS12381FieldElement::from_hex_unchecked("4");
137+
let b_param_qfe = FieldElement::<Degree2ExtensionField>::new([VALUE, VALUE]);
138+
139+
let y = sqrt::sqrt_qfe(&(x.pow(3_u64) + b_param_qfe), 0)
140+
.ok_or(ByteConversionError::InvalidValue)?;
141+
142+
Self::G2Point::from_affine(x, y).map_err(|_| ByteConversionError::InvalidValue)
92143
}
93144
}
94145

95146
#[cfg(test)]
96147
mod tests {
97148
use super::{BLS12381FieldElement, G1Point};
98149
use crate::elliptic_curve::short_weierstrass::curves::bls12_381::curve::BLS12381Curve;
150+
use crate::elliptic_curve::short_weierstrass::traits::Compress;
99151
use crate::elliptic_curve::traits::{FromAffine, IsEllipticCurve};
100152

101153
#[cfg(feature = "alloc")]
102-
use super::compress_g1_point;
103-
use super::decompress_g1_point;
104154
use crate::{
105155
cyclic_group::IsGroup, traits::ByteConversion, unsigned_integer::element::UnsignedInteger,
106156
};
@@ -121,8 +171,10 @@ mod tests {
121171
#[cfg(feature = "alloc")]
122172
#[test]
123173
fn test_g1_compress_generator() {
174+
use crate::elliptic_curve::short_weierstrass::traits::Compress;
175+
124176
let g = BLS12381Curve::generator();
125-
let mut compressed_g = compress_g1_point(&g);
177+
let mut compressed_g = BLS12381Curve::compress_g1_point(&g);
126178
let first_byte = compressed_g.first().unwrap();
127179

128180
let first_byte_without_control_bits = (first_byte << 3) >> 3;
@@ -137,8 +189,10 @@ mod tests {
137189
#[cfg(feature = "alloc")]
138190
#[test]
139191
fn test_g1_compress_point_at_inf() {
192+
use crate::elliptic_curve::short_weierstrass::traits::Compress;
193+
140194
let inf = G1Point::neutral_element();
141-
let compressed_inf = compress_g1_point(&inf);
195+
let compressed_inf = BLS12381Curve::compress_g1_point(&inf);
142196
let first_byte = compressed_inf.first().unwrap();
143197

144198
assert_eq!(*first_byte >> 6, 3_u8);
@@ -147,11 +201,13 @@ mod tests {
147201
#[cfg(feature = "alloc")]
148202
#[test]
149203
fn test_compress_decompress_generator() {
204+
use crate::elliptic_curve::short_weierstrass::traits::Compress;
205+
150206
let g = BLS12381Curve::generator();
151-
let compressed_g = compress_g1_point(&g);
207+
let compressed_g = BLS12381Curve::compress_g1_point(&g);
152208
let mut compressed_g_slice: [u8; 48] = compressed_g.try_into().unwrap();
153209

154-
let decompressed_g = decompress_g1_point(&mut compressed_g_slice).unwrap();
210+
let decompressed_g = BLS12381Curve::decompress_g1_point(&mut compressed_g_slice).unwrap();
155211

156212
assert_eq!(g, decompressed_g);
157213
}
@@ -163,10 +219,10 @@ mod tests {
163219
// calculate g point operate with itself
164220
let g_2 = g.operate_with_self(UnsignedInteger::<4>::from("2"));
165221

166-
let compressed_g2 = compress_g1_point(&g_2);
222+
let compressed_g2 = BLS12381Curve::compress_g1_point(&g_2);
167223
let mut compressed_g2_slice: [u8; 48] = compressed_g2.try_into().unwrap();
168224

169-
let decompressed_g2 = decompress_g1_point(&mut compressed_g2_slice).unwrap();
225+
let decompressed_g2 = BLS12381Curve::decompress_g1_point(&mut compressed_g2_slice).unwrap();
170226

171227
assert_eq!(g_2, decompressed_g2);
172228
}

math/src/elliptic_curve/short_weierstrass/traits.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::cyclic_group::IsGroup;
12
use crate::elliptic_curve::traits::IsEllipticCurve;
23
use crate::field::element::FieldElement;
34
use core::fmt::Debug;
@@ -18,3 +19,16 @@ pub trait IsShortWeierstrass: IsEllipticCurve + Clone + Debug {
1819
y.pow(2_u16) - x.pow(3_u16) - Self::a() * x - Self::b()
1920
}
2021
}
22+
23+
pub trait Compress {
24+
type G1Point: IsGroup;
25+
type G2Point: IsGroup;
26+
type Error;
27+
28+
#[cfg(feature = "alloc")]
29+
fn compress_g1_point(point: &Self::G1Point) -> alloc::vec::Vec<u8>;
30+
31+
fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result<Self::G1Point, Self::Error>;
32+
33+
fn decompress_g2_point(input_bytes: &mut [u8; 96]) -> Result<Self::G2Point, Self::Error>;
34+
}

0 commit comments

Comments
 (0)