tapyrus 0.3.1 → 0.3.3
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 +1 -1
- data/exe/tapyrus-script-debugger +71 -0
- data/lib/tapyrus/block_header.rb +1 -1
- data/lib/tapyrus/constants.rb +0 -2
- 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/script_interpreter.rb +441 -432
- data/lib/tapyrus/script/tx_checker.rb +5 -9
- data/lib/tapyrus/tx.rb +8 -17
- data/lib/tapyrus/tx_builder.rb +10 -5
- data/lib/tapyrus/version.rb +1 -1
- data/lib/tapyrus.rb +2 -0
- data/tapyrusrb.gemspec +1 -0
- metadata +22 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe8d938cb082800dbacfbdbebc6ab00d14a40693a92a4df46f967d0535f9e337
|
4
|
+
data.tar.gz: b6a51b4a565435d2a5a4f65d6fbcec3420464d6067742c64d00651919de22d6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c2ead3ceffe67d128f94af033ff788fbb15a283416af5ed2afe915ad8af6dc34e36e6e5ad7e21fa94ea2137e8cde1441a3b15228402b46243b582e21ab0dea1d
|
7
|
+
data.tar.gz: 1dbefaca1c96fa263ba870f8f892f33a1161c8d7ba0e2ecedad1aea3af98112cb8836e03f85201a1c2d0febe010b49f73e4497195eca90e157081d4a049f265e
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Tapyrusrb [](https://github.com/chaintope/tapyrusrb/actions/workflows/ruby.yml) [](https://badge.fury.io/rb/tapyrus) [](LICENSE)
|
2
2
|
|
3
3
|
Tapyrusrb is a Ruby implementation of [Tapyrus](https://github.com/chaintope/tapyrus-core) Protocol.
|
4
4
|
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'tapyrus'
|
4
|
+
require 'terminal-table'
|
5
|
+
|
6
|
+
|
7
|
+
print "Enter scriptPubkey: "
|
8
|
+
script_pubkey_hex = gets.chomp
|
9
|
+
script_pubkey = Tapyrus::Script.parse_from_payload(script_pubkey_hex.htb)
|
10
|
+
puts script_pubkey unless script_pubkey_hex.empty?
|
11
|
+
|
12
|
+
print "Enter scriptSig: "
|
13
|
+
script_sig_hex = gets.chomp
|
14
|
+
script_sig = Tapyrus::Script.parse_from_payload(script_sig_hex.htb)
|
15
|
+
puts script_sig unless script_sig_hex.empty?
|
16
|
+
|
17
|
+
unless script_sig.push_only?
|
18
|
+
warn "scriptSig has non-push opcode."
|
19
|
+
puts
|
20
|
+
end
|
21
|
+
|
22
|
+
if script_pubkey_hex.empty? && script_sig.empty?
|
23
|
+
puts "Empty script."
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
print "Enter tx: "
|
28
|
+
tx_hex = gets.chomp
|
29
|
+
tx = nil
|
30
|
+
input_index = nil
|
31
|
+
unless tx_hex.length == 0
|
32
|
+
print "Enter index of the input: "
|
33
|
+
input_index = gets.chomp
|
34
|
+
begin
|
35
|
+
tx = Tapyrus::Tx.parse_from_payload(tx_hex.htb)
|
36
|
+
rescue StandardError
|
37
|
+
warn "Invalid tx data."
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
if input_index.empty?
|
41
|
+
warn "Index of input missing."
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
input_index = input_index.to_i
|
45
|
+
end
|
46
|
+
|
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
|
53
|
+
|
54
|
+
puts "The Script is ready to be executed; you can step execution it by putting the Enter key."
|
55
|
+
print "> "
|
56
|
+
while cmd = gets.chomp
|
57
|
+
if cmd.length == 0
|
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
|
68
|
+
end
|
69
|
+
end
|
70
|
+
print "> "
|
71
|
+
end
|
data/lib/tapyrus/block_header.rb
CHANGED
@@ -104,7 +104,7 @@ module Tapyrus
|
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
# Check this header contains upgrade aggregated
|
107
|
+
# Check this header contains upgrade aggregated public key.
|
108
108
|
# @return [Boolean] if contains return true, otherwise false.
|
109
109
|
def upgrade_agg_pubkey?
|
110
110
|
x_field_type == X_FILED_TYPES[:aggregate_pubkey]
|
data/lib/tapyrus/constants.rb
CHANGED
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
|