Skip to content

Commit

Permalink
Refactor crypto.go to generate valid crypto addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonathan Schweder committed Oct 6, 2023
1 parent 802c3f2 commit 9c2eb39
Show file tree
Hide file tree
Showing 3 changed files with 344 additions and 268 deletions.
246 changes: 170 additions & 76 deletions crypto.go
Original file line number Diff line number Diff line change
@@ -1,115 +1,209 @@
package faker

import (
"strings"
"crypto/rand"
"fmt"
"math/big"
"crypto/sha256"
)

// Crypto is a faker struct for generating bitcoin data
type Crypto struct {
Faker *Faker
}

var (
bitcoinMin = 26
bitcoinMax = 35
ethLen = 42
ethPrefix = "0x"
)
// BitcoinAddress returns a random Bitcoin address of either Bech32, P2PKH, or P2SH type.
func (c Crypto) BitcoinAddress() string {
// Generate a random byte slice of length 20
bytes := make([]byte, 20)
_, err := rand.Read(bytes)
if err != nil {
panic(err)
}

// 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
// Choose a random address type
types := []string{"bech32", "p2pkh", "p2sh"}
addressType := types[c.Faker.IntBetween(0, len(types)-1)]

// Generate the address based on the chosen type
switch addressType {
case "bech32":
return c.bech32Address(bytes)
case "p2pkh":
return c.p2pkhAddress(bytes)
case "p2sh":
return c.p2shAddress(bytes)
default:
panic(fmt.Sprintf("Invalid address type: %s", addressType))
}
return false
}

// 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
// doubleSha256 calculates the SHA-256 hash of the input, and then calculates the SHA-256 hash of the result.
func doubleSha256(input []byte) []byte {
hasher := sha256.New()
hasher.Write(input)
hash := hasher.Sum(nil)

hasher.Reset()
hasher.Write(hash)
return hasher.Sum(nil)
}

// 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}
// base58Encode encodes the input byte slice as a base58 string.
func base58Encode(input []byte) string {
alphabet := "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

// Convert the input bytes to a big integer
num := new(big.Int).SetBytes(input)

for i := 0; i < length; i++ {
asciiStart, asciiEnd := c.algorithmRange()
val := f.IntBetween(asciiStart, asciiEnd)
if c.isInExclusionZone(val) {
val++
// Encode the big integer as a base58 string
var result []byte
for num.Sign() > 0 {
mod := new(big.Int)
num.DivMod(num, big.NewInt(58), mod)
result = append([]byte{alphabet[mod.Int64()]}, result...)
}

// Add leading zeros for each zero byte in the input
for _, b := range input {
if b != 0x00 {
break
}
address = append(address, string(rune(val)))
result = append([]byte{alphabet[0]}, result...)
}
return strings.Join(address, "")

return string(result)
}

// p2pkhAddress generates a P2PKH bitcoin address.
func (c Crypto) p2pkhAddress(bytes []byte) string {
// Prepend the version byte (0x00) to the public key hash
versionedBytes := append([]byte{0x00}, bytes...)

// Calculate the checksum by hashing the versioned bytes twice
checksum := doubleSha256(versionedBytes)[:4]

// Concatenate the versioned bytes and the checksum
addressBytes := append(versionedBytes, checksum...)

// Encode the address bytes as a base58 string
return base58Encode(addressBytes)
}

// P2PKHAddress generates a P2PKH bitcoin address.
func (c Crypto) P2PKHAddress() string {
length := c.Faker.IntBetween(bitcoinMin, bitcoinMax)
// subtract 1 for prefix
return c.generateBicoinAddress(length-1, "1", c.Faker)
return c.p2pkhAddress(c.Faker.RandomBytes(20))
}

// P2PKHAddressWithLength generates a P2PKH bitcoin address with specified length.
func (c Crypto) P2PKHAddressWithLength(length int) string {
return c.generateBicoinAddress(length-1, "1", c.Faker)
func (c Crypto) p2shAddress(bytes []byte) string {
// Prepend the version byte (0x05) to the redeem script hash
versionedBytes := append([]byte{0x05}, bytes...)

// Calculate the checksum by hashing the versioned bytes twice
checksum := doubleSha256(versionedBytes)[:4]

// Concatenate the versioned bytes and the checksum
addressBytes := append(versionedBytes, checksum...)

// Encode the address bytes as a base58 string
return base58Encode(addressBytes)
}

// P2SHAddress generates a P2SH bitcoin address.
func (c Crypto) P2SHAddress() string {
length := c.Faker.IntBetween(bitcoinMin, bitcoinMax)
// subtract 1 for prefix
return c.generateBicoinAddress(length-1, "3", c.Faker)
return c.p2shAddress(c.Faker.RandomBytes(20))
}

// P2SHAddressWithLength generates a P2PKH bitcoin address with specified length.
func (c Crypto) P2SHAddressWithLength(length int) string {
return c.generateBicoinAddress(length-1, "3", c.Faker)
}
// bech32Address generates a Bech32 bitcoin address
func (c Crypto) bech32Address(bytes []byte) string {
// Define the Bech32 character set
const charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"

// Bech32Address generates a Bech32 bitcoin address
func (c Crypto) Bech32Address() string {
length := c.Faker.IntBetween(bitcoinMin, bitcoinMax)
// subtract 1 for prefix
return c.generateBicoinAddress(length-3, "bc1", c.Faker)
}
// Convert the input bytes to a 5-bit binary representation
var bits []uint5
for _, b := range bytes {
bits = append(bits, uint5(b>>5), uint5(b&0x1f))
}

// Bech32AddressWithLength generates a Bech32 bitcoin address with specified length.
func (c Crypto) Bech32AddressWithLength(length int) string {
return c.generateBicoinAddress(length-3, "bc1", c.Faker)
}
// Calculate the checksum
checksum := bech32Checksum(bits)

// 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()
// Concatenate the input bytes and the checksum
allBits := append(bits, checksum...)

// Convert the 5-bit binary representation to a Bech32 string
var bech32Chars []rune
for _, b := range allBits {
bech32Chars = append(bech32Chars, rune(charset[b]))
}
return c.P2PKHAddress()

// Return the Bech32 string
return "bc1" + string(bech32Chars)
}

// EtheriumAddress returns a hexadecimal ethereum address of 42 characters.
func (c Crypto) EtheriumAddress() string {
address := []string{ethPrefix}
// bech32Checksum calculates the Bech32 checksum for the given 5-bit binary representation.
func bech32Checksum(bits []uint5) []uint5 {
// Define the generator polynomial
const generator = 0x3b6a57b2

// Append 6 zero bits to the input
bits = append(bits, make([]uint5, 6)...)

// Calculate the initial value of the checksum
var checksum uint32 = 1
for _, b := range bits {
// Extract the most significant bit of the checksum
msb := checksum >> 25

// Shift the checksum left by 1 bit and add the current bit
checksum = (checksum&0x1ffffff)<<1 | uint32(b)

for i := 0; i < ethLen-2; i++ {
asciiStart, asciiEnd := c.algorithmRange()
val := c.Faker.IntBetween(asciiStart, asciiEnd)
address = append(address, string(rune(val)))
// If the most significant bit is 1, XOR the checksum with the generator polynomial
if msb == 1 {
checksum ^= generator
}
}

// Convert the checksum to a 5-bit binary representation
var result []uint5
for i := 0; i < 6; i++ {
result = append(result, uint5(checksum>>(25-5*i)))

Check failure on line 169 in crypto.go

View workflow job for this annotation

GitHub Actions / test (1.11.x, ubuntu-latest)

invalid operation: checksum >> (25 - 5 * i) (shift count type int, must be unsigned integer)

Check failure on line 169 in crypto.go

View workflow job for this annotation

GitHub Actions / test (1.12.x, ubuntu-latest)

invalid operation: checksum >> (25 - 5 * i) (shift count type int, must be unsigned integer)

Check failure on line 169 in crypto.go

View workflow job for this annotation

GitHub Actions / test (1.18.x, ubuntu-latest)

invalid operation: signed shift count (25 - 5 * i) (value of type int) requires go1.13 or later (-lang was set to go1.11; check go.mod)

Check failure on line 169 in crypto.go

View workflow job for this annotation

GitHub Actions / test (1.16.x, ubuntu-latest)

invalid operation: checksum >> (25 - 5 * i) (signed shift count type int) requires go1.13 or later (-lang was set to go1.11; check go.mod)

Check failure on line 169 in crypto.go

View workflow job for this annotation

GitHub Actions / test (1.15.x, ubuntu-latest)

invalid operation: checksum >> (25 - 5 * i) (signed shift count type int) requires go1.13 or later (-lang was set to go1.11; check go.mod)

Check failure on line 169 in crypto.go

View workflow job for this annotation

GitHub Actions / test (1.13.x, ubuntu-latest)

invalid operation: checksum >> (25 - 5 * i) (signed shift count type int) requires go1.13 or later (-lang was set to go1.11; check go.mod)

Check failure on line 169 in crypto.go

View workflow job for this annotation

GitHub Actions / test (1.17.x, ubuntu-latest)

invalid operation: checksum >> (25 - 5 * i) (signed shift count type int) requires go1.13 or later (-lang was set to go1.11; check go.mod)

Check failure on line 169 in crypto.go

View workflow job for this annotation

GitHub Actions / test (1.14.x, ubuntu-latest)

invalid operation: checksum >> (25 - 5 * i) (signed shift count type int) requires go1.13 or later (-lang was set to go1.11; check go.mod)
}
return strings.Join(address, "")

return result
}

// uint5 is an unsigned 5-bit integer.
type uint5 uint8

// Bech32Address generates a Bech32 bitcoin address
func (c Crypto) Bech32Address() string {
return c.bech32Address(c.Faker.RandomBytes(20))
}

// // EtheriumAddress returns a hexadecimal ethereum address of 42 characters.
// func (c Crypto) EtheriumAddress() string {
// }

// // P2PKHAddress generates a P2PKH bitcoin address.
// func (c Crypto) P2PKHAddress() string {
// }

// // P2PKHAddressWithLength generates a P2PKH bitcoin address with specified length.
// func (c Crypto) P2PKHAddressWithLength(length int) string {
// }

// // P2SHAddress generates a P2SH bitcoin address.
// func (c Crypto) P2SHAddress() string {
// }

// // P2SHAddressWithLength generates a P2PKH bitcoin address with specified length.
// func (c Crypto) P2SHAddressWithLength(length int) string {
// }

// // Bech32Address generates a Bech32 bitcoin address
// func (c Crypto) Bech32Address() string {
// }

// // Bech32AddressWithLength generates a Bech32 bitcoin address with specified length.
// func (c Crypto) Bech32AddressWithLength(length int) string {
// }
Loading

0 comments on commit 9c2eb39

Please sign in to comment.