btcruby 1.1.3 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|