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 }