bitcoin_op_return 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/bitcoin_op_return.rb +252 -0
  3. metadata +45 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c0c7cdf9a7cfb03cbb4eafafd35585e3f92d1738
4
+ data.tar.gz: ed12beeb25a03a976bbdef0fe718f1d7d42d475c
5
+ SHA512:
6
+ metadata.gz: 522d7927e0017e7ed33f44c8b8c68ad333bdb5be22c9b274f4adcbb2b86ae05529eb28d9dac551abe34e8421ac96a62f087c9a1c06fc806b1f359508bb56d910
7
+ data.tar.gz: 4e2d504780ea85d5f3fcef85fc791eeeeef6cbb92014b46dabe60fe038671f8e65912d64d97206d960c246a4ca740562365853b0400d93d7238fa1820cebc6c4
@@ -0,0 +1,252 @@
1
+ require "shellwords"
2
+ require "json"
3
+
4
+ class String
5
+ def shift! n
6
+ n > 0 ? slice!(0..(n-1)) : ""
7
+ end
8
+ end
9
+
10
+ module BitcoinOpReturn
11
+ BITCOIND_CMD = "/usr/local/bin/bitcoind"
12
+ TRANSACTION_FEE = 0.0001
13
+
14
+ class << self
15
+ def create options
16
+ send_address = options[:address].to_s
17
+ send_amount = options[:amount].to_f
18
+ metadata = options[:metadata].to_s
19
+ testnet = !options[:testnet].nil?
20
+
21
+ # convert to hex where possible
22
+ metadata = [ metadata ].pack("H*") if metadata =~ /\A([0-9A-Fa-f]{2})*\z/
23
+
24
+ return { "error" => "invalid address" } unless bitcoind("validateaddress", testnet, send_address)["isvalid"]
25
+ return { "error" => "metadata too long, limit is 75, recommended is 40 bytes" } if metadata.length > 75
26
+
27
+ # get the unspent inputs
28
+ unspent_inputs = bitcoind("listunspent", testnet, 0)
29
+ return { "error" => "unable to retrieve unspent inputs" } unless unspent_inputs.kind_of? Array
30
+
31
+ unspent_inputs.each do |input|
32
+ input["priority"] = input["amount"] * input["confirmations"]
33
+ end
34
+
35
+ unspent_inputs.sort! do |a, b|
36
+ a = a["priority"]
37
+ b = b["priority"]
38
+ # a follows b => -1
39
+ # a == b => 0
40
+ # b follows a => 1
41
+ a == b ? 0 : (a < b ? 1 : -1)
42
+ end
43
+
44
+ inputs_spend = []
45
+ output_amount = send_amount + TRANSACTION_FEE
46
+ inputs_amount = 0
47
+
48
+ unspent_inputs.each do |input|
49
+ inputs_spend << input
50
+ inputs_amount += input["amount"]
51
+ break if inputs_amount >= output_amount
52
+ end
53
+
54
+ return { "error" => "insufficient funds to carry out transaction" } if inputs_amount < output_amount
55
+
56
+
57
+ outputs_hash = {}
58
+ outputs_hash[send_address] = send_amount
59
+
60
+
61
+ unless inputs_amount == output_amount # no change
62
+ change = inputs_amount - output_amount
63
+ outputs_hash[bitcoind("getrawchangeaddress", testnet)] = change
64
+ end
65
+
66
+ # pack then unpack
67
+ raw_txn = bitcoind("createrawtransaction", testnet, inputs_spend, outputs_hash)
68
+
69
+ unpacked_txn = unpack_raw_txn(raw_txn)
70
+
71
+ # append opreturn (6a represents op_return)
72
+
73
+ op_return_script = "6a" + "#{metadata.length.chr}#{metadata}".unpack("H*")[0]
74
+
75
+ unpacked_txn["vout"].push({
76
+ "value" => 0,
77
+ "scriptPubKey" => op_return_script
78
+ })
79
+
80
+ # $raw_txn=coinspark_pack_raw_txn($txn_unpacked);
81
+ raw_txn = pack_raw_txn(unpacked_txn)
82
+
83
+ sign_txn_response = bitcoind("signrawtransaction", testnet, raw_txn)
84
+ # txid = bitcoind("sendrawtransaction", testnet, signed_txn)
85
+
86
+ return { :error => "error signing transaction" } unless sign_txn_response["complete"]
87
+
88
+ txid = bitcoind("sendrawtransaction", testnet, sign_txn_response["hex"])
89
+
90
+ if txid.length != 64
91
+ { :error => "could not send transaction" }
92
+ else
93
+ { :txid => txid }
94
+ end
95
+ end
96
+
97
+ private
98
+ def bitcoind cmd, testnet, *args
99
+ command = "#{BITCOIND_CMD} #{testnet ? "-testnet" : ""}"
100
+
101
+ command += " #{Shellwords.escape(cmd)}"
102
+
103
+ args.each do |x|
104
+ begin
105
+ command += " #{Shellwords.escape(JSON.generate(x))}"
106
+ rescue
107
+ command += " #{Shellwords.escape(x)}"
108
+ end
109
+ end
110
+
111
+ raw_result = `#{command}`.strip.chomp
112
+
113
+ begin
114
+ JSON.parse(raw_result)
115
+ rescue
116
+ raw_result
117
+ end
118
+ end
119
+
120
+ def parse_var_int binary
121
+ val = binary.shift!(1).unpack("C")[0]
122
+
123
+ if val == 0xFF # 64
124
+ unpack_uint64(binary.shift!(8))
125
+ elsif val == 0xFE # 32 bits
126
+ binary.shift!(4).unpack("V")[0]
127
+ elsif val == 0xFD # 16 bits
128
+ binary.shift!(2).unpack("v")[0]
129
+ else
130
+ val
131
+ end
132
+ end
133
+
134
+ def pack_var_int n
135
+ if n < 0xFD # uint8_t
136
+ [ n ].pack("C")
137
+ elsif n <= 0xFFFF #0xfd followed by the uint16_t
138
+ "\xFD".b + [ n ].pack("v")
139
+ elsif n <= 0xFFFFFFFF #0xfe followed by the uint32_t
140
+ "\xFE".b + [ n ].pack("V")
141
+ else #0xff followed by the length as uint64_t
142
+ "\xFF".b + pack_uint64(n)
143
+ end
144
+ end
145
+
146
+ def unpack_uint64 str
147
+ # since it is lsb first ...
148
+ str[0..3].unpack("V*")[0] + str[4..7].unpack("V*")[0] * (2**32)
149
+ end
150
+
151
+ def pack_uint64 n
152
+ first = [ n % (2**32) ].pack("V") # small byte
153
+ second = [ (n / (2**32)).round ].pack("V") # big byte
154
+ first + second
155
+ end
156
+
157
+ def pack_raw_txn txn
158
+ binary = "".b
159
+
160
+ # pack version into 32 bit integer (little endian)
161
+ # $binary.=pack('V', $txn['version']);
162
+ binary += [ txn["version"] ].pack("V")
163
+
164
+ # $binary.=coinspark_pack_varint(count($txn['vin']));
165
+ # pack varint number of input
166
+ binary += pack_var_int(txn["vin"].length)
167
+
168
+ # pack the inputs
169
+ txn["vin"].each do |input|
170
+ binary += [ input["txid"] ].pack("H*").reverse
171
+ binary += [ input["vout"] ].pack("V")
172
+ # divide 2 because to positions in a hex string represents a byte
173
+ binary += pack_var_int(input["scriptSig"].length / 2)
174
+ binary += [ input["scriptSig"] ].pack("H*")
175
+
176
+ binary += [ input["sequence"] ].pack("V")
177
+ # input["sequence"].to_s(16).split("").each do |x|
178
+ # binary += x
179
+ # end
180
+ end
181
+
182
+ # pack varint number of outputs
183
+ binary += pack_var_int(txn["vout"].length)
184
+
185
+ # start packing the output
186
+ txn["vout"].each do |output|
187
+ binary += pack_uint64((output["value"] * 100000000).round)
188
+ binary += pack_var_int(output["scriptPubKey"].length / 2)
189
+ binary += [ output["scriptPubKey"] ].pack("H*")
190
+ end
191
+
192
+ # then append the lock time
193
+ binary += [ txn["locktime"] ].pack("V")
194
+
195
+ # convert the real reprsentation into a hex string
196
+ binary.unpack("H*")[0]
197
+ end
198
+
199
+ def unpack_raw_txn hex
200
+ # // see: https://en.bitcoin.it/wiki/Transactions
201
+
202
+ # convert the hex representation into a real string]
203
+ binary = [ hex ].pack("H*")
204
+
205
+ txn = {}
206
+
207
+ # get the version and initialize array
208
+
209
+ txn["version"] = binary.shift!(4).unpack("V")[0]
210
+ txn["vin"] = []
211
+ txn["vout"] = []
212
+
213
+ # parse the number of inputs
214
+ n = parse_var_int(binary)
215
+
216
+ # parse the inputs
217
+ n.times do
218
+ input = {}
219
+ input["txid"] = binary.shift!(32).reverse.unpack("H*")[0]
220
+ input["vout"] = binary.shift!(4).unpack("V")[0]
221
+ script_length = parse_var_int(binary)
222
+
223
+ input["scriptSig"] = binary.shift!(script_length).unpack("H*")[0]
224
+ input["sequence"] = binary.shift!(4).unpack("V")[0]
225
+ txn["vin"] << input
226
+ end
227
+
228
+ # parse the number of outputs
229
+ n = parse_var_int(binary)
230
+
231
+ # parse the outputs
232
+ n.times do
233
+ output = {}
234
+ output["value"] = binary.shift!(8).unpack("V")[0] / 100000000.0
235
+ # remember it is stored in satoshi inernally
236
+
237
+ script_length = parse_var_int(binary)
238
+ output["scriptPubKey"] = binary.shift!(script_length).unpack("H*")[0]
239
+ txn["vout"] << output
240
+ end
241
+
242
+ # locktime
243
+ txn["locktime"] = binary.shift!(4).unpack("V")[0]
244
+
245
+ # error handling
246
+ exit ("unexpected data in transaction") unless binary.length == 0
247
+
248
+ # finally, return
249
+ txn
250
+ end
251
+ end
252
+ end
metadata ADDED
@@ -0,0 +1,45 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitcoin_op_return
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - FY Quah
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A simple gem for you to send op_return into the bitcoin blockchain with
14
+ ruby
15
+ email: fuyong@fyquah.me
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/bitcoin_op_return.rb
21
+ homepage: https://github.com/fyquah95/bitcoin_op_return
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubyforge_project:
41
+ rubygems_version: 2.2.2
42
+ signing_key:
43
+ specification_version: 4
44
+ summary: Send OP Return transactions
45
+ test_files: []