├── .gitignore
├── LANGS.md
├── LICENSE.md
├── Makefile
├── README.md
├── assets
├── bitcoin.svg
├── bitcoin_logo.svg
├── bitcoin_white.ai
├── cover.jpg
├── cover.psd
├── cover_small.jpg
├── cover_small.psd
├── gopher.ai
├── gopher.png
└── gopher.svg
├── book.json
├── code
├── README.md
├── account_balance.go
├── accounts.txt
├── certs
│ ├── example.com.cert
│ └── example.com.key
├── transfer_coin.go
├── transfer_coin_simple.go
├── util.go
├── wallet_decode.go
└── wallet_generate.go
├── en
├── GLOSSARY.md
├── README.md
├── SUMMARY.md
├── account-balance
│ └── README.md
├── accounts
│ └── README.md
├── cover.jpg
├── cover_small.jpg
├── faucets
│ └── README.md
├── styles
│ └── website.css
├── test
│ └── README.md
├── transactions
│ └── README.md
├── transfer-coin-simple
│ └── README.md
├── transfer-coin
│ └── README.md
└── wallet-generate
│ └── README.md
├── package-lock.json
├── package.json
└── styles
└── website.css
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | _book
3 | todo.txt
4 | *.pdf
5 | *.epub
6 | *.mobi
7 | deploy.sh
8 | node_modules
9 | code/sandbox/
10 | sandbox.go
11 | sandbox*.go
12 | tmp/
13 |
14 | .ecrecover.go
15 |
16 | # TODO
17 | en/block-query
18 | en/block-subscribe
19 | en/client
20 | en/client-setup
21 | en/hd-wallet
22 | en/resources
23 | en/signature-generate
24 | en/signature-verify
25 | en/signatures
26 | en/transaction-query
27 |
28 | address.go
29 | blocks.go
30 | client.go
31 | signature_generate.go
32 | signature_verify.go
33 | transactions.go
34 | transfercoin*.go
35 |
--------------------------------------------------------------------------------
/LANGS.md:
--------------------------------------------------------------------------------
1 | # Languages
2 |
3 | * [English](en/)
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
118 |
119 | The Go gopher was designed by Renee French. http://reneefrench.blogspot.com/ The design is licensed under the Creative Commons 3.0 Attributions license. Read this article for more details: https://blog.golang.org/gopher
120 |
121 | Also see https://github.com/fatih/vim-go/blob/master/LICENSE
122 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: build
2 |
3 | .PHONY: install
4 | install:
5 | npm install gitbook-cli@latest -g
6 |
7 | .PHONY: serve
8 | serve:
9 | gitbook serve
10 |
11 | .PHONY: build
12 | build:
13 | gitbook build
14 |
15 | .PHONY: deploy
16 | deploy:
17 | ./deploy.sh
18 |
19 | .PHONY: deploy/all
20 | deploy/all: build pdf ebook mobi deploy
21 |
22 | .PHONY: pdf
23 | pdf:
24 | gitbook pdf ./ bitcoin-development-with-go.pdf
25 |
26 | .PHONY: ebook
27 | ebook:
28 | gitbook epub ./ bitcoin-development-with-go.epub
29 |
30 | .PHONY: mobi
31 | mobi:
32 | gitbook mobi ./ bitcoin-development-with-go.mobi
33 |
34 | .PHONY: plugins/install
35 | plugins/install:
36 | gitbook install
37 |
38 | .PHONY: btc/hexpriv2hexpub
39 | btc/hexpriv2hexpub:
40 | @bitcoin-tool --network bitcoin --input-type private-key --input-format hex --output-type public-key --public-key-compression uncompressed --output-format hex --input $(KEY)
41 |
42 | .PHONY: btc/b58priv2b58pub
43 | btc/b58priv2b58pub:
44 | @bitcoin-tool --network bitcoin --input-type private-key-wif --input-format base58check --output-type public-key --public-key-compression auto --output-format base58check --input $(KEY)
45 |
46 | .PHONY: btc/b58priv2hexpub
47 | btc/b58priv2hexpub:
48 | @bitcoin-tool --network bitcoin --input-type private-key-wif --input-format base58check --output-type public-key --public-key-compression auto --output-format hex --input $(KEY)
49 |
50 | .PHONY: btc/hexpriv2b58address
51 | btc/hexpriv2b58address:
52 | @bitcoin-tool --network bitcoin --input-type private-key --input-format hex --output-type address --public-key-compression uncompressed --output-format base58check --input $(KEY)
53 |
54 | .PHONY: btc/b58priv2b58address
55 | btc/b58priv2b58address:
56 | @bitcoin-tool --network bitcoin --input-type private-key-wif --input-format base58check --output-type address --public-key-compression auto --output-format base58check --input $(KEY)
57 |
58 | # 15vFKWjdm2LWWoJ2B2c1Kx9tu79eANoGLV
59 | # L2673vhaBt1UgmQAFoAV3qA2N4gJFVAPL2LdAr6FoTzjyYEizhGo
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Work in Progress
2 |
3 | ---
4 |
5 | # Bitcoin Development with Go
6 |
7 | > A little book on [Bitcoin](https://bitcoin.org/) Development with [Go](https://golang.org/) (golang)
8 |
9 | ## Online
10 |
11 | [https://gobitcoinbook.org](https://gobitcoinbook.org/)
12 |
13 |
22 |
23 | ## Contents
24 |
25 | * [Introduction](en/README.md)
26 | * [Accounts](en/accounts/README.md)
27 | * [Generating New Wallets](en/wallet-generate/README.md)
28 | * [Account Balances](en/account-balance/README.md)
29 | * [Transactions](en/transactions/README.md)
30 | * [Send BTC (basic example)](en/transfer-coin-simple/README.md)
31 | * [Send BTC (gather UTXOs)](en/transfer-coin/README.md)
32 |
33 | ## License
34 |
35 | [CC0-1.0](./LICENSE.md)
36 |
--------------------------------------------------------------------------------
/assets/bitcoin.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/assets/bitcoin_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/assets/bitcoin_white.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/assets/bitcoin_white.ai
--------------------------------------------------------------------------------
/assets/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/assets/cover.jpg
--------------------------------------------------------------------------------
/assets/cover.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/assets/cover.psd
--------------------------------------------------------------------------------
/assets/cover_small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/assets/cover_small.jpg
--------------------------------------------------------------------------------
/assets/cover_small.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/assets/cover_small.psd
--------------------------------------------------------------------------------
/assets/gopher.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/assets/gopher.ai
--------------------------------------------------------------------------------
/assets/gopher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/assets/gopher.png
--------------------------------------------------------------------------------
/assets/gopher.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
404 |
--------------------------------------------------------------------------------
/book.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": "./en",
3 | "title": "Bitcoin Development with Go",
4 | "description": "A little book on getting started with Bitcoin development using Go (Golang). Learn how to create wallets, transfer bitcoin, verify signatures, and much more.",
5 | "author": "Miguel Mota",
6 | "isbn": "",
7 | "language": "en",
8 | "direction": "ltr",
9 | "gitbook": ">= 3.0.0",
10 | "styles": {
11 | "website": "styles/website.css"
12 | },
13 | "plugins": [
14 | "analytics",
15 | "meta"
16 | ],
17 | "pluginsConfig": {
18 | "analytics": {
19 | "google": "UA-39494276-13"
20 | },
21 | "fontsettings": {
22 | "theme": "white",
23 | "family": "sans",
24 | "size": 2
25 | },
26 | "autocover": {
27 | "font": {
28 | "size": null,
29 | "family": "Impact",
30 | "color": "#FFF"
31 | },
32 | "size": {
33 | "w": 1800,
34 | "h": 2360
35 | },
36 | "background": {
37 | "color": "#09F"
38 | }
39 | },
40 | "meta": {
41 | "data": [
42 | {
43 | "name": "keywords",
44 | "content": "bitcoin, go, golang, development, examples, tutorial, explained, guide, help, reference, stackoverflow, blockchain, coins, btc, gopher, bitcoin go book, go bitcoin book, bitcoin golang, bitcoin golang tutorial, golang bitcoin tutorial, bitcoin golang guide, golang bitcoin guide, golang interact bitcoin, bitcoin go getting started"
45 | },
46 | {
47 | "name": "googlebot",
48 | "content": "index,follow"
49 | },
50 | {
51 | "name": "application-name",
52 | "content": "Go Bitcoin"
53 | }
54 | ]
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/code/README.md:
--------------------------------------------------------------------------------
1 | # Code samples
2 |
3 | > The code samples used in the book.
4 |
5 | ## Running
6 |
7 | ```go
8 | go run account_balance.go
9 | ```
10 |
11 | ## License
12 |
13 | MIT
14 |
--------------------------------------------------------------------------------
/code/account_balance.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "encoding/json"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "log"
10 | "math/big"
11 | )
12 |
13 | func main() {
14 | address := "3BMEXpRXTdAHagbFtjtuSS3S8ZXfQQqiTw"
15 |
16 | req := struct {
17 | ID int `json:"id"`
18 | Method string `json:"method"`
19 | Params []string `json:"params"`
20 | }{
21 | ID: 1,
22 | Method: "blockchain.address.get_balance",
23 | Params: []string{address},
24 | }
25 |
26 | res := struct {
27 | JSONRPC string `json:"jsonrpc,omitempty"`
28 | ID int `json:"id"`
29 | Result struct {
30 | Confirmed *big.Int `json:"confirmed"`
31 | Uncomfirmed *big.Int `json:"unconfirmed"`
32 | } `json:"result"`
33 | }{}
34 |
35 | serverAddr := "electrum.qtornado.com:50002" // mainnet
36 |
37 | certBytes, err := ioutil.ReadFile("certs/example.com.cert")
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 | certKeyBytes, err := ioutil.ReadFile("certs/example.com.key")
42 | if err != nil {
43 | log.Fatal(err)
44 | }
45 |
46 | cert, err := tls.X509KeyPair(certBytes, certKeyBytes)
47 | if err != nil {
48 | log.Fatal(err)
49 | }
50 |
51 | fmt.Printf("dialing to server: %s\n", serverAddr)
52 | conn, err := tls.Dial("tcp", serverAddr, &tls.Config{
53 | Certificates: []tls.Certificate{cert},
54 | InsecureSkipVerify: true,
55 | })
56 | if err != nil {
57 | log.Fatal(err)
58 | }
59 |
60 | defer conn.Close()
61 | fmt.Printf("client connected to: %s\n", conn.RemoteAddr())
62 |
63 | reqMsgBytes, err := json.Marshal(req)
64 | if err != nil {
65 | log.Fatal(err)
66 | }
67 |
68 | reqMsg := fmt.Sprintf("%s\n", string(reqMsgBytes))
69 | fmt.Printf("writing message: %s", reqMsg)
70 | _, err = io.WriteString(conn, reqMsg)
71 | if err != nil {
72 | log.Fatal(err)
73 | }
74 |
75 | var (
76 | i int
77 | readSize int = 1024
78 | respData []byte
79 | )
80 |
81 | for {
82 | fmt.Println("reading response...")
83 | respBytes := make([]byte, readSize)
84 | n, err := conn.Read(respBytes)
85 | if err != nil {
86 | if err != io.EOF {
87 | log.Fatal(err)
88 | }
89 | }
90 |
91 | fmt.Printf("reading: %q (%d bytes)\n", string(respBytes[:n]), n)
92 |
93 | respData = append(respData, respBytes[:n]...)
94 | i += n
95 |
96 | if n < readSize {
97 | break
98 | }
99 | }
100 |
101 | json.Unmarshal(respData[:i], &res)
102 |
103 | fmt.Printf("unconfirmed: %s\n", res.Result.Uncomfirmed.String()) // unconfirmed: 0
104 | fmt.Printf("confirmed: %s\n", res.Result.Confirmed.String()) // confirmed: 500000000
105 | }
106 |
--------------------------------------------------------------------------------
/code/accounts.txt:
--------------------------------------------------------------------------------
1 | /*
2 | SENDER
3 |
4 | private key [bytes]:
5 | [134 20 172 167 139 141 62 115 255 246 146 95 144 83 68 99 114 93 61 164 21 148 98 121 30 203 21 92 135 209 130 90]
6 |
7 | private key [hex]:
8 | 8614aca78b8d3e73fff6925f90534463725d3da4159462791ecb155c87d1825a
9 |
10 | private key [base58]:
11 | A2Pv8ferB76fb8S2HbxJkKtPNfgGY3F3jf3oPTeDuDKP
12 |
13 | private key [wif] (uncompressed):
14 | 92by5NkrizFMspyrH2C7WwSYeNTs86PwiNVNpL6LZP6DbQ2SRhg
15 |
16 | private key [wif] (compressed):
17 | cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe
18 |
19 | public key [bytes] (uncompressed):
20 | [4 216 3 244 213 162 176 49 249 79 64 236 119 48 190 216 75 172 239 45 140 67 131 217 162 246 31 102 240 218 73 61 151 155 173 232 94 181 133 64 242 62 158 235 48 11 205 10 147 244 160 37 171 176 254 153 63 184 249 93 190 235 254 27 103]
21 |
22 | public key [hex] (uncompressed):
23 | 04d803f4d5a2b031f94f40ec7730bed84bacef2d8c4383d9a2f61f66f0da493d979bade85eb58540f23e9eeb300bcd0a93f4a025abb0fe993fb8f95dbeebfe1b67
24 |
25 | public key [base58] (uncompressed):
26 | RnvtRG5JcFW9gpao5Wce9ggqybHzDate48NHAfT9kXvGjpiBfbWTvyYn8FHjWXNTqQnpFbZNo6LK1YmPHC82yM3t
27 |
28 | public key bytes (compressed):
29 | [3 216 3 244 213 162 176 49 249 79 64 236 119 48 190 216 75 172 239 45 140 67 131 217 162 246 31 102 240 218 73 61 151]
30 |
31 | public key [hex] (compressed):
32 | 03d803f4d5a2b031f94f40ec7730bed84bacef2d8c4383d9a2f61f66f0da493d97
33 |
34 | public key [base58] (compressed):
35 | 29EBg5912qskRr8RWj6vVhrWA338QgDTbwhynCvKrk42i
36 |
37 | address [base58] (uncompressed):
38 | mgjHgKi1g6qLFBM1gQwuMjjVBGMJdrs9pP
39 |
40 | address [base58] (compressed):
41 | msgZ3kjJDBkEfQZtUJYZevWN4isBnWPDPE
42 | */
43 |
44 | /*
45 | DESTINATION
46 |
47 | private key [bytes]:
48 | [32 73 70 110 60 38 135 92 170 243 60 150 175 158 182 183 79 249 198 133 3 244 148 115 9 173 24 84 128 140 135 164]
49 |
50 | private key [hex]:
51 | 2049466e3c26875caaf33c96af9eb6b74ff9c68503f4947309ad1854808c87a4
52 |
53 | private key [base58]:
54 | 3B2rNVNP3ukU16qmXa4mE2Yrohu8vsN9mFGmTQkBgYNo
55 |
56 | private key [wif] (uncompressed):
57 | 91q8sWT73Ar5SixEn2po2E6ByAPkpTD455RhWsP3JRHtmPDJRC6
58 |
59 | private key [wif] (compressed):
60 | cNfTjvzDeaTccYAoom3x8QmuQtTSQSgCYzY1pqNH9tDZUrUWZFgT
61 |
62 | public key [bytes] (uncompressed):
63 | [4 5 103 142 151 213 245 195 199 154 109 159 201 84 185 252 117 227 38 236 110 246 117 153 7 108 37 128 23 190 122 167 130 13 159 238 195 82 12 27 90 36 147 171 220 41 193 43 229 161 178 29 159 129 153 109 71 90 177 112 231 190 58 23 122]
64 |
65 | public key [hex] (uncompressed):
66 | 0405678e97d5f5c3c79a6d9fc954b9fc75e326ec6ef67599076c258017be7aa7820d9feec3520c1b5a2493abdc29c12be5a1b21d9f81996d475ab170e7be3a177a
67 |
68 | public key [base58] (uncompressed):
69 | MahoK6PFTWgskgC4wUqShNhVkwYMbbaiFXtFjkJuQ8JJJcy97wn8N92dbAdpzNR4qDJjCvzArwLbJnzaZGgEVcQ9
70 |
71 | public key bytes (compressed):
72 | [2 5 103 142 151 213 245 195 199 154 109 159 201 84 185 252 117 227 38 236 110 246 117 153 7 108 37 128 23 190 122 167 130]
73 |
74 | public key [hex] (compressed):
75 | 0205678e97d5f5c3c79a6d9fc954b9fc75e326ec6ef67599076c258017be7aa782
76 |
77 | public key [base58] (compressed):
78 | bpjNEW2MThXnR3G2T7DNVkyhmkpL5KNZemnJQAKb7tUh
79 |
80 | address [base58] (uncompressed):
81 | mgs2eXmc8Lai17pP7WvrY7QXKeXJSXpSCU
82 |
83 | address [base58] (compressed):
84 | mrdKfqWEkwferzEQus5NpgK2Dtpq7Qcgif
85 | */
86 |
--------------------------------------------------------------------------------
/code/certs/example.com.cert:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIICqDCCAZACCQDhnB8r4i5SEzANBgkqhkiG9w0BAQsFADAWMRQwEgYDVQQDDAtl
3 | eGFtcGxlLmNvbTAeFw0xODEwMDQwMjA2MzNaFw0yODEwMDEwMjA2MzNaMBYxFDAS
4 | BgNVBAMMC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
5 | AQEAtNi8gn8DJzJxpm04zbMgLExh7nYs6G5cLPKAhWAuEq6Mc9aq9yNP1W3K3Sxp
6 | njSpOJNqhi8enR6FSi1k8RowQfKcMbNOqwIXGGWB48rMxb6zQRTKswHFR42aOhzC
7 | JcVbG4BefSg0uiv3avxRWBkB2t8vVBMwWtthx1i2E92LtnOcISFHYatHVTEp2lBD
8 | SA1d6HOBXPfsZ4i1E5iM4rqHPK2mNEHmvz7ljGtuuVTSG141+0jZ5HPuTE7SIJ/M
9 | qFch6/ZXP4V5y8y8reMvwDT5Rfnt58LDahnhbenSZJs7W3kt7DF1n/rsaOnpuuCN
10 | G9BANK/ZhhHCBBrgMUPn5UbUcwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCJWq5T
11 | Dtfp4jtBzz+TV9zF67l4siPdwjxhnHhY38vzuo4DrWcvzAvaub2K01O2B9ksqVED
12 | JytLLMQEtHeLvk2CC4Dpbzrm1aTOmnKbXd6xGp9xlsfzjpzmj6GJAIkUHnWyPhZV
13 | XtjLBgeo8Ts+u89g3EylJmL3mR8Jrcr9kyKtdQFds4qq5oGIPJFYgXXu4JWcI/GL
14 | LC3Pe2D6o4qQ8xH2jNBjVVp+O9I7YN5a/TP0ebZ0Uhlaf0kfYRkxDXE6SG74p7si
15 | OdPYYwvymDoc/xkqTipbkkNwCzDmh6Ma8O13L0mUZHrXnUFTKR26Z/MSwqr2OUsk
16 | zRjKZbJ+moadN9fz
17 | -----END CERTIFICATE-----
18 |
--------------------------------------------------------------------------------
/code/certs/example.com.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAtNi8gn8DJzJxpm04zbMgLExh7nYs6G5cLPKAhWAuEq6Mc9aq
3 | 9yNP1W3K3SxpnjSpOJNqhi8enR6FSi1k8RowQfKcMbNOqwIXGGWB48rMxb6zQRTK
4 | swHFR42aOhzCJcVbG4BefSg0uiv3avxRWBkB2t8vVBMwWtthx1i2E92LtnOcISFH
5 | YatHVTEp2lBDSA1d6HOBXPfsZ4i1E5iM4rqHPK2mNEHmvz7ljGtuuVTSG141+0jZ
6 | 5HPuTE7SIJ/MqFch6/ZXP4V5y8y8reMvwDT5Rfnt58LDahnhbenSZJs7W3kt7DF1
7 | n/rsaOnpuuCNG9BANK/ZhhHCBBrgMUPn5UbUcwIDAQABAoIBAQCRBUSVuNiEdDUi
8 | 6m8ktMHWBCJ8IRP+B5GvEX/ydKA9pu9GbRyINi7szbBiEB1aGoygq8Y+eEaqZDEq
9 | vKA3n3KXT7/lMw6gn3p5u9yfGJ+A612/kLbDOWZD7M+CTlF6DHr04Mnkv3sY1+z+
10 | Q/vPE66jH1pKp2CdW1Nbkk3gPEavNUipg6fd0JpEE87wPek6w25nagfZAAAb+7Wm
11 | HOoJSfajZn8tqRPDheNosqoVC0FAifT2DpdCxE1nJY96IjMv5ByAeTqaYQR1EyrK
12 | BpPCCY2hv8BTGU1p1nWMNVTRQUNrhrHMf4CZveYf3OIUVJAc1pen4/W3IuuJ3y73
13 | SvtcBwTBAoGBAOLgsp1OFfBNcabRiBCl/JLM8qd/vTdeA97NuyE4Mce8EFAcVkgp
14 | pTPQyVBlNxtYoBUQxBysBjrtM5XaMI8tvWb/KsNMg9ufNf9/APGNPt4bzSeKYXxI
15 | /VFXM4FPdkCoa5iG8vuynJXL5r8WZwIaXus6qGNk7gbGwWw2Y4FrDNZxAoGBAMwP
16 | b7g0m5CfsIhr7QbHiBA8L9PRovvnN4h9ox8ek08jGhWbpUXPHvcYf0GqYM7fwmDx
17 | XB3oH9bCKXnKNhXEw0UKIXsWscZwW8dlpg5Df1usHJynUQtpsVnmAX5Xvio1ha5S
18 | BBq9AUerchVocHEEFFa1XerE9slfOFgsR8N5HTMjAoGAFgDc0czE6+1W3Grt2099
19 | 0271CbGl1DzV+0HQqEQe79QZcOuOoqkHUKMrIxTt50UNIX3ixzUX1ZczrZDfrMMu
20 | 31JX/2DoWOB0CDd1C/g65KelmfQdyEP77WubnyrpuROce8p6vlZwQUbpNhciHl4Y
21 | Xo/tzNX5D8cu8yPDOsX7FMECgYBVDYoPST4eBbFa60EcNkZsHeoBa7t3K2RmK5e1
22 | /NSBg6v3naxBcJcDft5rzEwVbgZiybcPcBT1OnB3JuVsJVsOh65003y9rU9TyPZx
23 | s4h9+TrjwIlzLFaTld7BfmjwxuY1RlIXovfJm5gtfB6BvKWNjoLau8XxIRMnDS3M
24 | N8sH6QKBgEwF4Hcn/h2lzXwTh5ADNEIN+yp1bFAvdfuWCzwmMo1wBGbzv557yA3H
25 | zAeQ8Pf62ydUKkSQ4Ocz5vMfUYqIsaozQAnPs9//MdmCGJ+oxsWs0NB7ow92ItrW
26 | 8qbj5FBTkK+LNT+K1Bs6Feh+X+KV9dO7mohT2uh2RS+MSGEHSQOq
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/code/transfer_coin.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "encoding/hex"
7 | "encoding/json"
8 | "errors"
9 | "fmt"
10 | "io"
11 | "io/ioutil"
12 | "log"
13 | "math/big"
14 | "math/rand"
15 | "sort"
16 | "time"
17 |
18 | "github.com/btcsuite/btcd/chaincfg"
19 | "github.com/btcsuite/btcd/chaincfg/chainhash"
20 | "github.com/btcsuite/btcd/txscript"
21 | "github.com/btcsuite/btcd/wire"
22 | "github.com/btcsuite/btcutil"
23 | )
24 |
25 | // UTXO ...
26 | type UTXO struct {
27 | Hash string
28 | TxIndex int
29 | Amount *big.Int
30 | Spendable bool
31 | PKScript []byte
32 | }
33 |
34 | func sendMsg(req, res interface{}) {
35 | //serverAddr := "electrum.qtornado.com:50002" // mainnet
36 | serverAddr := "testnet.qtornado.com:51002" // testnet
37 | //serverAddr := "testnet1.bauerj.eu:50002" // testnet
38 | //serverAddr := "testnet.hsmiths.com:53012" // testnet
39 |
40 | certBytes, err := ioutil.ReadFile("certs/example.com.cert")
41 | if err != nil {
42 | log.Fatal(err)
43 | }
44 | certKeyBytes, err := ioutil.ReadFile("certs/example.com.key")
45 | if err != nil {
46 | log.Fatal(err)
47 | }
48 |
49 | cert, err := tls.X509KeyPair(certBytes, certKeyBytes)
50 | if err != nil {
51 | log.Fatal(err)
52 | }
53 |
54 | fmt.Printf("dialing to server: %s\n", serverAddr)
55 | conn, err := tls.Dial("tcp", serverAddr, &tls.Config{
56 | Certificates: []tls.Certificate{cert},
57 | InsecureSkipVerify: true,
58 | })
59 | if err != nil {
60 | log.Fatal(err)
61 | }
62 |
63 | defer conn.Close()
64 | fmt.Printf("client connected to: %s\n", conn.RemoteAddr())
65 |
66 | reqMsgBytes, err := json.Marshal(req)
67 | if err != nil {
68 | log.Fatal(err)
69 | }
70 |
71 | reqMsg := fmt.Sprintf("%s\n", string(reqMsgBytes))
72 | fmt.Printf("writing message: %s", reqMsg)
73 | _, err = io.WriteString(conn, reqMsg)
74 | if err != nil {
75 | log.Fatal(err)
76 | }
77 |
78 | var (
79 | i int
80 | readSize int = 1024
81 | respData []byte
82 | )
83 |
84 | for {
85 | fmt.Println("reading response...")
86 | respBytes := make([]byte, readSize)
87 | n, err := conn.Read(respBytes)
88 | if err != nil {
89 | if err != io.EOF {
90 | log.Fatal(err)
91 | }
92 | }
93 |
94 | fmt.Printf("reading: %q (%d bytes)\n", string(respBytes[:n]), n)
95 |
96 | respData = append(respData, respBytes[:n]...)
97 | i += n
98 |
99 | if n < readSize {
100 | break
101 | }
102 | }
103 |
104 | json.Unmarshal(respData[:i], &res)
105 | }
106 |
107 | func main() {
108 | //chainParams := &chaincfg.MainNetParams
109 | chainParams := &chaincfg.TestNet3Params
110 |
111 | amountToSend := big.NewInt(1000000) // amount to send in satoshis (0.01 btc)
112 |
113 | feeRate, err := GetCurrentFeeRate()
114 | log.Printf("current fee rate: %v", feeRate)
115 | if err != nil {
116 | log.Fatal(err)
117 | }
118 |
119 | fromWalletPublicAddress := "mgjHgKi1g6qLFBM1gQwuMjjVBGMJdrs9pP"
120 |
121 | log.Printf("from wallet public address: %s", fromWalletPublicAddress)
122 |
123 | unspentTXOs, err := ListUnspentTXOs(fromWalletPublicAddress)
124 | if err != nil {
125 | log.Fatal(err)
126 | }
127 |
128 | unspentTXOs, UTXOsAmount, err := marshalUTXOs(unspentTXOs, amountToSend, feeRate)
129 | if err != nil {
130 | log.Fatal(err)
131 | }
132 |
133 | // prepare unspent transaction outputs with its privatekey.
134 | log.Println("unspent UTXOs", unspentTXOs, UTXOsAmount)
135 |
136 | tx := wire.NewMsgTx(wire.TxVersion)
137 |
138 | var sourceUTXOs []*UTXO
139 | // prepare tx ins
140 | for idx := range unspentTXOs {
141 | hashStr := unspentTXOs[idx].Hash
142 |
143 | sourceUTXOHash, err := chainhash.NewHashFromStr(hashStr)
144 | if err != nil {
145 | log.Fatal(err)
146 | }
147 |
148 | sourceUTXOIndex := uint32(unspentTXOs[idx].TxIndex)
149 | sourceUTXO := wire.NewOutPoint(sourceUTXOHash, sourceUTXOIndex)
150 | sourceUTXOs = append(sourceUTXOs, unspentTXOs[idx])
151 | sourceTxIn := wire.NewTxIn(sourceUTXO, nil, nil)
152 |
153 | tx.AddTxIn(sourceTxIn)
154 | }
155 |
156 | // calculate fees
157 | txByteSize := big.NewInt(int64(len(tx.TxIn)*180 + len(tx.TxOut)*34 + 10 + len(tx.TxIn)))
158 | totalFee := new(big.Int).Mul(feeRate, txByteSize)
159 | log.Printf("total fee: %s", totalFee)
160 |
161 | // calculate the change
162 | change := new(big.Int).Set(UTXOsAmount)
163 | change = new(big.Int).Sub(change, amountToSend)
164 | change = new(big.Int).Sub(change, totalFee)
165 | if change.Cmp(big.NewInt(0)) == -1 {
166 | log.Fatal(err)
167 | }
168 |
169 | destinationAddress := "mgs2eXmc8Lai17pP7WvrY7QXKeXJSXpSCU"
170 |
171 | // create the tx outs
172 | destAddress, err := btcutil.DecodeAddress(destinationAddress, chainParams)
173 | if err != nil {
174 | log.Fatal(err)
175 | }
176 |
177 | destScript, err := txscript.PayToAddrScript(destAddress)
178 | if err != nil {
179 | log.Fatal(err)
180 | }
181 |
182 | // tx out to send btc to user
183 | destOutput := wire.NewTxOut(amountToSend.Int64(), destScript)
184 | tx.AddTxOut(destOutput)
185 |
186 | // our change address
187 | changeSendToAddress, err := btcutil.DecodeAddress(fromWalletPublicAddress, chainParams)
188 | if err != nil {
189 | log.Fatal(err)
190 | }
191 |
192 | changeSendToScript, err := txscript.PayToAddrScript(changeSendToAddress)
193 | if err != nil {
194 | log.Fatal(err)
195 | }
196 |
197 | // tx out to send change back to us
198 | changeOutput := wire.NewTxOut(change.Int64(), changeSendToScript)
199 | tx.AddTxOut(changeOutput)
200 |
201 | privWif := "cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe"
202 |
203 | decodedWif, err := btcutil.DecodeWIF(privWif)
204 | if err != nil {
205 | log.Fatal(err)
206 | }
207 |
208 | addressPubKey, err := btcutil.NewAddressPubKey(decodedWif.PrivKey.PubKey().SerializeUncompressed(), chainParams)
209 | if err != nil {
210 | log.Fatal(err)
211 | }
212 | sourceAddress, err := btcutil.DecodeAddress(addressPubKey.EncodeAddress(), chainParams)
213 | if err != nil {
214 | log.Fatal(err)
215 | }
216 |
217 | fmt.Printf("Source Address: %s\n", sourceAddress) // Source Address: mgjHgKi1g6qLFBM1gQwuMjjVBGMJdrs9pP
218 |
219 | sourcePkScript, err := txscript.PayToAddrScript(sourceAddress)
220 | if err != nil {
221 | log.Fatal(err)
222 | }
223 |
224 | for i := range sourceUTXOs {
225 | sigScript, err := txscript.SignatureScript(tx, i, sourcePkScript, txscript.SigHashAll, decodedWif.PrivKey, false)
226 | if err != nil {
227 | log.Fatalf("could not generate pubSig; err: %v", err)
228 | }
229 | tx.TxIn[i].SignatureScript = sigScript
230 | }
231 |
232 | buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
233 | tx.Serialize(buf)
234 |
235 | fmt.Printf("Redeem Tx: %v\n", hex.EncodeToString(buf.Bytes()))
236 |
237 | t := hex.EncodeToString(buf.Bytes())
238 | txHash, err := SendTX(t)
239 | if err != nil {
240 | log.Fatal(err)
241 | }
242 |
243 | fmt.Printf("tx hash: %s\n", txHash) // 1d8f70dfc8b90bff672ee663a7cc811c4e88e98c6895dc93aa9f73202bb7809b
244 | }
245 |
246 | func marshalUTXOs(utxos []*UTXO, amount, feeRate *big.Int) ([]*UTXO, *big.Int, error) {
247 | // same strategy as bitcoin core
248 | // from: https://medium.com/@lopp/the-challenges-of-optimizing-unspent-output-selection-a3e5d05d13ef
249 | // 1. sort the UTXOs from smallest to largest amounts
250 | sort.Slice(utxos, func(i, j int) bool {
251 | return utxos[i].Amount.Cmp(utxos[j].Amount) == -1
252 | })
253 |
254 | // 2. search for exact match
255 | for idx := range utxos {
256 | exactTxSize := calculateTotalTxBytes(1, 2)
257 | totalFee := new(big.Int).Mul(feeRate, big.NewInt(int64(exactTxSize)))
258 | totalTxAmount := new(big.Int).Add(totalFee, amount)
259 |
260 | switch utxos[idx].Amount.Cmp(totalTxAmount) {
261 | case 0:
262 | var resp []*UTXO
263 | resp = append(resp, utxos[idx])
264 | // TODO: store these in the DB to be sure they aren't being claimed??
265 | return resp, sumUTXOs(resp), nil
266 |
267 | case 1:
268 | break
269 | }
270 | }
271 |
272 | // 3. calculate the sum of all UTXOs smaller than amount
273 | sumSmall := big.NewInt(0)
274 | var sumSmallUTXOs []*UTXO
275 | for idx := range utxos {
276 | switch utxos[idx].Amount.Cmp(amount) {
277 | case -1:
278 | _ = sumSmall.Add(sumSmall, utxos[idx].Amount)
279 | sumSmallUTXOs = append(sumSmallUTXOs, utxos[idx])
280 |
281 | default:
282 | break
283 | }
284 | }
285 |
286 | exactTxSize := calculateTotalTxBytes(len(sumSmallUTXOs), 2)
287 | totalFee := new(big.Int).Mul(feeRate, big.NewInt(int64(exactTxSize)))
288 | totalTxAmount := new(big.Int).Add(totalFee, amount)
289 |
290 | switch sumSmall.Cmp(totalTxAmount) {
291 | case 0:
292 | return sumSmallUTXOs, sumUTXOs(sumSmallUTXOs), nil
293 |
294 | case -1:
295 | for idx := range utxos {
296 | exactTxSize := calculateTotalTxBytes(1, 2)
297 | totalFee := new(big.Int).Mul(feeRate, big.NewInt(int64(exactTxSize)))
298 | totalTxAmount := new(big.Int).Add(totalFee, amount)
299 | if utxos[idx].Amount.Cmp(totalTxAmount) == 1 {
300 | var resp []*UTXO
301 | resp = append(resp, utxos[idx])
302 | return resp, sumUTXOs(resp), nil
303 | }
304 | }
305 |
306 | // should reach here if not enought UXOs
307 | log.Fatal("not enough UTXOs to meet target amount")
308 |
309 | case 1:
310 | return roundRobinSelectUTXOs(sumSmallUTXOs, amount, feeRate)
311 |
312 | default:
313 | log.Fatal("unknown comparison")
314 | }
315 |
316 | return nil, nil, nil
317 | }
318 |
319 | func roundRobinSelectUTXOs(utxos []*UTXO, amount, feeRate *big.Int) ([]*UTXO, *big.Int, error) {
320 | var possibilities [][]*UTXO
321 | lenInput := len(utxos)
322 | log.Printf("round robin select; lenInput: %v", lenInput)
323 | if lenInput == 0 {
324 | log.Fatal("expected utxos size to be greater than 0")
325 | }
326 |
327 | for i := 0; i < 1000; i++ {
328 | selectedIdxs := make(map[int]bool)
329 | var sum *big.Int
330 | var possibility []*UTXO
331 | for {
332 | for {
333 | rand.Seed(time.Now().Unix())
334 | tmp := 0
335 | if lenInput > 1 {
336 | tmp = rand.Intn(lenInput - 1)
337 | }
338 |
339 | if !selectedIdxs[tmp] {
340 | selectedIdxs[tmp] = true
341 | _ = sum.Add(sum, utxos[tmp].Amount)
342 | possibility = append(possibility, utxos[tmp])
343 |
344 | break
345 | }
346 | }
347 |
348 | exactTxSize := calculateTotalTxBytes(len(possibility), 2)
349 | totalFee := new(big.Int).Mul(feeRate, big.NewInt(int64(exactTxSize)))
350 | totalTxAmount := new(big.Int).Add(totalFee, amount)
351 |
352 | if sum.Cmp(totalTxAmount) == 0 {
353 | return possibility, sum, nil
354 | }
355 |
356 | if sum.Cmp(totalTxAmount) == 1 {
357 | possibilities = append(possibilities, possibility)
358 | break
359 | }
360 | }
361 | }
362 |
363 | if len(possibilities) < 1 {
364 | return nil, nil, errors.New("no possible utxo combos")
365 | }
366 |
367 | smallestLen := len(possibilities[0])
368 | smallestIdx := 0
369 |
370 | for idx := 1; idx < len(possibilities); idx++ {
371 | l := len(possibilities[idx])
372 | if l < smallestLen {
373 | smallestLen = l
374 | smallestIdx = idx
375 | }
376 | }
377 |
378 | return possibilities[smallestIdx], sumUTXOs(possibilities[smallestIdx]), nil
379 | }
380 |
381 | func sumUTXOs(utxos []*UTXO) *big.Int {
382 | sum := big.NewInt(0)
383 | for idx := range utxos {
384 | sum = sum.Add(sum, utxos[idx].Amount)
385 | }
386 |
387 | return sum
388 | }
389 |
390 | // https://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending-legacy-non-segwit-p2pkh-p2sh
391 | func calculateTotalTxBytes(txInLength, txOutLength int) int {
392 | return txInLength*180 + txOutLength*34 + 10 + txInLength
393 | }
394 |
395 | func decodeRawTx(rawTx string) (*wire.MsgTx, error) {
396 | raw, err := hex.DecodeString(rawTx)
397 | if err != nil {
398 | log.Printf("err decoding raw tx; err: %v", err)
399 | return nil, err
400 | }
401 |
402 | var version int32 = 2
403 | if rawTx[:8] == "01000000" {
404 | version = 1
405 | }
406 | log.Printf("version: %d", version)
407 |
408 | r := bytes.NewReader(raw)
409 | tmpTx := wire.NewMsgTx(version)
410 |
411 | err = tmpTx.BtcDecode(r, uint32(version), wire.BaseEncoding)
412 | if err != nil {
413 | log.Printf("could not decode raw tx; err: %v", err)
414 | return nil, err
415 | }
416 |
417 | return tmpTx, nil
418 | }
419 |
420 | // GetCurrentFee gets the current fee in bitcoin
421 | func GetCurrentFee() (float64, error) {
422 | req := struct {
423 | ID int `json:"id"`
424 | Method string `json:"method"`
425 | Params []int `json:"params"`
426 | }{
427 | ID: 1,
428 | Method: "blockchain.estimatefee",
429 | Params: []int{2},
430 | }
431 |
432 | msg := struct {
433 | JSONRPC string `json:"jsonrpc,omitempty"`
434 | ID int `json:"id"`
435 | Result float64 `json:"result"`
436 | }{}
437 |
438 | var fee float64
439 | var MaxTries = 5
440 | for try := 0; try < MaxTries; try++ {
441 | sendMsg(req, &msg)
442 |
443 | if msg.Result == -1.0 || msg.Result == 0 {
444 | log.Printf("expected result > 0; received: %f", msg.Result)
445 | continue
446 | }
447 |
448 | fee = msg.Result
449 | // sanity check
450 | if fee > 0.05 {
451 | fee = 0.1
452 | } else if fee < 0 {
453 | fee = 0
454 | }
455 |
456 | break
457 | }
458 |
459 | fmt.Printf("fee: %f\n", fee)
460 |
461 | if fee == 0 {
462 | log.Print("could not get fees")
463 | return fee, errors.New("could not get fees")
464 | }
465 |
466 | return fee, nil
467 | }
468 |
469 | // GetCurrentFeeRate gets the current fee in satoshis per kb
470 | func GetCurrentFeeRate() (*big.Int, error) {
471 | fee, err := GetCurrentFee()
472 | if err != nil {
473 | return nil, err
474 | }
475 |
476 | // convert to satoshis to bytes
477 | // feeRate := big.NewInt(int64(msg.Result * 1.0E8))
478 | // convert to satoshis to kb
479 | feeRate := big.NewInt(int64(fee * 1.0E5))
480 |
481 | fmt.Printf("fee rate: %s\n", feeRate)
482 |
483 | return feeRate, nil
484 | }
485 |
486 | // ListUnspentTXOs lists all UTXOs for an address
487 | func ListUnspentTXOs(address string) ([]*UTXO, error) {
488 | req := struct {
489 | ID int `json:"id"`
490 | Method string `json:"method"`
491 | Params []string `json:"params"`
492 | }{
493 | ID: 1,
494 | Method: "blockchain.address.listunspent",
495 | Params: []string{address},
496 | }
497 |
498 | msg := struct {
499 | JSONRPC string `json:"jsonrpc,omitempty"`
500 | ID int `json:"id"`
501 | Result []struct {
502 | TXHash string `json:"tx_hash"`
503 | TXPosition uint64 `json:"tx_pos"`
504 | Value *big.Int `json:"value"`
505 | Height uint64 `json:"height"`
506 | } `json:"result"`
507 | }{}
508 |
509 | var MaxTries = 5
510 | for try := 0; try < MaxTries; try++ {
511 | sendMsg(req, &msg)
512 |
513 | var utxos []*UTXO
514 | for idx := range msg.Result {
515 | utxos = append(utxos, &UTXO{
516 | Hash: msg.Result[idx].TXHash,
517 | TxIndex: int(msg.Result[idx].TXPosition),
518 | Amount: msg.Result[idx].Value,
519 | Spendable: true,
520 | })
521 | }
522 |
523 | return utxos, nil
524 | }
525 |
526 | log.Printf("could not get utxos")
527 | return nil, errors.New("could not get utxos")
528 | }
529 |
530 | // GetRawTransaction gets raw transaction data given transaction ID (hash)
531 | func GetRawTransaction(txHash string) ([]byte, error) {
532 | req := struct {
533 | ID int `json:"id"`
534 | Method string `json:"method"`
535 | Params []string `json:"params"`
536 | }{
537 | ID: 1,
538 | Method: "blockchain.transaction.get",
539 | Params: []string{txHash},
540 | }
541 |
542 | msg := struct {
543 | JSONRPC string `json:"jsonrpc,omitempty"`
544 | ID int `json:"id"`
545 | Result string `json:"result"`
546 | }{}
547 |
548 | var MaxTries = 5
549 | for try := 0; try < MaxTries; try++ {
550 | sendMsg(req, &msg)
551 |
552 | b, err := hex.DecodeString(msg.Result)
553 | if err != nil {
554 | log.Printf("could not decode tx raw data to bytes; err: %v", err)
555 | return nil, err
556 | }
557 |
558 | return b, nil
559 | }
560 |
561 | log.Print("could not get transaction info")
562 | return nil, errors.New("could not get transaction info")
563 | }
564 |
565 | // GetTransaction gets transaction data given transaction ID (hash)
566 | func GetTransaction(txHash string) (*wire.MsgTx, error) {
567 | rawTx, err := GetRawTransaction(txHash)
568 | if err != nil {
569 | log.Printf("err getting raw tx; err: %v", err)
570 | return nil, err
571 | }
572 |
573 | fmt.Println("RAW", hex.EncodeToString(rawTx))
574 |
575 | tx, err := decodeRawTx(hex.EncodeToString(rawTx))
576 | if err != nil {
577 | log.Printf("err parsing raw tx; err: %v", err)
578 | return nil, err
579 | }
580 |
581 | return tx, nil
582 | }
583 |
584 | // SendTX sends a transaction on the wire
585 | func SendTX(tx string) (string, error) {
586 | req := struct {
587 | ID int `json:"id"`
588 | Method string `json:"method"`
589 | Params []string `json:"params"`
590 | }{
591 | ID: 1,
592 | Method: "blockchain.transaction.broadcast",
593 | Params: []string{tx},
594 | }
595 |
596 | msg := struct {
597 | JSONRPC string `json:"jsonrpc,omitempty"`
598 | ID int `json:"id"`
599 | Result string `json:"result"`
600 | }{}
601 |
602 | log.Print("attempting to send bitcoin tx")
603 | var MaxTries = 5
604 | for try := 0; try < MaxTries; try++ {
605 | sendMsg(req, &msg)
606 |
607 | return msg.Result, nil
608 | }
609 |
610 | log.Print("could not broadcast tx")
611 | return "", errors.New("could not broadcast tx")
612 | }
613 |
--------------------------------------------------------------------------------
/code/transfer_coin_simple.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/hex"
6 | "fmt"
7 | "log"
8 |
9 | "github.com/btcsuite/btcd/chaincfg"
10 | "github.com/btcsuite/btcd/chaincfg/chainhash"
11 | "github.com/btcsuite/btcd/txscript"
12 | "github.com/btcsuite/btcd/wire"
13 | "github.com/btcsuite/btcutil"
14 | )
15 |
16 | func main() {
17 | privWif := "cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe"
18 | txHash := "12e0d25258ec29fadf75a3f569fccaeeb8ca4af5d2d34e9a48ab5a6fdc0efc1e"
19 | destination := "mrdKfqWEkwferzEQus5NpgK2Dtpq7Qcgif"
20 | amount := int64(11650795)
21 | txFee := int64(500000)
22 | sourceUTXOIndex := uint32(1)
23 | chainParams := &chaincfg.TestNet3Params // testnet
24 | // chainParams := &chaincfg.MainNetParams // mainnet
25 |
26 | decodedWif, err := btcutil.DecodeWIF(privWif)
27 | if err != nil {
28 | log.Fatal(err)
29 | }
30 |
31 | fmt.Printf("Decoded WIF: %v\n", decodedWif) // Decoded WIF: cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe
32 |
33 | addressPubKey, err := btcutil.NewAddressPubKey(decodedWif.PrivKey.PubKey().SerializeUncompressed(), chainParams)
34 | if err != nil {
35 | log.Fatal(err)
36 | }
37 |
38 | sourceUTXOHash, err := chainhash.NewHashFromStr(txHash)
39 | if err != nil {
40 | log.Fatal(err)
41 | }
42 |
43 | fmt.Printf("UTXO hash: %s\n", sourceUTXOHash) // utxo hash: 12e0d25258ec29fadf75a3f569fccaeeb8ca4af5d2d34e9a48ab5a6fdc0efc1e
44 |
45 | sourceUTXO := wire.NewOutPoint(sourceUTXOHash, sourceUTXOIndex)
46 | sourceTxIn := wire.NewTxIn(sourceUTXO, nil, nil)
47 | destinationAddress, err := btcutil.DecodeAddress(destination, chainParams)
48 | if err != nil {
49 | log.Fatal(err)
50 | }
51 |
52 | sourceAddress, err := btcutil.DecodeAddress(addressPubKey.EncodeAddress(), chainParams)
53 | if err != nil {
54 | log.Fatal(err)
55 | }
56 |
57 | fmt.Printf("Source Address: %s\n", sourceAddress) // Source Address: mgjHgKi1g6qLFBM1gQwuMjjVBGMJdrs9pP
58 |
59 | destinationPkScript, err := txscript.PayToAddrScript(destinationAddress)
60 | if err != nil {
61 | log.Fatal(err)
62 | }
63 |
64 | sourcePkScript, err := txscript.PayToAddrScript(sourceAddress)
65 | if err != nil {
66 | log.Fatal(err)
67 | }
68 |
69 | sourceTxOut := wire.NewTxOut(amount, sourcePkScript)
70 |
71 | redeemTx := wire.NewMsgTx(wire.TxVersion)
72 | redeemTx.AddTxIn(sourceTxIn)
73 | redeemTxOut := wire.NewTxOut((amount - txFee), destinationPkScript)
74 | redeemTx.AddTxOut(redeemTxOut)
75 |
76 | sigScript, err := txscript.SignatureScript(redeemTx, 0, sourceTxOut.PkScript, txscript.SigHashAll, decodedWif.PrivKey, false)
77 | if err != nil {
78 | log.Fatal(err)
79 | }
80 |
81 | redeemTx.TxIn[0].SignatureScript = sigScript
82 | fmt.Printf("Signature Script: %v\n", hex.EncodeToString(sigScript)) // Signature Script: 473...b67
83 |
84 | // validate signature
85 | flags := txscript.StandardVerifyFlags
86 | vm, err := txscript.NewEngine(sourceTxOut.PkScript, redeemTx, 0, flags, nil, nil, amount)
87 | if err != nil {
88 | log.Fatal(err)
89 | }
90 |
91 | if err := vm.Execute(); err != nil {
92 | log.Fatal(err)
93 | }
94 |
95 | buf := bytes.NewBuffer(make([]byte, 0, redeemTx.SerializeSize()))
96 | redeemTx.Serialize(buf)
97 |
98 | fmt.Printf("Redeem Tx: %v\n", hex.EncodeToString(buf.Bytes())) // redeem Tx: 01000000011efc...5bb88ac00000000
99 | }
100 |
--------------------------------------------------------------------------------
/code/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "crypto/sha256"
5 |
6 | "github.com/btcsuite/btcutil/base58"
7 | )
8 |
9 | func P2PKHToAddress(pkscript []byte, isTestnet bool) (string, error) {
10 | p := make([]byte, 1)
11 | p[0] = 0x00 // prefix with 00 if it's mainnet
12 | if isTestnet {
13 | p[0] = 0x6F // prefix with 0F if it's testnet
14 | }
15 | pub := pkscript[3 : len(pkscript)-2] // get pkhash
16 | pf := append(p[:], pub[:]...) // add prefix
17 | h1 := sha256.Sum256(pf) // hash it
18 | h2 := sha256.Sum256(h1[:]) // hash it again
19 | b := append(pf[:], h2[0:4]...) // prepend the prefix to the first 5 bytes
20 | address := base58.Encode(b) // encode to base58
21 | if !isTestnet {
22 | address = "1" + address // prefix with 1 if it's mainnet
23 | }
24 |
25 | return address, nil
26 | }
27 |
--------------------------------------------------------------------------------
/code/wallet_decode.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/hex"
5 | "fmt"
6 | "log"
7 |
8 | "github.com/btcsuite/btcd/btcec"
9 | )
10 |
11 | func main() {
12 | privHex := "12d6913912cedcd1859778902bde0f737740ffb532cd1335b08aff159c474038"
13 | privBytes, err := hex.DecodeString(privHex)
14 | if err != nil {
15 | log.Fatal(err)
16 | }
17 |
18 | priv, _ := btcec.PrivKeyFromBytes(btcec.S256(), privBytes)
19 | fmt.Printf("private key [hex]:\n%x\n\n", priv.Serialize())
20 |
21 | /*
22 | privUncWifB58 := "2GY6yKFr8FRX25zPtrAzLRko1Uryz7QWPy94Hw7i6Vaw"
23 | privWif, err := btcutil.DecodeWIF(privUncWifB58)
24 | if err != nil {
25 | log.Fatal(err)
26 | }
27 | priv2 := privWif.PrivKey
28 | fmt.Printf("private key2 [hex]:\n%x\n\n", priv2.Serialize())
29 | */
30 |
31 | uncPubHex := "048be27052fff64179cdb83d5e360606e6c696cf05445815cdb8ed2f47f8bb0a8e11af9ab997ef643262df572defb1af55ea876b48830ca99585e613cd7ac04ab0"
32 | uncPubBytes, err := hex.DecodeString(uncPubHex)
33 | if err != nil {
34 | log.Fatal(err)
35 | }
36 |
37 | pub, err := btcec.ParsePubKey(uncPubBytes, btcec.S256())
38 | if err != nil {
39 | log.Fatal(err)
40 | }
41 |
42 | fmt.Printf("public key [hex] (uncompressed):\n%x\n\n", pub.SerializeUncompressed())
43 | }
44 |
--------------------------------------------------------------------------------
/code/wallet_generate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/hex"
5 | "fmt"
6 | "log"
7 |
8 | "github.com/btcsuite/btcd/btcec"
9 | "github.com/btcsuite/btcd/chaincfg"
10 | "github.com/btcsuite/btcutil"
11 | "github.com/btcsuite/btcutil/base58"
12 | )
13 |
14 | func main() {
15 | //chain := &chaincfg.MainNetParams // mainnet
16 | chain := &chaincfg.TestNet3Params // testnet
17 |
18 | priv, err := btcec.NewPrivateKey(btcec.S256())
19 | if err != nil {
20 | log.Fatal(err)
21 | }
22 |
23 | privBytes := priv.Serialize()
24 | fmt.Printf("private key [bytes]:\n%v\n\n", privBytes) // [18 214 ... 64 56]
25 | fmt.Printf("private key [hex]:\n%s\n\n", hex.EncodeToString(privBytes)) // 12d6913912cedcd1859778902bde0f737740ffb532cd1335b08aff159c474038
26 | fmt.Printf("private key [base58]:\n%s\n\n", base58.Encode(privBytes)) // 2GY6yKFr8FRX25zPtrAzLRko1Uryz7QWPy94Hw7i6Vaw
27 |
28 | uncWif, err := btcutil.NewWIF(priv, chain, false)
29 | if err != nil {
30 | log.Fatal(err)
31 | }
32 |
33 | fmt.Printf("private key [wif] (uncompressed):\n%s\n\n", uncWif.String()) // 5Jim1MwMAu5WY8puAKL4gLE7tTKijSqoa9rqXhPWeT38Jd1AfsD
34 |
35 | cmpWif, err := btcutil.NewWIF(priv, chain, true)
36 | if err != nil {
37 | log.Fatal(err)
38 | }
39 |
40 | fmt.Printf("private key [wif] (compressed):\n%s\n\n", cmpWif.String()) // 2GY6yKFr8FRX25zPtrAzLRko1Uryz7QWPy94Hw7i6Vaw
41 |
42 | pub := priv.PubKey()
43 | uncPubBytes := pub.SerializeUncompressed()
44 | cmpPubBytes := pub.SerializeCompressed()
45 | fmt.Printf("public key [bytes] (uncompressed):\n%v\n\n", uncPubBytes) // [4 210 ... 154 207]
46 | fmt.Printf("public key [hex] (uncompressed):\n%s\n\n", hex.EncodeToString(uncPubBytes)) // 04d28f502980c5e874c3dd2e4aff019b18e3bef83b5828cf974ffc87c8b0f94576611afbf8780fbff9e6a31c7e3b5385b3d24a0777a8b8f37cd6355ed43d219acf
47 | fmt.Printf("public key [base58] (uncompressed):\n%s\n\n", base58.Encode(uncPubBytes)) // RgbxSrecyPCc3jsEcDmLh5ERueFyrz7m1QEg3U4SUQAZhoPABbik2GvS9adSRHHTV3f2ourctb4qPjuYiyiLdH3k
48 | fmt.Printf("public key bytes (compressed):\n%v\n\n", cmpPubBytes) // [3 210 ... 69 118]
49 | fmt.Printf("public key [hex] (compressed):\n%s\n\n", hex.EncodeToString(cmpPubBytes)) // 03d28f502980c5e874c3dd2e4aff019b18e3bef83b5828cf974ffc87c8b0f94576
50 | fmt.Printf("public key [base58] (compressed):\n%s\n\n", base58.Encode(cmpPubBytes)) // 28rtUZpHgFeEKjkBzTqRxGwohCF8KmSaMS9o38VGzoA3X
51 |
52 | uncAddr, err := btcutil.NewAddressPubKey(uncPubBytes, chain)
53 | if err != nil {
54 | log.Fatal(err)
55 | }
56 |
57 | cmpAddr, err := btcutil.NewAddressPubKey(cmpPubBytes, chain)
58 | if err != nil {
59 | log.Fatal(err)
60 | }
61 |
62 | encUncAddr := uncAddr.EncodeAddress()
63 | encCmpAddr := cmpAddr.EncodeAddress()
64 | fmt.Printf("address [base58] (uncompressed):\n%s\n\n", encUncAddr) // 16385kYLPqkczsyhJirzjunz27bTpqJrNm
65 | fmt.Printf("address [base58] (compressed):\n%s\n\n", encCmpAddr) // 15xQjUYRuk59ijmbCkSFTiP7zYWD4NVN1G
66 | }
67 |
--------------------------------------------------------------------------------
/en/GLOSSARY.md:
--------------------------------------------------------------------------------
1 | # Glossary
2 |
3 | ## Addresses
4 |
5 | Used to receive and send transactions on the network. An address is a string of alphanumeric characters, but can also be represented as a scannable QR code. They are derived from the public/private ECDSA key pair.
6 |
7 | ## Agreement Ledgers
8 |
9 | Distributed ledgers used by two or more parties to negotiate and reach and agreement.
10 |
11 | ## Altcoin
12 |
13 | An abbreviation of "Bitcoin alternative". Currently, the majority of altcoins are forks of Bitcoin with usually minor changes to the proof of work (POW) algorithm of the Bitcoin blockchain. The most prominent altcoin is Litecoin. Litecoin introduces changes to the original Bitcoin protocol such as decreased block generation time, increased maximum number of coins and different hashing algorithm.
14 |
15 | ## Attestation Ledgers
16 |
17 | Distributed ledgers that provide a durable record of agreements, commitments or statements, providing evidence (attestation) that these agreements, commitments or statements were made.
18 |
19 | ## ASIC
20 |
21 | An acronym for "Application Specific Integrated Circuit". ASICs are silicon chips specifically designed to do a single task. In the case of bitcoin, they are designed to process SHA-256 hashing problems to mine new bitcoins.
22 |
23 | ## Bitcoin
24 |
25 | Currently the most well known cryptocurrency, based on the proof-of-work blockchain.
26 |
27 | ## Blockchain
28 |
29 | A type of distributed ledger, comprised of unchangable, digitally recorded data in packages called blocks (rather like collating them on to a single sheet of paper). Each block is then ‘chained’ to the next block, using a cryptographic signature. This allows block chains to be used like a ledger, which can be shared and accessed by anyone with the appropriate permissions.
30 |
31 | ## Block Ciphers
32 |
33 | A method of encrypting text (to produce ciphertext) in which a cryptographic key and algorithm are applied to a block of data at once as a group rather than to one bit at a time.
34 |
35 | ## Block Height
36 |
37 | Refers to the number of blocks connected together in the block chain. For example, Height 0, would be the very first block, which is also called the Genesis Block.
38 |
39 | ## Block Rewards
40 |
41 | Rewards given to a miner which has successfully hashed a transaction block. Block rewards can be a mixture of coins and transaction fees, depending on the policy used by the cryptocurrency in question, and whether all of the coins have already been successfully mined. The current block reward for the Bitcoin network is 25 bitcoins for each block.
42 |
43 | ## Central Ledger
44 |
45 | Refers to a ledger maintained by a central agency.
46 |
47 | ## Chain Linking
48 |
49 | The process of connecting two blockchains with each other, thus allowing transactions between the chains to take place. This will allow blockchains like Bitcoin to communicate with other sidechains, allowing the exchange of assets between them
50 |
51 | ## Cipher
52 |
53 | The algorithm used for the encryption and/or decryption of information. In common language, 'cipher' is also used to refer to an encryption message, also known as 'code'.
54 |
55 | ## Confirmation
56 |
57 | The blockchain transaction has been verified by the network. This happens through a process known as mining, in a proof-of-work system (e.g. Bitcoin). Once a transaction is confirmed, it cannot be reversed or double spent. The more confirmations a transaction has, the harder it becomes to perform a double spend attack.
58 |
59 | ## Consensus Process
60 |
61 | A group of peers responsible for maintaining a distributed ledger use to reach consensus on the ledger's contents.
62 |
63 | ## Consortium Blockchain
64 |
65 | A blockchain where the consensus process is controlled by a pre-selected set of nodes; for example, one might imagine a consortium of 15 financial institutions, each of which operates a node and of which ten must sign every block for the block to be valid. The right to read the blockchain may be public or restricted to the participants. There are also hybrid routes such as the root hashes of the blocks being public together with an API that allows members of the public to make a limited number of queries and get back cryptographic proofs of some parts of the blockchain state. These blockchains may be considered "partially decentralized".
66 |
67 | ## Cryptoanalysis
68 |
69 | The study of methods for obtaining the meaning of encrypted information, without access to the secret information that is normally required to do so.
70 |
71 | ## Cryptocurrency
72 |
73 | A form of digital currency based on mathematics, where encryption techniques are used to regulate the generation of units of currency and verify the transfer of funds. Furthermore, cryptocurrencies operate independently of a central bank.
74 |
75 | ## Cryptography
76 |
77 | Refers to the process of encrypting and decrypting information.
78 |
79 | ## Decryption
80 |
81 | The process of turning cipher-text back into plaintext
82 |
83 | ## Encryption
84 |
85 | The process of turning a clear-text message (plaintext) into a data stream (cipher-text), which looks like a meaningless and random sequence of bits.
86 |
87 | ## Digital Commodity
88 |
89 | A scarce, electronically transferrable, intangible, with a market value.
90 |
91 | ## Digital Identity
92 |
93 | An online or networked identity adopted or claimed in cyberspace by an individual, organization, or electronic device.
94 |
95 | ## Distributed Ledgers
96 |
97 | A type of database that are spread across multiple sites, countries or institutions. Records are stored one after the other in a continuous ledger. Distributed ledger data can be either "permissioned" or "unpermissioned" to control who can view it.
98 |
99 | ## Difficulty
100 |
101 | In Proof-of-Work mining, is how hard it is to verify blocks in a blockchain network. In the Bitcoin network, the difficulty of mining adjusts verifying blocks every 2016 blocks. This is to keep block verification time at ten minutes.
102 |
103 | ## Double Spend
104 |
105 | Refers to a scenario, in the Bitcoin network, where someone tries to send a bitcoin transaction to two different recipients at the same time. However, once a bitcoin transaction is confirmed, it makes it nearly impossible to double spend it. The more confirmations that a particular transaction has, the harder it becomes to double spend the bitcoins.
106 |
107 | ## Fiat currency
108 |
109 | is any money declared by a government to be to be valid for meeting a financial obligation, like USD or EUR.
110 |
111 | ## Fork
112 |
113 | The creation of an ongoing alternative version of the blockchain, by creating two blocks simultaneously on different parts of the network. This creates two parallel blockchains, where one of the two is the winning blockchain.
114 |
115 | ## Go
116 |
117 | Go is a programming language created at Google in 2009 by Robert Griesemer, Rob Pike, and Ken Thompson.
118 |
119 | ## Golang
120 |
121 | The Go programming language.
122 |
123 | ## Halving
124 |
125 | Bitcoins have a finite supply, which makes them a scarce digital commodity. The total amount of bitcoins that will ever be issued is 21 million. The number of bitcoins generated per block is decreased 50% every four years. This is called "halving". The final halving will take place in the year 2140.
126 |
127 | ## Hard fork
128 |
129 | A change to the blockchain protocol that makes previously invalid blocks/transactions valid, and therefore requires all users to upgrade their clients.
130 |
131 | ## Hashcash
132 |
133 | A proof-of-work system used to limit email spam and denial-of-service attacks, and more recently has become known for its use in bitcoin (and other cryptocurrencies) as part of the mining algorithm.
134 |
135 | ## Hashrate
136 |
137 | The number of hashes that can be performed by a bitcoin miner in a given period of time (usually a second).
138 |
139 | ## HD Wallet
140 |
141 | An HD Wallet, or Hierarchical Deterministic wallet, is a new-age digital wallet that automatically generates a hierarchical tree-like structure of private/public addresses (or keys), thereby addressing the problem of the user having to generate them on his own.
142 |
143 | ## Initial Coin Offering
144 |
145 | (ICO) is an event in which a new cryptocurrency sells advance tokens from its overall coinbase, in exchange for upfront capital. ICOs are frequently used for developers of a new cryptocurrency to raise capital.
146 |
147 | ## Keystore
148 |
149 | A file containing an encrypted wallet private keys and wallet metadata.
150 |
151 | ## Ledger
152 |
153 | An append-only record store, where records are immutable and may hold more general information than financial records.
154 |
155 | ## Litecoin
156 |
157 | A peer-to-peer cryptocurrency based on the Scrypt proof-of-work network. Sometimes referred to as the silver of bitcoin’s gold.
158 |
159 | ## Mining
160 |
161 | The process by which transactions are verified and added to a blockchain. This process of solving cryptographic problems using computing hardware also triggers the release of cryptocurrencies.
162 |
163 | ## Mnemonic
164 |
165 | A mnemonic phrase, mnemonic recovery phrase or mnemonic seed is a list of words used as a seed to generate the master private key and master chain code for an HD wallet.
166 |
167 | ## Multi-signature
168 |
169 | (multisig) addresses allow multiple parties to require more than one key to authorize a transaction. The needed number of signatures is agreed at the creation of the address. Multi signature addresses have a much greater resistance to theft.
170 |
171 | ## Node
172 |
173 | Any computer that connects to the blockchain network.
174 |
175 | ## Nonce
176 |
177 | A number only used once.
178 |
179 | ## Full node
180 |
181 | A node that fully enforces all of the rules of the blockchain.
182 |
183 | ## P2P
184 |
185 | P2P stands for Peer to Peer.
186 |
187 | ## Peer-to-peer
188 |
189 | Refers to the decentralized interactions that happen between at least two parties in a highly interconnected network. P2P participants deal directly with each other through a single mediation point.
190 |
191 | ## Permissioned Ledger
192 |
193 | Is a ledger where actors must have permission to access the ledger. Permissioned ledgers may have one or many owners. When a new record is added, the ledger’s integrity is checked by a limited consensus process. This is carried out by trusted actors — government departments or banks, for example — which makes maintaining a shared record much simpler that the consensus process used by unpermissioned ledgers.
194 |
195 | ## Permissioned Blockchains
196 |
197 | Provide highly-verifiable data sets because the consensus process creates a digital signature, which can be seen by all parties.
198 |
199 | ## Private Key
200 |
201 | A string of data that shows you have access to bitcoins in a specific wallet. Private keys can be thought of as a password; private keys must never be revealed to anyone but you, as they allow you to spend the bitcoins from your bitcoin wallet through a cryptographic signature.
202 |
203 | ## Proof of Authority
204 |
205 | A consensus mechanism in a private blockchain which essentially gives one client (or a specific number of clients) with one particular private key the right to make all of the blocks in the blockchain.
206 |
207 | ## Proof of Stake
208 |
209 | An alternative to the proof-of-work system, in which your existing stake in a cryptocurrency (the amount of that currency that you hold) is used to calculate the amount of that currency that you can mine.
210 |
211 | ## Proof of Work
212 |
213 | A system that ties mining capability to computational power. Blocks must be hashed, which is in itself an easy computational process, but an additional variable is added to the hashing process to make it more difficult. When a block is successfully hashed, the hashing must have taken some time and computational effort. Thus, a hashed block is considered proof of work.
214 |
215 | ## Protocols
216 |
217 | Sets of formal rules describing how to transmit or exchange data, especially across a network.
218 |
219 | # Scrypt
220 |
221 | An alternative proof of work system to SHA-256, designed to be particularly friendly to CPU and GPU miners, while offering little advantage to ASIC miners.
222 |
223 | ## SHA256
224 |
225 | The cryptographic function used as the basis for bitcoin’s proof of work system.
226 |
227 | ## Signature
228 |
229 | A digital signature is a mathematical scheme for presenting the authenticity of digital messages or documents.
230 |
231 | ## Smart contract
232 |
233 | Contracts whose terms are recorded in a computer language instead of legal language. Smart contracts can be automatically executed by a computing system, such as a suitable distributed ledger system.
234 |
235 | ## Soft fork
236 |
237 | A change to the bitcoin protocol wherein only previously valid blocks/transactions are made invalid. Since old nodes will recognize the new blocks as valid, a softfork is backward-compatible. This kind of fork requires only a majority of the miners upgrading to enforce the new rules.
238 |
239 | ## Stream ciphers
240 |
241 | A method of encrypting text (cyphertext) in which a cryptographic key and algorithm are applied to each binary digit in a data stream, one bit at a time.
242 |
243 | ## Token
244 |
245 | Is a digital identity for something that can be owned.
246 |
247 | ## Tokenless Ledger
248 |
249 | Refers to a distributed ledger that doesn’t require a native currency to operate.
250 |
251 | ## Transaction Block
252 |
253 | A collection of transactions on the bitcoin network, gathered into a block that can then be hashed and added to the blockchain.
254 |
255 | ## Transaction Fees
256 |
257 | Small fees imposed on some transactions sent across the bitcoin network. The transaction fee is awarded to the miner that successfully hashes the block containing the relevant transaction.
258 |
259 | ## Unpermissioned ledgers
260 |
261 | Blockchains that do not have a single owner; they cannot be owned. The purpose of an unpermissioned ledger is to allow anyone to contribute data to the ledger and for everyone in possession of the ledger to have identical copies.
262 |
263 | ## Wallet
264 |
265 | A file that contains a collection of private keys.
266 |
--------------------------------------------------------------------------------
/en/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Learn how to generate wallets, send transactions, sign messages, query the blockchain, and much more with this little guide book on Bitcoin Development with Go.
3 | ---
4 |
5 | # (Work in Progress)
6 |
7 | # Bitcoin Development with Go
8 |
9 | This little guide book is to serve as a general help guide for anyone wanting to develop Bitcoin applications using the Go programming language. It's meant to provide a starting point if you're already pretty familiar with Bitcoin and Go but don't know where to to start on bringing it all together. You'll learn how to perform general blockchain tasks like generating wallets, transferring Bitcoin, and queries the blockchain using Golang.
10 |
11 | This book is composed of many examples that I wish I had encountered before when I first started doing Bitcoin development with Go. This book will walk you through most things that you should be aware of in order for you to be a productive Bitcoin developer using Go.
12 |
13 | If you see something out-of-date or invalid, I strongly suggest opening an [issue](https://github.com/miguelmota/bitcoin-development-with-go-book/issues) or making a [pull request](https://github.com/miguelmota/bitcoin-development-with-go-book/pulls) if you observe things that can be improved. This book is completely open and free and available on [github](https://github.com/miguelmota/bitcoin-development-with-go-book).
14 |
15 | #### Online
16 |
17 | [https://gobitcoinbook.org](https://gobitcoinbook.org/)
18 |
19 |
28 |
29 | ## Introduction
30 |
31 | > Bitcoin (₿) is a cryptocurrency, a form of electronic cash. It is a decentralized digital currency without a central bank or single administrator, and can be sent from user to user on the peer-to-peer bitcoin network without the need for intermediaries.
32 |
33 | -[Wikipedia](https://en.wikipedia.org/wiki/Bitcoin)
34 |
35 | ---
36 |
37 | Enough with the introduction, let's get [started](../client)!
38 |
--------------------------------------------------------------------------------
/en/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | * [Introduction](README.md)
4 | * [Accounts](accounts/README.md)
5 | * [Generating New Wallets](wallet-generate/README.md)
6 | * [Account Balances](account-balance/README.md)
7 | * [Transactions](transactions/README.md)
8 | * [Send BTC (basic example)](transfer-coin-simple/README.md)
9 | * [Send BTC (gather UTXOs)](transfer-coin/README.md)
10 |
11 |
39 |
--------------------------------------------------------------------------------
/en/account-balance/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Tutorial on how to read account balances from the bitcoin blockchain with Go.
3 | ---
4 |
5 | # Account Balances
6 |
7 | ---
8 |
9 | ### Full code
10 |
11 | [account_balance.go](https://github.com/miguelmota/bitcoin-development-with-go-book/blob/master/code/account_balance.go)
12 |
13 | ```go
14 | package main
15 |
16 | import (
17 | "crypto/tls"
18 | "encoding/json"
19 | "fmt"
20 | "io"
21 | "io/ioutil"
22 | "log"
23 | "math/big"
24 | )
25 |
26 | func main() {
27 | address := "3BMEXpRXTdAHagbFtjtuSS3S8ZXfQQqiTw"
28 |
29 | req := struct {
30 | ID int `json:"id"`
31 | Method string `json:"method"`
32 | Params []string `json:"params"`
33 | }{
34 | ID: 1,
35 | Method: "blockchain.address.get_balance",
36 | Params: []string{address},
37 | }
38 |
39 | res := struct {
40 | JSONRPC string `json:"jsonrpc,omitempty"`
41 | ID int `json:"id"`
42 | Result struct {
43 | Confirmed *big.Int `json:"confirmed"`
44 | Uncomfirmed *big.Int `json:"unconfirmed"`
45 | } `json:"result"`
46 | }{}
47 |
48 | serverAddr := "electrum.qtornado.com:50002" // mainnet
49 |
50 | certBytes, err := ioutil.ReadFile("certs/example.com.cert")
51 | if err != nil {
52 | log.Fatal(err)
53 | }
54 | certKeyBytes, err := ioutil.ReadFile("certs/example.com.key")
55 | if err != nil {
56 | log.Fatal(err)
57 | }
58 |
59 | cert, err := tls.X509KeyPair(certBytes, certKeyBytes)
60 | if err != nil {
61 | log.Fatal(err)
62 | }
63 |
64 | fmt.Printf("dialing to server: %s\n", serverAddr)
65 | conn, err := tls.Dial("tcp", serverAddr, &tls.Config{
66 | Certificates: []tls.Certificate{cert},
67 | InsecureSkipVerify: true,
68 | })
69 | if err != nil {
70 | log.Fatal(err)
71 | }
72 |
73 | defer conn.Close()
74 | fmt.Printf("client connected to: %s\n", conn.RemoteAddr())
75 |
76 | reqMsgBytes, err := json.Marshal(req)
77 | if err != nil {
78 | log.Fatal(err)
79 | }
80 |
81 | reqMsg := fmt.Sprintf("%s\n", string(reqMsgBytes))
82 | fmt.Printf("writing message: %s", reqMsg)
83 | _, err = io.WriteString(conn, reqMsg)
84 | if err != nil {
85 | log.Fatal(err)
86 | }
87 |
88 | var (
89 | i int
90 | readSize int = 1024
91 | respData []byte
92 | )
93 |
94 | for {
95 | fmt.Println("reading response...")
96 | respBytes := make([]byte, readSize)
97 | n, err := conn.Read(respBytes)
98 | if err != nil {
99 | if err != io.EOF {
100 | log.Fatal(err)
101 | }
102 | }
103 |
104 | fmt.Printf("reading: %q (%d bytes)\n", string(respBytes[:n]), n)
105 |
106 | respData = append(respData, respBytes[:n]...)
107 | i += n
108 |
109 | if n < readSize {
110 | break
111 | }
112 | }
113 |
114 | json.Unmarshal(respData[:i], &res)
115 |
116 | fmt.Printf("unconfirmed: %s\n", res.Result.Uncomfirmed.String()) // unconfirmed: 0
117 | fmt.Printf("confirmed: %s\n", res.Result.Confirmed.String()) // confirmed: 500000000
118 | }
119 | ```
120 |
--------------------------------------------------------------------------------
/en/accounts/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Tutorial on how bitcoin accounts with Go.
3 | ---
4 |
5 | # Accounts
6 |
7 | The following sections are about accounts.
8 |
--------------------------------------------------------------------------------
/en/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/en/cover.jpg
--------------------------------------------------------------------------------
/en/cover_small.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miguelmota/bitcoin-development-with-go/b25b10b9974e795dff759d0a2dbb3927704ac872/en/cover_small.jpg
--------------------------------------------------------------------------------
/en/faucets/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Tutorial on using faucets on Bitcoin.
3 | ---
4 |
5 | # Faucets
6 |
7 | A faucet is where you can acquire free [testnet] Bitcoin to use while testing.
8 |
9 | Below are faucet links to each respective testnet.
10 |
11 | - Bitcoin testnet3 - [https://testnet.manu.backend.hamburg/faucet](https://testnet.manu.backend.hamburg/faucet)
12 | - Bitcoin testnet3 - [https://coinfaucet.eu/en/btc-testnet](https://coinfaucet.eu/en/btc-testnet)
13 | https://kuttler.eu/en/bitcoin/btc/faucet/success/
14 |
--------------------------------------------------------------------------------
/en/styles/website.css:
--------------------------------------------------------------------------------
1 | .gitbook-link {
2 | display: none !important;
3 | }
4 |
5 | body::-webkit-scrollbar,
6 | .book-summary::-webkit-scrollbar {
7 | width: 8px;
8 | height: 8px;
9 | }
10 |
11 | body::-webkit-scrollbar-track,
12 | .book-summary::-webkit-scrollbar-track {
13 | background: transparent;
14 | }
15 |
16 | body::-webkit-scrollbar-thumb,
17 | .book-summary::-webkit-scrollbar-thumb {
18 | background-color: rgba(0,0,0,0.05);
19 | border: 1px solid transparent;
20 | box-shadow: inset 1px 1px 0 rgba(0,0,0,0.05), inset 0 -1px 0 rgba(0,0,0,0.1);
21 | }
22 |
23 | body::-webkit-scrollbar-thumb:hover,
24 | .book-summary::-webkit-scrollbar-thumb:hover {
25 | box-shadow: inset 1px 1px 0 rgba(0,0,0,0.1), inset 0 -1px 0 rgba(0,0,0,0.2);
26 | }
27 |
--------------------------------------------------------------------------------
/en/test/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Tutorial on how to test your Ethereum application with Go.
3 | ---
4 |
5 | # Testing
6 |
7 | - [Faucets](../faucets)
8 | - [Using a Simulated Client](../client-simulated)
9 |
10 |
--------------------------------------------------------------------------------
/en/transactions/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Tutorial on bitcoin transactions with Go.
3 | ---
4 |
5 | # Transactions
6 |
7 | These sections will discuss how create bitcoin transactions with Go. Proceed to the [first section](../transfer-coin-simple).
8 |
--------------------------------------------------------------------------------
/en/transfer-coin-simple/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Tutorial on how to send BTC to an account with Go.
3 | ---
4 |
5 | # Send BTC (basic example)
6 |
7 | ```go
8 | package main
9 |
10 | import (
11 | "bytes"
12 | "encoding/hex"
13 | "fmt"
14 | "log"
15 |
16 | "github.com/btcsuite/btcd/chaincfg"
17 | "github.com/btcsuite/btcd/chaincfg/chainhash"
18 | "github.com/btcsuite/btcd/txscript"
19 | "github.com/btcsuite/btcd/wire"
20 | "github.com/btcsuite/btcutil"
21 | )
22 |
23 | func main() {
24 | privWif := "cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe"
25 | txHash := "12e0d25258ec29fadf75a3f569fccaeeb8ca4af5d2d34e9a48ab5a6fdc0efc1e"
26 | destination := "mrdKfqWEkwferzEQus5NpgK2Dtpq7Qcgif"
27 | amount := int64(11650795)
28 | txFee := int64(500000)
29 | sourceUTXOIndex := uint32(1)
30 | chainParams := &chaincfg.TestNet3Params // testnet
31 | // chainParams := &chaincfg.MainNetParams // mainnet
32 |
33 | decodedWif, err := btcutil.DecodeWIF(privWif)
34 | if err != nil {
35 | log.Fatal(err)
36 | }
37 |
38 | fmt.Printf("Decoded WIF: %v\n", decodedWif) // Decoded WIF: cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe
39 |
40 | addressPubKey, err := btcutil.NewAddressPubKey(decodedWif.PrivKey.PubKey().SerializeUncompressed(), chainParams)
41 | if err != nil {
42 | log.Fatal(err)
43 | }
44 |
45 | sourceUTXOHash, err := chainhash.NewHashFromStr(txHash)
46 | if err != nil {
47 | log.Fatal(err)
48 | }
49 |
50 | fmt.Printf("UTXO hash: %s\n", sourceUTXOHash) // utxo hash: 12e0d25258ec29fadf75a3f569fccaeeb8ca4af5d2d34e9a48ab5a6fdc0efc1e
51 |
52 | sourceUTXO := wire.NewOutPoint(sourceUTXOHash, sourceUTXOIndex)
53 | sourceTxIn := wire.NewTxIn(sourceUTXO, nil, nil)
54 | destinationAddress, err := btcutil.DecodeAddress(destination, chainParams)
55 | if err != nil {
56 | log.Fatal(err)
57 | }
58 |
59 | sourceAddress, err := btcutil.DecodeAddress(addressPubKey.EncodeAddress(), chainParams)
60 | if err != nil {
61 | log.Fatal(err)
62 | }
63 |
64 | fmt.Printf("Source Address: %s\n", sourceAddress) // Source Address: mgjHgKi1g6qLFBM1gQwuMjjVBGMJdrs9pP
65 |
66 | destinationPkScript, err := txscript.PayToAddrScript(destinationAddress)
67 | if err != nil {
68 | log.Fatal(err)
69 | }
70 |
71 | sourcePkScript, err := txscript.PayToAddrScript(sourceAddress)
72 | if err != nil {
73 | log.Fatal(err)
74 | }
75 |
76 | sourceTxOut := wire.NewTxOut(amount, sourcePkScript)
77 |
78 | redeemTx := wire.NewMsgTx(wire.TxVersion)
79 | redeemTx.AddTxIn(sourceTxIn)
80 | redeemTxOut := wire.NewTxOut((amount - txFee), destinationPkScript)
81 | redeemTx.AddTxOut(redeemTxOut)
82 |
83 | sigScript, err := txscript.SignatureScript(redeemTx, 0, sourceTxOut.PkScript, txscript.SigHashAll, decodedWif.PrivKey, false)
84 | if err != nil {
85 | log.Fatal(err)
86 | }
87 |
88 | redeemTx.TxIn[0].SignatureScript = sigScript
89 | fmt.Printf("Signature Script: %v\n", hex.EncodeToString(sigScript)) // Signature Script: 473...b67
90 |
91 | // validate signature
92 | flags := txscript.StandardVerifyFlags
93 | vm, err := txscript.NewEngine(sourceTxOut.PkScript, redeemTx, 0, flags, nil, nil, amount)
94 | if err != nil {
95 | log.Fatal(err)
96 | }
97 |
98 | if err := vm.Execute(); err != nil {
99 | log.Fatal(err)
100 | }
101 |
102 | buf := bytes.NewBuffer(make([]byte, 0, redeemTx.SerializeSize()))
103 | redeemTx.Serialize(buf)
104 |
105 | fmt.Printf("Redeem Tx: %v\n", hex.EncodeToString(buf.Bytes())) // redeem Tx: 01000000011efc...5bb88ac00000000
106 | }
107 | ```
108 |
109 | Send the raw transaction using: [https://live.blockcypher.com/btc-testnet/pushtx](https://live.blockcypher.com/btc-testnet/pushtx)
110 |
111 | See the transaction on the block explorer: [https://live.blockcypher.com/btc-testnet/tx/49618053876e8e0fc13860cd5f67c849ba2489e18463a15714b7e284a8caaa50](https://live.blockcypher.com/btc-testnet/tx/49618053876e8e0fc13860cd5f67c849ba2489e18463a15714b7e284a8caaa50)
112 |
113 | ---
114 |
115 | ### Full code
116 |
117 | [transfer_coin_simple.go](https://github.com/miguelmota/bitcoin-development-with-go-book/blob/master/code/transfer_coin_simple.go)
118 |
119 | ```go
120 | package main
121 |
122 | import (
123 | "bytes"
124 | "encoding/hex"
125 | "fmt"
126 | "log"
127 |
128 | "github.com/btcsuite/btcd/chaincfg"
129 | "github.com/btcsuite/btcd/chaincfg/chainhash"
130 | "github.com/btcsuite/btcd/txscript"
131 | "github.com/btcsuite/btcd/wire"
132 | "github.com/btcsuite/btcutil"
133 | )
134 |
135 | func main() {
136 | privWif := "cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe"
137 | txHash := "12e0d25258ec29fadf75a3f569fccaeeb8ca4af5d2d34e9a48ab5a6fdc0efc1e"
138 | destination := "mrdKfqWEkwferzEQus5NpgK2Dtpq7Qcgif"
139 | amount := int64(11650795)
140 | txFee := int64(500000)
141 | sourceUTXOIndex := uint32(1)
142 | chainParams := &chaincfg.TestNet3Params
143 |
144 | decodedWif, err := btcutil.DecodeWIF(privWif)
145 | if err != nil {
146 | log.Fatal(err)
147 | }
148 |
149 | fmt.Printf("Decoded WIF: %v\n", decodedWif) // Decoded WIF: cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe
150 |
151 | addressPubKey, err := btcutil.NewAddressPubKey(decodedWif.PrivKey.PubKey().SerializeUncompressed(), chainParams)
152 | if err != nil {
153 | log.Fatal(err)
154 | }
155 |
156 | sourceUTXOHash, err := chainhash.NewHashFromStr(txHash)
157 | if err != nil {
158 | log.Fatal(err)
159 | }
160 |
161 | fmt.Printf("UTXO hash: %s\n", sourceUTXOHash) // utxo hash: 12e0d25258ec29fadf75a3f569fccaeeb8ca4af5d2d34e9a48ab5a6fdc0efc1e
162 |
163 | sourceUTXO := wire.NewOutPoint(sourceUTXOHash, sourceUTXOIndex)
164 | sourceTxIn := wire.NewTxIn(sourceUTXO, nil, nil)
165 | destinationAddress, err := btcutil.DecodeAddress(destination, chainParams)
166 | if err != nil {
167 | log.Fatal(err)
168 | }
169 |
170 | sourceAddress, err := btcutil.DecodeAddress(addressPubKey.EncodeAddress(), chainParams)
171 | if err != nil {
172 | log.Fatal(err)
173 | }
174 |
175 | fmt.Printf("Source Address: %s\n", sourceAddress) // Source Address: mgjHgKi1g6qLFBM1gQwuMjjVBGMJdrs9pP
176 |
177 | destinationPkScript, err := txscript.PayToAddrScript(destinationAddress)
178 | if err != nil {
179 | log.Fatal(err)
180 | }
181 |
182 | sourcePkScript, err := txscript.PayToAddrScript(sourceAddress)
183 | if err != nil {
184 | log.Fatal(err)
185 | }
186 |
187 | sourceTxOut := wire.NewTxOut(amount, sourcePkScript)
188 |
189 | redeemTx := wire.NewMsgTx(wire.TxVersion)
190 | redeemTx.AddTxIn(sourceTxIn)
191 | redeemTxOut := wire.NewTxOut((amount - txFee), destinationPkScript)
192 | redeemTx.AddTxOut(redeemTxOut)
193 |
194 | sigScript, err := txscript.SignatureScript(redeemTx, 0, sourceTxOut.PkScript, txscript.SigHashAll, decodedWif.PrivKey, false)
195 | if err != nil {
196 | log.Fatal(err)
197 | }
198 |
199 | redeemTx.TxIn[0].SignatureScript = sigScript
200 | fmt.Printf("Signature Script: %v\n", hex.EncodeToString(sigScript)) // Signature Script: 473...b67
201 |
202 | // validate signature
203 | flags := txscript.StandardVerifyFlags
204 | vm, err := txscript.NewEngine(sourceTxOut.PkScript, redeemTx, 0, flags, nil, nil, amount)
205 | if err != nil {
206 | log.Fatal(err)
207 | }
208 |
209 | if err := vm.Execute(); err != nil {
210 | log.Fatal(err)
211 | }
212 |
213 | buf := bytes.NewBuffer(make([]byte, 0, redeemTx.SerializeSize()))
214 | redeemTx.Serialize(buf)
215 |
216 | fmt.Printf("Redeem Tx: %v\n", hex.EncodeToString(buf.Bytes())) // redeem Tx: 01000000011efc...5bb88ac00000000
217 | }
218 | ```
219 |
--------------------------------------------------------------------------------
/en/transfer-coin/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Tutorial on how to gather UTXOs and send BTC with Go.
3 | ---
4 |
5 | # Send BTC (gather UTXOs)
6 |
7 | ---
8 |
9 | ### Full code
10 |
11 | [transfer_coin.go](https://github.com/miguelmota/bitcoin-development-with-go-book/blob/master/code/transfer_coin.go)
12 |
13 | ```go
14 | package main
15 |
16 | import (
17 | "bytes"
18 | "crypto/tls"
19 | "encoding/hex"
20 | "encoding/json"
21 | "errors"
22 | "fmt"
23 | "io"
24 | "io/ioutil"
25 | "log"
26 | "math/big"
27 | "math/rand"
28 | "sort"
29 | "time"
30 |
31 | "github.com/btcsuite/btcd/chaincfg"
32 | "github.com/btcsuite/btcd/chaincfg/chainhash"
33 | "github.com/btcsuite/btcd/txscript"
34 | "github.com/btcsuite/btcd/wire"
35 | "github.com/btcsuite/btcutil"
36 | )
37 |
38 | // UTXO ...
39 | type UTXO struct {
40 | Hash string
41 | TxIndex int
42 | Amount *big.Int
43 | Spendable bool
44 | PKScript []byte
45 | }
46 |
47 | func sendMsg(req, res interface{}) {
48 | //serverAddr := "electrum.qtornado.com:50002" // mainnet
49 | serverAddr := "testnet.qtornado.com:51002" // testnet
50 | //serverAddr := "testnet1.bauerj.eu:50002" // testnet
51 | //serverAddr := "testnet.hsmiths.com:53012" // testnet
52 |
53 | certBytes, err := ioutil.ReadFile("certs/example.com.cert")
54 | if err != nil {
55 | log.Fatal(err)
56 | }
57 | certKeyBytes, err := ioutil.ReadFile("certs/example.com.key")
58 | if err != nil {
59 | log.Fatal(err)
60 | }
61 |
62 | cert, err := tls.X509KeyPair(certBytes, certKeyBytes)
63 | if err != nil {
64 | log.Fatal(err)
65 | }
66 |
67 | fmt.Printf("dialing to server: %s\n", serverAddr)
68 | conn, err := tls.Dial("tcp", serverAddr, &tls.Config{
69 | Certificates: []tls.Certificate{cert},
70 | InsecureSkipVerify: true,
71 | })
72 | if err != nil {
73 | log.Fatal(err)
74 | }
75 |
76 | defer conn.Close()
77 | fmt.Printf("client connected to: %s\n", conn.RemoteAddr())
78 |
79 | reqMsgBytes, err := json.Marshal(req)
80 | if err != nil {
81 | log.Fatal(err)
82 | }
83 |
84 | reqMsg := fmt.Sprintf("%s\n", string(reqMsgBytes))
85 | fmt.Printf("writing message: %s", reqMsg)
86 | _, err = io.WriteString(conn, reqMsg)
87 | if err != nil {
88 | log.Fatal(err)
89 | }
90 |
91 | var (
92 | i int
93 | readSize int = 1024
94 | respData []byte
95 | )
96 |
97 | for {
98 | fmt.Println("reading response...")
99 | respBytes := make([]byte, readSize)
100 | n, err := conn.Read(respBytes)
101 | if err != nil {
102 | if err != io.EOF {
103 | log.Fatal(err)
104 | }
105 | }
106 |
107 | fmt.Printf("reading: %q (%d bytes)\n", string(respBytes[:n]), n)
108 |
109 | respData = append(respData, respBytes[:n]...)
110 | i += n
111 |
112 | if n < readSize {
113 | break
114 | }
115 | }
116 |
117 | json.Unmarshal(respData[:i], &res)
118 | }
119 |
120 | func main() {
121 | //chainParams := &chaincfg.MainNetParams
122 | chainParams := &chaincfg.TestNet3Params
123 |
124 | amountToSend := big.NewInt(1000000) // amount to send in satoshis (0.01 btc)
125 |
126 | feeRate, err := GetCurrentFeeRate()
127 | log.Printf("current fee rate: %v", feeRate)
128 | if err != nil {
129 | log.Fatal(err)
130 | }
131 |
132 | fromWalletPublicAddress := "mgjHgKi1g6qLFBM1gQwuMjjVBGMJdrs9pP"
133 |
134 | log.Printf("from wallet public address: %s", fromWalletPublicAddress)
135 |
136 | unspentTXOs, err := ListUnspentTXOs(fromWalletPublicAddress)
137 | if err != nil {
138 | log.Fatal(err)
139 | }
140 |
141 | unspentTXOs, UTXOsAmount, err := marshalUTXOs(unspentTXOs, amountToSend, feeRate)
142 | if err != nil {
143 | log.Fatal(err)
144 | }
145 |
146 | // prepare unspent transaction outputs with its privatekey.
147 | log.Println("unspent UTXOs", unspentTXOs, UTXOsAmount)
148 |
149 | tx := wire.NewMsgTx(wire.TxVersion)
150 |
151 | var sourceUTXOs []*UTXO
152 | // prepare tx ins
153 | for idx := range unspentTXOs {
154 | hashStr := unspentTXOs[idx].Hash
155 |
156 | sourceUTXOHash, err := chainhash.NewHashFromStr(hashStr)
157 | if err != nil {
158 | log.Fatal(err)
159 | }
160 |
161 | sourceUTXOIndex := uint32(unspentTXOs[idx].TxIndex)
162 | sourceUTXO := wire.NewOutPoint(sourceUTXOHash, sourceUTXOIndex)
163 | sourceUTXOs = append(sourceUTXOs, unspentTXOs[idx])
164 | sourceTxIn := wire.NewTxIn(sourceUTXO, nil, nil)
165 |
166 | tx.AddTxIn(sourceTxIn)
167 | }
168 |
169 | // calculate fees
170 | txByteSize := big.NewInt(int64(len(tx.TxIn)*180 + len(tx.TxOut)*34 + 10 + len(tx.TxIn)))
171 | totalFee := new(big.Int).Mul(feeRate, txByteSize)
172 | log.Printf("total fee: %s", totalFee)
173 |
174 | // calculate the change
175 | change := new(big.Int).Set(UTXOsAmount)
176 | change = new(big.Int).Sub(change, amountToSend)
177 | change = new(big.Int).Sub(change, totalFee)
178 | if change.Cmp(big.NewInt(0)) == -1 {
179 | log.Fatal(err)
180 | }
181 |
182 | destinationAddress := "mgs2eXmc8Lai17pP7WvrY7QXKeXJSXpSCU"
183 |
184 | // create the tx outs
185 | destAddress, err := btcutil.DecodeAddress(destinationAddress, chainParams)
186 | if err != nil {
187 | log.Fatal(err)
188 | }
189 |
190 | destScript, err := txscript.PayToAddrScript(destAddress)
191 | if err != nil {
192 | log.Fatal(err)
193 | }
194 |
195 | // tx out to send btc to user
196 | destOutput := wire.NewTxOut(amountToSend.Int64(), destScript)
197 | tx.AddTxOut(destOutput)
198 |
199 | // our change address
200 | changeSendToAddress, err := btcutil.DecodeAddress(fromWalletPublicAddress, chainParams)
201 | if err != nil {
202 | log.Fatal(err)
203 | }
204 |
205 | changeSendToScript, err := txscript.PayToAddrScript(changeSendToAddress)
206 | if err != nil {
207 | log.Fatal(err)
208 | }
209 |
210 | // tx out to send change back to us
211 | changeOutput := wire.NewTxOut(change.Int64(), changeSendToScript)
212 | tx.AddTxOut(changeOutput)
213 |
214 | privWif := "cS5LWK2aUKgP9LmvViG3m9HkfwjaEJpGVbrFHuGZKvW2ae3W9aUe"
215 |
216 | decodedWif, err := btcutil.DecodeWIF(privWif)
217 | if err != nil {
218 | log.Fatal(err)
219 | }
220 |
221 | addressPubKey, err := btcutil.NewAddressPubKey(decodedWif.PrivKey.PubKey().SerializeUncompressed(), chainParams)
222 | if err != nil {
223 | log.Fatal(err)
224 | }
225 | sourceAddress, err := btcutil.DecodeAddress(addressPubKey.EncodeAddress(), chainParams)
226 | if err != nil {
227 | log.Fatal(err)
228 | }
229 |
230 | fmt.Printf("Source Address: %s\n", sourceAddress) // Source Address: mgjHgKi1g6qLFBM1gQwuMjjVBGMJdrs9pP
231 |
232 | sourcePkScript, err := txscript.PayToAddrScript(sourceAddress)
233 | if err != nil {
234 | log.Fatal(err)
235 | }
236 |
237 | for i := range sourceUTXOs {
238 | sigScript, err := txscript.SignatureScript(tx, i, sourcePkScript, txscript.SigHashAll, decodedWif.PrivKey, false)
239 | if err != nil {
240 | log.Fatalf("could not generate pubSig; err: %v", err)
241 | }
242 | tx.TxIn[i].SignatureScript = sigScript
243 | }
244 |
245 | buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
246 | tx.Serialize(buf)
247 |
248 | fmt.Printf("Redeem Tx: %v\n", hex.EncodeToString(buf.Bytes()))
249 |
250 | t := hex.EncodeToString(buf.Bytes())
251 | txHash, err := SendTX(t)
252 | if err != nil {
253 | log.Fatal(err)
254 | }
255 |
256 | fmt.Printf("tx hash: %s\n", txHash) // 1d8f70dfc8b90bff672ee663a7cc811c4e88e98c6895dc93aa9f73202bb7809b
257 | }
258 |
259 | func marshalUTXOs(utxos []*UTXO, amount, feeRate *big.Int) ([]*UTXO, *big.Int, error) {
260 | // same strategy as bitcoin core
261 | // from: https://medium.com/@lopp/the-challenges-of-optimizing-unspent-output-selection-a3e5d05d13ef
262 | // 1. sort the UTXOs from smallest to largest amounts
263 | sort.Slice(utxos, func(i, j int) bool {
264 | return utxos[i].Amount.Cmp(utxos[j].Amount) == -1
265 | })
266 |
267 | // 2. search for exact match
268 | for idx := range utxos {
269 | exactTxSize := calculateTotalTxBytes(1, 2)
270 | totalFee := new(big.Int).Mul(feeRate, big.NewInt(int64(exactTxSize)))
271 | totalTxAmount := new(big.Int).Add(totalFee, amount)
272 |
273 | switch utxos[idx].Amount.Cmp(totalTxAmount) {
274 | case 0:
275 | var resp []*UTXO
276 | resp = append(resp, utxos[idx])
277 | // TODO: store these in the DB to be sure they aren't being claimed??
278 | return resp, sumUTXOs(resp), nil
279 |
280 | case 1:
281 | break
282 | }
283 | }
284 |
285 | // 3. calculate the sum of all UTXOs smaller than amount
286 | sumSmall := big.NewInt(0)
287 | var sumSmallUTXOs []*UTXO
288 | for idx := range utxos {
289 | switch utxos[idx].Amount.Cmp(amount) {
290 | case -1:
291 | _ = sumSmall.Add(sumSmall, utxos[idx].Amount)
292 | sumSmallUTXOs = append(sumSmallUTXOs, utxos[idx])
293 |
294 | default:
295 | break
296 | }
297 | }
298 |
299 | exactTxSize := calculateTotalTxBytes(len(sumSmallUTXOs), 2)
300 | totalFee := new(big.Int).Mul(feeRate, big.NewInt(int64(exactTxSize)))
301 | totalTxAmount := new(big.Int).Add(totalFee, amount)
302 |
303 | switch sumSmall.Cmp(totalTxAmount) {
304 | case 0:
305 | return sumSmallUTXOs, sumUTXOs(sumSmallUTXOs), nil
306 |
307 | case -1:
308 | for idx := range utxos {
309 | exactTxSize := calculateTotalTxBytes(1, 2)
310 | totalFee := new(big.Int).Mul(feeRate, big.NewInt(int64(exactTxSize)))
311 | totalTxAmount := new(big.Int).Add(totalFee, amount)
312 | if utxos[idx].Amount.Cmp(totalTxAmount) == 1 {
313 | var resp []*UTXO
314 | resp = append(resp, utxos[idx])
315 | return resp, sumUTXOs(resp), nil
316 | }
317 | }
318 |
319 | // should reach here if not enought UXOs
320 | log.Fatal("not enough UTXOs to meet target amount")
321 |
322 | case 1:
323 | return roundRobinSelectUTXOs(sumSmallUTXOs, amount, feeRate)
324 |
325 | default:
326 | log.Fatal("unknown comparison")
327 | }
328 |
329 | return nil, nil, nil
330 | }
331 |
332 | func roundRobinSelectUTXOs(utxos []*UTXO, amount, feeRate *big.Int) ([]*UTXO, *big.Int, error) {
333 | var possibilities [][]*UTXO
334 | lenInput := len(utxos)
335 | log.Printf("round robin select; lenInput: %v", lenInput)
336 | if lenInput == 0 {
337 | log.Fatal("expected utxos size to be greater than 0")
338 | }
339 |
340 | for i := 0; i < 1000; i++ {
341 | selectedIdxs := make(map[int]bool)
342 | var sum *big.Int
343 | var possibility []*UTXO
344 | for {
345 | for {
346 | rand.Seed(time.Now().Unix())
347 | tmp := 0
348 | if lenInput > 1 {
349 | tmp = rand.Intn(lenInput - 1)
350 | }
351 |
352 | if !selectedIdxs[tmp] {
353 | selectedIdxs[tmp] = true
354 | _ = sum.Add(sum, utxos[tmp].Amount)
355 | possibility = append(possibility, utxos[tmp])
356 |
357 | break
358 | }
359 | }
360 |
361 | exactTxSize := calculateTotalTxBytes(len(possibility), 2)
362 | totalFee := new(big.Int).Mul(feeRate, big.NewInt(int64(exactTxSize)))
363 | totalTxAmount := new(big.Int).Add(totalFee, amount)
364 |
365 | if sum.Cmp(totalTxAmount) == 0 {
366 | return possibility, sum, nil
367 | }
368 |
369 | if sum.Cmp(totalTxAmount) == 1 {
370 | possibilities = append(possibilities, possibility)
371 | break
372 | }
373 | }
374 | }
375 |
376 | if len(possibilities) < 1 {
377 | return nil, nil, errors.New("no possible utxo combos")
378 | }
379 |
380 | smallestLen := len(possibilities[0])
381 | smallestIdx := 0
382 |
383 | for idx := 1; idx < len(possibilities); idx++ {
384 | l := len(possibilities[idx])
385 | if l < smallestLen {
386 | smallestLen = l
387 | smallestIdx = idx
388 | }
389 | }
390 |
391 | return possibilities[smallestIdx], sumUTXOs(possibilities[smallestIdx]), nil
392 | }
393 |
394 | func sumUTXOs(utxos []*UTXO) *big.Int {
395 | sum := big.NewInt(0)
396 | for idx := range utxos {
397 | sum = sum.Add(sum, utxos[idx].Amount)
398 | }
399 |
400 | return sum
401 | }
402 |
403 | // https://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending-legacy-non-segwit-p2pkh-p2sh
404 | func calculateTotalTxBytes(txInLength, txOutLength int) int {
405 | return txInLength*180 + txOutLength*34 + 10 + txInLength
406 | }
407 |
408 | func decodeRawTx(rawTx string) (*wire.MsgTx, error) {
409 | raw, err := hex.DecodeString(rawTx)
410 | if err != nil {
411 | log.Printf("err decoding raw tx; err: %v", err)
412 | return nil, err
413 | }
414 |
415 | var version int32 = 2
416 | if rawTx[:8] == "01000000" {
417 | version = 1
418 | }
419 | log.Printf("version: %d", version)
420 |
421 | r := bytes.NewReader(raw)
422 | tmpTx := wire.NewMsgTx(version)
423 |
424 | err = tmpTx.BtcDecode(r, uint32(version), wire.BaseEncoding)
425 | if err != nil {
426 | log.Printf("could not decode raw tx; err: %v", err)
427 | return nil, err
428 | }
429 |
430 | return tmpTx, nil
431 | }
432 |
433 | // GetCurrentFee gets the current fee in bitcoin
434 | func GetCurrentFee() (float64, error) {
435 | req := struct {
436 | ID int `json:"id"`
437 | Method string `json:"method"`
438 | Params []int `json:"params"`
439 | }{
440 | ID: 1,
441 | Method: "blockchain.estimatefee",
442 | Params: []int{2},
443 | }
444 |
445 | msg := struct {
446 | JSONRPC string `json:"jsonrpc,omitempty"`
447 | ID int `json:"id"`
448 | Result float64 `json:"result"`
449 | }{}
450 |
451 | var fee float64
452 | var MaxTries = 5
453 | for try := 0; try < MaxTries; try++ {
454 | sendMsg(req, &msg)
455 |
456 | if msg.Result == -1.0 || msg.Result == 0 {
457 | log.Printf("expected result > 0; received: %f", msg.Result)
458 | continue
459 | }
460 |
461 | fee = msg.Result
462 | // sanity check
463 | if fee > 0.05 {
464 | fee = 0.1
465 | } else if fee < 0 {
466 | fee = 0
467 | }
468 |
469 | break
470 | }
471 |
472 | fmt.Printf("fee: %f\n", fee)
473 |
474 | if fee == 0 {
475 | log.Print("could not get fees")
476 | return fee, errors.New("could not get fees")
477 | }
478 |
479 | return fee, nil
480 | }
481 |
482 | // GetCurrentFeeRate gets the current fee in satoshis per kb
483 | func GetCurrentFeeRate() (*big.Int, error) {
484 | fee, err := GetCurrentFee()
485 | if err != nil {
486 | return nil, err
487 | }
488 |
489 | // convert to satoshis to bytes
490 | // feeRate := big.NewInt(int64(msg.Result * 1.0E8))
491 | // convert to satoshis to kb
492 | feeRate := big.NewInt(int64(fee * 1.0E5))
493 |
494 | fmt.Printf("fee rate: %s\n", feeRate)
495 |
496 | return feeRate, nil
497 | }
498 |
499 | // ListUnspentTXOs lists all UTXOs for an address
500 | func ListUnspentTXOs(address string) ([]*UTXO, error) {
501 | req := struct {
502 | ID int `json:"id"`
503 | Method string `json:"method"`
504 | Params []string `json:"params"`
505 | }{
506 | ID: 1,
507 | Method: "blockchain.address.listunspent",
508 | Params: []string{address},
509 | }
510 |
511 | msg := struct {
512 | JSONRPC string `json:"jsonrpc,omitempty"`
513 | ID int `json:"id"`
514 | Result []struct {
515 | TXHash string `json:"tx_hash"`
516 | TXPosition uint64 `json:"tx_pos"`
517 | Value *big.Int `json:"value"`
518 | Height uint64 `json:"height"`
519 | } `json:"result"`
520 | }{}
521 |
522 | var MaxTries = 5
523 | for try := 0; try < MaxTries; try++ {
524 | sendMsg(req, &msg)
525 |
526 | var utxos []*UTXO
527 | for idx := range msg.Result {
528 | utxos = append(utxos, &UTXO{
529 | Hash: msg.Result[idx].TXHash,
530 | TxIndex: int(msg.Result[idx].TXPosition),
531 | Amount: msg.Result[idx].Value,
532 | Spendable: true,
533 | })
534 | }
535 |
536 | return utxos, nil
537 | }
538 |
539 | log.Printf("could not get utxos")
540 | return nil, errors.New("could not get utxos")
541 | }
542 |
543 | // GetRawTransaction gets raw transaction data given transaction ID (hash)
544 | func GetRawTransaction(txHash string) ([]byte, error) {
545 | req := struct {
546 | ID int `json:"id"`
547 | Method string `json:"method"`
548 | Params []string `json:"params"`
549 | }{
550 | ID: 1,
551 | Method: "blockchain.transaction.get",
552 | Params: []string{txHash},
553 | }
554 |
555 | msg := struct {
556 | JSONRPC string `json:"jsonrpc,omitempty"`
557 | ID int `json:"id"`
558 | Result string `json:"result"`
559 | }{}
560 |
561 | var MaxTries = 5
562 | for try := 0; try < MaxTries; try++ {
563 | sendMsg(req, &msg)
564 |
565 | b, err := hex.DecodeString(msg.Result)
566 | if err != nil {
567 | log.Printf("could not decode tx raw data to bytes; err: %v", err)
568 | return nil, err
569 | }
570 |
571 | return b, nil
572 | }
573 |
574 | log.Print("could not get transaction info")
575 | return nil, errors.New("could not get transaction info")
576 | }
577 |
578 | // GetTransaction gets transaction data given transaction ID (hash)
579 | func GetTransaction(txHash string) (*wire.MsgTx, error) {
580 | rawTx, err := GetRawTransaction(txHash)
581 | if err != nil {
582 | log.Printf("err getting raw tx; err: %v", err)
583 | return nil, err
584 | }
585 |
586 | fmt.Println("RAW", hex.EncodeToString(rawTx))
587 |
588 | tx, err := decodeRawTx(hex.EncodeToString(rawTx))
589 | if err != nil {
590 | log.Printf("err parsing raw tx; err: %v", err)
591 | return nil, err
592 | }
593 |
594 | return tx, nil
595 | }
596 |
597 | // SendTX sends a transaction on the wire
598 | func SendTX(tx string) (string, error) {
599 | req := struct {
600 | ID int `json:"id"`
601 | Method string `json:"method"`
602 | Params []string `json:"params"`
603 | }{
604 | ID: 1,
605 | Method: "blockchain.transaction.broadcast",
606 | Params: []string{tx},
607 | }
608 |
609 | msg := struct {
610 | JSONRPC string `json:"jsonrpc,omitempty"`
611 | ID int `json:"id"`
612 | Result string `json:"result"`
613 | }{}
614 |
615 | log.Print("attempting to send bitcoin tx")
616 | var MaxTries = 5
617 | for try := 0; try < MaxTries; try++ {
618 | sendMsg(req, &msg)
619 |
620 | return msg.Result, nil
621 | }
622 |
623 | log.Print("could not broadcast tx")
624 | return "", errors.New("could not broadcast tx")
625 | }
626 | ```
627 |
--------------------------------------------------------------------------------
/en/wallet-generate/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | description: Tutorial on how to generate Bitcoin wallets with Go.
3 | ---
4 |
5 | # Generating New Wallets
6 |
7 |
8 |
17 |
18 | ---
19 |
20 | ### Full code
21 |
22 | [wallet_generate.go](https://github.com/miguelmota/bitcoin-development-with-go-book/blob/master/code/wallet_generate.go)
23 |
24 | ```go
25 | package main
26 |
27 | import (
28 | "encoding/hex"
29 | "fmt"
30 | "log"
31 |
32 | "github.com/btcsuite/btcd/btcec"
33 | "github.com/btcsuite/btcd/chaincfg"
34 | "github.com/btcsuite/btcutil"
35 | "github.com/btcsuite/btcutil/base58"
36 | )
37 |
38 | func main() {
39 | priv, err := btcec.NewPrivateKey(btcec.S256())
40 | if err != nil {
41 | log.Fatal(err)
42 | }
43 |
44 | privBytes := priv.Serialize()
45 | fmt.Printf("private key [bytes]:\n%v\n\n", privBytes) // [18 214 ... 64 56]
46 | fmt.Printf("private key [hex]:\n%s\n\n", hex.EncodeToString(privBytes)) // 12d6913912cedcd1859778902bde0f737740ffb532cd1335b08aff159c474038
47 | fmt.Printf("private key [base58]:\n%s\n\n", base58.Encode(privBytes)) // 2GY6yKFr8FRX25zPtrAzLRko1Uryz7QWPy94Hw7i6Vaw
48 |
49 | uncWif, err := btcutil.NewWIF(priv, &chaincfg.MainNetParams, false)
50 | if err != nil {
51 | log.Fatal(err)
52 | }
53 |
54 | fmt.Printf("private key [wif] (uncompressed):\n%s\n\n", uncWif.String()) // 5Jim1MwMAu5WY8puAKL4gLE7tTKijSqoa9rqXhPWeT38Jd1AfsD
55 |
56 | cmpWif, err := btcutil.NewWIF(priv, &chaincfg.MainNetParams, true)
57 | if err != nil {
58 | log.Fatal(err)
59 | }
60 |
61 | fmt.Printf("private key [wif] (compressed):\n%s\n\n", cmpWif.String()) // 2GY6yKFr8FRX25zPtrAzLRko1Uryz7QWPy94Hw7i6Vaw
62 |
63 | pub := priv.PubKey()
64 | uncPubBytes := pub.SerializeUncompressed()
65 | cmpPubBytes := pub.SerializeCompressed()
66 | fmt.Printf("public key [bytes] (uncompressed):\n%v\n\n", uncPubBytes) // [4 210 ... 154 207]
67 | fmt.Printf("public key [hex] (uncompressed):\n%s\n\n", hex.EncodeToString(uncPubBytes)) // 04d28f502980c5e874c3dd2e4aff019b18e3bef83b5828cf974ffc87c8b0f94576611afbf8780fbff9e6a31c7e3b5385b3d24a0777a8b8f37cd6355ed43d219acf
68 | fmt.Printf("public key [base58] (uncompressed):\n%s\n\n", base58.Encode(uncPubBytes)) // RgbxSrecyPCc3jsEcDmLh5ERueFyrz7m1QEg3U4SUQAZhoPABbik2GvS9adSRHHTV3f2ourctb4qPjuYiyiLdH3k
69 | fmt.Printf("public key bytes (compressed):\n%v\n\n", cmpPubBytes) // [3 210 ... 69 118]
70 | fmt.Printf("public key [hex] (compressed):\n%s\n\n", hex.EncodeToString(cmpPubBytes)) // 03d28f502980c5e874c3dd2e4aff019b18e3bef83b5828cf974ffc87c8b0f94576
71 | fmt.Printf("public key [base58] (compressed):\n%s\n\n", base58.Encode(cmpPubBytes)) // 28rtUZpHgFeEKjkBzTqRxGwohCF8KmSaMS9o38VGzoA3X
72 |
73 | uncAddr, err := btcutil.NewAddressPubKey(uncPubBytes, &chaincfg.MainNetParams)
74 | if err != nil {
75 | log.Fatal(err)
76 | }
77 |
78 | cmpAddr, err := btcutil.NewAddressPubKey(cmpPubBytes, &chaincfg.MainNetParams)
79 | if err != nil {
80 | log.Fatal(err)
81 | }
82 |
83 | encUncAddr := uncAddr.EncodeAddress()
84 | encCmpAddr := cmpAddr.EncodeAddress()
85 | fmt.Printf("address [base58] (uncompressed):\n%s\n\n", encUncAddr) // 16385kYLPqkczsyhJirzjunz27bTpqJrNm
86 | fmt.Printf("address [base58] (compressed):\n%s\n\n", encCmpAddr) // 15xQjUYRuk59ijmbCkSFTiP7zYWD4NVN1G
87 | }
88 | ```
89 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bitcoin-development-with-go",
3 | "version": "1.0.0",
4 | "description": "> A little book on [Bitcoin](https://bitcoin.org/) Development with [Go](https://golang.org/) (golang)",
5 | "main": "index.js",
6 | "dependencies": {
7 | "canvas": "^1.6.11",
8 | "gitbook-plugin-analytics": "^0.2.1",
9 | "gitbook-plugin-autocover": "^2.0.1",
10 | "gitbook-plugin-ga": "^2.0.0",
11 | "install": "^0.11.0",
12 | "npm": "^6.1.0"
13 | },
14 | "devDependencies": {},
15 | "scripts": {
16 | "test": "echo \"Error: no test specified\" && exit 1"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/miguelmota/bitcoin-development-with-go.git"
21 | },
22 | "author": "",
23 | "license": "ISC",
24 | "bugs": {
25 | "url": "https://github.com/miguelmota/bitcoin-development-with-go/issues"
26 | },
27 | "homepage": "https://github.com/miguelmota/bitcoin-development-with-go#readme"
28 | }
29 |
--------------------------------------------------------------------------------
/styles/website.css:
--------------------------------------------------------------------------------
1 | .gitbook-link {
2 | display: none !important;
3 | }
4 |
--------------------------------------------------------------------------------