btcruby 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +18 -0
- data/.travis.yml +7 -0
- data/FAQ.md +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +18 -0
- data/HOWTO.md +17 -0
- data/LICENSE +19 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/TODO.txt +40 -0
- data/bin/console +19 -0
- data/btcruby.gemspec +20 -0
- data/documentation/address.md +73 -0
- data/documentation/base58.md +52 -0
- data/documentation/block.md +127 -0
- data/documentation/block_header.md +120 -0
- data/documentation/constants.md +88 -0
- data/documentation/data.md +54 -0
- data/documentation/diagnostics.md +90 -0
- data/documentation/extensions.md +76 -0
- data/documentation/hash_functions.md +58 -0
- data/documentation/hash_id.md +22 -0
- data/documentation/index.md +230 -0
- data/documentation/key.md +177 -0
- data/documentation/keychain.md +180 -0
- data/documentation/network.md +75 -0
- data/documentation/opcode.md +220 -0
- data/documentation/openssl.md +7 -0
- data/documentation/p2pkh.md +71 -0
- data/documentation/p2sh.md +64 -0
- data/documentation/proof_of_work.md +84 -0
- data/documentation/script.md +280 -0
- data/documentation/signature.md +71 -0
- data/documentation/transaction.md +213 -0
- data/documentation/transaction_builder.md +188 -0
- data/documentation/transaction_input.md +133 -0
- data/documentation/transaction_output.md +130 -0
- data/documentation/wif.md +72 -0
- data/documentation/wire_format.md +70 -0
- data/lib/btcruby/address.rb +296 -0
- data/lib/btcruby/base58.rb +108 -0
- data/lib/btcruby/big_number.rb +47 -0
- data/lib/btcruby/block.rb +170 -0
- data/lib/btcruby/block_header.rb +231 -0
- data/lib/btcruby/constants.rb +59 -0
- data/lib/btcruby/currency_formatter.rb +64 -0
- data/lib/btcruby/data.rb +98 -0
- data/lib/btcruby/diagnostics.rb +92 -0
- data/lib/btcruby/errors.rb +8 -0
- data/lib/btcruby/extensions.rb +65 -0
- data/lib/btcruby/hash_functions.rb +54 -0
- data/lib/btcruby/hash_id.rb +18 -0
- data/lib/btcruby/key.rb +517 -0
- data/lib/btcruby/keychain.rb +464 -0
- data/lib/btcruby/network.rb +73 -0
- data/lib/btcruby/opcode.rb +197 -0
- data/lib/btcruby/open_assets/asset.rb +35 -0
- data/lib/btcruby/open_assets/asset_address.rb +49 -0
- data/lib/btcruby/open_assets/asset_definition.rb +75 -0
- data/lib/btcruby/open_assets/asset_id.rb +24 -0
- data/lib/btcruby/open_assets/asset_marker.rb +94 -0
- data/lib/btcruby/open_assets/asset_processor.rb +377 -0
- data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
- data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
- data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
- data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
- data/lib/btcruby/open_assets.rb +26 -0
- data/lib/btcruby/openssl.rb +536 -0
- data/lib/btcruby/proof_of_work.rb +110 -0
- data/lib/btcruby/safety.rb +26 -0
- data/lib/btcruby/script.rb +733 -0
- data/lib/btcruby/signature_hashtype.rb +37 -0
- data/lib/btcruby/transaction.rb +511 -0
- data/lib/btcruby/transaction_builder/errors.rb +15 -0
- data/lib/btcruby/transaction_builder/provider.rb +54 -0
- data/lib/btcruby/transaction_builder/result.rb +73 -0
- data/lib/btcruby/transaction_builder/signer.rb +28 -0
- data/lib/btcruby/transaction_builder.rb +520 -0
- data/lib/btcruby/transaction_input.rb +298 -0
- data/lib/btcruby/transaction_outpoint.rb +30 -0
- data/lib/btcruby/transaction_output.rb +315 -0
- data/lib/btcruby/version.rb +3 -0
- data/lib/btcruby/wif.rb +118 -0
- data/lib/btcruby/wire_format.rb +362 -0
- data/lib/btcruby.rb +44 -2
- data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
- data/sample_code/creating_a_transaction_manually.rb +44 -0
- data/sample_code/generating_an_address.rb +20 -0
- data/sample_code/using_transaction_builder.rb +49 -0
- data/spec/address_spec.rb +206 -0
- data/spec/all.rb +6 -0
- data/spec/base58_spec.rb +83 -0
- data/spec/block_header_spec.rb +18 -0
- data/spec/block_spec.rb +18 -0
- data/spec/currency_formatter_spec.rb +46 -0
- data/spec/data_spec.rb +50 -0
- data/spec/diagnostics_spec.rb +41 -0
- data/spec/key_spec.rb +205 -0
- data/spec/keychain_spec.rb +261 -0
- data/spec/network_spec.rb +48 -0
- data/spec/open_assets/asset_address_spec.rb +33 -0
- data/spec/open_assets/asset_id_spec.rb +15 -0
- data/spec/open_assets/asset_marker_spec.rb +47 -0
- data/spec/open_assets/asset_processor_spec.rb +567 -0
- data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
- data/spec/open_assets/asset_transaction_spec.rb +70 -0
- data/spec/proof_of_work_spec.rb +53 -0
- data/spec/script_spec.rb +66 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/transaction_builder_spec.rb +338 -0
- data/spec/transaction_spec.rb +162 -0
- data/spec/wire_format_spec.rb +283 -0
- metadata +141 -7
@@ -0,0 +1,37 @@
|
|
1
|
+
# Hash type determines how OP_CHECKSIG hashes the transaction to create or
|
2
|
+
# verify the signature in a transaction input.
|
3
|
+
# Depending on hash type, transaction is modified in some way before its hash is computed.
|
4
|
+
# Hash type is 1 byte appended to a signature in a transaction input.
|
5
|
+
module BTC
|
6
|
+
# First three types are mutually exclusive (tested using "type & 0x1F").
|
7
|
+
|
8
|
+
# Default. Signs all inputs and outputs.
|
9
|
+
# Other inputs have scripts and sequences zeroed out, current input has its script
|
10
|
+
# replaced by the previous transaction's output script (or, in case of P2SH,
|
11
|
+
# by the signatures and the redemption script).
|
12
|
+
# If (type & 0x1F) is not NONE or SINGLE, then this type is used.
|
13
|
+
SIGHASH_ALL = 1
|
14
|
+
|
15
|
+
# All outputs are removed. "I don't care where it goes as long as others pay".
|
16
|
+
# Note: this is not safe when used with ANYONECANPAY, because then anyone who relays the transaction
|
17
|
+
# can pick your input and use in his own transaction.
|
18
|
+
# It's also not safe if all inputs are SIGHASH_NONE as well (or it's the only input).
|
19
|
+
SIGHASH_NONE = 2
|
20
|
+
|
21
|
+
# Hash only the output with the same index as the current input.
|
22
|
+
# Preceding outputs are "nullified", other outputs are removed.
|
23
|
+
# Special case: if there is no matching output, hash is equal
|
24
|
+
# "0000000000000000000000000000000000000000000000000000000000000001" (32 bytes)
|
25
|
+
SIGHASH_SINGLE = 3
|
26
|
+
|
27
|
+
# This mask is used to determine one of the first types independently from ANYONECANPAY option:
|
28
|
+
# E.g. if (type & SIGHASH_OUTPUT_MASK == SIGHASH_NONE) { blank all outputs }
|
29
|
+
SIGHASH_OUTPUT_MASK = 0x1F
|
30
|
+
|
31
|
+
# Removes all inputs except for current txin before hashing.
|
32
|
+
# This allows to sign the transaction outputs without knowing who and how adds other inputs.
|
33
|
+
# E.g. a crowdfunding transaction with 100 BTC output can be signed independently by any number of people
|
34
|
+
# and will become valid only when someone combines all inputs in a single transaction to make it valid.
|
35
|
+
# Can be used together with any of the above types.
|
36
|
+
SIGHASH_ANYONECANPAY = 0x80
|
37
|
+
end
|
@@ -0,0 +1,511 @@
|
|
1
|
+
# Transaction represents a bitcoin transfer from one or more inputs to one or more outputs.
|
2
|
+
require 'stringio'
|
3
|
+
module BTC
|
4
|
+
|
5
|
+
class Transaction
|
6
|
+
|
7
|
+
CURRENT_VERSION = 1
|
8
|
+
|
9
|
+
DEFAULT_FEE_RATE = 10_000 # satoshis per 1000 bytes
|
10
|
+
DEFAULT_RELAY_FEE_RATE = 1000 # satoshis per 1000 bytes
|
11
|
+
|
12
|
+
# Core attributes.
|
13
|
+
|
14
|
+
# Version of the transaction. Default is CURRENT_VERSION.
|
15
|
+
attr_accessor :version
|
16
|
+
|
17
|
+
# List of TransactionInputs. See also #add_input and #remove_all_inputs.
|
18
|
+
attr_accessor :inputs
|
19
|
+
|
20
|
+
# List of TransactionOutputs. See also #add_output and #remove_all_outputs.
|
21
|
+
attr_accessor :outputs
|
22
|
+
|
23
|
+
# Lock time. Either a block height or a unix timestamp.
|
24
|
+
# Default is 0.
|
25
|
+
attr_accessor :lock_time
|
26
|
+
|
27
|
+
|
28
|
+
# Derived attributes.
|
29
|
+
|
30
|
+
# 32-byte transaction hash
|
31
|
+
attr_reader :transaction_hash
|
32
|
+
|
33
|
+
# Hexadecimal transaction ID with bytes reversed.
|
34
|
+
# Used by Chain.com, Blockchain.info, Blockr.io.
|
35
|
+
attr_reader :transaction_id
|
36
|
+
|
37
|
+
# Binary representation of the transaction in wire format (aka payload).
|
38
|
+
attr_reader :data
|
39
|
+
|
40
|
+
# Dictionary representation of transaction ready to be encoded in JSON, PropertyList etc.
|
41
|
+
attr_reader :dictionary
|
42
|
+
|
43
|
+
|
44
|
+
# Optional Attributes.
|
45
|
+
# These are not derived from tx data, but attached externally (e.g. via external APIs).
|
46
|
+
|
47
|
+
# Binary hash of the block at which transaction was included.
|
48
|
+
# If not confirmed or not available, equals nil.
|
49
|
+
attr_accessor :block_hash
|
50
|
+
|
51
|
+
# Hex-encoded block ID.
|
52
|
+
# If not confirmed or not available, equals nil.
|
53
|
+
attr_accessor :block_id
|
54
|
+
|
55
|
+
# Height of the block at which transaction was included.
|
56
|
+
# If not confirmed equals -1.
|
57
|
+
# Note: `block_height` might not be provided by some APIs while `confirmations` may be.
|
58
|
+
attr_accessor :block_height
|
59
|
+
|
60
|
+
# Time of the block at which tx was included (::Time instance or nil).
|
61
|
+
attr_accessor :block_time
|
62
|
+
|
63
|
+
# Number of confirmations for this transaction (depth in the blockchan).
|
64
|
+
# 0 stands for unconfirmed mempool transaction. Default is nil ("no info").
|
65
|
+
attr_accessor :confirmations
|
66
|
+
|
67
|
+
# If available, returns mining fee paid by this transaction.
|
68
|
+
# If set, `inputs_amount` is updated as (`outputs_amount` + `fee`).
|
69
|
+
# Default is nil.
|
70
|
+
attr_accessor :fee
|
71
|
+
|
72
|
+
# If available, returns total amount of all inputs.
|
73
|
+
# If set, `fee` is updated as (`inputs_amount` - `outputs_amount`).
|
74
|
+
# Default is nil.
|
75
|
+
attr_accessor :inputs_amount
|
76
|
+
|
77
|
+
# Total amount on all outputs (not including fees).
|
78
|
+
# Always available since outputs contain their amounts.
|
79
|
+
attr_reader :outputs_amount
|
80
|
+
|
81
|
+
# Initializes transaction with its attributes. Every attribute has a valid default value.
|
82
|
+
def initialize(hex: nil,
|
83
|
+
data: nil,
|
84
|
+
stream: nil,
|
85
|
+
dictionary: nil,
|
86
|
+
version: CURRENT_VERSION,
|
87
|
+
inputs: [],
|
88
|
+
outputs: [],
|
89
|
+
lock_time: 0,
|
90
|
+
|
91
|
+
# optional attributes
|
92
|
+
block_hash: nil,
|
93
|
+
block_id: nil,
|
94
|
+
block_height: nil,
|
95
|
+
block_time: nil,
|
96
|
+
confirmations: nil,
|
97
|
+
fee: nil,
|
98
|
+
inputs_amount: nil)
|
99
|
+
|
100
|
+
data = BTC::Data.data_from_hex(hex) if hex
|
101
|
+
stream = StringIO.new(data) if data
|
102
|
+
if stream
|
103
|
+
init_with_stream(stream)
|
104
|
+
elsif dictionary
|
105
|
+
init_with_dictionary(dictionary)
|
106
|
+
else
|
107
|
+
init_with_components(version: version, inputs: inputs, outputs: outputs, lock_time: lock_time)
|
108
|
+
end
|
109
|
+
|
110
|
+
@block_hash = block_hash
|
111
|
+
@block_hash = BTC.hash_from_id(block_id) if block_id
|
112
|
+
@block_height = block_height
|
113
|
+
@block_time = block_time
|
114
|
+
@confirmations = confirmations
|
115
|
+
@fee = fee
|
116
|
+
@inputs_amount = inputs_amount
|
117
|
+
end
|
118
|
+
|
119
|
+
def init_with_components(version: CURRENT_VERSION, inputs: [], outputs: [], lock_time: 0)
|
120
|
+
@version = version || CURRENT_VERSION
|
121
|
+
@inputs = inputs || []
|
122
|
+
@outputs = outputs || []
|
123
|
+
@lock_time = lock_time || 0
|
124
|
+
@inputs.each_with_index do |txin, i|
|
125
|
+
txin.transaction = self
|
126
|
+
txin.index = i
|
127
|
+
end
|
128
|
+
@outputs.each_with_index do |txout, i|
|
129
|
+
txout.transaction = self
|
130
|
+
txout.index = i
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def init_with_stream(stream)
|
135
|
+
raise ArgumentError, "Stream is missing" if !stream
|
136
|
+
if stream.eof?
|
137
|
+
raise ArgumentError, "Can't parse transaction from stream because it is already closed."
|
138
|
+
end
|
139
|
+
|
140
|
+
if !(version = BTC::WireFormat.read_int32le(stream: stream).first)
|
141
|
+
raise ArgumentError, "Failed to read version prefix from the stream."
|
142
|
+
end
|
143
|
+
|
144
|
+
if !(inputs_count = BTC::WireFormat.read_varint(stream: stream).first)
|
145
|
+
raise ArgumentError, "Failed to read inputs count from the stream."
|
146
|
+
end
|
147
|
+
|
148
|
+
txins = (0...inputs_count).map do
|
149
|
+
TransactionInput.new(stream: stream)
|
150
|
+
end
|
151
|
+
|
152
|
+
if !(outputs_count = BTC::WireFormat.read_varint(stream: stream).first)
|
153
|
+
raise ArgumentError, "Failed to read outputs count from the stream."
|
154
|
+
end
|
155
|
+
|
156
|
+
txouts = (0...outputs_count).map do
|
157
|
+
TransactionOutput.new(stream: stream)
|
158
|
+
end
|
159
|
+
|
160
|
+
if !(lock_time = BTC::WireFormat.read_uint32le(stream: stream).first)
|
161
|
+
raise ArgumentError, "Failed to read lock_time from the stream."
|
162
|
+
end
|
163
|
+
|
164
|
+
init_with_components(version: version, inputs: txins, outputs: txouts, lock_time: lock_time)
|
165
|
+
end
|
166
|
+
|
167
|
+
def init_with_dictionary(dict)
|
168
|
+
version = dict["ver"] || CURRENT_VERSION
|
169
|
+
lock_time = dict["lock_time"] || 0
|
170
|
+
|
171
|
+
txins = dict["in"].map { |i| TransactionInput.new(dictionary: i) }
|
172
|
+
txouts = dict["out"].map {|o| TransactionOutput.new(dictionary: o) }
|
173
|
+
|
174
|
+
init_with_components(version: version, inputs: txins, outputs: txouts, lock_time: lock_time)
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.with_data(data)
|
178
|
+
raise ArgumentError, "Use Transaction.new(data: ...) instead"
|
179
|
+
end
|
180
|
+
|
181
|
+
def self.with_stream(stream)
|
182
|
+
raise ArgumentError, "Use Transaction.new(stream: ...) instead"
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.with_dictionary(dict)
|
186
|
+
raise ArgumentError, "Use Transaction.new(dictionary: ...)"
|
187
|
+
end
|
188
|
+
|
189
|
+
# Returns true if this transaction is a coinbase transaction.
|
190
|
+
def coinbase?
|
191
|
+
self.inputs.size == 1 && self.inputs[0].coinbase?
|
192
|
+
end
|
193
|
+
|
194
|
+
# Returns `true` if this transaction contains an Open Assets marker.
|
195
|
+
# Does not perform expensive validation.
|
196
|
+
# Use this method to quickly filter out non-asset transactions.
|
197
|
+
def open_assets_transaction?
|
198
|
+
self.outputs.any? {|txout| txout.script.open_assets_marker? }
|
199
|
+
end
|
200
|
+
|
201
|
+
def inputs=(inputs)
|
202
|
+
remove_all_inputs
|
203
|
+
@inputs = inputs || []
|
204
|
+
@inputs.each_with_index do |txin, i|
|
205
|
+
txin.transaction = self
|
206
|
+
txin.index = i
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def outputs=(outputs)
|
211
|
+
remove_all_outputs
|
212
|
+
@outputs = outputs || []
|
213
|
+
@outputs.each_with_index do |txout, i|
|
214
|
+
txout.transaction = self
|
215
|
+
txout.index = i
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Adds another input to the transaction.
|
220
|
+
def add_input(txin)
|
221
|
+
raise ArgumentError, "Input is missing" if !txin
|
222
|
+
if !(txin.transaction == nil || txin.transaction == self)
|
223
|
+
raise ArgumentError, "Can't add an input to a transaction when it references another transaction" # sanity check
|
224
|
+
end
|
225
|
+
txin.transaction = self
|
226
|
+
txin.index = @inputs.size
|
227
|
+
@inputs << txin
|
228
|
+
self
|
229
|
+
end
|
230
|
+
|
231
|
+
def add_output(txout)
|
232
|
+
raise ArgumentError, "Output is missing" if !txout
|
233
|
+
if !(txout.transaction == nil || txout.transaction == self)
|
234
|
+
raise ArgumentError, "Can't add an output to a transaction when it references another transaction" # sanity check
|
235
|
+
end
|
236
|
+
txout.transaction = self
|
237
|
+
txout.index = @outputs.size
|
238
|
+
@outputs << txout
|
239
|
+
self
|
240
|
+
end
|
241
|
+
|
242
|
+
def remove_all_inputs
|
243
|
+
return if !@inputs
|
244
|
+
@inputs.each do |txin|
|
245
|
+
txin.transaction = nil
|
246
|
+
txin.index = nil
|
247
|
+
end
|
248
|
+
@inputs = []
|
249
|
+
self
|
250
|
+
end
|
251
|
+
|
252
|
+
def remove_all_outputs
|
253
|
+
return if !@outputs
|
254
|
+
@outputs.each do |txout|
|
255
|
+
txout.transaction = nil
|
256
|
+
txout.index = nil
|
257
|
+
end
|
258
|
+
@outputs = []
|
259
|
+
self
|
260
|
+
end
|
261
|
+
|
262
|
+
def transaction_hash
|
263
|
+
BTC.hash256(self.data)
|
264
|
+
end
|
265
|
+
|
266
|
+
def transaction_id
|
267
|
+
BTC.id_from_hash(self.transaction_hash)
|
268
|
+
end
|
269
|
+
|
270
|
+
def block_id
|
271
|
+
BTC.id_from_hash(self.block_hash)
|
272
|
+
end
|
273
|
+
|
274
|
+
def block_id=(block_id)
|
275
|
+
self.block_hash = BTC.hash_from_id(block_id)
|
276
|
+
end
|
277
|
+
|
278
|
+
# Amounts and fee
|
279
|
+
|
280
|
+
def fee=(fee)
|
281
|
+
@fee = fee
|
282
|
+
@inputs_amount = nil # will be computed from fee.
|
283
|
+
end
|
284
|
+
|
285
|
+
def fee
|
286
|
+
return @fee if @fee
|
287
|
+
if ia = self.inputs_amount
|
288
|
+
return (ia - self.outputs_amount)
|
289
|
+
end
|
290
|
+
return nil
|
291
|
+
end
|
292
|
+
|
293
|
+
def inputs_amount=(inputs_amount)
|
294
|
+
@inputs_amount = inputs_amount
|
295
|
+
@fee = nil # will be computed from inputs_amount. inputs_amount ? (inputs_amount - self.outputs_amount) : nil
|
296
|
+
end
|
297
|
+
|
298
|
+
def inputs_amount
|
299
|
+
return @inputs_amount if @inputs_amount
|
300
|
+
return (@fee + self.outputs_amount) if @fee
|
301
|
+
# Try to figure the total amount from amounts on inputs.
|
302
|
+
# If all of them are non-nil, we have a valid amount.
|
303
|
+
inputs.inject(0) do |total, input|
|
304
|
+
if total && (v = input.value)
|
305
|
+
total + input.value
|
306
|
+
else
|
307
|
+
return nil # quickly return nil
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def outputs_amount
|
313
|
+
self.outputs.inject(0){|t,o| t + o.value}
|
314
|
+
end
|
315
|
+
|
316
|
+
def data
|
317
|
+
data = "".b
|
318
|
+
data << BTC::WireFormat.encode_int32le(self.version)
|
319
|
+
data << BTC::WireFormat.encode_varint(self.inputs.size)
|
320
|
+
self.inputs.each do |txin|
|
321
|
+
data << txin.data
|
322
|
+
end
|
323
|
+
data << BTC::WireFormat.encode_varint(self.outputs.size)
|
324
|
+
self.outputs.each do |txout|
|
325
|
+
data << txout.data
|
326
|
+
end
|
327
|
+
data << BTC::WireFormat.encode_uint32le(self.lock_time)
|
328
|
+
data
|
329
|
+
end
|
330
|
+
|
331
|
+
def dictionary
|
332
|
+
{
|
333
|
+
"hash" => self.transaction_id,
|
334
|
+
"ver" => self.version,
|
335
|
+
"vin_sz" => self.inputs.size,
|
336
|
+
"vout_sz" => self.outputs.size,
|
337
|
+
"lock_time" => self.lock_time,
|
338
|
+
"size" => self.data.bytesize,
|
339
|
+
"in" => self.inputs.map{|i| i.dictionary},
|
340
|
+
"out" => self.outputs.map{|o| o.dictionary}
|
341
|
+
}
|
342
|
+
end
|
343
|
+
|
344
|
+
# Hash for signing a transaction.
|
345
|
+
# You should specify an input index, output script of the previous transaction for that input,
|
346
|
+
# and an optional hash type (default is SIGHASH_ALL).
|
347
|
+
def signature_hash(input_index: nil, output_script: nil, hash_type: BTC::SIGHASH_ALL)
|
348
|
+
|
349
|
+
raise ArgumentError, "Should specify input_index in Transaction#signature_hash." if !input_index
|
350
|
+
raise ArgumentError, "Should specify output_script in Transaction#signature_hash." if !output_script
|
351
|
+
raise ArgumentError, "Should specify hash_type in Transaction#signature_hash." if !hash_type
|
352
|
+
|
353
|
+
# Create a temporary copy of the transaction to apply modifications to it.
|
354
|
+
tx = self.dup
|
355
|
+
|
356
|
+
# Note: BitcoinQT returns a 256-bit little-endian number 1 in such case,
|
357
|
+
# but it does not matter because it would crash before that in CScriptCheck::operator()().
|
358
|
+
# We normally won't enter this condition if script machine is instantiated
|
359
|
+
# with transaction and input index, but it's better to check anyway.
|
360
|
+
if (input_index >= tx.inputs.size)
|
361
|
+
raise ArgumentError, "Input index is out of bounds for transaction: #{input_index} >= #{tx.inputs.size}"
|
362
|
+
end
|
363
|
+
|
364
|
+
# In case concatenating two scripts ends up with two codeseparators,
|
365
|
+
# or an extra one at the end, this prevents all those possible incompatibilities.
|
366
|
+
# Note: this normally never happens because there is no use for OP_CODESEPARATOR.
|
367
|
+
# But we have to do that cleanup anyway to not break on rare transaction that use that for lulz.
|
368
|
+
# Also: we modify the same subscript which is used several times for multisig check,
|
369
|
+
# but that's what BitcoinQT does as well.
|
370
|
+
output_script.delete_opcode(BTC::OP_CODESEPARATOR)
|
371
|
+
|
372
|
+
# Blank out other inputs' signature scripts
|
373
|
+
# and replace our input script with a subscript (which is typically a full
|
374
|
+
# output script from the previous transaction).
|
375
|
+
tx.inputs.each do |txin|
|
376
|
+
txin.signature_script = BTC::Script.new
|
377
|
+
end
|
378
|
+
tx.inputs[input_index].signature_script = output_script
|
379
|
+
|
380
|
+
# Blank out some of the outputs depending on BTCSignatureHashType
|
381
|
+
# Default is SIGHASH_ALL - all inputs and outputs are signed.
|
382
|
+
if (hash_type & BTC::SIGHASH_OUTPUT_MASK) == BTC::SIGHASH_NONE
|
383
|
+
# Wildcard payee - we can pay anywhere.
|
384
|
+
tx.remove_all_outputs
|
385
|
+
|
386
|
+
# Blank out others' input sequence numbers to let others update transaction at will.
|
387
|
+
tx.inputs.each_with_index do |txin, i|
|
388
|
+
if i != input_index
|
389
|
+
tx.inputs[i].sequence = 0
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
# Single mode assumes we sign an output at the same index as an input.
|
394
|
+
# Outputs before the one we need are blanked out. All outputs after are simply removed.
|
395
|
+
elsif (hash_type & BTC::SIGHASH_OUTPUT_MASK) == BTC::SIGHASH_SINGLE
|
396
|
+
|
397
|
+
# Only lock-in the txout payee at same index as txin.
|
398
|
+
output_index = input_index;
|
399
|
+
|
400
|
+
# If output_index is out of bounds, BitcoinQT is returning a 256-bit little-endian 0x01 instead of failing with error.
|
401
|
+
# We should do the same to stay compatible.
|
402
|
+
if output_index >= tx.outputs.size
|
403
|
+
return "\x01" + "\x00"*31
|
404
|
+
end
|
405
|
+
|
406
|
+
# All outputs before the one we need are blanked out. All outputs after are simply removed.
|
407
|
+
# This is equivalent to replacing outputs with (i-1) empty outputs and a i-th original one.
|
408
|
+
my_output = tx.outputs[output_index]
|
409
|
+
tx.remove_all_outputs
|
410
|
+
(0...output_index).each do |i|
|
411
|
+
tx.add_output(BTC::TransactionOutput.new)
|
412
|
+
end
|
413
|
+
tx.add_output(my_output)
|
414
|
+
|
415
|
+
# Blank out others' input sequence numbers to let others update transaction at will.
|
416
|
+
tx.inputs.each_with_index do |txin, i|
|
417
|
+
if i != input_index
|
418
|
+
tx.inputs[i].sequence = 0
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end # if hashtype is none or single
|
422
|
+
|
423
|
+
# Blank out other inputs completely. This is not recommended for open transactions.
|
424
|
+
if (hash_type & BTC::SIGHASH_ANYONECANPAY) != 0
|
425
|
+
input = tx.inputs[input_index]
|
426
|
+
tx.remove_all_outputs
|
427
|
+
tx.add_input(input)
|
428
|
+
end
|
429
|
+
|
430
|
+
# Important: we have to hash transaction together with its hash type.
|
431
|
+
# Hash type is appended as a little endian uint32 unlike 1-byte suffix of the signature.
|
432
|
+
data = tx.data + BTC::WireFormat.encode_uint32le(hash_type)
|
433
|
+
hash = BTC.hash256(data)
|
434
|
+
|
435
|
+
return hash
|
436
|
+
end
|
437
|
+
|
438
|
+
# Compute a fee for a transaction of a given size with a specified per-KB fee rate.
|
439
|
+
# By default uses built-in DEFAULT_FEE_RATE.
|
440
|
+
# Makes sure that whole number of fee_rate amounts are paid.
|
441
|
+
def self.compute_fee(size, fee_rate: DEFAULT_FEE_RATE)
|
442
|
+
return 0 if fee_rate <= 0
|
443
|
+
fee = fee_rate*size/1000 # according to Bitcoin Core as of March 15, 2015.
|
444
|
+
fee = fee_rate if fee == 0
|
445
|
+
fee
|
446
|
+
end
|
447
|
+
|
448
|
+
# Compute a fee for this transaction with a specified per-KB fee rate.
|
449
|
+
# By default uses built-in DEFAULT_FEE_RATE.
|
450
|
+
def compute_fee(fee_rate: DEFAULT_FEE_RATE)
|
451
|
+
self.class.compute_fee(self.data.bytesize, fee_rate: fee_rate)
|
452
|
+
end
|
453
|
+
|
454
|
+
# Returns dictionary representation of the transaction.
|
455
|
+
def to_h
|
456
|
+
self.dictionary
|
457
|
+
end
|
458
|
+
|
459
|
+
# Returns hex representation of the transaction.
|
460
|
+
def to_s
|
461
|
+
to_hex
|
462
|
+
end
|
463
|
+
|
464
|
+
# Returns hex representation of the transaction.
|
465
|
+
def to_hex
|
466
|
+
BTC::Data.hex_from_data(self.data)
|
467
|
+
end
|
468
|
+
|
469
|
+
# Makes a deep copy of a transaction (all inputs and outputs are copied too).
|
470
|
+
def dup
|
471
|
+
Transaction.new(version: @version,
|
472
|
+
inputs: (@inputs || []).map{|txin|txin.dup},
|
473
|
+
outputs: (@outputs || []).map{|txout|txout.dup},
|
474
|
+
lock_time: @lock_time,
|
475
|
+
block_hash: @block_hash,
|
476
|
+
block_height: @block_height,
|
477
|
+
block_time: @block_time,
|
478
|
+
confirmations: @confirmations,
|
479
|
+
fee: @fee,
|
480
|
+
inputs_amount: @inputs_amount)
|
481
|
+
end
|
482
|
+
|
483
|
+
def ==(other)
|
484
|
+
return false if other == nil
|
485
|
+
self.data == other.data
|
486
|
+
end
|
487
|
+
alias_method :eql?, :==
|
488
|
+
|
489
|
+
def inspect
|
490
|
+
%{#<#{self.class.name}:#{transaction_id}} +
|
491
|
+
%{ v#{version}} +
|
492
|
+
(lock_time > 0 ? %{ lock_time:#{lock_time} #{lock_time > LOCKTIME_THRESHOLD ? 'sec' : 'block'}} : "") +
|
493
|
+
%{ inputs:[#{inputs.map{|i|i.inspect(:light)}.join(", ")}]} +
|
494
|
+
%{ outputs:[#{outputs.map{|o|o.inspect(:light)}.join(", ")}]} +
|
495
|
+
%{>}
|
496
|
+
end
|
497
|
+
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
# if $0 == __FILE__
|
502
|
+
# require_relative "../btcruby.rb"
|
503
|
+
# require 'pp'
|
504
|
+
# include BTC
|
505
|
+
#
|
506
|
+
# tx = Transaction.new
|
507
|
+
# puts tx.inspect
|
508
|
+
# pp tx.to_h
|
509
|
+
# puts tx.to_s
|
510
|
+
#
|
511
|
+
# end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module BTC
|
2
|
+
TransactionBuilder
|
3
|
+
class TransactionBuilder
|
4
|
+
class Error < BTCError; end
|
5
|
+
|
6
|
+
# Change address is not specified
|
7
|
+
class MissingChangeAddressError < Error; end
|
8
|
+
|
9
|
+
# Unspent outputs are missing. Maybe because input_addresses are not specified.
|
10
|
+
class MissingUnspentOutputsError < Error; end
|
11
|
+
|
12
|
+
# Unspent outputs are not sufficient to build the transaction.
|
13
|
+
class InsufficientFundsError < Error; end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module BTC
|
2
|
+
TransactionBuilder
|
3
|
+
class TransactionBuilder
|
4
|
+
|
5
|
+
# Interface for providing unspent outputs to transaction builder.
|
6
|
+
# You can use it instead of Enumerable attribute `unspent_outputs` to customize which unspents to be used.
|
7
|
+
module Provider
|
8
|
+
# Returns an Enumerable object yielding unspent outputs for the given addresses.
|
9
|
+
# Each unspent output is a BTC::TransactionOutput instance with non-nil `transaction_hash` and `index` attributes.
|
10
|
+
# Additional information about outputs and fees may be used to optimize the set of unspents.
|
11
|
+
# If builder's `outputs_amount` is nil, all available unspent outputs are expected to be returned.
|
12
|
+
def unspent_outputs(transaction_builder)
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Allows provider to track unspents that are actually used by the transaction builder,
|
17
|
+
# so it can avoid providing the same outputs to another transaction builder.
|
18
|
+
def consume_unspent_output(output)
|
19
|
+
#puts "========> BTCRUBY: Provider #{self.object_id} consumes output #{btc_txbuilder_outpoint_id(output)}."
|
20
|
+
@btc_txbuilder_provider_consumed_unspent_outputs ||= {}
|
21
|
+
@btc_txbuilder_provider_consumed_unspent_outputs[btc_txbuilder_outpoint_id(output)] = 1
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def consumed_unspent_output?(output)
|
26
|
+
#puts "========> BTCRUBY: Does Provider #{self.object_id} have output #{btc_txbuilder_outpoint_id(output)}?"
|
27
|
+
!!((@btc_txbuilder_provider_consumed_unspent_outputs ||= {})[btc_txbuilder_outpoint_id(output)])
|
28
|
+
end
|
29
|
+
|
30
|
+
def btc_txbuilder_outpoint_id(output)
|
31
|
+
raise ArgumentError, "Output must have txid" if !output.transaction_hash
|
32
|
+
raise ArgumentError, "Output must have txid" if !output.index
|
33
|
+
output.outpoint_id
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a block-based provider:
|
37
|
+
# txbuilder.provider = TransactionBuilder::Provider.new {|builder| [...] }
|
38
|
+
def self.new(&block)
|
39
|
+
BlockProvider.new(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
class BlockProvider # private
|
43
|
+
include Provider
|
44
|
+
def initialize(&block)
|
45
|
+
@block = block
|
46
|
+
end
|
47
|
+
def unspent_outputs(txbuilder)
|
48
|
+
@block.call(txbuilder)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module BTC
|
2
|
+
TransactionBuilder
|
3
|
+
class TransactionBuilder
|
4
|
+
|
5
|
+
# Result object containing transaction itself and various info about it.
|
6
|
+
# You get this object from `TransactionBuilder#build` method.
|
7
|
+
class Result
|
8
|
+
|
9
|
+
# BTC::Transaction instance. Each input is either signed (if WIF was used)
|
10
|
+
# or contains an unspent output's script as its signature_script.
|
11
|
+
# Unsigned inputs are marked using #unsigned_input_indexes attribute.
|
12
|
+
attr_reader :transaction
|
13
|
+
|
14
|
+
# List of input indexes that are not signed.
|
15
|
+
# Empty list means all indexes are signed.
|
16
|
+
attr_reader :unsigned_input_indexes
|
17
|
+
|
18
|
+
# Total fee for the composed transaction.
|
19
|
+
# Equals (inputs_amount - outputs_amount)
|
20
|
+
attr_reader :fee
|
21
|
+
|
22
|
+
# Total amount on the inputs.
|
23
|
+
attr_reader :inputs_amount
|
24
|
+
|
25
|
+
# Total amount on the outputs.
|
26
|
+
attr_reader :outputs_amount
|
27
|
+
|
28
|
+
# Amount in satoshis sent to a change address.
|
29
|
+
# change_amount = outputs_amount - sent_amount
|
30
|
+
attr_reader :change_amount
|
31
|
+
|
32
|
+
# Amount in satoshis sent to outputs specified by the user.
|
33
|
+
# sent_amount = outputs_amount - change_amount
|
34
|
+
attr_reader :sent_amount
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
self.transaction = Transaction.new
|
38
|
+
self.transaction.inputs_amount = 0
|
39
|
+
self.unsigned_input_indexes = []
|
40
|
+
self.fee = 0
|
41
|
+
self.outputs_amount = 0
|
42
|
+
self.change_amount = 0
|
43
|
+
self.inputs_amount = 0
|
44
|
+
end
|
45
|
+
|
46
|
+
def inputs_amount=(amount)
|
47
|
+
@inputs_amount = amount
|
48
|
+
@transaction.inputs_amount = amount
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Internal-only setters.
|
53
|
+
class Result
|
54
|
+
attr_accessor :transaction
|
55
|
+
attr_accessor :unsigned_input_indexes
|
56
|
+
attr_accessor :fee
|
57
|
+
attr_accessor :inputs_amount
|
58
|
+
attr_accessor :outputs_amount
|
59
|
+
attr_accessor :change_amount
|
60
|
+
def sent_amount
|
61
|
+
outputs_amount - change_amount
|
62
|
+
end
|
63
|
+
|
64
|
+
def unsigned_input_indices
|
65
|
+
unsigned_input_indexes
|
66
|
+
end
|
67
|
+
|
68
|
+
def unsigned_input_indices=(arr)
|
69
|
+
self.unsigned_input_indexes = arr
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|