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
@@ -0,0 +1,551 @@
|
|
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 script
|
7
|
+
class Script
|
8
|
+
include Tapyrus::Opcodes
|
9
|
+
|
10
|
+
attr_accessor :chunks
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@chunks = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# generate P2PKH script
|
17
|
+
def self.to_p2pkh(pubkey_hash)
|
18
|
+
new << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
|
19
|
+
end
|
20
|
+
|
21
|
+
# generate P2WPKH script
|
22
|
+
def self.to_p2wpkh(pubkey_hash)
|
23
|
+
new << WITNESS_VERSION << pubkey_hash
|
24
|
+
end
|
25
|
+
|
26
|
+
# generate m of n multisig p2sh script
|
27
|
+
# @param [String] m the number of signatures required for multisig
|
28
|
+
# @param [Array] pubkeys array of public keys that compose multisig
|
29
|
+
# @return [Script, Script] first element is p2sh script, second one is redeem script.
|
30
|
+
def self.to_p2sh_multisig_script(m, pubkeys)
|
31
|
+
redeem_script = to_multisig_script(m, pubkeys)
|
32
|
+
[redeem_script.to_p2sh, redeem_script]
|
33
|
+
end
|
34
|
+
|
35
|
+
# generate p2sh script.
|
36
|
+
# @param [String] script_hash script hash for P2SH
|
37
|
+
# @return [Script] P2SH script
|
38
|
+
def self.to_p2sh(script_hash)
|
39
|
+
Script.new << OP_HASH160 << script_hash << OP_EQUAL
|
40
|
+
end
|
41
|
+
|
42
|
+
# generate p2sh script with this as a redeem script
|
43
|
+
# @return [Script] P2SH script
|
44
|
+
def to_p2sh
|
45
|
+
Script.to_p2sh(to_hash160)
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_multisig_pubkeys
|
49
|
+
num = Tapyrus::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
|
50
|
+
(1..num).map{ |i| chunks[i].pushed_data }
|
51
|
+
end
|
52
|
+
|
53
|
+
# generate m of n multisig script
|
54
|
+
# @param [String] m the number of signatures required for multisig
|
55
|
+
# @param [Array] pubkeys array of public keys that compose multisig
|
56
|
+
# @return [Script] multisig script.
|
57
|
+
def self.to_multisig_script(m, pubkeys, sort: false)
|
58
|
+
pubkeys = pubkeys.sort if sort
|
59
|
+
new << m << pubkeys << pubkeys.size << OP_CHECKMULTISIG
|
60
|
+
end
|
61
|
+
|
62
|
+
# generate p2wsh script for +redeem_script+
|
63
|
+
# @param [Script] redeem_script target redeem script
|
64
|
+
# @param [Script] p2wsh script
|
65
|
+
def self.to_p2wsh(redeem_script)
|
66
|
+
new << WITNESS_VERSION << redeem_script.to_sha256
|
67
|
+
end
|
68
|
+
|
69
|
+
# generate script from string.
|
70
|
+
def self.from_string(string)
|
71
|
+
script = new
|
72
|
+
string.split(' ').each do |v|
|
73
|
+
opcode = Opcodes.name_to_opcode(v)
|
74
|
+
if opcode
|
75
|
+
script << (v =~ /^\d/ && Opcodes.small_int_to_opcode(v.ord) ? v.ord : opcode)
|
76
|
+
else
|
77
|
+
script << (v =~ /^[0-9]+$/ ? v.to_i : v)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
script
|
81
|
+
end
|
82
|
+
|
83
|
+
# generate script from addr.
|
84
|
+
# @param [String] addr address.
|
85
|
+
# @return [Tapyrus::Script] parsed script.
|
86
|
+
def self.parse_from_addr(addr)
|
87
|
+
begin
|
88
|
+
segwit_addr = Bech32::SegwitAddr.new(addr)
|
89
|
+
raise 'Invalid hrp.' unless Tapyrus.chain_params.bech32_hrp == segwit_addr.hrp
|
90
|
+
Tapyrus::Script.parse_from_payload(segwit_addr.to_script_pubkey.htb)
|
91
|
+
rescue Exception => e
|
92
|
+
hex, addr_version = Tapyrus.decode_base58_address(addr)
|
93
|
+
case addr_version
|
94
|
+
when Tapyrus.chain_params.address_version
|
95
|
+
Tapyrus::Script.to_p2pkh(hex)
|
96
|
+
when Tapyrus.chain_params.p2sh_version
|
97
|
+
Tapyrus::Script.to_p2sh(hex)
|
98
|
+
else
|
99
|
+
throw e
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.parse_from_payload(payload)
|
105
|
+
s = new
|
106
|
+
buf = StringIO.new(payload)
|
107
|
+
until buf.eof?
|
108
|
+
opcode = buf.read(1)
|
109
|
+
if opcode.pushdata?
|
110
|
+
pushcode = opcode.ord
|
111
|
+
packed_size = nil
|
112
|
+
len = case pushcode
|
113
|
+
when OP_PUSHDATA1
|
114
|
+
packed_size = buf.read(1)
|
115
|
+
packed_size.unpack('C').first
|
116
|
+
when OP_PUSHDATA2
|
117
|
+
packed_size = buf.read(2)
|
118
|
+
packed_size.unpack('v').first
|
119
|
+
when OP_PUSHDATA4
|
120
|
+
packed_size = buf.read(4)
|
121
|
+
packed_size.unpack('V').first
|
122
|
+
else
|
123
|
+
pushcode if pushcode < OP_PUSHDATA1
|
124
|
+
end
|
125
|
+
if len
|
126
|
+
s.chunks << [len].pack('C') if buf.eof?
|
127
|
+
unless buf.eof?
|
128
|
+
chunk = (packed_size ? (opcode + packed_size) : (opcode)) + buf.read(len)
|
129
|
+
s.chunks << chunk
|
130
|
+
end
|
131
|
+
end
|
132
|
+
else
|
133
|
+
if Opcodes.defined?(opcode.ord)
|
134
|
+
s << opcode.ord
|
135
|
+
else
|
136
|
+
s.chunks << (opcode + buf.read) # If opcode is invalid, put all remaining data in last chunk.
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
s
|
141
|
+
end
|
142
|
+
|
143
|
+
def to_payload
|
144
|
+
chunks.join
|
145
|
+
end
|
146
|
+
|
147
|
+
def empty?
|
148
|
+
chunks.size == 0
|
149
|
+
end
|
150
|
+
|
151
|
+
def addresses
|
152
|
+
return [p2pkh_addr] if p2pkh?
|
153
|
+
return [p2sh_addr] if p2sh?
|
154
|
+
return [bech32_addr] if witness_program?
|
155
|
+
return get_multisig_pubkeys.map{|pubkey| Tapyrus::Key.new(pubkey: pubkey.bth).to_p2pkh} if multisig?
|
156
|
+
[]
|
157
|
+
end
|
158
|
+
|
159
|
+
# check whether standard script.
|
160
|
+
def standard?
|
161
|
+
p2pkh? | p2sh? | p2wpkh? | p2wsh? | multisig? | standard_op_return?
|
162
|
+
end
|
163
|
+
|
164
|
+
# whether this script is a P2PKH format script.
|
165
|
+
def p2pkh?
|
166
|
+
return false unless chunks.size == 5
|
167
|
+
[OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
|
168
|
+
(chunks[0..1]+ chunks[3..4]).map(&:ord) && chunks[2].bytesize == 21
|
169
|
+
end
|
170
|
+
|
171
|
+
# whether this script is a P2WPKH format script.
|
172
|
+
def p2wpkh?
|
173
|
+
return false unless chunks.size == 2
|
174
|
+
chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 21
|
175
|
+
end
|
176
|
+
|
177
|
+
def p2wsh?
|
178
|
+
return false unless chunks.size == 2
|
179
|
+
chunks[0].ord == WITNESS_VERSION && chunks[1].bytesize == 33
|
180
|
+
end
|
181
|
+
|
182
|
+
def p2sh?
|
183
|
+
return false unless chunks.size == 3
|
184
|
+
OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
|
185
|
+
end
|
186
|
+
|
187
|
+
def multisig?
|
188
|
+
return false if chunks.size < 4 || chunks.last.ord != OP_CHECKMULTISIG
|
189
|
+
pubkey_count = Opcodes.opcode_to_small_int(chunks[-2].opcode)
|
190
|
+
sig_count = Opcodes.opcode_to_small_int(chunks[0].opcode)
|
191
|
+
return false unless pubkey_count || sig_count
|
192
|
+
sig_count <= pubkey_count
|
193
|
+
end
|
194
|
+
|
195
|
+
def op_return?
|
196
|
+
chunks.size >= 1 && chunks[0].ord == OP_RETURN
|
197
|
+
end
|
198
|
+
|
199
|
+
def standard_op_return?
|
200
|
+
op_return? && size <= MAX_OP_RETURN_RELAY &&
|
201
|
+
(chunks.size == 1 || chunks[1].opcode <= OP_16)
|
202
|
+
end
|
203
|
+
|
204
|
+
def op_return_data
|
205
|
+
return nil unless op_return?
|
206
|
+
return nil if chunks.size == 1
|
207
|
+
chunks[1].pushed_data
|
208
|
+
end
|
209
|
+
|
210
|
+
# whether data push only script which dose not include other opcode
|
211
|
+
def push_only?
|
212
|
+
chunks.each do |c|
|
213
|
+
return false if !c.opcode.nil? && c.opcode > OP_16
|
214
|
+
end
|
215
|
+
true
|
216
|
+
end
|
217
|
+
|
218
|
+
# get public keys in the stack.
|
219
|
+
# @return[Array[String]] an array of the pubkeys with hex format.
|
220
|
+
def get_pubkeys
|
221
|
+
chunks.select{|c|c.pushdata? && [33, 65].include?(c.pushed_data.bytesize) && [2, 3, 4, 6, 7].include?(c.pushed_data[0].bth.to_i(16))}.map{|c|c.pushed_data.bth}
|
222
|
+
end
|
223
|
+
|
224
|
+
# A witness program is any valid Script that consists of a 1-byte push opcode followed by a data push between 2 and 40 bytes.
|
225
|
+
def witness_program?
|
226
|
+
return false if size < 4 || size > 42 || chunks.size < 2
|
227
|
+
|
228
|
+
opcode = chunks[0].opcode
|
229
|
+
|
230
|
+
return false if opcode != OP_0 && (opcode < OP_1 || opcode > OP_16)
|
231
|
+
return false unless chunks[1].pushdata?
|
232
|
+
|
233
|
+
if size == (chunks[1][0].unpack('C').first + 2)
|
234
|
+
program_size = chunks[1].pushed_data.bytesize
|
235
|
+
return program_size >= 2 && program_size <= 40
|
236
|
+
end
|
237
|
+
|
238
|
+
false
|
239
|
+
end
|
240
|
+
|
241
|
+
# get witness commitment
|
242
|
+
def witness_commitment
|
243
|
+
return nil if !op_return? || op_return_data.bytesize < 36
|
244
|
+
buf = StringIO.new(op_return_data)
|
245
|
+
return nil unless buf.read(4).bth == WITNESS_COMMITMENT_HEADER
|
246
|
+
buf.read(32).bth
|
247
|
+
end
|
248
|
+
|
249
|
+
# If this script is witness program, return its script code,
|
250
|
+
# otherwise returns the self payload. ScriptInterpreter does not use this.
|
251
|
+
def to_script_code(skip_separator_index = 0)
|
252
|
+
payload = to_payload
|
253
|
+
if p2wpkh?
|
254
|
+
payload = Script.to_p2pkh(chunks[1].pushed_data.bth).to_payload
|
255
|
+
elsif skip_separator_index > 0
|
256
|
+
payload = subscript_codeseparator(skip_separator_index)
|
257
|
+
end
|
258
|
+
Tapyrus.pack_var_string(payload)
|
259
|
+
end
|
260
|
+
|
261
|
+
# get witness version and witness program
|
262
|
+
def witness_data
|
263
|
+
version = opcode_to_small_int(chunks[0].opcode)
|
264
|
+
program = chunks[1].pushed_data
|
265
|
+
[version, program]
|
266
|
+
end
|
267
|
+
|
268
|
+
# append object to payload
|
269
|
+
def <<(obj)
|
270
|
+
if obj.is_a?(Integer)
|
271
|
+
push_int(obj)
|
272
|
+
elsif obj.is_a?(String)
|
273
|
+
append_data(obj)
|
274
|
+
elsif obj.is_a?(Array)
|
275
|
+
obj.each { |o| self.<< o}
|
276
|
+
self
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
# push integer to stack.
|
281
|
+
def push_int(n)
|
282
|
+
begin
|
283
|
+
append_opcode(n)
|
284
|
+
rescue ArgumentError
|
285
|
+
append_data(Script.encode_number(n))
|
286
|
+
end
|
287
|
+
self
|
288
|
+
end
|
289
|
+
|
290
|
+
# append opcode to payload
|
291
|
+
# @param [Integer] opcode append opcode which defined by Tapyrus::Opcodes
|
292
|
+
# @return [Script] return self
|
293
|
+
def append_opcode(opcode)
|
294
|
+
opcode = Opcodes.small_int_to_opcode(opcode) if -1 <= opcode && opcode <= 16
|
295
|
+
raise ArgumentError, "specified invalid opcode #{opcode}." unless Opcodes.defined?(opcode)
|
296
|
+
chunks << opcode.chr
|
297
|
+
self
|
298
|
+
end
|
299
|
+
|
300
|
+
# append data to payload with pushdata opcode
|
301
|
+
# @param [String] data append data. this data is not binary
|
302
|
+
# @return [Script] return self
|
303
|
+
def append_data(data)
|
304
|
+
data = Encoding::ASCII_8BIT == data.encoding ? data : data.htb
|
305
|
+
chunks << Tapyrus::Script.pack_pushdata(data)
|
306
|
+
self
|
307
|
+
end
|
308
|
+
|
309
|
+
# Check the item is in the chunk of the script.
|
310
|
+
def include?(item)
|
311
|
+
chunk_item = if item.is_a?(Integer)
|
312
|
+
item.chr
|
313
|
+
elsif item.is_a?(String)
|
314
|
+
data = Encoding::ASCII_8BIT == item.encoding ? item : item.htb
|
315
|
+
Tapyrus::Script.pack_pushdata(data)
|
316
|
+
end
|
317
|
+
return false unless chunk_item
|
318
|
+
chunks.include?(chunk_item)
|
319
|
+
end
|
320
|
+
|
321
|
+
def to_s
|
322
|
+
chunks.map { |c|
|
323
|
+
case c
|
324
|
+
when Integer
|
325
|
+
opcode_to_name(c)
|
326
|
+
when String
|
327
|
+
if c.pushdata?
|
328
|
+
v = Opcodes.opcode_to_small_int(c.ord)
|
329
|
+
if v
|
330
|
+
v
|
331
|
+
else
|
332
|
+
data = c.pushed_data
|
333
|
+
if data.bytesize <= 4
|
334
|
+
Script.decode_number(data.bth) # for scriptnum
|
335
|
+
else
|
336
|
+
data.bth
|
337
|
+
end
|
338
|
+
end
|
339
|
+
else
|
340
|
+
opcode = Opcodes.opcode_to_name(c.ord)
|
341
|
+
opcode ? opcode : 'OP_UNKNOWN [error]'
|
342
|
+
end
|
343
|
+
end
|
344
|
+
}.join(' ')
|
345
|
+
end
|
346
|
+
|
347
|
+
# generate sha-256 hash for payload
|
348
|
+
def to_sha256
|
349
|
+
Tapyrus.sha256(to_payload).bth
|
350
|
+
end
|
351
|
+
|
352
|
+
# generate hash160 hash for payload
|
353
|
+
def to_hash160
|
354
|
+
Tapyrus.hash160(to_payload.bth)
|
355
|
+
end
|
356
|
+
|
357
|
+
# script size
|
358
|
+
def size
|
359
|
+
to_payload.bytesize
|
360
|
+
end
|
361
|
+
|
362
|
+
# execute script interpreter using this script for development.
|
363
|
+
def run
|
364
|
+
Tapyrus::ScriptInterpreter.eval(Tapyrus::Script.new, self.dup)
|
365
|
+
end
|
366
|
+
|
367
|
+
# encode int value to script number hex.
|
368
|
+
# The stacks hold byte vectors.
|
369
|
+
# When used as numbers, byte vectors are interpreted as little-endian variable-length integers
|
370
|
+
# with the most significant bit determining the sign of the integer.
|
371
|
+
# Thus 0x81 represents -1. 0x80 is another representation of zero (so called negative 0).
|
372
|
+
# Positive 0 is represented by a null-length vector.
|
373
|
+
# Byte vectors are interpreted as Booleans where False is represented by any representation of zero,
|
374
|
+
# and True is represented by any representation of non-zero.
|
375
|
+
def self.encode_number(i)
|
376
|
+
return '' if i == 0
|
377
|
+
negative = i < 0
|
378
|
+
|
379
|
+
hex = i.abs.to_even_length_hex
|
380
|
+
hex = '0' + hex unless (hex.length % 2).zero?
|
381
|
+
v = hex.htb.reverse # change endian
|
382
|
+
|
383
|
+
v = v << (negative ? 0x80 : 0x00) unless (v[-1].unpack('C').first & 0x80) == 0
|
384
|
+
v[-1] = [v[-1].unpack('C').first | 0x80].pack('C') if negative
|
385
|
+
v.bth
|
386
|
+
end
|
387
|
+
|
388
|
+
# decode script number hex to int value
|
389
|
+
def self.decode_number(s)
|
390
|
+
v = s.htb.reverse
|
391
|
+
return 0 if v.length.zero?
|
392
|
+
mbs = v[0].unpack('C').first
|
393
|
+
v[0] = [mbs - 0x80].pack('C') unless (mbs & 0x80) == 0
|
394
|
+
result = v.bth.to_i(16)
|
395
|
+
result = -result unless (mbs & 0x80) == 0
|
396
|
+
result
|
397
|
+
end
|
398
|
+
|
399
|
+
# binary +data+ convert pushdata which contains data length and append PUSHDATA opcode if necessary.
|
400
|
+
def self.pack_pushdata(data)
|
401
|
+
size = data.bytesize
|
402
|
+
header = if size < OP_PUSHDATA1
|
403
|
+
[size].pack('C')
|
404
|
+
elsif size < 0xff
|
405
|
+
[OP_PUSHDATA1, size].pack('CC')
|
406
|
+
elsif size < 0xffff
|
407
|
+
[OP_PUSHDATA2, size].pack('Cv')
|
408
|
+
elsif size < 0xffffffff
|
409
|
+
[OP_PUSHDATA4, size].pack('CV')
|
410
|
+
else
|
411
|
+
raise ArgumentError, 'data size is too big.'
|
412
|
+
end
|
413
|
+
header + data
|
414
|
+
end
|
415
|
+
|
416
|
+
# subscript this script to the specified range.
|
417
|
+
def subscript(*args)
|
418
|
+
s = self.class.new
|
419
|
+
s.chunks = chunks[*args]
|
420
|
+
s
|
421
|
+
end
|
422
|
+
|
423
|
+
# removes chunks matching subscript byte-for-byte and returns as a new object.
|
424
|
+
def find_and_delete(subscript)
|
425
|
+
raise ArgumentError, 'subscript must be Tapyrus::Script' unless subscript.is_a?(Script)
|
426
|
+
return self if subscript.chunks.empty?
|
427
|
+
buf = []
|
428
|
+
i = 0
|
429
|
+
result = Script.new
|
430
|
+
chunks.each do |chunk|
|
431
|
+
sub_chunk = subscript.chunks[i]
|
432
|
+
if chunk.start_with?(sub_chunk)
|
433
|
+
if chunk == sub_chunk
|
434
|
+
buf << chunk
|
435
|
+
i += 1
|
436
|
+
(i = 0; buf.clear) if i == subscript.chunks.size # matched the whole subscript
|
437
|
+
else # matched the part of head
|
438
|
+
i = 0
|
439
|
+
tmp = chunk.dup
|
440
|
+
tmp.slice!(sub_chunk)
|
441
|
+
result.chunks << tmp
|
442
|
+
end
|
443
|
+
else
|
444
|
+
result.chunks << buf.join unless buf.empty?
|
445
|
+
if buf.first == chunk
|
446
|
+
i = 1
|
447
|
+
buf = [chunk]
|
448
|
+
else
|
449
|
+
i = 0
|
450
|
+
result.chunks << chunk
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
result
|
455
|
+
end
|
456
|
+
|
457
|
+
# remove all occurences of opcode. Typically it's OP_CODESEPARATOR.
|
458
|
+
def delete_opcode(opcode)
|
459
|
+
@chunks = chunks.select{|chunk| chunk.ord != opcode}
|
460
|
+
self
|
461
|
+
end
|
462
|
+
|
463
|
+
# Returns a script that deleted the script before the index specified by separator_index.
|
464
|
+
def subscript_codeseparator(separator_index)
|
465
|
+
buf = []
|
466
|
+
process_separator_index = 0
|
467
|
+
chunks.each{|chunk|
|
468
|
+
buf << chunk if process_separator_index == separator_index
|
469
|
+
if chunk.ord == OP_CODESEPARATOR && process_separator_index < separator_index
|
470
|
+
process_separator_index += 1
|
471
|
+
end
|
472
|
+
}
|
473
|
+
buf.join
|
474
|
+
end
|
475
|
+
|
476
|
+
def ==(other)
|
477
|
+
return false unless other
|
478
|
+
chunks == other.chunks
|
479
|
+
end
|
480
|
+
|
481
|
+
def type
|
482
|
+
return 'pubkeyhash' if p2pkh?
|
483
|
+
return 'scripthash' if p2sh?
|
484
|
+
return 'multisig' if multisig?
|
485
|
+
return 'witness_v0_keyhash' if p2wpkh?
|
486
|
+
return 'witness_v0_scripthash' if p2wsh?
|
487
|
+
'nonstandard'
|
488
|
+
end
|
489
|
+
|
490
|
+
def to_h
|
491
|
+
h = {asm: to_s, hex: to_payload.bth, type: type}
|
492
|
+
addrs = addresses
|
493
|
+
unless addrs.empty?
|
494
|
+
h[:req_sigs] = multisig? ? Tapyrus::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
|
495
|
+
h[:addresses] = addrs
|
496
|
+
end
|
497
|
+
h
|
498
|
+
end
|
499
|
+
|
500
|
+
# Returns whether the script is guaranteed to fail at execution, regardless of the initial stack.
|
501
|
+
# This allows outputs to be pruned instantly when entering the UTXO set.
|
502
|
+
# @return [Boolean] whether the script is guaranteed to fail at execution
|
503
|
+
def unspendable?
|
504
|
+
(size > 0 && op_return?) || size > Tapyrus::MAX_SCRIPT_SIZE
|
505
|
+
end
|
506
|
+
|
507
|
+
# convert payload to hex data.
|
508
|
+
# @return [String] script with hex format.
|
509
|
+
def to_hex
|
510
|
+
to_payload.bth
|
511
|
+
end
|
512
|
+
|
513
|
+
private
|
514
|
+
|
515
|
+
# generate p2pkh address. if script dose not p2pkh, return nil.
|
516
|
+
def p2pkh_addr
|
517
|
+
return nil unless p2pkh?
|
518
|
+
hash160 = chunks[2].pushed_data.bth
|
519
|
+
return nil unless hash160.htb.bytesize == 20
|
520
|
+
Tapyrus.encode_base58_address(hash160, Tapyrus.chain_params.address_version)
|
521
|
+
end
|
522
|
+
|
523
|
+
# generate p2wpkh address. if script dose not p2wpkh, return nil.
|
524
|
+
def p2wpkh_addr
|
525
|
+
p2wpkh? ? bech32_addr : nil
|
526
|
+
end
|
527
|
+
|
528
|
+
# generate p2sh address. if script dose not p2sh, return nil.
|
529
|
+
def p2sh_addr
|
530
|
+
return nil unless p2sh?
|
531
|
+
hash160 = chunks[1].pushed_data.bth
|
532
|
+
return nil unless hash160.htb.bytesize == 20
|
533
|
+
Tapyrus.encode_base58_address(hash160, Tapyrus.chain_params.p2sh_version)
|
534
|
+
end
|
535
|
+
|
536
|
+
# generate p2wsh address. if script dose not p2wsh, return nil.
|
537
|
+
def p2wsh_addr
|
538
|
+
p2wsh? ? bech32_addr : nil
|
539
|
+
end
|
540
|
+
|
541
|
+
# return bech32 address for payload
|
542
|
+
def bech32_addr
|
543
|
+
segwit_addr = Bech32::SegwitAddr.new
|
544
|
+
segwit_addr.hrp = Tapyrus.chain_params.bech32_hrp
|
545
|
+
segwit_addr.script_pubkey = to_payload.bth
|
546
|
+
segwit_addr.addr
|
547
|
+
end
|
548
|
+
|
549
|
+
end
|
550
|
+
|
551
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Tapyrus
|
2
|
+
|
3
|
+
# tapyrus script error
|
4
|
+
class ScriptError < Exception
|
5
|
+
|
6
|
+
attr_accessor :code
|
7
|
+
attr_accessor :extra_msg
|
8
|
+
|
9
|
+
def initialize(code, extra_msg = '')
|
10
|
+
raise 'invalid error code.' unless ERRCODES_MAP[code]
|
11
|
+
@code = code
|
12
|
+
@extra_msg = extra_msg
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
case code
|
17
|
+
when SCRIPT_ERR_OK
|
18
|
+
'No error'
|
19
|
+
when SCRIPT_ERR_EVAL_FALSE
|
20
|
+
'Script evaluated without error but finished with a false/empty top stack element'
|
21
|
+
when SCRIPT_ERR_VERIFY
|
22
|
+
'Script failed an OP_VERIFY operation'
|
23
|
+
when SCRIPT_ERR_EQUALVERIFY
|
24
|
+
'Script failed an OP_EQUALVERIFY operation'
|
25
|
+
when SCRIPT_ERR_CHECKMULTISIGVERIFY
|
26
|
+
'Script failed an OP_CHECKMULTISIGVERIFY operation'
|
27
|
+
when SCRIPT_ERR_CHECKSIGVERIFY
|
28
|
+
'Script failed an OP_CHECKSIGVERIFY operation'
|
29
|
+
when SCRIPT_ERR_NUMEQUALVERIFY
|
30
|
+
'Script failed an OP_NUMEQUALVERIFY operation'
|
31
|
+
when SCRIPT_ERR_SCRIPT_SIZE
|
32
|
+
'Script is too big'
|
33
|
+
when SCRIPT_ERR_PUSH_SIZE
|
34
|
+
'Push value size limit exceeded'
|
35
|
+
when SCRIPT_ERR_OP_COUNT
|
36
|
+
'Operation limit exceeded'
|
37
|
+
when SCRIPT_ERR_STACK_SIZE
|
38
|
+
'Stack size limit exceeded'
|
39
|
+
when SCRIPT_ERR_SIG_COUNT
|
40
|
+
'Signature count negative or greater than pubkey count'
|
41
|
+
when SCRIPT_ERR_PUBKEY_COUNT
|
42
|
+
'Pubkey count negative or limit exceeded'
|
43
|
+
when SCRIPT_ERR_BAD_OPCODE
|
44
|
+
'Opcode missing or not understood'
|
45
|
+
when SCRIPT_ERR_DISABLED_OPCODE
|
46
|
+
'Attempted to use a disabled opcode'
|
47
|
+
when SCRIPT_ERR_INVALID_STACK_OPERATION
|
48
|
+
'Operation not valid with the current stack size'
|
49
|
+
when SCRIPT_ERR_INVALID_ALTSTACK_OPERATION
|
50
|
+
'Operation not valid with the current altstack size'
|
51
|
+
when SCRIPT_ERR_OP_RETURN
|
52
|
+
'OP_was encountered'
|
53
|
+
when SCRIPT_ERR_UNBALANCED_CONDITIONAL
|
54
|
+
'Invalid OP_IF construction'
|
55
|
+
when SCRIPT_ERR_NEGATIVE_LOCKTIME
|
56
|
+
'Negative locktime'
|
57
|
+
when SCRIPT_ERR_UNSATISFIED_LOCKTIME
|
58
|
+
'Locktime requirement not satisfied'
|
59
|
+
when SCRIPT_ERR_SIG_HASHTYPE
|
60
|
+
'Signature hash type missing or not understood'
|
61
|
+
when SCRIPT_ERR_SIG_DER
|
62
|
+
'Non-canonical DER signature'
|
63
|
+
when SCRIPT_ERR_MINIMALDATA
|
64
|
+
'Data push larger than necessary'
|
65
|
+
when SCRIPT_ERR_SIG_PUSHONLY
|
66
|
+
'Only non-push operators allowed in signatures'
|
67
|
+
when SCRIPT_ERR_SIG_HIGH_S
|
68
|
+
'Non-canonical signature S value is unnecessarily high'
|
69
|
+
when SCRIPT_ERR_SIG_NULLDUMMY
|
70
|
+
'Dummy CHECKMULTISIG argument must be zero'
|
71
|
+
when SCRIPT_ERR_MINIMALIF
|
72
|
+
'OP_IF/NOTIF argument must be minimal'
|
73
|
+
when SCRIPT_ERR_SIG_NULLFAIL
|
74
|
+
'Signature must be zero for failed CHECK(MULTI)SIG operation'
|
75
|
+
when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_NOPS
|
76
|
+
'NOPx reserved for soft-fork upgrades'
|
77
|
+
when SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM
|
78
|
+
'Witness version reserved for soft-fork upgrades'
|
79
|
+
when SCRIPT_ERR_PUBKEYTYPE
|
80
|
+
'Public key is neither compressed or uncompressed'
|
81
|
+
when SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH
|
82
|
+
'Witness program has incorrect length'
|
83
|
+
when SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY
|
84
|
+
'Witness program was passed an empty witness'
|
85
|
+
when SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH
|
86
|
+
'Witness program hash mismatch'
|
87
|
+
when SCRIPT_ERR_WITNESS_MALLEATED
|
88
|
+
'Witness requires empty scriptSig'
|
89
|
+
when SCRIPT_ERR_WITNESS_MALLEATED_P2SH
|
90
|
+
'Witness requires only-redeemscript scriptSig'
|
91
|
+
when SCRIPT_ERR_WITNESS_UNEXPECTED
|
92
|
+
'Witness provided for non-witness script'
|
93
|
+
when SCRIPT_ERR_WITNESS_PUBKEYTYPE
|
94
|
+
'Using non-compressed keys in segwit'
|
95
|
+
when SCRIPT_ERR_OP_CODESEPARATOR
|
96
|
+
'Using OP_CODESEPARATOR in non-witness scrip'
|
97
|
+
when SCRIPT_ERR_SIG_FINDANDDELETE
|
98
|
+
'Signature is found in scriptCode'
|
99
|
+
when SCRIPT_ERR_UNKNOWN_ERROR, SCRIPT_ERR_ERROR_COUNT
|
100
|
+
'unknown error'
|
101
|
+
else
|
102
|
+
extra_msg ? extra_msg : 'unknown error'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.name_to_code(name)
|
107
|
+
NAME_MAP[name]
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|