diff --git a/README.md b/README.md index 4d5485e..65231ed 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,12 @@ or $ docker pull planxthanee/ethereum-wallet-generator:latest ``` +## Modes + +- **[1] Normal Mode** - Generate wallets with mnemonic phrase. (default) +- **[2] Fast Mode** - Generate wallets with a mnemonic phrase, **but less secure**. Use cumulative entropy instead of generating new random entropy(changing only the first or second words of mnemonic phrases). It will generate new random entropy every 2048 wallets. +- **[3] Only Private Key Mode** - Generate wallets with private key only. **This mode is the fastest, but you will not get a mnemonic phrase.** + ## Usage ```console @@ -77,6 +83,7 @@ Usage of ethereum-wallet-generator: -db string set sqlite output file name eg. wallets.db (db file will create in `/db` folder) -c int set concurrency value (default 1) -bit int set number of entropy bits [128 for 12 words, 256 for 24 words] (default 128) + -mode int set mode of wallet generator [1: normal mode, 2: fast mode, 3: only private key mode] -strict bool strict contains mode, resolve only the addresses that contain all the given letters (required contains to use) -contains string show only result that contained with the given letters (support for multiple characters) -prefix string show only result that prefix was matched with the given letters (support for single character) @@ -88,11 +95,11 @@ Usage of ethereum-wallet-generator: ## Benchmark -We've dryrun the generator with 8 concurrents for 60,000 wallets on MacBook Air M1 2020 Memory 16 GB
+We've dryrun the generator on normal mode with 8 concurrents for 60,000 wallets on MacBook Air M1 2020 Memory 16 GB
and got speed up to 6,468.58 wallet/sec. ```console -ethereum-wallet-generator -n 60000 -dryrun -c 8 +ethereum-wallet-generator -n 60000 -dryrun -c 8 -mode 1 ===============ETH Wallet Generator=============== 60000 / 60000 | [██████████████████████████████████████████████████████] | 100.00% | 6469 p/s | resolved: 60000 diff --git a/bip39/bip39.go b/bip39/bip39.go index 93054b4..b084c6a 100644 --- a/bip39/bip39.go +++ b/bip39/bip39.go @@ -10,10 +10,12 @@ package bip39 import ( "crypto/rand" "crypto/sha256" + "crypto/sha512" "strings" "github.com/holiman/uint256" "github.com/pkg/errors" + "golang.org/x/crypto/pbkdf2" ) var ( @@ -88,6 +90,12 @@ func NewMnemonic(entropy []byte) (string, error) { return w.String(), nil } +// NewSeed creates a hashed seed output given a provided string and password. +// No checking is performed to validate that the string provided is a valid mnemonic. +func NewSeed(mnemonic, password string) []byte { + return pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"+password), 2048, 64, sha512.New) +} + // Appends to data the first (len(data) / 32)bits of the result of sha256(data) // abd returns the result as a uint256.Int. // diff --git a/internal/generators/generators.go b/internal/generators/generators.go index d40319a..766ec84 100644 --- a/internal/generators/generators.go +++ b/internal/generators/generators.go @@ -14,7 +14,6 @@ import ( ) type Config struct { - BitSize int AddresValidator func(address string) bool ProgressBar progressbar.ProgressBar DryRun bool @@ -24,16 +23,18 @@ type Config struct { } type Generator struct { - repo repository.Repository - config Config + walletGen wallets.Generator + repo repository.Repository + config Config isShutdown atomic.Bool shutdownSignal chan struct{} shutDownWg sync.WaitGroup } -func New(repo repository.Repository, cfg Config) *Generator { +func New(walletGen wallets.Generator, repo repository.Repository, cfg Config) *Generator { return &Generator{ + walletGen: walletGen, repo: repo, config: cfg, shutdownSignal: make(chan struct{}), @@ -59,14 +60,19 @@ func (g *Generator) Start() (err error) { } if w := g.repo.Result(); len(w) > 0 && !g.config.DryRun { + col2Name := "Seed" var result strings.Builder for _, wallet := range w { - if _, err := fmt.Fprintf(&result, "%-42s %s\n", wallet.Address, wallet.Mnemonic); err != nil { + col2 := wallet.Mnemonic + if wallet.Mnemonic == "" { + col2 = wallet.PrivateKey + col2Name = "Private Key" + } + if _, err := fmt.Fprintf(&result, "%-42s %s\n", wallet.Address, col2); err != nil { continue } } - - fmt.Printf("\n%-42s %s\n", "Address", "Seed") + fmt.Printf("\n%-42s %s\n", "Address", col2Name) fmt.Printf("%-42s %s\n", strings.Repeat("-", 42), strings.Repeat("-", 90)) fmt.Println(result.String()) } @@ -90,7 +96,7 @@ func (g *Generator) Start() (err error) { return } - wallet, err := wallets.NewWallet(g.config.BitSize) + wallet, err := g.walletGen() if err != nil { // Ignore error log.Printf("[ERROR] failed to generate wallet: %+v\n", err) diff --git a/main.go b/main.go index 96c2e3a..4f4c45f 100644 --- a/main.go +++ b/main.go @@ -43,10 +43,11 @@ func main() { fmt.Println("===============ETH Wallet Generator===============") fmt.Println(" ") + // Parse flags number := flag.Int("n", 10, "set number of generate times (not number of result wallets) (set number to 0 for Infinite loop ∞)") limit := flag.Int("limit", 0, "set limit number of result wallets. stop generate when result of vanity wallets reach the limit (set number to 0 for no limit)") dbPath := flag.String("db", "", "set sqlite output name eg. wallets.db (db file will create in /db)") - concurrency := flag.Int("c", 1, "set concurrency value (default 1)") + concurrency := flag.Int("c", 1, "set concurrency value") bits := flag.Int("bit", 128, "set number of entropy bits [128, 256]") strict := flag.Bool("strict", false, "strict contains mode (required contains to use)") contain := flag.String("contains", "", "show only result that contained with the given letters (support for multiple characters)") @@ -55,8 +56,10 @@ func main() { regEx := flag.String("regex", "", "show only result that was matched with given regex (eg. ^0x99 or ^0x00)") isDryrun := flag.Bool("dryrun", false, "generate wallet without a result (used for benchmark speed)") isCompatible := flag.Bool("compatible", false, "logging compatible mode (turn this on to fix logging glitch)") + mode := flag.Int("mode", 1, "wallet generate mode [1: normal mode, 2: fast mode(reduce entropy random times, but less secure), 3: only private key mode(generate only privatekey, this fastest mode)]") flag.Parse() + // Wallet Address Validator r, err := regexp.Compile(*regEx) if err != nil { panic(err) @@ -105,6 +108,7 @@ func main() { *limit = *number } + // Progress bar var bar progressbar.ProgressBar if *isCompatible { bar = progressbar.NewCompatibleProgressBar(*number) @@ -112,6 +116,7 @@ func main() { bar = progressbar.NewStandardProgressBar(*number) } + // Repository var repo repository.Repository switch { case *dbPath != "": @@ -140,10 +145,23 @@ func main() { repo = repository.NewInMemoryRepository() } + // Wallet generator + var walletGenerator wallets.Generator + switch *mode { + case 1: + walletGenerator = wallets.NewGeneratorMnemonic(*bits) + case 2: + panic("Fast mode is not supported yet") + case 3: + walletGenerator = wallets.NewGeneratorPrivatekey() + default: + panic("Invalid mode. See: https://github.com/Planxnx/ethereum-wallet-generator#Modes") + } + generator := generators.New( + walletGenerator, repo, generators.Config{ - BitSize: *bits, AddresValidator: validateAddress, ProgressBar: bar, DryRun: *isDryrun, diff --git a/wallets/mnemonic.go b/wallets/mnemonic.go index 0918cc2..3cfaff4 100644 --- a/wallets/mnemonic.go +++ b/wallets/mnemonic.go @@ -1,10 +1,50 @@ package wallets import ( + "crypto/ecdsa" + + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/ethereum/go-ethereum/accounts" "github.com/pkg/errors" "github.com/planxnx/ethereum-wallet-generator/bip39" ) +var ( + // DefaultBaseDerivationPath is the base path from which custom derivation endpoints + // are incremented. As such, the first account will be at m/44'/60'/0'/0, the second + // at m/44'/60'/0'/1, etc + DefaultBaseDerivationPath = accounts.DefaultBaseDerivationPath + DefaultBaseDerivationPathString = DefaultBaseDerivationPath.String() +) + +// NewGeneratorMnemonic returns a new wallet generator that uses a mnemonic(BIP39) and a derivation path(BIP44) to generate a wallet. +func NewGeneratorMnemonic(bitSize int) Generator { + return func() (*Wallet, error) { + mnemonic, err := NewMnemonic(bitSize) + if err != nil { + return nil, err + } + + privateKey, err := deriveWallet(bip39.NewSeed(mnemonic, ""), DefaultBaseDerivationPath) + if err != nil { + return nil, err + } + + wallet, err := NewFromPrivatekey(privateKey) + if err != nil { + return nil, errors.WithStack(err) + } + + wallet.Bits = bitSize + wallet.Mnemonic = mnemonic + wallet.HDPath = DefaultBaseDerivationPathString + + return wallet, nil + } +} + +// NewMnemonic returns a new mnemonic(BIP39) with the given bit size. func NewMnemonic(bitSize int) (string, error) { entropy, err := bip39.NewEntropy(bitSize) if err != nil { @@ -18,3 +58,25 @@ func NewMnemonic(bitSize int) (string, error) { return mnemonic, nil } + +func deriveWallet(seed []byte, path accounts.DerivationPath) (*ecdsa.PrivateKey, error) { + key, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) + if err != nil { + return nil, errors.WithStack(err) + } + + for _, n := range path { + key, err = key.Derive(n) + if err != nil { + return nil, errors.WithStack(err) + } + } + + privateKey, err := key.ECPrivKey() + privateKeyECDSA := privateKey.ToECDSA() + if err != nil { + return nil, errors.WithStack(err) + } + + return privateKeyECDSA, nil +} diff --git a/wallets/privatekey.go b/wallets/privatekey.go new file mode 100644 index 0000000..c8808ee --- /dev/null +++ b/wallets/privatekey.go @@ -0,0 +1,22 @@ +package wallets + +import ( + "github.com/ethereum/go-ethereum/crypto" + "github.com/pkg/errors" +) + +func NewGeneratorPrivatekey() Generator { + return func() (*Wallet, error) { + privateKey, err := crypto.GenerateKey() + if err != nil { + return nil, errors.WithStack(err) + } + + wallet, err := NewFromPrivatekey(privateKey) + if err != nil { + return nil, errors.WithStack(err) + } + + return wallet, nil + } +} diff --git a/wallets/utils.go b/wallets/utils.go deleted file mode 100644 index 9c38aba..0000000 --- a/wallets/utils.go +++ /dev/null @@ -1,8 +0,0 @@ -package wallets - -import "unsafe" - -// b2s converts a byte slice to a string without memory allocation. -func b2s(b []byte) string { - return *(*string)(unsafe.Pointer(&b)) -} diff --git a/wallets/wallet.go b/wallets/wallet.go index 83252e1..6e88fa3 100644 --- a/wallets/wallet.go +++ b/wallets/wallet.go @@ -3,47 +3,50 @@ package wallets import ( "crypto/ecdsa" "encoding/hex" + "errors" + "unsafe" - "github.com/btcsuite/btcd/btcutil/hdkeychain" - "github.com/btcsuite/btcd/chaincfg" - "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/pkg/errors" - "github.com/tyler-smith/go-bip39" "gorm.io/gorm" ) -var ( - // DefaultBaseDerivationPath is the base path from which custom derivation endpoints - // are incremented. As such, the first account will be at m/44'/60'/0'/0, the second - // at m/44'/60'/0'/1, etc - DefaultBaseDerivationPath = accounts.DefaultBaseDerivationPath - DefaultBaseDerivationPathString = DefaultBaseDerivationPath.String() +type ( + // Generator is a function that generates a wallet. + Generator func() (*Wallet, error) + + // Wallet is a struct that contains the information of a wallet. + Wallet struct { + Address string + PrivateKey string + Mnemonic string + HDPath string + gorm.Model + Bits int + } ) -type Wallet struct { - Address string - PrivateKey string - Mnemonic string - HDPath string - gorm.Model - Bits int -} +const ( + // DefaultMnemonicBits is the default number of bits to use when generating a mnemonic. default is 128 bits (12 words). + DefaultMnemonicBits = 128 +) -func NewWallet(bitSize int) (*Wallet, error) { - mnemonic, err := NewMnemonic(bitSize) - if err != nil { - return nil, err - } +// DefaultGenerator is the default wallet generator. +var DefaultGenerator = NewGeneratorMnemonic(DefaultMnemonicBits) - // TODO: only private key mode for speed up to 100k wallet per second (20x) +// NewWallet returns a new wallet using the default generator. +func NewWallet() (*Wallet, error) { + return DefaultGenerator() +} - privateKey, publicKey, err := deriveWallet(bip39.NewSeed(mnemonic, ""), DefaultBaseDerivationPath) - if err != nil { - return nil, err +// NewFromPrivatekey returns a new wallet from a given private key. +func NewFromPrivatekey(privateKey *ecdsa.PrivateKey) (*Wallet, error) { + if privateKey == nil { + return nil, errors.New("private key is nil") } + publicKey := &privateKey.PublicKey + // toString PrivateKey priveKeyBytes := crypto.FromECDSA(privateKey) privHex := make([]byte, len(priveKeyBytes)*2) @@ -63,30 +66,10 @@ func NewWallet(bitSize int) (*Wallet, error) { return &Wallet{ Address: pubString, PrivateKey: privString, - Mnemonic: mnemonic, - Bits: bitSize, - HDPath: DefaultBaseDerivationPathString, }, nil } -func deriveWallet(seed []byte, path accounts.DerivationPath) (*ecdsa.PrivateKey, *ecdsa.PublicKey, error) { - key, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) - if err != nil { - return nil, nil, errors.WithStack(err) - } - - for _, n := range path { - key, err = key.Derive(n) - if err != nil { - return nil, nil, errors.WithStack(err) - } - } - - privateKey, err := key.ECPrivKey() - privateKeyECDSA := privateKey.ToECDSA() - if err != nil { - return nil, nil, errors.WithStack(err) - } - - return privateKeyECDSA, privateKeyECDSA.Public().(*ecdsa.PublicKey), nil +// b2s converts a byte slice to a string without memory allocation. +func b2s(b []byte) string { + return *(*string)(unsafe.Pointer(&b)) } diff --git a/wallets/utils_test.go b/wallets/wallet_test.go similarity index 100% rename from wallets/utils_test.go rename to wallets/wallet_test.go