Skip to content

Commit

Permalink
BLS 12-381 group ops review (#688)
Browse files Browse the repository at this point in the history
* Uncompress is safer

* Avoid bias in random sampling

* Review

* More tests

* Docs
  • Loading branch information
jonas-lj authored Nov 8, 2023
1 parent 1b3323b commit 3c366db
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 28 deletions.
56 changes: 29 additions & 27 deletions fastcrypto/src/groups/bls12381.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ use blst::{
blst_fr_from_scalar, blst_fr_from_uint64, blst_fr_inverse, blst_fr_mul, blst_fr_rshift,
blst_fr_sub, blst_hash_to_g1, blst_hash_to_g2, blst_lendian_from_scalar, blst_miller_loop,
blst_p1, blst_p1_add_or_double, blst_p1_affine, blst_p1_cneg, blst_p1_compress,
blst_p1_deserialize, blst_p1_from_affine, blst_p1_in_g1, blst_p1_mult, blst_p1_to_affine,
blst_p1_from_affine, blst_p1_in_g1, blst_p1_mult, blst_p1_to_affine, blst_p1_uncompress,
blst_p2, blst_p2_add_or_double, blst_p2_affine, blst_p2_cneg, blst_p2_compress,
blst_p2_deserialize, blst_p2_from_affine, blst_p2_in_g2, blst_p2_mult, blst_p2_to_affine,
blst_scalar, blst_scalar_fr_check, blst_scalar_from_bendian, blst_scalar_from_fr, p1_affines,
p2_affines, BLS12_381_G1, BLS12_381_G2, BLST_ERROR,
blst_p2_from_affine, blst_p2_in_g2, blst_p2_mult, blst_p2_to_affine, blst_p2_uncompress,
blst_scalar, blst_scalar_fr_check, blst_scalar_from_be_bytes, blst_scalar_from_bendian,
blst_scalar_from_fr, p1_affines, p2_affines, BLS12_381_G1, BLS12_381_G2, BLST_ERROR,
};
use derive_more::From;
use fastcrypto_derive::GroupOpsExtend;
Expand Down Expand Up @@ -167,6 +167,7 @@ impl MultiScalarMul for G1Element {
}
scalar_bytes.extend_from_slice(&scalar.b);
}
// The scalar field size is smaller than 2^255, so we need at most 255 bits.
let res = points.mult(scalar_bytes.as_slice(), 255);
Ok(Self::from(res))
}
Expand Down Expand Up @@ -229,7 +230,7 @@ impl ToFromByteArray<G1_ELEMENT_BYTE_LENGTH> for G1Element {
let mut ret = blst_p1::default();
unsafe {
let mut affine = blst_p1_affine::default();
if blst_p1_deserialize(&mut affine, bytes.as_ptr()) != BLST_ERROR::BLST_SUCCESS {
if blst_p1_uncompress(&mut affine, bytes.as_ptr()) != BLST_ERROR::BLST_SUCCESS {
return Err(FastCryptoError::InvalidInput);
}
blst_p1_from_affine(&mut ret, &affine);
Expand Down Expand Up @@ -354,6 +355,7 @@ impl MultiScalarMul for G2Element {
}
scalar_bytes.extend_from_slice(&scalar.b);
}
// The scalar field size is smaller than 2^255, so we need at most 255 bits.
let res = points.mult(scalar_bytes.as_slice(), 255);
Ok(Self::from(res))
}
Expand Down Expand Up @@ -398,7 +400,7 @@ impl ToFromByteArray<G2_ELEMENT_BYTE_LENGTH> for G2Element {
let mut ret = blst_p2::default();
unsafe {
let mut affine = blst_p2_affine::default();
if blst_p2_deserialize(&mut affine, bytes.as_ptr()) != BLST_ERROR::BLST_SUCCESS {
if blst_p2_uncompress(&mut affine, bytes.as_ptr()) != BLST_ERROR::BLST_SUCCESS {
return Err(FastCryptoError::InvalidInput);
}
blst_p2_from_affine(&mut ret, &affine);
Expand Down Expand Up @@ -651,16 +653,9 @@ impl Div<Scalar> for Scalar {

impl ScalarType for Scalar {
fn rand<R: AllowedRng>(rng: &mut R) -> Self {
let mut ret = blst_fr::default();
let mut bytes = [0u8; SCALAR_LENGTH];
rng.fill_bytes(&mut bytes);
// TODO: is this secure?
unsafe {
let mut scalar = blst_scalar::default();
blst_scalar_from_bendian(&mut scalar, bytes.as_ptr());
blst_fr_from_scalar(&mut ret, &scalar);
}
Scalar::from(ret)
let mut buffer = [0u8; 64];
rng.fill_bytes(&mut buffer);
reduce_mod_uniform_buffer(&buffer)
}

fn inverse(&self) -> FastCryptoResult<Self> {
Expand All @@ -675,19 +670,26 @@ impl ScalarType for Scalar {
}
}

/// Reduce a big-endian integer of arbitrary size modulo the scalar field size and return the scalar.
/// If the input bytes are uniformly distributed, the output will be uniformly distributed in the
/// scalar field.
///
/// The input buffer must be at least 48 bytes long to ensure that there is only negligible bias in
/// the output.
pub(crate) fn reduce_mod_uniform_buffer(buffer: &[u8]) -> Scalar {
assert!(buffer.len() >= 48);
let mut ret = blst_fr::default();
let mut tmp = blst_scalar::default();
unsafe {
blst_scalar_from_be_bytes(&mut tmp, buffer.as_ptr(), buffer.len());
blst_fr_from_scalar(&mut ret, &tmp);
}
Scalar::from(ret)
}

impl FiatShamirChallenge for Scalar {
fn fiat_shamir_reduction_to_group_element(uniform_buffer: &[u8]) -> Self {
const INPUT_LENGTH: usize = SCALAR_LENGTH - 1; // Safe for our prime field
assert!(INPUT_LENGTH <= uniform_buffer.len());
let mut bytes = [0u8; SCALAR_LENGTH];
bytes[..INPUT_LENGTH].copy_from_slice(&uniform_buffer[..INPUT_LENGTH]);
let mut ret = blst_fr::default();
unsafe {
let mut scalar = blst_scalar::default();
blst_scalar_from_bendian(&mut scalar, bytes.as_ptr());
blst_fr_from_scalar(&mut ret, &scalar);
}
Scalar::from(ret)
reduce_mod_uniform_buffer(uniform_buffer)
}
}

Expand Down
118 changes: 117 additions & 1 deletion fastcrypto/src/tests/bls12381_group_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use crate::bls12381::min_pk::{BLS12381KeyPair, BLS12381Signature};
use crate::groups::bls12381::{G1Element, G2Element, GTElement, Scalar};
use crate::groups::bls12381::{reduce_mod_uniform_buffer, G1Element, G2Element, GTElement, Scalar};
use crate::groups::{
GroupElement, HashToGroupElement, MultiScalarMul, Pairing, Scalar as ScalarTrait,
};
Expand All @@ -11,6 +11,10 @@ use crate::test_helpers::verify_serialization;
use crate::traits::Signer;
use crate::traits::VerifyingKey;
use crate::traits::{KeyPair, ToFromBytes};
use blst::{
blst_p1_affine_generator, blst_p1_affine_serialize, blst_p2_affine_generator,
blst_p2_affine_serialize,
};
use rand::{rngs::StdRng, thread_rng, SeedableRng as _};

const MSG: &[u8] = b"test message";
Expand Down Expand Up @@ -243,3 +247,115 @@ fn test_consistent_bls12381_serialization() {
pk1.verify(MSG, &sig3).unwrap();
assert_eq!(sig1, sig3);
}

#[test]
fn test_serialization_g1() {
let infinity_bit = 0x40;
let compressed_bit = 0x80;

// All zero serialization for G1 should fail.
let mut bytes = [0u8; 48];
assert!(G1Element::from_byte_array(&bytes).is_err());

// Infinity w/o compressed byte should fail.
bytes[0] |= infinity_bit;
assert!(G1Element::from_byte_array(&bytes).is_err());

// Valid infinity
bytes[0] |= compressed_bit;
assert_eq!(
G1Element::zero(),
G1Element::from_byte_array(&bytes).unwrap()
);

// to and from_byte_array should be inverses.
let mut bytes = G1Element::generator().to_byte_array();
assert_eq!(
G1Element::generator(),
G1Element::from_byte_array(&bytes).unwrap()
);
assert_ne!(bytes[0] & compressed_bit, 0);

// Unsetting the compressed bit set, this should fail.
bytes[0] ^= compressed_bit;
assert!(G1Element::from_byte_array(&bytes).is_err());

// Test correct uncompressed serialization of a point
let mut uncompressed_bytes = [0u8; 96];
unsafe {
blst_p1_affine_serialize(uncompressed_bytes.as_mut_ptr(), blst_p1_affine_generator());
}
// This should fail because from_byte_array only accepts compressed format.
assert!(G1Element::from_byte_array(&(uncompressed_bytes[0..48].try_into().unwrap())).is_err());

// But if we set the uncompressed bit, it should work because the compressed format is just the first coordinate.
uncompressed_bytes[0] |= 0x80;
assert_eq!(
G1Element::generator(),
G1Element::from_byte_array(&(uncompressed_bytes[0..48].try_into().unwrap())).unwrap()
);
}

#[test]
fn test_serialization_g2() {
let infinity_bit = 0x40;
let compressed_bit = 0x80;

// All zero serialization for G2 should fail.
let mut bytes = [0u8; 96];
assert!(G2Element::from_byte_array(&bytes).is_err());

// Infinity w/o compressed byte should fail.
bytes[0] |= infinity_bit;
assert!(G2Element::from_byte_array(&bytes).is_err());

// Valid infinity when the right bits are set.
bytes[0] |= compressed_bit;
assert_eq!(
G2Element::zero(),
G2Element::from_byte_array(&bytes).unwrap()
);

// to and from_byte_array should be inverses.
let mut bytes = G2Element::generator().to_byte_array();
assert_eq!(
G2Element::generator(),
G2Element::from_byte_array(&bytes).unwrap()
);
assert_ne!(bytes[0] & compressed_bit, 0);

// Unsetting the compressed bit set, this should fail.
bytes[0] ^= compressed_bit;
assert!(G2Element::from_byte_array(&bytes).is_err());

// Test correct uncompressed serialization of a point
let mut uncompressed_bytes = [0u8; 192];
unsafe {
blst_p2_affine_serialize(uncompressed_bytes.as_mut_ptr(), blst_p2_affine_generator());
}
// This should fail because from_byte_array only accepts compressed format.
assert!(G2Element::from_byte_array(&(uncompressed_bytes[0..96].try_into().unwrap())).is_err());

// But if we set the uncompressed bit, it should work because the compressed format is just the first coordinate.
uncompressed_bytes[0] |= 0x80;
assert_eq!(
G2Element::generator(),
G2Element::from_byte_array(&(uncompressed_bytes[0..96].try_into().unwrap())).unwrap()
);
}

#[test]
fn test_reduce_mod_uniform_buffer() {
// 9920230154395168010467440495232506909487652629574290093191912925556996116934135093887783048487593217824704573634359454220706793741831181736379748807477797
let bytes = <[u8; 64]>::try_from(hex::decode("bd69132eca59d8eb6b2aeaab1bb0f4128ea2554a2a5fd5ed90cfa341311d63d2bddef3cf93ebbd3781dc09921ca8611e0db756164b297a90cff258c8138a0a25").unwrap()).unwrap();
// This is the above bytes as a big-endian integer modulo the BLS scalar field size and then written as big-endian bytes.
let expected =
hex::decode("42326e5eb98173088355c38dace25686f73f8900c8af2da6480b34e2313c49c2").unwrap();
assert_eq!(expected, reduce_mod_uniform_buffer(&bytes).to_byte_array());

// 99202309022396765790443178473142775358161915835492099699231487822465101596204583014819121570129071631157073920534979728799457207703011355835025584728154395168010467440495232506909487652629574290093191912925556996116934135093887783048487593217824704573634359454220706793741831181736379748807477797
let bytes = <[u8; 59]>::try_from(hex::decode("bd69132eca59d8eb6b2aeaab1bb0f4128ea2554a2a5fd5ed90cfa341311d63d2bddef3cf93ebbd3781dc09921ca8611e0db756164b297a90cff258").unwrap()).unwrap();
let expected =
hex::decode("21015212b5c7a44c04c39447bf7d2addc5035a9b118f07a29956bf00fa65bd74").unwrap();
assert_eq!(expected, reduce_mod_uniform_buffer(&bytes).to_byte_array());
}

0 comments on commit 3c366db

Please sign in to comment.