Skip to content

Instantly share code, notes, and snippets.

@tonyfabeen
Created March 19, 2021 18:23
Show Gist options
  • Save tonyfabeen/36e9b9200de6d89af28c4ec3f9f7ba7b to your computer and use it in GitHub Desktop.
Save tonyfabeen/36e9b9200de6d89af28c4ec3f9f7ba7b to your computer and use it in GitHub Desktop.
Create a new Bitcoin Wallet along with a new address
package main
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"github.com/shengdoushi/base58"
"github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39"
"golang.org/x/crypto/ripemd160"
)
func hashSha256(data []byte) ([]byte, error) {
hasher := sha256.New()
_, err := hasher.Write(data)
if err != nil {
return nil, err
}
return hasher.Sum(nil), nil
}
func hashDoubleSha256(data []byte) ([]byte, error) {
hash1, err := hashSha256(data)
if err != nil {
return nil, err
}
hash2, err := hashSha256(hash1)
if err != nil {
return nil, err
}
return hash2, nil
}
func hashRipeMD160(data []byte) ([]byte, error) {
hasher := ripemd160.New()
_, err := io.WriteString(hasher, string(data))
if err != nil {
return nil, err
}
return hasher.Sum(nil), nil
}
func hash160(data []byte) ([]byte, error) {
hash1, err := hashSha256(data)
if err != nil {
return nil, err
}
hash2, err := hashRipeMD160(hash1)
if err != nil {
return nil, err
}
return hash2, nil
}
type Address []byte
func (address Address) ToBase58() string {
alphabet := base58.BitcoinAlphabet
return base58.Encode(address, alphabet)
}
func (address Address) ToHex() string {
addressEncoded := make([]byte, hex.EncodedLen(len(address)))
hex.Encode(addressEncoded, address)
return string(addressEncoded)
}
type Wallet struct {
PrivateKey *bip32.Key
PublicKey *bip32.Key
Mnemonic string
}
func (wallet *Wallet) GenerateAddress() (Address, error) {
// 1 - Take the corresponding public key generated with it (33 bytes, 1 byte 0x02 (y-coord is even), and 32 bytes corresponding to X coordinate)
// 2 - Perform SHA-256 hashing on the public key
// 3 - Perform RIPEMD-160 hashing on the result of SHA-256
hashedPubKeyXCoordinate, err := hash160(wallet.PublicKey.Key[:32])
if err != nil {
return nil, err
}
// 4 - Add version byte in front of RIPEMD-160 hash (0x00 for Main Network)
hashedPubKeyXCoordinateWithNetwork := append([]byte{0x00}, hashedPubKeyXCoordinate...)
// 5 - Perform SHA-256 hash on the extended RIPEMD-160 result
// 6 - Perform SHA-256 hash on the result of the previous SHA-256 hash
doubledHashedSha256, err := hashDoubleSha256(hashedPubKeyXCoordinateWithNetwork)
if err != nil {
return nil, err
}
// 7 - Take the first 4 bytes of the second SHA-256 hash. This is the address checksum
addressChecksum := doubledHashedSha256[:4]
//8 - Add the 4 checksum bytes from stage 7 at the end of extended RIPEMD-160 hash from stage 4.
// This is the 25-byte binary Bitcoin Address.
address := append(hashedPubKeyXCoordinateWithNetwork, addressChecksum...)
return address, nil
}
func CreateWallet(password string) (*Wallet, error) {
if password == "" {
return nil, errors.New("password should not be an empty string")
}
panicWhenError := func(err error) {
if err != nil {
panic(err)
}
}
entropy, err := bip39.NewEntropy(256)
panicWhenError(err)
mnemonic, err := bip39.NewMnemonic(entropy)
panicWhenError(err)
seed := bip39.NewSeed(mnemonic, password)
masterKey, err := bip32.NewMasterKey(seed)
panicWhenError(err)
publicKey := masterKey.PublicKey()
wallet := &Wallet{
Mnemonic: mnemonic,
PrivateKey: masterKey,
PublicKey: publicKey,
}
return wallet, nil
}
// Background
// https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
//
// Validators
// http://lenschulwitz.com/base58
// https://www.appdevtools.com/base58-encoder-decoder
func main() {
wallet, _ := CreateWallet("Secret Passphrase")
address, _ := wallet.GenerateAddress()
fmt.Println("address in base58", address.ToBase58())
fmt.Println("address in Hex", address.ToHex())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment