@@ -3,6 +3,7 @@ package fund
33import (
44 "context"
55 "crypto/ecdsa"
6+ "crypto/sha256"
67 "encoding/hex"
78 "encoding/json"
89 "errors"
@@ -51,17 +52,31 @@ func runFunding(ctx context.Context) error {
5152 }
5253
5354 var addresses []common.Address
55+ var privateKeys []* ecdsa.PrivateKey
5456
5557 if len (params .KeyFile ) > 0 { // get addresses from key-file
56- addresses , err = getAddressesFromKeyFile (params .KeyFile )
58+ addresses , privateKeys , err = getAddressesAndKeysFromKeyFile (params .KeyFile )
59+ } else if len (params .Seed ) > 0 { // get addresses from seed
60+ addresses , privateKeys , err = getAddressesAndKeysFromSeed (params .Seed , int (params .WalletsNumber ))
5761 } else { // get addresses from private key
58- addresses , err = getAddressesFromPrivateKey (ctx , c )
62+ addresses , privateKeys , err = getAddressesAndKeysFromPrivateKey (ctx , c )
5963 }
6064 // check errors after getting addresses
6165 if err != nil {
6266 return err
6367 }
6468
69+ // Save private and public keys to a file if we have private keys.
70+ if len (privateKeys ) > 0 {
71+ go func () {
72+ if err := saveToFile (params .OutputFile , privateKeys ); err != nil {
73+ log .Error ().Err (err ).Msg ("Unable to save keys to file" )
74+ panic (err )
75+ }
76+ log .Info ().Str ("fileName" , params .OutputFile ).Msg ("Wallets' address(es) and private key(s) saved to file" )
77+ }()
78+ }
79+
6580 // Deploy or instantiate the Funder contract.
6681 var contract * funder.Funder
6782 contract , err = deployOrInstantiateFunderContract (ctx , c , tops , privateKey , len (addresses ))
@@ -79,9 +94,9 @@ func runFunding(ctx context.Context) error {
7994 return nil
8095}
8196
82- func getAddressesFromKeyFile (keyFilePath string ) ([]common.Address , error ) {
97+ func getAddressesAndKeysFromKeyFile (keyFilePath string ) ([]common.Address , [] * ecdsa. PrivateKey , error ) {
8398 if len (keyFilePath ) == 0 {
84- return nil , errors .New ("the key file path is empty" )
99+ return nil , nil , errors .New ("the key file path is empty" )
85100 }
86101
87102 log .Trace ().
@@ -93,7 +108,7 @@ func getAddressesFromKeyFile(keyFilePath string) ([]common.Address, error) {
93108 log .Error ().
94109 Err (iErr ).
95110 Msg ("Unable to read private keys from key file" )
96- return nil , fmt .Errorf ("unable to read private keys from key file. %w" , iErr )
111+ return nil , nil , fmt .Errorf ("unable to read private keys from key file. %w" , iErr )
97112 }
98113 addresses := make ([]common.Address , len (privateKeys ))
99114 for i , privateKey := range privateKeys {
@@ -104,30 +119,33 @@ func getAddressesFromKeyFile(keyFilePath string) ([]common.Address, error) {
104119 Msg ("New wallet derived from key file" )
105120 }
106121 log .Info ().Int ("count" , len (addresses )).Msg ("Wallet(s) derived from key file" )
107- return addresses , nil
122+ return addresses , privateKeys , nil
108123}
109124
110- func getAddressesFromPrivateKey (ctx context.Context , c * ethclient.Client ) ([]common.Address , error ) {
125+ func getAddressesAndKeysFromPrivateKey (ctx context.Context , c * ethclient.Client ) ([]common.Address , [] * ecdsa. PrivateKey , error ) {
111126 // Derive or generate a set of wallets.
112127 var addresses []common.Address
128+ var privateKeys []* ecdsa.PrivateKey
113129 var err error
114130 if len (params .WalletAddresses ) > 0 {
115131 log .Info ().Msg ("Using addresses provided by the user" )
116132 addresses = make ([]common.Address , len (params .WalletAddresses ))
117133 for i , address := range params .WalletAddresses {
118134 addresses [i ] = common .HexToAddress (address )
119135 }
136+ // No private keys available when using provided addresses
137+ privateKeys = nil
120138 } else if params .UseHDDerivation {
121139 log .Info ().Msg ("Deriving wallets from the default mnemonic" )
122- addresses , err = deriveHDWallets (int (params .WalletsNumber ))
140+ addresses , privateKeys , err = deriveHDWalletsWithKeys (int (params .WalletsNumber ))
123141 } else {
124142 log .Info ().Msg ("Generating random wallets" )
125- addresses , err = generateWallets (int (params .WalletsNumber ))
143+ addresses , privateKeys , err = generateWalletsWithKeys (int (params .WalletsNumber ))
126144 }
127145 if err != nil {
128- return nil , err
146+ return nil , nil , err
129147 }
130- return addresses , nil
148+ return addresses , privateKeys , nil
131149}
132150
133151// dialRpc dials the Ethereum RPC server and return an Ethereum client.
@@ -203,56 +221,55 @@ func deployOrInstantiateFunderContract(ctx context.Context, c *ethclient.Client,
203221 return contract , nil
204222}
205223
206- // deriveHDWallets generates and exports a specified number of HD wallet addresses.
207- func deriveHDWallets (n int ) ([]common.Address , error ) {
224+ // deriveHDWalletsWithKeys generates and exports a specified number of HD wallet addresses and their private keys .
225+ func deriveHDWalletsWithKeys (n int ) ([]common.Address , [] * ecdsa. PrivateKey , error ) {
208226 wallet , err := hdwallet .NewPolyWallet (defaultMnemonic , defaultPassword )
209227 if err != nil {
210- return nil , err
228+ return nil , nil , err
211229 }
212230
213231 var derivedWallets * hdwallet.PolyWalletExport
214232 derivedWallets , err = wallet .ExportHDAddresses (n )
215233 if err != nil {
216- return nil , err
234+ return nil , nil , err
217235 }
218236
219237 addresses := make ([]common.Address , n )
238+ privateKeys := make ([]* ecdsa.PrivateKey , n )
220239 for i , wallet := range derivedWallets .Addresses {
221240 addresses [i ] = common .HexToAddress (wallet .ETHAddress )
241+ // Parse the private key
242+ trimmedHexPrivateKey := strings .TrimPrefix (wallet .HexPrivateKey , "0x" )
243+ privateKey , err := crypto .HexToECDSA (trimmedHexPrivateKey )
244+ if err != nil {
245+ return nil , nil , fmt .Errorf ("unable to parse private key for wallet %d: %w" , i , err )
246+ }
247+ privateKeys [i ] = privateKey
222248 log .Trace ().Interface ("address" , addresses [i ]).Str ("privateKey" , wallet .HexPrivateKey ).Str ("path" , wallet .Path ).Msg ("New wallet derived" )
223249 }
224250 log .Info ().Int ("count" , n ).Msg ("Wallet(s) derived" )
225- return addresses , nil
251+ return addresses , privateKeys , nil
226252}
227253
228- // generateWallets generates a specified number of Ethereum wallets with random private keys.
229- // It returns a slice of common.Address representing the Ethereum addresses of the generated wallets .
230- func generateWallets (n int ) ([]common.Address , error ) {
254+ // generateWalletsWithKeys generates a specified number of Ethereum wallets with random private keys.
255+ // It returns a slice of common.Address representing the Ethereum addresses and their corresponding private keys .
256+ func generateWalletsWithKeys (n int ) ([]common.Address , [] * ecdsa. PrivateKey , error ) {
231257 // Generate private keys.
232258 privateKeys := make ([]* ecdsa.PrivateKey , n )
233259 addresses := make ([]common.Address , n )
234260 for i := 0 ; i < n ; i ++ {
235261 pk , err := crypto .GenerateKey ()
236262 if err != nil {
237263 log .Error ().Err (err ).Msg ("Error generating key" )
238- return nil , err
264+ return nil , nil , err
239265 }
240266 privateKeys [i ] = pk
241267 addresses [i ] = crypto .PubkeyToAddress (pk .PublicKey )
242268 log .Trace ().Interface ("address" , addresses [i ]).Str ("privateKey" , hex .EncodeToString (pk .D .Bytes ())).Msg ("New wallet generated" )
243269 }
244270 log .Info ().Int ("count" , n ).Msg ("Wallet(s) generated" )
245271
246- // Save private and public keys to a file.
247- go func () {
248- if err := saveToFile (params .OutputFile , privateKeys ); err != nil {
249- log .Error ().Err (err ).Msg ("Unable to save keys to file" )
250- panic (err )
251- }
252- log .Info ().Str ("fileName" , params .OutputFile ).Msg ("Wallets' address(es) and private key(s) saved to file" )
253- }()
254-
255- return addresses , nil
272+ return addresses , privateKeys , nil
256273}
257274
258275// saveToFile serializes wallet data into the specified JSON format and writes it to the designated file.
@@ -304,3 +321,49 @@ func fundWallets(ctx context.Context, c *ethclient.Client, tops *bind.TransactOp
304321 }
305322 return nil
306323}
324+
325+ func getAddressesAndKeysFromSeed (seed string , numWallets int ) ([]common.Address , []* ecdsa.PrivateKey , error ) {
326+ if len (seed ) == 0 {
327+ return nil , nil , errors .New ("the seed string is empty" )
328+ }
329+ if numWallets <= 0 {
330+ return nil , nil , errors .New ("number of wallets must be greater than 0" )
331+ }
332+
333+ log .Info ().
334+ Str ("seed" , seed ).
335+ Int ("numWallets" , numWallets ).
336+ Msg ("Generating wallets from seed" )
337+
338+ addresses := make ([]common.Address , numWallets )
339+ privateKeys := make ([]* ecdsa.PrivateKey , numWallets )
340+
341+ for i := 0 ; i < numWallets ; i ++ {
342+ // Create a deterministic string by combining seed with index and current date
343+ // Format: seed_index_YYYYMMDD (e.g., "ephemeral_test_0_20241010")
344+ currentDate := time .Now ().Format ("20060102" ) // YYYYMMDD format
345+ seedWithIndex := fmt .Sprintf ("%s_%d_%s" , seed , i , currentDate )
346+
347+ // Generate SHA256 hash of the seed+index+date
348+ hash := sha256 .Sum256 ([]byte (seedWithIndex ))
349+ hashHex := hex .EncodeToString (hash [:])
350+
351+ // Create private key from hash
352+ privateKey , err := crypto .HexToECDSA (hashHex )
353+ if err != nil {
354+ return nil , nil , fmt .Errorf ("unable to create private key from seed for wallet %d: %w" , i , err )
355+ }
356+
357+ privateKeys [i ] = privateKey
358+ addresses [i ] = crypto .PubkeyToAddress (privateKey .PublicKey )
359+
360+ log .Trace ().
361+ Interface ("address" , addresses [i ]).
362+ Str ("privateKey" , hashHex ).
363+ Str ("seedWithIndex" , seedWithIndex ).
364+ Msg ("New wallet generated from seed" )
365+ }
366+
367+ log .Info ().Int ("count" , numWallets ).Msg ("Wallet(s) generated from seed" )
368+ return addresses , privateKeys , nil
369+ }
0 commit comments