├── .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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /assets/bitcoin_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | 5 | 47 | migopher-2 48 | 49 | 52 | 53 | 54 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | 74 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 92 | 93 | 94 | 95 | 96 | 97 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 112 | 113 | 114 | 115 | 116 | 117 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 132 | 133 | 134 | 135 | 136 | 137 | 144 | 145 | 146 | 147 | 148 | 149 | 152 | 153 | 154 | 155 | 156 | 157 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 170 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 185 | 186 | 187 | 188 | 189 | 190 | 197 | 198 | 199 | 200 | 201 | 202 | 205 | 206 | 207 | 208 | 209 | 210 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 223 | 224 | 225 | 226 | 227 | 228 | 235 | 236 | 237 | 238 | 239 | 250 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 265 | 266 | 267 | 268 | 269 | 270 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 298 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 315 | 318 | 319 | 320 | 321 | 322 | 323 | 327 | 328 | 329 | 330 | 331 | 332 | 336 | 337 | 338 | 339 | 340 | 341 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 357 | 358 | 359 | 360 | 361 | 362 | 369 | 370 | 371 | 372 | 375 | 377 | 380 | 384 | 388 | 389 | 390 | 391 | 392 | 393 | 395 | 401 | 402 | 403 | 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 | --------------------------------------------------------------------------------