bsv-sdk 0.25.0 → 0.26.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 +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +4 -2
- data/lib/bsv/primitives/digest.rb +107 -13
- data/lib/bsv/script/interpreter/error.rb +2 -0
- data/lib/bsv/script/interpreter/interpreter.rb +226 -7
- data/lib/bsv/script/interpreter/operations/flow_control.rb +30 -13
- data/lib/bsv/script/script.rb +21 -2
- data/lib/bsv/storage/downloader.rb +1 -1
- data/lib/bsv/storage/utils.rb +1 -1
- data/lib/bsv/transaction/transaction_input.rb +77 -13
- data/lib/bsv/transaction/transaction_output.rb +46 -6
- data/lib/bsv/transaction/tx.rb +359 -28
- data/lib/bsv/version.rb +1 -1
- metadata +1 -2
- data/lib/bsv/secp256k1_native.bundle +0 -0
|
@@ -42,12 +42,21 @@ module BSV
|
|
|
42
42
|
@else_stack.push(false)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
# OP_ELSE: toggle conditional branch
|
|
45
|
+
# OP_ELSE: toggle the current conditional branch.
|
|
46
|
+
#
|
|
47
|
+
# Multi-ELSE handling matches the TS SDK (Spend.ts OP_ELSE case):
|
|
48
|
+
# - In relaxed mode (no explicit flags), each ELSE toggles the branch —
|
|
49
|
+
# any number of ELSEs per IF is valid. This is the script-021 case.
|
|
50
|
+
# - With explicit post-genesis flags, only one ELSE per IF is permitted;
|
|
51
|
+
# a second ELSE raises UNBALANCED_CONDITIONAL. Vectors such as
|
|
52
|
+
# node.script.bitcoin-sv.0731 and teranode.0057 enforce this.
|
|
46
53
|
def op_else
|
|
47
54
|
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'OP_ELSE without matching OP_IF') if @cond_stack.empty?
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
already_seen_else = @else_stack.pop
|
|
57
|
+
if already_seen_else && explicit_flags? && after_genesis?
|
|
58
|
+
raise ScriptError.new(ScriptErrorCode::UNBALANCED_CONDITIONAL, 'duplicate OP_ELSE')
|
|
59
|
+
end
|
|
51
60
|
|
|
52
61
|
case @cond_stack.last
|
|
53
62
|
when :true then @cond_stack[-1] = :false
|
|
@@ -97,20 +106,23 @@ module BSV
|
|
|
97
106
|
|
|
98
107
|
# OP_VER: push 4-byte little-endian transaction version onto the stack.
|
|
99
108
|
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
109
|
+
# Resolves the version from +@tx.version+ (when verifying a
|
|
110
|
+
# transaction) or from the +tx_version:+ parameter passed to
|
|
111
|
+
# +Interpreter.evaluate+. Raises MISSING_TX_CONTEXT only if neither
|
|
112
|
+
# is available.
|
|
102
113
|
#
|
|
103
114
|
# The version is always encoded as exactly 4 bytes (LE uint32), never as
|
|
104
115
|
# a ScriptNumber. Version 1 → 01000000, version 2 → 02000000.
|
|
105
116
|
def op_ver
|
|
106
|
-
|
|
117
|
+
bytes = tx_version_bytes
|
|
118
|
+
if bytes.nil?
|
|
107
119
|
raise ScriptError.new(
|
|
108
120
|
ScriptErrorCode::MISSING_TX_CONTEXT,
|
|
109
121
|
'OP_VER requires transaction context'
|
|
110
122
|
)
|
|
111
123
|
end
|
|
112
124
|
|
|
113
|
-
@dstack.push_bytes(
|
|
125
|
+
@dstack.push_bytes(bytes)
|
|
114
126
|
end
|
|
115
127
|
|
|
116
128
|
# OP_VERIF: version-conditional branch — executes like OP_IF but compares
|
|
@@ -166,20 +178,25 @@ module BSV
|
|
|
166
178
|
end
|
|
167
179
|
|
|
168
180
|
# Returns the transaction version as a 4-byte little-endian binary
|
|
169
|
-
# string. Used by OP_VER,
|
|
181
|
+
# string, or +nil+ if no version is available. Used by OP_VER,
|
|
182
|
+
# OP_VERIF, and OP_VERNOTIF. Prefers +@tx.version+ when verifying a
|
|
183
|
+
# transaction; falls back to the +tx_version:+ argument used by
|
|
184
|
+
# evaluate-without-tx (e.g. conformance corpus runs).
|
|
170
185
|
def tx_version_bytes
|
|
171
|
-
|
|
186
|
+
v = @tx&.version || @tx_version
|
|
187
|
+
v.nil? ? nil : [v].pack('V')
|
|
172
188
|
end
|
|
173
189
|
|
|
174
190
|
# Compares raw bytes against the current transaction version.
|
|
175
191
|
# Returns false if:
|
|
176
192
|
# - +data+ is not exactly 4 bytes
|
|
177
|
-
# -
|
|
178
|
-
# - the bytes do not match the 4-byte LE encoding of
|
|
193
|
+
# - no version is available (neither @tx nor @tx_version set)
|
|
194
|
+
# - the bytes do not match the 4-byte LE encoding of the version
|
|
179
195
|
def tx_version_matches?(data)
|
|
180
|
-
return false if
|
|
196
|
+
return false if data.bytesize != 4
|
|
181
197
|
|
|
182
|
-
|
|
198
|
+
bytes = tx_version_bytes
|
|
199
|
+
!bytes.nil? && data == bytes
|
|
183
200
|
end
|
|
184
201
|
end
|
|
185
202
|
end
|
data/lib/bsv/script/script.rb
CHANGED
|
@@ -22,12 +22,20 @@ module BSV
|
|
|
22
22
|
# script.p2pkh? #=> true
|
|
23
23
|
# script.to_asm #=> "OP_DUP OP_HASH160 ... OP_EQUALVERIFY OP_CHECKSIG"
|
|
24
24
|
class Script
|
|
25
|
-
# @return [String] the raw script bytes
|
|
25
|
+
# @return [String] the raw script bytes (frozen, binary encoding)
|
|
26
|
+
# @note Scripts are protocol-level immutable; the underlying bytes are
|
|
27
|
+
# defensively copied + frozen on construction. Downstream callers
|
|
28
|
+
# that need to build a script incrementally must compose bytes
|
|
29
|
+
# into a new String and pass it to a fresh +Script.new+.
|
|
26
30
|
attr_reader :bytes
|
|
27
31
|
|
|
28
32
|
# @param bytes [String] raw script bytes (default: empty)
|
|
29
33
|
def initialize(bytes = ''.b)
|
|
30
|
-
|
|
34
|
+
# Defensive copy + freeze: scripts are immutable in the protocol; freezing
|
|
35
|
+
# @bytes prevents external in-place mutation from staling caches that
|
|
36
|
+
# depend on the script's serialisation (see
|
|
37
|
+
# docs/reference/sighash-cache.md).
|
|
38
|
+
@bytes = bytes.b.dup.freeze
|
|
31
39
|
@chunks = nil
|
|
32
40
|
end
|
|
33
41
|
|
|
@@ -460,6 +468,17 @@ module BSV
|
|
|
460
468
|
b.getbyte(22) == Opcodes::OP_EQUAL
|
|
461
469
|
end
|
|
462
470
|
|
|
471
|
+
# Whether the script consists entirely of push-data operations.
|
|
472
|
+
#
|
|
473
|
+
# A script is push-only iff every chunk's opcode is at most +OP_16+
|
|
474
|
+
# (0x60). Used by the script interpreter to enforce the +SIGPUSHONLY+
|
|
475
|
+
# verification flag on unlock scripts.
|
|
476
|
+
#
|
|
477
|
+
# @return [Boolean]
|
|
478
|
+
def push_only?
|
|
479
|
+
chunks.all? { |chunk| chunk.opcode <= Opcodes::OP_16 }
|
|
480
|
+
end
|
|
481
|
+
|
|
463
482
|
# Whether this is an OP_RETURN data carrier script.
|
|
464
483
|
#
|
|
465
484
|
# Matches both +OP_RETURN ...+ and +OP_FALSE OP_RETURN ...+ forms.
|
|
@@ -145,7 +145,7 @@ module BSV
|
|
|
145
145
|
body = response.body.to_s
|
|
146
146
|
return nil if body.empty?
|
|
147
147
|
|
|
148
|
-
actual_hash =
|
|
148
|
+
actual_hash = BSV::Primitives::Digest.sha256(body)
|
|
149
149
|
return nil unless actual_hash == expected_hash
|
|
150
150
|
|
|
151
151
|
DownloadResult.new(data: body, mime_type: response['Content-Type'])
|
data/lib/bsv/storage/utils.rb
CHANGED
|
@@ -38,7 +38,7 @@ module BSV
|
|
|
38
38
|
# @param data [String] binary file content
|
|
39
39
|
# @return [String] Base58Check-encoded UHRP URL
|
|
40
40
|
def get_url_for_file(data)
|
|
41
|
-
hash =
|
|
41
|
+
hash = BSV::Primitives::Digest.sha256(data.b)
|
|
42
42
|
get_url_for_hash(hash)
|
|
43
43
|
end
|
|
44
44
|
|
|
@@ -14,19 +14,42 @@ module BSV
|
|
|
14
14
|
# @return [Integer] index of the output within the previous transaction
|
|
15
15
|
attr_reader :prev_tx_out_index
|
|
16
16
|
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
#
|
|
21
|
-
|
|
17
|
+
# @!attribute [rw] sequence
|
|
18
|
+
# @return [Integer] sequence number (default: 0xFFFFFFFF)
|
|
19
|
+
# @note Setting this invalidates the owning Tx's wire cache and the
|
|
20
|
+
# hash_sequence component of the sighash cache. See
|
|
21
|
+
# {file:docs/reference/sighash-cache.md}.
|
|
22
|
+
attr_reader :sequence
|
|
23
|
+
|
|
24
|
+
# @!attribute [rw] unlocking_script
|
|
25
|
+
# @return [Script::Script, nil] the unlocking script (set after signing)
|
|
26
|
+
# @note Setting this invalidates the owning Tx's wire cache only.
|
|
27
|
+
# The unlocking script does not enter the BIP-143 preimage, so the
|
|
28
|
+
# sighash component caches are not touched. See
|
|
29
|
+
# {file:docs/reference/sighash-cache.md}.
|
|
30
|
+
attr_reader :unlocking_script
|
|
22
31
|
|
|
23
32
|
# @return [Integer, nil] satoshi value of the source output (needed for sighash)
|
|
33
|
+
# @note Enters the BIP-143 preimage at step 6 but no current cache layer
|
|
34
|
+
# memoises the per-input preimage, so no invalidator is required. If a
|
|
35
|
+
# future change adds preimage or per-input digest memoisation, add a
|
|
36
|
+
# setter override here that invalidates the relevant cache slice. See
|
|
37
|
+
# {file:docs/reference/sighash-cache.md}.
|
|
24
38
|
attr_accessor :source_satoshis
|
|
25
39
|
|
|
26
40
|
# @return [Script::Script, nil] locking script of the source output (needed for sighash)
|
|
41
|
+
# @note Enters the BIP-143 preimage as scriptCode (step 5) when no
|
|
42
|
+
# subscript override is supplied. Same caveat as {#source_satoshis}:
|
|
43
|
+
# no current cache layer depends on this field, but a future preimage
|
|
44
|
+
# cache would need a setter override here. See
|
|
45
|
+
# {file:docs/reference/sighash-cache.md}.
|
|
27
46
|
attr_accessor :source_locking_script
|
|
28
47
|
|
|
29
48
|
# @return [Transaction::Tx, nil] the full source transaction (for BEEF wiring)
|
|
49
|
+
# @note Source data is lazily resolved from this Tx during {Tx#verify}
|
|
50
|
+
# and {Tx#sighash_preimage}. Mutation of this field after resolution
|
|
51
|
+
# has occurred has no effect on caches because no current cache layer
|
|
52
|
+
# depends on resolved source data.
|
|
30
53
|
attr_accessor :source_transaction
|
|
31
54
|
|
|
32
55
|
# @return [UnlockingScriptTemplate, nil] template for deferred signing
|
|
@@ -38,23 +61,60 @@ module BSV
|
|
|
38
61
|
# @param sequence [Integer] sequence number
|
|
39
62
|
def initialize(prev_wtxid:, prev_tx_out_index:, unlocking_script: nil, sequence: 0xFFFFFFFF)
|
|
40
63
|
BSV::Primitives::Hex.validate_wtxid!(prev_wtxid, name: 'prev_wtxid')
|
|
41
|
-
|
|
64
|
+
# Defensively copy + freeze so external mutation of the caller's String
|
|
65
|
+
# cannot stale the cached outpoint_binary / to_binary.
|
|
66
|
+
@prev_wtxid = prev_wtxid.b.dup.freeze
|
|
42
67
|
@prev_tx_out_index = prev_tx_out_index
|
|
43
68
|
@unlocking_script = unlocking_script
|
|
44
69
|
@sequence = sequence
|
|
70
|
+
@owning_tx = nil
|
|
45
71
|
BSV.logger&.debug { "[TransactionInput] prev_wtxid set: #{dtxid_hex}:#{@prev_tx_out_index}" }
|
|
46
72
|
end
|
|
47
73
|
|
|
74
|
+
# Called by +#dup+ and +#clone+. Clears the owning-Tx backref so that the
|
|
75
|
+
# cloned input does not belong to any transaction until it is explicitly
|
|
76
|
+
# added via +Tx#add_input+.
|
|
77
|
+
def initialize_copy(other)
|
|
78
|
+
super
|
|
79
|
+
@owning_tx = nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Sets the sequence number and invalidates the L1 binary memo and any
|
|
83
|
+
# owning-Tx slice caches that incorporate sequence (BIP-143 preimage
|
|
84
|
+
# and wire format).
|
|
85
|
+
#
|
|
86
|
+
# @param value [Integer] new sequence number
|
|
87
|
+
def sequence=(value)
|
|
88
|
+
@sequence = value
|
|
89
|
+
@to_binary = nil
|
|
90
|
+
@owning_tx&.send(:invalidate_sequence_components_cache)
|
|
91
|
+
@owning_tx&.send(:invalidate_wire_cache)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Sets the unlocking script and invalidates the L1 binary memo and the
|
|
95
|
+
# owning-Tx wire cache. Unlocking script does not enter the BIP-143
|
|
96
|
+
# preimage, so the sequence/outputs components caches are not touched.
|
|
97
|
+
#
|
|
98
|
+
# @param value [Script::Script, nil] new unlocking script
|
|
99
|
+
def unlocking_script=(value)
|
|
100
|
+
@unlocking_script = value
|
|
101
|
+
@to_binary = nil
|
|
102
|
+
@owning_tx&.send(:invalidate_wire_cache)
|
|
103
|
+
end
|
|
104
|
+
|
|
48
105
|
# Serialise the input to its binary wire format.
|
|
49
106
|
#
|
|
107
|
+
# @note Memoised; see {file:docs/reference/sighash-cache.md} for the invalidation contract.
|
|
50
108
|
# @return [String] binary input (outpoint + varint + script + sequence)
|
|
51
109
|
def to_binary
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
110
|
+
@to_binary ||= begin
|
|
111
|
+
script_bytes = @unlocking_script ? @unlocking_script.to_binary : ''.b
|
|
112
|
+
(@prev_wtxid +
|
|
113
|
+
[@prev_tx_out_index].pack('V') +
|
|
114
|
+
VarInt.encode(script_bytes.bytesize) +
|
|
115
|
+
script_bytes +
|
|
116
|
+
[@sequence].pack('V')).freeze
|
|
117
|
+
end
|
|
58
118
|
end
|
|
59
119
|
|
|
60
120
|
# Deserialise a transaction input from binary data.
|
|
@@ -113,9 +173,13 @@ module BSV
|
|
|
113
173
|
|
|
114
174
|
# Serialise the outpoint (prev_wtxid + output index) as binary.
|
|
115
175
|
#
|
|
176
|
+
# Memoised: outpoint components are +attr_reader+ only so the value is
|
|
177
|
+
# immutable after construction. Returns a frozen binary string.
|
|
178
|
+
#
|
|
179
|
+
# @note Memoised; see {file:docs/reference/sighash-cache.md} for the invalidation contract.
|
|
116
180
|
# @return [String] 36-byte outpoint
|
|
117
181
|
def outpoint_binary
|
|
118
|
-
@prev_wtxid + [@prev_tx_out_index].pack('V')
|
|
182
|
+
@outpoint_binary ||= (@prev_wtxid + [@prev_tx_out_index].pack('V')).freeze
|
|
119
183
|
end
|
|
120
184
|
|
|
121
185
|
# The previous transaction ID in display-order hex.
|
|
@@ -8,11 +8,17 @@ module BSV
|
|
|
8
8
|
# Outputs are consumed by transaction inputs that provide matching
|
|
9
9
|
# unlocking scripts.
|
|
10
10
|
class TransactionOutput
|
|
11
|
-
#
|
|
12
|
-
|
|
11
|
+
# @!attribute [rw] satoshis
|
|
12
|
+
# @return [Integer] the output value in satoshis
|
|
13
|
+
# @note Setting this invalidates the owning Tx's outputs-components
|
|
14
|
+
# and wire caches. See {file:docs/reference/sighash-cache.md}.
|
|
15
|
+
attr_reader :satoshis
|
|
13
16
|
|
|
14
|
-
#
|
|
15
|
-
|
|
17
|
+
# @!attribute [rw] locking_script
|
|
18
|
+
# @return [Script::Script] the locking script (spending conditions)
|
|
19
|
+
# @note Setting this invalidates the owning Tx's outputs-components
|
|
20
|
+
# and wire caches. See {file:docs/reference/sighash-cache.md}.
|
|
21
|
+
attr_reader :locking_script
|
|
16
22
|
|
|
17
23
|
# @return [Boolean] whether this output receives change
|
|
18
24
|
attr_accessor :change
|
|
@@ -24,14 +30,48 @@ module BSV
|
|
|
24
30
|
@satoshis = satoshis
|
|
25
31
|
@locking_script = locking_script
|
|
26
32
|
@change = change
|
|
33
|
+
@owning_tx = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Called by +#dup+ and +#clone+. Clears the owning-Tx backref so that the
|
|
37
|
+
# cloned output does not belong to any transaction until it is explicitly
|
|
38
|
+
# added via +Tx#add_output+.
|
|
39
|
+
def initialize_copy(other)
|
|
40
|
+
super
|
|
41
|
+
@owning_tx = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Sets the satoshi value and invalidates the L1 binary memo and the
|
|
45
|
+
# owning-Tx outputs-components and wire caches.
|
|
46
|
+
#
|
|
47
|
+
# @param value [Integer] new satoshi value
|
|
48
|
+
def satoshis=(value)
|
|
49
|
+
@satoshis = value
|
|
50
|
+
@to_binary = nil
|
|
51
|
+
@owning_tx&.send(:invalidate_outputs_components_cache)
|
|
52
|
+
@owning_tx&.send(:invalidate_wire_cache)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Sets the locking script and invalidates the L1 binary memo and the
|
|
56
|
+
# owning-Tx outputs-components and wire caches.
|
|
57
|
+
#
|
|
58
|
+
# @param value [Script::Script] new locking script
|
|
59
|
+
def locking_script=(value)
|
|
60
|
+
@locking_script = value
|
|
61
|
+
@to_binary = nil
|
|
62
|
+
@owning_tx&.send(:invalidate_outputs_components_cache)
|
|
63
|
+
@owning_tx&.send(:invalidate_wire_cache)
|
|
27
64
|
end
|
|
28
65
|
|
|
29
66
|
# Serialise the output to its binary wire format.
|
|
30
67
|
#
|
|
68
|
+
# @note Memoised; see {file:docs/reference/sighash-cache.md} for the invalidation contract.
|
|
31
69
|
# @return [String] binary output (8-byte LE satoshis + varint + script)
|
|
32
70
|
def to_binary
|
|
33
|
-
|
|
34
|
-
|
|
71
|
+
@to_binary ||= begin
|
|
72
|
+
script_bytes = @locking_script.to_binary
|
|
73
|
+
([@satoshis].pack('Q<') + VarInt.encode(script_bytes.bytesize) + script_bytes).freeze
|
|
74
|
+
end
|
|
35
75
|
end
|
|
36
76
|
|
|
37
77
|
# Deserialise a transaction output from binary data.
|