tapyrus 0.1.0 → 0.2.4
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 +4 -4
- data/README.md +7 -15
- data/exe/tapyrusrbd +2 -2
- data/lib/openassets/util.rb +2 -4
- data/lib/schnorr.rb +83 -0
- data/lib/schnorr/signature.rb +38 -0
- data/lib/tapyrus.rb +11 -18
- data/lib/tapyrus/block.rb +3 -38
- data/lib/tapyrus/block_header.rb +70 -41
- data/lib/tapyrus/chain_params.rb +13 -36
- data/lib/tapyrus/chainparams/{regtest.yml → dev.yml} +10 -19
- data/lib/tapyrus/chainparams/{mainnet.yml → prod.yml} +7 -19
- data/lib/tapyrus/constants.rb +12 -34
- data/lib/tapyrus/errors.rb +17 -0
- data/lib/tapyrus/ext.rb +5 -0
- data/lib/tapyrus/ext/ecdsa.rb +39 -0
- data/lib/tapyrus/ext/json_parser.rb +47 -0
- data/lib/tapyrus/ext_key.rb +42 -23
- data/lib/tapyrus/key.rb +47 -40
- data/lib/tapyrus/message.rb +2 -2
- data/lib/tapyrus/message/base.rb +1 -0
- data/lib/tapyrus/message/block.rb +4 -4
- data/lib/tapyrus/message/cmpct_block.rb +3 -5
- data/lib/tapyrus/message/header_and_short_ids.rb +1 -1
- data/lib/tapyrus/message/headers.rb +1 -1
- data/lib/tapyrus/message/merkle_block.rb +1 -1
- data/lib/tapyrus/message/tx.rb +2 -2
- data/lib/tapyrus/network/peer.rb +1 -15
- data/lib/tapyrus/node/cli.rb +15 -11
- data/lib/tapyrus/node/configuration.rb +1 -1
- data/lib/tapyrus/node/spv.rb +1 -1
- data/lib/tapyrus/opcodes.rb +5 -0
- data/lib/tapyrus/out_point.rb +1 -1
- data/lib/tapyrus/rpc/request_handler.rb +7 -6
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +17 -15
- data/lib/tapyrus/script/color.rb +79 -0
- data/lib/tapyrus/script/multisig.rb +0 -27
- data/lib/tapyrus/script/script.rb +74 -89
- data/lib/tapyrus/script/script_error.rb +8 -14
- data/lib/tapyrus/script/script_interpreter.rb +65 -86
- data/lib/tapyrus/script/tx_checker.rb +21 -5
- data/lib/tapyrus/secp256k1.rb +1 -0
- data/lib/tapyrus/secp256k1/native.rb +93 -20
- data/lib/tapyrus/secp256k1/rfc6979.rb +43 -0
- data/lib/tapyrus/secp256k1/ruby.rb +63 -54
- data/lib/tapyrus/store/chain_entry.rb +3 -2
- data/lib/tapyrus/store/db/level_db.rb +58 -0
- data/lib/tapyrus/store/spv_chain.rb +29 -11
- data/lib/tapyrus/tx.rb +18 -160
- data/lib/tapyrus/tx_in.rb +4 -11
- data/lib/tapyrus/tx_out.rb +2 -1
- data/lib/tapyrus/util.rb +8 -0
- data/lib/tapyrus/validation.rb +1 -6
- data/lib/tapyrus/version.rb +1 -1
- data/lib/tapyrus/wallet/account.rb +1 -0
- data/lib/tapyrus/wallet/master_key.rb +1 -0
- data/tapyrusrb.gemspec +3 -3
- metadata +44 -39
- data/lib/tapyrus/chainparams/testnet.yml +0 -41
- data/lib/tapyrus/descriptor.rb +0 -147
- data/lib/tapyrus/script_witness.rb +0 -38
@@ -0,0 +1,79 @@
|
|
1
|
+
module Tapyrus
|
2
|
+
module Color
|
3
|
+
module TokenTypes
|
4
|
+
NONE = 0x00
|
5
|
+
REISSUABLE = 0xC1
|
6
|
+
NON_REISSUABLE = 0xC2
|
7
|
+
NFT = 0xC3
|
8
|
+
end
|
9
|
+
|
10
|
+
class ColorIdentifier
|
11
|
+
include Tapyrus::HexConverter
|
12
|
+
|
13
|
+
attr_reader :type, :payload
|
14
|
+
|
15
|
+
def self.reissuable(script_pubkey)
|
16
|
+
new(TokenTypes::REISSUABLE, Tapyrus.sha256(script_pubkey.to_payload))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.non_reissuable(out_point)
|
20
|
+
new(TokenTypes::NON_REISSUABLE, Tapyrus.sha256(out_point.to_payload))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.nft(out_point)
|
24
|
+
new(TokenTypes::NFT, Tapyrus.sha256(out_point.to_payload))
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_payload
|
28
|
+
[type, payload].pack('Ca*')
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.parse_from_payload(payload)
|
32
|
+
type, payload = payload.unpack('Ca*')
|
33
|
+
new(type, payload)
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other)
|
37
|
+
other && other.to_payload == to_payload
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid?
|
41
|
+
return false unless [TokenTypes::REISSUABLE, TokenTypes::NON_REISSUABLE, TokenTypes::NFT].include?(type)
|
42
|
+
return false unless payload.bytesize == 32
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def initialize(type, payload)
|
49
|
+
@type = type
|
50
|
+
@payload = payload
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module ColoredOutput
|
55
|
+
def colored?
|
56
|
+
script_pubkey.cp2pkh? || script_pubkey.cp2sh?
|
57
|
+
end
|
58
|
+
|
59
|
+
def color_id
|
60
|
+
@color_id ||= ColorIdentifier.parse_from_payload(script_pubkey.chunks[0].pushed_data)
|
61
|
+
end
|
62
|
+
|
63
|
+
def reissuable?
|
64
|
+
return false unless colored?
|
65
|
+
color_id.type == TokenTypes::REISSUABLE
|
66
|
+
end
|
67
|
+
|
68
|
+
def non_reissuable?
|
69
|
+
return false unless colored?
|
70
|
+
color_id.type == TokenTypes::NON_REISSUABLE
|
71
|
+
end
|
72
|
+
|
73
|
+
def nft?
|
74
|
+
return false unless colored?
|
75
|
+
color_id.type == TokenTypes::NFT
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -61,32 +61,5 @@ module Tapyrus
|
|
61
61
|
prefix + pubkeys.map { |k| sigs[k] ? Tapyrus::Script.pack_pushdata(sigs[k]) : nil }.join +
|
62
62
|
Tapyrus::Script.pack_pushdata(script.chunks[-1].pushed_data)
|
63
63
|
end
|
64
|
-
|
65
|
-
def self.add_sig_to_multisig_script_witness(sig_to_add, script_witness, hash_type = SIGHASH_TYPE[:all])
|
66
|
-
signature = sig_to_add + [hash_type].pack("C*")
|
67
|
-
script_witness.stack << signature
|
68
|
-
end
|
69
|
-
|
70
|
-
# Sort signatures in the given +script_witness+ according to the order of pubkeys in
|
71
|
-
# the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
|
72
|
-
# @param [ScriptWitness] script_witness for multisig.
|
73
|
-
# @param [String] sig_hash to be signed.
|
74
|
-
def self.sort_witness_multisig_signatures(script_witness, sig_hash)
|
75
|
-
redeem_script = Tapyrus::Script.parse_from_payload(script_witness.stack[-1])
|
76
|
-
pubkeys = redeem_script.get_multisig_pubkeys
|
77
|
-
sigs = Hash[script_witness.stack[1...-1].map.with_index do |sig, idx|
|
78
|
-
pubkey = pubkeys.map do |key|
|
79
|
-
Tapyrus::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil
|
80
|
-
end.compact.first
|
81
|
-
raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
|
82
|
-
[pubkey, sig]
|
83
|
-
end]
|
84
|
-
script_witness.stack.clear
|
85
|
-
script_witness.stack << ''
|
86
|
-
pubkeys.each do |pubkey|
|
87
|
-
script_witness.stack << sigs[pubkey] if sigs[pubkey]
|
88
|
-
end
|
89
|
-
script_witness.stack << redeem_script.to_payload
|
90
|
-
end
|
91
64
|
end
|
92
65
|
end
|
@@ -5,6 +5,7 @@ module Tapyrus
|
|
5
5
|
|
6
6
|
# tapyrus script
|
7
7
|
class Script
|
8
|
+
include Tapyrus::HexConverter
|
8
9
|
include Tapyrus::Opcodes
|
9
10
|
|
10
11
|
attr_accessor :chunks
|
@@ -18,11 +19,6 @@ module Tapyrus
|
|
18
19
|
new << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
|
19
20
|
end
|
20
21
|
|
21
|
-
# generate P2WPKH script
|
22
|
-
def self.to_p2wpkh(pubkey_hash)
|
23
|
-
new << WITNESS_VERSION << pubkey_hash
|
24
|
-
end
|
25
|
-
|
26
22
|
# generate m of n multisig p2sh script
|
27
23
|
# @param [String] m the number of signatures required for multisig
|
28
24
|
# @param [Array] pubkeys array of public keys that compose multisig
|
@@ -45,6 +41,40 @@ module Tapyrus
|
|
45
41
|
Script.to_p2sh(to_hash160)
|
46
42
|
end
|
47
43
|
|
44
|
+
# generate cp2pkh script
|
45
|
+
# @param [ColorIdentifier] color identifier
|
46
|
+
# @param [String] hash160 of pubkey
|
47
|
+
# @return [Script] CP2PKH script
|
48
|
+
# @raise [ArgumentError] if color_id is nil or invalid
|
49
|
+
def self.to_cp2pkh(color_id, pubkey_hash)
|
50
|
+
raise ArgumentError, 'Specified color identifier is invalid' unless color_id&.valid?
|
51
|
+
new << color_id.to_payload << OP_COLOR << OP_DUP << OP_HASH160 << pubkey_hash << OP_EQUALVERIFY << OP_CHECKSIG
|
52
|
+
end
|
53
|
+
|
54
|
+
# generate cp2sh script
|
55
|
+
# @param [ColorIdentifier] color identifier
|
56
|
+
# @param [String] hash160 of script
|
57
|
+
# @return [Script] CP2SH script
|
58
|
+
# @raise [ArgumentError] if color_id is nil or invalid
|
59
|
+
def self.to_cp2sh(color_id, script_hash)
|
60
|
+
raise ArgumentError, 'Specified color identifier is invalid' unless color_id&.valid?
|
61
|
+
new << color_id.to_payload << OP_COLOR << OP_HASH160 << script_hash << OP_EQUAL
|
62
|
+
end
|
63
|
+
|
64
|
+
# Add color identifier to existing p2pkh or p2sh
|
65
|
+
# @param [ColorIdentifier] color identifier
|
66
|
+
# @return [Script] CP2PKH or CP2SH script
|
67
|
+
# @raise [ArgumentError] if color_id is nil or invalid
|
68
|
+
# @raise [RuntimeError] if script is neither p2pkh nor p2sh
|
69
|
+
def add_color(color_id)
|
70
|
+
raise ArgumentError, 'Specified color identifier is invalid' unless color_id&.valid?
|
71
|
+
raise RuntimeError, 'Only p2pkh and p2sh can add color' unless p2pkh? or p2sh?
|
72
|
+
Tapyrus::Script.new.tap do |s|
|
73
|
+
s << color_id.to_payload << OP_COLOR
|
74
|
+
s.chunks += self.chunks
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
48
78
|
def get_multisig_pubkeys
|
49
79
|
num = Tapyrus::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
|
50
80
|
(1..num).map{ |i| chunks[i].pushed_data }
|
@@ -59,13 +89,6 @@ module Tapyrus
|
|
59
89
|
new << m << pubkeys << pubkeys.size << OP_CHECKMULTISIG
|
60
90
|
end
|
61
91
|
|
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
92
|
# generate script from string.
|
70
93
|
def self.from_string(string)
|
71
94
|
script = new
|
@@ -151,14 +174,15 @@ module Tapyrus
|
|
151
174
|
def addresses
|
152
175
|
return [p2pkh_addr] if p2pkh?
|
153
176
|
return [p2sh_addr] if p2sh?
|
154
|
-
return [
|
177
|
+
return [cp2pkh_addr] if cp2pkh?
|
178
|
+
return [cp2sh_addr] if cp2sh?
|
155
179
|
return get_multisig_pubkeys.map{|pubkey| Tapyrus::Key.new(pubkey: pubkey.bth).to_p2pkh} if multisig?
|
156
180
|
[]
|
157
181
|
end
|
158
182
|
|
159
183
|
# check whether standard script.
|
160
184
|
def standard?
|
161
|
-
p2pkh? | p2sh? |
|
185
|
+
p2pkh? | p2sh? | multisig? | standard_op_return?
|
162
186
|
end
|
163
187
|
|
164
188
|
# whether this script is a P2PKH format script.
|
@@ -168,17 +192,6 @@ module Tapyrus
|
|
168
192
|
(chunks[0..1]+ chunks[3..4]).map(&:ord) && chunks[2].bytesize == 21
|
169
193
|
end
|
170
194
|
|
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
195
|
def p2sh?
|
183
196
|
return false unless chunks.size == 3
|
184
197
|
OP_HASH160 == chunks[0].ord && OP_EQUAL == chunks[2].ord && chunks[1].bytesize == 21
|
@@ -201,6 +214,23 @@ module Tapyrus
|
|
201
214
|
(chunks.size == 1 || chunks[1].opcode <= OP_16)
|
202
215
|
end
|
203
216
|
|
217
|
+
def cp2pkh?
|
218
|
+
return false unless chunks.size == 7
|
219
|
+
return false unless chunks[0].bytesize == 34
|
220
|
+
return false unless Tapyrus::Color::ColorIdentifier.parse_from_payload(chunks[0].pushed_data)&.valid?
|
221
|
+
return false unless chunks[1].ord == OP_COLOR
|
222
|
+
[OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] ==
|
223
|
+
(chunks[2..3]+ chunks[5..6]).map(&:ord) && chunks[4].bytesize == 21
|
224
|
+
end
|
225
|
+
|
226
|
+
def cp2sh?
|
227
|
+
return false unless chunks.size == 5
|
228
|
+
return false unless chunks[0].bytesize == 34
|
229
|
+
return false unless Tapyrus::Color::ColorIdentifier.parse_from_payload(chunks[0].pushed_data)&.valid?
|
230
|
+
return false unless chunks[1].ord == OP_COLOR
|
231
|
+
OP_HASH160 == chunks[2].ord && OP_EQUAL == chunks[4].ord && chunks[3].bytesize == 21
|
232
|
+
end
|
233
|
+
|
204
234
|
def op_return_data
|
205
235
|
return nil unless op_return?
|
206
236
|
return nil if chunks.size == 1
|
@@ -221,50 +251,15 @@ module Tapyrus
|
|
221
251
|
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
252
|
end
|
223
253
|
|
224
|
-
#
|
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.
|
254
|
+
# returns the self payload. ScriptInterpreter does not use this.
|
251
255
|
def to_script_code(skip_separator_index = 0)
|
252
256
|
payload = to_payload
|
253
|
-
if
|
254
|
-
payload = Script.to_p2pkh(chunks[1].pushed_data.bth).to_payload
|
255
|
-
elsif skip_separator_index > 0
|
257
|
+
if skip_separator_index > 0
|
256
258
|
payload = subscript_codeseparator(skip_separator_index)
|
257
259
|
end
|
258
260
|
Tapyrus.pack_var_string(payload)
|
259
261
|
end
|
260
262
|
|
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
263
|
# append object to payload
|
269
264
|
def <<(obj)
|
270
265
|
if obj.is_a?(Integer)
|
@@ -324,6 +319,7 @@ module Tapyrus
|
|
324
319
|
when Integer
|
325
320
|
opcode_to_name(c)
|
326
321
|
when String
|
322
|
+
return c if c.empty?
|
327
323
|
if c.pushdata?
|
328
324
|
v = Opcodes.opcode_to_small_int(c.ord)
|
329
325
|
if v
|
@@ -351,7 +347,7 @@ module Tapyrus
|
|
351
347
|
|
352
348
|
# generate hash160 hash for payload
|
353
349
|
def to_hash160
|
354
|
-
Tapyrus.hash160(
|
350
|
+
Tapyrus.hash160(to_hex)
|
355
351
|
end
|
356
352
|
|
357
353
|
# script size
|
@@ -482,13 +478,11 @@ module Tapyrus
|
|
482
478
|
return 'pubkeyhash' if p2pkh?
|
483
479
|
return 'scripthash' if p2sh?
|
484
480
|
return 'multisig' if multisig?
|
485
|
-
return 'witness_v0_keyhash' if p2wpkh?
|
486
|
-
return 'witness_v0_scripthash' if p2wsh?
|
487
481
|
'nonstandard'
|
488
482
|
end
|
489
483
|
|
490
484
|
def to_h
|
491
|
-
h = {asm: to_s, hex:
|
485
|
+
h = {asm: to_s, hex: to_hex, type: type}
|
492
486
|
addrs = addresses
|
493
487
|
unless addrs.empty?
|
494
488
|
h[:req_sigs] = multisig? ? Tapyrus::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
|
@@ -504,48 +498,39 @@ module Tapyrus
|
|
504
498
|
(size > 0 && op_return?) || size > Tapyrus::MAX_SCRIPT_SIZE
|
505
499
|
end
|
506
500
|
|
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
501
|
private
|
514
502
|
|
515
503
|
# generate p2pkh address. if script dose not p2pkh, return nil.
|
516
504
|
def p2pkh_addr
|
517
505
|
return nil unless p2pkh?
|
518
506
|
hash160 = chunks[2].pushed_data.bth
|
519
|
-
return nil unless hash160.htb.bytesize == 20
|
520
507
|
Tapyrus.encode_base58_address(hash160, Tapyrus.chain_params.address_version)
|
521
508
|
end
|
522
509
|
|
523
|
-
# generate p2wpkh address. if script dose not p2wpkh, return nil.
|
524
|
-
def p2wpkh_addr
|
525
|
-
p2wpkh? ? bech32_addr : nil
|
526
|
-
end
|
527
|
-
|
528
510
|
# generate p2sh address. if script dose not p2sh, return nil.
|
529
511
|
def p2sh_addr
|
530
512
|
return nil unless p2sh?
|
531
513
|
hash160 = chunks[1].pushed_data.bth
|
532
|
-
return nil unless hash160.htb.bytesize == 20
|
533
514
|
Tapyrus.encode_base58_address(hash160, Tapyrus.chain_params.p2sh_version)
|
534
515
|
end
|
535
516
|
|
536
|
-
# generate
|
537
|
-
def
|
538
|
-
|
539
|
-
end
|
517
|
+
# generate cp2pkh address. if script dose not cp2pkh, return nil.
|
518
|
+
def cp2pkh_addr
|
519
|
+
return nil unless cp2pkh?
|
540
520
|
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
segwit_addr.hrp = Tapyrus.chain_params.bech32_hrp
|
545
|
-
segwit_addr.script_pubkey = to_payload.bth
|
546
|
-
segwit_addr.addr
|
521
|
+
color_id = chunks[0].pushed_data.bth
|
522
|
+
hash160 = chunks[4].pushed_data.bth
|
523
|
+
Tapyrus.encode_base58_address(color_id + hash160, Tapyrus.chain_params.cp2pkh_version)
|
547
524
|
end
|
548
525
|
|
526
|
+
# generate cp2sh address. if script dose not cp2sh, return nil.
|
527
|
+
def cp2sh_addr
|
528
|
+
return nil unless cp2sh?
|
529
|
+
|
530
|
+
color_id = chunks[0].pushed_data.bth
|
531
|
+
hash160 = chunks[3].pushed_data.bth
|
532
|
+
Tapyrus.encode_base58_address(color_id + hash160, Tapyrus.chain_params.cp2sh_version)
|
533
|
+
end
|
549
534
|
end
|
550
535
|
|
551
536
|
end
|
@@ -78,24 +78,18 @@ module Tapyrus
|
|
78
78
|
'Witness version reserved for soft-fork upgrades'
|
79
79
|
when SCRIPT_ERR_PUBKEYTYPE
|
80
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
81
|
when SCRIPT_ERR_OP_CODESEPARATOR
|
96
82
|
'Using OP_CODESEPARATOR in non-witness scrip'
|
97
83
|
when SCRIPT_ERR_SIG_FINDANDDELETE
|
98
84
|
'Signature is found in scriptCode'
|
85
|
+
when SCRIPT_ERR_OP_COLOR_UNEXPECTED
|
86
|
+
'Unexpected OP_COLOR in script'
|
87
|
+
when SCRIPT_ERR_OP_COLOR_ID_INVALID
|
88
|
+
'Invalid ColorId in script'
|
89
|
+
when SCRIPT_ERR_OP_COLOR_MULTIPLE
|
90
|
+
'Multiple OP_COLOR found in script'
|
91
|
+
when SCRIPT_ERR_OP_COLOR_IN_BRANCH
|
92
|
+
'OP_COLOR is not permitted in a script branch'
|
99
93
|
when SCRIPT_ERR_UNKNOWN_ERROR, SCRIPT_ERR_ERROR_COUNT
|
100
94
|
'unknown error'
|
101
95
|
else
|
@@ -32,32 +32,21 @@ module Tapyrus
|
|
32
32
|
# eval script
|
33
33
|
# @param [Tapyrus::Script] script_sig a signature script (unlock script which data push only)
|
34
34
|
# @param [Tapyrus::Script] script_pubkey a script pubkey (locking script)
|
35
|
-
# @param [Tapyrus::ScriptWitness] witness a witness script
|
36
35
|
# @return [Boolean] result
|
37
|
-
def verify_script(script_sig, script_pubkey
|
36
|
+
def verify_script(script_sig, script_pubkey)
|
38
37
|
|
39
38
|
return set_error(SCRIPT_ERR_SIG_PUSHONLY) if flag?(SCRIPT_VERIFY_SIGPUSHONLY) && !script_sig.push_only?
|
40
39
|
|
41
40
|
stack_copy = nil
|
42
|
-
had_witness = false
|
43
41
|
|
44
|
-
return false unless eval_script(script_sig, :base)
|
42
|
+
return false unless eval_script(script_sig, :base, false)
|
45
43
|
|
46
44
|
stack_copy = stack.dup if flag?(SCRIPT_VERIFY_P2SH)
|
47
45
|
|
48
|
-
return false unless eval_script(script_pubkey, :base)
|
46
|
+
return false unless eval_script(script_pubkey, :base, false)
|
49
47
|
|
50
48
|
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last.htb)
|
51
49
|
|
52
|
-
# Bare witness programs
|
53
|
-
if flag?(SCRIPT_VERIFY_WITNESS) && script_pubkey.witness_program?
|
54
|
-
had_witness = true
|
55
|
-
return set_error(SCRIPT_ERR_WITNESS_MALLEATED) unless script_sig.size == 0
|
56
|
-
version, program = script_pubkey.witness_data
|
57
|
-
stack_copy = stack.dup
|
58
|
-
return false unless verify_witness_program(witness, version, program)
|
59
|
-
end
|
60
|
-
|
61
50
|
# Additional validation for spend-to-script-hash transactions
|
62
51
|
if flag?(SCRIPT_VERIFY_P2SH) && script_pubkey.p2sh?
|
63
52
|
return set_error(SCRIPT_ERR_SIG_PUSHONLY) unless script_sig.push_only?
|
@@ -69,18 +58,8 @@ module Tapyrus
|
|
69
58
|
rescue Exception => e
|
70
59
|
return set_error(SCRIPT_ERR_BAD_OPCODE, "Failed to parse serialized redeem script for P2SH. #{e.message}")
|
71
60
|
end
|
72
|
-
return false unless eval_script(redeem_script, :base)
|
61
|
+
return false unless eval_script(redeem_script, :base, true)
|
73
62
|
return set_error(SCRIPT_ERR_EVAL_FALSE) if stack.empty? || !cast_to_bool(stack.last)
|
74
|
-
|
75
|
-
# P2SH witness program
|
76
|
-
if flag?(SCRIPT_VERIFY_WITNESS) && redeem_script.witness_program?
|
77
|
-
had_witness = true
|
78
|
-
# The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we reintroduce malleability.
|
79
|
-
return set_error(SCRIPT_ERR_WITNESS_MALLEATED_P2SH) unless script_sig == (Tapyrus::Script.new << redeem_script.to_payload.bth)
|
80
|
-
|
81
|
-
version, program = redeem_script.witness_data
|
82
|
-
return false unless verify_witness_program(witness, version, program)
|
83
|
-
end
|
84
63
|
end
|
85
64
|
|
86
65
|
# The CLEANSTACK check is only performed after potential P2SH evaluation,
|
@@ -93,11 +72,6 @@ module Tapyrus
|
|
93
72
|
return set_error(SCRIPT_ERR_CLEANSTACK) unless stack.size == 1
|
94
73
|
end
|
95
74
|
|
96
|
-
if flag?(SCRIPT_VERIFY_WITNESS)
|
97
|
-
raise 'assert' unless flag?(SCRIPT_VERIFY_P2SH)
|
98
|
-
return set_error(SCRIPT_ERR_WITNESS_UNEXPECTED) if !had_witness && !witness.empty?
|
99
|
-
end
|
100
|
-
|
101
75
|
true
|
102
76
|
end
|
103
77
|
|
@@ -106,45 +80,14 @@ module Tapyrus
|
|
106
80
|
false
|
107
81
|
end
|
108
82
|
|
109
|
-
def
|
110
|
-
if version == 0
|
111
|
-
if program.bytesize == 32
|
112
|
-
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WITNESS_EMPTY) if witness.stack.size == 0
|
113
|
-
script_pubkey = Tapyrus::Script.parse_from_payload(witness.stack.last)
|
114
|
-
@stack = witness.stack[0..-2].map{|w|w.bth}
|
115
|
-
script_hash = Tapyrus.sha256(script_pubkey.to_payload)
|
116
|
-
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless script_hash == program
|
117
|
-
elsif program.bytesize == 20
|
118
|
-
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH) unless witness.stack.size == 2
|
119
|
-
script_pubkey = Tapyrus::Script.to_p2pkh(program.bth)
|
120
|
-
@stack = witness.stack.map{|w|w.bth}
|
121
|
-
else
|
122
|
-
return set_error(SCRIPT_ERR_WITNESS_PROGRAM_WRONG_LENGTH)
|
123
|
-
end
|
124
|
-
elsif flag?(SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
125
|
-
return set_error(SCRIPT_ERR_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM)
|
126
|
-
else
|
127
|
-
return true # Higher version witness scripts return true for future softfork compatibility
|
128
|
-
end
|
129
|
-
|
130
|
-
stack.each do |s| # Disallow stack item size > MAX_SCRIPT_ELEMENT_SIZE in witness stack
|
131
|
-
return set_error(SCRIPT_ERR_PUSH_SIZE) if s.htb.bytesize > MAX_SCRIPT_ELEMENT_SIZE
|
132
|
-
end
|
133
|
-
|
134
|
-
return false unless eval_script(script_pubkey, :witness_v0)
|
135
|
-
|
136
|
-
return set_error(SCRIPT_ERR_EVAL_FALSE) unless stack.size == 1
|
137
|
-
return set_error(SCRIPT_ERR_EVAL_FALSE) unless cast_to_bool(stack.last)
|
138
|
-
true
|
139
|
-
end
|
140
|
-
|
141
|
-
def eval_script(script, sig_version)
|
83
|
+
def eval_script(script, sig_version, is_redeem_script)
|
142
84
|
return set_error(SCRIPT_ERR_SCRIPT_SIZE) if script.size > MAX_SCRIPT_SIZE
|
143
85
|
begin
|
144
86
|
flow_stack = []
|
145
87
|
alt_stack = []
|
146
88
|
last_code_separator_index = 0
|
147
89
|
op_count = 0
|
90
|
+
color_id = nil
|
148
91
|
|
149
92
|
script.chunks.each_with_index do |c, index|
|
150
93
|
need_exec = !flow_stack.include?(false)
|
@@ -209,7 +152,7 @@ module Tapyrus
|
|
209
152
|
if need_exec
|
210
153
|
return set_error(SCRIPT_ERR_UNBALANCED_CONDITIONAL) if stack.size < 1
|
211
154
|
value = pop_string.htb
|
212
|
-
if
|
155
|
+
if flag?(SCRIPT_VERIFY_MINIMALIF)
|
213
156
|
if value.bytesize > 1 || (value.bytesize == 1 && value[0].unpack('C').first != 1)
|
214
157
|
return set_error(SCRIPT_ERR_MINIMALIF)
|
215
158
|
end
|
@@ -409,8 +352,8 @@ module Tapyrus
|
|
409
352
|
return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
|
410
353
|
subscript = tmp
|
411
354
|
end
|
412
|
-
|
413
|
-
|
355
|
+
return false if (sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ?
|
356
|
+
!check_schnorr_signature_encoding(sig) : !check_ecdsa_signature_encoding(sig)) || !check_pubkey_encoding(pubkey)
|
414
357
|
|
415
358
|
success = checker.check_sig(sig, pubkey, subscript, sig_version)
|
416
359
|
|
@@ -428,6 +371,19 @@ module Tapyrus
|
|
428
371
|
return set_error(SCRIPT_ERR_CHECKSIGVERIFY)
|
429
372
|
end
|
430
373
|
end
|
374
|
+
when OP_CHECKDATASIG, OP_CHECKDATASIGVERIFY
|
375
|
+
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 3
|
376
|
+
sig, msg, pubkey = pop_string(3)
|
377
|
+
# check signature encoding without hashtype byte
|
378
|
+
return false if (sig.htb.bytesize != (Tapyrus::Key::COMPACT_SIGNATURE_SIZE - 1) && !check_ecdsa_signature_encoding(sig, true)) || !check_pubkey_encoding(pubkey)
|
379
|
+
digest = Tapyrus.sha256(msg)
|
380
|
+
success = checker.verify_sig(sig, pubkey, digest)
|
381
|
+
return set_error(SCRIPT_ERR_SIG_NULLFAIL) if !success && flag?(SCRIPT_VERIFY_NULLFAIL) && sig.bytesize > 0
|
382
|
+
push_int(success ? 1 : 0)
|
383
|
+
if opcode == OP_CHECKDATASIGVERIFY
|
384
|
+
stack.pop if success
|
385
|
+
return set_error(SCRIPT_ERR_CHECKDATASIGVERIFY) unless success
|
386
|
+
end
|
431
387
|
when OP_CHECKMULTISIG, OP_CHECKMULTISIGVERIFY
|
432
388
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
433
389
|
pubkey_count = pop_int
|
@@ -463,10 +419,17 @@ module Tapyrus
|
|
463
419
|
end
|
464
420
|
|
465
421
|
success = true
|
422
|
+
current_sig_scheme = nil
|
466
423
|
while success && sig_count > 0
|
467
424
|
sig = sigs.pop
|
468
425
|
pubkey = pubkeys.pop
|
469
|
-
|
426
|
+
sig_scheme = sig.htb.bytesize == Tapyrus::Key::COMPACT_SIGNATURE_SIZE ? :schnorr : :ecdsa
|
427
|
+
current_sig_scheme = sig_scheme if current_sig_scheme.nil?
|
428
|
+
|
429
|
+
return false if (sig_scheme == :schnorr ? !check_schnorr_signature_encoding(sig) : !check_ecdsa_signature_encoding(sig)) || !check_pubkey_encoding(pubkey) # error already set.
|
430
|
+
|
431
|
+
return set_error(SCRIPT_ERR_MIXED_SCHEME_MULTISIG) unless sig_scheme == current_sig_scheme
|
432
|
+
|
470
433
|
ok = checker.check_sig(sig, pubkey, subscript, sig_version)
|
471
434
|
if ok
|
472
435
|
sig_count -= 1
|
@@ -488,9 +451,8 @@ module Tapyrus
|
|
488
451
|
# Unfortunately this is a potential source of mutability,
|
489
452
|
# so optionally verify it is exactly equal to zero prior to removing it from the stack.
|
490
453
|
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size < 1
|
491
|
-
|
492
|
-
|
493
|
-
end
|
454
|
+
return set_error(SCRIPT_ERR_SIG_NULLDUMMY) if stack[-1].size > 0
|
455
|
+
|
494
456
|
stack.pop
|
495
457
|
|
496
458
|
push_int(success ? 1 : 0)
|
@@ -503,6 +465,25 @@ module Tapyrus
|
|
503
465
|
end
|
504
466
|
when OP_RETURN
|
505
467
|
return set_error(SCRIPT_ERR_OP_RETURN)
|
468
|
+
when OP_COLOR
|
469
|
+
# Color id is not permitted in p2sh redeem script
|
470
|
+
return set_error(SCRIPT_ERR_OP_COLOR_UNEXPECTED) if is_redeem_script
|
471
|
+
|
472
|
+
# if Color id is already initialized this must be an extra
|
473
|
+
return set_error(SCRIPT_ERR_OP_COLOR_MULTIPLE) if color_id && color_id.type != Tapyrus::Color::TokenTypes::NONE
|
474
|
+
|
475
|
+
# color id is not allowed inside OP_IF
|
476
|
+
return set_error(SCRIPT_ERR_OP_COLOR_IN_BRANCH) unless flow_stack.empty?
|
477
|
+
|
478
|
+
# pop one stack element and verify that it exists
|
479
|
+
return set_error(SCRIPT_ERR_INVALID_STACK_OPERATION) if stack.size() < 1
|
480
|
+
|
481
|
+
color_id = Tapyrus::Color::ColorIdentifier.parse_from_payload(stack.last.htb)
|
482
|
+
|
483
|
+
# check ColorIdentifier is valid
|
484
|
+
return set_error(SCRIPT_ERR_OP_COLOR_ID_INVALID) unless color_id.valid?
|
485
|
+
|
486
|
+
stack.pop
|
506
487
|
else
|
507
488
|
return set_error(SCRIPT_ERR_BAD_OPCODE)
|
508
489
|
end
|
@@ -586,20 +567,26 @@ module Tapyrus
|
|
586
567
|
end
|
587
568
|
end
|
588
569
|
|
589
|
-
def
|
570
|
+
def check_ecdsa_signature_encoding(sig, data_sig = false)
|
590
571
|
return true if sig.size.zero?
|
591
|
-
if
|
572
|
+
if !Key.valid_signature_encoding?(sig.htb, data_sig)
|
592
573
|
return set_error(SCRIPT_ERR_SIG_DER)
|
593
|
-
elsif
|
574
|
+
elsif !low_der_signature?(sig, data_sig)
|
594
575
|
return false
|
595
|
-
elsif
|
576
|
+
elsif !data_sig && !defined_hashtype_signature?(sig)
|
596
577
|
return set_error(SCRIPT_ERR_SIG_HASHTYPE)
|
597
578
|
end
|
598
579
|
true
|
599
580
|
end
|
600
581
|
|
601
|
-
def
|
602
|
-
return
|
582
|
+
def check_schnorr_signature_encoding(sig, data_sig = false)
|
583
|
+
return false unless sig.htb.bytesize == (data_sig ? 64 : 65)
|
584
|
+
return set_error(SCRIPT_ERR_SIG_HASHTYPE) if !data_sig && !defined_hashtype_signature?(sig)
|
585
|
+
true
|
586
|
+
end
|
587
|
+
|
588
|
+
def low_der_signature?(sig, data_sig = false)
|
589
|
+
return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb, data_sig)
|
603
590
|
return set_error(SCRIPT_ERR_SIG_HIGH_S) unless Key.low_signature?(sig.htb)
|
604
591
|
true
|
605
592
|
end
|
@@ -609,20 +596,12 @@ module Tapyrus
|
|
609
596
|
return false if sig.empty?
|
610
597
|
s = sig.unpack('C*')
|
611
598
|
hash_type = s[-1] & (~(SIGHASH_TYPE[:anyonecanpay]))
|
612
|
-
hash_type &= (~(Tapyrus::SIGHASH_FORK_ID)) if Tapyrus.chain_params.fork_chain? # for fork coin.
|
613
599
|
return false if hash_type < SIGHASH_TYPE[:all] || hash_type > SIGHASH_TYPE[:single]
|
614
600
|
true
|
615
601
|
end
|
616
602
|
|
617
|
-
def check_pubkey_encoding(pubkey
|
618
|
-
|
619
|
-
return set_error(SCRIPT_ERR_PUBKEYTYPE)
|
620
|
-
end
|
621
|
-
# Only compressed keys are accepted in segwit
|
622
|
-
if flag?(SCRIPT_VERIFY_WITNESS_PUBKEYTYPE) &&
|
623
|
-
sig_version == :witness_v0 && !Key.compress_pubkey?(pubkey)
|
624
|
-
return set_error(SCRIPT_ERR_WITNESS_PUBKEYTYPE)
|
625
|
-
end
|
603
|
+
def check_pubkey_encoding(pubkey)
|
604
|
+
return set_error(SCRIPT_ERR_PUBKEYTYPE) unless Key.compress_or_uncompress_pubkey?(pubkey)
|
626
605
|
true
|
627
606
|
end
|
628
607
|
|