Skip to content

Commit

Permalink
Merge #1180
Browse files Browse the repository at this point in the history
1180: [bootstrap] Key Generation for Unstaked Access Nodes r=huitseeker a=huitseeker

This introduces a generator for the unstaked Access Nodes, which by convention only have positive secp256k1 keys.

The keys in question have a specific format (positive ECDSA Secp256k1 keys) because:
- they are meant to serve ephemeral nodes,
- allow a bijection between flow.NodeID, flow.NetworkPublicKey, libp2p.PeerID, libp2p.PublicKey
- hence allowing us to only retain the minimum amount of information for these nodes.
For this bijection, see #1165 and #1129.

This is to be used in a bootstrap of the unstaked Access Node post #1133. 

For now, having this in master allows the direct bootstrapping of network in tests by creating suitable libp2p peers.

Co-authored-by: François Garillot <francois.garillot@dapperlabs.com>
  • Loading branch information
bors[bot] and huitseeker committed Aug 24, 2021
2 parents cb68936 + 596f946 commit 4d60f3a
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
68 changes: 68 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 @@ -10,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 {
Expand All @@ -18,6 +29,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] == X962_INVERSION {
// 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, 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, X962_NO_INVERSION, 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 4d60f3a

Please sign in to comment.