├── .gitignore ├── LICENSE ├── README.md ├── main.go ├── utils.go └── wallet.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | bitcoin_protocol 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 hww 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitcoin_address_protocol 2 | generate bitcoin address protocol by raw way 3 | 4 | code for article https://www.jianshu.com/p/71a4454c74da 5 | 6 | assume that there is Golang env for you machine. 7 | 1. go build 8 | 2. ./bitcoin_protocol 9 | 10 | ```shell 11 | ➜ bitcoin_protocol git:(master) ./bitcoin_protocol 12 | 0 - Having a private ECDSA key 13 | 5C992BE8E980E508402B62AE19B272C7089197445653C94D28B02ABAD47F9B0C 14 | ======================= 15 | 1 - Take the corresponding public key generated with it (65 bytes, 1 byte 0x04, 32 bytes corresponding to X coordinate, 32 bytes corresponding to Y coordinate) 16 | raw public key B53DD83B65F131B45B44B04B5D45CA095AF1CEB0918C8AE024A414B93BC17811A77451344682B1E12561304943235FB887F8216D047372F6588539C3A57321B2 17 | ======================= 18 | 2 - Perform SHA-256 hashing on the public key 19 | 2EE04B5A2BF7CBBCB82D6B2334066CF91EF56B199CB48D803D138C23B43FBACD 20 | ======================= 21 | 3 - Perform RIPEMD-160 hashing on the result of SHA-256 22 | 51337C281FADA1EDAE5EB3AFD827FBE56031755C 23 | ======================= 24 | 4 - Add version byte in front of RIPEMD-160 hash (0x00 for Main Network) 25 | 0051337C281FADA1EDAE5EB3AFD827FBE56031755C 26 | ======================= 27 | 5 - Perform SHA-256 hash on the extended RIPEMD-160 result 28 | 23CF88427B84E39760B9BD04D78F046115729C5AFF8578F7AF451DD3E86A8FDC 29 | ======================= 30 | 6 - Perform SHA-256 hash on the result of the previous SHA-256 hash 31 | 66A6F89BAD3E3C29162BD8E9C1A4513EFDCB3AAC305BF48A70D287001FC9117A 32 | ======================= 33 | 7 - Take the first 4 bytes of the second SHA-256 hash. This is the address checksum 34 | 66A6F89B 35 | ======================= 36 | 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. 37 | 0051337C281FADA1EDAE5EB3AFD827FBE56031755C66A6F89B 38 | ======================= 39 | 9 - Convert the result from a byte string into a base58 string using Base58Check encoding. This is the most commonly used Bitcoin Address format 40 | 18QMQn7poD737SBC7mqaQYs6bQHoe6mMqU 41 | ======================= 42 | ``` 43 | 44 | see the address in blockchain browser: [18QMQn7poD737SBC7mqaQYs6bQHoe6mMqU](https://blockchain.info/address/18QMQn7poD737SBC7mqaQYs6bQHoe6mMqU) 45 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | wallet := NewWallet() 7 | 8 | fmt.Println("0 - Having a private ECDSA key") 9 | fmt.Println(byteString(wallet.PrivateKey)) 10 | fmt.Println("=======================") 11 | // fmt.Println("private wallet import format") 12 | // fmt.Println("private wallet import format", ToWIF(wallet.PrivateKey)) 13 | // fmt.Println("=======================") 14 | fmt.Println("1 - Take the corresponding public key generated with it (65 bytes, 1 byte 0x04, 32 bytes corresponding to X coordinate, 32 bytes corresponding to Y coordinate)") 15 | fmt.Println("raw public key", byteString(wallet.PublicKey)) 16 | fmt.Println("=======================") 17 | wallet.GetAddress() 18 | } 19 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | func byteString(b []byte) (s string) { 10 | s = "" 11 | for i := 0; i < len(b); i++ { 12 | s += fmt.Sprintf("%02X", b[i]) 13 | } 14 | return s 15 | } 16 | 17 | // b58encode encodes a byte slice b into a base-58 encoded string. 18 | func b58encode(b []byte) (s string) { 19 | /* See https://en.bitcoin.it/wiki/Base58Check_encoding */ 20 | 21 | const BITCOIN_BASE58_TABLE = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 22 | 23 | /* Convert big endian bytes to big int */ 24 | x := new(big.Int).SetBytes(b) 25 | 26 | /* Initialize */ 27 | r := new(big.Int) 28 | m := big.NewInt(58) 29 | zero := big.NewInt(0) 30 | s = "" 31 | 32 | /* Convert big int to string */ 33 | for x.Cmp(zero) > 0 { 34 | /* x, r = (x / 58, x % 58) */ 35 | x.QuoRem(x, m, r) 36 | /* Prepend ASCII character */ 37 | s = string(BITCOIN_BASE58_TABLE[r.Int64()]) + s 38 | } 39 | 40 | return s 41 | } 42 | 43 | // b58checkencode encodes version ver and byte slice b into a base-58 check encoded string. 44 | func b58checkencode(ver uint8, b []byte) (s string) { 45 | /* Prepend version */ 46 | fmt.Println("4 - Add version byte in front of RIPEMD-160 hash (0x00 for Main Network)") 47 | bcpy := append([]byte{ver}, b...) 48 | fmt.Println(byteString(bcpy)) 49 | fmt.Println("=======================") 50 | 51 | /* Create a new SHA256 context */ 52 | sha256H := sha256.New() 53 | 54 | /* SHA256 Hash #1 */ 55 | fmt.Println("5 - Perform SHA-256 hash on the extended RIPEMD-160 result") 56 | sha256H.Reset() 57 | sha256H.Write(bcpy) 58 | hash1 := sha256H.Sum(nil) 59 | fmt.Println(byteString(hash1)) 60 | fmt.Println("=======================") 61 | 62 | /* SHA256 Hash #2 */ 63 | fmt.Println("6 - Perform SHA-256 hash on the result of the previous SHA-256 hash") 64 | sha256H.Reset() 65 | sha256H.Write(hash1) 66 | hash2 := sha256H.Sum(nil) 67 | fmt.Println(byteString(hash2)) 68 | fmt.Println("=======================") 69 | 70 | /* Append first four bytes of hash */ 71 | fmt.Println("7 - Take the first 4 bytes of the second SHA-256 hash. This is the address checksum") 72 | fmt.Println(byteString(hash2[0:4])) 73 | fmt.Println("=======================") 74 | 75 | fmt.Println("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.") 76 | bcpy = append(bcpy, hash2[0:4]...) 77 | fmt.Println(byteString(bcpy)) 78 | fmt.Println("=======================") 79 | 80 | /* Encode base58 string */ 81 | s = b58encode(bcpy) 82 | 83 | /* For number of leading 0's in bytes, prepend 1 */ 84 | for _, v := range bcpy { 85 | if v != 0 { 86 | break 87 | } 88 | s = "1" + s 89 | } 90 | fmt.Println("9 - Convert the result from a byte string into a base58 string using Base58Check encoding. This is the most commonly used Bitcoin Address format") 91 | fmt.Println(s) 92 | fmt.Println("=======================") 93 | 94 | return s 95 | } 96 | 97 | // paddedAppend appends the src byte slice to dst, returning the new slice. 98 | // If the length of the source is smaller than the passed size, leading zero 99 | // bytes are appended to the dst slice before appending src. 100 | func paddedAppend(size uint, dst, src []byte) []byte { 101 | for i := 0; i < int(size)-len(src); i++ { 102 | dst = append(dst, 0) 103 | } 104 | return append(dst, src...) 105 | } 106 | -------------------------------------------------------------------------------- /wallet.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/sha256" 8 | "fmt" 9 | "log" 10 | 11 | "golang.org/x/crypto/ripemd160" 12 | ) 13 | 14 | const version = byte(0x00) 15 | const addressChecksumLen = 4 16 | 17 | // Wallet stores private and public keys 18 | type Wallet struct { 19 | PrivateKey []byte 20 | PublicKey []byte 21 | } 22 | 23 | // NewWallet creates and returns a Wallet 24 | func NewWallet() *Wallet { 25 | private, public := newKeyPair() 26 | wallet := Wallet{private, public} 27 | 28 | return &wallet 29 | } 30 | 31 | // GetAddress returns wallet address 32 | func (w Wallet) GetAddress() (address string) { 33 | /* See https://en.bitcoin.it/wiki/Technical_background_of_Bitcoin_addresses */ 34 | 35 | /* Convert the public key to bytes */ 36 | pub_bytes := w.PublicKey 37 | 38 | /* SHA256 Hash */ 39 | fmt.Println("2 - Perform SHA-256 hashing on the public key") 40 | sha256_h := sha256.New() 41 | sha256_h.Reset() 42 | sha256_h.Write(pub_bytes) 43 | pub_hash_1 := sha256_h.Sum(nil) 44 | fmt.Println(byteString(pub_hash_1)) 45 | fmt.Println("=======================") 46 | 47 | /* RIPEMD-160 Hash */ 48 | fmt.Println("3 - Perform RIPEMD-160 hashing on the result of SHA-256") 49 | ripemd160_h := ripemd160.New() 50 | ripemd160_h.Reset() 51 | ripemd160_h.Write(pub_hash_1) 52 | pub_hash_2 := ripemd160_h.Sum(nil) 53 | fmt.Println(byteString(pub_hash_2)) 54 | fmt.Println("=======================") 55 | /* Convert hash bytes to base58 check encoded sequence */ 56 | address = b58checkencode(0x00, pub_hash_2) 57 | 58 | return address 59 | } 60 | 61 | // HashPubKey hashes public key 62 | func HashPubKey(pubKey []byte) []byte { 63 | publicSHA256 := sha256.Sum256(pubKey) 64 | 65 | RIPEMD160Hasher := ripemd160.New() 66 | _, err := RIPEMD160Hasher.Write(publicSHA256[:]) 67 | if err != nil { 68 | log.Panic(err) 69 | } 70 | publicRIPEMD160 := RIPEMD160Hasher.Sum(nil) 71 | 72 | return publicRIPEMD160 73 | } 74 | 75 | const privKeyBytesLen = 32 76 | 77 | func newKeyPair() ([]byte, []byte) { 78 | curve := elliptic.P256() 79 | private, err := ecdsa.GenerateKey(curve, rand.Reader) 80 | if err != nil { 81 | log.Panic(err) 82 | } 83 | d := private.D.Bytes() 84 | b := make([]byte, 0, privKeyBytesLen) 85 | priKet := paddedAppend(privKeyBytesLen, b, d) 86 | pubKey := append(private.PublicKey.X.Bytes(), private.PublicKey.Y.Bytes()...) 87 | 88 | return priKet, pubKey 89 | } 90 | 91 | // ToWIF converts a Bitcoin private key to a Wallet Import Format string. 92 | func ToWIF(priv []byte) (wif string) { 93 | /* Convert bytes to base-58 check encoded string with version 0x80 */ 94 | wif = b58checkencode(0x80, priv) 95 | 96 | return wif 97 | } 98 | --------------------------------------------------------------------------------