tapyrus 0.3.2 → 0.3.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/exe/tapyrus-script-debugger +21 -81
- data/lib/tapyrus/errors.rb +1 -0
- data/lib/tapyrus/ext_key.rb +6 -3
- data/lib/tapyrus/key.rb +5 -1
- data/lib/tapyrus/script/debugger.rb +164 -0
- data/lib/tapyrus/script/tx_checker.rb +0 -3
- data/lib/tapyrus/version.rb +1 -1
- data/lib/tapyrus.rb +2 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b6715191d7f10eb9d2d6851660e71abcf1aa9866b2568d15774faa22af6e31d
|
4
|
+
data.tar.gz: e224860705d15fe929011d56db6d1ea959f13eadb32dcb915c859db5751ddf00
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b96b08953e986ad0db52c4885df2315ee0c69748e19e9486c80898886ec74f763158b71dc972881f7831a099f91f896ba21d3d04552d258dcbc6057d9d56f08a
|
7
|
+
data.tar.gz: 84a9558443d75e15768d4f485dbff148fb8ac4014574e41a49050197f4d0eb18dfb5a3b5f0d3945d56a7da53c056c2db84a722ed9aed1b25df9da62be2f98aac
|
data/exe/tapyrus-script-debugger
CHANGED
@@ -3,32 +3,6 @@
|
|
3
3
|
require 'tapyrus'
|
4
4
|
require 'terminal-table'
|
5
5
|
|
6
|
-
class EmptyTxChecker
|
7
|
-
def check_sig(script_sig, pubkey, script_code)
|
8
|
-
warn "Signature verification failed. You need to enter tx and input index."
|
9
|
-
exit
|
10
|
-
end
|
11
|
-
def verify_sig(sig, pubkey, digest, allow_hybrid: false)
|
12
|
-
warn "Signature verification failed. You need to enter tx and input index."
|
13
|
-
exit
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def run_step(interpreter, script, chunk, index, is_redeem_script)
|
18
|
-
if chunk.pushdata?
|
19
|
-
puts "PUSH #{chunk.pushed_data.bth}"
|
20
|
-
else
|
21
|
-
puts "APPLY #{Tapyrus::Opcodes.opcode_to_name(chunk.opcode)}"
|
22
|
-
end
|
23
|
-
result = interpreter.next_step(script, chunk, index, is_redeem_script)
|
24
|
-
if result.is_a?(FalseClass)
|
25
|
-
warn "script failed. Reason: #{interpreter.error}"
|
26
|
-
exit
|
27
|
-
end
|
28
|
-
rows = interpreter.stack.map{|s|[s]}.reverse
|
29
|
-
table = Terminal::Table.new(title: 'Current Stack', rows: rows )
|
30
|
-
puts table
|
31
|
-
end
|
32
6
|
|
33
7
|
print "Enter scriptPubkey: "
|
34
8
|
script_pubkey_hex = gets.chomp
|
@@ -52,14 +26,13 @@ end
|
|
52
26
|
|
53
27
|
print "Enter tx: "
|
54
28
|
tx_hex = gets.chomp
|
55
|
-
|
56
|
-
|
57
|
-
|
29
|
+
tx = nil
|
30
|
+
input_index = nil
|
31
|
+
unless tx_hex.length == 0
|
58
32
|
print "Enter index of the input: "
|
59
33
|
input_index = gets.chomp
|
60
|
-
tx_checker = Tapyrus::TxChecker.new
|
61
34
|
begin
|
62
|
-
|
35
|
+
tx = Tapyrus::Tx.parse_from_payload(tx_hex.htb)
|
63
36
|
rescue StandardError
|
64
37
|
warn "Invalid tx data."
|
65
38
|
exit
|
@@ -68,64 +41,31 @@ else
|
|
68
41
|
warn "Index of input missing."
|
69
42
|
exit
|
70
43
|
end
|
71
|
-
|
72
|
-
if (tx_checker.tx.in.size - 1) < tx_checker.input_index
|
73
|
-
warn "Tx does not have #{tx_checker.input_index}-th input."
|
74
|
-
exit
|
75
|
-
end
|
44
|
+
input_index = input_index.to_i
|
76
45
|
end
|
77
46
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
chunk_index = 0
|
85
|
-
chunks_size = target_script.chunks.length
|
86
|
-
stack_copy = nil
|
47
|
+
begin
|
48
|
+
debugger = Tapyrus::ScriptDebugger.new(script_pubkey: script_pubkey, script_sig: script_sig, tx: tx, index: input_index)
|
49
|
+
rescue ArgumentError => e
|
50
|
+
warn e.message
|
51
|
+
exit
|
52
|
+
end
|
87
53
|
|
88
54
|
puts "The Script is ready to be executed; you can step execution it by putting the Enter key."
|
89
55
|
print "> "
|
90
56
|
while cmd = gets.chomp
|
91
57
|
if cmd.length == 0
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
warn "Script evaluated without error but finished with a false/empty top stack element"
|
103
|
-
exit
|
104
|
-
end
|
105
|
-
if script_pubkey.p2sh?
|
106
|
-
interpreter.stack = stack_copy
|
107
|
-
redeem_script = Tapyrus::Script.parse_from_payload(interpreter.stack.pop.htb)
|
108
|
-
puts "APPLY P2SH: #{redeem_script}"
|
109
|
-
rows = interpreter.stack.map{|s|[s]}.reverse
|
110
|
-
table = Terminal::Table.new(title: 'Current Stack', rows: rows )
|
111
|
-
puts table
|
112
|
-
target_script = redeem_script
|
113
|
-
chunks = target_script.chunks.each
|
114
|
-
chunk_index = 0
|
115
|
-
chunks_size = target_script.chunks.length
|
116
|
-
else
|
117
|
-
puts "Execution finished."
|
118
|
-
exit
|
119
|
-
end
|
120
|
-
else
|
121
|
-
puts "Execution finished."
|
122
|
-
exit
|
123
|
-
end
|
58
|
+
result = debugger.step
|
59
|
+
if result.halt?
|
60
|
+
puts result.message if result.message
|
61
|
+
warn result.error
|
62
|
+
exit
|
63
|
+
elsif result.finished?
|
64
|
+
puts "Execution finished."
|
65
|
+
exit
|
66
|
+
else
|
67
|
+
result.print_result
|
124
68
|
end
|
125
|
-
run_step(interpreter, target_script, chunks.next, chunk_index, is_redeem)
|
126
|
-
chunk_index += 1
|
127
|
-
else
|
128
|
-
puts "Put enter key to step execution or Ctrl+D to exit."
|
129
69
|
end
|
130
70
|
print "> "
|
131
71
|
end
|
data/lib/tapyrus/errors.rb
CHANGED
data/lib/tapyrus/ext_key.rb
CHANGED
@@ -25,7 +25,8 @@ module Tapyrus
|
|
25
25
|
l = Tapyrus.hmac_sha512('Tapyrus seed', seed.htb)
|
26
26
|
left = l[0..31].bth.to_i(16)
|
27
27
|
raise 'invalid key' if left >= CURVE_ORDER || left == 0
|
28
|
-
|
28
|
+
l_priv = ECDSA::Format::IntegerOctetString.encode(left, 32)
|
29
|
+
ext_key.key = Tapyrus::Key.new(priv_key: l_priv.bth, key_type: Tapyrus::Key::TYPES[:compressed])
|
29
30
|
ext_key.chain_code = l[32..-1]
|
30
31
|
ext_key
|
31
32
|
end
|
@@ -107,7 +108,8 @@ module Tapyrus
|
|
107
108
|
raise 'invalid key' if left >= CURVE_ORDER
|
108
109
|
child_priv = (left + key.priv_key.to_i(16)) % CURVE_ORDER
|
109
110
|
raise 'invalid key ' if child_priv >= CURVE_ORDER
|
110
|
-
|
111
|
+
child_priv = ECDSA::Format::IntegerOctetString.encode(child_priv, 32)
|
112
|
+
new_key.key = Tapyrus::Key.new(priv_key: child_priv.bth, key_type: key_type)
|
111
113
|
new_key.chain_code = l[32..-1]
|
112
114
|
new_key.ver = version
|
113
115
|
new_key
|
@@ -275,7 +277,8 @@ module Tapyrus
|
|
275
277
|
l = Tapyrus.hmac_sha512(chain_code, data)
|
276
278
|
left = l[0..31].bth.to_i(16)
|
277
279
|
raise 'invalid key' if left >= CURVE_ORDER
|
278
|
-
|
280
|
+
l_priv = ECDSA::Format::IntegerOctetString.encode(left, 32)
|
281
|
+
p1 = Tapyrus::Key.new(priv_key: l_priv.bth, key_type: Tapyrus::Key::TYPES[:uncompressed]).to_point
|
279
282
|
p2 = Tapyrus::Key.new(pubkey: pubkey, key_type: key_type).to_point
|
280
283
|
new_key.pubkey = (p1 + p2).to_hex
|
281
284
|
new_key.chain_code = l[32..-1]
|
data/lib/tapyrus/key.rb
CHANGED
@@ -41,7 +41,11 @@ module Tapyrus
|
|
41
41
|
end
|
42
42
|
@secp256k1_module = Tapyrus.secp_impl
|
43
43
|
@priv_key = priv_key
|
44
|
-
|
44
|
+
|
45
|
+
if @priv_key
|
46
|
+
raise ArgumentError, Errors::Messages::INVALID_PRIV_KEY unless validate_private_key_range(@priv_key)
|
47
|
+
raise ArgumentError, Errors::Messages::INVALID_PRIV_LENGTH unless @priv_key.htb.bytesize == 32
|
48
|
+
end
|
45
49
|
if pubkey
|
46
50
|
@pubkey = pubkey
|
47
51
|
else
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Tapyrus
|
2
|
+
class ScriptDebugger
|
3
|
+
attr_reader :script_pubkey
|
4
|
+
attr_reader :script_sig
|
5
|
+
attr_reader :tx_checker
|
6
|
+
attr_reader :interpreter
|
7
|
+
|
8
|
+
attr_accessor :target_script
|
9
|
+
attr_accessor :is_redeem
|
10
|
+
attr_accessor :chunk_index
|
11
|
+
attr_accessor :chunk_size
|
12
|
+
attr_accessor :chunks
|
13
|
+
attr_accessor :stack_copy
|
14
|
+
|
15
|
+
# @param [Tapyrus::Script] script_pubkey
|
16
|
+
# @param [Tapyrus::Script] script_sig
|
17
|
+
# @param [Tapyrus::Tx] tx (optional)
|
18
|
+
# @param [Integer] index (optional) input index
|
19
|
+
# @raise [ArgumentError]
|
20
|
+
def initialize(script_pubkey:, script_sig:, tx: nil, index: nil)
|
21
|
+
@script_pubkey = script_pubkey
|
22
|
+
@script_sig = script_sig
|
23
|
+
if tx
|
24
|
+
raise ArgumentError, 'index should be specified' if index.nil?
|
25
|
+
@tx_checker = Tapyrus::TxChecker.new(tx: tx, input_index: index)
|
26
|
+
if (tx_checker.tx.in.size - 1) < tx_checker.input_index
|
27
|
+
raise ArgumentError, "Tx does not have #{tx_checker.input_index}-th input."
|
28
|
+
end
|
29
|
+
else
|
30
|
+
@tx_checker = EmptyTxChecker.new
|
31
|
+
end
|
32
|
+
@interpreter = Tapyrus::ScriptInterpreter.new(checker: tx_checker)
|
33
|
+
@interpreter.reset_params
|
34
|
+
@chunk_index = 0
|
35
|
+
@target_script = script_sig
|
36
|
+
@chunks = target_script.chunks.each
|
37
|
+
@chunk_size = target_script.chunks.length
|
38
|
+
@stack_copy = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def step
|
42
|
+
if chunk_size == chunk_index
|
43
|
+
if target_script == script_sig
|
44
|
+
@stack_copy = interpreter.stack.dup
|
45
|
+
@target_script = script_pubkey
|
46
|
+
@interpreter.reset_params
|
47
|
+
@chunks = target_script.chunks.each
|
48
|
+
@chunk_index = 0
|
49
|
+
@chunk_size = target_script.chunks.length
|
50
|
+
elsif target_script == script_pubkey
|
51
|
+
if interpreter.stack.empty? || !interpreter.cast_to_bool(interpreter.stack.last.htb)
|
52
|
+
return(
|
53
|
+
StepResult.error(
|
54
|
+
current_stack: interpreter.stack.dup,
|
55
|
+
error: 'Script evaluated without error but finished with a false/empty top stack element'
|
56
|
+
)
|
57
|
+
)
|
58
|
+
end
|
59
|
+
if script_pubkey.p2sh?
|
60
|
+
interpreter.stack = stack_copy
|
61
|
+
redeem_script = Tapyrus::Script.parse_from_payload(interpreter.stack.pop.htb)
|
62
|
+
@target_script = redeem_script
|
63
|
+
@chunks = target_script.chunks.each
|
64
|
+
@chunk_index = 0
|
65
|
+
@chunk_size = target_script.chunks.length
|
66
|
+
else
|
67
|
+
return StepResult.finished(current_stack: interpreter.stack.dup)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
return StepResult.finished(current_stack: interpreter.stack.dup)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
result = step_process(interpreter, target_script, chunks.next, chunk_index, is_redeem)
|
74
|
+
return result if result.is_a?(StepResult)
|
75
|
+
@chunk_index += 1
|
76
|
+
StepResult.success(current_stack: interpreter.stack.dup, message: result)
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def step_process(interpreter, script, chunk, index, is_redeem_script)
|
82
|
+
message =
|
83
|
+
chunk.pushdata? ? "PUSH #{chunk.pushed_data.bth}" : "APPLY #{Tapyrus::Opcodes.opcode_to_name(chunk.opcode)}"
|
84
|
+
begin
|
85
|
+
result = interpreter.next_step(script, chunk, index, is_redeem_script)
|
86
|
+
if result.is_a?(FalseClass)
|
87
|
+
return(
|
88
|
+
StepResult.error(current_stack: interpreter.stack.dup, error: "script failed. Reason: #{interpreter.error}")
|
89
|
+
)
|
90
|
+
end
|
91
|
+
message
|
92
|
+
rescue TxUnspecifiedError => e
|
93
|
+
return StepResult.error(current_stack: interpreter.stack.dup, error: e.message, message: message)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class StepResult
|
99
|
+
STATUS_RUNNING = 'running'
|
100
|
+
STATUS_HALT = 'halt'
|
101
|
+
STATUS_FINISHED = 'finished'
|
102
|
+
|
103
|
+
STATUSES = [STATUS_RUNNING, STATUS_HALT, STATUS_FINISHED]
|
104
|
+
|
105
|
+
attr_reader :status
|
106
|
+
attr_reader :current_stack
|
107
|
+
attr_reader :message
|
108
|
+
attr_reader :error
|
109
|
+
|
110
|
+
# @param [String] status
|
111
|
+
# @param [Array] current_stack
|
112
|
+
# @param [String] message
|
113
|
+
# @param [String] error an error message.
|
114
|
+
def initialize(status, current_stack: [], message: nil, error: nil)
|
115
|
+
raise ArgumentError, 'Unsupported status specified.' unless STATUSES.include?(status)
|
116
|
+
@status = status
|
117
|
+
@current_stack = current_stack
|
118
|
+
@error = error
|
119
|
+
@message = message
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.error(current_stack: [], error:, message: nil)
|
123
|
+
StepResult.new(STATUS_HALT, current_stack: current_stack, error: error, message: message)
|
124
|
+
end
|
125
|
+
|
126
|
+
def self.success(current_stack: [], message: nil)
|
127
|
+
StepResult.new(STATUS_RUNNING, current_stack: current_stack, message: message)
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.finished(current_stack: [])
|
131
|
+
StepResult.new(STATUS_FINISHED, current_stack: current_stack)
|
132
|
+
end
|
133
|
+
|
134
|
+
def halt?
|
135
|
+
status == STATUS_HALT
|
136
|
+
end
|
137
|
+
|
138
|
+
def finished?
|
139
|
+
status == STATUS_FINISHED
|
140
|
+
end
|
141
|
+
|
142
|
+
def stack_table
|
143
|
+
rows = current_stack.map { |s| [s] }.reverse
|
144
|
+
Terminal::Table.new(title: 'Current Stack', rows: rows)
|
145
|
+
end
|
146
|
+
|
147
|
+
def print_result
|
148
|
+
puts message if message
|
149
|
+
puts stack_table
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
class TxUnspecifiedError < StandardError
|
154
|
+
end
|
155
|
+
|
156
|
+
class EmptyTxChecker
|
157
|
+
def check_sig(script_sig, pubkey, script_code)
|
158
|
+
raise TxUnspecifiedError, 'Signature verification failed. You need to enter tx and input index.'
|
159
|
+
end
|
160
|
+
def verify_sig(sig, pubkey, digest, allow_hybrid: false)
|
161
|
+
raise TxUnspecifiedError, 'Signature verification failed. You need to enter tx and input index.'
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -67,9 +67,6 @@ module Tapyrus
|
|
67
67
|
def check_sequence(sequence)
|
68
68
|
tx_sequence = tx.inputs[input_index].sequence
|
69
69
|
|
70
|
-
# Fail if the transaction's version number is not set high enough to trigger BIP 68 rules.
|
71
|
-
return false if tx.features < 2
|
72
|
-
|
73
70
|
# Sequence numbers with their most significant bit set are not consensus constrained.
|
74
71
|
# Testing that the transaction's sequence number do not have this bit set prevents using this property to get around a CHECKSEQUENCEVERIFY check.
|
75
72
|
return false unless tx_sequence & TxIn::SEQUENCE_LOCKTIME_DISABLE_FLAG == 0
|
data/lib/tapyrus/version.rb
CHANGED
data/lib/tapyrus.rb
CHANGED
@@ -24,6 +24,7 @@ module Tapyrus
|
|
24
24
|
autoload :Script, 'tapyrus/script/script'
|
25
25
|
autoload :Multisig, 'tapyrus/script/multisig'
|
26
26
|
autoload :ScriptInterpreter, 'tapyrus/script/script_interpreter'
|
27
|
+
autoload :ScriptDebugger, 'tapyrus/script/debugger'
|
27
28
|
autoload :ScriptError, 'tapyrus/script/script_error'
|
28
29
|
autoload :TxChecker, 'tapyrus/script/tx_checker'
|
29
30
|
autoload :TxIn, 'tapyrus/tx_in'
|
@@ -50,6 +51,7 @@ module Tapyrus
|
|
50
51
|
autoload :Errors, 'tapyrus/errors'
|
51
52
|
autoload :TxBuilder, 'tapyrus/tx_builder'
|
52
53
|
autoload :BIP175, 'tapyrus/bip175'
|
54
|
+
autoload :Contract, 'tapyrus/contract'
|
53
55
|
|
54
56
|
require_relative 'tapyrus/constants'
|
55
57
|
require_relative 'tapyrus/ext/ecdsa'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tapyrus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-06-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ecdsa
|
@@ -405,6 +405,7 @@ files:
|
|
405
405
|
- lib/tapyrus/rpc/request_handler.rb
|
406
406
|
- lib/tapyrus/rpc/tapyrus_core_client.rb
|
407
407
|
- lib/tapyrus/script/color.rb
|
408
|
+
- lib/tapyrus/script/debugger.rb
|
408
409
|
- lib/tapyrus/script/multisig.rb
|
409
410
|
- lib/tapyrus/script/script.rb
|
410
411
|
- lib/tapyrus/script/script_error.rb
|
@@ -456,7 +457,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
456
457
|
- !ruby/object:Gem::Version
|
457
458
|
version: '0'
|
458
459
|
requirements: []
|
459
|
-
rubygems_version: 3.3.
|
460
|
+
rubygems_version: 3.3.23
|
460
461
|
signing_key:
|
461
462
|
specification_version: 4
|
462
463
|
summary: The implementation of Tapyrus Protocol for Ruby.
|