github.com/prysmaticlabs/prysm@v1.4.4/tools/interop/split-keys/main.go (about)

     1  // Package main provides a tool named split-keys which allows for generating any number of Ethereum validator keys
     2  // from a list of BIP39 mnemonics and spreading them across any number of Prysm wallets. This is useful for creating
     3  // custom allocations of keys across containers running in a cloud environment, such as for public testnets.
     4  // An example of why you would use this tool is as follows. Let's say we have 1 mnemonic contained inside of a file.
     5  // Then, we want to generate 10 keys from the mnemonic, and we want to spread them across 5 different wallets, each
     6  // containing two keys. Then, you would run the tool as follows:
     7  //
     8  // ./main -mnemonics-file=/path/to/file.txt -keys-per-mnemonic=10 -num-wallets=5
     9  //
    10  // You can also specify the output directory for the wallet files using -out-dir and also the password
    11  // used to encrypt the wallets in a text file using -wallet-password-file.
    12  package main
    13  
    14  import (
    15  	"bufio"
    16  	"context"
    17  	"flag"
    18  	"fmt"
    19  	"log"
    20  	"os"
    21  	"path"
    22  
    23  	"github.com/prysmaticlabs/prysm/shared/fileutil"
    24  	"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
    25  	"github.com/prysmaticlabs/prysm/validator/keymanager"
    26  	"github.com/prysmaticlabs/prysm/validator/keymanager/derived"
    27  	"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
    28  	"github.com/tyler-smith/go-bip39"
    29  	util "github.com/wealdtech/go-eth2-util"
    30  )
    31  
    32  var (
    33  	mnemonicsFileFlag      = flag.String("mnemonics-file", "", "File containing mnemonics, one mnemonic per line")
    34  	keysPerMnemonicFlag    = flag.Int("keys-per-mnemonic", 0, "The number of keys per mnemonic to generate")
    35  	numberOfWalletsFlag    = flag.Int("num-wallets", 0, "Number of wallets to generate")
    36  	walletOutDirFlag       = flag.String("out-dir", "", "Output directory for wallet files")
    37  	walletPasswordFileFlag = flag.String("wallet-password-file", "", "File containing the password to encrypt all generated wallets")
    38  )
    39  
    40  // This application is run to generate keystores for testnets.
    41  func main() {
    42  	flag.Parse()
    43  	f, err := os.Open(*mnemonicsFileFlag)
    44  	if err != nil {
    45  		log.Fatal(err)
    46  	}
    47  	defer func() {
    48  		if err = f.Close(); err != nil {
    49  			log.Fatal(err)
    50  		}
    51  	}()
    52  
    53  	pubKeys, privKeys, err := generateKeysFromMnemonicList(bufio.NewScanner(f), *keysPerMnemonicFlag)
    54  	if err != nil {
    55  		log.Fatal(err)
    56  	}
    57  
    58  	log.Printf("Splitting %d keys across %d wallets\n", len(privKeys), *numberOfWalletsFlag)
    59  	wPass, err := fileutil.ReadFileAsBytes(*walletPasswordFileFlag)
    60  	if err != nil {
    61  		log.Fatal(err)
    62  	}
    63  
    64  	keysPerWallet := len(privKeys) / *numberOfWalletsFlag
    65  	if err := spreadKeysAcrossImportedWallets(
    66  		pubKeys,
    67  		privKeys,
    68  		*numberOfWalletsFlag,
    69  		keysPerWallet,
    70  		*walletOutDirFlag,
    71  		string(wPass),
    72  	); err != nil {
    73  		log.Fatal(err)
    74  	}
    75  	log.Println("Done")
    76  }
    77  
    78  // Uses the provided mnemonic seed phrase to generate the
    79  // appropriate seed file for recovering a derived wallets.
    80  func seedFromMnemonic(mnemonic, mnemonicPassphrase string) ([]byte, error) {
    81  	if ok := bip39.IsMnemonicValid(mnemonic); !ok {
    82  		return nil, bip39.ErrInvalidMnemonic
    83  	}
    84  	return bip39.NewSeed(mnemonic, mnemonicPassphrase), nil
    85  }
    86  
    87  func generateKeysFromMnemonicList(mnemonicListFile *bufio.Scanner, keysPerMnemonic int) (pubKeys, privKeys [][]byte, err error) {
    88  	pubKeys = make([][]byte, 0)
    89  	privKeys = make([][]byte, 0)
    90  	var seed []byte
    91  	for mnemonicListFile.Scan() {
    92  		log.Printf("Generating %d keys from mnemonic\n", keysPerMnemonic)
    93  		mnemonic := mnemonicListFile.Text()
    94  		seed, err = seedFromMnemonic(mnemonic, "" /* 25th word*/)
    95  		if err != nil {
    96  			return
    97  		}
    98  		for i := 0; i < keysPerMnemonic; i++ {
    99  			if i%250 == 0 && i > 0 {
   100  				log.Printf("%d/%d keys generated\n", i, keysPerMnemonic)
   101  			}
   102  			privKey, seedErr := util.PrivateKeyFromSeedAndPath(
   103  				seed, fmt.Sprintf(derived.ValidatingKeyDerivationPathTemplate, i),
   104  			)
   105  			if seedErr != nil {
   106  				err = seedErr
   107  				return
   108  			}
   109  			privKeys = append(privKeys, privKey.Marshal())
   110  			pubKeys = append(pubKeys, privKey.PublicKey().Marshal())
   111  		}
   112  	}
   113  	return
   114  }
   115  
   116  func spreadKeysAcrossImportedWallets(
   117  	pubKeys,
   118  	privKeys [][]byte,
   119  	numWallets,
   120  	keysPerWallet int,
   121  	walletOutputDir,
   122  	walletPassword string,
   123  ) error {
   124  	ctx := context.Background()
   125  	for i := 0; i < numWallets; i++ {
   126  		w := wallet.New(&wallet.Config{
   127  			WalletDir:      path.Join(walletOutputDir, fmt.Sprintf("wallet_%d", i)),
   128  			KeymanagerKind: keymanager.Imported,
   129  			WalletPassword: walletPassword,
   130  		})
   131  		km, err := imported.NewKeymanager(ctx, &imported.SetupConfig{
   132  			Wallet: w,
   133  		})
   134  		if err != nil {
   135  			return err
   136  		}
   137  		log.Printf("Importing %d keys into wallet %d\n", keysPerWallet, i)
   138  		if err := km.ImportKeypairs(ctx, privKeys[i*keysPerWallet:(i+1)*keysPerWallet], pubKeys[i*keysPerWallet:(i+1)*keysPerWallet]); err != nil {
   139  			return err
   140  		}
   141  	}
   142  	return nil
   143  }