tapyrus 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +100 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/tapyrusrb-cli +5 -0
- data/exe/tapyrusrbd +41 -0
- data/lib/openassets/marker_output.rb +20 -0
- data/lib/openassets/payload.rb +54 -0
- data/lib/openassets/util.rb +28 -0
- data/lib/openassets.rb +9 -0
- data/lib/tapyrus/base58.rb +38 -0
- data/lib/tapyrus/block.rb +77 -0
- data/lib/tapyrus/block_header.rb +88 -0
- data/lib/tapyrus/bloom_filter.rb +78 -0
- data/lib/tapyrus/chain_params.rb +90 -0
- data/lib/tapyrus/chainparams/mainnet.yml +41 -0
- data/lib/tapyrus/chainparams/regtest.yml +38 -0
- data/lib/tapyrus/chainparams/testnet.yml +41 -0
- data/lib/tapyrus/constants.rb +195 -0
- data/lib/tapyrus/descriptor.rb +147 -0
- data/lib/tapyrus/ext_key.rb +337 -0
- data/lib/tapyrus/key.rb +296 -0
- data/lib/tapyrus/key_path.rb +26 -0
- data/lib/tapyrus/logger.rb +42 -0
- data/lib/tapyrus/merkle_tree.rb +149 -0
- data/lib/tapyrus/message/addr.rb +35 -0
- data/lib/tapyrus/message/base.rb +28 -0
- data/lib/tapyrus/message/block.rb +46 -0
- data/lib/tapyrus/message/block_transaction_request.rb +45 -0
- data/lib/tapyrus/message/block_transactions.rb +31 -0
- data/lib/tapyrus/message/block_txn.rb +27 -0
- data/lib/tapyrus/message/cmpct_block.rb +42 -0
- data/lib/tapyrus/message/error.rb +10 -0
- data/lib/tapyrus/message/fee_filter.rb +27 -0
- data/lib/tapyrus/message/filter_add.rb +28 -0
- data/lib/tapyrus/message/filter_clear.rb +17 -0
- data/lib/tapyrus/message/filter_load.rb +39 -0
- data/lib/tapyrus/message/get_addr.rb +17 -0
- data/lib/tapyrus/message/get_block_txn.rb +27 -0
- data/lib/tapyrus/message/get_blocks.rb +29 -0
- data/lib/tapyrus/message/get_data.rb +21 -0
- data/lib/tapyrus/message/get_headers.rb +28 -0
- data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
- data/lib/tapyrus/message/headers.rb +35 -0
- data/lib/tapyrus/message/headers_parser.rb +24 -0
- data/lib/tapyrus/message/inv.rb +21 -0
- data/lib/tapyrus/message/inventories_parser.rb +23 -0
- data/lib/tapyrus/message/inventory.rb +51 -0
- data/lib/tapyrus/message/mem_pool.rb +17 -0
- data/lib/tapyrus/message/merkle_block.rb +42 -0
- data/lib/tapyrus/message/network_addr.rb +63 -0
- data/lib/tapyrus/message/not_found.rb +21 -0
- data/lib/tapyrus/message/ping.rb +30 -0
- data/lib/tapyrus/message/pong.rb +26 -0
- data/lib/tapyrus/message/prefilled_tx.rb +29 -0
- data/lib/tapyrus/message/reject.rb +46 -0
- data/lib/tapyrus/message/send_cmpct.rb +43 -0
- data/lib/tapyrus/message/send_headers.rb +16 -0
- data/lib/tapyrus/message/tx.rb +30 -0
- data/lib/tapyrus/message/ver_ack.rb +17 -0
- data/lib/tapyrus/message/version.rb +69 -0
- data/lib/tapyrus/message.rb +70 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
- data/lib/tapyrus/mnemonic.rb +77 -0
- data/lib/tapyrus/network/connection.rb +73 -0
- data/lib/tapyrus/network/message_handler.rb +241 -0
- data/lib/tapyrus/network/peer.rb +223 -0
- data/lib/tapyrus/network/peer_discovery.rb +42 -0
- data/lib/tapyrus/network/pool.rb +135 -0
- data/lib/tapyrus/network.rb +13 -0
- data/lib/tapyrus/node/cli.rb +112 -0
- data/lib/tapyrus/node/configuration.rb +38 -0
- data/lib/tapyrus/node/spv.rb +79 -0
- data/lib/tapyrus/node.rb +7 -0
- data/lib/tapyrus/opcodes.rb +178 -0
- data/lib/tapyrus/out_point.rb +44 -0
- data/lib/tapyrus/rpc/http_server.rb +65 -0
- data/lib/tapyrus/rpc/request_handler.rb +150 -0
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
- data/lib/tapyrus/rpc.rb +7 -0
- data/lib/tapyrus/script/multisig.rb +92 -0
- data/lib/tapyrus/script/script.rb +551 -0
- data/lib/tapyrus/script/script_error.rb +111 -0
- data/lib/tapyrus/script/script_interpreter.rb +668 -0
- data/lib/tapyrus/script/tx_checker.rb +81 -0
- data/lib/tapyrus/script_witness.rb +38 -0
- data/lib/tapyrus/secp256k1/native.rb +174 -0
- data/lib/tapyrus/secp256k1/ruby.rb +123 -0
- data/lib/tapyrus/secp256k1.rb +12 -0
- data/lib/tapyrus/slip39/share.rb +122 -0
- data/lib/tapyrus/slip39/sss.rb +245 -0
- data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
- data/lib/tapyrus/slip39.rb +93 -0
- data/lib/tapyrus/store/chain_entry.rb +67 -0
- data/lib/tapyrus/store/db/level_db.rb +98 -0
- data/lib/tapyrus/store/db.rb +9 -0
- data/lib/tapyrus/store/spv_chain.rb +101 -0
- data/lib/tapyrus/store.rb +9 -0
- data/lib/tapyrus/tx.rb +347 -0
- data/lib/tapyrus/tx_in.rb +89 -0
- data/lib/tapyrus/tx_out.rb +74 -0
- data/lib/tapyrus/util.rb +133 -0
- data/lib/tapyrus/validation.rb +115 -0
- data/lib/tapyrus/version.rb +3 -0
- data/lib/tapyrus/wallet/account.rb +151 -0
- data/lib/tapyrus/wallet/base.rb +162 -0
- data/lib/tapyrus/wallet/db.rb +81 -0
- data/lib/tapyrus/wallet/master_key.rb +110 -0
- data/lib/tapyrus/wallet.rb +8 -0
- data/lib/tapyrus.rb +219 -0
- data/tapyrusrb.conf.sample +0 -0
- data/tapyrusrb.gemspec +47 -0
- metadata +451 -0
data/lib/tapyrus/tx.rb
ADDED
@@ -0,0 +1,347 @@
|
|
1
|
+
# Porting part of the code from bitcoin-ruby. see the license.
|
2
|
+
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
3
|
+
|
4
|
+
module Tapyrus
|
5
|
+
|
6
|
+
# Transaction class
|
7
|
+
class Tx
|
8
|
+
|
9
|
+
MAX_STANDARD_VERSION = 2
|
10
|
+
|
11
|
+
# The maximum weight for transactions we're willing to relay/mine
|
12
|
+
MAX_STANDARD_TX_WEIGHT = 400000
|
13
|
+
|
14
|
+
MARKER = 0x00
|
15
|
+
FLAG = 0x01
|
16
|
+
|
17
|
+
attr_accessor :version
|
18
|
+
attr_accessor :marker
|
19
|
+
attr_accessor :flag
|
20
|
+
attr_reader :inputs
|
21
|
+
attr_reader :outputs
|
22
|
+
attr_accessor :lock_time
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@inputs = []
|
26
|
+
@outputs = []
|
27
|
+
@version = 1
|
28
|
+
@lock_time = 0
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :in, :inputs
|
32
|
+
alias_method :out, :outputs
|
33
|
+
|
34
|
+
def self.parse_from_payload(payload, non_witness: false)
|
35
|
+
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
36
|
+
tx = new
|
37
|
+
tx.version = buf.read(4).unpack('V').first
|
38
|
+
|
39
|
+
in_count = Tapyrus.unpack_var_int_from_io(buf)
|
40
|
+
witness = false
|
41
|
+
if in_count.zero? && !non_witness
|
42
|
+
tx.marker = 0
|
43
|
+
tx.flag = buf.read(1).unpack('c').first
|
44
|
+
if tx.flag.zero?
|
45
|
+
buf.pos -= 1
|
46
|
+
else
|
47
|
+
in_count = Tapyrus.unpack_var_int_from_io(buf)
|
48
|
+
witness = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
in_count.times do
|
53
|
+
tx.inputs << TxIn.parse_from_payload(buf)
|
54
|
+
end
|
55
|
+
|
56
|
+
out_count = Tapyrus.unpack_var_int_from_io(buf)
|
57
|
+
out_count.times do
|
58
|
+
tx.outputs << TxOut.parse_from_payload(buf)
|
59
|
+
end
|
60
|
+
|
61
|
+
if witness
|
62
|
+
in_count.times do |i|
|
63
|
+
tx.inputs[i].script_witness = Tapyrus::ScriptWitness.parse_from_payload(buf)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
tx.lock_time = buf.read(4).unpack('V').first
|
68
|
+
|
69
|
+
tx
|
70
|
+
end
|
71
|
+
|
72
|
+
def hash
|
73
|
+
to_payload.bth.to_i(16)
|
74
|
+
end
|
75
|
+
|
76
|
+
def tx_hash
|
77
|
+
Tapyrus.double_sha256(serialize_old_format).bth
|
78
|
+
end
|
79
|
+
|
80
|
+
def txid
|
81
|
+
buf = [version].pack('V')
|
82
|
+
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map{|i|i.to_payload(use_malfix: true)}.join
|
83
|
+
buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
|
84
|
+
buf << [lock_time].pack('V')
|
85
|
+
Tapyrus.double_sha256(buf).reverse.bth
|
86
|
+
end
|
87
|
+
|
88
|
+
def witness_hash
|
89
|
+
Tapyrus.double_sha256(to_payload).bth
|
90
|
+
end
|
91
|
+
|
92
|
+
def wtxid
|
93
|
+
witness_hash.rhex
|
94
|
+
end
|
95
|
+
|
96
|
+
# get the witness commitment of coinbase tx.
|
97
|
+
# if this tx does not coinbase or not have commitment, return nil.
|
98
|
+
def witness_commitment
|
99
|
+
return nil unless coinbase_tx?
|
100
|
+
outputs.each do |output|
|
101
|
+
commitment = output.script_pubkey.witness_commitment
|
102
|
+
return commitment if commitment
|
103
|
+
end
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def to_payload
|
108
|
+
witness? ? serialize_witness_format : serialize_old_format
|
109
|
+
end
|
110
|
+
|
111
|
+
# convert tx to hex format.
|
112
|
+
# @return [String] tx with hex format.
|
113
|
+
def to_hex
|
114
|
+
to_payload.bth
|
115
|
+
end
|
116
|
+
|
117
|
+
def coinbase_tx?
|
118
|
+
inputs.length == 1 && inputs.first.coinbase?
|
119
|
+
end
|
120
|
+
|
121
|
+
def witness?
|
122
|
+
!inputs.find { |i| !i.script_witness.empty? }.nil?
|
123
|
+
end
|
124
|
+
|
125
|
+
def ==(other)
|
126
|
+
to_payload == other.to_payload
|
127
|
+
end
|
128
|
+
|
129
|
+
# serialize tx with old tx format
|
130
|
+
def serialize_old_format
|
131
|
+
buf = [version].pack('V')
|
132
|
+
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
|
133
|
+
buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
|
134
|
+
buf << [lock_time].pack('V')
|
135
|
+
buf
|
136
|
+
end
|
137
|
+
|
138
|
+
# serialize tx with segwit tx format
|
139
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0144.mediawiki
|
140
|
+
def serialize_witness_format
|
141
|
+
buf = [version, MARKER, FLAG].pack('Vcc')
|
142
|
+
buf << Tapyrus.pack_var_int(inputs.length) << inputs.map(&:to_payload).join
|
143
|
+
buf << Tapyrus.pack_var_int(outputs.length) << outputs.map(&:to_payload).join
|
144
|
+
buf << witness_payload << [lock_time].pack('V')
|
145
|
+
buf
|
146
|
+
end
|
147
|
+
|
148
|
+
def witness_payload
|
149
|
+
inputs.map { |i| i.script_witness.to_payload }.join
|
150
|
+
end
|
151
|
+
|
152
|
+
# check this tx is standard.
|
153
|
+
def standard?
|
154
|
+
return false if version > MAX_STANDARD_VERSION
|
155
|
+
return false if weight > MAX_STANDARD_TX_WEIGHT
|
156
|
+
inputs.each do |i|
|
157
|
+
# Biggest 'standard' txin is a 15-of-15 P2SH multisig with compressed keys (remember the 520 byte limit on redeemScript size).
|
158
|
+
# That works out to a (15*(33+1))+3=513 byte redeemScript, 513+1+15*(73+1)+3=1627
|
159
|
+
# bytes of scriptSig, which we round off to 1650 bytes for some minor future-proofing.
|
160
|
+
# That's also enough to spend a 20-of-20 CHECKMULTISIG scriptPubKey, though such a scriptPubKey is not considered standard.
|
161
|
+
return false if i.script_sig.size > 1650
|
162
|
+
return false unless i.script_sig.push_only?
|
163
|
+
end
|
164
|
+
data_count = 0
|
165
|
+
outputs.each do |o|
|
166
|
+
return false unless o.script_pubkey.standard?
|
167
|
+
data_count += 1 if o.script_pubkey.op_return?
|
168
|
+
# TODO add non P2SH multisig relay(permitbaremultisig)
|
169
|
+
return false if o.dust?
|
170
|
+
end
|
171
|
+
return false if data_count > 1
|
172
|
+
true
|
173
|
+
end
|
174
|
+
|
175
|
+
# The serialized transaction size
|
176
|
+
def size
|
177
|
+
to_payload.bytesize
|
178
|
+
end
|
179
|
+
|
180
|
+
# The virtual transaction size (differs from size for witness transactions)
|
181
|
+
def vsize
|
182
|
+
(weight.to_f / 4).ceil
|
183
|
+
end
|
184
|
+
|
185
|
+
# calculate tx weight
|
186
|
+
# weight = (legacy tx payload) * 3 + (witness tx payload)
|
187
|
+
def weight
|
188
|
+
if witness?
|
189
|
+
serialize_old_format.bytesize * (WITNESS_SCALE_FACTOR - 1) + serialize_witness_format.bytesize
|
190
|
+
else
|
191
|
+
serialize_old_format.bytesize * WITNESS_SCALE_FACTOR
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# get signature hash
|
196
|
+
# @param [Integer] input_index input index.
|
197
|
+
# @param [Integer] hash_type signature hash type
|
198
|
+
# @param [Tapyrus::Script] output_script script pubkey or script code. if script pubkey is P2WSH, set witness script to this.
|
199
|
+
# @param [Integer] amount tapyrus amount locked in input. required for witness input only.
|
200
|
+
# @param [Integer] skip_separator_index If output_script is P2WSH and output_script contains any OP_CODESEPARATOR,
|
201
|
+
# the script code needs is the witnessScript but removing everything up to and including the last executed OP_CODESEPARATOR before the signature checking opcode being executed.
|
202
|
+
def sighash_for_input(input_index, output_script, hash_type: SIGHASH_TYPE[:all],
|
203
|
+
sig_version: :base, amount: nil, skip_separator_index: 0)
|
204
|
+
raise ArgumentError, 'input_index must be specified.' unless input_index
|
205
|
+
raise ArgumentError, 'does not exist input corresponding to input_index.' if input_index >= inputs.size
|
206
|
+
raise ArgumentError, 'script_pubkey must be specified.' unless output_script
|
207
|
+
raise ArgumentError, 'unsupported sig version specified.' unless SIG_VERSION.include?(sig_version)
|
208
|
+
|
209
|
+
if sig_version == :witness_v0 || Tapyrus.chain_params.fork_chain?
|
210
|
+
raise ArgumentError, 'amount must be specified.' unless amount
|
211
|
+
sighash_for_witness(input_index, output_script, hash_type, amount, skip_separator_index)
|
212
|
+
else
|
213
|
+
sighash_for_legacy(input_index, output_script, hash_type)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# verify input signature.
|
218
|
+
# @param [Integer] input_index
|
219
|
+
# @param [Tapyrus::Script] script_pubkey the script pubkey for target input.
|
220
|
+
# @param [Integer] amount the amount of tapyrus, require for witness program only.
|
221
|
+
# @param [Array] flags the flags used when execute script interpreter.
|
222
|
+
def verify_input_sig(input_index, script_pubkey, amount: nil, flags: STANDARD_SCRIPT_VERIFY_FLAGS)
|
223
|
+
script_sig = inputs[input_index].script_sig
|
224
|
+
has_witness = inputs[input_index].has_witness?
|
225
|
+
|
226
|
+
if script_pubkey.p2sh?
|
227
|
+
flags << SCRIPT_VERIFY_P2SH
|
228
|
+
redeem_script = Script.parse_from_payload(script_sig.chunks.last)
|
229
|
+
script_pubkey = redeem_script if redeem_script.p2wpkh?
|
230
|
+
end
|
231
|
+
|
232
|
+
if has_witness || Tapyrus.chain_params.fork_chain?
|
233
|
+
verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
|
234
|
+
else
|
235
|
+
verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def to_h
|
240
|
+
{
|
241
|
+
txid: txid, hash: witness_hash.rhex, version: version, size: size, vsize: vsize, locktime: lock_time,
|
242
|
+
vin: inputs.map(&:to_h), vout: outputs.map.with_index{|tx_out, index| tx_out.to_h.merge({n: index})}
|
243
|
+
}
|
244
|
+
end
|
245
|
+
|
246
|
+
# Verify transaction validity.
|
247
|
+
# @return [Boolean] whether this tx is valid or not.
|
248
|
+
def valid?
|
249
|
+
state = Tapyrus::ValidationState.new
|
250
|
+
validation = Tapyrus::Validation.new
|
251
|
+
validation.check_tx(self, state) && state.valid?
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
# generate sighash with legacy format
|
257
|
+
def sighash_for_legacy(index, script_code, hash_type)
|
258
|
+
ins = inputs.map.with_index do |i, idx|
|
259
|
+
if idx == index
|
260
|
+
i.to_payload(script_code.delete_opcode(Tapyrus::Opcodes::OP_CODESEPARATOR))
|
261
|
+
else
|
262
|
+
case hash_type & 0x1f
|
263
|
+
when SIGHASH_TYPE[:none], SIGHASH_TYPE[:single]
|
264
|
+
i.to_payload(Tapyrus::Script.new, 0)
|
265
|
+
else
|
266
|
+
i.to_payload(Tapyrus::Script.new)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
outs = outputs.map(&:to_payload)
|
272
|
+
out_size = Tapyrus.pack_var_int(outputs.size)
|
273
|
+
|
274
|
+
case hash_type & 0x1f
|
275
|
+
when SIGHASH_TYPE[:none]
|
276
|
+
outs = ''
|
277
|
+
out_size = Tapyrus.pack_var_int(0)
|
278
|
+
when SIGHASH_TYPE[:single]
|
279
|
+
return "\x01".ljust(32, "\x00") if index >= outputs.size
|
280
|
+
outs = outputs[0...(index + 1)].map.with_index { |o, idx| (idx == index) ? o.to_payload : o.to_empty_payload }.join
|
281
|
+
out_size = Tapyrus.pack_var_int(index + 1)
|
282
|
+
end
|
283
|
+
|
284
|
+
if hash_type & SIGHASH_TYPE[:anyonecanpay] != 0
|
285
|
+
ins = [ins[index]]
|
286
|
+
end
|
287
|
+
|
288
|
+
buf = [[version].pack('V'), Tapyrus.pack_var_int(ins.size),
|
289
|
+
ins, out_size, outs, [lock_time, hash_type].pack('VV')].join
|
290
|
+
|
291
|
+
Tapyrus.double_sha256(buf)
|
292
|
+
end
|
293
|
+
|
294
|
+
# generate sighash with BIP-143 format
|
295
|
+
# https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki
|
296
|
+
def sighash_for_witness(index, script_pubkey_or_script_code, hash_type, amount, skip_separator_index)
|
297
|
+
hash_prevouts = Tapyrus.double_sha256(inputs.map{|i|i.out_point.to_payload}.join)
|
298
|
+
hash_sequence = Tapyrus.double_sha256(inputs.map{|i|[i.sequence].pack('V')}.join)
|
299
|
+
outpoint = inputs[index].out_point.to_payload
|
300
|
+
amount = [amount].pack('Q')
|
301
|
+
nsequence = [inputs[index].sequence].pack('V')
|
302
|
+
hash_outputs = Tapyrus.double_sha256(outputs.map{|o|o.to_payload}.join)
|
303
|
+
|
304
|
+
script_code = script_pubkey_or_script_code.to_script_code(skip_separator_index)
|
305
|
+
|
306
|
+
case (hash_type & 0x1f)
|
307
|
+
when SIGHASH_TYPE[:single]
|
308
|
+
hash_outputs = index >= outputs.size ? "\x00".ljust(32, "\x00") : Tapyrus.double_sha256(outputs[index].to_payload)
|
309
|
+
hash_sequence = "\x00".ljust(32, "\x00")
|
310
|
+
when SIGHASH_TYPE[:none]
|
311
|
+
hash_sequence = hash_outputs = "\x00".ljust(32, "\x00")
|
312
|
+
end
|
313
|
+
|
314
|
+
if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
|
315
|
+
hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
|
316
|
+
end
|
317
|
+
hash_type |= (Tapyrus.chain_params.fork_id << 8) if Tapyrus.chain_params.fork_chain?
|
318
|
+
buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint,
|
319
|
+
script_code ,amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
|
320
|
+
Tapyrus.double_sha256(buf)
|
321
|
+
end
|
322
|
+
|
323
|
+
# verify input signature for legacy tx.
|
324
|
+
def verify_input_sig_for_legacy(input_index, script_pubkey, flags)
|
325
|
+
script_sig = inputs[input_index].script_sig
|
326
|
+
checker = Tapyrus::TxChecker.new(tx: self, input_index: input_index)
|
327
|
+
interpreter = Tapyrus::ScriptInterpreter.new(checker: checker, flags: flags)
|
328
|
+
|
329
|
+
interpreter.verify_script(script_sig, script_pubkey)
|
330
|
+
end
|
331
|
+
|
332
|
+
# verify input signature for witness tx.
|
333
|
+
def verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
|
334
|
+
flags |= SCRIPT_VERIFY_WITNESS
|
335
|
+
flags |= SCRIPT_VERIFY_WITNESS_PUBKEYTYPE
|
336
|
+
checker = Tapyrus::TxChecker.new(tx: self, input_index: input_index, amount: amount)
|
337
|
+
interpreter = Tapyrus::ScriptInterpreter.new(checker: checker, flags: flags)
|
338
|
+
i = inputs[input_index]
|
339
|
+
|
340
|
+
script_sig = i.script_sig
|
341
|
+
witness = i.script_witness
|
342
|
+
interpreter.verify_script(script_sig, script_pubkey, witness)
|
343
|
+
end
|
344
|
+
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Porting part of the code from bitcoin-ruby. see the license.
|
2
|
+
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
3
|
+
|
4
|
+
module Tapyrus
|
5
|
+
|
6
|
+
# transaction input
|
7
|
+
class TxIn
|
8
|
+
|
9
|
+
attr_accessor :out_point
|
10
|
+
attr_accessor :script_sig
|
11
|
+
attr_accessor :sequence
|
12
|
+
attr_accessor :script_witness
|
13
|
+
|
14
|
+
# Setting nSequence to this value for every input in a transaction disables nLockTime.
|
15
|
+
SEQUENCE_FINAL = 0xffffffff
|
16
|
+
|
17
|
+
# If this flag set, TxIn#sequence is NOT interpreted as a relative lock-time.
|
18
|
+
SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31)
|
19
|
+
|
20
|
+
# If TxIn#sequence encodes a relative lock-time and this flag is set, the relative lock-time has units of 512 seconds,
|
21
|
+
# otherwise it specifies blocks with a granularity of 1.
|
22
|
+
SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22)
|
23
|
+
|
24
|
+
# If TxIn#sequence encodes a relative lock-time, this mask is applied to extract that lock-time from the sequence field.
|
25
|
+
SEQUENCE_LOCKTIME_MASK = 0x0000ffff
|
26
|
+
|
27
|
+
def initialize(out_point: nil, script_sig: Tapyrus::Script.new, script_witness: ScriptWitness.new, sequence: SEQUENCE_FINAL)
|
28
|
+
@out_point = out_point
|
29
|
+
@script_sig = script_sig
|
30
|
+
@script_witness = script_witness
|
31
|
+
@sequence = sequence
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.parse_from_payload(payload)
|
35
|
+
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
36
|
+
i = new
|
37
|
+
hash, index = buf.read(36).unpack('a32V')
|
38
|
+
i.out_point = OutPoint.new(hash.bth, index)
|
39
|
+
sig_length = Tapyrus.unpack_var_int_from_io(buf)
|
40
|
+
sig = buf.read(sig_length)
|
41
|
+
if i.coinbase?
|
42
|
+
i.script_sig.chunks[0] = sig
|
43
|
+
else
|
44
|
+
i.script_sig = Script.parse_from_payload(sig)
|
45
|
+
end
|
46
|
+
i.sequence = buf.read(4).unpack('V').first
|
47
|
+
i
|
48
|
+
end
|
49
|
+
|
50
|
+
def coinbase?
|
51
|
+
out_point.coinbase?
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_payload(script_sig = @script_sig, sequence = @sequence, use_malfix: false)
|
55
|
+
p = out_point.to_payload
|
56
|
+
unless use_malfix
|
57
|
+
p << Tapyrus.pack_var_int(script_sig.to_payload.bytesize)
|
58
|
+
p << script_sig.to_payload
|
59
|
+
end
|
60
|
+
p << [sequence].pack('V')
|
61
|
+
p
|
62
|
+
end
|
63
|
+
|
64
|
+
def has_witness?
|
65
|
+
!script_witness.empty?
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_h
|
69
|
+
sig = script_sig.to_h
|
70
|
+
sig.delete(:type)
|
71
|
+
h = {txid: out_point.txid, vout: out_point.index, script_sig: sig }
|
72
|
+
h[:txinwitness] = script_witness.stack.map(&:bth) if has_witness?
|
73
|
+
h[:sequence] = sequence
|
74
|
+
h
|
75
|
+
end
|
76
|
+
|
77
|
+
def ==(other)
|
78
|
+
to_payload == other.to_payload
|
79
|
+
end
|
80
|
+
|
81
|
+
# return previous output hash (not txid)
|
82
|
+
def prev_hash
|
83
|
+
return nil unless out_point
|
84
|
+
out_point.tx_hash
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Porting part of the code from bitcoin-ruby. see the license.
|
2
|
+
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
3
|
+
|
4
|
+
module Tapyrus
|
5
|
+
|
6
|
+
# transaction output
|
7
|
+
class TxOut
|
8
|
+
|
9
|
+
include OpenAssets::MarkerOutput
|
10
|
+
|
11
|
+
attr_accessor :value
|
12
|
+
attr_accessor :script_pubkey
|
13
|
+
|
14
|
+
def initialize(value: 0, script_pubkey: nil)
|
15
|
+
@value = value
|
16
|
+
@script_pubkey = script_pubkey
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.parse_from_payload(payload)
|
20
|
+
buf = payload.is_a?(String) ? StringIO.new(payload) : payload
|
21
|
+
value = buf.read(8).unpack('q').first
|
22
|
+
script_size = Tapyrus.unpack_var_int_from_io(buf)
|
23
|
+
new(value: value, script_pubkey: Script.parse_from_payload(buf.read(script_size)))
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_payload
|
27
|
+
s = script_pubkey.to_payload
|
28
|
+
[value].pack('Q') << Tapyrus.pack_var_int(s.length) << s
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_empty_payload
|
32
|
+
'ffffffffffffffff00'.htb
|
33
|
+
end
|
34
|
+
|
35
|
+
# convert satoshi to btc
|
36
|
+
def value_to_btc
|
37
|
+
value / 100000000.0
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_h
|
41
|
+
{value: value_to_btc, script_pubkey: script_pubkey.to_h}
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
to_payload == other.to_payload
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns this output bytesize
|
49
|
+
# @return [Integer] bytesize
|
50
|
+
def size
|
51
|
+
to_payload.bytesize
|
52
|
+
end
|
53
|
+
|
54
|
+
# Whether this output is dust or not
|
55
|
+
# @return [Boolean]
|
56
|
+
def dust?
|
57
|
+
value < dust_threshold
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def dust_threshold
|
63
|
+
return 0 if script_pubkey.unspendable?
|
64
|
+
n_size = size
|
65
|
+
n_size += script_pubkey.witness_program? ? (32 + 4 + 1 + (107 / Tapyrus::WITNESS_SCALE_FACTOR) + 4) : (32 + 4 + 1 + 107 + 4)
|
66
|
+
fee = n_size * Tapyrus.chain_params.dust_relay_fee / 1000
|
67
|
+
if fee == 0 && n_size != 0
|
68
|
+
fee = Tapyrus.chain_params.dust_relay_fee > 0 ? 1 : -1
|
69
|
+
end
|
70
|
+
fee
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/lib/tapyrus/util.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# Porting part of the code from bitcoin-ruby. see the license.
|
2
|
+
# https://github.com/lian/bitcoin-ruby/blob/master/COPYING
|
3
|
+
|
4
|
+
module Tapyrus
|
5
|
+
|
6
|
+
# tapyrus utility.
|
7
|
+
# following methods can be used as follows.
|
8
|
+
# Tapyrus.pack_var_int(5)
|
9
|
+
module Util
|
10
|
+
|
11
|
+
def pack_var_string(payload)
|
12
|
+
pack_var_int(payload.bytesize) + payload
|
13
|
+
end
|
14
|
+
|
15
|
+
def unpack_var_string(payload)
|
16
|
+
size, payload = unpack_var_int(payload)
|
17
|
+
size > 0 ? payload.unpack("a#{size}a*") : [nil, payload]
|
18
|
+
end
|
19
|
+
|
20
|
+
def pack_var_int(i)
|
21
|
+
if i < 0xfd
|
22
|
+
[i].pack('C')
|
23
|
+
elsif i <= 0xffff
|
24
|
+
[0xfd, i].pack('Cv')
|
25
|
+
elsif i <= 0xffffffff
|
26
|
+
[0xfe, i].pack('CV')
|
27
|
+
elsif i <= 0xffffffffffffffff
|
28
|
+
[0xff, i].pack('CQ')
|
29
|
+
else
|
30
|
+
raise "int(#{i}) too large!"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return an integer for a valid payload, otherwise nil
|
35
|
+
def unpack_var_int(payload)
|
36
|
+
case payload.unpack('C').first
|
37
|
+
when 0xfd
|
38
|
+
payload.unpack('xva*')
|
39
|
+
when 0xfe
|
40
|
+
payload.unpack('xVa*')
|
41
|
+
when 0xff
|
42
|
+
payload.unpack('xQa*')
|
43
|
+
else
|
44
|
+
payload.unpack('Ca*')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return an integer for a valid payload, otherwise nil
|
49
|
+
def unpack_var_int_from_io(buf)
|
50
|
+
uchar = buf.read(1)&.unpack('C')&.first
|
51
|
+
case uchar
|
52
|
+
when 0xfd
|
53
|
+
buf.read(2)&.unpack('v')&.first
|
54
|
+
when 0xfe
|
55
|
+
buf.read(4)&.unpack('V')&.first
|
56
|
+
when 0xff
|
57
|
+
buf.read(8)&.unpack('Q')&.first
|
58
|
+
else
|
59
|
+
uchar
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def pack_boolean(b)
|
64
|
+
b ? [0x01].pack('C') : [0x00].pack('C')
|
65
|
+
end
|
66
|
+
|
67
|
+
def unpack_boolean(payload)
|
68
|
+
data, payload = payload.unpack('Ca*')
|
69
|
+
[(data.zero? ? false : true), payload]
|
70
|
+
end
|
71
|
+
|
72
|
+
def sha256(payload)
|
73
|
+
Digest::SHA256.digest(payload)
|
74
|
+
end
|
75
|
+
|
76
|
+
def double_sha256(payload)
|
77
|
+
sha256(sha256(payload))
|
78
|
+
end
|
79
|
+
|
80
|
+
# byte convert to the sequence of bits packed eight in a byte with the least significant bit first.
|
81
|
+
def byte_to_bit(byte)
|
82
|
+
byte.unpack('b*').first
|
83
|
+
end
|
84
|
+
|
85
|
+
# padding zero to the left of binary string until bytesize.
|
86
|
+
# @param [String] binary string
|
87
|
+
# @param [Integer] bytesize total bytesize.
|
88
|
+
# @return [String] padded binary string.
|
89
|
+
def padding_zero(binary, bytesize)
|
90
|
+
return binary unless binary.bytesize < bytesize
|
91
|
+
('00' * (bytesize - binary.bytesize)).htb + binary
|
92
|
+
end
|
93
|
+
|
94
|
+
# generate sha256-ripemd160 hash for value
|
95
|
+
def hash160(hex)
|
96
|
+
Digest::RMD160.hexdigest(Digest::SHA256.digest(hex.htb))
|
97
|
+
end
|
98
|
+
|
99
|
+
# encode Base58 check address.
|
100
|
+
# @param [String] hex the address payload.
|
101
|
+
# @param [String] addr_version the address version for P2PKH and P2SH.
|
102
|
+
# @return [String] Base58 check encoding address.
|
103
|
+
def encode_base58_address(hex, addr_version)
|
104
|
+
base = addr_version + hex
|
105
|
+
Base58.encode(base + calc_checksum(base))
|
106
|
+
end
|
107
|
+
|
108
|
+
# decode Base58 check encoding address.
|
109
|
+
# @param [String] addr address.
|
110
|
+
# @return [Array] hex and address version
|
111
|
+
def decode_base58_address(addr)
|
112
|
+
hex = Base58.decode(addr)
|
113
|
+
if hex.size == 50 && calc_checksum(hex[0...-8]) == hex[-8..-1]
|
114
|
+
raise 'Invalid version bytes.' unless [Tapyrus.chain_params.address_version, Tapyrus.chain_params.p2sh_version].include?(hex[0..1])
|
115
|
+
[hex[2...-8], hex[0..1]]
|
116
|
+
else
|
117
|
+
raise 'Invalid address.'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def calc_checksum(hex)
|
122
|
+
double_sha256(hex.htb).bth[0..7]
|
123
|
+
end
|
124
|
+
|
125
|
+
DIGEST_NAME_SHA256 = 'sha256'
|
126
|
+
|
127
|
+
def hmac_sha256(key, data)
|
128
|
+
OpenSSL::HMAC.digest(DIGEST_NAME_SHA256, key, data)
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|