diff --git a/crypto.go b/crypto.go index f4f758f..97cacab 100644 --- a/crypto.go +++ b/crypto.go @@ -1,80 +1,121 @@ package faker +import ( + "strings" +) + // Crypto is a faker struct for generating bitcoin data type Crypto struct { Faker *Faker } var ( - bitcoinAddresses = []string{ - "1JyxpLZzvYP2TyXaQV3J3vwajJz4hbxtRC", - "1JyVFJVUNx8RQrjyNCGDe7BQ62wPyxU8bC", - "1JVcPDeBfGP5PmNZwnJSfms7hKLncMSenV", - "1FwiYqdgLH6w5XdB9QXgZGi7ZHGiyUYucT", - "1H3nknk2Pdav9LjXyfLd8umqPC57ZQbN4", - "1DMwH1FMx2yJ67az7ZTJqViBD6iz3vQkUK", - "1JzvBX9Q86LbEcBxT58npYXS31QexVVMGG", - "1HBTfs2QLK459tQrdpeQs4stR25GWwJTui", - "1NZMxDpqB1ehEdvGJeAs9Gdmh3dXfUfAZB", - "19ePAsmdkM4u9e3euzfnQa1AXEoD2UgmEj", - } + bitcoinMin = 26 + bitcoinMax = 35 + ethLen = 42 + ethPrefix = "0x" +) - etheriumAddresses = []string{ - "0x83e1e8f10092d42db425D81c2e99f312a7E011aA", - "0xd3E823D4C999e4ef9c92835eEF6906E519C13251", - "0x4e3adfcdD456DDe868B1225aA0ed103Dd188B5F6", - "0xbC39DCa632f8f7f2A94B095d48bcEE779d961728", - "0xE0A6c75e545947E7Bb4dde2D8182762a4C698E5c", - "0x45C20Fd8F6B07359750f92B79f1C41754Bd09Ac3", - "0xA882bE0b4C10E91c3565EE01878A48F9B940f2c5", - "0x8c2B7B23f01fcAD2946A3C214c4D96338A5eFD6D", - "0x271253c6B815a07506719116262c1673692eD76E", - "0x1e887dC08ba56e369E68987F9D82b44065677c87", +// Checks whether the ascii value provided is in the exclusion for bitcoin. +func (Crypto) isInExclusionZone(ascii int) bool { + switch ascii { + // Ascii for uppercase letter "O", uppercase letter "I", lowercase letter "l", and the number "0" + case 48, 73, 79, 108: + return true } -) + return false +} -// BitcoinAddress returns a valid address of either Bech32, P2PKH, or P2SH type. -func (c Crypto) BitcoinAddress() string { - return c.Faker.RandomStringElement(bitcoinAddresses) +// algorithmRange decides whether to get digit, uppercase, or lowercase. returns the ascii range to do IntBetween on +func (c Crypto) algorithmRange() (int, int) { + dec := c.Faker.IntBetween(0, 2) + if dec == 0 { + // digit + return 48, 57 + } else if dec == 1 { + // upper + return 65, 90 + } + // lower + return 97, 122 } -// EtheriumAddress returns a valid hexadecimal ethereum address of 42 characters. -func (c Crypto) EtheriumAddress() string { - return c.Faker.RandomStringElement(etheriumAddresses) +// generateBicoinAddress returns a bitcoin address with a given prefix and length +func (c Crypto) generateBicoinAddress(length int, prefix string, f *Faker) string { + address := []string{prefix} + + for i := 0; i < length; i++ { + asciiStart, asciiEnd := c.algorithmRange() + val := f.IntBetween(asciiStart, asciiEnd) + if c.isInExclusionZone(val) { + val++ + } + address = append(address, string(rune(val))) + } + return strings.Join(address, "") } // P2PKHAddress generates a P2PKH bitcoin address. -// Deprecated: Use BitcoinAddress instead. +// Deprecated: Please use BitcoinAddress instead. func (c Crypto) P2PKHAddress() string { - return "1" + c.BitcoinAddress()[1:] + length := c.Faker.IntBetween(bitcoinMin, bitcoinMax) + // subtract 1 for prefix + return c.generateBicoinAddress(length-1, "1", c.Faker) } // P2PKHAddressWithLength generates a P2PKH bitcoin address with specified length. -// Deprecated: Use BitcoinAddress instead. +// Deprecated: Please use BitcoinAddress instead. func (c Crypto) P2PKHAddressWithLength(length int) string { - return "1" + c.P2PKHAddress()[1:length-1] + return c.generateBicoinAddress(length-1, "1", c.Faker) } // P2SHAddress generates a P2SH bitcoin address. -// Deprecated: Use BitcoinAddress instead. +// Deprecated: Please use BitcoinAddress instead. func (c Crypto) P2SHAddress() string { - return "3" + c.BitcoinAddress()[1:] + length := c.Faker.IntBetween(bitcoinMin, bitcoinMax) + // subtract 1 for prefix + return c.generateBicoinAddress(length-1, "3", c.Faker) } // P2SHAddressWithLength generates a P2PKH bitcoin address with specified length. -// Deprecated: Use BitcoinAddress instead. +// Deprecated: Please use BitcoinAddress instead. func (c Crypto) P2SHAddressWithLength(length int) string { - return "3" + c.P2SHAddress()[1:length-1] + return c.generateBicoinAddress(length-1, "3", c.Faker) } // Bech32Address generates a Bech32 bitcoin address -// Deprecated: Use BitcoinAddress instead. +// Deprecated: Please use BitcoinAddress instead. func (c Crypto) Bech32Address() string { - return "bc1" + c.BitcoinAddress()[3:] + length := c.Faker.IntBetween(bitcoinMin, bitcoinMax) + // subtract 1 for prefix + return c.generateBicoinAddress(length-3, "bc1", c.Faker) } // Bech32AddressWithLength generates a Bech32 bitcoin address with specified length. -// Deprecated: Use BitcoinAddress instead. +// Deprecated: Please use BitcoinAddress instead. func (c Crypto) Bech32AddressWithLength(length int) string { - return "bc1" + c.Bech32Address()[3:length-3] + return c.generateBicoinAddress(length-3, "bc1", c.Faker) +} + +// BitcoinAddress returns an address of either Bech32, P2PKH, or P2SH type. +func (c Crypto) BitcoinAddress() string { + dec := c.Faker.IntBetween(0, 2) + if dec == 0 { + return c.Bech32Address() + } else if dec == 1 { + return c.P2SHAddress() + } + return c.P2PKHAddress() +} + +// EtheriumAddress returns a hexadecimal ethereum address of 42 characters. +func (c Crypto) EtheriumAddress() string { + address := []string{ethPrefix} + + for i := 0; i < ethLen-2; i++ { + asciiStart, asciiEnd := c.algorithmRange() + val := c.Faker.IntBetween(asciiStart, asciiEnd) + address = append(address, string(rune(val))) + } + return strings.Join(address, "") } diff --git a/crypto_test.go b/crypto_test.go index 5491bec..106d400 100644 --- a/crypto_test.go +++ b/crypto_test.go @@ -1,61 +1,199 @@ package faker import ( + "fmt" "strings" "testing" ) -func TestBitcoinAddress(t *testing.T) { +var ( + bannedBitcoin = []string{"O", "I", "l", "0"} + validBitcoinPrefix = map[string]string{ + "p2pkh": "1", + "p2sh": "3", + "bech32": "bc1", + } + validEthPrefix = "0x" +) + +type GeneratorMock struct { + local int +} + +func (g GeneratorMock) Intn(_ int) int { + return g.local +} + +func (g GeneratorMock) Int() int { + return g.local +} + +type TestCaseAlnum struct { + desc string + localInt int + assert func(t *testing.T, a int, b int) +} + +type TestCaseRandomBitcoin struct { + desc string + localInt int + expectedSubstring string +} + +func TestIsInExclusionZone(t *testing.T) { c := New().Crypto() - addr := c.BitcoinAddress() - Expect(t, false, addr == "") + for _, address := range bannedBitcoin { + Expect(t, true, c.isInExclusionZone(int(rune(address[0])))) + } + // take any banned rune and + 1 it to get a valid character + Expect(t, false, c.isInExclusionZone(int(rune(bannedBitcoin[0][0]))+1)) } -func TestEtheriumAddress(t *testing.T) { +func TestGenerateBicoinAddress(t *testing.T) { c := New().Crypto() - addr := c.EtheriumAddress() - Expect(t, false, addr == "") - Expect(t, true, strings.HasPrefix(addr, "0x")) + length := c.Faker.IntBetween(5, 10) + Expect(t, length+1, len(c.generateBicoinAddress(length, "a", c.Faker))) } func TestP2PKHAddress(t *testing.T) { c := New().Crypto() addr := c.P2PKHAddress() - Expect(t, false, addr == "") - Expect(t, true, strings.HasPrefix(addr, "1")) + Expect(t, true, len(addr) >= bitcoinMin) + Expect(t, true, len(addr) <= bitcoinMax) + Expect(t, true, strings.HasPrefix(addr, validBitcoinPrefix["p2pkh"])) + for i := 0; i < len(bannedBitcoin); i++ { + Expect(t, true, !strings.Contains(addr, bannedBitcoin[i])) + } } func TestP2PKHAddressWithLength(t *testing.T) { c := New().Crypto() - addr := c.P2PKHAddressWithLength(10) - Expect(t, false, addr == "") - Expect(t, true, strings.HasPrefix(addr, "1")) + length := c.Faker.IntBetween(26, 62) + addr := c.P2PKHAddressWithLength(length) + Expect(t, true, len(addr) == length) + Expect(t, true, strings.HasPrefix(addr, validBitcoinPrefix["p2pkh"])) } func TestP2SHAddress(t *testing.T) { c := New().Crypto() addr := c.P2SHAddress() - Expect(t, false, addr == "") - Expect(t, true, strings.HasPrefix(addr, "3")) + Expect(t, true, len(addr) >= bitcoinMin) + Expect(t, true, len(addr) <= bitcoinMax) + Expect(t, true, strings.HasPrefix(addr, validBitcoinPrefix["p2sh"])) + for i := 0; i < len(bannedBitcoin); i++ { + Expect(t, true, !strings.Contains(addr, bannedBitcoin[i])) + } } func TestP2SHAddressWithLength(t *testing.T) { c := New().Crypto() - addr := c.P2SHAddressWithLength(10) - Expect(t, false, addr == "") - Expect(t, true, strings.HasPrefix(addr, "3")) + length := c.Faker.IntBetween(26, 62) + addr := c.P2SHAddressWithLength(length) + Expect(t, true, len(addr) == length) + Expect(t, true, strings.HasPrefix(addr, validBitcoinPrefix["p2sh"])) } func TestBech32Address(t *testing.T) { c := New().Crypto() addr := c.Bech32Address() - Expect(t, false, addr == "") - Expect(t, true, strings.HasPrefix(addr, "bc1")) + Expect(t, true, len(addr) >= bitcoinMin) + Expect(t, true, len(addr) <= bitcoinMax) + Expect(t, true, strings.HasPrefix(addr, validBitcoinPrefix["bech32"])) + for i := 0; i < len(bannedBitcoin); i++ { + Expect(t, true, !strings.Contains(addr, bannedBitcoin[i])) + } } func TestBech32AddressWithLength(t *testing.T) { c := New().Crypto() - addr := c.Bech32AddressWithLength(10) - Expect(t, false, addr == "") - Expect(t, true, strings.HasPrefix(addr, "bc1")) + length := c.Faker.IntBetween(26, 62) + addr := c.Bech32AddressWithLength(length) + Expect(t, true, len(addr) == length) + Expect(t, true, strings.HasPrefix(addr, validBitcoinPrefix["bech32"])) +} + +func TestEtheriumAddress(t *testing.T) { + c := New().Crypto() + addr := c.EtheriumAddress() + Expect(t, true, len(addr) == ethLen) + Expect(t, true, strings.HasPrefix(addr, ethPrefix)) +} + +func TestAlgorithmRange(t *testing.T) { + for k, tc := range []TestCaseAlnum{ + { + // The Description of the test case + desc: "Test Get Digit 0-9", + localInt: 0, + // Our anticipated result + assert: func(t *testing.T, a int, b int) { + Expect(t, true, a == int('0')) + Expect(t, true, b == int('9')) + }, + }, + { + desc: "Test Get Uppercase A-Z", + localInt: 1, + assert: func(t *testing.T, a int, b int) { + Expect(t, true, a == int('A')) + Expect(t, true, b == int('Z')) + }, + }, + { + desc: "Test Get Lowercase a-z", + localInt: 2, + assert: func(t *testing.T, a int, b int) { + Expect(t, true, a == int('a')) + Expect(t, true, b == int('z')) + }, + }, + } { + t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.desc), func(t *testing.T) { + // Use our mock here instead of using a seed. + gen := GeneratorMock{} + gen.local = tc.localInt + // populate the generator with our mock as it is an interface. + c := Faker{Generator: gen} + a, b := c.Crypto().algorithmRange() + tc.assert(t, a, b) + }) + } +} + +func TestRandomBitcoin(t *testing.T) { + for k, tc := range []TestCaseRandomBitcoin{ + { + // The Description of the test case + desc: "Test Get Bech32", + localInt: 0, + // Our anticipated result + expectedSubstring: "bc1", + }, + { + // The Description of the test case + desc: "Test Get P2SH", + localInt: 1, + // Our anticipated result + expectedSubstring: "3", + }, + { + // The Description of the test case + desc: "Test Get P2PKH", + localInt: 2, + // Our anticipated result + expectedSubstring: "1", + }, + } { + t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.desc), func(t *testing.T) { + // Use our mock here instead of using a seed. + gen := GeneratorMock{} + gen.local = tc.localInt + // populate the generator with our mock as it is an interface. + c := Faker{Generator: gen} + rs := c.Crypto().BitcoinAddress() + Expect(t, true, strings.HasPrefix(rs, tc.expectedSubstring)) + Expect(t, true, len(rs) >= bitcoinMin) + Expect(t, true, len(rs) <= bitcoinMax) + }) + } }