Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Crypto] KeyGen improvement #3788

Merged
merged 19 commits into from
Feb 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 59 additions & 23 deletions crypto/bls.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,12 @@ import "C"

import (
"bytes"
"crypto/sha256"
"errors"
"fmt"

"golang.org/x/crypto/hkdf"

"github.com/onflow/flow-go/crypto/hash"
)

Expand All @@ -60,14 +63,14 @@ const (
SignatureLenBLSBLS12381 = fieldSize * (2 - serializationG1) // the length is divided by 2 if compression is on
PrKeyLenBLSBLS12381 = 32
// PubKeyLenBLSBLS12381 is the size of G2 elements
PubKeyLenBLSBLS12381 = 2 * fieldSize * (2 - serializationG2) // the length is divided by 2 if compression is on
KeyGenSeedMinLenBLSBLS12381 = PrKeyLenBLSBLS12381 + (securityBits / 8)
KeyGenSeedMaxLenBLSBLS12381 = maxScalarSize
PubKeyLenBLSBLS12381 = 2 * fieldSize * (2 - serializationG2) // the length is divided by 2 if compression is on

// Hash to curve params
// expandMsgOutput is the output length of the expand_message step as required by the hash_to_curve algorithm
expandMsgOutput = 2 * (fieldSize + (securityBits / 8))
// hash to curve suite ID of the form : CurveID_ || HashID_ || MapID_ || encodingVariant_
h2cSuiteID = "BLS12381G1_XOF:KMAC128_SSWU_RO_"
// scheme implemented as a countermasure for Rogue attacks of the form : SchemeTag_
// scheme implemented as a countermasure for rogue attacks of the form : SchemeTag_
schemeTag = "POP_"
// Cipher suite used for BLS signatures of the form : BLS_SIG_ || h2cSuiteID || SchemeTag_
blsSigCipherSuite = "BLS_SIG_" + h2cSuiteID + schemeTag
Expand Down Expand Up @@ -116,10 +119,6 @@ func NewExpandMsgXOFKMAC128(domainTag string) hash.Hasher {
// the hash-to-curve function, hashing data to G1 on BLS12 381.
// The key is used as a customizer rather than a MAC key.
func internalExpandMsgXOFKMAC128(key string) hash.Hasher {
// UTF-8 is used by Go to convert strings into bytes.
// UTF-8 is a non-ambiguous encoding as required by draft-irtf-cfrg-hash-to-curve
// (similarly to the recommended ASCII).

// blsKMACFunction is the customizer used for KMAC in BLS
const blsKMACFunction = "H2C"
// the error is ignored as the parameter lengths are chosen to be in the correct range for kmac
Expand Down Expand Up @@ -250,30 +249,67 @@ func IsBLSSignatureIdentity(s Signature) bool {
return bytes.Equal(s, identityBLSSignature)
}

// generatePrivateKey generates a private key for BLS on BLS12-381 curve.
// The minimum size of the input seed is 48 bytes.
// generatePrivateKey deterministically generates a private key for BLS on BLS12-381 curve.
// The minimum size of the input seed is 32 bytes.
//
// It is recommended to use a secure crypto RNG to generate the seed.
// The seed must have enough entropy and should be sampled uniformly at random.
// Otherwise, the seed must have enough entropy.
//
// The generated private key (resp. its corresponding public key) are guaranteed
// not to be equal to the identity element of Z_r (resp. G2).
func (a *blsBLS12381Algo) generatePrivateKey(seed []byte) (PrivateKey, error) {
if len(seed) < KeyGenSeedMinLenBLSBLS12381 || len(seed) > KeyGenSeedMaxLenBLSBLS12381 {
// The generated private key (resp. its corresponding public key) is guaranteed
// to not be equal to the identity element of Z_r (resp. G2).
func (a *blsBLS12381Algo) generatePrivateKey(ikm []byte) (PrivateKey, error) {
if len(ikm) < KeyGenSeedMinLen || len(ikm) > KeyGenSeedMaxLen {
return nil, invalidInputsErrorf(
"seed length should be between %d and %d bytes",
KeyGenSeedMinLenBLSBLS12381,
KeyGenSeedMaxLenBLSBLS12381)
"seed length should be at least %d bytes and at most %d bytes",
KeyGenSeedMinLen, KeyGenSeedMaxLen)
}

// HKDF parameters

// use SHA2-256 as the building block H in HKDF
hashFunction := sha256.New
// salt = H(UTF-8("BLS-SIG-KEYGEN-SALT-")) as per draft-irtf-cfrg-bls-signature-05 section 2.3.
saltString := "BLS-SIG-KEYGEN-SALT-"
hasher := hashFunction()
hasher.Write([]byte(saltString))
salt := make([]byte, hasher.Size())
hasher.Sum(salt[:0])

// L is the OKM length
// L = ceil((3 * ceil(log2(r))) / 16) which makes L (security_bits/8)-larger than r size
okmLength := (3 * PrKeyLenBLSBLS12381) / 2
tarakby marked this conversation as resolved.
Show resolved Hide resolved

// HKDF secret = IKM || I2OSP(0, 1)
secret := make([]byte, len(ikm)+1)
copy(secret, ikm)
defer overwrite(secret) // overwrite secret
// HKDF info = key_info || I2OSP(L, 2)
keyInfo := "" // use empty key diversifier. TODO: update header to accept input identifier
info := append([]byte(keyInfo), byte(okmLength>>8), byte(okmLength))

sk := newPrKeyBLSBLS12381(nil)
for {
// instantiate HKDF and extract L bytes
reader := hkdf.New(hashFunction, secret, salt, info)
okm := make([]byte, okmLength)
n, err := reader.Read(okm)
if err != nil || n != okmLength {
return nil, fmt.Errorf("key generation failed because of the HKDF reader, %d bytes were read: %w",
n, err)
}
defer overwrite(okm) // overwrite okm

// maps the seed to a private key
err := mapToZrStar(&sk.scalar, seed)
if err != nil {
return nil, invalidInputsErrorf("private key generation failed %w", err)
// map the bytes to a private key : SK = OS2IP(OKM) mod r
isZero := mapToZr(&sk.scalar, okm)
if !isZero {
return sk, nil
}

// update salt = H(salt)
hasher.Reset()
hasher.Write(salt)
salt = hasher.Sum(salt[:0])
}
return sk, nil
}

const invalidBLSSignatureHeader = byte(0xE0)
Expand Down
43 changes: 29 additions & 14 deletions crypto/bls12381_utils.c
Original file line number Diff line number Diff line change
Expand Up @@ -189,36 +189,51 @@ void bn_randZr_star(bn_t x) {
byte seed[seed_len];
rand_bytes(seed, seed_len);
bn_map_to_Zr_star(x, seed, seed_len);
rand_bytes(seed, seed_len); // overwrite seed
}

// generates a random number less than the order r
void bn_randZr(bn_t x) {
bn_t r;
bn_new(r);
g2_get_ord(r);
// reduce the modular reduction bias
bn_new_size(x, BITS_TO_DIGITS(Fr_BITS + SEC_BITS));
bn_rand(x, RLC_POS, Fr_BITS + SEC_BITS);
bn_mod(x, x, r);
bn_free(r);
bn_mod(x, x, &core_get()->ep_r);
}

// Reads a scalar from an array and maps it to Zr.
// The resulting scalar `a` satisfies 0 <= a < r.
// `len` must be less than BITS_TO_BYTES(RLC_BN_BITS).
// It returns VALID if scalar is zero and INVALID otherwise
int bn_map_to_Zr(bn_t a, const uint8_t* bin, int len) {
bn_t tmp;
bn_new(tmp);
bn_new_size(tmp, BYTES_TO_DIGITS(len));
bn_read_bin(tmp, bin, len);
bn_mod(a, tmp, &core_get()->ep_r);
bn_rand(tmp, RLC_POS, len << 3); // overwrite tmp
bn_free(tmp);
if (bn_cmp_dig(a, 0) == RLC_EQ) {
return VALID;
}
return INVALID;
}

// reads a scalar from an array and maps it to Zr
// the resulting scalar is in the range 0 < a < r
// len must be less than BITS_TO_BYTES(RLC_BN_BITS)
// Reads a scalar from an array and maps it to Zr*.
// The resulting scalar `a` satisfies 0 < a < r.
// `len` must be less than BITS_TO_BYTES(RLC_BN_BITS)
void bn_map_to_Zr_star(bn_t a, const uint8_t* bin, int len) {
bn_t tmp;
bn_new(tmp);
bn_new_size(tmp, BYTES_TO_DIGITS(len));
bn_read_bin(tmp, bin, len);
bn_t r;
bn_new(r);
g2_get_ord(r);
bn_sub_dig(r,r,1);
bn_mod_basic(a,tmp,r);
bn_t r_1;
bn_new(r_1);
bn_sub_dig(r_1, &core_get()->ep_r, 1);
bn_mod_basic(a,tmp,r_1);
bn_add_dig(a,a,1);
bn_free(r);
bn_rand(tmp, RLC_POS, len << 3); // overwrite tmp
bn_free(tmp);
bn_free(r_1);
}

// returns the sign of y.
Expand Down
16 changes: 6 additions & 10 deletions crypto/bls12381_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,14 @@ func randZrStar(x *scalar) {
C.bn_randZr_star((*C.bn_st)(x))
}

// mapToZrStar reads a scalar from a slice of bytes and maps it to Zr
// the resulting scalar is in the range 0 < k < r
func mapToZrStar(x *scalar, src []byte) error {
if len(src) > maxScalarSize {
return invalidInputsErrorf(
"input slice length must be less than %d",
maxScalarSize)
}
C.bn_map_to_Zr_star((*C.bn_st)(x),
// mapToZr reads a scalar from a slice of bytes and maps it to Zr.
// The resulting scalar `k` satisfies 0 <= k < r.
// It returns true if scalar is zero and false otherwise.
func mapToZr(x *scalar, src []byte) bool {
isZero := C.bn_map_to_Zr((*C.bn_st)(x),
(*C.uchar)(&src[0]),
(C.int)(len(src)))
return nil
return isZero == valid
}

// writeScalar writes a G2 point in a slice of bytes
Expand Down
1 change: 1 addition & 0 deletions crypto/bls12381_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ void ep2_mult_gen(ep2_t, const bn_t);

void bn_randZr(bn_t);
void bn_randZr_star(bn_t);
int bn_map_to_Zr(bn_t, const uint8_t*, int);
void bn_map_to_Zr_star(bn_t, const uint8_t*, int);

void bn_sum_vector(bn_t, const bn_st*, const int);
Expand Down
8 changes: 4 additions & 4 deletions crypto/bls12381_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import (

func TestDeterministicKeyGen(t *testing.T) {
// 2 keys generated with the same seed should be equal
seed := make([]byte, KeyGenSeedMinLenBLSBLS12381)
seed := make([]byte, KeyGenSeedMinLen)
n, err := rand.Read(seed)
require.Equal(t, n, KeyGenSeedMinLenBLSBLS12381)
require.Equal(t, n, KeyGenSeedMinLen)
require.NoError(t, err)
sk1, err := GeneratePrivateKey(BLSBLS12381, seed)
require.Nil(t, err)
Expand All @@ -29,9 +29,9 @@ func TestDeterministicKeyGen(t *testing.T) {
func TestPRGseeding(t *testing.T) {
blsInstance.reInit()
// 2 scalars generated with the same seed should be equal
seed := make([]byte, KeyGenSeedMinLenBLSBLS12381)
seed := make([]byte, KeyGenSeedMinLen)
n, err := rand.Read(seed)
require.Equal(t, n, KeyGenSeedMinLenBLSBLS12381)
require.Equal(t, n, KeyGenSeedMinLen)
require.NoError(t, err)
// 1st scalar (wrapped in a private key)
err = seedRelic(seed)
Expand Down
24 changes: 18 additions & 6 deletions crypto/bls_crossBLST_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (

// validPrivateKeyBytesFlow generates bytes of a valid private key in Flow library
func validPrivateKeyBytesFlow(t *rapid.T) []byte {
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLenBLSBLS12381, KeyGenSeedMaxLenBLSBLS12381).Draw(t, "seed").([]byte)
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "seed").([]byte)
sk, err := GeneratePrivateKey(BLSBLS12381, seed)
// TODO: require.NoError(t, err) seems to mess with rapid
if err != nil {
Expand All @@ -38,15 +38,15 @@ func validPrivateKeyBytesFlow(t *rapid.T) []byte {

// validPublicKeyBytesFlow generates bytes of a valid public key in Flow library
func validPublicKeyBytesFlow(t *rapid.T) []byte {
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLenBLSBLS12381, KeyGenSeedMaxLenBLSBLS12381).Draw(t, "seed").([]byte)
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "seed").([]byte)
sk, err := GeneratePrivateKey(BLSBLS12381, seed)
require.NoError(t, err)
return sk.PublicKey().Encode()
}

// validSignatureBytesFlow generates bytes of a valid signature in Flow library
func validSignatureBytesFlow(t *rapid.T) []byte {
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLenBLSBLS12381, KeyGenSeedMaxLenBLSBLS12381).Draw(t, "seed").([]byte)
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "seed").([]byte)
sk, err := GeneratePrivateKey(BLSBLS12381, seed)
require.NoError(t, err)
hasher := NewExpandMsgXOFKMAC128("random_tag")
Expand All @@ -58,22 +58,22 @@ func validSignatureBytesFlow(t *rapid.T) []byte {

// validPrivateKeyBytesBLST generates bytes of a valid private key in BLST library
func validPrivateKeyBytesBLST(t *rapid.T) []byte {
randomSlice := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLenBLSBLS12381, KeyGenSeedMaxLenBLSBLS12381)
randomSlice := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen)
ikm := randomSlice.Draw(t, "ikm").([]byte)
return blst.KeyGen(ikm).Serialize()
}

// validPublicKeyBytesBLST generates bytes of a valid public key in BLST library
func validPublicKeyBytesBLST(t *rapid.T) []byte {
ikm := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLenBLSBLS12381, KeyGenSeedMaxLenBLSBLS12381).Draw(t, "ikm").([]byte)
ikm := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "ikm").([]byte)
blstS := blst.KeyGen(ikm)
blstG2 := new(blst.P2Affine).From(blstS)
return blstG2.Compress()
}

// validSignatureBytesBLST generates bytes of a valid signature in BLST library
func validSignatureBytesBLST(t *rapid.T) []byte {
ikm := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLenBLSBLS12381, KeyGenSeedMaxLenBLSBLS12381).Draw(t, "ikm").([]byte)
ikm := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "ikm").([]byte)
blstS := blst.KeyGen(ikm[:])
blstG1 := new(blst.P1Affine).From(blstS)
return blstG1.Compress()
Expand Down Expand Up @@ -203,7 +203,19 @@ func testSignHashCrossBLST(t *rapid.T) {
assert.Equal(t, sigBytesBLST, sigBytesFlow)
}

func testKeyGenCrossBLST(t *rapid.T) {
seed := rapid.SliceOfN(rapid.Byte(), KeyGenSeedMinLen, KeyGenSeedMaxLen).Draw(t, "seed").([]byte)

skFlow, err := GeneratePrivateKey(BLSBLS12381, seed)
if err != nil {
assert.FailNow(t, "failed key generation")
}
skBLST := blst.KeyGen(seed)
assert.Equal(t, skFlow.Encode(), skBLST.Serialize())
}

func TestAgainstBLST(t *testing.T) {
rapid.Check(t, testKeyGenCrossBLST)
rapid.Check(t, testEncodeDecodePrivateKeyCrossBLST)
rapid.Check(t, testEncodeDecodePublicKeyCrossBLST)
rapid.Check(t, testEncodeDecodeSignatureCrossBLST)
Expand Down
3 changes: 1 addition & 2 deletions crypto/bls_no_relic.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import (
const relic_panic = "function is not supported when building without \"relic\" Go build tag"

const (
SignatureLenBLSBLS12381 = 48
KeyGenSeedMinLenBLSBLS12381 = 48
SignatureLenBLSBLS12381 = 48
)

// bls.go functions
Expand Down
Loading