Skip to content

Commit

Permalink
Compression for bls12381 (#904)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
lazcanoluca authored Sep 4, 2024
1 parent 0c23464 commit 3795dbc
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 98 deletions.
16 changes: 8 additions & 8 deletions math/benches/elliptic_curves/bls12_381.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use criterion::{black_box, Criterion};
use lambdaworks_math::{
cyclic_group::IsGroup,
elliptic_curve::{
short_weierstrass::curves::bls12_381::{
compression::{compress_g1_point, decompress_g1_point},
curve::BLS12381Curve,
pairing::BLS12381AtePairing,
twist::BLS12381TwistCurve,
short_weierstrass::{
curves::bls12_381::{
curve::BLS12381Curve, pairing::BLS12381AtePairing, twist::BLS12381TwistCurve,
},
traits::Compress,
},
traits::{IsEllipticCurve, IsPairing},
},
Expand Down Expand Up @@ -70,13 +70,13 @@ pub fn bls12_381_elliptic_curve_benchmarks(c: &mut Criterion) {

// Compress_G1_point
group.bench_function("Compress G1 point", |bencher| {
bencher.iter(|| black_box(compress_g1_point(black_box(&a_g1))));
bencher.iter(|| black_box(BLS12381Curve::compress_g1_point(black_box(&a_g1))));
});

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

// Subgroup Check G1
Expand Down
13 changes: 6 additions & 7 deletions math/benches/elliptic_curves/iai_bls12_381.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ use lambdaworks_math::{
elliptic_curve::{
short_weierstrass::{
curves::bls12_381::{
compression::{compress_g1_point, decompress_g1_point},
curve::BLS12381Curve,
pairing::BLS12381AtePairing,
twist::BLS12381TwistCurve,
curve::BLS12381Curve, pairing::BLS12381AtePairing, twist::BLS12381TwistCurve,
},
point::ShortWeierstrassProjectivePoint,
traits::Compress,
},
traits::{IsEllipticCurve, IsPairing},
},
};

use rand::{rngs::StdRng, Rng, SeedableRng};
#[allow(dead_code)]
type G1 = ShortWeierstrassProjectivePoint<BLS12381Curve>;
Expand Down Expand Up @@ -91,14 +90,14 @@ pub fn bls12_381_neg_g2() {
#[allow(dead_code)]
pub fn bls12_381_compress_g1() {
let (a, _, _, _) = rand_points_g1();
let _ = black_box(compress_g1_point(black_box(&a)));
let _ = black_box(BLS12381Curve::compress_g1_point(black_box(&a)));
}

#[allow(dead_code)]
pub fn bls12_381_decompress_g1() {
let (a, _, _, _) = rand_points_g1();
let a: [u8; 48] = compress_g1_point(&a).try_into().unwrap();
let _ = black_box(decompress_g1_point(&mut black_box(a))).unwrap();
let a: [u8; 48] = BLS12381Curve::compress_g1_point(&a).try_into().unwrap();
let _ = black_box(BLS12381Curve::decompress_g1_point(&mut black_box(a))).unwrap();
}

#[allow(dead_code)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
use super::field_extension::BLS12381PrimeField;
use super::{field_extension::BLS12381PrimeField, twist::BLS12381TwistCurve};
use crate::{
elliptic_curve::short_weierstrass::{
curves::bls12_381::curve::BLS12381Curve, point::ShortWeierstrassProjectivePoint,
elliptic_curve::{
short_weierstrass::{
curves::bls12_381::{
curve::BLS12381Curve, field_extension::Degree2ExtensionField, sqrt,
},
point::ShortWeierstrassProjectivePoint,
traits::Compress,
},
traits::IsEllipticCurve,
},
field::element::FieldElement,
};
Expand All @@ -12,95 +19,138 @@ use crate::{
traits::ByteConversion,
};

pub type G1Point = ShortWeierstrassProjectivePoint<BLS12381Curve>;
pub type BLS12381FieldElement = FieldElement<BLS12381PrimeField>;

pub fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result<G1Point, ByteConversionError> {
let first_byte = input_bytes.first().unwrap();
// We get the 3 most significant bits
let prefix_bits = first_byte >> 5;
let first_bit = (prefix_bits & 4_u8) >> 2;
// If first bit is not 1, then the value is not compressed.
if first_bit != 1 {
return Err(ByteConversionError::ValueNotCompressed);
}
let second_bit = (prefix_bits & 2_u8) >> 1;
// If the second bit is 1, then the compressed point is the
// point at infinity and we return it directly.
if second_bit == 1 {
return Ok(G1Point::neutral_element());
type G1Point = ShortWeierstrassProjectivePoint<BLS12381Curve>;
type BLS12381FieldElement = FieldElement<BLS12381PrimeField>;

impl Compress for BLS12381Curve {
type G1Point = G1Point;

type G2Point = <BLS12381TwistCurve as IsEllipticCurve>::PointRepresentation;

type Error = ByteConversionError;

#[cfg(feature = "alloc")]
fn compress_g1_point(point: &Self::G1Point) -> alloc::vec::Vec<u8> {
if *point == G1Point::neutral_element() {
// point is at infinity
let mut x_bytes = alloc::vec![0_u8; 48];
x_bytes[0] |= 1 << 7;
x_bytes[0] |= 1 << 6;
x_bytes
} else {
// point is not at infinity
let point_affine = point.to_affine();
let x = point_affine.x();
let y = point_affine.y();

let mut x_bytes = x.to_bytes_be();

// Set first bit to to 1 indicate this is compressed element.
x_bytes[0] |= 1 << 7;

let y_neg = core::ops::Neg::neg(y);
if y_neg.representative() < y.representative() {
x_bytes[0] |= 1 << 5;
}
x_bytes
}
}
let third_bit = prefix_bits & 1_u8;

let first_byte_without_control_bits = (first_byte << 3) >> 3;
input_bytes[0] = first_byte_without_control_bits;

let x = BLS12381FieldElement::from_bytes_be(input_bytes)?;

// We apply the elliptic curve formula to know the y^2 value.
let y_squared = x.pow(3_u16) + BLS12381FieldElement::from(4);

let (y_sqrt_1, y_sqrt_2) = &y_squared.sqrt().ok_or(ByteConversionError::InvalidValue)?;

// we call "negative" to the greate root,
// if the third bit is 1, we take this grater value.
// Otherwise, we take the second one.
let y = match (
y_sqrt_1.representative().cmp(&y_sqrt_2.representative()),
third_bit,
) {
(Ordering::Greater, 0) => y_sqrt_2,
(Ordering::Greater, _) => y_sqrt_1,
(Ordering::Less, 0) => y_sqrt_1,
(Ordering::Less, _) => y_sqrt_2,
(Ordering::Equal, _) => y_sqrt_1,
};

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

point
.is_in_subgroup()
.then_some(point)
.ok_or(ByteConversionError::PointNotInSubgroup)
}
let first_byte_without_control_bits = (first_byte << 3) >> 3;
input_bytes[0] = first_byte_without_control_bits;

let x = BLS12381FieldElement::from_bytes_be(input_bytes)?;

// We apply the elliptic curve formula to know the y^2 value.
let y_squared = x.pow(3_u16) + BLS12381FieldElement::from(4);

let (y_sqrt_1, y_sqrt_2) = &y_squared.sqrt().ok_or(ByteConversionError::InvalidValue)?;

// we call "negative" to the greate root,
// if the third bit is 1, we take this grater value.
// Otherwise, we take the second one.
let y = match (
y_sqrt_1.representative().cmp(&y_sqrt_2.representative()),
third_bit,
) {
(Ordering::Greater, 0) => y_sqrt_2,
(Ordering::Greater, _) => y_sqrt_1,
(Ordering::Less, 0) => y_sqrt_1,
(Ordering::Less, _) => y_sqrt_2,
(Ordering::Equal, _) => y_sqrt_1,
};

let point =
G1Point::from_affine(x, y.clone()).map_err(|_| ByteConversionError::InvalidValue)?;

point
.is_in_subgroup()
.then_some(point)
.ok_or(ByteConversionError::PointNotInSubgroup)
}

#[allow(unused)]
fn decompress_g2_point(input_bytes: &mut [u8; 96]) -> Result<Self::G2Point, Self::Error> {
let first_byte = input_bytes.first().unwrap();

#[cfg(feature = "alloc")]
pub fn compress_g1_point(point: &G1Point) -> alloc::vec::Vec<u8> {
if *point == G1Point::neutral_element() {
// point is at infinity
let mut x_bytes = alloc::vec![0_u8; 48];
x_bytes[0] |= 1 << 7;
x_bytes[0] |= 1 << 6;
x_bytes
} else {
// point is not at infinity
let point_affine = point.to_affine();
let x = point_affine.x();
let y = point_affine.y();

let mut x_bytes = x.to_bytes_be();

// Set first bit to to 1 indicate this is compressed element.
x_bytes[0] |= 1 << 7;

let y_neg = core::ops::Neg::neg(y);
if y_neg.representative() < y.representative() {
x_bytes[0] |= 1 << 5;
// We get the first 3 bits
let prefix_bits = first_byte >> 5;
let first_bit = (prefix_bits & 4_u8) >> 2;
// If first bit is not 1, then the value is not compressed.
if first_bit != 1 {
return Err(ByteConversionError::InvalidValue);
}
let second_bit = (prefix_bits & 2_u8) >> 1;
// If the second bit is 1, then the compressed point is the
// point at infinity and we return it directly.
if second_bit == 1 {
return Ok(Self::G2Point::neutral_element());
}
x_bytes

let first_byte_without_control_bits = (first_byte << 3) >> 3;
input_bytes[0] = first_byte_without_control_bits;

let input0 = &input_bytes[48..];
let input1 = &input_bytes[0..48];
let x0 = BLS12381FieldElement::from_bytes_be(input0).unwrap();
let x1 = BLS12381FieldElement::from_bytes_be(input1).unwrap();
let x: FieldElement<Degree2ExtensionField> = FieldElement::new([x0, x1]);

const VALUE: BLS12381FieldElement = BLS12381FieldElement::from_hex_unchecked("4");
let b_param_qfe = FieldElement::<Degree2ExtensionField>::new([VALUE, VALUE]);

let y = sqrt::sqrt_qfe(&(x.pow(3_u64) + b_param_qfe), 0)
.ok_or(ByteConversionError::InvalidValue)?;

Self::G2Point::from_affine(x, y).map_err(|_| ByteConversionError::InvalidValue)
}
}

#[cfg(test)]
mod tests {
use super::{BLS12381FieldElement, G1Point};
use crate::elliptic_curve::short_weierstrass::curves::bls12_381::curve::BLS12381Curve;
use crate::elliptic_curve::short_weierstrass::traits::Compress;
use crate::elliptic_curve::traits::{FromAffine, IsEllipticCurve};

#[cfg(feature = "alloc")]
use super::compress_g1_point;
use super::decompress_g1_point;
use crate::{
cyclic_group::IsGroup, traits::ByteConversion, unsigned_integer::element::UnsignedInteger,
};
Expand All @@ -121,8 +171,10 @@ mod tests {
#[cfg(feature = "alloc")]
#[test]
fn test_g1_compress_generator() {
use crate::elliptic_curve::short_weierstrass::traits::Compress;

let g = BLS12381Curve::generator();
let mut compressed_g = compress_g1_point(&g);
let mut compressed_g = BLS12381Curve::compress_g1_point(&g);
let first_byte = compressed_g.first().unwrap();

let first_byte_without_control_bits = (first_byte << 3) >> 3;
Expand All @@ -137,8 +189,10 @@ mod tests {
#[cfg(feature = "alloc")]
#[test]
fn test_g1_compress_point_at_inf() {
use crate::elliptic_curve::short_weierstrass::traits::Compress;

let inf = G1Point::neutral_element();
let compressed_inf = compress_g1_point(&inf);
let compressed_inf = BLS12381Curve::compress_g1_point(&inf);
let first_byte = compressed_inf.first().unwrap();

assert_eq!(*first_byte >> 6, 3_u8);
Expand All @@ -147,11 +201,13 @@ mod tests {
#[cfg(feature = "alloc")]
#[test]
fn test_compress_decompress_generator() {
use crate::elliptic_curve::short_weierstrass::traits::Compress;

let g = BLS12381Curve::generator();
let compressed_g = compress_g1_point(&g);
let compressed_g = BLS12381Curve::compress_g1_point(&g);
let mut compressed_g_slice: [u8; 48] = compressed_g.try_into().unwrap();

let decompressed_g = decompress_g1_point(&mut compressed_g_slice).unwrap();
let decompressed_g = BLS12381Curve::decompress_g1_point(&mut compressed_g_slice).unwrap();

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

let compressed_g2 = compress_g1_point(&g_2);
let compressed_g2 = BLS12381Curve::compress_g1_point(&g_2);
let mut compressed_g2_slice: [u8; 48] = compressed_g2.try_into().unwrap();

let decompressed_g2 = decompress_g1_point(&mut compressed_g2_slice).unwrap();
let decompressed_g2 = BLS12381Curve::decompress_g1_point(&mut compressed_g2_slice).unwrap();

assert_eq!(g_2, decompressed_g2);
}
Expand Down
14 changes: 14 additions & 0 deletions math/src/elliptic_curve/short_weierstrass/traits.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::cyclic_group::IsGroup;
use crate::elliptic_curve::traits::IsEllipticCurve;
use crate::field::element::FieldElement;
use core::fmt::Debug;
Expand All @@ -18,3 +19,16 @@ pub trait IsShortWeierstrass: IsEllipticCurve + Clone + Debug {
y.pow(2_u16) - x.pow(3_u16) - Self::a() * x - Self::b()
}
}

pub trait Compress {
type G1Point: IsGroup;
type G2Point: IsGroup;
type Error;

#[cfg(feature = "alloc")]
fn compress_g1_point(point: &Self::G1Point) -> alloc::vec::Vec<u8>;

fn decompress_g1_point(input_bytes: &mut [u8; 48]) -> Result<Self::G1Point, Self::Error>;

fn decompress_g2_point(input_bytes: &mut [u8; 96]) -> Result<Self::G2Point, Self::Error>;
}

0 comments on commit 3795dbc

Please sign in to comment.