github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/cmd/bootstrap/utils/key_generation.go (about) 1 package utils 2 3 import ( 4 "crypto/rand" 5 "crypto/sha256" 6 "fmt" 7 gohash "hash" 8 "io" 9 10 "github.com/onflow/crypto" 11 "golang.org/x/crypto/hkdf" 12 13 sdk "github.com/onflow/flow-go-sdk" 14 sdkcrypto "github.com/onflow/flow-go-sdk/crypto" 15 16 "github.com/onflow/flow-go/fvm/systemcontracts" 17 "github.com/onflow/flow-go/model/bootstrap" 18 "github.com/onflow/flow-go/model/encodable" 19 "github.com/onflow/flow-go/model/flow" 20 ) 21 22 // these constants are defined in X9.62 section 4.2 and 4.3 23 // see https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf 24 // they indicate if the conversion to/from a public key (point) in compressed form must involve an inversion of the ordinate coordinate 25 const X962_NO_INVERSION = uint8(0x02) 26 const X962_INVERSION = uint8(0x03) 27 28 func GenerateMachineAccountKey(seed []byte) (crypto.PrivateKey, error) { 29 keys, err := GenerateKeys(crypto.ECDSAP256, 1, [][]byte{seed}) 30 if err != nil { 31 return nil, err 32 } 33 return keys[0], nil 34 } 35 36 // GenerateSecretsDBEncryptionKey generates an encryption key for encrypting a 37 // Badger database. 38 func GenerateSecretsDBEncryptionKey() ([]byte, error) { 39 // 32-byte key to use AES-256 40 // https://pkg.go.dev/github.com/dgraph-io/badger/v2#Options.WithEncryptionKey 41 const keyLen = 32 42 43 key := make([]byte, keyLen) 44 _, err := rand.Read(key) 45 if err != nil { 46 return nil, fmt.Errorf("could not generate key: %w", err) 47 } 48 return key, nil 49 } 50 51 // The unstaked nodes have special networking keys, in two aspects: 52 // - they use crypto.ECDSASecp256k1 keys, not crypto.ECDSAP256 keys, 53 // - they use only positive keys (in the sense that the elliptic curve point of their public key is positive) 54 // 55 // Thanks to various properties of the cryptographic algorithm and libp2p, 56 // this affords us to not have to maintain a table of flow.NodeID -> NetworkPublicKey 57 // for those numerous and ephemeral nodes. 58 // It incurs a one-bit security reduction, which is deemed acceptable. 59 60 // drawUnstakedKey draws a single positive ECDSASecp256k1 key, and returns an error otherwise. 61 func drawUnstakedKey(seed []byte) (crypto.PrivateKey, error) { 62 key, err := crypto.GeneratePrivateKey(crypto.ECDSASecp256k1, seed) 63 if err != nil { 64 // this should not happen 65 return nil, err 66 } else if key.PublicKey().EncodeCompressed()[0] == X962_INVERSION { 67 // negative key -> unsuitable 68 return nil, fmt.Errorf("Unsuitable negative key") 69 } 70 return key, nil 71 } 72 73 // GeneratePublicNetworkingKey draws ECDSASecp256k1 keys until finding a suitable one. 74 // though this will return fast, this is not constant-time and will leak ~1 bit of information through its runtime 75 func GeneratePublicNetworkingKey(seed []byte) (key crypto.PrivateKey, err error) { 76 hkdf := hkdf.New(func() gohash.Hash { return sha256.New() }, seed, nil, []byte("public network")) 77 round_seed := make([]byte, len(seed)) 78 max_iterations := 20 // 1/(2^20) failure chance 79 for i := 0; i < max_iterations; i++ { 80 if _, err = io.ReadFull(hkdf, round_seed); err != nil { 81 // the hkdf Reader should not fail 82 panic(err) 83 } 84 if key, err = drawUnstakedKey(round_seed); err == nil { 85 return 86 } 87 } 88 return 89 } 90 91 func GenerateUnstakedNetworkingKeys(n int, seeds [][]byte) ([]crypto.PrivateKey, error) { 92 if n != len(seeds) { 93 return nil, fmt.Errorf("n needs to match the number of seeds (%v != %v)", n, len(seeds)) 94 } 95 96 keys := make([]crypto.PrivateKey, n) 97 98 var err error 99 for i, seed := range seeds { 100 if keys[i], err = GeneratePublicNetworkingKey(seed); err != nil { 101 return nil, err 102 } 103 } 104 105 return keys, nil 106 } 107 108 func GenerateNetworkingKey(seed []byte) (crypto.PrivateKey, error) { 109 keys, err := GenerateKeys(crypto.ECDSAP256, 1, [][]byte{seed}) 110 if err != nil { 111 return nil, err 112 } 113 return keys[0], nil 114 } 115 116 func GenerateNetworkingKeys(n int, seeds [][]byte) ([]crypto.PrivateKey, error) { 117 return GenerateKeys(crypto.ECDSAP256, n, seeds) 118 } 119 120 func GenerateStakingKey(seed []byte) (crypto.PrivateKey, error) { 121 keys, err := GenerateKeys(crypto.BLSBLS12381, 1, [][]byte{seed}) 122 if err != nil { 123 return nil, err 124 } 125 return keys[0], nil 126 } 127 128 func GenerateStakingKeys(n int, seeds [][]byte) ([]crypto.PrivateKey, error) { 129 return GenerateKeys(crypto.BLSBLS12381, n, seeds) 130 } 131 132 func GenerateKeys(algo crypto.SigningAlgorithm, n int, seeds [][]byte) ([]crypto.PrivateKey, error) { 133 if n != len(seeds) { 134 return nil, fmt.Errorf("n needs to match the number of seeds (%v != %v)", n, len(seeds)) 135 } 136 137 keys := make([]crypto.PrivateKey, n) 138 139 var err error 140 for i, seed := range seeds { 141 if keys[i], err = crypto.GeneratePrivateKey(algo, seed); err != nil { 142 return nil, err 143 } 144 } 145 146 return keys, nil 147 } 148 149 // WriteJSONFileFunc is a function which writes a file during bootstrapping. It 150 // accepts the path for the file (relative to the bootstrapping root directory) 151 // and the value to write. The function must marshal the value as JSON and write 152 // the result to the given path. 153 type WriteJSONFileFunc func(relativePath string, value interface{}) error 154 155 // WriteFileFunc is the same as WriteJSONFileFunc, but it writes the bytes directly 156 // rather than marshalling a structure to json. 157 type WriteFileFunc func(relativePath string, data []byte) error 158 159 // WriteMachineAccountFiles writes machine account key files for a set of nodeInfos. 160 // Assumes that machine accounts have been created using the default execution state 161 // bootstrapping. Further assumes that the order of nodeInfos is the same order that 162 // nodes were registered during execution state bootstrapping. 163 // 164 // Only applicable for transient test networks. 165 func WriteMachineAccountFiles(chainID flow.ChainID, nodeInfos []bootstrap.NodeInfo, write WriteJSONFileFunc) error { 166 167 // ensure the chain ID is for a transient chain, where it is possible to 168 // infer machine account addresses this way 169 if !chainID.Transient() { 170 return fmt.Errorf("cannot write default machine account files for non-transient network") 171 } 172 173 // write machine account key files for each node which has a machine account (LN/SN) 174 // 175 // for the machine account key, we keep track of the address index to map 176 // the Flow address of the machine account to the key. 177 addressIndex := uint64(systemcontracts.LastSystemAccountIndex) 178 for _, nodeInfo := range nodeInfos { 179 180 // retrieve private representation of the node 181 private, err := nodeInfo.Private() 182 if err != nil { 183 return err 184 } 185 186 // We use the network key for the machine account. Normally it would be 187 // a separate key. 188 189 // Accounts are generated in a known order during bootstrapping, and 190 // account addresses are deterministic based on order for a given chain 191 // configuration. During the bootstrapping we create 4 Flow accounts besides 192 // the service account (index 0) so node accounts will start at index 5. 193 // 194 // All nodes have a staking account created for them, only collection and 195 // consensus nodes have a second machine account created. 196 // 197 // The accounts are created in the same order defined by the identity list 198 // provided to BootstrapProcedure, which is the same order as this iteration. 199 if nodeInfo.Role == flow.RoleCollection || nodeInfo.Role == flow.RoleConsensus { 200 // increment the address index to account for both the staking account 201 // and the machine account. 202 // now addressIndex points to the machine account address index 203 addressIndex += 2 204 } else { 205 // increment the address index to account for the staking account 206 // we don't need to persist anything related to the staking account 207 addressIndex += 1 208 continue 209 } 210 211 accountAddress, err := chainID.Chain().AddressAtIndex(addressIndex) 212 if err != nil { 213 return err 214 } 215 216 info := bootstrap.NodeMachineAccountInfo{ 217 Address: accountAddress.HexWithPrefix(), 218 EncodedPrivateKey: private.NetworkPrivKey.Encode(), 219 KeyIndex: 0, 220 SigningAlgorithm: private.NetworkPrivKey.Algorithm(), 221 HashAlgorithm: sdkcrypto.SHA3_256, 222 } 223 224 path := fmt.Sprintf(bootstrap.PathNodeMachineAccountInfoPriv, nodeInfo.NodeID) 225 err = write(path, info) 226 if err != nil { 227 return err 228 } 229 } 230 231 return nil 232 } 233 234 func WriteMachineAccountFile( 235 nodeID flow.Identifier, 236 accountAddress sdk.Address, 237 accountKey encodable.MachineAccountPrivKey, 238 write WriteJSONFileFunc) error { 239 240 info := bootstrap.NodeMachineAccountInfo{ 241 Address: fmt.Sprintf("0x%s", accountAddress.Hex()), 242 EncodedPrivateKey: accountKey.Encode(), 243 KeyIndex: 0, 244 SigningAlgorithm: accountKey.Algorithm(), 245 HashAlgorithm: sdkcrypto.SHA3_256, 246 } 247 248 path := fmt.Sprintf(bootstrap.PathNodeMachineAccountInfoPriv, nodeID) 249 err := write(path, info) 250 if err != nil { 251 return err 252 } 253 254 return nil 255 } 256 257 // WriteSecretsDBEncryptionKeyFiles writes secret db encryption keys to private 258 // node info directory. 259 func WriteSecretsDBEncryptionKeyFiles(nodeInfos []bootstrap.NodeInfo, write WriteFileFunc) error { 260 261 for _, nodeInfo := range nodeInfos { 262 263 // generate an encryption key for the node 264 encryptionKey, err := GenerateSecretsDBEncryptionKey() 265 if err != nil { 266 return err 267 } 268 269 path := fmt.Sprintf(bootstrap.PathSecretsEncryptionKey, nodeInfo.NodeID) 270 err = write(path, encryptionKey) 271 if err != nil { 272 return err 273 } 274 } 275 return nil 276 } 277 278 // WriteStakingNetworkingKeyFiles writes staking and networking keys to private 279 // node info files. 280 func WriteStakingNetworkingKeyFiles(nodeInfos []bootstrap.NodeInfo, write WriteJSONFileFunc) error { 281 282 for _, nodeInfo := range nodeInfos { 283 284 // retrieve private representation of the node 285 private, err := nodeInfo.Private() 286 if err != nil { 287 return err 288 } 289 290 path := fmt.Sprintf(bootstrap.PathNodeInfoPriv, nodeInfo.NodeID) 291 err = write(path, private) 292 if err != nil { 293 return err 294 } 295 } 296 297 return nil 298 }