package wallet import ( "crypto/ecdsa" "fmt" "github.com/synapsecns/sanguine/core" "os" "strings" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/crypto" hdwallet "github.com/miguelmota/go-ethereum-hdwallet" "github.com/tyler-smith/go-bip39" ) // FromPrivateKey creates a new wallet from a private key. func FromPrivateKey(privKey *ecdsa.PrivateKey) (Wallet, error) { var account walletImpl account.privateKey = privKey pubKey := privKey.Public() pubKeyEcdsa, ok := pubKey.(*ecdsa.PublicKey) if !ok { return account, fmt.Errorf("unable to cast public key of type %T to *ecdsa.PublicKey", pubKey) } accountAddr := crypto.PubkeyToAddress(*pubKeyEcdsa) return &walletImpl{ address: accountAddr, privateKey: privKey, publicKey: pubKeyEcdsa, }, nil } // FromKeyFile creates a wallet from a file. The wallet detects if this is a // seed phrase or a private key hex and returns the appropriate wallet. An error is // returned if this cannot be determined. // // Currently, the default derivation path is used. This should be compatible with metamask // and most wallets. It gets the first wallet in this derivation path. // TODO: support json files. func FromKeyFile(keyFile string) (Wallet, error) { //nolint:gosec rawKey, err := os.ReadFile(core.ExpandOrReturnPath(keyFile)) if err != nil { return nil, fmt.Errorf("could not get seed phrase: %w", err) } key := strings.TrimSpace(string(rawKey)) // if it's a mnemonic use that if bip39.IsMnemonicValid(key) { return FromSeedPhrase(key, accounts.DefaultBaseDerivationPath) } return FromHex(key) } // FromHex gets the wallet from the private key. func FromHex(privateKey string) (Wallet, error) { // Check for '0x' prefix and remove it if it exists if len(privateKey) >= 2 && strings.EqualFold(privateKey[:2], "0x") { privateKey = privateKey[2:] } privKey, err := crypto.HexToECDSA(privateKey) if err != nil { return nil, fmt.Errorf("could not decode key: %w", err) } return FromPrivateKey(privKey) } // FromSeedPhrase gets the seed phrase for the wallet. // Note: there seems to be some issue here w/ longer seed phraeses should investigate. func FromSeedPhrase(seedPhrase string, derivationPath accounts.DerivationPath) (Wallet, error) { wallet, err := hdwallet.NewFromMnemonic(seedPhrase) if err != nil { return nil, fmt.Errorf("could not get parse phrase: %w", err) } wallet.SetFixIssue172(true) account, err := wallet.Derive(derivationPath, true) if err != nil { return nil, fmt.Errorf("could not derive account: %w", err) } privKey, err := wallet.PrivateKey(account) if err != nil { return nil, fmt.Errorf("could not get private key: %w", err) } return FromPrivateKey(privKey) } // FromRandom generates a new private key. Note: this should be used for testing only and has not been audited yet. func FromRandom() (Wallet, error) { newSeed, err := hdwallet.NewSeed() if err != nil { return nil, fmt.Errorf("could not generate seed: %w", err) } newWallet, err := hdwallet.NewFromSeed(newSeed) if err != nil { return nil, fmt.Errorf("could not use seed: %w", err) } account, err := newWallet.Derive(accounts.DefaultBaseDerivationPath, true) if err != nil { return nil, fmt.Errorf("could not use derive account at %s: %w", accounts.DefaultBaseDerivationPath, err) } privKey, err := newWallet.PrivateKey(account) if err != nil { return nil, fmt.Errorf("could not get private key: %w", err) } return FromPrivateKey(privKey) }