From 09b720241baa4f1b2dfc54be819703f5770e3109 Mon Sep 17 00:00:00 2001 From: micovi Date: Mon, 22 Jan 2024 07:55:45 +0200 Subject: [PATCH 1/2] implement EVM addresses and berachain --- .gitignore | 1 + constants.go | 68 +++++++++++++++++ ecsda.go | 109 +++++++++++++++++++++++++++ ecsda_test.go | 146 ++++++++++++++++++++++++++++++++++++ go.mod | 12 ++- go.sum | 21 +++++- main.go | 132 ++++++++++++++++---------------- matcher.go | 186 +++++++++++++++++++++++++++++++++------------- secp256k1.go | 86 +++++++++++++++++++++ secp256k1_test.go | 59 +++++++++++++++ utils.go | 19 ----- wallet.go | 29 ++------ 12 files changed, 703 insertions(+), 165 deletions(-) create mode 100644 constants.go create mode 100644 ecsda.go create mode 100644 ecsda_test.go create mode 100644 secp256k1.go create mode 100644 secp256k1_test.go delete mode 100644 utils.go diff --git a/.gitignore b/.gitignore index 3f790ca..5ec75f6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ ./vanity-forge dist/ +vanity-forge diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..a72917f --- /dev/null +++ b/constants.go @@ -0,0 +1,68 @@ +package main + +type Encryption int64 + +const ( + Undefined Encryption = iota + Secp256k1 + Ethsecp256k1 + ECSDA +) + +type chain struct { + Name string + Prefix string + PrefixFull string + Encryption +} + +type settings struct { + SelectedChain chain // chain selector string or nil + MatcherMode string // starts-with, ends-with, contains + SearchString string // search string + NumAccounts string // number of accounts to generate + RequiredLetters int // number of letters to generate + RequiredDigits int // number of digits to generate +} + +type walletgenerator struct { + GenerateWallet func() wallet +} + +type matcher struct { + Mode string + SearchString string + Chain chain + RequiredLetters int + RequiredDigits int +} + +var ( + AvailableChains = []chain{ + { + Name: "celestia", + Prefix: "celestia", + PrefixFull: "celestia1", + Encryption: Secp256k1, + }, + { + Name: "cosmos", + Prefix: "cosmos", + PrefixFull: "cosmos1", + Encryption: Secp256k1, + }, + { + Name: "dydx", + Prefix: "dydx", + PrefixFull: "dydx1", + Encryption: Secp256k1, + }, + { + Name: "berachain", + Prefix: "0x", + PrefixFull: "0x", + Encryption: ECSDA, + }, + } + MatcherModes = []string{"contains", "starts-with", "ends-with", "regex"} +) diff --git a/ecsda.go b/ecsda.go new file mode 100644 index 0000000..8faf3e6 --- /dev/null +++ b/ecsda.go @@ -0,0 +1,109 @@ +package main + +import ( + "crypto/ecdsa" + "log" + "strings" + + "github.com/ethereum/go-ethereum/crypto" +) + +type ecsdaWallet struct { + Chain chain +} + +// On Ethereum and other networks compatible with the Ethereum Virtual Machine (EVM), public addresses all share the same format: they begin with 0x, and are followed by 40 alphanumeric characters (numerals and letters), adding up to 42 characters in total. They're also not case sensitive. + +// This address is a number, even though it also includes alphabetical characters. This is because the hexadecimal (base 16) system used to generate the address doesn't just use numerals, like our ten-digit decimal system. Instead, the hexadecimal system uses the numerals 0-9 and the letters A-F. This means it has 16 characters at its disposal, hence the name base 16. In computer science and many programming languages, the 0x prefix is used at the start of all hex numbers, as they are known, to differentiate them from decimal values. + +// bech16digits is a constant string representing the valid characters in a Bech16 encoding. +const bech16digits = "0123456789" + +// bech16letters represents the valid characters for a Bech16 encoding. +const bech16letters = "abcdefABCDEF" + +// bech16chars is a constant string that represents the characters used in the bech16 encoding scheme, which includes both digits and letters. +const bech16chars = bech16digits + bech16letters + +// bech16Only checks if the given string contains only characters from the bech16 character set. +func (w ecsdaWallet) bech16Only(s string) bool { + return w.countUnionChars(s, bech16chars) == len(s) +} + +// countUnionChars counts the number of characters in the input string 's' that are present in the 'letterSet'. +// It returns the count of such characters. +func (w ecsdaWallet) countUnionChars(s string, letterSet string) int { + count := 0 + for _, char := range s { + if strings.Contains(letterSet, string(char)) { + count++ + } + } + return count +} + +// CheckRequiredDigits checks if the given candidate string has the required number of digits. +// It counts the number of union characters between the candidate string and the bech16digits string. +// If the count is less than the required number, it returns false; otherwise, it returns true. +func (w ecsdaWallet) CheckRequiredDigits(candidate string, required int) bool { + if w.countUnionChars(candidate, bech16digits) < required { + return false + } + + return true +} + +// CheckRequiredLetters checks if a candidate string contains the required number of union characters. +// It returns true if the candidate string meets the requirement, otherwise false. +func (w ecsdaWallet) CheckRequiredLetters(candidate string, required int) bool { + if w.countUnionChars(candidate, bech16letters) < required { + return false + } + + return true +} + +// ValidateInput validates the input string based on the specified criteria. +// It checks if the input string contains bech16 incompatible characters, +// if it exceeds the maximum length of 40 characters, +// and if the required number of letters and digits are non-negative and do not exceed 40. +// It returns a slice of error messages indicating the validation errors, if any. +func (w ecsdaWallet) ValidateInput(SearchString string, RequiredLetters int, RequiredDigits int) []string { + var errs []string + if !w.bech16Only(SearchString) { + errs = append(errs, "ERROR: "+SearchString+" contains bech16 incompatible characters") + } + if len(SearchString) > 40 { + errs = append(errs, "ERROR: "+SearchString+" is too long. Must be max 40 characters.") + } + if RequiredDigits < 0 || RequiredLetters < 0 { + errs = append(errs, "ERROR: Can't require negative amount of characters.") + } + if RequiredDigits+RequiredLetters > 40 { + errs = append(errs, "ERROR: Can't require more than 40 characters.") + } + return errs +} + +// GenerateWallet generates a new wallet by generating a private key and deriving the corresponding public key and address. +// It returns a wallet struct containing the address, public key, and private key bytes. +func (w ecsdaWallet) GenerateWallet() wallet { + privateKey, err := crypto.GenerateKey() + if err != nil { + log.Fatal(err) + } + + privateKeyBytes := crypto.FromECDSA(privateKey) + + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + log.Fatal("cannot assert type: publicKey is not of type *ecdsa.PublicKey") + } + + publicKeyBytes := crypto.FromECDSAPub(publicKeyECDSA) + + address := crypto.PubkeyToAddress(*publicKeyECDSA).Hex() + + return wallet{address, publicKeyBytes, privateKeyBytes} +} diff --git a/ecsda_test.go b/ecsda_test.go new file mode 100644 index 0000000..821d42a --- /dev/null +++ b/ecsda_test.go @@ -0,0 +1,146 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEcsdaWallet_GenerateWallet(t *testing.T) { + wallet := ecsdaWallet{} + w := wallet.GenerateWallet() + assert.NotEmpty(t, w.Address) + assert.NotNil(t, w.PublicKey) + assert.NotNil(t, w.PrivateKey) +} +func TestEcsdaWallet_CountUnionChars(t *testing.T) { + wallet := ecsdaWallet{} + + // Test case 1: Counting union characters in a string with valid letter set + s1 := "abcdef123456" + letterSet1 := "abc123" + expectedCount1 := 6 + assert.Equal(t, expectedCount1, wallet.countUnionChars(s1, letterSet1), "Expected count of 6 for valid letter set") + + // Test case 2: Counting union characters in a string with empty letter set + s2 := "abcdef123456" + letterSet2 := "" + expectedCount2 := 0 + assert.Equal(t, expectedCount2, wallet.countUnionChars(s2, letterSet2), "Expected count of 0 for empty letter set") + + // Test case 3: Counting union characters in an empty string with valid letter set + s3 := "" + letterSet3 := "abc123" + expectedCount3 := 0 + assert.Equal(t, expectedCount3, wallet.countUnionChars(s3, letterSet3), "Expected count of 0 for empty string") + + // Test case 4: Counting union characters in a string with invalid letter set + s4 := "abcdef123456" + letterSet4 := "!@#$%" + expectedCount4 := 0 + assert.Equal(t, expectedCount4, wallet.countUnionChars(s4, letterSet4), "Expected count of 0 for invalid letter set") +} +func TestEcsdaWallet_Bech16Only(t *testing.T) { + wallet := ecsdaWallet{} + + // Test case 1: All characters are valid bech16 characters + s1 := "abcdef123456" + assert.True(t, wallet.bech16Only(s1), "Expected true for valid bech16 characters") + + // Test case 2: Some characters are not valid bech16 characters + s2 := "abcde!@#$%" + assert.False(t, wallet.bech16Only(s2), "Expected false for invalid bech16 characters") + + // Test case 3: Empty string + s3 := "" + assert.True(t, wallet.bech16Only(s3), "Expected true for empty string") + + // Test case 4: String with valid bech16 characters and additional characters + s4 := "abcdef123456!" + assert.False(t, wallet.bech16Only(s4), "Expected false for string with additional characters") +} +func TestEcsdaWallet_CheckRequiredDigits(t *testing.T) { + wallet := ecsdaWallet{} + + // Test case 1: Candidate string has less required digits + candidate1 := "abcdef123456" + required1 := 7 + assert.False(t, wallet.CheckRequiredDigits(candidate1, required1), "Expected false for candidate string with less required digits") + + // Test case 2: Candidate string has exact required digits + candidate2 := "abcdef123456" + required2 := 6 + assert.True(t, wallet.CheckRequiredDigits(candidate2, required2), "Expected true for candidate string with exact required digits") + + // Test case 3: Candidate string has more than required digits + candidate3 := "abcdef123456" + required3 := 5 + assert.True(t, wallet.CheckRequiredDigits(candidate3, required3), "Expected true for candidate string with more than required digits") + + // Test case 4: Empty candidate string + candidate4 := "" + required4 := 3 + assert.False(t, wallet.CheckRequiredDigits(candidate4, required4), "Expected false for empty candidate string") +} +func TestEcsdaWallet_CheckRequiredLetters(t *testing.T) { + wallet := ecsdaWallet{} + + // Test case 1: Candidate string has enough required letters + candidate1 := "abcdef123" + required1 := 5 + assert.True(t, wallet.CheckRequiredLetters(candidate1, required1), "Expected true for candidate string with enough required letters") + + // Test case 2: Candidate string does not have enough required letters + candidate2 := "abc123" + required2 := 10 + assert.False(t, wallet.CheckRequiredLetters(candidate2, required2), "Expected false for candidate string without enough required letters") + + // Test case 3: Candidate string is empty + candidate3 := "" + required3 := 5 + assert.False(t, wallet.CheckRequiredLetters(candidate3, required3), "Expected false for empty candidate string") + + // Test case 4: Required letters is 0 + candidate4 := "abc123" + required4 := 0 + assert.True(t, wallet.CheckRequiredLetters(candidate4, required4), "Expected true for required letters equal to 0") +} + +func TestEcsdaWallet_ValidateInput(t *testing.T) { + wallet := ecsdaWallet{} + + // Test case 1: Valid input + searchString1 := "abcdef123456" + requiredLetters1 := 6 + requiredDigits1 := 6 + expectedErrs1 := []string(nil) + assert.Equal(t, expectedErrs1, wallet.ValidateInput(searchString1, requiredLetters1, requiredDigits1), "Expected no errors for valid input") + + // Test case 2: Invalid bech16 characters + searchString2 := "abcde!@#$%" + requiredLetters2 := 6 + requiredDigits2 := 6 + expectedErrs2 := []string{"ERROR: abcde!@#$% contains bech16 incompatible characters"} + assert.Equal(t, expectedErrs2, wallet.ValidateInput(searchString2, requiredLetters2, requiredDigits2), "Expected error for invalid bech16 characters") + + // Test case 3: String too long + searchString3 := "abcdef123456abcdef123456abcdef123456abcdef1234567" + requiredLetters3 := 6 + requiredDigits3 := 6 + expectedErrs3 := []string{"ERROR: abcdef123456abcdef123456abcdef123456abcdef1234567 is too long. Must be max 40 characters."} + assert.Equal(t, expectedErrs3, wallet.ValidateInput(searchString3, requiredLetters3, requiredDigits3), "Expected error for string too long") + + // Test case 4: Negative required characters + searchString4 := "abcdef123456" + requiredLetters4 := -1 + requiredDigits4 := 6 + expectedErrs4 := []string{"ERROR: Can't require negative amount of characters."} + assert.Equal(t, expectedErrs4, wallet.ValidateInput(searchString4, requiredLetters4, requiredDigits4), "Expected error for negative required characters") + + // Test case 5: Required characters exceed string length + searchString5 := "abcdef123456abcdef123456abcdef123456abcdef123456abcdef123456abcdef123456" + requiredLetters5 := 10 + requiredDigits5 := 10 + expectedErrs5 := []string{"ERROR: abcdef123456abcdef123456abcdef123456abcdef123456abcdef123456abcdef123456 is too long. Must be max 40 characters."} + assert.Equal(t, expectedErrs5, wallet.ValidateInput(searchString5, requiredLetters5, requiredDigits5), "Expected error for required characters exceeding string length") +} diff --git a/go.mod b/go.mod index 9a06752..43c2fd1 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,9 @@ require ( github.com/charmbracelet/huh v0.2.3 github.com/charmbracelet/huh/spinner v0.0.0-20240108162426-58163e7b5b2f github.com/cosmos/cosmos-sdk v0.50.3 + github.com/ethereum/go-ethereum v1.13.10 github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.8.4 github.com/tendermint/tendermint v0.35.9 golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 ) @@ -19,6 +21,7 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/btcsuite/btcd v0.22.1 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/catppuccin/go v0.2.0 // indirect github.com/charmbracelet/bubbles v0.17.1 // indirect github.com/charmbracelet/bubbletea v0.25.0 // indirect @@ -26,8 +29,11 @@ require ( github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/cosmos/btcutil v1.0.5 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/dlclark/regexp2 v1.10.0 // indirect github.com/gorilla/css v1.0.0 // indirect + github.com/holiman/uint256 v1.2.4 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect @@ -39,14 +45,16 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/yuin/goldmark v1.6.0 // indirect github.com/yuin/goldmark-emoji v1.0.2 // indirect - golang.org/x/crypto v0.16.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/net v0.19.0 // indirect - golang.org/x/sync v0.4.0 // indirect + golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/term v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 0589e3e..21f480d 100644 --- a/go.sum +++ b/go.sum @@ -148,6 +148,8 @@ github.com/breml/errchkjson v0.3.0/go.mod h1:9Cogkyv9gcT8HREpzi3TiqBxCqDzo8awa92 github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= @@ -245,6 +247,10 @@ github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= @@ -282,6 +288,8 @@ github.com/envoyproxy/protoc-gen-validate v0.0.14/go.mod h1:iSmxcyjqTsJpI2R4NaDN github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= +github.com/ethereum/go-ethereum v1.13.10 h1:Ppdil79nN+Vc+mXfge0AuUgmKWuVv4eMqzoIVSdqZek= +github.com/ethereum/go-ethereum v1.13.10/go.mod h1:sc48XYQxCzH3fG9BcrXCOOgQk2JfZzNAmIKnceogzsA= github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= @@ -528,6 +536,8 @@ github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKEN github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= +github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= @@ -597,6 +607,7 @@ github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= github.com/kunwardeep/paralleltest v1.0.6/go.mod h1:Y0Y0XISdZM5IKm3TREQMZ6iteqn1YuwCsJO/0kL9Zes= @@ -718,6 +729,7 @@ github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1t github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.8.1/go.mod h1:qj+zJJUgJ76tR92+25+03oYUhzF4R7/2Wk7fGTfCHmg= github.com/nishanths/predeclared v0.0.0-20190419143655-18a43bb90ffc/go.mod h1:62PewwiQTlm/7Rj+cxVYqZvDIUc+JjZq6GHAC1fsObQ= @@ -1072,8 +1084,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= -golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1224,8 +1236,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= -golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1679,6 +1691,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= diff --git a/main.go b/main.go index 1ffd5e3..d99de10 100644 --- a/main.go +++ b/main.go @@ -14,30 +14,17 @@ import ( "github.com/spf13/pflag" ) -var ( - AvailableChains = []string{"cosmos", "celestia", "dydx"} - MatcherModes = []string{"contains", "starts-with", "ends-with", "regex"} -) - -type settings struct { - Encryption string // secp256k1 or ethsecp256k1 - SelectedChain string // chain selector string or nil - MatcherMode string // starts-with, ends-with, contains - SearchString string // search string - NumAccounts string // number of accounts to generate - RequiredLetters int // number of letters to generate - RequiredDigits int // number of digits to generate -} - func main() { // Defined flags var accountsNumber = pflag.IntP("accounts-number", "n", 0, "Amount of accounts you need") var matcherMode = pflag.StringP("mode", "m", "", "Matcher mode (contains, starts-with, ends-with, regex)") var searchString = pflag.StringP("search", "s", "", "Search string") - var chain = pflag.StringP("chain", "c", "", "Chain selector string") + var chainflag = pflag.StringP("chain", "c", "", "Chain selector string") + var verbose = pflag.BoolP("verbose", "v", false, "Verbose output") + + // Extra flags var letters = pflag.IntP("letters", "l", 0, "Amount of letters (a-z) that the address must contain") var digits = pflag.IntP("digits", "d", 0, "Amount of digits (0-9) that the address must contain") - var verbose = pflag.BoolP("verbose", "v", false, "Verbose output") // Parse flags pflag.Parse() @@ -51,35 +38,55 @@ func main() { } // Validate chain flag - if *chain != "" { - if !slices.Contains(AvailableChains, *chain) { - fmt.Println("ERROR: Invalid chain. Must be one of the available chains. (cross-vanity --help for more info)") + var selectedChain chain = chain{} + + if *chainflag != "" { + var chainnamesslice []string = make([]string, len(AvailableChains)) + for i, chain := range AvailableChains { + chainnamesslice[i] = chain.Name + } + + if !slices.Contains(chainnamesslice, *chainflag) { + fmt.Println("ERROR: Invalid chain. Must be one of the available chains: ", chainnamesslice) os.Exit(1) } - } - // Validate letters flag - if *letters < 0 || *letters > 38 { - fmt.Println("ERROR: Invalid letters. Must be between 0 and 38.") - os.Exit(1) + for i, chain := range AvailableChains { + if chain.Name == *chainflag { + selectedChain = AvailableChains[i] + } + } } - // Validate digits flag - if *digits < 0 || *digits > 38 { - fmt.Println("ERROR: Invalid digits. Must be between 0 and 38.") - os.Exit(1) + if selectedChain.Encryption == Secp256k1 { + // Validate letters flag + if *letters < 0 || *letters > 38 { + fmt.Println("ERROR: Invalid letters. Must be between 0 and 38.") + os.Exit(1) + } + + // Validate digits flag + if *digits < 0 || *digits > 38 { + fmt.Println("ERROR: Invalid digits. Must be between 0 and 38.") + os.Exit(1) + } + + // Letters + Digits must be less than 38 + if *letters+*digits > 38 { + fmt.Println("ERROR: Letters + Digits must be less than 38.") + os.Exit(1) + } } - // Letters + Digits must be less than 38 - if *letters+*digits > 38 { - fmt.Println("ERROR: Letters + Digits must be less than 38.") - os.Exit(1) + // Prompt user for missing settings on chain + selectChainOptions := make([]huh.Option[chain], len(AvailableChains)) + for i, chain := range AvailableChains { + selectChainOptions[i] = huh.NewOption(chain.Name, chain) } // Create settings struct with details from flags settings := settings{ - Encryption: "secp256k1", - SelectedChain: *chain, + SelectedChain: selectedChain, MatcherMode: *matcherMode, SearchString: *searchString, NumAccounts: strconv.Itoa(*accountsNumber), @@ -87,26 +94,8 @@ func main() { RequiredDigits: *digits, } - // Prompt user for missing settings on encryption - // TODO add ethsecp256k1 - if settings.Encryption == "" { - huh.NewSelect[string](). - Title("Select encryption"). - Options( - huh.NewOption("secp256k1", "secp256k1"), - ). - Value(&settings.Encryption). - Run() - } - - // Prompt user for missing settings on chain - selectChainOptions := make([]huh.Option[string], len(AvailableChains)) - for i, chain := range AvailableChains { - selectChainOptions[i] = huh.NewOption(chain, chain) - } - - if settings.SelectedChain == "" { - huh.NewSelect[string](). + if settings.SelectedChain == (chain{}) { + huh.NewSelect[chain](). Title("Select Chain"). Options(selectChainOptions...). Value(&settings.SelectedChain). @@ -157,7 +146,8 @@ func main() { RequiredDigits: *&settings.RequiredDigits, } - matcherValidationErrs := m.ValidationErrors() + matcherValidationErrs := m.ValidateInput() + if len(matcherValidationErrs) > 0 { for i := 0; i < len(matcherValidationErrs); i++ { fmt.Println(matcherValidationErrs[i]) @@ -172,6 +162,27 @@ func main() { os.Exit(1) } + // Print out settings + if *verbose == true { + fmt.Println("Matcher Mode: " + *&settings.MatcherMode) + fmt.Println("Search String: " + *&settings.SearchString) + fmt.Println("Number of Accounts to Generate: " + *&settings.NumAccounts) + fmt.Println("Selected Chain: ") + fmt.Println(" Name: " + *&settings.SelectedChain.Name) + fmt.Println(" Prefix: " + *&settings.SelectedChain.Prefix) + fmt.Println(" Encryption: ") + + switch *&settings.SelectedChain.Encryption { + case Secp256k1: + fmt.Println(" Secp256k1") + case Ethsecp256k1: + fmt.Println(" Ethsecp256k1") + case ECSDA: + fmt.Println(" ECSDA") + } + + } + action := func() { for i := 0; i < NumAccountsInt; i++ { // TODO limit CPU cores by flag @@ -182,15 +193,6 @@ func main() { } } - // Print out settings - if *verbose == true { - fmt.Println("Encryption: " + *&settings.Encryption) - fmt.Println("Selected Chain: " + *&settings.SelectedChain) - fmt.Println("Matcher Mode: " + *&settings.MatcherMode) - fmt.Println("Search String: " + *&settings.SearchString) - fmt.Println("Number of Accounts to Generate: " + *&settings.NumAccounts) - } - spinerr := spinner.New(). Type(spinner.Meter). Action(action). diff --git a/matcher.go b/matcher.go index 5e145b8..24a94f8 100644 --- a/matcher.go +++ b/matcher.go @@ -5,37 +5,9 @@ import ( "strings" ) -type matcher struct { - Mode string - SearchString string - Chain string - RequiredLetters int - RequiredDigits int -} - -// The Bech32 alphabet contains 32 characters, including lowercase letters a-z and the numbers 0-9, excluding the number 1 and the letters 'b', 'i', 'o' to avoid reader confusion. - -const bech32digits = "023456789" -const bech32letters = "acdefghjklmnpqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ" - -// This is alphanumeric chars minus chars "1", "b", "i", "o" (case insensitive) -const bech32chars = bech32digits + bech32letters - -func bech32Only(s string) bool { - return countUnionChars(s, bech32chars) == len(s) -} - -func countUnionChars(s string, letterSet string) int { - count := 0 - for _, char := range s { - if strings.Contains(letterSet, string(char)) { - count++ - } - } - return count -} - -func (m matcher) MatchDefault(candidate string) bool { +// MatchWithMode matches the candidate string with the specified mode in the matcher. +// It returns true if the candidate matches the mode, otherwise false. +func (m matcher) MatchWithMode(candidate string) bool { switch m.Mode { case "contains": return strings.Contains(candidate, m.SearchString) @@ -54,49 +26,155 @@ func (m matcher) MatchDefault(candidate string) bool { } } +// Match checks if the candidate string matches the criteria specified in the matcher. +// It trims the prefix from the candidate, checks the required amount of digits and letters, +// and then calls MatchWithMode to perform the matching based on the mode. +// It returns true if the candidate matches the criteria, otherwise false. func (m matcher) Match(candidate string) bool { - // Get chain prefix - prefix := prefixFromChain(m.Chain) - // Trim prefix from candidate - candidate = strings.TrimPrefix(candidate, prefix) + candidate = strings.TrimPrefix(candidate, m.Chain.PrefixFull) - // Check if candidate contains required amount of digits - if countUnionChars(candidate, bech32digits) < m.RequiredDigits { + if !m.CheckRequiredDigits(candidate, m.RequiredDigits) { return false } - // Check if candidate contains required amount of letters - if countUnionChars(candidate, bech32letters) < m.RequiredLetters { + if !m.CheckRequiredLetters(candidate, m.RequiredLetters) { return false } - return m.MatchDefault(candidate) + return m.MatchWithMode(candidate) } -func (m matcher) ValidationErrors() []string { - var errs []string - if !bech32Only(m.SearchString) { - errs = append(errs, "ERROR: SearchString contains bech32 incompatible characters") +// ValidateInput validates the input parameters of the matcher and returns any validation errors. +// It dynamically selects the appropriate generator based on the encryption type in the chain, +// and then calls the generator's ValidateInput method. +// It returns a slice of validation error messages. +func (m matcher) ValidateInput() []string { + var generatorValidate func(SearchString string, RequiredLetters, RequiredDigits int) []string + + switch m.Chain.Encryption { + case Secp256k1: + var secp256k1generator = secp256k1Wallet{ + Chain: m.Chain, + } + + generatorValidate = secp256k1generator.ValidateInput + case ECSDA: + var ecsdagenerator = ecsdaWallet{ + Chain: m.Chain, + } + + generatorValidate = ecsdagenerator.ValidateInput + default: + var secp256k1generator = secp256k1Wallet{ + Chain: m.Chain, + } + generatorValidate = secp256k1generator.ValidateInput } - if len(m.SearchString) > 38 { - errs = append(errs, "ERROR: SearchString is too long. Must be max 38 characters.") + + return generatorValidate(m.SearchString, m.RequiredLetters, m.RequiredDigits) +} + +// CheckRequiredDigits checks if the candidate string contains the required amount of digits. +// It dynamically selects the appropriate generator based on the encryption type in the chain, +// and then calls the generator's CheckRequiredDigits method. +// It returns true if the candidate contains the required amount of digits, otherwise false. +func (m matcher) CheckRequiredDigits(candidate string, required int) bool { + var gcrd func(candidate string, required int) bool + + switch m.Chain.Encryption { + case Secp256k1: + var secp256k1generator = secp256k1Wallet{ + Chain: m.Chain, + } + + gcrd = secp256k1generator.CheckRequiredDigits + case ECSDA: + var ecsdagenerator = ecsdaWallet{ + Chain: m.Chain, + } + + gcrd = ecsdagenerator.CheckRequiredDigits + default: + var secp256k1generator = secp256k1Wallet{ + Chain: m.Chain, + } + gcrd = secp256k1generator.CheckRequiredDigits } - if m.RequiredDigits < 0 || m.RequiredLetters < 0 { - errs = append(errs, "ERROR: Can't require negative amount of characters") + + return gcrd(candidate, required) +} + +// CheckRequiredLetters checks if the candidate string contains the required amount of letters. +// It dynamically selects the appropriate generator based on the encryption type in the chain, +// and then calls the generator's CheckRequiredLetters method. +// It returns true if the candidate contains the required amount of letters, otherwise false. +func (m matcher) CheckRequiredLetters(candidate string, required int) bool { + var gcrl func(candidate string, required int) bool + + switch m.Chain.Encryption { + case Secp256k1: + var secp256k1generator = secp256k1Wallet{ + Chain: m.Chain, + } + + gcrl = secp256k1generator.CheckRequiredLetters + case ECSDA: + var ecsdagenerator = ecsdaWallet{ + Chain: m.Chain, + } + + gcrl = ecsdagenerator.CheckRequiredLetters + default: + var secp256k1generator = secp256k1Wallet{ + Chain: m.Chain, + } + gcrl = secp256k1generator.CheckRequiredLetters } - if m.RequiredDigits+m.RequiredLetters > 38 { - errs = append(errs, "ERROR: Can't require more than 38 characters") + + return gcrl(candidate, required) +} + +// GenerateWallet generates a wallet based on the encryption type in the chain. +// It dynamically selects the appropriate generator based on the encryption type in the chain, +// and then calls the generator's GenerateWallet method. +// It returns the generated wallet. +func (m matcher) GenerateWallet() wallet { + var generate func() wallet + + switch m.Chain.Encryption { + case Secp256k1: + var secp256k1generator = secp256k1Wallet{ + Chain: m.Chain, + } + + generate = secp256k1generator.GenerateWallet + case ECSDA: + var ecsdagenerator = ecsdaWallet{ + Chain: m.Chain, + } + + generate = ecsdagenerator.GenerateWallet + default: + var secp256k1generator = secp256k1Wallet{ + Chain: m.Chain, + } + generate = secp256k1generator.GenerateWallet } - return errs + + return generate() } +// findMatchingWallets finds matching wallets based on the matcher criteria and sends them to the channel. +// It runs in a loop until the quit signal is received. +// It generates a wallet using the GenerateWallet method and checks if it matches the criteria using the Match method. +// If a match is found, it sends the wallet to the channel. func findMatchingWallets(ch chan wallet, quit chan struct{}, m matcher) { for { select { case <-quit: return default: - w := generateWallet(m.Chain) + w := m.GenerateWallet() if m.Match(w.Address) { // Do a non-blocking write instead of simple `ch <- w` to prevent // blocking when it's time to quit and ch is full. @@ -109,6 +187,10 @@ func findMatchingWallets(ch chan wallet, quit chan struct{}, m matcher) { } } +// findMatchingWalletConcurrent finds a matching wallet concurrently using multiple goroutines. +// It creates a channel for sending and receiving wallets, and a quit channel for signaling the goroutines to stop. +// It spawns the specified number of goroutines, each running the findMatchingWallets function. +// It returns the first matching wallet received from the channel. func findMatchingWalletConcurrent(m matcher, goroutines int) wallet { ch := make(chan wallet) quit := make(chan struct{}) diff --git a/secp256k1.go b/secp256k1.go new file mode 100644 index 0000000..27f4234 --- /dev/null +++ b/secp256k1.go @@ -0,0 +1,86 @@ +package main + +import ( + "strings" + + "github.com/cosmos/cosmos-sdk/types/bech32" + "github.com/tendermint/tendermint/crypto/secp256k1" +) + +// secp256k1Wallet represents a secp256k1 wallet. +type secp256k1Wallet struct { + Chain chain +} + +// bech32digits represents the digits allowed in the Bech32 alphabet. +const bech32digits = "023456789" + +// bech32letters represents the letters allowed in the Bech32 alphabet. +const bech32letters = "acdefghjklmnpqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ" + +// bech32chars represents the alphanumeric characters allowed in the Bech32 alphabet. +const bech32chars = bech32digits + bech32letters + +// bech32Only checks if a string contains only characters from the Bech32 alphabet. +func (w secp256k1Wallet) bech32Only(s string) bool { + return w.countUnionChars(s, bech32chars) == len(s) +} + +// countUnionChars counts the number of characters in a string that are present in a given letter set. +func (w secp256k1Wallet) countUnionChars(s string, letterSet string) int { + count := 0 + for _, char := range s { + if strings.Contains(letterSet, string(char)) { + count++ + } + } + return count +} + +// CheckRequiredDigits checks if a candidate string contains the required number of digits. +func (w secp256k1Wallet) CheckRequiredDigits(candidate string, required int) bool { + if w.countUnionChars(candidate, bech32digits) < required { + return false + } + return true +} + +// CheckRequiredLetters checks if a candidate string contains the required number of letters. +func (w secp256k1Wallet) CheckRequiredLetters(candidate string, required int) bool { + if w.countUnionChars(candidate, bech32letters) < required { + return false + } + return true +} + +// ValidateInput validates the search string, required letters, and required digits. +// It returns a list of errors encountered during validation. +func (w secp256k1Wallet) ValidateInput(SearchString string, RequiredLetters int, RequiredDigits int) []string { + var errs []string + if !w.bech32Only(SearchString) { + errs = append(errs, "ERROR: "+SearchString+" contains bech32 incompatible characters.") + } + if len(SearchString) > 38 { + errs = append(errs, "ERROR: "+SearchString+" is too long. Must be max 38 characters.") + } + if RequiredDigits < 0 || RequiredLetters < 0 { + errs = append(errs, "ERROR: Can't require negative amount of characters.") + } + if RequiredDigits+RequiredLetters > 38 { + errs = append(errs, "ERROR: Can't require more than 38 characters.") + } + + return errs +} + +// GenerateWallet generates a new secp256k1 wallet. +func (w secp256k1Wallet) GenerateWallet() wallet { + var privkey secp256k1.PrivKey = secp256k1.GenPrivKey() + var pubkey secp256k1.PubKey = privkey.PubKey().(secp256k1.PubKey) + bech32Addr, err := bech32.ConvertAndEncode(w.Chain.Prefix, pubkey.Address()) + if err != nil { + panic(err) + } + + return wallet{bech32Addr, pubkey, privkey} +} diff --git a/secp256k1_test.go b/secp256k1_test.go new file mode 100644 index 0000000..a3c9ed7 --- /dev/null +++ b/secp256k1_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSecp256k1Wallet_Bech32Only(t *testing.T) { + wallet := secp256k1Wallet{} + assert.True(t, wallet.bech32Only("acde")) + assert.True(t, wallet.bech32Only("023456789")) + assert.True(t, wallet.bech32Only("acdefghjklmnpqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ")) + assert.False(t, wallet.bech32Only("abcde!")) + assert.False(t, wallet.bech32Only("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")) + assert.False(t, wallet.bech32Only("abcde")) +} + +func TestSecp256k1Wallet_CheckRequiredDigits(t *testing.T) { + wallet := secp256k1Wallet{} + assert.True(t, wallet.CheckRequiredDigits("12345", 3)) + assert.False(t, wallet.CheckRequiredDigits("12345", 6)) +} + +func TestSecp256k1Wallet_CheckRequiredLetters(t *testing.T) { + wallet := secp256k1Wallet{} + assert.True(t, wallet.CheckRequiredLetters("abcde", 2)) + assert.False(t, wallet.CheckRequiredLetters("abcde", 6)) +} + +func TestSecp256k1Wallet_ValidateInput(t *testing.T) { + wallet := secp256k1Wallet{} + errs := wallet.ValidateInput("acde", 2, 3) + assert.Empty(t, errs) + + errs = wallet.ValidateInput("abcde!", 2, 3) + assert.NotEmpty(t, errs) + assert.Contains(t, errs[0], "bech32 incompatible characters") + + errs = wallet.ValidateInput("acdefgacdefghjklmnpqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ", 2, 3) + assert.NotEmpty(t, errs) + assert.Contains(t, errs[0], "is too long") + + errs = wallet.ValidateInput("acde", -2, 3) + assert.NotEmpty(t, errs) + assert.Contains(t, errs[0], "Can't require negative amount of characters") + + errs = wallet.ValidateInput("acde", 10, 30) + assert.NotEmpty(t, errs) + assert.Contains(t, errs[0], "Can't require more than 38 characters") +} + +func TestSecp256k1Wallet_GenerateWallet(t *testing.T) { + wallet := secp256k1Wallet{} + w := wallet.GenerateWallet() + assert.NotEmpty(t, w.Address) + assert.NotNil(t, w.PublicKey) + assert.NotNil(t, w.PrivateKey) +} diff --git a/utils.go b/utils.go deleted file mode 100644 index acf9fdd..0000000 --- a/utils.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -func prefixFromChain(chain string) string { - switch chain { - case "celestia": - return "celestia1" - default: - return chain + "1" - } -} - -func internalPrefixFromChain(chain string) string { - switch chain { - case "celestia": - return "celestia" - default: - return chain - } -} diff --git a/wallet.go b/wallet.go index 845ea6c..3c72ce0 100644 --- a/wallet.go +++ b/wallet.go @@ -1,32 +1,15 @@ package main -import ( - "encoding/hex" - - "github.com/cosmos/cosmos-sdk/types/bech32" - "github.com/tendermint/tendermint/crypto/secp256k1" -) +import "encoding/hex" type wallet struct { - Address string - Pubkey []byte - Privkey []byte + Address string + PublicKey []byte + PrivateKey []byte } func (w wallet) String() string { - return "Private key:\t" + hex.EncodeToString(w.Privkey) + "\n" + - "Public key:\t" + hex.EncodeToString(w.Pubkey) + "\n" + + return "Private key:\t" + hex.EncodeToString(w.PrivateKey) + "\n" + + "Public key:\t" + hex.EncodeToString(w.PublicKey) + "\n" + "Address:\t" + w.Address } - -func generateWallet(chain string) wallet { - var prefix = internalPrefixFromChain(chain) - var privkey secp256k1.PrivKey = secp256k1.GenPrivKey() - var pubkey secp256k1.PubKey = privkey.PubKey().(secp256k1.PubKey) - bech32Addr, err := bech32.ConvertAndEncode(prefix, pubkey.Address()) - if err != nil { - panic(err) - } - - return wallet{bech32Addr, pubkey, privkey} -} From 66b67c9e8153c803c8e79b34e532e0a072258071 Mon Sep 17 00:00:00 2001 From: micovi Date: Mon, 22 Jan 2024 08:01:21 +0200 Subject: [PATCH 2/2] update readme --- readme.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 8cd051a..f985b47 100644 --- a/readme.md +++ b/readme.md @@ -1,7 +1,7 @@ # VanityForge ![VanityForge](https://vhs.charm.sh/vhs-328VUMdxvRha1adlp4fTx3.gif) ## Introduction -VanityForge is a powerful CLI tool designed for generating Tendermint Chains vanity addresses with efficiency and ease. It supports multiple networks offering a wide range of customization options for address generation. +VanityForge is a powerful CLI tool designed for generating blockchain Vanity addresses with efficiency and ease. It supports multiple networks offering a wide range of customization options for address generation. (See Supported Chains) ## Key Features - **Generate Bech32 Vanity Addresses**: Create personalized addresses with specific patterns. @@ -9,6 +9,7 @@ VanityForge is a powerful CLI tool designed for generating Tendermint Chains van - **Customizable Address Patterns**: Specify substrings for addresses to start with, end with, or contain. - **Minimum Character Requirements**: Set required minimum letters or digits in addresses. - **Cross-Platform Compatibility**: Binaries available for Linux, macOS, and Windows. +- **Generate Bech16 EVM Vanity Addresses** ## Getting Started @@ -43,6 +44,12 @@ Usage of ./vanity-forge: ``` ![Advanced demo](https://vhs.charm.sh/vhs-2v4VLUIOfeCaiu8Lz4OpU3.gif) +## Supported Chains +- Cosmos +- Celestia +- dYdX +- Berachain + ## License This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.