diff --git a/cmd/bootstrap/README.md b/cmd/bootstrap/README.md index 1d4eef5f580..28caa0d34ca 100644 --- a/cmd/bootstrap/README.md +++ b/cmd/bootstrap/README.md @@ -3,9 +3,9 @@ This package contains script for generating the bootstrap files needed to initialize the Flow network. The high-level bootstrapping process is described in [Notion](https://www.notion.so/dapperlabs/Flow-Bootstrapping-ce9d227f18a8410dbce74ed7d4ddee27). -WARNING: These scripts use Go's crypto/rand package to generate seeds for private keys. Make sure you are running the bootstrap scripts on a machine that does provide proper a low-level implementation. See https://golang.org/pkg/crypto/rand/ for details. +WARNING: These scripts use Go's crypto/rand package to generate seeds for private keys, whenever seeds are not provided to the commands. Make sure you are running the bootstrap scripts on a machine that does provide a low-level cryptographically secure RNG. See https://golang.org/pkg/crypto/rand/ for details. -NOTE: Public and private keys are encoded in JSON files as base64 strings, not as hex, contrary to what might be expected. +NOTE: Public and private keys are encoded in JSON files as hex strings. Code structure: * `cmd/bootstrap/cmd` contains CLI logic that can exit the program and read/write files. It also uses structures and data types that are purely relevant for CLI purposes, such as encoding, decoding, etc. @@ -18,9 +18,9 @@ Code structure: The bootstrapping will generate the following information: #### Per node -* Staking key (BLS key with curve BLS12-381) -* Networking key (ECDSA key) -* Random beacon key; _only_ for consensus nodes (BLS based on Joint-Feldman DKG for threshold signatures) +* Staking private key (BLS key on curve BLS12-381) +* Networking private key (ECDSA key on curve P-256) +* Random beacon private key; _only_ for consensus nodes (BLS key on curve BLS12-381, used for a BLS-based threshold signatures) #### Node Identities * List of all authorized Flow nodes @@ -28,6 +28,7 @@ The bootstrapping will generate the following information: - node ID - node role - public staking key + - proof of possession of the staking private key - public networking key - weight @@ -61,7 +62,8 @@ Values directly specified as command line parameters: Values can be specified as command line parameters: - seed for generating staking key (min 48 bytes in hex encoding) - seed for generating networking key (min 48 bytes in hex encoding) -If seeds are not provided, the CLI will try to use the system's pseudo-random number generator (PRNG), e. g. `dev/urandom`. Make sure you are running the CLI on a hardware that has a cryptographically secure PRNG, or provide seeds generated on such a system. +Provided seeds must be of high entropy, ideally generated by a crypto secure RNG. +If seeds are not provided, the CLI will try to use the system's random number generator (RNG), e. g. `dev/urandom`. Make sure you are running the CLI on a hardware that has a cryptographically secure RNG. #### Example ```bash @@ -76,7 +78,7 @@ go run ./cmd/bootstrap key --address "example.com:1234" --role "consensus" -o ./ file needs to be available to respective partner node at boot up (or recovery after crash) * file `.node-info.pub.json` - public information - - file needs to be delivered to Dapper Labs for Phase 2 of generating root information, + - file needs to be delivered to the Flow Foundation team for Phase 2 of generating root information, but is not required at node start diff --git a/cmd/bootstrap/cmd/final_list.go b/cmd/bootstrap/cmd/final_list.go index ca34739de2a..daa4c8f872f 100644 --- a/cmd/bootstrap/cmd/final_list.go +++ b/cmd/bootstrap/cmd/final_list.go @@ -1,6 +1,7 @@ package cmd import ( + "bytes" "fmt" "github.com/spf13/cobra" @@ -68,7 +69,11 @@ func finalList(cmd *cobra.Command, args []string) { validateNodes(localNodes, registeredNodes) // write node-config.json with the new list of nodes to be used for the `finalize` command - err := common.WriteJSON(model.PathFinallist, flagOutdir, model.ToPublicNodeInfoList(localNodes)) + pubInfo, err := model.ToPublicNodeInfoList(localNodes) + if err != nil { + log.Fatal().Err(err).Msg("failed to read public info") + } + err = common.WriteJSON(model.PathFinallist, flagOutdir, pubInfo) if err != nil { log.Fatal().Err(err).Msg("failed to write json") } @@ -132,28 +137,44 @@ func validateNodes(localNodes []model.NodeInfo, registeredNodes []model.NodeInfo // flow localNodes contain private key info if matchingNode.NetworkPubKey().String() != "" { // check networking pubkey match - matchNodeKey := matchingNode.NetworkPubKey().String() - registeredNodeKey := registeredNode.NetworkPubKey().String() + matchNodeKey := matchingNode.NetworkPubKey() + registeredNodeKey := registeredNode.NetworkPubKey() - if matchNodeKey != registeredNodeKey { + if !matchNodeKey.Equals(registeredNodeKey) { log.Error(). - Str("registered network key", registeredNodeKey). - Str("network key", matchNodeKey). + Str("registered network key", registeredNodeKey.String()). + Str("network key", matchNodeKey.String()). Msg("networking keys do not match") } } // flow localNodes contain privatekey info if matchingNode.StakingPubKey().String() != "" { - matchNodeKey := matchingNode.StakingPubKey().String() - registeredNodeKey := registeredNode.StakingPubKey().String() + matchNodeKey := matchingNode.StakingPubKey() + registeredNodeKey := registeredNode.StakingPubKey() - if matchNodeKey != registeredNodeKey { + if !matchNodeKey.Equals(registeredNodeKey) { log.Error(). - Str("registered staking key", registeredNodeKey). - Str("staking key", matchNodeKey). + Str("registered staking key", registeredNodeKey.String()). + Str("staking key", matchNodeKey.String()). Msg("staking keys do not match") } + + matchingPoP, err := matchingNode.StakingPoP() + if err != nil { + log.Error().Msgf("error reading matching PoP: %s", err.Error()) + } + registeredPoP, err := registeredNode.StakingPoP() + if err != nil { + log.Error().Msgf("error reading registered PoP: %s", err.Error()) + } + + if !bytes.Equal(matchingPoP, registeredPoP) { + log.Error(). + Str("registered staking PoP", fmt.Sprintf("%x", registeredPoP)). + Str("staking PoP", fmt.Sprintf("%x", matchingPoP)). + Msg("staking PoP do not match") + } } } } @@ -253,14 +274,17 @@ func assembleInternalNodesWithoutWeight() []model.NodeInfo { log.Fatal().Err(err).Msg(fmt.Sprintf("invalid node ID: %s", internal.NodeID)) } - node := model.NewPrivateNodeInfo( + node, err := model.NewPrivateNodeInfo( internal.NodeID, internal.Role, internal.Address, flow.DefaultInitialWeight, internal.NetworkPrivKey, - internal.StakingPrivKey, + internal.StakingPrivKey.PrivateKey, ) + if err != nil { + panic(err) + } nodes = append(nodes, node) } @@ -300,9 +324,9 @@ func createPublicNodeInfo(nodes []model.NodeInfoPub) []model.NodeInfo { if err != nil { log.Fatal().Err(err).Msg(fmt.Sprintf("invalid network public key: %s", n.NetworkPubKey)) } - err = common.ValidateStakingPubKey(n.StakingPubKey) + err = common.ValidateStakingPubKey(n.StakingPubKey, n.StakingPoP) if err != nil { - log.Fatal().Err(err).Msg(fmt.Sprintf("invalid staking public key: %s", n.StakingPubKey)) + log.Fatal().Err(err).Msg(fmt.Sprintf("invalid staking public key : %s, or staking PoP: %s", n.StakingPubKey, n.StakingPoP)) } // all nodes should have equal weight (this might change in the future) @@ -313,6 +337,7 @@ func createPublicNodeInfo(nodes []model.NodeInfoPub) []model.NodeInfo { flow.DefaultInitialWeight, n.NetworkPubKey, n.StakingPubKey, + n.StakingPoP, ) publicInfoNodes = append(publicInfoNodes, node) diff --git a/cmd/bootstrap/cmd/finalize.go b/cmd/bootstrap/cmd/finalize.go index e730df90360..74333af8de7 100644 --- a/cmd/bootstrap/cmd/finalize.go +++ b/cmd/bootstrap/cmd/finalize.go @@ -67,7 +67,7 @@ func addFinalizeCmdFlags() { "containing the output from the `keygen` command for internal nodes") finalizeCmd.Flags().StringVar(&flagPartnerNodeInfoDir, "partner-dir", "", "path to directory "+ "containing one JSON file starting with node-info.pub..json for every partner node (fields "+ - " in the JSON file: Role, Address, NodeID, NetworkPubKey, StakingPubKey)") + " in the JSON file: Role, Address, NodeID, NetworkPubKey, StakingPubKey, StakingKeyPoP)") // Deprecated: remove this flag finalizeCmd.Flags().StringVar(&deprecatedFlagPartnerStakes, "partner-stakes", "", "deprecated: use partner-weights instead") finalizeCmd.Flags().StringVar(&flagPartnerWeights, "partner-weights", "", "path to a JSON file containing "+ diff --git a/cmd/bootstrap/cmd/key.go b/cmd/bootstrap/cmd/key.go index 7ef97a19a8e..689d7d280f0 100644 --- a/cmd/bootstrap/cmd/key.go +++ b/cmd/bootstrap/cmd/key.go @@ -112,7 +112,11 @@ func keyCmdRun(_ *cobra.Command, _ []string) { } log.Info().Msgf("wrote file %s/%s", flagOutdir, model.PathSecretsEncryptionKey) - err = common.WriteJSON(fmt.Sprintf(model.PathNodeInfoPub, nodeInfo.NodeID), flagOutdir, nodeInfo.Public()) + public, err := nodeInfo.Public() + if err != nil { + log.Fatal().Err(err).Msg("could not access public keys") + } + err = common.WriteJSON(fmt.Sprintf(model.PathNodeInfoPub, nodeInfo.NodeID), flagOutdir, public) if err != nil { log.Fatal().Err(err).Msg("failed to write json") } diff --git a/cmd/bootstrap/cmd/keygen.go b/cmd/bootstrap/cmd/keygen.go index c76a4ca4e9a..c665247aed8 100644 --- a/cmd/bootstrap/cmd/keygen.go +++ b/cmd/bootstrap/cmd/keygen.go @@ -89,7 +89,10 @@ var keygenCmd = &cobra.Command{ } log.Info().Msg("generating node public information") - genNodePubInfo(nodes) + err = genNodePubInfo(nodes) + if err != nil { + log.Fatal().Err(err).Msg("failed to generate nodes public info") + } }, } @@ -120,14 +123,19 @@ func isEmptyDir(path string) (bool, error) { return false, err // Either not empty or error, suits both cases } -func genNodePubInfo(nodes []model.NodeInfo) { +func genNodePubInfo(nodes []model.NodeInfo) error { pubNodes := make([]model.NodeInfoPub, 0, len(nodes)) for _, node := range nodes { - pubNodes = append(pubNodes, node.Public()) + pub, err := node.Public() + if err != nil { + return fmt.Errorf("failed to read public info: %w", err) + } + pubNodes = append(pubNodes, pub) } err := common.WriteJSON(model.PathInternalNodeInfosPub, flagOutdir, pubNodes) if err != nil { - log.Fatal().Err(err).Msg("failed to write json") + return err } log.Info().Msgf("wrote file %s/%s", flagOutdir, model.PathInternalNodeInfosPub) + return nil } diff --git a/cmd/bootstrap/cmd/keys.go b/cmd/bootstrap/cmd/keys.go index f33b5f28241..e6e75391567 100644 --- a/cmd/bootstrap/cmd/keys.go +++ b/cmd/bootstrap/cmd/keys.go @@ -56,7 +56,8 @@ func genNetworkAndStakingKeys() []model.NodeInfo { return model.Sort(internalNodes, flow.Canonical[flow.Identity]) } -func assembleNodeInfo(nodeConfig model.NodeConfig, networkKey, stakingKey crypto.PrivateKey) model.NodeInfo { +func assembleNodeInfo(nodeConfig model.NodeConfig, networkKey, stakingKey crypto.PrivateKey, +) model.NodeInfo { var err error nodeID, found := getNameID() if !found { @@ -71,7 +72,7 @@ func assembleNodeInfo(nodeConfig model.NodeConfig, networkKey, stakingKey crypto Str("stakingPubKey", stakingKey.PublicKey().String()). Msg("encoded public staking and network keys") - nodeInfo := model.NewPrivateNodeInfo( + nodeInfo, err := model.NewPrivateNodeInfo( nodeID, nodeConfig.Role, nodeConfig.Address, @@ -79,6 +80,9 @@ func assembleNodeInfo(nodeConfig model.NodeConfig, networkKey, stakingKey crypto networkKey, stakingKey, ) + if err != nil { + log.Fatal().Err(err).Msg("creating node info failed") + } return nodeInfo } diff --git a/cmd/bootstrap/cmd/partner_infos.go b/cmd/bootstrap/cmd/partner_infos.go index aac230ce19e..f8d9fc29882 100644 --- a/cmd/bootstrap/cmd/partner_infos.go +++ b/cmd/bootstrap/cmd/partner_infos.go @@ -27,6 +27,7 @@ const ( networkingAddressField = "networkingAddress" networkingKeyField = "networkingKey" stakingKeyField = "stakingKey" + stakingKePOPyField = "stakingKeyPoP" ) const ( @@ -172,6 +173,11 @@ func parseNodeInfo(info cadence.Value) (*bootstrap.NodeInfoPub, error) { return nil, fmt.Errorf("failed to decode staking public key: %w", err) } + stakingPOP, err := hex.DecodeString(string(fields[stakingKePOPyField].(cadence.String))) + if err != nil { + return nil, fmt.Errorf("failed to decode staking private key PoP hex (%s): %w", string(fields[stakingKePOPyField].(cadence.String)), err) + } + return &bootstrap.NodeInfoPub{ Role: flow.Role(fields[roleField].(cadence.UInt8)), Address: string(fields[networkingAddressField].(cadence.String)), @@ -179,6 +185,7 @@ func parseNodeInfo(info cadence.Value) (*bootstrap.NodeInfoPub, error) { Weight: flow.DefaultInitialWeight, NetworkPubKey: encodable.NetworkPubKey{PublicKey: networkPubKey}, StakingPubKey: encodable.StakingPubKey{PublicKey: stakingPubKey}, + StakingPoP: stakingPOP, }, nil } diff --git a/cmd/bootstrap/cmd/rootblock.go b/cmd/bootstrap/cmd/rootblock.go index 194e625000c..ed14a026ec5 100644 --- a/cmd/bootstrap/cmd/rootblock.go +++ b/cmd/bootstrap/cmd/rootblock.go @@ -161,7 +161,11 @@ func rootBlock(cmd *cobra.Command, args []string) { log.Info().Msg("assembling network and staking keys") stakingNodes := mergeNodeInfos(internalNodes, partnerNodes) - err = common.WriteJSON(model.PathNodeInfosPub, flagOutdir, model.ToPublicNodeInfoList(stakingNodes)) + publicInfo, err := model.ToPublicNodeInfoList(stakingNodes) + if err != nil { + log.Fatal().Msg("failed to read public node info") + } + err = common.WriteJSON(model.PathNodeInfosPub, flagOutdir, publicInfo) if err != nil { log.Fatal().Err(err).Msg("failed to write json") } diff --git a/cmd/bootstrap/example_files/partner-node-infos/public-genesis-information/node-info.pub.047e39d906e9ff961fa76cb5f479942d862c6cb1e768eb4525d6066cd707d595.json b/cmd/bootstrap/example_files/partner-node-infos/public-genesis-information/node-info.pub.047e39d906e9ff961fa76cb5f479942d862c6cb1e768eb4525d6066cd707d595.json deleted file mode 100644 index 1c54ec0d6aa..00000000000 --- a/cmd/bootstrap/example_files/partner-node-infos/public-genesis-information/node-info.pub.047e39d906e9ff961fa76cb5f479942d862c6cb1e768eb4525d6066cd707d595.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Role": "consensus", - "Address": "example.com", - "NodeID": "047e39d906e9ff961fa76cb5f479942d862c6cb1e768eb4525d6066cd707d595", - "Weight": 0, - "NetworkPubKey": "6fSXKlHhPLMqWo3nhBsyjTZlxGBl5HC4ZqC7p1z4GEHx48UKnDaKdz0QdOxzSJP2G+P3bzDiJdLbvEd1CSvVgA==", - "StakingPubKey": "gdQQp6cbOzc/pnhOMl8mNQTAsbkuGs78Q72/zmhrAK+Ii2c/v04F9CDEo+FuVc0eALL/T0ioZwaTFCBO9+JRjfakqOiBCI9b7Xj4E8Dv4vBHDQyLOBBqXeA2VLAJYgFL" -} diff --git a/cmd/bootstrap/example_files/partner-node-infos/public-genesis-information/node-info.pub.79a7f711ba44dfdcfe774769eb97af6af732337fbcf96b788dbe6c51d29c5ec6.json b/cmd/bootstrap/example_files/partner-node-infos/public-genesis-information/node-info.pub.79a7f711ba44dfdcfe774769eb97af6af732337fbcf96b788dbe6c51d29c5ec6.json new file mode 100644 index 00000000000..60f83924e4d --- /dev/null +++ b/cmd/bootstrap/example_files/partner-node-infos/public-genesis-information/node-info.pub.79a7f711ba44dfdcfe774769eb97af6af732337fbcf96b788dbe6c51d29c5ec6.json @@ -0,0 +1,9 @@ +{ + "Role": "consensus", + "Address": "example.com:1234", + "NodeID": "79a7f711ba44dfdcfe774769eb97af6af732337fbcf96b788dbe6c51d29c5ec6", + "Weight": 0, + "NetworkPubKey": "0162f95166d39db8a6486625813019fcc8bb3d9439ad1e57b44f7bf01235cbcabd66411c3faa98de806e439cb4372275b76dcd3af7d384d24851cbae89f92cda", + "StakingPubKey": "84f806be7e4db914358e5b66a405244161ad5bfd87939b3a9b428a941baa6ae245d0d7a6cef684bd7168815fda5e9b6506b2cc87ec9c52576913d1990fd7c376fc2c6884247ff6a7c0c46ca143e3697422913d53c134b9534a199b7fc8f57d50", + "StakingPoP": "oEz2R3qe86/ZaRAemZfpdjcBZcOt7RHLjMhqjf7gg99XMsaLjmDma94Rr9ylciti" +} \ No newline at end of file diff --git a/cmd/bootstrap/run/cluster_qc_test.go b/cmd/bootstrap/run/cluster_qc_test.go index 69b181d6bbe..ee22b45f5b9 100644 --- a/cmd/bootstrap/run/cluster_qc_test.go +++ b/cmd/bootstrap/run/cluster_qc_test.go @@ -45,7 +45,8 @@ func createClusterParticipants(t *testing.T, n int) []model.NodeInfo { participants := make([]model.NodeInfo, n) for i, id := range ids { - participants[i] = model.NewPrivateNodeInfo( + var err error + participants[i], err = model.NewPrivateNodeInfo( id.NodeID, id.Role, id.Address, @@ -53,6 +54,7 @@ func createClusterParticipants(t *testing.T, n int) []model.NodeInfo { networkKeys[i], stakingKeys[i], ) + require.NoError(t, err) } return participants diff --git a/cmd/bootstrap/run/qc_test.go b/cmd/bootstrap/run/qc_test.go index 701bc17e836..26d99ca5834 100644 --- a/cmd/bootstrap/run/qc_test.go +++ b/cmd/bootstrap/run/qc_test.go @@ -69,7 +69,7 @@ func createSignerData(t *testing.T, n int) *ParticipantData { participantLookup[identity.NodeID] = lookupParticipant // add to participant list - nodeInfo := bootstrap.NewPrivateNodeInfo( + nodeInfo, err := bootstrap.NewPrivateNodeInfo( identity.NodeID, identity.Role, identity.Address, @@ -77,6 +77,7 @@ func createSignerData(t *testing.T, n int) *ParticipantData { networkingKeys[i], stakingKeys[i], ) + require.NoError(t, err) participants[i] = Participant{ NodeInfo: nodeInfo, RandomBeaconPrivKey: randomBSKs[i], diff --git a/cmd/bootstrap/utils/key_generation.go b/cmd/bootstrap/utils/key_generation.go index 627030a789f..975e02741ee 100644 --- a/cmd/bootstrap/utils/key_generation.go +++ b/cmd/bootstrap/utils/key_generation.go @@ -126,7 +126,15 @@ func GenerateStakingKey(seed []byte) (crypto.PrivateKey, error) { } func GenerateStakingKeys(n int, seeds [][]byte) ([]crypto.PrivateKey, error) { - return GenerateKeys(crypto.BLSBLS12381, n, seeds) + keys := make([]crypto.PrivateKey, 0, n) + for i := 0; i < n; i++ { + key, err := GenerateStakingKey(seeds[i]) + if err != nil { + return nil, err + } + keys = append(keys, key) + } + return keys, nil } func GenerateKeys(algo crypto.SigningAlgorithm, n int, seeds [][]byte) ([]crypto.PrivateKey, error) { diff --git a/cmd/bootstrap/utils/node_info.go b/cmd/bootstrap/utils/node_info.go index 2dbafa7d1fa..88172fcccc8 100644 --- a/cmd/bootstrap/utils/node_info.go +++ b/cmd/bootstrap/utils/node_info.go @@ -20,7 +20,11 @@ func WritePartnerFiles(nodeInfos []model.NodeInfo, bootDir string) (string, stri nodePubInfos := make([]model.NodeInfoPub, len(nodeInfos)) weights := make(map[flow.Identifier]uint64) for i, node := range nodeInfos { - nodePubInfos[i] = node.Public() + var err error + nodePubInfos[i], err = node.Public() + if err != nil { + return "", "", fmt.Errorf("could not read public info: %w", err) + } weights[node.NodeID] = node.Weight } @@ -105,35 +109,35 @@ func GenerateNodeInfos(consensus, collection, execution, verification, access in nodes := make([]model.NodeInfo, 0) - // CONSENSUS = 1 + // CONSENSUS consensusNodes := unittest.NodeInfosFixture(consensus, unittest.WithRole(flow.RoleConsensus), unittest.WithInitialWeight(flow.DefaultInitialWeight), ) nodes = append(nodes, consensusNodes...) - // COLLECTION = 1 + // COLLECTION collectionNodes := unittest.NodeInfosFixture(collection, unittest.WithRole(flow.RoleCollection), unittest.WithInitialWeight(flow.DefaultInitialWeight), ) nodes = append(nodes, collectionNodes...) - // EXECUTION = 1 + // EXECUTION executionNodes := unittest.NodeInfosFixture(execution, unittest.WithRole(flow.RoleExecution), unittest.WithInitialWeight(flow.DefaultInitialWeight), ) nodes = append(nodes, executionNodes...) - // VERIFICATION = 1 + // VERIFICATION verificationNodes := unittest.NodeInfosFixture(verification, unittest.WithRole(flow.RoleVerification), unittest.WithInitialWeight(flow.DefaultInitialWeight), ) nodes = append(nodes, verificationNodes...) - // ACCESS = 1 + // ACCESS accessNodes := unittest.NodeInfosFixture(access, unittest.WithRole(flow.RoleAccess), unittest.WithInitialWeight(flow.DefaultInitialWeight), diff --git a/cmd/util/cmd/common/node_info.go b/cmd/util/cmd/common/node_info.go index 061741d0955..b30379eff0e 100644 --- a/cmd/util/cmd/common/node_info.go +++ b/cmd/util/cmd/common/node_info.go @@ -41,16 +41,16 @@ func ReadFullPartnerNodeInfos(log zerolog.Logger, partnerWeightsPath, partnerNod } err = ValidateNetworkPubKey(partner.NetworkPubKey) if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("invalid network public key: %s", partner.NetworkPubKey)) + return nil, fmt.Errorf("invalid network public key: %s", partner.NetworkPubKey) } - err = ValidateStakingPubKey(partner.StakingPubKey) + err = ValidateStakingPubKey(partner.StakingPubKey, partner.StakingPoP) if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("invalid staking public key: %s", partner.StakingPubKey)) + return nil, fmt.Errorf("invalid staking public key: %s", partner.StakingPubKey) } weight := weights[partner.NodeID] if valid := ValidateWeight(weight); !valid { - return nil, fmt.Errorf(fmt.Sprintf("invalid partner weight: %d", weight)) + return nil, fmt.Errorf("invalid partner weight: %d", weight) } if weight != flow.DefaultInitialWeight { @@ -64,6 +64,7 @@ func ReadFullPartnerNodeInfos(log zerolog.Logger, partnerWeightsPath, partnerNod weight, partner.NetworkPubKey.PublicKey, partner.StakingPubKey.PublicKey, + partner.StakingPoP, ) nodes = append(nodes, node) } @@ -136,33 +137,37 @@ func ReadFullInternalNodeInfos(log zerolog.Logger, internalNodePrivInfoDir, inte log.Info().Msgf("read %d weights for internal nodes", len(weights)) var nodes []bootstrap.NodeInfo - for _, internal := range privInternals { + for i, internal := range privInternals { // check if address is valid format ValidateAddressFormat(log, internal.Address) // validate every single internal node err := ValidateNodeID(internal.NodeID) if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("invalid internal node ID: %s", internal.NodeID)) + return nil, fmt.Errorf("invalid internal node ID: %s", internal.NodeID) } weight := weights[internal.Address] if valid := ValidateWeight(weight); !valid { - return nil, fmt.Errorf(fmt.Sprintf("invalid partner weight: %d", weight)) + return nil, fmt.Errorf("invalid partner weight: %d", weight) } if weight != flow.DefaultInitialWeight { log.Warn().Msgf("internal node (id=%x) has non-default weight (%d != %d)", internal.NodeID, weight, flow.DefaultInitialWeight) } - node := bootstrap.NewPrivateNodeInfo( + node, err := bootstrap.NewPrivateNodeInfo( internal.NodeID, internal.Role, internal.Address, weight, internal.NetworkPrivKey, - internal.StakingPrivKey, + internal.StakingPrivKey.PrivateKey, ) + if err != nil { + return nil, fmt.Errorf("failed to build private node info at index %d: %w", i, err) + } + nodes = append(nodes, node) } diff --git a/cmd/util/cmd/common/utils.go b/cmd/util/cmd/common/utils.go index f5b9570071e..a14c31ba59c 100644 --- a/cmd/util/cmd/common/utils.go +++ b/cmd/util/cmd/common/utils.go @@ -160,10 +160,20 @@ func ValidateNetworkPubKey(key encodable.NetworkPubKey) error { // - key: the public key. // Returns: // - error: if the staking key is nil. -func ValidateStakingPubKey(key encodable.StakingPubKey) error { +func ValidateStakingPubKey(key encodable.StakingPubKey, pop []byte) error { if key.PublicKey == nil { return fmt.Errorf("staking public key must not be nil") } + if pop == nil { + return fmt.Errorf("staking key proof of possession must not be nil") + } + valid, err := crypto.BLSVerifyPOP(key.PublicKey, pop) + if err != nil { + return fmt.Errorf("verifying staking key PoP failed") + } + if !valid { + return fmt.Errorf("staking key PoP is invalid") + } return nil } diff --git a/consensus/hotstuff/timeoutcollector/aggregation_test.go b/consensus/hotstuff/timeoutcollector/aggregation_test.go index c0beaf473fa..93eb0774d0a 100644 --- a/consensus/hotstuff/timeoutcollector/aggregation_test.go +++ b/consensus/hotstuff/timeoutcollector/aggregation_test.go @@ -40,7 +40,7 @@ func createAggregationData(t *testing.T, signersNumber int) ( pks := make([]crypto.PublicKey, 0, signersNumber) view := 10 + uint64(rand.Uint32()) for i := 0; i < signersNumber; i++ { - sk := unittest.PrivateKeyFixture(crypto.BLSBLS12381, crypto.KeyGenSeedMinLen) + sk := unittest.PrivateKeyFixture(crypto.BLSBLS12381) identity := unittest.IdentityFixture(unittest.WithStakingPubKey(sk.PublicKey())) // id ids = append(ids, &identity.IdentitySkeleton) @@ -70,7 +70,7 @@ func createAggregationData(t *testing.T, signersNumber int) ( func TestNewTimeoutSignatureAggregator(t *testing.T) { tag := "random_tag" - sk := unittest.PrivateKeyFixture(crypto.ECDSAP256, crypto.KeyGenSeedMinLen) + sk := unittest.PrivateKeyFixture(crypto.ECDSAP256) signer := unittest.IdentityFixture(unittest.WithStakingPubKey(sk.PublicKey())) // wrong key type _, err := NewTimeoutSignatureAggregator(0, flow.IdentitySkeletonList{&signer.IdentitySkeleton}, tag) @@ -191,7 +191,7 @@ func TestTimeoutSignatureAggregator_Aggregate(t *testing.T) { var err error aggregator, ids, pks, sigs, signersInfo, msgs, hashers := createAggregationData(t, signersNum) // replace sig with random one - sk := unittest.PrivateKeyFixture(crypto.BLSBLS12381, crypto.KeyGenSeedMinLen) + sk := unittest.PrivateKeyFixture(crypto.BLSBLS12381) sigs[0], err = sk.Sign([]byte("dummy"), hashers[0]) require.NoError(t, err) diff --git a/consensus/hotstuff/verification/combined_signer_v3_test.go b/consensus/hotstuff/verification/combined_signer_v3_test.go index eaab5d6ac47..b2195e0c575 100644 --- a/consensus/hotstuff/verification/combined_signer_v3_test.go +++ b/consensus/hotstuff/verification/combined_signer_v3_test.go @@ -339,7 +339,7 @@ func generateAggregatedSignature(t *testing.T, n int, msg []byte, tag string) ([ // generateSignature creates a single private BLS 12-381 key, signs the provided `message` with // using domain separation `tag` and return the private key and signature. func generateSignature(t *testing.T, message []byte, tag string) (crypto.PrivateKey, crypto.Signature) { - priv := unittest.PrivateKeyFixture(crypto.BLSBLS12381, crypto.KeyGenSeedMinLen) + priv := unittest.PrivateKeyFixture(crypto.BLSBLS12381) sig, err := priv.Sign(message, msig.NewBLSHasher(tag)) require.NoError(t, err) return priv, sig diff --git a/consensus/integration/epoch_test.go b/consensus/integration/epoch_test.go index 2a96a1c9d72..ea396b261cf 100644 --- a/consensus/integration/epoch_test.go +++ b/consensus/integration/epoch_test.go @@ -97,7 +97,7 @@ func TestStaticEpochTransition(t *testing.T) { func TestEpochTransition_IdentitiesOverlap(t *testing.T) { // must finalize 8 blocks, we specify the epoch transition after 4 views stopper := NewStopper(8, 0) - privateNodeInfos := createPrivateNodeIdentities(4) + privateNodeInfos := createPrivateNodeIdentities(t, 4) firstEpochConsensusParticipants := completeConsensusIdentities(t, privateNodeInfos[:3]) rootSnapshot := createRootSnapshot(t, firstEpochConsensusParticipants) consensusParticipants := NewConsensusParticipants(firstEpochConsensusParticipants) diff --git a/consensus/integration/nodes_test.go b/consensus/integration/nodes_test.go index 9a8ff0a81e6..2f31395fb52 100644 --- a/consensus/integration/nodes_test.go +++ b/consensus/integration/nodes_test.go @@ -291,13 +291,13 @@ func createRootBlockData(participantData *run.ParticipantData) (*flow.Block, *fl return root, result, seal } -func createPrivateNodeIdentities(n int) []bootstrap.NodeInfo { +func createPrivateNodeIdentities(t *testing.T, n int) []bootstrap.NodeInfo { consensus := unittest.IdentityListFixture(n, unittest.WithRole(flow.RoleConsensus)).Sort(flow.Canonical[flow.Identity]) infos := make([]bootstrap.NodeInfo, 0, n) for _, node := range consensus { networkPrivKey := unittest.NetworkingPrivKeyFixture() stakingPrivKey := unittest.StakingPrivKeyFixture() - nodeInfo := bootstrap.NewPrivateNodeInfo( + nodeInfo, err := bootstrap.NewPrivateNodeInfo( node.NodeID, node.Role, node.Address, @@ -305,6 +305,7 @@ func createPrivateNodeIdentities(n int) []bootstrap.NodeInfo { networkPrivKey, stakingPrivKey, ) + require.NoError(t, err) infos = append(infos, nodeInfo) } return infos @@ -312,7 +313,7 @@ func createPrivateNodeIdentities(n int) []bootstrap.NodeInfo { func createConsensusIdentities(t *testing.T, n int) *run.ParticipantData { // create n consensus node participants - consensus := createPrivateNodeIdentities(n) + consensus := createPrivateNodeIdentities(t, n) return completeConsensusIdentities(t, consensus) } diff --git a/deploy/systemd-docker/example-node-infos.pub.json b/deploy/systemd-docker/example-node-infos.pub.json index cfba2da97fc..a60dcf48625 100644 --- a/deploy/systemd-docker/example-node-infos.pub.json +++ b/deploy/systemd-docker/example-node-infos.pub.json @@ -5,7 +5,8 @@ "NodeID": "9bbea0644b5e91ec66b4afddef7083e896da545d530ee52b85c78afa88301556", "Weight": 1000, "NetworkPubKey": "...", - "StakingPubKey": "..." + "StakingPubKey": "...", + "StakingKeyPoP": "..." }, { "Role": "consensus", @@ -13,7 +14,8 @@ "NodeID": "9bbea0644b5e91ec66b4afddef7083e896da545d530ee52b85c78afa88301556", "Weight": 1000, "NetworkPubKey": "...", - "StakingPubKey": "..." + "StakingPubKey": "...", + "StakingKeyPoP": "..." }, { "Role": "execution", @@ -21,7 +23,8 @@ "NodeID": "9bbea0644b5e91ec66b4afddef7083e896da545d530ee52b85c78afa88301556", "Weight": 1000, "NetworkPubKey": "...", - "StakingPubKey": "..." + "StakingPubKey": "...", + "StakingKeyPoP": "..." }, { "Role": "verification", @@ -29,6 +32,7 @@ "NodeID": "9bbea0644b5e91ec66b4afddef7083e896da545d530ee52b85c78afa88301556", "Weight": 1000, "NetworkPubKey": "...", - "StakingPubKey": "..." + "StakingPubKey": "...", + "StakingKeyPoP": "..." } ] diff --git a/engine/consensus/dkg/reactor_engine_test.go b/engine/consensus/dkg/reactor_engine_test.go index b484fe503ee..bca7caaee01 100644 --- a/engine/consensus/dkg/reactor_engine_test.go +++ b/engine/consensus/dkg/reactor_engine_test.go @@ -114,7 +114,7 @@ func (suite *ReactorEngineSuite_SetupPhase) SetupTest() { // expectedPrivKey is the expected private share produced by the dkg run. We // will mock the controller to return this value, and we will check it // against the value that gets inserted in the DB at the end. - suite.expectedPrivateKey = unittest.PrivateKeyFixture(crypto.BLSBLS12381, 48) + suite.expectedPrivateKey = unittest.PrivateKeyFixture(crypto.BLSBLS12381) // mock protocol state suite.currentEpoch = new(protocol.Epoch) diff --git a/integration/testnet/container.go b/integration/testnet/container.go index 7d310139e8f..7549c53febc 100644 --- a/integration/testnet/container.go +++ b/integration/testnet/container.go @@ -96,8 +96,9 @@ func GetPrivateNodeInfoAddress(nodeName string) string { return fmt.Sprintf("%s:%d", nodeName, DefaultFlowPort) } -func NewContainerConfig(nodeName string, conf NodeConfig, networkKey, stakingKey crypto.PrivateKey) ContainerConfig { - info := bootstrap.NewPrivateNodeInfo( +func NewContainerConfig(nodeName string, conf NodeConfig, networkKey, stakingKey crypto.PrivateKey, +) (ContainerConfig, error) { + info, err := bootstrap.NewPrivateNodeInfo( conf.Identifier, conf.Role, GetPrivateNodeInfoAddress(nodeName), @@ -105,6 +106,9 @@ func NewContainerConfig(nodeName string, conf NodeConfig, networkKey, stakingKey networkKey, stakingKey, ) + if err != nil { + return ContainerConfig{}, err + } containerConf := ContainerConfig{ NodeInfo: info, @@ -117,7 +121,7 @@ func NewContainerConfig(nodeName string, conf NodeConfig, networkKey, stakingKey Corrupted: conf.Corrupted, } - return containerConf + return containerConf, nil } // ImageName returns the Docker image name for the given config. diff --git a/integration/testnet/network.go b/integration/testnet/network.go index ca1f98396b6..22b88caee78 100644 --- a/integration/testnet/network.go +++ b/integration/testnet/network.go @@ -1007,7 +1007,7 @@ func followerNodeInfos(confs []ConsensusFollowerConfig) ([]bootstrap.NodeInfo, e dummyStakingKey := unittest.StakingPrivKeyFixture() for _, conf := range confs { - info := bootstrap.NewPrivateNodeInfo( + info, err := bootstrap.NewPrivateNodeInfo( conf.NodeID, flow.RoleAccess, // use Access role "", // no address @@ -1015,6 +1015,9 @@ func followerNodeInfos(confs []ConsensusFollowerConfig) ([]bootstrap.NodeInfo, e conf.NetworkingPrivKey, dummyStakingKey, ) + if err != nil { + return nil, err + } nodeInfos = append(nodeInfos, info) } @@ -1281,7 +1284,7 @@ func setupKeys(networkConf NetworkConfig) ([]ContainerConfig, error) { addr := fmt.Sprintf("%s:%d", name, DefaultFlowPort) roleCounter[conf.Role]++ - info := bootstrap.NewPrivateNodeInfo( + info, err := bootstrap.NewPrivateNodeInfo( conf.Identifier, conf.Role, addr, @@ -1289,6 +1292,9 @@ func setupKeys(networkConf NetworkConfig) ([]ContainerConfig, error) { networkKeys[i], stakingKeys[i], ) + if err != nil { + return nil, err + } containerConf := ContainerConfig{ NodeInfo: info, diff --git a/integration/testnet/util.go b/integration/testnet/util.go index c48d9bc0afd..6559a819dc5 100644 --- a/integration/testnet/util.go +++ b/integration/testnet/util.go @@ -171,7 +171,7 @@ func WriteTestExecutionService(_ flow.Identifier, address, observerName, bootstr log.Info().Msgf("test execution node private key: %v, public key: %x, peerID: %v, nodeID: %v", networkKey, k, peerID, nodeID) - nodeInfo := bootstrap.NewPrivateNodeInfo( + nodeInfo, err := bootstrap.NewPrivateNodeInfo( nodeID, flow.RoleExecution, address, @@ -180,6 +180,10 @@ func WriteTestExecutionService(_ flow.Identifier, address, observerName, bootstr stakingKey, ) + if err != nil { + return bootstrap.NodeInfo{}, fmt.Errorf("failed to create node info: %w", err) + } + path := fmt.Sprintf("%s/private-root-information/private-node-info_%v/%vjson", bootstrapDir, nodeID, bootstrap.PathPrivNodeInfoPrefix) diff --git a/integration/tests/epochs/dynamic_epoch_transition_suite.go b/integration/tests/epochs/dynamic_epoch_transition_suite.go index 828d688e74e..bb8b29fde32 100644 --- a/integration/tests/epochs/dynamic_epoch_transition_suite.go +++ b/integration/tests/epochs/dynamic_epoch_transition_suite.go @@ -165,13 +165,13 @@ func (s *DynamicEpochTransitionSuite) generateAccountKeys(role flow.Role) ( machineAccountKey crypto.PrivateKey, machineAccountPubKey *sdk.AccountKey, ) { - operatorAccountKey = unittest.PrivateKeyFixture(crypto.ECDSAP256, crypto.KeyGenSeedMinLen) + operatorAccountKey = unittest.PrivateKeyFixture(crypto.ECDSAP256) networkingKey = unittest.NetworkingPrivKeyFixture() stakingKey = unittest.StakingPrivKeyFixture() // create a machine account if role == flow.RoleConsensus || role == flow.RoleCollection { - machineAccountKey = unittest.PrivateKeyFixture(crypto.ECDSAP256, crypto.KeyGenSeedMinLen) + machineAccountKey = unittest.PrivateKeyFixture(crypto.ECDSAP256) machineAccountPubKey = &sdk.AccountKey{ PublicKey: machineAccountKey.PublicKey(), @@ -303,8 +303,9 @@ func (s *DynamicEpochTransitionSuite) newTestContainerOnNetwork(role flow.Role, } nodeConfig := testnet.NewNodeConfig(role, containerConfigs...) - testContainerConfig := testnet.NewContainerConfig(info.ContainerName, nodeConfig, info.NetworkingKey, info.StakingKey) - err := testContainerConfig.WriteKeyFiles(s.net.BootstrapDir, info.MachineAccountAddress, encodable.MachineAccountPrivKey{PrivateKey: info.MachineAccountKey}, role) + testContainerConfig, err := testnet.NewContainerConfig(info.ContainerName, nodeConfig, info.NetworkingKey, info.StakingKey) + require.NoError(s.T(), err) + err = testContainerConfig.WriteKeyFiles(s.net.BootstrapDir, info.MachineAccountAddress, encodable.MachineAccountPrivKey{PrivateKey: info.MachineAccountKey}, role) require.NoError(s.T(), err) //add our container to the network diff --git a/integration/utils/transactions.go b/integration/utils/transactions.go index 1eaef320b68..4c804290240 100644 --- a/integration/utils/transactions.go +++ b/integration/utils/transactions.go @@ -218,7 +218,7 @@ func MakeSetProtocolStateVersionTx( // This ensures a single transaction can be sealed by the network. func CreateFlowAccount(ctx context.Context, client *testnet.Client) (sdk.Address, error) { fullAccountKey := sdk.NewAccountKey(). - SetPublicKey(unittest.PrivateKeyFixture(crypto.ECDSAP256, crypto.KeyGenSeedMinLen).PublicKey()). + SetPublicKey(unittest.PrivateKeyFixture(crypto.ECDSAP256).PublicKey()). SetHashAlgo(sdkcrypto.SHA2_256). SetWeight(sdk.AccountKeyWeightThreshold) diff --git a/model/bootstrap/node_info.go b/model/bootstrap/node_info.go index 12b35fabe86..c98da5c0fbe 100644 --- a/model/bootstrap/node_info.go +++ b/model/bootstrap/node_info.go @@ -159,6 +159,7 @@ type NodeInfoPub struct { Weight uint64 NetworkPubKey encodable.NetworkPubKey StakingPubKey encodable.StakingPubKey + StakingPoP []byte } // decodableNodeInfoPub provides backward-compatible decoding of old models @@ -170,6 +171,7 @@ type decodableNodeInfoPub struct { Weight uint64 NetworkPubKey encodable.NetworkPubKey StakingPubKey encodable.StakingPubKey + StakingPoP []byte // Stake previously was used in place of the Weight field. // Deprecated: supported in decoding for backward-compatibility Stake uint64 @@ -206,6 +208,7 @@ func (info *NodeInfoPub) UnmarshalJSON(b []byte) error { info.Weight = decodable.Weight info.NetworkPubKey = decodable.NetworkPubKey info.StakingPubKey = decodable.StakingPubKey + info.StakingPoP = decodable.StakingPoP return nil } @@ -244,6 +247,7 @@ type NodeInfo struct { networkPrivKey crypto.PrivateKey stakingPubKey crypto.PublicKey stakingPrivKey crypto.PrivateKey + stakingPoP crypto.Signature } func NewPublicNodeInfo( @@ -253,6 +257,7 @@ func NewPublicNodeInfo( weight uint64, networkKey crypto.PublicKey, stakingKey crypto.PublicKey, + stakingPoP crypto.Signature, ) NodeInfo { return NodeInfo{ NodeID: nodeID, @@ -261,6 +266,7 @@ func NewPublicNodeInfo( Weight: weight, networkPubKey: networkKey, stakingPubKey: stakingKey, + stakingPoP: stakingPoP, } } @@ -271,7 +277,12 @@ func NewPrivateNodeInfo( weight uint64, networkKey crypto.PrivateKey, stakingKey crypto.PrivateKey, -) NodeInfo { +) (NodeInfo, error) { + pop, err := crypto.BLSGeneratePOP(stakingKey) + if err != nil { + return NodeInfo{}, fmt.Errorf("failed to generate PoP: %w", err) + } + return NodeInfo{ NodeID: nodeID, Role: role, @@ -281,7 +292,8 @@ func NewPrivateNodeInfo( stakingPrivKey: stakingKey, networkPubKey: networkKey.PublicKey(), stakingPubKey: stakingKey.PublicKey(), - } + stakingPoP: pop, + }, nil } // Type returns the type of the node info instance. @@ -309,6 +321,17 @@ func (node NodeInfo) StakingPubKey() crypto.PublicKey { return node.stakingPrivKey.PublicKey() } +func (node NodeInfo) StakingPoP() (crypto.Signature, error) { + if node.stakingPoP != nil { + return node.stakingPoP, nil + } + pop, err := crypto.BLSGeneratePOP(node.stakingPrivKey) + if err != nil { + return nil, fmt.Errorf("staking PoP generation failed: %w", err) + } + return pop, nil +} + func (node NodeInfo) PrivateKeys() (*NodePrivateKeys, error) { if node.Type() != NodeInfoTypePrivate { return nil, ErrMissingPrivateInfo @@ -335,7 +358,12 @@ func (node NodeInfo) Private() (NodeInfoPriv, error) { } // Public returns the canonical public encodable structure -func (node NodeInfo) Public() NodeInfoPub { +func (node NodeInfo) Public() (NodeInfoPub, error) { + stakingPoP, err := node.StakingPoP() + if err != nil { + return NodeInfoPub{}, fmt.Errorf("failed to generate staking PoP: %w", err) + } + return NodeInfoPub{ Role: node.Role, Address: node.Address, @@ -343,18 +371,25 @@ func (node NodeInfo) Public() NodeInfoPub { Weight: node.Weight, NetworkPubKey: encodable.NetworkPubKey{PublicKey: node.NetworkPubKey()}, StakingPubKey: encodable.StakingPubKey{PublicKey: node.StakingPubKey()}, - } + StakingPoP: stakingPoP, + }, nil } // PartnerPublic returns the public data for a partner node. -func (node NodeInfo) PartnerPublic() PartnerNodeInfoPub { +func (node NodeInfo) PartnerPublic() (PartnerNodeInfoPub, error) { + + stakingPoP, err := node.StakingPoP() + if err != nil { + return PartnerNodeInfoPub{}, fmt.Errorf("failed to generate staking PoP: %w", err) + } return PartnerNodeInfoPub{ Role: node.Role, Address: node.Address, NodeID: node.NodeID, NetworkPubKey: encodable.NetworkPubKey{PublicKey: node.NetworkPubKey()}, StakingPubKey: encodable.StakingPubKey{PublicKey: node.StakingPubKey()}, - } + StakingPoP: stakingPoP, + }, nil } // Identity returns the node info as a public Flow identity. @@ -375,18 +410,9 @@ func (node NodeInfo) Identity() *flow.Identity { return identity } -// NodeInfoFromIdentity converts an identity to a public NodeInfo -func NodeInfoFromIdentity(identity *flow.Identity) NodeInfo { - return NewPublicNodeInfo( - identity.NodeID, - identity.Role, - identity.Address, - identity.InitialWeight, - identity.NetworkPubKey, - identity.StakingPubKey) -} - -func PrivateNodeInfoFromIdentity(identity *flow.Identity, networkKey, stakingKey crypto.PrivateKey) NodeInfo { +// PrivateNodeInfoFromIdentity builds a NodeInfo from a flow Identity. +// WARNING: Nothing enforces that the output NodeInfo's keys are corresponding to the input Identity. +func PrivateNodeInfoFromIdentity(identity *flow.Identity, networkKey, stakingKey crypto.PrivateKey) (NodeInfo, error) { return NewPrivateNodeInfo( identity.NodeID, identity.Role, @@ -428,10 +454,14 @@ func ToIdentityList(nodes []NodeInfo) flow.IdentityList { return il } -func ToPublicNodeInfoList(nodes []NodeInfo) []NodeInfoPub { +func ToPublicNodeInfoList(nodes []NodeInfo) ([]NodeInfoPub, error) { pub := make([]NodeInfoPub, 0, len(nodes)) for _, node := range nodes { - pub = append(pub, node.Public()) + info, err := node.Public() + if err != nil { + return nil, fmt.Errorf("could not read public info: %w", err) + } + pub = append(pub, info) } - return pub + return pub, nil } diff --git a/model/bootstrap/node_info_test.go b/model/bootstrap/node_info_test.go index 635826dd43c..4c67b4bf81c 100644 --- a/model/bootstrap/node_info_test.go +++ b/model/bootstrap/node_info_test.go @@ -66,7 +66,8 @@ func TestNodeConfigEncodingJSON(t *testing.T) { func TestNodeInfoPubEncodingJSON(t *testing.T) { t.Run("normal node info", func(t *testing.T) { - conf := unittest.NodeInfoFixture().Public() + conf, err := unittest.NodeInfoFixture().Public() + require.NoError(t, err) enc, err := json.Marshal(conf) require.NoError(t, err) var dec bootstrap.NodeInfoPub @@ -75,7 +76,8 @@ func TestNodeInfoPubEncodingJSON(t *testing.T) { assert.True(t, dec.Equals(&conf)) }) t.Run("compat: should accept old files using Stake field", func(t *testing.T) { - conf := unittest.NodeInfoFixture().Public() + conf, err := unittest.NodeInfoFixture().Public() + require.NoError(t, err) enc, err := json.Marshal(conf) require.NoError(t, err) // emulate the old encoding by replacing the new field with old field name diff --git a/model/bootstrap/partner_nodes.go.go b/model/bootstrap/partner_nodes.go.go index a65f09d2e18..36e5c9cc41a 100644 --- a/model/bootstrap/partner_nodes.go.go +++ b/model/bootstrap/partner_nodes.go.go @@ -14,4 +14,5 @@ type PartnerNodeInfoPub struct { NodeID flow.Identifier NetworkPubKey encodable.NetworkPubKey StakingPubKey encodable.StakingPubKey + StakingPoP []byte } diff --git a/module/epochs/machine_account_test.go b/module/epochs/machine_account_test.go index 0eb5c593bcf..e80af3e31ce 100644 --- a/module/epochs/machine_account_test.go +++ b/module/epochs/machine_account_test.go @@ -36,7 +36,7 @@ func TestMachineAccountChecking(t *testing.T) { }) t.Run("inconsistent key", func(t *testing.T) { local, remote := unittest.MachineAccountFixture(t) - randomKey := unittest.PrivateKeyFixture(crypto.ECDSAP256, unittest.DefaultSeedFixtureLength) + randomKey := unittest.PrivateKeyFixture(crypto.ECDSAP256) remote.Keys[0].PublicKey = randomKey.PublicKey() err := CheckMachineAccountInfo(zerolog.Nop(), conf, flow.RoleConsensus, local, remote) require.Error(t, err) @@ -154,7 +154,7 @@ func TestMachineAccountChecking(t *testing.T) { local, remote := unittest.MachineAccountFixture(t) // non-standard sig algo - sk := unittest.PrivateKeyFixture(crypto.ECDSASecp256k1, unittest.DefaultSeedFixtureLength) + sk := unittest.PrivateKeyFixture(crypto.ECDSASecp256k1) local.EncodedPrivateKey = sk.Encode() local.SigningAlgorithm = crypto.ECDSASecp256k1 // consistent between local/remote diff --git a/utils/unittest/fixtures.go b/utils/unittest/fixtures.go index ca48c8aca29..6657cdfcc6d 100644 --- a/utils/unittest/fixtures.go +++ b/utils/unittest/fixtures.go @@ -52,8 +52,7 @@ import ( ) const ( - DefaultSeedFixtureLength = 64 - DefaultAddress = "localhost:0" + DefaultAddress = "localhost:0" ) // returns a deterministic math/rand PRG that can be used for deterministic randomness in tests only. @@ -1142,8 +1141,28 @@ func NodeConfigFixture(opts ...func(*flow.Identity)) bootstrap.NodeConfig { } func NodeInfoFixture(opts ...func(*flow.Identity)) bootstrap.NodeInfo { - opts = append(opts, WithKeys) - return bootstrap.NodeInfoFromIdentity(IdentityFixture(opts...)) + nodes := NodeInfosFixture(1, opts...) + return nodes[0] +} + +// NodeInfoFromIdentity converts an identity to a public NodeInfo +// WARNING: the function replaces the staking key from the identity by a freshly generated one. +func NodeInfoFromIdentity(identity *flow.Identity) bootstrap.NodeInfo { + stakingSK := StakingPrivKeyFixture() + stakingPoP, err := crypto.BLSGeneratePOP(stakingSK) + if err != nil { + panic(err.Error()) + } + + return bootstrap.NewPublicNodeInfo( + identity.NodeID, + identity.Role, + identity.Address, + identity.InitialWeight, + identity.NetworkPubKey, + stakingSK.PublicKey(), + stakingPoP, + ) } func NodeInfosFixture(n int, opts ...func(*flow.Identity)) []bootstrap.NodeInfo { @@ -1151,7 +1170,7 @@ func NodeInfosFixture(n int, opts ...func(*flow.Identity)) []bootstrap.NodeInfo il := IdentityListFixture(n, opts...) nodeInfos := make([]bootstrap.NodeInfo, 0, n) for _, identity := range il { - nodeInfos = append(nodeInfos, bootstrap.NodeInfoFromIdentity(identity)) + nodeInfos = append(nodeInfos, NodeInfoFromIdentity(identity)) } return nodeInfos } @@ -1167,7 +1186,10 @@ func PrivateNodeInfosFixture(n int, opts ...func(*flow.Identity)) []bootstrap.No func PrivateNodeInfosFromIdentityList(il flow.IdentityList) []bootstrap.NodeInfo { nodeInfos := make([]bootstrap.NodeInfo, 0, len(il)) for _, identity := range il { - nodeInfo := bootstrap.PrivateNodeInfoFromIdentity(identity, KeyFixture(crypto.ECDSAP256), KeyFixture(crypto.BLSBLS12381)) + nodeInfo, err := bootstrap.PrivateNodeInfoFromIdentity(identity, KeyFixture(crypto.ECDSAP256), KeyFixture(crypto.BLSBLS12381)) + if err != nil { + panic(err.Error()) + } nodeInfos = append(nodeInfos, nodeInfo) } return nodeInfos @@ -2381,9 +2403,9 @@ func DKGBroadcastMessageFixture() *messages.BroadcastDKGMessage { } } -// PrivateKeyFixture returns a random private key with specified signature algorithm and seed length -func PrivateKeyFixture(algo crypto.SigningAlgorithm, seedLength int) crypto.PrivateKey { - sk, err := crypto.GeneratePrivateKey(algo, SeedFixture(seedLength)) +// PrivateKeyFixture returns a random private key with specified signature algorithm +func PrivateKeyFixture(algo crypto.SigningAlgorithm) crypto.PrivateKey { + sk, err := crypto.GeneratePrivateKey(algo, SeedFixture(crypto.KeyGenSeedMinLen)) if err != nil { panic(err) } @@ -2397,8 +2419,9 @@ func PrivateKeyFixtureByIdentifier( seedLength int, id flow.Identifier, ) crypto.PrivateKey { - seed := append(id[:], id[:]...) - sk, err := crypto.GeneratePrivateKey(algo, seed[:seedLength]) + seed := make([]byte, seedLength) + copy(seed, id[:]) + sk, err := crypto.GeneratePrivateKey(algo, seed) if err != nil { panic(err) } @@ -2411,18 +2434,18 @@ func StakingPrivKeyByIdentifier(id flow.Identifier) crypto.PrivateKey { // NetworkingPrivKeyFixture returns random ECDSAP256 private key func NetworkingPrivKeyFixture() crypto.PrivateKey { - return PrivateKeyFixture(crypto.ECDSAP256, crypto.KeyGenSeedMinLen) + return PrivateKeyFixture(crypto.ECDSAP256) } // StakingPrivKeyFixture returns a random BLS12381 private keyf func StakingPrivKeyFixture() crypto.PrivateKey { - return PrivateKeyFixture(crypto.BLSBLS12381, crypto.KeyGenSeedMinLen) + return PrivateKeyFixture(crypto.BLSBLS12381) } func NodeMachineAccountInfoFixture() bootstrap.NodeMachineAccountInfo { return bootstrap.NodeMachineAccountInfo{ Address: RandomAddressFixture().String(), - EncodedPrivateKey: PrivateKeyFixture(crypto.ECDSAP256, DefaultSeedFixtureLength).Encode(), + EncodedPrivateKey: PrivateKeyFixture(crypto.ECDSAP256).Encode(), HashAlgorithm: bootstrap.DefaultMachineAccountHashAlgo, SigningAlgorithm: bootstrap.DefaultMachineAccountSignAlgo, KeyIndex: bootstrap.DefaultMachineAccountKeyIndex,