Skip to content

Commit

Permalink
[bootstrap] Key Generation for Unstaked Access Nodes
Browse files Browse the repository at this point in the history
This introduces a generator for the unstaked Access Nodes, which by convention only have positive secp256k1 keys.
  • Loading branch information
huitseeker committed Aug 21, 2021
1 parent 4bcdeb1 commit 44c7a67
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 0 deletions.
62 changes: 62 additions & 0 deletions 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"

Expand All @@ -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 {
Expand Down
16 changes: 16 additions & 0 deletions cmd/bootstrap/utils/key_generation_test.go
Expand Up @@ -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)")
Expand Down

0 comments on commit 44c7a67

Please sign in to comment.