From 44c7a67b21cb4a6896b14cf288e19d2faa9c9a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Fri, 20 Aug 2021 19:30:06 -0400 Subject: [PATCH 1/2] [bootstrap] Key Generation for Unstaked Access Nodes This introduces a generator for the unstaked Access Nodes, which by convention only have positive secp256k1 keys. --- cmd/bootstrap/utils/key_generation.go | 62 ++++++++++++++++++++++ cmd/bootstrap/utils/key_generation_test.go | 16 ++++++ 2 files changed, 78 insertions(+) diff --git a/cmd/bootstrap/utils/key_generation.go b/cmd/bootstrap/utils/key_generation.go index 73cc80ba2e3..be26856d29f 100644 --- a/cmd/bootstrap/utils/key_generation.go +++ b/cmd/bootstrap/utils/key_generation.go @@ -1,7 +1,12 @@ package utils import ( + "crypto/sha256" "fmt" + gohash "hash" + "io" + + "golang.org/x/crypto/hkdf" sdkcrypto "github.com/onflow/flow-go-sdk/crypto" @@ -18,6 +23,63 @@ func GenerateMachineAccountKey(seed []byte) (crypto.PrivateKey, error) { return keys[0], nil } +// The unstaked nodes have special networking keys, in two aspects: +// - they use crypto.ECDSASecp256k1 keys, not crypto.ECDSAP256 keys, +// - they use only positive keys (in the sense that the elliptic curve point of their public key is positive) +// +// Thanks to various properties of the cryptographic algorithm and libp2p, +// this affords us to not have to maintain a table of flow.NodeID -> NetworkPublicKey +// for those numerous and ephemeral nodes. +// It incurs a one-bit security reduction, which is deemed acceptable. + +// drawUnstakedKey draws a single positive ECDSASecp256k1 key, and returns an error otherwise. +func drawUnstakedKey(seed []byte) (crypto.PrivateKey, error) { + key, err := crypto.GeneratePrivateKey(crypto.ECDSASecp256k1, seed) + if err != nil { + // this should not happen + return nil, err + } else if key.PublicKey().EncodeCompressed()[0] == 0x03 { + // negative key -> unsuitable + return nil, fmt.Errorf("Unsuitable negative key") + } + return key, nil +} + +// GenerateUnstakedNetworkingKey draws ECDSASecp256k1 keys until finding a suitable one. +// though this will return fast, this is not constant-time and will leak ~1 bit of information through its runtime +func GenerateUnstakedNetworkingKey(seed []byte) (key crypto.PrivateKey, err error) { + hkdf := hkdf.New(func() gohash.Hash { return sha256.New() }, seed, nil, []byte("unstaked network")) + round_seed := make([]byte, len(seed)) + max_iterations := 20 // 1/(2^20) failure chance + for i := 0; i < max_iterations; i++ { + if _, err = io.ReadFull(hkdf, round_seed); err != nil { + // the hkdf Reader should not fail + panic(err) + } + if key, err = drawUnstakedKey(round_seed); err == nil { + return + } + } + return +} + +func GenerateUnstakedNetworkingKeys(n int, seeds [][]byte) ([]crypto.PrivateKey, error) { + if n != len(seeds) { + return nil, fmt.Errorf("n needs to match the number of seeds (%v != %v)", n, len(seeds)) + } + + keys := make([]crypto.PrivateKey, n) + + var err error + for i, seed := range seeds { + if keys[i], err = GenerateUnstakedNetworkingKey(seed); err != nil { + return nil, err + } + } + + return keys, nil +} + func GenerateNetworkingKey(seed []byte) (crypto.PrivateKey, error) { keys, err := GenerateKeys(crypto.ECDSAP256, 1, [][]byte{seed}) if err != nil { diff --git a/cmd/bootstrap/utils/key_generation_test.go b/cmd/bootstrap/utils/key_generation_test.go index b77b4da39e9..2c64a27cf02 100644 --- a/cmd/bootstrap/utils/key_generation_test.go +++ b/cmd/bootstrap/utils/key_generation_test.go @@ -14,6 +14,22 @@ import ( "github.com/onflow/flow-go/utils/unittest" ) +func TestGenerateUnstakedNetworkingKey(t *testing.T) { + + key, err := GenerateUnstakedNetworkingKey(unittest.SeedFixture(crypto.KeyGenSeedMinLenECDSASecp256k1)) + require.NoError(t, err) + assert.Equal(t, crypto.ECDSASecp256k1, key.Algorithm()) + assert.Equal(t, uint8(0x02), key.PublicKey().EncodeCompressed()[0]) + + keys, err := GenerateUnstakedNetworkingKeys(20, unittest.SeedFixtures(20, crypto.KeyGenSeedMinLenECDSASecp256k1)) + require.NoError(t, err) + for _, key := range keys { + assert.Equal(t, crypto.ECDSASecp256k1, key.Algorithm()) + assert.Equal(t, uint8(0x02), key.PublicKey().EncodeCompressed()[0]) + } + +} + func TestGenerateKeys(t *testing.T) { _, err := GenerateKeys(crypto.BLSBLS12381, 0, unittest.SeedFixtures(2, crypto.KeyGenSeedMinLenBLSBLS12381)) require.EqualError(t, err, "n needs to match the number of seeds (0 != 2)") From 596f946b0a6126b75632d0e0f14c48df3ebdf6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Tue, 24 Aug 2021 11:29:36 -0400 Subject: [PATCH 2/2] [bootstrap] define & doc consts for magic numbers in decompression --- cmd/bootstrap/utils/key_generation.go | 8 +++++++- cmd/bootstrap/utils/key_generation_test.go | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/bootstrap/utils/key_generation.go b/cmd/bootstrap/utils/key_generation.go index be26856d29f..78e2a3df008 100644 --- a/cmd/bootstrap/utils/key_generation.go +++ b/cmd/bootstrap/utils/key_generation.go @@ -15,6 +15,12 @@ import ( "github.com/onflow/flow-go/model/flow" ) +// these constants are defined in X9.62 section 4.2 and 4.3 +// see https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf +// they indicate if the conversion to/from a public key (point) in compressed form must involve an inversion of the ordinate coordinate +const X962_NO_INVERSION = uint8(0x02) +const X962_INVERSION = uint8(0x03) + func GenerateMachineAccountKey(seed []byte) (crypto.PrivateKey, error) { keys, err := GenerateKeys(crypto.ECDSAP256, 1, [][]byte{seed}) if err != nil { @@ -38,7 +44,7 @@ func drawUnstakedKey(seed []byte) (crypto.PrivateKey, error) { if err != nil { // this should not happen return nil, err - } else if key.PublicKey().EncodeCompressed()[0] == 0x03 { + } else if key.PublicKey().EncodeCompressed()[0] == X962_INVERSION { // negative key -> unsuitable return nil, fmt.Errorf("Unsuitable negative key") } diff --git a/cmd/bootstrap/utils/key_generation_test.go b/cmd/bootstrap/utils/key_generation_test.go index 2c64a27cf02..f6f8fb2a937 100644 --- a/cmd/bootstrap/utils/key_generation_test.go +++ b/cmd/bootstrap/utils/key_generation_test.go @@ -19,13 +19,13 @@ func TestGenerateUnstakedNetworkingKey(t *testing.T) { key, err := GenerateUnstakedNetworkingKey(unittest.SeedFixture(crypto.KeyGenSeedMinLenECDSASecp256k1)) require.NoError(t, err) assert.Equal(t, crypto.ECDSASecp256k1, key.Algorithm()) - assert.Equal(t, uint8(0x02), key.PublicKey().EncodeCompressed()[0]) + assert.Equal(t, X962_NO_INVERSION, key.PublicKey().EncodeCompressed()[0]) keys, err := GenerateUnstakedNetworkingKeys(20, unittest.SeedFixtures(20, crypto.KeyGenSeedMinLenECDSASecp256k1)) require.NoError(t, err) for _, key := range keys { assert.Equal(t, crypto.ECDSASecp256k1, key.Algorithm()) - assert.Equal(t, uint8(0x02), key.PublicKey().EncodeCompressed()[0]) + assert.Equal(t, X962_NO_INVERSION, key.PublicKey().EncodeCompressed()[0]) } }