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

[bootstrap] Key Generation for Unstaked Access Nodes #1180

Merged
merged 2 commits into from Aug 24, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find a definition that uses the terms "positive" and "negative" points. It would be good to add a link to definitions using these terms since they are not self explanatory.
I am more familiar with the "parity" term used in this context, or to not use any adjective and just give a definition (like 0x02 if y mod p is even, and 0x3 otherwise).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I agree that connecting this bit with a notion of sign requires the reader to be familiar with curves defined in Weierstrass form & with p>3 (and then the notion of sign is apparent from the definition of point inverse and the square in the equation). We could indeed reformulate that part.
  • I'm confused about where the notion of parity comes from here: it seems to me standards specify both little-endianness and the ỹ bit as a MSB ? (whereas a parity bit would be a LSB?) e.g.:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I think even considering prime fields with p>3, it's still not clear which point should be called positive between (x,y) and (x,p-y). In some other cases (like the Zcash definition for BLS12-381) the sign of a point is defined as the comparison of y with (p-1)/2. This is why I thought we had to specify what definition we are referring to in our case.

Regarding the second point:

  • IEEE 1363 defines ỹ as (y mod 2) with y seen as an integer less than p (because strictly speaking, y is a field element). It also uses the "rightmost" bit of the integer y.
  • SEC 1 uses the "rightmost" bit of y. Looking at the definition of octet strings, "rightmost" represents the LSB.
  • X9.62 uses (y mod 2)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think even considering prime fields with p>3, it's still not clear which point should be called positive between (x,y) and (x,p-y)

OK, I get your point better, now.

SEC 1 uses the "rightmost" bit of y. Looking at the definition of octet strings, "rightmost" represents the LSB.

Yep, there's one step between the bit string and the octet sting I missed.
And of course, looking at a mod 2 over a field element, rather than backtracking from bytes, the notion of parity makes sense.

I'll issue a PR to reformulate comments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no rush to create a specific PR for this. Including the X9.62 link as you did was good.
We can just update the references to positive and negative keys in a future libp2p related PR.

//
// 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"))
huitseeker marked this conversation as resolved.
Show resolved Hide resolved
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