tapyrus 0.1.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.
- 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
|