diff --git a/fastcrypto/src/groups/bls12381.rs b/fastcrypto/src/groups/bls12381.rs index 13c140d365..26e579eb6e 100644 --- a/fastcrypto/src/groups/bls12381.rs +++ b/fastcrypto/src/groups/bls12381.rs @@ -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; @@ -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)) } @@ -229,7 +230,7 @@ impl ToFromByteArray 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); @@ -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)) } @@ -398,7 +400,7 @@ impl ToFromByteArray 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); @@ -651,16 +653,9 @@ impl Div for Scalar { impl ScalarType for Scalar { fn rand(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 { @@ -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) } } diff --git a/fastcrypto/src/tests/bls12381_group_tests.rs b/fastcrypto/src/tests/bls12381_group_tests.rs index 1b697fc05a..0967466149 100644 --- a/fastcrypto/src/tests/bls12381_group_tests.rs +++ b/fastcrypto/src/tests/bls12381_group_tests.rs @@ -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, }; @@ -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"; @@ -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()); +}