-
Notifications
You must be signed in to change notification settings - Fork 7
/
spend.go
279 lines (227 loc) · 8.13 KB
/
spend.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
/*
For this program to execute correctly the following needs to be provided:
- An internet connection
- A private key
- A receiving address
- The raw json of the funding transaction
- The index into that transaction that funds your private key.
The program will formulate a transaction from these provided parameters
and then it will dump the newly formed tx to the command line as well as
try to broadcast the transaction into the bitcoin network. The raw hex dumped
by the program can parsed into a 'semi' human readable format using services
like: https://blockchain.info/decode-tx
*/
package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/btcsuite/btcec"
"github.com/btcsuite/btcnet"
"github.com/btcsuite/btcscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwire"
)
var a = flag.String("address", "", "The address to send Bitcoin to.")
var k = flag.String("privkey", "", "The private key of the input tx.")
var t = flag.String("txid", "", "The transaction id corresponding to the funding Bitcoin transaction.")
var v = flag.Int("vout", -1, "The index into the funding transaction.")
type requiredArgs struct {
txid *btcwire.ShaHash
vout uint32
toAddress btcutil.Address
privKey *btcec.PrivateKey
}
// getArgs parses command line args and asserts that a private key and an
// address are present and correctly formatted.
func getArgs() requiredArgs {
flag.Parse()
if *a == "" || *k == "" || *t == "" || *v == -1 {
fmt.Println("\nThis tool generates a bitcoin transaction that moves coins from an input to an output.\n" +
"You must provide a key, an address, a transaction id (the hash\n" +
"of a tx) and the index into the outputs of that tx that fund your\n" +
"address! Use http://blockchain.info/pushtx to send the raw transaction.\n")
flag.PrintDefaults()
fmt.Println("")
os.Exit(0)
}
pkBytes, err := hex.DecodeString(*k)
if err != nil {
log.Fatal(err)
}
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), pkBytes)
addr, err := btcutil.DecodeAddress(*a, &btcnet.MainNetParams)
if err != nil {
log.Fatal(err)
}
txid, err := btcwire.NewShaHashFromStr(*t)
args := requiredArgs{
txid: txid,
vout: uint32(*v),
toAddress: addr,
privKey: privKey,
}
return args
}
type BlockChainInfoTxOut struct {
Value int `json:"value"`
ScriptHex string `json:"script"`
}
type blockChainInfoTx struct {
Ver int `json:"ver"`
Hash string `json:"hash"`
Outputs []BlockChainInfoTxOut `json:"out"`
}
// Uses the txid of the target funding transaction and asks blockchain.info's
// api for information (in json) relaated to that transaction.
func lookupTxid(hash *btcwire.ShaHash) *blockChainInfoTx {
url := "https://blockchain.info/rawtx/" + hash.String()
resp, err := http.Get(url)
if err != nil {
log.Fatal(fmt.Errorf("Tx Lookup failed: %v", err))
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(fmt.Errorf("TxInfo read failed: %s", err))
}
//fmt.Printf("%s\n", b)
txinfo := &blockChainInfoTx{}
err = json.Unmarshal(b, txinfo)
if err != nil {
log.Fatal(err)
}
if txinfo.Ver != 1 {
log.Fatal(fmt.Errorf("Blockchain.info's response seems bad: %v", txinfo))
}
return txinfo
}
// getFundingParams pulls the relevant transaction information from the json returned by blockchain.info
// To generate a new valid transaction all of the parameters of the TxOut we are
// spending from must be used.
func getFundingParams(rawtx *blockChainInfoTx, vout uint32) (*btcwire.TxOut, *btcwire.OutPoint) {
blkChnTxOut := rawtx.Outputs[vout]
hash, err := btcwire.NewShaHashFromStr(rawtx.Hash)
if err != nil {
log.Fatal(err)
}
// Then convert it to a btcutil amount
amnt := btcutil.Amount(int64(blkChnTxOut.Value))
if err != nil {
log.Fatal(err)
}
outpoint := btcwire.NewOutPoint(hash, vout)
subscript, err := hex.DecodeString(blkChnTxOut.ScriptHex)
if err != nil {
log.Fatal(err)
}
oldTxOut := btcwire.NewTxOut(int64(amnt), subscript)
return oldTxOut, outpoint
}
func main() {
// Pull the required arguments off of the command line.
reqArgs := getArgs()
// Get the bitcoin tx from blockchain.info's api
rawFundingTx := lookupTxid(reqArgs.txid)
// Get the parameters we need from the funding transaction
oldTxOut, outpoint := getFundingParams(rawFundingTx, reqArgs.vout)
// Formulate a new transaction from the provided parameters
tx := btcwire.NewMsgTx()
// Create the TxIn
txin := createTxIn(outpoint)
tx.AddTxIn(txin)
// Create the TxOut
txout := createTxOut(oldTxOut.Value, reqArgs.toAddress)
tx.AddTxOut(txout)
// Generate a signature over the whole tx.
sig := generateSig(tx, reqArgs.privKey, oldTxOut.PkScript)
tx.TxIn[0].SignatureScript = sig
// Dump the bytes to stdout
dumpHex(tx)
// Send the transaction to the network
broadcastTx(tx)
}
// createTxIn pulls the outpoint out of the funding TxOut and uses it as a reference
// for the txin that will be placed in a new transaction.
func createTxIn(outpoint *btcwire.OutPoint) *btcwire.TxIn {
// The second arg is the txin's signature script, which we are leaving empty
// until the entire transaction is ready.
txin := btcwire.NewTxIn(outpoint, []byte{})
return txin
}
// createTxOut generates a TxOut can be added to a transaction. Instead of sending
// every coin in the txin to the target address, a fee 10,000 Satoshi is set aside.
// If this fee is left out then, nodes on the network will ignore the transaction,
// since they would otherwise be providing you a service for free.
func createTxOut(inCoin int64, addr btcutil.Address) *btcwire.TxOut {
// Pay the minimum network fee so that nodes will broadcast the tx.
outCoin := inCoin - 10000
// Take the address and generate a PubKeyScript out of it
script, err := btcscript.PayToAddrScript(addr)
if err != nil {
log.Fatal(err)
}
txout := btcwire.NewTxOut(outCoin, script)
return txout
}
// generateSig requires a transaction, a private key, and the bytes of the raw
// scriptPubKey. It will then generate a signature over all of the outputs of
// the provided tx. This is the last step of creating a valid transaction.
func generateSig(tx *btcwire.MsgTx, privkey *btcec.PrivateKey, scriptPubKey []byte) []byte {
// The all important signature. Each input is documented below.
scriptSig, err := btcscript.SignatureScript(
tx, // The tx to be signed.
0, // The index of the txin the signature is for.
scriptPubKey, // The other half of the script from the PubKeyHash.
btcscript.SigHashAll, // The signature flags that indicate what the sig covers.
privkey, // The key to generate the signature with.
true, // The compress sig flag. This saves space on the blockchain.
)
if err != nil {
log.Fatal(err)
}
return scriptSig
}
// dumpHex dumps the raw bytes of a Bitcoin transaction to stdout. This is the
// format that Bitcoin wire's protocol accepts, so you could connect to a node,
// send them these bytes, and if the tx was valid, the node would forward the
// tx through the network.
func dumpHex(tx *btcwire.MsgTx) {
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
tx.Serialize(buf)
hexstr := hex.EncodeToString(buf.Bytes())
fmt.Println("Here is your raw bitcoin transaction:")
fmt.Println(hexstr)
}
type sendTxJson struct {
RawTx string `json:"rawtx"`
}
// broadcastTx tries to send the transaction using an api that will broadcast
// a submitted transaction on behalf of the user.
func broadcastTx(tx *btcwire.MsgTx) {
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
tx.Serialize(buf)
hexstr := hex.EncodeToString(buf.Bytes())
url := "https://insight.bitpay.com/api/tx/send"
contentType := "application/json"
sendTxJson := &sendTxJson{RawTx: hexstr}
j, err := json.Marshal(sendTxJson)
if err != nil {
log.Fatal(fmt.Errorf("Broadcasting the tx failed: %v", err))
}
buf = bytes.NewBuffer(j)
resp, err := http.Post(url, contentType, buf)
if err != nil {
log.Fatal(fmt.Errorf("Broadcasting the tx failed: %v", err))
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("The sending api responded with:\n%s\n", b)
}