btcruby 1.1.3 → 1.1.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/RELEASE_NOTES.md +8 -0
- data/lib/btcruby.rb +1 -0
- data/lib/btcruby/script/opcode.rb +163 -156
- data/lib/btcruby/script/script.rb +8 -201
- data/lib/btcruby/script/script_chunk.rb +216 -0
- data/lib/btcruby/version.rb +1 -1
- data/spec/script_spec.rb +7 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 496c31ea05b95ca3fe1971a17eea51f43a2c00b6
|
4
|
+
data.tar.gz: 6a8cbbe43cd39b004f188f32c90e6c492d7f78d2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5de016754bc4fdec389d62e4fbce3b3f104a00eb698fb1647c02c7531223cfb41066e3c13eb0a286cb91ba59950493af8edd9c061f427c0317cc424b699b3e43
|
7
|
+
data.tar.gz: 4514a56b1c78db5e1e3beaf3fc5983b964451bf4a007adeb359d2c16b05e0e4b291ecdf06917b689446f33ec22ef5bf9a36cfd6496d2bc37a955896a01606e23
|
data/RELEASE_NOTES.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
BTCRuby Release Notes
|
3
3
|
=====================
|
4
4
|
|
5
|
+
1.1.4 (August 18, 2015)
|
6
|
+
-----------------------
|
7
|
+
|
8
|
+
* Public API for `ScriptChunk` instances.
|
9
|
+
* Added `ScriptChunk#data_only?`.
|
10
|
+
* Added `ScriptChunk#interpreted_data`.
|
11
|
+
|
12
|
+
|
5
13
|
1.1.3 (August 17, 2015)
|
6
14
|
-----------------------
|
7
15
|
|
data/lib/btcruby.rb
CHANGED
@@ -32,6 +32,7 @@ require_relative 'btcruby/validation.rb'
|
|
32
32
|
require_relative 'btcruby/script/script_error.rb'
|
33
33
|
require_relative 'btcruby/script/script_flags.rb'
|
34
34
|
require_relative 'btcruby/script/script_number.rb'
|
35
|
+
require_relative 'btcruby/script/script_chunk.rb'
|
35
36
|
require_relative 'btcruby/script/script.rb'
|
36
37
|
require_relative 'btcruby/script/script_interpreter.rb'
|
37
38
|
require_relative 'btcruby/script/opcode.rb'
|
@@ -38,161 +38,168 @@ module BTC
|
|
38
38
|
return nil
|
39
39
|
end
|
40
40
|
end
|
41
|
-
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
41
|
+
|
42
|
+
# All opcodes are defined in Opcodes module so they can be included in
|
43
|
+
# a different namespace without the baggage of other BTC identifiers.
|
44
|
+
module Opcodes
|
45
|
+
|
46
|
+
# 1. Operators pushing data on stack.
|
47
|
+
|
48
|
+
# Push 1 byte 0x00 on the stack
|
49
|
+
OP_FALSE = 0x00
|
50
|
+
OP_0 = 0x00
|
51
|
+
|
52
|
+
# Any opcode with value < PUSHDATA1 is a length of the string to be pushed on the stack.
|
53
|
+
# So opcode 0x01 is followed by 1 byte of data, 0x09 by 9 bytes and so on up to 0x4b (75 bytes)
|
54
|
+
|
55
|
+
# PUSHDATA<N> opcode is followed by N-byte length of the string that follows.
|
56
|
+
OP_PUSHDATA1 = 0x4c # followed by a 1-byte length of the string to push (allows pushing 0..255 bytes).
|
57
|
+
OP_PUSHDATA2 = 0x4d # followed by a 2-byte length of the string to push (allows pushing 0..65535 bytes).
|
58
|
+
OP_PUSHDATA4 = 0x4e # followed by a 4-byte length of the string to push (allows pushing 0..4294967295 bytes).
|
59
|
+
OP_1NEGATE = 0x4f # pushes -1 number on the stack
|
60
|
+
OP_RESERVED = 0x50 # Not assigned. If executed, transaction is invalid.
|
61
|
+
|
62
|
+
# OP_<N> pushes number <N> on the stack
|
63
|
+
OP_TRUE = 0x51
|
64
|
+
OP_1 = 0x51
|
65
|
+
OP_2 = 0x52
|
66
|
+
OP_3 = 0x53
|
67
|
+
OP_4 = 0x54
|
68
|
+
OP_5 = 0x55
|
69
|
+
OP_6 = 0x56
|
70
|
+
OP_7 = 0x57
|
71
|
+
OP_8 = 0x58
|
72
|
+
OP_9 = 0x59
|
73
|
+
OP_10 = 0x5a
|
74
|
+
OP_11 = 0x5b
|
75
|
+
OP_12 = 0x5c
|
76
|
+
OP_13 = 0x5d
|
77
|
+
OP_14 = 0x5e
|
78
|
+
OP_15 = 0x5f
|
79
|
+
OP_16 = 0x60
|
80
|
+
|
81
|
+
# 2. Control flow operators
|
82
|
+
|
83
|
+
OP_NOP = 0x61 # Does nothing
|
84
|
+
OP_VER = 0x62 # Not assigned. If executed, transaction is invalid.
|
85
|
+
|
86
|
+
# BitcoinQT executes all operators from OP_IF to OP_ENDIF even inside "non-executed" branch (to keep track of nesting).
|
87
|
+
# Since OP_VERIF and OP_VERNOTIF are not assigned, even inside a non-executed branch they will fall in "default:" switch case
|
88
|
+
# and cause the script to fail. Some other ops like OP_VER can be present inside non-executed branch because they'll be skipped.
|
89
|
+
OP_IF = 0x63 # If the top stack value is not 0, the statements are executed. The top stack value is removed.
|
90
|
+
OP_NOTIF = 0x64 # If the top stack value is 0, the statements are executed. The top stack value is removed.
|
91
|
+
OP_VERIF = 0x65 # Not assigned. Script is invalid with that opcode (even if inside non-executed branch).
|
92
|
+
OP_VERNOTIF = 0x66 # Not assigned. Script is invalid with that opcode (even if inside non-executed branch).
|
93
|
+
OP_ELSE = 0x67 # Executes code if the previous OP_IF or OP_NOTIF was not executed.
|
94
|
+
OP_ENDIF = 0x68 # Finishes if/else block
|
95
|
+
|
96
|
+
OP_VERIFY = 0x69 # Removes item from the stack if it's not 0x00 or 0x80 (negative zero). Otherwise, marks script as invalid.
|
97
|
+
OP_RETURN = 0x6a # Marks transaction as invalid.
|
98
|
+
|
99
|
+
# Stack ops
|
100
|
+
OP_TOALTSTACK = 0x6b # Moves item from the stack to altstack
|
101
|
+
OP_FROMALTSTACK = 0x6c # Moves item from the altstack to stack
|
102
|
+
OP_2DROP = 0x6d
|
103
|
+
OP_2DUP = 0x6e
|
104
|
+
OP_3DUP = 0x6f
|
105
|
+
OP_2OVER = 0x70
|
106
|
+
OP_2ROT = 0x71
|
107
|
+
OP_2SWAP = 0x72
|
108
|
+
OP_IFDUP = 0x73
|
109
|
+
OP_DEPTH = 0x74
|
110
|
+
OP_DROP = 0x75
|
111
|
+
OP_DUP = 0x76
|
112
|
+
OP_NIP = 0x77
|
113
|
+
OP_OVER = 0x78
|
114
|
+
OP_PICK = 0x79
|
115
|
+
OP_ROLL = 0x7a
|
116
|
+
OP_ROT = 0x7b
|
117
|
+
OP_SWAP = 0x7c
|
118
|
+
OP_TUCK = 0x7d
|
119
|
+
|
120
|
+
# Splice ops
|
121
|
+
OP_CAT = 0x7e # Disabled opcode. If executed, transaction is invalid.
|
122
|
+
OP_SUBSTR = 0x7f # Disabled opcode. If executed, transaction is invalid.
|
123
|
+
OP_LEFT = 0x80 # Disabled opcode. If executed, transaction is invalid.
|
124
|
+
OP_RIGHT = 0x81 # Disabled opcode. If executed, transaction is invalid.
|
125
|
+
OP_SIZE = 0x82
|
126
|
+
|
127
|
+
# Bit logic
|
128
|
+
OP_INVERT = 0x83 # Disabled opcode. If executed, transaction is invalid.
|
129
|
+
OP_AND = 0x84 # Disabled opcode. If executed, transaction is invalid.
|
130
|
+
OP_OR = 0x85 # Disabled opcode. If executed, transaction is invalid.
|
131
|
+
OP_XOR = 0x86 # Disabled opcode. If executed, transaction is invalid.
|
132
|
+
|
133
|
+
OP_EQUAL = 0x87 # Last two items are removed from the stack and compared. Result (true or false) is pushed to the stack.
|
134
|
+
OP_EQUALVERIFY = 0x88 # Same as OP_EQUAL, but removes the result from the stack if it's true or marks script as invalid.
|
135
|
+
|
136
|
+
OP_RESERVED1 = 0x89 # Disabled opcode. If executed, transaction is invalid.
|
137
|
+
OP_RESERVED2 = 0x8a # Disabled opcode. If executed, transaction is invalid.
|
138
|
+
|
139
|
+
# Numeric
|
140
|
+
OP_1ADD = 0x8b # adds 1 to last item, pops it from stack and pushes result.
|
141
|
+
OP_1SUB = 0x8c # substracts 1 to last item, pops it from stack and pushes result.
|
142
|
+
OP_2MUL = 0x8d # Disabled opcode. If executed, transaction is invalid.
|
143
|
+
OP_2DIV = 0x8e # Disabled opcode. If executed, transaction is invalid.
|
144
|
+
OP_NEGATE = 0x8f # negates the number, pops it from stack and pushes result.
|
145
|
+
OP_ABS = 0x90 # replaces number with its absolute value
|
146
|
+
OP_NOT = 0x91 # replaces number with True if it's zero, False otherwise.
|
147
|
+
OP_0NOTEQUAL = 0x92 # replaces number with True if it's not zero, False otherwise.
|
148
|
+
|
149
|
+
OP_ADD = 0x93 # (x y -- x+y)
|
150
|
+
OP_SUB = 0x94 # (x y -- x-y)
|
151
|
+
OP_MUL = 0x95 # Disabled opcode. If executed, transaction is invalid.
|
152
|
+
OP_DIV = 0x96 # Disabled opcode. If executed, transaction is invalid.
|
153
|
+
OP_MOD = 0x97 # Disabled opcode. If executed, transaction is invalid.
|
154
|
+
OP_LSHIFT = 0x98 # Disabled opcode. If executed, transaction is invalid.
|
155
|
+
OP_RSHIFT = 0x99 # Disabled opcode. If executed, transaction is invalid.
|
156
|
+
|
157
|
+
OP_BOOLAND = 0x9a
|
158
|
+
OP_BOOLOR = 0x9b
|
159
|
+
OP_NUMEQUAL = 0x9c
|
160
|
+
OP_NUMEQUALVERIFY = 0x9d
|
161
|
+
OP_NUMNOTEQUAL = 0x9e
|
162
|
+
OP_LESSTHAN = 0x9f
|
163
|
+
OP_GREATERTHAN = 0xa0
|
164
|
+
OP_LESSTHANOREQUAL = 0xa1
|
165
|
+
OP_GREATERTHANOREQUAL = 0xa2
|
166
|
+
OP_MIN = 0xa3
|
167
|
+
OP_MAX = 0xa4
|
168
|
+
|
169
|
+
OP_WITHIN = 0xa5
|
170
|
+
|
171
|
+
# Crypto
|
172
|
+
OP_RIPEMD160 = 0xa6
|
173
|
+
OP_SHA1 = 0xa7
|
174
|
+
OP_SHA256 = 0xa8
|
175
|
+
OP_HASH160 = 0xa9
|
176
|
+
OP_HASH256 = 0xaa
|
177
|
+
OP_CODESEPARATOR = 0xab # This opcode is rarely used because it's useless, but we need to support it anyway.
|
178
|
+
OP_CHECKSIG = 0xac
|
179
|
+
OP_CHECKSIGVERIFY = 0xad
|
180
|
+
OP_CHECKMULTISIG = 0xae
|
181
|
+
OP_CHECKMULTISIGVERIFY = 0xaf
|
182
|
+
|
183
|
+
# Expansion
|
184
|
+
OP_NOP1 = 0xb0
|
185
|
+
OP_NOP2 = 0xb1
|
186
|
+
OP_CHECKLOCKTIMEVERIFY = OP_NOP2 # See BIP65
|
187
|
+
OP_NOP3 = 0xb2
|
188
|
+
OP_NOP4 = 0xb3
|
189
|
+
OP_NOP5 = 0xb4
|
190
|
+
OP_NOP6 = 0xb5
|
191
|
+
OP_NOP7 = 0xb6
|
192
|
+
OP_NOP8 = 0xb7
|
193
|
+
OP_NOP9 = 0xb8
|
194
|
+
OP_NOP10 = 0xb9
|
195
|
+
|
196
|
+
OP_INVALIDOPCODE = 0xff
|
197
|
+
|
198
|
+
OPCODE_NAME_TO_VALUE = Hash[*constants.grep(/^OP_/).map{|c| [c.to_s, const_get(c)] }.flatten]
|
199
|
+
OPCODE_VALUE_TO_NAME = Hash[*constants.grep(/^OP_/).map{|c| [const_get(c), c.to_s] }.flatten]
|
200
|
+
end
|
201
|
+
|
202
|
+
# Make all opcodes available as BTC::OP_...
|
203
|
+
include Opcodes
|
197
204
|
end
|
198
205
|
|
@@ -13,6 +13,9 @@ module BTC
|
|
13
13
|
# If it is not a multisig script, returns nil.
|
14
14
|
# See also #multisig_script?
|
15
15
|
attr_reader :multisig_signatures_required
|
16
|
+
|
17
|
+
# Returns an array of chunks that constitute the script.
|
18
|
+
attr_reader :chunks
|
16
19
|
|
17
20
|
def initialize(hex: nil, # raw script data in hex
|
18
21
|
data: nil, # raw script data in binary
|
@@ -25,7 +28,7 @@ module BTC
|
|
25
28
|
@chunks = []
|
26
29
|
offset = 0
|
27
30
|
while offset < data.bytesize
|
28
|
-
chunk =
|
31
|
+
chunk = ScriptChunk.with_data(data, offset: offset)
|
29
32
|
if !chunk.canonical?
|
30
33
|
Diagnostics.current.add_message("BTC::Script: decoded non-canonical chunk at offset #{offset}: #{chunk.to_s}")
|
31
34
|
end
|
@@ -249,7 +252,7 @@ module BTC
|
|
249
252
|
def data_only?
|
250
253
|
# Include PUSHDATA ops and OP_0..OP_16 literals.
|
251
254
|
@chunks.each do |chunk|
|
252
|
-
return false if chunk.
|
255
|
+
return false if !chunk.data_only?
|
253
256
|
end
|
254
257
|
return true
|
255
258
|
end
|
@@ -377,7 +380,7 @@ module BTC
|
|
377
380
|
if opcode > 0 && opcode <= OP_PUSHDATA4
|
378
381
|
raise ArgumentError, "Cannot add pushdata opcode without data"
|
379
382
|
end
|
380
|
-
@chunks <<
|
383
|
+
@chunks << ScriptChunk.new(opcode.chr)
|
381
384
|
return self
|
382
385
|
end
|
383
386
|
|
@@ -390,7 +393,7 @@ module BTC
|
|
390
393
|
if !encoded_pushdata
|
391
394
|
raise ArgumentError, "Cannot encode pushdata with opcode #{opcode}"
|
392
395
|
end
|
393
|
-
@chunks <<
|
396
|
+
@chunks << ScriptChunk.new(encoded_pushdata)
|
394
397
|
return self
|
395
398
|
end
|
396
399
|
|
@@ -434,7 +437,7 @@ module BTC
|
|
434
437
|
object.each do |element|
|
435
438
|
self << element
|
436
439
|
end
|
437
|
-
elsif object.is_a?(
|
440
|
+
elsif object.is_a?(ScriptChunk)
|
438
441
|
@chunks << object
|
439
442
|
else
|
440
443
|
raise ArgumentError, "Operand must be an integer, a string a BTC::Script instance or an array of any of those."
|
@@ -490,8 +493,6 @@ module BTC
|
|
490
493
|
# Private API
|
491
494
|
# -----------
|
492
495
|
|
493
|
-
attr_reader :chunks
|
494
|
-
|
495
496
|
# If opcode is nil, then the most compact encoding will be chosen.
|
496
497
|
# Returns nil if opcode can't be used for data, or data is nil or too big.
|
497
498
|
def self.encode_pushdata(pushdata, opcode: nil)
|
@@ -561,199 +562,5 @@ module BTC
|
|
561
562
|
return true
|
562
563
|
end
|
563
564
|
|
564
|
-
# Script::Chunk represents either an opcode or a pushdata command.
|
565
|
-
class Chunk
|
566
|
-
# Raw data for this chunk.
|
567
|
-
# 1 byte for regular opcode, 1 or more bytes for pushdata command.
|
568
|
-
# We do not call it 'data' to avoid confusion with `pushdata` (see below).
|
569
|
-
# The encoding is guaranteed to be binary.
|
570
|
-
attr_reader :raw_data
|
571
|
-
|
572
|
-
# Opcode for this chunk (first byte of the raw_data).
|
573
|
-
attr_reader :opcode
|
574
|
-
|
575
|
-
# If opcode is OP_PUSHDATA*, contains pure data being pushed.
|
576
|
-
# If opcode is OP_0, returns an empty string.
|
577
|
-
# If opcode is not pushdata, returns nil.
|
578
|
-
attr_reader :pushdata
|
579
|
-
|
580
|
-
# Length of raw_data in bytes.
|
581
|
-
attr_reader :size
|
582
|
-
alias :length :size
|
583
|
-
|
584
|
-
# Returns true if this is a non-pushdata (also not OP_0) opcode.
|
585
|
-
def opcode?
|
586
|
-
!pushdata?
|
587
|
-
end
|
588
|
-
|
589
|
-
# Returns true if this is a pushdata chunk (or OP_0 opcode).
|
590
|
-
def pushdata?
|
591
|
-
# Compact pushdata opcodes are "virtual", just length prefixes.
|
592
|
-
# Attention: OP_0 is also "pushdata" code that pushes empty data.
|
593
|
-
self.opcode <= OP_PUSHDATA4
|
594
|
-
end
|
595
|
-
|
596
|
-
# Returns true if this chunk is in canonical form (the most compact one).
|
597
|
-
# Returns false if it contains pushdata with too big length prefix.
|
598
|
-
# Example of non-canonical chunk: 75 bytes pushed with OP_PUSHDATA1 instead
|
599
|
-
# of simple 0x4b prefix.
|
600
|
-
def canonical?
|
601
|
-
opcode = self.opcode
|
602
|
-
if opcode < OP_PUSHDATA1
|
603
|
-
return true # most compact pushdata is always canonical.
|
604
|
-
elsif opcode == OP_PUSHDATA1
|
605
|
-
return (self.raw_data.bytesize - (1+1)) >= OP_PUSHDATA1
|
606
|
-
elsif opcode == OP_PUSHDATA2
|
607
|
-
return (self.raw_data.bytesize - (1+2)) > 0xff
|
608
|
-
elsif opcode == OP_PUSHDATA4
|
609
|
-
return (self.raw_data.bytesize - (1+4)) > 0xffff
|
610
|
-
else
|
611
|
-
return true # all other opcodes are canonical (just 1 byte code)
|
612
|
-
end
|
613
|
-
end
|
614
|
-
|
615
|
-
def opcode
|
616
|
-
# raises StopIteration if raw_data is empty,
|
617
|
-
# but we don't allow empty raw_data for chunks.
|
618
|
-
raw_data.each_byte.next
|
619
|
-
end
|
620
|
-
|
621
|
-
def pushdata
|
622
|
-
return nil if !pushdata?
|
623
|
-
opcode = self.opcode
|
624
|
-
offset = 1 # by default, opcode is just a length prefix.
|
625
|
-
if opcode == OP_PUSHDATA1
|
626
|
-
offset += 1
|
627
|
-
elsif opcode == OP_PUSHDATA2
|
628
|
-
offset += 2
|
629
|
-
elsif opcode == OP_PUSHDATA4
|
630
|
-
offset += 4
|
631
|
-
end
|
632
|
-
self.raw_data[offset, self.raw_data.size - offset]
|
633
|
-
end
|
634
|
-
|
635
|
-
def size
|
636
|
-
self.raw_data.bytesize
|
637
|
-
end
|
638
|
-
|
639
|
-
def to_s
|
640
|
-
opcode = self.opcode
|
641
|
-
|
642
|
-
if self.opcode?
|
643
|
-
return "OP_0" if opcode == OP_0
|
644
|
-
return "OP_1NEGATE" if opcode == OP_1NEGATE
|
645
|
-
if opcode >= OP_1 && opcode <= OP_16
|
646
|
-
return "OP_#{opcode + 1 - OP_1}"
|
647
|
-
end
|
648
|
-
return Opcode.name_for_opcode(opcode)
|
649
|
-
end
|
650
|
-
|
651
|
-
pushdata = self.pushdata
|
652
|
-
return "OP_0" if pushdata.bytesize == 0 # Empty data is encoded as OP_0.
|
653
|
-
|
654
|
-
string = ""
|
655
|
-
|
656
|
-
# If it's some weird readable string, show it as a readable string.
|
657
|
-
if data_is_ascii_printable?(pushdata) && (pushdata.bytesize < 20 || pushdata.bytesize > 65)
|
658
|
-
string = pushdata.encode(Encoding::ASCII)
|
659
|
-
# Escape escapes & single quote characters.
|
660
|
-
string.gsub!("\\", "\\\\")
|
661
|
-
string.gsub!("'", "\\'")
|
662
|
-
# Wrap in single quotes. Why not double? Because they are already used in JSON and we don't want to multiply the mess.
|
663
|
-
string = "'#{string}'"
|
664
|
-
else
|
665
|
-
string = BTC.to_hex(pushdata)
|
666
|
-
# Shorter than 128-bit chunks are wrapped in square brackets to avoid ambiguity with big all-decimal numbers.
|
667
|
-
if (pushdata.bytesize < 16)
|
668
|
-
string = "[#{string}]"
|
669
|
-
end
|
670
|
-
end
|
671
|
-
|
672
|
-
# Pushdata with non-compact encoding will have explicit length prefix (1 for OP_PUSHDATA1, 2 for OP_PUSHDATA2 and 4 for OP_PUSHDATA4).
|
673
|
-
if !canonical?
|
674
|
-
prefix = 1
|
675
|
-
prefix = 2 if opcode == OP_PUSHDATA2
|
676
|
-
prefix = 4 if opcode == OP_PUSHDATA4
|
677
|
-
string = "#{prefix}:#{string}"
|
678
|
-
end
|
679
|
-
|
680
|
-
return string
|
681
|
-
end
|
682
|
-
|
683
|
-
# Parses the chunk with binary data. Assumes the encoding is binary.
|
684
|
-
def self.with_data(data, offset: 0)
|
685
|
-
raise ArgumentError, "Data is missing" if !data
|
686
|
-
|
687
|
-
opcode, _ = BTC::WireFormat.read_uint8(data: data, offset: offset)
|
688
|
-
|
689
|
-
raise ArgumentError, "Failed to read opcode of the script chunk" if !opcode
|
690
|
-
|
691
|
-
# push data opcode
|
692
|
-
if opcode <= OP_PUSHDATA4
|
693
|
-
|
694
|
-
length = data.bytesize
|
695
|
-
|
696
|
-
if opcode < OP_PUSHDATA1
|
697
|
-
pushdata_length = opcode
|
698
|
-
chunk_length = 1 + pushdata_length
|
699
|
-
if offset + chunk_length > length
|
700
|
-
raise ArgumentError, "PUSHDATA is longer than we have bytes available"
|
701
|
-
end
|
702
|
-
return self.new(data[offset, chunk_length])
|
703
|
-
elsif opcode == OP_PUSHDATA1
|
704
|
-
pushdata_length, _ = BTC::WireFormat.read_uint8(data: data, offset: offset + 1)
|
705
|
-
if !pushdata_length
|
706
|
-
raise ArgumentError, "Failed to read length for PUSHDATA1"
|
707
|
-
end
|
708
|
-
chunk_length = 1 + 1 + pushdata_length
|
709
|
-
if offset + chunk_length > length
|
710
|
-
raise ArgumentError, "PUSHDATA1 is longer than we have bytes available"
|
711
|
-
end
|
712
|
-
return self.new(data[offset, chunk_length])
|
713
|
-
elsif (opcode == OP_PUSHDATA2)
|
714
|
-
pushdata_length, _ = BTC::WireFormat.read_uint16le(data: data, offset: offset + 1)
|
715
|
-
if !pushdata_length
|
716
|
-
raise ArgumentError, "Failed to read length for PUSHDATA2"
|
717
|
-
end
|
718
|
-
chunk_length = 1 + 2 + pushdata_length
|
719
|
-
if offset + chunk_length > length
|
720
|
-
raise ArgumentError, "PUSHDATA2 is longer than we have bytes available"
|
721
|
-
end
|
722
|
-
return self.new(data[offset, chunk_length])
|
723
|
-
elsif (opcode == OP_PUSHDATA4)
|
724
|
-
pushdata_length, _ = BTC::WireFormat.read_uint32le(data: data, offset: offset + 1)
|
725
|
-
if !pushdata_length
|
726
|
-
raise ArgumentError, "Failed to read length for PUSHDATA4"
|
727
|
-
end
|
728
|
-
chunk_length = 1 + 4 + pushdata_length
|
729
|
-
if offset + chunk_length > length
|
730
|
-
raise ArgumentError, "PUSHDATA4 is longer than we have bytes available"
|
731
|
-
end
|
732
|
-
return self.new(data[offset, chunk_length])
|
733
|
-
end
|
734
|
-
else
|
735
|
-
# simple opcode - 1 byte
|
736
|
-
return self.new(data[offset, 1])
|
737
|
-
end
|
738
|
-
end
|
739
|
-
|
740
|
-
def initialize(raw_data)
|
741
|
-
@raw_data = raw_data
|
742
|
-
end
|
743
|
-
|
744
|
-
def ==(other)
|
745
|
-
@raw_data == other.raw_data
|
746
|
-
end
|
747
|
-
|
748
|
-
protected
|
749
|
-
|
750
|
-
def data_is_ascii_printable?(data)
|
751
|
-
data.each_byte do |byte|
|
752
|
-
return false if !(byte >= 0x20 && byte <= 0x7E)
|
753
|
-
end
|
754
|
-
return true
|
755
|
-
end
|
756
|
-
|
757
|
-
end # Chunk
|
758
565
|
end # Script
|
759
566
|
end # BTC
|
@@ -0,0 +1,216 @@
|
|
1
|
+
module BTC
|
2
|
+
# ScriptChunk represents either an opcode or a pushdata command.
|
3
|
+
class ScriptChunk
|
4
|
+
# Raw data for this chunk.
|
5
|
+
# 1 byte for regular opcode, 1 or more bytes for pushdata command.
|
6
|
+
# We do not call it 'data' to avoid confusion with `pushdata` (see below).
|
7
|
+
# The encoding is guaranteed to be binary.
|
8
|
+
attr_reader :raw_data
|
9
|
+
|
10
|
+
# Opcode for this chunk (first byte of the raw_data).
|
11
|
+
attr_reader :opcode
|
12
|
+
|
13
|
+
# If opcode is OP_PUSHDATA*, contains pure data being pushed.
|
14
|
+
# If opcode is OP_0, returns an empty string.
|
15
|
+
# If opcode is not pushdata, returns nil.
|
16
|
+
attr_reader :pushdata
|
17
|
+
|
18
|
+
# Length of raw_data in bytes.
|
19
|
+
attr_reader :size
|
20
|
+
alias :length :size
|
21
|
+
|
22
|
+
# Returns true if this is a non-pushdata (also not OP_0) opcode.
|
23
|
+
def opcode?
|
24
|
+
!pushdata?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Returns true if this is a pushdata chunk (or OP_0 opcode).
|
28
|
+
def pushdata?
|
29
|
+
# Compact pushdata opcodes are "virtual", just length prefixes.
|
30
|
+
# Attention: OP_0 is also "pushdata" code that pushes empty data.
|
31
|
+
opcode <= OP_PUSHDATA4
|
32
|
+
end
|
33
|
+
|
34
|
+
def data_only?
|
35
|
+
opcode <= OP_16
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns true if this chunk is in canonical form (the most compact one).
|
39
|
+
# Returns false if it contains pushdata with too big length prefix.
|
40
|
+
# Example of non-canonical chunk: 75 bytes pushed with OP_PUSHDATA1 instead
|
41
|
+
# of simple 0x4b prefix.
|
42
|
+
# Note: this is not as strict as `check_minimal_push` in ScriptInterpreter.
|
43
|
+
def canonical?
|
44
|
+
opcode = self.opcode
|
45
|
+
if opcode < OP_PUSHDATA1
|
46
|
+
return true # most compact pushdata is always canonical.
|
47
|
+
elsif opcode == OP_PUSHDATA1
|
48
|
+
return (self.raw_data.bytesize - (1+1)) >= OP_PUSHDATA1
|
49
|
+
elsif opcode == OP_PUSHDATA2
|
50
|
+
return (self.raw_data.bytesize - (1+2)) > 0xff
|
51
|
+
elsif opcode == OP_PUSHDATA4
|
52
|
+
return (self.raw_data.bytesize - (1+4)) > 0xffff
|
53
|
+
else
|
54
|
+
return true # all other opcodes are canonical (just 1 byte code)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def opcode
|
59
|
+
# raises StopIteration if raw_data is empty,
|
60
|
+
# but we don't allow empty raw_data for chunks.
|
61
|
+
raw_data.each_byte.next
|
62
|
+
end
|
63
|
+
|
64
|
+
def pushdata
|
65
|
+
return nil if !pushdata?
|
66
|
+
opcode = self.opcode
|
67
|
+
offset = 1 # by default, opcode is just a length prefix.
|
68
|
+
if opcode == OP_PUSHDATA1
|
69
|
+
offset += 1
|
70
|
+
elsif opcode == OP_PUSHDATA2
|
71
|
+
offset += 2
|
72
|
+
elsif opcode == OP_PUSHDATA4
|
73
|
+
offset += 4
|
74
|
+
end
|
75
|
+
self.raw_data[offset, self.raw_data.size - offset]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns corresponding data for data_only opcodes:
|
79
|
+
# pushdata or OP_N-encoded numbers.
|
80
|
+
# For all other opcodes returns nil.
|
81
|
+
def interpreted_data
|
82
|
+
if d = pushdata
|
83
|
+
return d
|
84
|
+
end
|
85
|
+
opcode = self.opcode
|
86
|
+
if opcode == OP_1NEGATE || (opcode >= OP_1 && opcode <= OP_16)
|
87
|
+
ScriptNumber.new(integer: opcode - OP_1 + 1).data
|
88
|
+
else
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def size
|
94
|
+
self.raw_data.bytesize
|
95
|
+
end
|
96
|
+
|
97
|
+
def to_s
|
98
|
+
opcode = self.opcode
|
99
|
+
|
100
|
+
if self.opcode?
|
101
|
+
return "OP_0" if opcode == OP_0
|
102
|
+
return "OP_1NEGATE" if opcode == OP_1NEGATE
|
103
|
+
if opcode >= OP_1 && opcode <= OP_16
|
104
|
+
return "OP_#{opcode + 1 - OP_1}"
|
105
|
+
end
|
106
|
+
return Opcode.name_for_opcode(opcode)
|
107
|
+
end
|
108
|
+
|
109
|
+
pushdata = self.pushdata
|
110
|
+
return "OP_0" if pushdata.bytesize == 0 # Empty data is encoded as OP_0.
|
111
|
+
|
112
|
+
string = ""
|
113
|
+
|
114
|
+
# If it's some weird readable string, show it as a readable string.
|
115
|
+
if data_is_ascii_printable?(pushdata) && (pushdata.bytesize < 20 || pushdata.bytesize > 65)
|
116
|
+
string = pushdata.encode(Encoding::ASCII)
|
117
|
+
# Escape escapes & single quote characters.
|
118
|
+
string.gsub!("\\", "\\\\")
|
119
|
+
string.gsub!("'", "\\'")
|
120
|
+
# Wrap in single quotes. Why not double? Because they are already used in JSON and we don't want to multiply the mess.
|
121
|
+
string = "'#{string}'"
|
122
|
+
else
|
123
|
+
string = BTC.to_hex(pushdata)
|
124
|
+
# Shorter than 128-bit chunks are wrapped in square brackets to avoid ambiguity with big all-decimal numbers.
|
125
|
+
if (pushdata.bytesize < 16)
|
126
|
+
string = "[#{string}]"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Pushdata with non-compact encoding will have explicit length prefix (1 for OP_PUSHDATA1, 2 for OP_PUSHDATA2 and 4 for OP_PUSHDATA4).
|
131
|
+
if !canonical?
|
132
|
+
prefix = 1
|
133
|
+
prefix = 2 if opcode == OP_PUSHDATA2
|
134
|
+
prefix = 4 if opcode == OP_PUSHDATA4
|
135
|
+
string = "#{prefix}:#{string}"
|
136
|
+
end
|
137
|
+
|
138
|
+
return string
|
139
|
+
end
|
140
|
+
|
141
|
+
# Parses the chunk with binary data. Assumes the encoding is binary.
|
142
|
+
def self.with_data(data, offset: 0)
|
143
|
+
raise ArgumentError, "Data is missing" if !data
|
144
|
+
|
145
|
+
opcode, _ = BTC::WireFormat.read_uint8(data: data, offset: offset)
|
146
|
+
|
147
|
+
raise ArgumentError, "Failed to read opcode of the script chunk" if !opcode
|
148
|
+
|
149
|
+
# push data opcode
|
150
|
+
if opcode <= OP_PUSHDATA4
|
151
|
+
|
152
|
+
length = data.bytesize
|
153
|
+
|
154
|
+
if opcode < OP_PUSHDATA1
|
155
|
+
pushdata_length = opcode
|
156
|
+
chunk_length = 1 + pushdata_length
|
157
|
+
if offset + chunk_length > length
|
158
|
+
raise ArgumentError, "PUSHDATA is longer than we have bytes available"
|
159
|
+
end
|
160
|
+
return self.new(data[offset, chunk_length])
|
161
|
+
elsif opcode == OP_PUSHDATA1
|
162
|
+
pushdata_length, _ = BTC::WireFormat.read_uint8(data: data, offset: offset + 1)
|
163
|
+
if !pushdata_length
|
164
|
+
raise ArgumentError, "Failed to read length for PUSHDATA1"
|
165
|
+
end
|
166
|
+
chunk_length = 1 + 1 + pushdata_length
|
167
|
+
if offset + chunk_length > length
|
168
|
+
raise ArgumentError, "PUSHDATA1 is longer than we have bytes available"
|
169
|
+
end
|
170
|
+
return self.new(data[offset, chunk_length])
|
171
|
+
elsif (opcode == OP_PUSHDATA2)
|
172
|
+
pushdata_length, _ = BTC::WireFormat.read_uint16le(data: data, offset: offset + 1)
|
173
|
+
if !pushdata_length
|
174
|
+
raise ArgumentError, "Failed to read length for PUSHDATA2"
|
175
|
+
end
|
176
|
+
chunk_length = 1 + 2 + pushdata_length
|
177
|
+
if offset + chunk_length > length
|
178
|
+
raise ArgumentError, "PUSHDATA2 is longer than we have bytes available"
|
179
|
+
end
|
180
|
+
return self.new(data[offset, chunk_length])
|
181
|
+
elsif (opcode == OP_PUSHDATA4)
|
182
|
+
pushdata_length, _ = BTC::WireFormat.read_uint32le(data: data, offset: offset + 1)
|
183
|
+
if !pushdata_length
|
184
|
+
raise ArgumentError, "Failed to read length for PUSHDATA4"
|
185
|
+
end
|
186
|
+
chunk_length = 1 + 4 + pushdata_length
|
187
|
+
if offset + chunk_length > length
|
188
|
+
raise ArgumentError, "PUSHDATA4 is longer than we have bytes available"
|
189
|
+
end
|
190
|
+
return self.new(data[offset, chunk_length])
|
191
|
+
end
|
192
|
+
else
|
193
|
+
# simple opcode - 1 byte
|
194
|
+
return self.new(data[offset, 1])
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def initialize(raw_data)
|
199
|
+
@raw_data = raw_data
|
200
|
+
end
|
201
|
+
|
202
|
+
def ==(other)
|
203
|
+
@raw_data == other.raw_data
|
204
|
+
end
|
205
|
+
|
206
|
+
protected
|
207
|
+
|
208
|
+
def data_is_ascii_printable?(data)
|
209
|
+
data.each_byte do |byte|
|
210
|
+
return false if !(byte >= 0x20 && byte <= 0x7E)
|
211
|
+
end
|
212
|
+
return true
|
213
|
+
end
|
214
|
+
|
215
|
+
end # ScriptChunk
|
216
|
+
end
|
data/lib/btcruby/version.rb
CHANGED
data/spec/script_spec.rb
CHANGED
@@ -71,7 +71,7 @@ describe BTC::Script do
|
|
71
71
|
end
|
72
72
|
|
73
73
|
|
74
|
-
it "removing subscript does not modify receiver" do
|
74
|
+
it "removing subscript does not modify the receiver" do
|
75
75
|
s = Script.new << OP_DUP << OP_HASH160 << OP_CODESEPARATOR << "some data" << OP_EQUALVERIFY << OP_CHECKSIG
|
76
76
|
s1 = s.dup
|
77
77
|
s2 = s1.find_and_delete(Script.new << OP_HASH160)
|
@@ -91,4 +91,10 @@ describe BTC::Script do
|
|
91
91
|
s2.must_equal(Script.new.append_pushdata("foo", opcode:OP_PUSHDATA1))
|
92
92
|
end
|
93
93
|
|
94
|
+
it "should parse interpreted data and pushdata correctly" do
|
95
|
+
script = Script.new << OP_0 << OP_1NEGATE << OP_NOP << OP_RESERVED << OP_1 << OP_16 << "1" << "2" << "chancellor"
|
96
|
+
script.chunks.map{|c| c.interpreted_data }.must_equal ["", "\x81".b, nil, nil, "\x01".b, "\x10".b, "1", "2", "chancellor"]
|
97
|
+
script.chunks.map{|c| c.pushdata }.must_equal ["", nil, nil, nil, nil, nil, "1", "2", "chancellor"]
|
98
|
+
end
|
99
|
+
|
94
100
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: btcruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Oleg Andreev
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-08-
|
12
|
+
date: 2015-08-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ffi
|
@@ -108,6 +108,7 @@ files:
|
|
108
108
|
- lib/btcruby/script/opcode.rb
|
109
109
|
- lib/btcruby/script/p2sh_plugin.rb
|
110
110
|
- lib/btcruby/script/script.rb
|
111
|
+
- lib/btcruby/script/script_chunk.rb
|
111
112
|
- lib/btcruby/script/script_error.rb
|
112
113
|
- lib/btcruby/script/script_flags.rb
|
113
114
|
- lib/btcruby/script/script_interpreter.rb
|