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