102 lines
2.7 KiB
Go
102 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
gethcrypto "github.com/luxfi/geth/crypto"
|
|
bip32 "github.com/luxfi/go-bip32"
|
|
bip39 "github.com/luxfi/go-bip39"
|
|
)
|
|
|
|
// Wallet holds a derived private key and its Ethereum address.
|
|
type Wallet struct {
|
|
Index int
|
|
PrivateKey *ecdsa.PrivateKey
|
|
Address common.Address
|
|
}
|
|
|
|
// DeriveWallets generates N wallets from a BIP-39 mnemonic using the
|
|
// ETH BIP-44 derivation path m/44'/60'/0'/0/{index}.
|
|
// Indices range from startIndex to startIndex+count-1.
|
|
func DeriveWallets(mnemonic string, startIndex, count int) ([]*Wallet, error) {
|
|
if !bip39.IsMnemonicValid(mnemonic) {
|
|
return nil, fmt.Errorf("invalid mnemonic")
|
|
}
|
|
|
|
// Convert mnemonic to seed (no passphrase)
|
|
seed := bip39.NewSeed(mnemonic, "")
|
|
|
|
// Derive master key
|
|
masterKey, err := bip32.NewMasterKey(seed)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("master key derivation failed: %w", err)
|
|
}
|
|
|
|
// BIP-44 path: m/44'/60'/0'/0/{index}
|
|
// Hardened indices use offset 0x80000000
|
|
const hardenedOffset = 0x80000000
|
|
|
|
purpose, err := masterKey.NewChildKey(44 + hardenedOffset) // 44'
|
|
if err != nil {
|
|
return nil, fmt.Errorf("purpose derivation failed: %w", err)
|
|
}
|
|
|
|
coinType, err := purpose.NewChildKey(60 + hardenedOffset) // 60'
|
|
if err != nil {
|
|
return nil, fmt.Errorf("coin type derivation failed: %w", err)
|
|
}
|
|
|
|
account, err := coinType.NewChildKey(0 + hardenedOffset) // 0'
|
|
if err != nil {
|
|
return nil, fmt.Errorf("account derivation failed: %w", err)
|
|
}
|
|
|
|
change, err := account.NewChildKey(0) // 0 (external chain)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("change derivation failed: %w", err)
|
|
}
|
|
|
|
wallets := make([]*Wallet, 0, count)
|
|
for i := startIndex; i < startIndex+count; i++ {
|
|
child, err := change.NewChildKey(uint32(i))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("child key derivation failed for index %d: %w", i, err)
|
|
}
|
|
|
|
// The child key bytes are the raw 32-byte private key
|
|
privKey, err := gethcrypto.ToECDSA(child.Key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ECDSA conversion failed for index %d: %w", i, err)
|
|
}
|
|
|
|
addr := gethcrypto.PubkeyToAddress(privKey.PublicKey)
|
|
|
|
wallets = append(wallets, &Wallet{
|
|
Index: i,
|
|
PrivateKey: privKey,
|
|
Address: addr,
|
|
})
|
|
}
|
|
|
|
return wallets, nil
|
|
}
|
|
|
|
// ShortAddr returns a shortened address string for logging.
|
|
func (w *Wallet) ShortAddr() string {
|
|
s := w.Address.Hex()
|
|
return s[:6] + ".." + s[len(s)-4:]
|
|
}
|
|
|
|
// FundingRequired checks if a wallet needs funding on a given chain.
|
|
// Returns the deficit amount in wei (0 if no funding needed).
|
|
func FundingRequired(balance *big.Int, minBalance *big.Int) *big.Int {
|
|
if balance.Cmp(minBalance) >= 0 {
|
|
return big.NewInt(0)
|
|
}
|
|
deficit := new(big.Int).Sub(minBalance, balance)
|
|
return deficit
|
|
}
|