bsv-sdk 0.1.0
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 +7 -0
- data/CHANGELOG.md +58 -0
- data/LICENCE +86 -0
- data/README.md +155 -0
- data/lib/bsv/attest/configuration.rb +9 -0
- data/lib/bsv/attest/response.rb +19 -0
- data/lib/bsv/attest/verification_error.rb +7 -0
- data/lib/bsv/attest/version.rb +7 -0
- data/lib/bsv/attest.rb +71 -0
- data/lib/bsv/network/arc.rb +113 -0
- data/lib/bsv/network/broadcast_error.rb +15 -0
- data/lib/bsv/network/broadcast_response.rb +29 -0
- data/lib/bsv/network/chain_provider_error.rb +14 -0
- data/lib/bsv/network/utxo.rb +28 -0
- data/lib/bsv/network/whats_on_chain.rb +82 -0
- data/lib/bsv/network.rb +12 -0
- data/lib/bsv/primitives/base58.rb +117 -0
- data/lib/bsv/primitives/bsm.rb +131 -0
- data/lib/bsv/primitives/curve.rb +115 -0
- data/lib/bsv/primitives/digest.rb +99 -0
- data/lib/bsv/primitives/ecdsa.rb +224 -0
- data/lib/bsv/primitives/ecies.rb +128 -0
- data/lib/bsv/primitives/extended_key.rb +315 -0
- data/lib/bsv/primitives/mnemonic/wordlist.rb +270 -0
- data/lib/bsv/primitives/mnemonic.rb +192 -0
- data/lib/bsv/primitives/private_key.rb +139 -0
- data/lib/bsv/primitives/public_key.rb +118 -0
- data/lib/bsv/primitives/schnorr.rb +108 -0
- data/lib/bsv/primitives/signature.rb +136 -0
- data/lib/bsv/primitives.rb +23 -0
- data/lib/bsv/script/builder.rb +73 -0
- data/lib/bsv/script/chunk.rb +77 -0
- data/lib/bsv/script/interpreter/error.rb +54 -0
- data/lib/bsv/script/interpreter/interpreter.rb +281 -0
- data/lib/bsv/script/interpreter/operations/arithmetic.rb +243 -0
- data/lib/bsv/script/interpreter/operations/bitwise.rb +68 -0
- data/lib/bsv/script/interpreter/operations/crypto.rb +209 -0
- data/lib/bsv/script/interpreter/operations/data_push.rb +34 -0
- data/lib/bsv/script/interpreter/operations/flow_control.rb +94 -0
- data/lib/bsv/script/interpreter/operations/splice.rb +89 -0
- data/lib/bsv/script/interpreter/operations/stack_ops.rb +112 -0
- data/lib/bsv/script/interpreter/script_number.rb +218 -0
- data/lib/bsv/script/interpreter/stack.rb +203 -0
- data/lib/bsv/script/opcodes.rb +165 -0
- data/lib/bsv/script/script.rb +424 -0
- data/lib/bsv/script.rb +20 -0
- data/lib/bsv/transaction/beef.rb +323 -0
- data/lib/bsv/transaction/merkle_path.rb +250 -0
- data/lib/bsv/transaction/p2pkh.rb +44 -0
- data/lib/bsv/transaction/sighash.rb +48 -0
- data/lib/bsv/transaction/transaction.rb +380 -0
- data/lib/bsv/transaction/transaction_input.rb +109 -0
- data/lib/bsv/transaction/transaction_output.rb +51 -0
- data/lib/bsv/transaction/unlocking_script_template.rb +36 -0
- data/lib/bsv/transaction/var_int.rb +50 -0
- data/lib/bsv/transaction.rb +21 -0
- data/lib/bsv/version.rb +5 -0
- data/lib/bsv/wallet/insufficient_funds_error.rb +15 -0
- data/lib/bsv/wallet/wallet.rb +119 -0
- data/lib/bsv/wallet.rb +8 -0
- data/lib/bsv-attest.rb +4 -0
- data/lib/bsv-sdk.rb +11 -0
- metadata +104 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'error'
|
|
4
|
+
|
|
5
|
+
module BSV
|
|
6
|
+
module Script
|
|
7
|
+
# Bitcoin script number: arbitrary-precision integer with sign-magnitude
|
|
8
|
+
# little-endian byte encoding.
|
|
9
|
+
#
|
|
10
|
+
# Script numbers use a specialised encoding where the sign bit occupies
|
|
11
|
+
# the MSB of the last byte, and the magnitude is stored little-endian.
|
|
12
|
+
# This class handles encoding/decoding, minimal encoding validation,
|
|
13
|
+
# and arithmetic operations as required by the script interpreter.
|
|
14
|
+
class ScriptNumber # rubocop:disable Metrics/ClassLength
|
|
15
|
+
include Comparable
|
|
16
|
+
|
|
17
|
+
# Maximum byte length for script numbers (post-Genesis: 750 KB).
|
|
18
|
+
MAX_BYTE_LENGTH = 750_000
|
|
19
|
+
|
|
20
|
+
# Minimum 32-bit signed integer value.
|
|
21
|
+
INT32_MIN = -(2**31)
|
|
22
|
+
|
|
23
|
+
# Maximum 32-bit signed integer value.
|
|
24
|
+
INT32_MAX = (2**31) - 1
|
|
25
|
+
|
|
26
|
+
# @return [Integer] the numeric value
|
|
27
|
+
attr_reader :value
|
|
28
|
+
|
|
29
|
+
def initialize(value)
|
|
30
|
+
@value = value
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Decode little-endian sign-magnitude bytes into a ScriptNumber.
|
|
34
|
+
#
|
|
35
|
+
# Encoding: little-endian magnitude with sign bit in the MSB of the last byte.
|
|
36
|
+
# 127 -> [0x7f]
|
|
37
|
+
# -127 -> [0xff]
|
|
38
|
+
# 128 -> [0x80 0x00]
|
|
39
|
+
# -128 -> [0x80 0x80]
|
|
40
|
+
# 256 -> [0x00 0x01]
|
|
41
|
+
# -256 -> [0x00 0x81]
|
|
42
|
+
def self.from_bytes(bytes, max_length: MAX_BYTE_LENGTH, require_minimal: false)
|
|
43
|
+
bytes = bytes.b if bytes.encoding != Encoding::ASCII_8BIT
|
|
44
|
+
return new(0) if bytes.empty?
|
|
45
|
+
|
|
46
|
+
if bytes.bytesize > max_length
|
|
47
|
+
raise ScriptError.new(
|
|
48
|
+
ScriptErrorCode::NUMBER_TOO_BIG,
|
|
49
|
+
"script number overflow: #{bytes.bytesize} > #{max_length}"
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
check_minimal_encoding!(bytes) if require_minimal
|
|
54
|
+
|
|
55
|
+
# Accumulate little-endian magnitude
|
|
56
|
+
val = 0
|
|
57
|
+
bytes.each_byte.with_index do |byte, i|
|
|
58
|
+
val |= byte << (8 * i)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Extract sign from MSB of last byte
|
|
62
|
+
if bytes.getbyte(bytes.bytesize - 1) & 0x80 != 0
|
|
63
|
+
val ^= 0x80 << (8 * (bytes.bytesize - 1))
|
|
64
|
+
val = -val
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
new(val)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Encode as little-endian sign-magnitude bytes.
|
|
71
|
+
def to_bytes
|
|
72
|
+
return ''.b if @value.zero?
|
|
73
|
+
|
|
74
|
+
negative = @value.negative?
|
|
75
|
+
abs_val = @value.abs
|
|
76
|
+
|
|
77
|
+
result = []
|
|
78
|
+
v = abs_val
|
|
79
|
+
while v.positive?
|
|
80
|
+
result << (v & 0xff)
|
|
81
|
+
v >>= 8
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if result.last & 0x80 != 0
|
|
85
|
+
# MSB conflicts with sign bit — add padding byte
|
|
86
|
+
result << (negative ? 0x80 : 0x00)
|
|
87
|
+
elsif negative
|
|
88
|
+
# Set sign bit in MSB
|
|
89
|
+
result[-1] |= 0x80
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
result.pack('C*')
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Strip trailing zero-padding while preserving sign.
|
|
96
|
+
def self.minimally_encode(data)
|
|
97
|
+
return ''.b if data.nil? || data.empty?
|
|
98
|
+
|
|
99
|
+
data = data.b
|
|
100
|
+
last = data.getbyte(data.bytesize - 1)
|
|
101
|
+
|
|
102
|
+
# If MSB has non-sign bits set, already minimal
|
|
103
|
+
return data.dup if last.anybits?(0x7f)
|
|
104
|
+
|
|
105
|
+
# Single byte with only sign/zero bits — encode as empty
|
|
106
|
+
return ''.b if data.bytesize == 1
|
|
107
|
+
|
|
108
|
+
# If second-to-last byte needs the sign extension, keep it
|
|
109
|
+
return data.dup if data.getbyte(data.bytesize - 2) & 0x80 != 0
|
|
110
|
+
|
|
111
|
+
# Scan backwards to find last non-zero byte
|
|
112
|
+
i = data.bytesize - 2
|
|
113
|
+
i -= 1 while i.positive? && data.getbyte(i).zero?
|
|
114
|
+
|
|
115
|
+
if data.getbyte(i).zero?
|
|
116
|
+
# All zeros
|
|
117
|
+
''.b
|
|
118
|
+
elsif data.getbyte(i) & 0x80 != 0
|
|
119
|
+
# This byte has high bit set — keep sign extension byte
|
|
120
|
+
result = data.byteslice(0, i + 1)
|
|
121
|
+
result << [last].pack('C')
|
|
122
|
+
result
|
|
123
|
+
else
|
|
124
|
+
# Fold sign bit into this byte
|
|
125
|
+
result = data.byteslice(0, i + 1).b
|
|
126
|
+
result.setbyte(i, data.getbyte(i) | (last & 0x80))
|
|
127
|
+
result
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Clamp to int32 range (for opcodes that need bounded indices).
|
|
132
|
+
def to_i32
|
|
133
|
+
return INT32_MIN if @value < INT32_MIN
|
|
134
|
+
return INT32_MAX if @value > INT32_MAX
|
|
135
|
+
|
|
136
|
+
@value
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def to_i
|
|
140
|
+
@value
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def zero?
|
|
144
|
+
@value.zero?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# --- Arithmetic (returns new ScriptNumber) ---
|
|
148
|
+
|
|
149
|
+
def +(other)
|
|
150
|
+
self.class.new(@value + other.value)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def -(other)
|
|
154
|
+
self.class.new(@value - other.value)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def *(other)
|
|
158
|
+
self.class.new(@value * other.value)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Truncated-toward-zero division (matching Bitcoin consensus).
|
|
162
|
+
def /(other)
|
|
163
|
+
raise ScriptError.new(ScriptErrorCode::DIVIDE_BY_ZERO, 'division by zero') if other.value.zero?
|
|
164
|
+
|
|
165
|
+
result = @value.abs / other.value.abs
|
|
166
|
+
result = -result if @value.negative? ^ other.value.negative?
|
|
167
|
+
self.class.new(result)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Remainder with sign of dividend (matching Bitcoin consensus).
|
|
171
|
+
def %(other)
|
|
172
|
+
raise ScriptError.new(ScriptErrorCode::DIVIDE_BY_ZERO, 'modulo by zero') if other.value.zero?
|
|
173
|
+
|
|
174
|
+
result = @value.abs % other.value.abs
|
|
175
|
+
result = -result if @value.negative?
|
|
176
|
+
self.class.new(result)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def -@
|
|
180
|
+
self.class.new(-@value)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def abs
|
|
184
|
+
self.class.new(@value.abs)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def <=>(other)
|
|
188
|
+
case other
|
|
189
|
+
when ScriptNumber
|
|
190
|
+
@value <=> other.value
|
|
191
|
+
when Integer
|
|
192
|
+
@value <=> other
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def self.check_minimal_encoding!(bytes)
|
|
197
|
+
return if bytes.empty?
|
|
198
|
+
|
|
199
|
+
msb = bytes.getbyte(bytes.bytesize - 1)
|
|
200
|
+
|
|
201
|
+
# If MSB has non-sign bits set, encoding is minimal
|
|
202
|
+
return if msb.anybits?(0x7f)
|
|
203
|
+
|
|
204
|
+
# Single byte that is pure sign/zero (0x00 or 0x80) — not minimal
|
|
205
|
+
if bytes.bytesize == 1
|
|
206
|
+
raise ScriptError.new(ScriptErrorCode::MINIMAL_DATA, 'non-minimal script number encoding')
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Padding is justified if second-to-last byte has high bit set
|
|
210
|
+
return if bytes.getbyte(bytes.bytesize - 2) & 0x80 != 0
|
|
211
|
+
|
|
212
|
+
raise ScriptError.new(ScriptErrorCode::MINIMAL_DATA, 'non-minimal script number encoding')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
private_class_method :check_minimal_encoding!
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'error'
|
|
4
|
+
require_relative 'script_number'
|
|
5
|
+
|
|
6
|
+
module BSV
|
|
7
|
+
module Script
|
|
8
|
+
# Script execution stack providing push/pop/peek operations for bytes,
|
|
9
|
+
# integers ({ScriptNumber}), and booleans.
|
|
10
|
+
#
|
|
11
|
+
# Implements Forth-like stack manipulation operations (dup, drop, swap,
|
|
12
|
+
# rot, over, pick, roll, tuck) parameterised by count for the multi-element
|
|
13
|
+
# opcodes (OP_2DUP, OP_2SWAP, etc.).
|
|
14
|
+
class Stack # rubocop:disable Metrics/ClassLength
|
|
15
|
+
def initialize
|
|
16
|
+
@items = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# --- Push ---
|
|
20
|
+
|
|
21
|
+
def push_bytes(data)
|
|
22
|
+
@items.push(data.b)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def push_int(script_number)
|
|
26
|
+
@items.push(script_number.to_bytes)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def push_bool(val)
|
|
30
|
+
@items.push(val ? "\x01".b : ''.b)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# --- Pop ---
|
|
34
|
+
|
|
35
|
+
def pop_bytes
|
|
36
|
+
stack_error!('stack empty') if @items.empty?
|
|
37
|
+
|
|
38
|
+
@items.pop
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def pop_int(max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: false)
|
|
42
|
+
ScriptNumber.from_bytes(pop_bytes, max_length: max_length, require_minimal: require_minimal)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def pop_bool
|
|
46
|
+
self.class.cast_bool(pop_bytes)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# --- Peek ---
|
|
50
|
+
|
|
51
|
+
def peek_bytes(idx = 0)
|
|
52
|
+
check_index!(idx)
|
|
53
|
+
|
|
54
|
+
@items[@items.length - 1 - idx]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def peek_int(idx = 0, max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: false)
|
|
58
|
+
ScriptNumber.from_bytes(peek_bytes(idx), max_length: max_length, require_minimal: require_minimal)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def peek_bool(idx = 0)
|
|
62
|
+
self.class.cast_bool(peek_bytes(idx))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# --- Info ---
|
|
66
|
+
|
|
67
|
+
def depth
|
|
68
|
+
@items.length
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def empty?
|
|
72
|
+
@items.empty?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def clear
|
|
76
|
+
@items.clear
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def to_a
|
|
80
|
+
@items.dup
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# --- FORTH-like operations ---
|
|
84
|
+
|
|
85
|
+
# Duplicate the top N items.
|
|
86
|
+
def dup_n(count)
|
|
87
|
+
check_count!(count, 'dup_n')
|
|
88
|
+
stack_error!("stack too small for dup_n(#{count})") if @items.length < count
|
|
89
|
+
|
|
90
|
+
start = @items.length - count
|
|
91
|
+
count.times { |i| @items.push(@items[start + i].dup) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Remove the top N items.
|
|
95
|
+
def drop_n(count)
|
|
96
|
+
check_count!(count, 'drop_n')
|
|
97
|
+
stack_error!("stack too small for drop_n(#{count})") if @items.length < count
|
|
98
|
+
|
|
99
|
+
@items.pop(count)
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Remove item at offset idx from top (0 = top).
|
|
104
|
+
def nip_n(idx)
|
|
105
|
+
check_index!(idx)
|
|
106
|
+
|
|
107
|
+
@items.delete_at(@items.length - 1 - idx)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Rotate: move the bottom N of the top 3N items to the top.
|
|
111
|
+
# OP_ROT (n=1): [x1 x2 x3] -> [x2 x3 x1]
|
|
112
|
+
# OP_2ROT (n=2): [x1 x2 x3 x4 x5 x6] -> [x3 x4 x5 x6 x1 x2]
|
|
113
|
+
def rot_n(count)
|
|
114
|
+
check_count!(count, 'rot_n')
|
|
115
|
+
stack_error!("stack too small for rot_n(#{count})") if @items.length < (3 * count)
|
|
116
|
+
|
|
117
|
+
removed = @items.slice!(@items.length - (3 * count), count)
|
|
118
|
+
@items.concat(removed)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Swap the top N items with the next N.
|
|
122
|
+
# OP_SWAP (n=1): [x1 x2] -> [x2 x1]
|
|
123
|
+
# OP_2SWAP (n=2): [x1 x2 x3 x4] -> [x3 x4 x1 x2]
|
|
124
|
+
def swap_n(count)
|
|
125
|
+
check_count!(count, 'swap_n')
|
|
126
|
+
stack_error!("stack too small for swap_n(#{count})") if @items.length < (2 * count)
|
|
127
|
+
|
|
128
|
+
count.times do |i|
|
|
129
|
+
a = @items.length - count + i
|
|
130
|
+
b = @items.length - (2 * count) + i
|
|
131
|
+
@items[a], @items[b] = @items[b], @items[a]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Copy N items from 2N depth to top.
|
|
136
|
+
# OP_OVER (n=1): [x1 x2] -> [x1 x2 x1]
|
|
137
|
+
# OP_2OVER (n=2): [x1 x2 x3 x4] -> [x1 x2 x3 x4 x1 x2]
|
|
138
|
+
def over_n(count)
|
|
139
|
+
check_count!(count, 'over_n')
|
|
140
|
+
stack_error!("stack too small for over_n(#{count})") if @items.length < (2 * count)
|
|
141
|
+
|
|
142
|
+
start = @items.length - (2 * count)
|
|
143
|
+
count.times { |i| @items.push(@items[start + i].dup) }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Copy item at index n to top (0 = top).
|
|
147
|
+
def pick_n(idx)
|
|
148
|
+
check_index!(idx)
|
|
149
|
+
|
|
150
|
+
@items.push(@items[@items.length - 1 - idx].dup)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Move item at index n to top (0 = top).
|
|
154
|
+
def roll_n(idx)
|
|
155
|
+
check_index!(idx)
|
|
156
|
+
|
|
157
|
+
val = @items.delete_at(@items.length - 1 - idx)
|
|
158
|
+
@items.push(val)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Copy top and insert before second: [x1 x2] -> [x2 x1 x2]
|
|
162
|
+
def tuck
|
|
163
|
+
stack_error!('stack too small for tuck') if @items.length < 2
|
|
164
|
+
|
|
165
|
+
@items.insert(@items.length - 2, @items.last.dup)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# --- Boolean conversion ---
|
|
169
|
+
|
|
170
|
+
# Bitcoin consensus boolean: false if empty, all-zero, or negative zero (0x80 last byte).
|
|
171
|
+
def self.cast_bool(bytes) # rubocop:disable Naming/PredicateMethod
|
|
172
|
+
return false if bytes.nil? || bytes.empty?
|
|
173
|
+
|
|
174
|
+
bytes.each_byte.with_index do |byte, i|
|
|
175
|
+
next if byte.zero?
|
|
176
|
+
|
|
177
|
+
# Negative zero: last byte is exactly 0x80
|
|
178
|
+
return !(i == bytes.bytesize - 1 && byte == 0x80)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
false
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
private
|
|
185
|
+
|
|
186
|
+
def stack_error!(message)
|
|
187
|
+
raise ScriptError.new(ScriptErrorCode::INVALID_STACK_OPERATION, message)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def check_index!(idx)
|
|
191
|
+
return if idx >= 0 && idx < @items.length
|
|
192
|
+
|
|
193
|
+
stack_error!("index #{idx} invalid for stack size #{@items.length}")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def check_count!(count, operation)
|
|
197
|
+
return if count >= 1
|
|
198
|
+
|
|
199
|
+
stack_error!("#{operation} requires n >= 1, got #{count}")
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Script
|
|
5
|
+
# All Bitcoin script opcodes as integer constants.
|
|
6
|
+
#
|
|
7
|
+
# Includes data push operations, flow control, stack manipulation,
|
|
8
|
+
# splice, bitwise, arithmetic, and cryptographic opcodes. Also
|
|
9
|
+
# provides {.name_for} for reverse lookup (opcode byte → name).
|
|
10
|
+
module Opcodes
|
|
11
|
+
# Push value
|
|
12
|
+
OP_0 = 0x00
|
|
13
|
+
OP_FALSE = OP_0
|
|
14
|
+
OP_PUSHDATA1 = 0x4c
|
|
15
|
+
OP_PUSHDATA2 = 0x4d
|
|
16
|
+
OP_PUSHDATA4 = 0x4e
|
|
17
|
+
OP_1NEGATE = 0x4f
|
|
18
|
+
OP_RESERVED = 0x50
|
|
19
|
+
OP_1 = 0x51
|
|
20
|
+
OP_TRUE = OP_1
|
|
21
|
+
OP_2 = 0x52
|
|
22
|
+
OP_3 = 0x53
|
|
23
|
+
OP_4 = 0x54
|
|
24
|
+
OP_5 = 0x55
|
|
25
|
+
OP_6 = 0x56
|
|
26
|
+
OP_7 = 0x57
|
|
27
|
+
OP_8 = 0x58
|
|
28
|
+
OP_9 = 0x59
|
|
29
|
+
OP_10 = 0x5a
|
|
30
|
+
OP_11 = 0x5b
|
|
31
|
+
OP_12 = 0x5c
|
|
32
|
+
OP_13 = 0x5d
|
|
33
|
+
OP_14 = 0x5e
|
|
34
|
+
OP_15 = 0x5f
|
|
35
|
+
OP_16 = 0x60
|
|
36
|
+
|
|
37
|
+
# Flow control
|
|
38
|
+
OP_NOP = 0x61
|
|
39
|
+
OP_VER = 0x62
|
|
40
|
+
OP_IF = 0x63
|
|
41
|
+
OP_NOTIF = 0x64
|
|
42
|
+
OP_VERIF = 0x65
|
|
43
|
+
OP_VERNOTIF = 0x66
|
|
44
|
+
OP_ELSE = 0x67
|
|
45
|
+
OP_ENDIF = 0x68
|
|
46
|
+
OP_VERIFY = 0x69
|
|
47
|
+
OP_RETURN = 0x6a
|
|
48
|
+
|
|
49
|
+
# Stack
|
|
50
|
+
OP_TOALTSTACK = 0x6b
|
|
51
|
+
OP_FROMALTSTACK = 0x6c
|
|
52
|
+
OP_2DROP = 0x6d
|
|
53
|
+
OP_2DUP = 0x6e
|
|
54
|
+
OP_3DUP = 0x6f
|
|
55
|
+
OP_2OVER = 0x70
|
|
56
|
+
OP_2ROT = 0x71
|
|
57
|
+
OP_2SWAP = 0x72
|
|
58
|
+
OP_IFDUP = 0x73
|
|
59
|
+
OP_DEPTH = 0x74
|
|
60
|
+
OP_DROP = 0x75
|
|
61
|
+
OP_DUP = 0x76
|
|
62
|
+
OP_NIP = 0x77
|
|
63
|
+
OP_OVER = 0x78
|
|
64
|
+
OP_PICK = 0x79
|
|
65
|
+
OP_ROLL = 0x7a
|
|
66
|
+
OP_ROT = 0x7b
|
|
67
|
+
OP_SWAP = 0x7c
|
|
68
|
+
OP_TUCK = 0x7d
|
|
69
|
+
|
|
70
|
+
# Splice
|
|
71
|
+
OP_CAT = 0x7e
|
|
72
|
+
OP_SPLIT = 0x7f
|
|
73
|
+
OP_NUM2BIN = 0x80
|
|
74
|
+
OP_BIN2NUM = 0x81
|
|
75
|
+
OP_SIZE = 0x82
|
|
76
|
+
|
|
77
|
+
# Bitwise logic
|
|
78
|
+
OP_INVERT = 0x83
|
|
79
|
+
OP_AND = 0x84
|
|
80
|
+
OP_OR = 0x85
|
|
81
|
+
OP_XOR = 0x86
|
|
82
|
+
OP_EQUAL = 0x87
|
|
83
|
+
OP_EQUALVERIFY = 0x88
|
|
84
|
+
OP_RESERVED1 = 0x89
|
|
85
|
+
OP_RESERVED2 = 0x8a
|
|
86
|
+
|
|
87
|
+
# Arithmetic
|
|
88
|
+
OP_1ADD = 0x8b
|
|
89
|
+
OP_1SUB = 0x8c
|
|
90
|
+
OP_2MUL = 0x8d
|
|
91
|
+
OP_2DIV = 0x8e
|
|
92
|
+
OP_NEGATE = 0x8f
|
|
93
|
+
OP_ABS = 0x90
|
|
94
|
+
OP_NOT = 0x91
|
|
95
|
+
OP_0NOTEQUAL = 0x92
|
|
96
|
+
OP_ADD = 0x93
|
|
97
|
+
OP_SUB = 0x94
|
|
98
|
+
OP_MUL = 0x95
|
|
99
|
+
OP_DIV = 0x96
|
|
100
|
+
OP_MOD = 0x97
|
|
101
|
+
OP_LSHIFT = 0x98
|
|
102
|
+
OP_RSHIFT = 0x99
|
|
103
|
+
OP_BOOLAND = 0x9a
|
|
104
|
+
OP_BOOLOR = 0x9b
|
|
105
|
+
OP_NUMEQUAL = 0x9c
|
|
106
|
+
OP_NUMEQUALVERIFY = 0x9d
|
|
107
|
+
OP_NUMNOTEQUAL = 0x9e
|
|
108
|
+
OP_LESSTHAN = 0x9f
|
|
109
|
+
OP_GREATERTHAN = 0xa0
|
|
110
|
+
OP_LESSTHANOREQUAL = 0xa1
|
|
111
|
+
OP_GREATERTHANOREQUAL = 0xa2
|
|
112
|
+
OP_MIN = 0xa3
|
|
113
|
+
OP_MAX = 0xa4
|
|
114
|
+
OP_WITHIN = 0xa5
|
|
115
|
+
|
|
116
|
+
# Crypto
|
|
117
|
+
OP_RIPEMD160 = 0xa6
|
|
118
|
+
OP_SHA1 = 0xa7
|
|
119
|
+
OP_SHA256 = 0xa8
|
|
120
|
+
OP_HASH160 = 0xa9
|
|
121
|
+
OP_HASH256 = 0xaa
|
|
122
|
+
OP_CODESEPARATOR = 0xab
|
|
123
|
+
OP_CHECKSIG = 0xac
|
|
124
|
+
OP_CHECKSIGVERIFY = 0xad
|
|
125
|
+
OP_CHECKMULTISIG = 0xae
|
|
126
|
+
OP_CHECKMULTISIGVERIFY = 0xaf
|
|
127
|
+
|
|
128
|
+
# Expansion
|
|
129
|
+
OP_NOP1 = 0xb0
|
|
130
|
+
OP_CHECKLOCKTIMEVERIFY = 0xb1
|
|
131
|
+
OP_CHECKSEQUENCEVERIFY = 0xb2
|
|
132
|
+
OP_NOP4 = 0xb3
|
|
133
|
+
OP_NOP5 = 0xb4
|
|
134
|
+
OP_NOP6 = 0xb5
|
|
135
|
+
OP_NOP7 = 0xb6
|
|
136
|
+
OP_NOP8 = 0xb7
|
|
137
|
+
OP_NOP9 = 0xb8
|
|
138
|
+
OP_NOP10 = 0xb9
|
|
139
|
+
|
|
140
|
+
# Pseudo-words (not used in scripts)
|
|
141
|
+
OP_PUBKEYHASH = 0xfd
|
|
142
|
+
OP_PUBKEY = 0xfe
|
|
143
|
+
OP_INVALIDOPCODE = 0xff
|
|
144
|
+
|
|
145
|
+
# Reverse lookup: opcode byte → name string.
|
|
146
|
+
# Sorted so canonical names (OP_0, OP_1) win over aliases (OP_FALSE, OP_TRUE)
|
|
147
|
+
# regardless of Module#constants enumeration order.
|
|
148
|
+
NAME = constants
|
|
149
|
+
.select { |c| c.to_s.start_with?('OP_') }
|
|
150
|
+
.sort
|
|
151
|
+
.each_with_object({}) { |c, h| h[const_get(c)] ||= c.to_s }
|
|
152
|
+
.freeze
|
|
153
|
+
|
|
154
|
+
module_function
|
|
155
|
+
|
|
156
|
+
# Look up the canonical name for an opcode byte.
|
|
157
|
+
#
|
|
158
|
+
# @param opcode [Integer] the opcode byte value
|
|
159
|
+
# @return [String, nil] the opcode name (e.g. +"OP_DUP"+), or +nil+ if unknown
|
|
160
|
+
def name_for(opcode)
|
|
161
|
+
NAME[opcode]
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|