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  }