bsv-sdk 0.15.0 → 0.17.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 +38 -0
- data/lib/bsv/auth/auth_middleware.rb +6 -6
- data/lib/bsv/auth/certificate.rb +22 -18
- data/lib/bsv/auth/master_certificate.rb +5 -5
- data/lib/bsv/auth/nonce.rb +13 -13
- data/lib/bsv/auth/peer.rb +53 -53
- data/lib/bsv/auth/verifiable_certificate.rb +1 -1
- data/lib/bsv/identity/client.rb +27 -32
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +18 -12
- data/lib/bsv/mcp/tools/check_balance.rb +16 -4
- data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
- data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
- data/lib/bsv/mcp/tools/helpers.rb +2 -2
- data/lib/bsv/network/arc.rb +13 -153
- data/lib/bsv/network/broadcast_error.rb +1 -0
- data/lib/bsv/network/broadcast_response.rb +1 -0
- data/lib/bsv/network/protocols/arc.rb +4 -3
- data/lib/bsv/network/protocols/taal_binary.rb +1 -0
- data/lib/bsv/network/protocols/woc_rest.rb +2 -1
- data/lib/bsv/network/whats_on_chain.rb +13 -107
- data/lib/bsv/overlay/admin_token_template.rb +4 -4
- data/lib/bsv/overlay/lookup_resolver.rb +1 -0
- data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
- data/lib/bsv/overlay/types.rb +1 -0
- data/lib/bsv/primitives/hex.rb +64 -0
- data/lib/bsv/registry/client.rb +26 -28
- data/lib/bsv/registry/types.rb +1 -0
- data/lib/bsv/script/interpreter/interpreter.rb +7 -0
- data/lib/bsv/script/interpreter/operations/crypto.rb +7 -1
- data/lib/bsv/script/push_drop_template.rb +4 -4
- data/lib/bsv/transaction/beef.rb +122 -83
- data/lib/bsv/transaction/merkle_path.rb +54 -38
- data/lib/bsv/transaction/transaction.rb +81 -30
- data/lib/bsv/transaction/transaction_input.rb +23 -18
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/errors.rb +47 -0
- data/lib/bsv/wallet/interface/brc100.rb +270 -0
- data/lib/bsv/wallet/interface.rb +9 -0
- data/lib/bsv/wallet/proto_wallet/key_deriver.rb +152 -0
- data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
- data/lib/bsv/wallet/proto_wallet.rb +327 -0
- data/lib/bsv/wallet.rb +16 -0
- data/lib/bsv-sdk.rb +18 -1
- metadata +22 -1
data/lib/bsv/registry/client.rb
CHANGED
|
@@ -66,18 +66,16 @@ module BSV
|
|
|
66
66
|
|
|
67
67
|
basket_name = basket_name_for(definition_type)
|
|
68
68
|
create_result = @wallet.create_action(
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
options: { randomize_outputs: false }
|
|
80
|
-
},
|
|
69
|
+
description: "Register a new #{definition_type} definition",
|
|
70
|
+
outputs: [
|
|
71
|
+
{
|
|
72
|
+
satoshis: Constants::TOKEN_AMOUNT,
|
|
73
|
+
locking_script: locking_script.to_hex,
|
|
74
|
+
output_description: "New #{definition_type} registration token",
|
|
75
|
+
basket: basket_name
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
options: { randomize_outputs: false },
|
|
81
79
|
originator: @originator
|
|
82
80
|
)
|
|
83
81
|
|
|
@@ -122,7 +120,7 @@ module BSV
|
|
|
122
120
|
def list_own_registry_entries(definition_type)
|
|
123
121
|
basket_name = basket_name_for(definition_type)
|
|
124
122
|
list_result = @wallet.list_outputs(
|
|
125
|
-
|
|
123
|
+
basket: basket_name, include: 'entire transactions',
|
|
126
124
|
originator: @originator
|
|
127
125
|
)
|
|
128
126
|
|
|
@@ -157,21 +155,20 @@ module BSV
|
|
|
157
155
|
verify_ownership(registered_definition)
|
|
158
156
|
|
|
159
157
|
definition_type = registered_definition.definition_type
|
|
158
|
+
# Registry API boundary: outpoint uses display-order hex txid from RegisteredDefinition
|
|
160
159
|
outpoint = "#{registered_definition.txid}.#{registered_definition.output_index}"
|
|
161
160
|
|
|
162
161
|
create_result = @wallet.create_action(
|
|
163
|
-
{
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
options: { randomize_outputs: false, no_send: true }
|
|
174
|
-
},
|
|
162
|
+
description: "Revoke #{definition_type} definition: #{item_identifier(registered_definition)}",
|
|
163
|
+
input_beef: registered_definition.beef,
|
|
164
|
+
inputs: [
|
|
165
|
+
{
|
|
166
|
+
outpoint: outpoint,
|
|
167
|
+
unlocking_script_length: BSV::Script::PushDropTemplate::Unlocker::ESTIMATED_LENGTH,
|
|
168
|
+
input_description: "Revoking #{definition_type} token"
|
|
169
|
+
}
|
|
170
|
+
],
|
|
171
|
+
options: { randomize_outputs: false, no_send: true },
|
|
175
172
|
originator: @originator
|
|
176
173
|
)
|
|
177
174
|
|
|
@@ -210,7 +207,7 @@ module BSV
|
|
|
210
207
|
def identity_key
|
|
211
208
|
return @identity_key if defined?(@identity_key)
|
|
212
209
|
|
|
213
|
-
result = @wallet.get_public_key(
|
|
210
|
+
result = @wallet.get_public_key(identity_key: true, originator: @originator)
|
|
214
211
|
@identity_key = result[:public_key] || result['public_key'] || result['publicKey'] || result[:publicKey]
|
|
215
212
|
end
|
|
216
213
|
|
|
@@ -419,7 +416,7 @@ module BSV
|
|
|
419
416
|
|
|
420
417
|
RegisteredDefinition.new(
|
|
421
418
|
definition_data: definition_data,
|
|
422
|
-
txid: tx.txid_hex,
|
|
419
|
+
txid: tx.txid_hex, # Registry API boundary: display-order hex txid
|
|
423
420
|
output_index: output_idx,
|
|
424
421
|
locking_script: locking_script.to_hex,
|
|
425
422
|
beef: beef_raw,
|
|
@@ -438,6 +435,7 @@ module BSV
|
|
|
438
435
|
outpoint_str = output[:outpoint] || output['outpoint']
|
|
439
436
|
return nil unless outpoint_str
|
|
440
437
|
|
|
438
|
+
# Registry API boundary: outpoint string uses display-order hex txid by convention
|
|
441
439
|
txid, output_idx_str = outpoint_str.split('.')
|
|
442
440
|
output_idx = output_idx_str.to_i
|
|
443
441
|
|
|
@@ -561,7 +559,7 @@ module BSV
|
|
|
561
559
|
#
|
|
562
560
|
# @return [Symbol] :mainnet or :testnet
|
|
563
561
|
def wallet_network
|
|
564
|
-
result = @wallet.get_network(
|
|
562
|
+
result = @wallet.get_network(originator: @originator)
|
|
565
563
|
net_str = result[:network] || result['network'] || 'mainnet'
|
|
566
564
|
net_str.to_sym
|
|
567
565
|
end
|
data/lib/bsv/registry/types.rb
CHANGED
|
@@ -188,6 +188,7 @@ module BSV
|
|
|
188
188
|
# @return [String] the definition type (see {DefinitionType})
|
|
189
189
|
attr_reader :definition_type
|
|
190
190
|
|
|
191
|
+
# Registry API boundary: display-order hex txid from the outpoint string held in the registry token.
|
|
191
192
|
# @return [String] transaction ID of the containing UTXO
|
|
192
193
|
attr_reader :txid
|
|
193
194
|
|
|
@@ -89,10 +89,12 @@ module BSV
|
|
|
89
89
|
|
|
90
90
|
def execute
|
|
91
91
|
scripts = [@unlock_script, @lock_script]
|
|
92
|
+
script_names = %w[unlock_script lock_script]
|
|
92
93
|
|
|
93
94
|
scripts.each_with_index do |script, script_idx|
|
|
94
95
|
@current_script = script
|
|
95
96
|
chunks = script.chunks
|
|
97
|
+
BSV.logger&.debug { "[Interpreter] === #{script_names[script_idx]} (#{chunks.length} chunks) ===" }
|
|
96
98
|
|
|
97
99
|
chunks.each_with_index do |chunk, chunk_idx|
|
|
98
100
|
@current_chunk_idx = chunk_idx
|
|
@@ -115,6 +117,7 @@ module BSV
|
|
|
115
117
|
end
|
|
116
118
|
|
|
117
119
|
check_final_stack
|
|
120
|
+
BSV.logger&.debug { "[Interpreter] final stack: #{@dstack.length} items -> success" }
|
|
118
121
|
true
|
|
119
122
|
end
|
|
120
123
|
|
|
@@ -156,6 +159,10 @@ module BSV
|
|
|
156
159
|
return
|
|
157
160
|
end
|
|
158
161
|
|
|
162
|
+
BSV.logger&.debug do
|
|
163
|
+
name = Opcodes.name_for(opcode) || format('0x%02x', opcode)
|
|
164
|
+
"[Interpreter] #{name} (stack: #{@dstack.length})"
|
|
165
|
+
end
|
|
159
166
|
dispatch_opcode(opcode, chunk)
|
|
160
167
|
end
|
|
161
168
|
|
|
@@ -126,7 +126,13 @@ module BSV
|
|
|
126
126
|
|
|
127
127
|
pubkey = BSV::Primitives::PublicKey.from_bytes(pubkey_bytes)
|
|
128
128
|
hash = @tx.sighash(@input_index, sighash_type, subscript: sub_script)
|
|
129
|
-
pubkey.verify(hash, sig)
|
|
129
|
+
result = pubkey.verify(hash, sig)
|
|
130
|
+
BSV.logger&.debug do
|
|
131
|
+
pk_hex = pubkey_bytes.unpack1('H*')
|
|
132
|
+
"[Interpreter] CHECKSIG: sighash_type=0x#{format('%02x', sighash_type)} " \
|
|
133
|
+
"pubkey=#{pk_hex[0, 8]}...#{pk_hex[-4..]} result=#{result}"
|
|
134
|
+
end
|
|
135
|
+
result
|
|
130
136
|
rescue ArgumentError
|
|
131
137
|
false
|
|
132
138
|
end
|
|
@@ -108,14 +108,14 @@ module BSV
|
|
|
108
108
|
counterparty: @counterparty
|
|
109
109
|
}
|
|
110
110
|
orig_kw = @originator ? { originator: @originator } : {}
|
|
111
|
-
result = @wallet.create_signature(sig_args, **orig_kw)
|
|
111
|
+
result = @wallet.create_signature(**sig_args, **orig_kw)
|
|
112
112
|
|
|
113
113
|
sig_bytes = result[:signature].pack('C*')
|
|
114
114
|
sig_with_hashtype = sig_bytes + [sighash_type].pack('C')
|
|
115
115
|
|
|
116
116
|
# Fetch the derived public key so the P2PKH unlock can include it
|
|
117
117
|
pub_args = { protocol_id: @protocol_id, key_id: @key_id, counterparty: @counterparty }
|
|
118
|
-
pub_result = @wallet.get_public_key(pub_args, **orig_kw)
|
|
118
|
+
pub_result = @wallet.get_public_key(**pub_args, **orig_kw)
|
|
119
119
|
pubkey_bytes = [pub_result[:public_key]].pack('H*')
|
|
120
120
|
|
|
121
121
|
BSV::Script::Script.pushdrop_unlock(
|
|
@@ -176,7 +176,7 @@ module BSV
|
|
|
176
176
|
else
|
|
177
177
|
pub_args = { protocol_id: protocol_id, key_id: key_id, counterparty: counterparty }
|
|
178
178
|
orig_kw = @originator ? { originator: @originator } : {}
|
|
179
|
-
pub_result = @wallet.get_public_key(pub_args, **orig_kw)
|
|
179
|
+
pub_result = @wallet.get_public_key(**pub_args, **orig_kw)
|
|
180
180
|
[pub_result[:public_key]].pack('H*')
|
|
181
181
|
end
|
|
182
182
|
|
|
@@ -187,7 +187,7 @@ module BSV
|
|
|
187
187
|
data_to_sign = all_fields.reduce(''.b) { |acc, f| acc + f }.unpack('C*')
|
|
188
188
|
sig_args = { data: data_to_sign, protocol_id: protocol_id, key_id: key_id, counterparty: counterparty }
|
|
189
189
|
orig_kw = @originator ? { originator: @originator } : {}
|
|
190
|
-
sig_result = @wallet.create_signature(sig_args, **orig_kw)
|
|
190
|
+
sig_result = @wallet.create_signature(**sig_args, **orig_kw)
|
|
191
191
|
all_fields << sig_result[:signature].pack('C*')
|
|
192
192
|
end
|
|
193
193
|
|
data/lib/bsv/transaction/beef.rb
CHANGED
|
@@ -13,7 +13,7 @@ module BSV
|
|
|
13
13
|
#
|
|
14
14
|
# @example Parse a BEEF bundle and find a transaction
|
|
15
15
|
# beef = BSV::Transaction::Beef.from_hex(beef_hex)
|
|
16
|
-
# tx = beef.find_transaction(
|
|
16
|
+
# tx = beef.find_transaction(wtxid)
|
|
17
17
|
class Beef
|
|
18
18
|
# @!group Version constants
|
|
19
19
|
|
|
@@ -44,37 +44,53 @@ module BSV
|
|
|
44
44
|
# @return [Transaction, nil] the transaction (nil for TXID-only entries)
|
|
45
45
|
attr_reader :transaction
|
|
46
46
|
|
|
47
|
-
# @return [String, nil] 32-byte
|
|
48
|
-
attr_reader :
|
|
47
|
+
# @return [String, nil] 32-byte wire-order wtxid for TXID-only entries
|
|
48
|
+
attr_reader :known_wtxid
|
|
49
49
|
|
|
50
50
|
# @return [Integer, nil] index into the BEEF bumps array
|
|
51
51
|
attr_reader :bump_index
|
|
52
52
|
|
|
53
53
|
# @param format [Integer] format flag
|
|
54
54
|
# @param transaction [Transaction, nil] the transaction
|
|
55
|
-
# @param
|
|
55
|
+
# @param known_wtxid [String, nil] 32-byte wire-order wtxid for TXID-only entries
|
|
56
56
|
# @param bump_index [Integer, nil] index into the bumps array
|
|
57
57
|
# @raise [ArgumentError] if format is FORMAT_RAW_TX_AND_BUMP without a bump_index
|
|
58
|
-
def initialize(format:, transaction: nil,
|
|
58
|
+
def initialize(format:, transaction: nil, known_wtxid: nil, bump_index: nil)
|
|
59
59
|
raise ArgumentError, 'FORMAT_RAW_TX_AND_BUMP requires a bump_index' if format == FORMAT_RAW_TX_AND_BUMP && bump_index.nil?
|
|
60
60
|
|
|
61
|
+
BSV::Primitives::Hex.validate_wtxid!(known_wtxid, name: 'known_wtxid') if known_wtxid
|
|
61
62
|
@format = format
|
|
62
63
|
@transaction = transaction
|
|
63
|
-
@
|
|
64
|
+
@known_wtxid = known_wtxid
|
|
64
65
|
@bump_index = bump_index
|
|
65
66
|
end
|
|
66
67
|
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
|
|
70
|
-
def txid
|
|
68
|
+
# Wire-order transaction ID.
|
|
69
|
+
# @return [String, nil] 32-byte wtxid
|
|
70
|
+
def wtxid
|
|
71
71
|
case @format
|
|
72
72
|
when FORMAT_TXID_ONLY
|
|
73
|
-
@
|
|
73
|
+
@known_wtxid
|
|
74
74
|
else
|
|
75
|
-
@transaction&.
|
|
75
|
+
@transaction&.wtxid
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
|
+
|
|
79
|
+
# Display-order transaction ID as binary bytes.
|
|
80
|
+
# @return [String, nil] 32-byte display-order txid
|
|
81
|
+
def txid
|
|
82
|
+
wtxid&.reverse
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Display-order transaction ID as a hex string.
|
|
86
|
+
#
|
|
87
|
+
# +dtxid+ always returns a 64-char hex string suitable for JSON
|
|
88
|
+
# and UI boundaries.
|
|
89
|
+
#
|
|
90
|
+
# @return [String, nil] hex-encoded transaction ID (display order)
|
|
91
|
+
def dtxid
|
|
92
|
+
wtxid&.reverse&.unpack1('H*')
|
|
93
|
+
end
|
|
78
94
|
end
|
|
79
95
|
|
|
80
96
|
# @return [Integer] BEEF version constant
|
|
@@ -86,8 +102,21 @@ module BSV
|
|
|
86
102
|
# @return [Array<BeefTx>] the transactions in dependency order
|
|
87
103
|
attr_reader :transactions
|
|
88
104
|
|
|
89
|
-
# @return [String, nil] 32-byte subject txid (Atomic BEEF only)
|
|
90
|
-
attr_reader :
|
|
105
|
+
# @return [String, nil] 32-byte wire-order subject txid (Atomic BEEF only)
|
|
106
|
+
attr_reader :subject_wtxid
|
|
107
|
+
|
|
108
|
+
# Display-order subject txid as binary bytes (Atomic BEEF only).
|
|
109
|
+
# @return [String, nil] 32-byte display-order txid, or nil
|
|
110
|
+
def subject_txid
|
|
111
|
+
@subject_wtxid&.reverse
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Display-order subject txid as a hex string (Atomic BEEF only).
|
|
115
|
+
#
|
|
116
|
+
# @return [String, nil] hex-encoded display-order txid, or nil
|
|
117
|
+
def subject_dtxid
|
|
118
|
+
@subject_wtxid&.reverse&.unpack1('H*')
|
|
119
|
+
end
|
|
91
120
|
|
|
92
121
|
# @param version [Integer] BEEF version constant (default: BEEF_V1, matching to_binary's
|
|
93
122
|
# default for ARC compatibility; from_binary overwrites this with the parsed version)
|
|
@@ -97,7 +126,7 @@ module BSV
|
|
|
97
126
|
@version = version
|
|
98
127
|
@bumps = bumps
|
|
99
128
|
@transactions = transactions
|
|
100
|
-
@
|
|
129
|
+
@subject_wtxid = nil
|
|
101
130
|
end
|
|
102
131
|
|
|
103
132
|
# --- Deserialisation ---
|
|
@@ -133,9 +162,9 @@ module BSV
|
|
|
133
162
|
raise ArgumentError, "truncated Atomic BEEF: need 36 bytes at offset #{offset}, got #{remaining}"
|
|
134
163
|
end
|
|
135
164
|
|
|
136
|
-
# Atomic BEEF stores the subject txid in internal
|
|
137
|
-
#
|
|
138
|
-
beef.instance_variable_set(:@
|
|
165
|
+
# Atomic BEEF stores the subject txid in wire (internal / little-endian) byte order,
|
|
166
|
+
# matching JS and Go SDKs. Store as-is in @subject_wtxid (wire-order).
|
|
167
|
+
beef.instance_variable_set(:@subject_wtxid, data.byteslice(offset, 32))
|
|
139
168
|
offset += 32
|
|
140
169
|
inner_version = data.byteslice(offset, 4).unpack1('V')
|
|
141
170
|
offset += 4
|
|
@@ -223,13 +252,13 @@ module BSV
|
|
|
223
252
|
|
|
224
253
|
# Serialise as Atomic BEEF (BRC-95), wrapping V2 data with a subject txid.
|
|
225
254
|
#
|
|
226
|
-
# @param
|
|
255
|
+
# @param subject_wtxid [String] 32-byte wire-order subject transaction ID
|
|
227
256
|
# @return [String] raw Atomic BEEF binary
|
|
228
|
-
def to_atomic_binary(
|
|
257
|
+
def to_atomic_binary(subject_wtxid)
|
|
258
|
+
BSV::Primitives::Hex.validate_wtxid!(subject_wtxid, name: 'subject_wtxid')
|
|
229
259
|
buf = [ATOMIC_BEEF].pack('V')
|
|
230
|
-
#
|
|
231
|
-
|
|
232
|
-
buf << subject_txid.b.reverse
|
|
260
|
+
# subject_wtxid is already in wire (internal) byte order — write as-is.
|
|
261
|
+
buf << subject_wtxid.b
|
|
233
262
|
# BRC-95: inner envelope is always V2
|
|
234
263
|
buf << to_binary(version: BEEF_V2)
|
|
235
264
|
buf
|
|
@@ -237,42 +266,45 @@ module BSV
|
|
|
237
266
|
|
|
238
267
|
# --- Lookup ---
|
|
239
268
|
|
|
240
|
-
# Find a transaction in the bundle by its transaction ID.
|
|
269
|
+
# Find a transaction in the bundle by its wire-order transaction ID.
|
|
241
270
|
#
|
|
242
|
-
# @param
|
|
271
|
+
# @param wtxid [String] 32-byte wire-order wtxid
|
|
243
272
|
# @return [Transaction, nil] the matching transaction, or nil
|
|
244
|
-
def find_transaction(
|
|
273
|
+
def find_transaction(wtxid)
|
|
274
|
+
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
275
|
+
BSV.logger&.debug { "[Beef] find_transaction: #{wtxid.reverse.unpack1('H*')} in #{@transactions.length} entries" }
|
|
245
276
|
@transactions.each do |beef_tx|
|
|
246
|
-
return beef_tx.transaction if beef_tx.
|
|
277
|
+
return beef_tx.transaction if beef_tx.wtxid == wtxid
|
|
247
278
|
end
|
|
248
279
|
nil
|
|
249
280
|
end
|
|
250
281
|
|
|
251
|
-
# Find the merkle path (BUMP) for a transaction by its txid.
|
|
282
|
+
# Find the merkle path (BUMP) for a transaction by its wire-order txid.
|
|
252
283
|
#
|
|
253
284
|
# First checks the transaction-table entries, then scans @bumps directly
|
|
254
|
-
# for a BUMP whose level-0 leaves contain the
|
|
285
|
+
# for a BUMP whose level-0 leaves contain the wtxid.
|
|
255
286
|
#
|
|
256
|
-
# @param
|
|
287
|
+
# @param wtxid [String] 32-byte wire-order wtxid
|
|
257
288
|
# @return [MerklePath, nil] the merkle path, or nil if not found
|
|
258
|
-
def find_bump(
|
|
289
|
+
def find_bump(wtxid)
|
|
290
|
+
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
259
291
|
# Check transaction-table entries first (fast path)
|
|
260
|
-
bt = @transactions.find { |entry| entry.
|
|
292
|
+
bt = @transactions.find { |entry| entry.wtxid == wtxid && entry.format == FORMAT_RAW_TX_AND_BUMP }
|
|
261
293
|
return bt.transaction&.merkle_path || (bt.bump_index && @bumps[bt.bump_index]) if bt
|
|
262
294
|
|
|
263
|
-
# F5.8: also scan @bumps directly for a path containing the
|
|
264
|
-
txid_internal = txid.reverse
|
|
295
|
+
# F5.8: also scan @bumps directly for a path containing the wtxid leaf
|
|
265
296
|
@bumps.find do |bump|
|
|
266
|
-
bump.path[0]&.any? { |leaf| leaf.hash ==
|
|
297
|
+
bump.path[0]&.any? { |leaf| leaf.hash == wtxid }
|
|
267
298
|
end
|
|
268
299
|
end
|
|
269
300
|
|
|
270
301
|
# Find a transaction with all source_transactions wired for signing.
|
|
271
302
|
#
|
|
272
|
-
# @param
|
|
303
|
+
# @param wtxid [String] 32-byte wire-order wtxid
|
|
273
304
|
# @return [Transaction, nil] the transaction with wired inputs, or nil
|
|
274
|
-
def find_transaction_for_signing(
|
|
275
|
-
|
|
305
|
+
def find_transaction_for_signing(wtxid)
|
|
306
|
+
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
307
|
+
tx = find_transaction(wtxid)
|
|
276
308
|
return unless tx
|
|
277
309
|
|
|
278
310
|
wire_inputs(tx)
|
|
@@ -282,10 +314,11 @@ module BSV
|
|
|
282
314
|
# Find a transaction and recursively wire its ancestry (source transactions
|
|
283
315
|
# and merkle paths) for atomic proof validation.
|
|
284
316
|
#
|
|
285
|
-
# @param
|
|
317
|
+
# @param wtxid [String] 32-byte wire-order wtxid
|
|
286
318
|
# @return [Transaction, nil] the transaction with full proof tree, or nil
|
|
287
|
-
def find_atomic_transaction(
|
|
288
|
-
|
|
319
|
+
def find_atomic_transaction(wtxid)
|
|
320
|
+
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
321
|
+
tx = find_transaction(wtxid)
|
|
289
322
|
return unless tx
|
|
290
323
|
|
|
291
324
|
wire_ancestry(tx)
|
|
@@ -294,10 +327,10 @@ module BSV
|
|
|
294
327
|
|
|
295
328
|
# Serialise as Atomic BEEF (BRC-95) hex string.
|
|
296
329
|
#
|
|
297
|
-
# @param
|
|
330
|
+
# @param subject_wtxid [String] 32-byte wire-order subject transaction ID
|
|
298
331
|
# @return [String] hex-encoded Atomic BEEF
|
|
299
|
-
def to_atomic_hex(
|
|
300
|
-
to_atomic_binary(
|
|
332
|
+
def to_atomic_hex(subject_wtxid)
|
|
333
|
+
to_atomic_binary(subject_wtxid).unpack1('H*')
|
|
301
334
|
end
|
|
302
335
|
|
|
303
336
|
# --- Merge operations ---
|
|
@@ -339,7 +372,7 @@ module BSV
|
|
|
339
372
|
level0_internal = level0_leaves.map(&:hash).compact.to_set
|
|
340
373
|
@transactions.each_with_index do |bt, i|
|
|
341
374
|
next unless bt.format == FORMAT_RAW_TX && bt.transaction
|
|
342
|
-
next unless level0_internal.include?(bt.transaction.
|
|
375
|
+
next unless level0_internal.include?(bt.transaction.wtxid)
|
|
343
376
|
|
|
344
377
|
bt.transaction.merkle_path ||= bump
|
|
345
378
|
@transactions[i] = BeefTx.new(
|
|
@@ -362,10 +395,10 @@ module BSV
|
|
|
362
395
|
# @param tx [Transaction] the transaction to merge
|
|
363
396
|
# @return [BeefTx] the (possibly existing or upgraded) BeefTx entry
|
|
364
397
|
def merge_transaction(tx)
|
|
365
|
-
|
|
398
|
+
wtxid = tx.wtxid
|
|
366
399
|
|
|
367
400
|
# Check for existing entry and upgrade if a stronger format is available
|
|
368
|
-
existing_idx = @transactions.index { |bt| bt.
|
|
401
|
+
existing_idx = @transactions.index { |bt| bt.wtxid == wtxid }
|
|
369
402
|
if existing_idx
|
|
370
403
|
existing = @transactions[existing_idx]
|
|
371
404
|
upgraded = upgrade_beef_tx(existing, tx)
|
|
@@ -409,7 +442,7 @@ module BSV
|
|
|
409
442
|
tx.merkle_path = @bumps[bump_index]
|
|
410
443
|
end
|
|
411
444
|
|
|
412
|
-
existing_idx = @transactions.index { |bt| bt.
|
|
445
|
+
existing_idx = @transactions.index { |bt| bt.wtxid == tx.wtxid }
|
|
413
446
|
if existing_idx
|
|
414
447
|
existing = @transactions[existing_idx]
|
|
415
448
|
upgraded = upgrade_beef_tx(existing, tx, bump_index: bump_index)
|
|
@@ -448,11 +481,11 @@ module BSV
|
|
|
448
481
|
other.transactions.each do |beef_tx|
|
|
449
482
|
case beef_tx.format
|
|
450
483
|
when FORMAT_TXID_ONLY
|
|
451
|
-
next if @transactions.any? { |bt| bt.
|
|
484
|
+
next if @transactions.any? { |bt| bt.wtxid == beef_tx.known_wtxid }
|
|
452
485
|
|
|
453
|
-
@transactions << BeefTx.new(format: FORMAT_TXID_ONLY,
|
|
486
|
+
@transactions << BeefTx.new(format: FORMAT_TXID_ONLY, known_wtxid: beef_tx.known_wtxid)
|
|
454
487
|
else
|
|
455
|
-
next if @transactions.any? { |bt| bt.
|
|
488
|
+
next if @transactions.any? { |bt| bt.wtxid == beef_tx.wtxid }
|
|
456
489
|
|
|
457
490
|
if beef_tx.format == FORMAT_RAW_TX_AND_BUMP && beef_tx.bump_index
|
|
458
491
|
new_idx = bump_remap[beef_tx.bump_index]
|
|
@@ -482,13 +515,14 @@ module BSV
|
|
|
482
515
|
|
|
483
516
|
# Convert a transaction entry to TXID-only format.
|
|
484
517
|
#
|
|
485
|
-
# @param
|
|
518
|
+
# @param wtxid [String] 32-byte wire-order wtxid
|
|
486
519
|
# @return [BeefTx, nil] the converted entry, or nil if not found
|
|
487
|
-
def make_txid_only(
|
|
488
|
-
|
|
520
|
+
def make_txid_only(wtxid)
|
|
521
|
+
BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
|
|
522
|
+
idx = @transactions.index { |bt| bt.wtxid == wtxid }
|
|
489
523
|
return unless idx
|
|
490
524
|
|
|
491
|
-
@transactions[idx] = BeefTx.new(format: FORMAT_TXID_ONLY,
|
|
525
|
+
@transactions[idx] = BeefTx.new(format: FORMAT_TXID_ONLY, known_wtxid: wtxid)
|
|
492
526
|
end
|
|
493
527
|
|
|
494
528
|
# --- Validation ---
|
|
@@ -520,15 +554,15 @@ module BSV
|
|
|
520
554
|
|
|
521
555
|
# The txid must appear as a leaf in the BUMP and compute a valid root
|
|
522
556
|
begin
|
|
523
|
-
bump.compute_root(bt.transaction.
|
|
557
|
+
bump.compute_root(bt.transaction.wtxid)
|
|
524
558
|
rescue ArgumentError
|
|
525
559
|
return false
|
|
526
560
|
end
|
|
527
561
|
end
|
|
528
562
|
|
|
529
|
-
|
|
563
|
+
known_wtxids = build_known_wtxids(allow_txid_only)
|
|
530
564
|
|
|
531
|
-
pending = @transactions.select { |bt| bt.transaction && !
|
|
565
|
+
pending = @transactions.select { |bt| bt.transaction && !known_wtxids.include?(bt.wtxid) }
|
|
532
566
|
|
|
533
567
|
# Iteratively resolve: if all inputs of a tx are known, it becomes known
|
|
534
568
|
changed = true
|
|
@@ -536,10 +570,10 @@ module BSV
|
|
|
536
570
|
changed = false
|
|
537
571
|
pending.reject! do |bt|
|
|
538
572
|
all_inputs_known = bt.transaction.inputs.all? do |input|
|
|
539
|
-
|
|
573
|
+
known_wtxids.include?(input.prev_wtxid)
|
|
540
574
|
end
|
|
541
575
|
if all_inputs_known
|
|
542
|
-
|
|
576
|
+
known_wtxids.add(bt.wtxid)
|
|
543
577
|
changed = true
|
|
544
578
|
end
|
|
545
579
|
all_inputs_known
|
|
@@ -584,8 +618,8 @@ module BSV
|
|
|
584
618
|
def sort_transactions!
|
|
585
619
|
return self if @transactions.length <= 1
|
|
586
620
|
|
|
587
|
-
|
|
588
|
-
@transactions.each_with_index { |bt, i|
|
|
621
|
+
wtxid_index = {}
|
|
622
|
+
@transactions.each_with_index { |bt, i| wtxid_index[bt.wtxid] = i }
|
|
589
623
|
|
|
590
624
|
# Build adjacency: for each tx, which other txs must come before it?
|
|
591
625
|
in_degree = Array.new(@transactions.length, 0)
|
|
@@ -595,7 +629,7 @@ module BSV
|
|
|
595
629
|
next unless bt.transaction
|
|
596
630
|
|
|
597
631
|
bt.transaction.inputs.each do |input|
|
|
598
|
-
dep_idx =
|
|
632
|
+
dep_idx = wtxid_index[input.prev_wtxid]
|
|
599
633
|
next unless dep_idx
|
|
600
634
|
|
|
601
635
|
dependents[dep_idx] << i
|
|
@@ -618,8 +652,8 @@ module BSV
|
|
|
618
652
|
|
|
619
653
|
# F5.5: preserve unsortable (cyclic) transactions rather than silently dropping them
|
|
620
654
|
if sorted.length < @transactions.length
|
|
621
|
-
sorted_set = sorted.to_set(&:
|
|
622
|
-
@txs_not_valid = @transactions.reject { |bt| sorted_set.include?(bt.
|
|
655
|
+
sorted_set = sorted.to_set(&:wtxid)
|
|
656
|
+
@txs_not_valid = @transactions.reject { |bt| sorted_set.include?(bt.wtxid) }
|
|
623
657
|
end
|
|
624
658
|
|
|
625
659
|
@transactions = sorted
|
|
@@ -661,14 +695,13 @@ module BSV
|
|
|
661
695
|
|
|
662
696
|
case format
|
|
663
697
|
when FORMAT_TXID_ONLY
|
|
664
|
-
# Wire stores txid in internal (little-endian) byte order;
|
|
665
|
-
#
|
|
666
|
-
# Transaction#txid across all format types.
|
|
698
|
+
# Wire stores txid in internal (little-endian / wire) byte order;
|
|
699
|
+
# store as-is in known_wtxid so it matches Transaction#wtxid.
|
|
667
700
|
raise ArgumentError, 'truncated BEEF: not enough bytes for TXID_ONLY entry' if offset + 32 > data.bytesize
|
|
668
701
|
|
|
669
|
-
|
|
702
|
+
known_wtxid = data.byteslice(offset, 32)
|
|
670
703
|
offset += 32
|
|
671
|
-
beef.transactions << BeefTx.new(format: FORMAT_TXID_ONLY,
|
|
704
|
+
beef.transactions << BeefTx.new(format: FORMAT_TXID_ONLY, known_wtxid: known_wtxid)
|
|
672
705
|
when FORMAT_RAW_TX_AND_BUMP
|
|
673
706
|
bump_index, vi_size = VarInt.decode(data, offset)
|
|
674
707
|
offset += vi_size
|
|
@@ -719,14 +752,20 @@ module BSV
|
|
|
719
752
|
beef.transactions.each do |beef_tx|
|
|
720
753
|
next unless beef_tx.transaction
|
|
721
754
|
|
|
722
|
-
# Wire inputs to ancestors already in the map (BEEF is dependency-ordered)
|
|
755
|
+
# Wire inputs to ancestors already in the map (BEEF is dependency-ordered).
|
|
756
|
+
# Both prev_wtxid and wtxid are wire-order — no conversion needed.
|
|
723
757
|
beef_tx.transaction.inputs.each do |input|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
758
|
+
source = tx_map[input.prev_wtxid]
|
|
759
|
+
next unless source
|
|
760
|
+
|
|
761
|
+
input.source_transaction = source
|
|
762
|
+
BSV.logger&.debug do
|
|
763
|
+
"[Beef] wired input #{input.prev_wtxid.reverse.unpack1('H*')}:#{input.prev_tx_out_index} " \
|
|
764
|
+
"-> source #{source.wtxid.reverse.unpack1('H*')}"
|
|
765
|
+
end
|
|
727
766
|
end
|
|
728
767
|
|
|
729
|
-
tx_map[beef_tx.transaction.
|
|
768
|
+
tx_map[beef_tx.transaction.wtxid] = beef_tx.transaction
|
|
730
769
|
end
|
|
731
770
|
end
|
|
732
771
|
end
|
|
@@ -768,15 +807,15 @@ module BSV
|
|
|
768
807
|
# FORMAT_RAW_TX_AND_BUMP is already the strongest — no upgrade needed
|
|
769
808
|
end
|
|
770
809
|
|
|
771
|
-
# Build a set of
|
|
772
|
-
def
|
|
810
|
+
# Build a set of wire-order wtxids that are "known" (proven or txid-only).
|
|
811
|
+
def build_known_wtxids(allow_txid_only)
|
|
773
812
|
known = Set.new
|
|
774
813
|
@transactions.each do |bt|
|
|
775
814
|
case bt.format
|
|
776
815
|
when FORMAT_RAW_TX_AND_BUMP
|
|
777
|
-
known.add(bt.
|
|
816
|
+
known.add(bt.wtxid)
|
|
778
817
|
when FORMAT_TXID_ONLY
|
|
779
|
-
known.add(bt.
|
|
818
|
+
known.add(bt.wtxid) if allow_txid_only
|
|
780
819
|
end
|
|
781
820
|
end
|
|
782
821
|
known
|
|
@@ -787,7 +826,7 @@ module BSV
|
|
|
787
826
|
tx.inputs.each do |input|
|
|
788
827
|
next if input.source_transaction
|
|
789
828
|
|
|
790
|
-
source = find_transaction(input.
|
|
829
|
+
source = find_transaction(input.prev_wtxid)
|
|
791
830
|
input.source_transaction = source if source
|
|
792
831
|
end
|
|
793
832
|
end
|
|
@@ -799,7 +838,7 @@ module BSV
|
|
|
799
838
|
next unless input.source_transaction
|
|
800
839
|
|
|
801
840
|
source = input.source_transaction
|
|
802
|
-
source.merkle_path ||= find_bump(source.
|
|
841
|
+
source.merkle_path ||= find_bump(source.wtxid)
|
|
803
842
|
wire_ancestry(source)
|
|
804
843
|
end
|
|
805
844
|
end
|
|
@@ -820,8 +859,8 @@ module BSV
|
|
|
820
859
|
case beef_tx.format
|
|
821
860
|
when FORMAT_TXID_ONLY
|
|
822
861
|
buf << [FORMAT_TXID_ONLY].pack('C')
|
|
823
|
-
#
|
|
824
|
-
buf << beef_tx.
|
|
862
|
+
# known_wtxid is already wire (internal) byte order.
|
|
863
|
+
buf << beef_tx.known_wtxid
|
|
825
864
|
when FORMAT_RAW_TX_AND_BUMP
|
|
826
865
|
buf << [FORMAT_RAW_TX_AND_BUMP].pack('C')
|
|
827
866
|
buf << VarInt.encode(beef_tx.bump_index)
|