bsv-sdk 0.2.0 → 0.3.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 +16 -0
- data/lib/bsv/network/broadcast_response.rb +1 -2
- data/lib/bsv/primitives/bsm.rb +2 -6
- data/lib/bsv/primitives/curve.rb +1 -2
- data/lib/bsv/primitives/encrypted_message.rb +100 -0
- data/lib/bsv/primitives/extended_key.rb +1 -2
- data/lib/bsv/primitives/key_shares.rb +83 -0
- data/lib/bsv/primitives/mnemonic.rb +1 -3
- data/lib/bsv/primitives/point_in_finite_field.rb +72 -0
- data/lib/bsv/primitives/polynomial.rb +95 -0
- data/lib/bsv/primitives/private_key.rb +101 -5
- data/lib/bsv/primitives/signed_message.rb +104 -0
- data/lib/bsv/primitives/symmetric_key.rb +128 -0
- data/lib/bsv/primitives.rb +18 -12
- data/lib/bsv/script/interpreter/interpreter.rb +1 -3
- data/lib/bsv/script/interpreter/operations/bitwise.rb +1 -3
- data/lib/bsv/script/interpreter/operations/crypto.rb +3 -9
- data/lib/bsv/script/interpreter/operations/flow_control.rb +2 -6
- data/lib/bsv/script/interpreter/operations/splice.rb +1 -3
- data/lib/bsv/script/interpreter/script_number.rb +2 -7
- data/lib/bsv/script/script.rb +256 -1
- data/lib/bsv/transaction/beef.rb +8 -11
- data/lib/bsv/transaction/transaction.rb +131 -59
- data/lib/bsv/transaction/transaction_input.rb +1 -2
- data/lib/bsv/transaction/transaction_output.rb +1 -2
- data/lib/bsv/transaction/var_int.rb +4 -16
- data/lib/bsv/transaction.rb +14 -14
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet_interface/errors/invalid_hmac_error.rb +11 -0
- data/lib/bsv/wallet_interface/errors/invalid_parameter_error.rb +14 -0
- data/lib/bsv/wallet_interface/errors/invalid_signature_error.rb +11 -0
- data/lib/bsv/wallet_interface/errors/unsupported_action_error.rb +11 -0
- data/lib/bsv/wallet_interface/errors/wallet_error.rb +14 -0
- data/lib/bsv/wallet_interface/interface.rb +384 -0
- data/lib/bsv/wallet_interface/key_deriver.rb +142 -0
- data/lib/bsv/wallet_interface/memory_store.rb +115 -0
- data/lib/bsv/wallet_interface/proto_wallet.rb +361 -0
- data/lib/bsv/wallet_interface/storage_adapter.rb +51 -0
- data/lib/bsv/wallet_interface/validators.rb +126 -0
- data/lib/bsv/wallet_interface/version.rb +7 -0
- data/lib/bsv/wallet_interface/wallet_client.rb +486 -0
- data/lib/bsv/wallet_interface.rb +25 -0
- data/lib/bsv-wallet.rb +4 -0
- metadata +24 -3
- /data/{LICENCE → LICENSE} +0 -0
data/lib/bsv/script/script.rb
CHANGED
|
@@ -177,6 +177,100 @@ module BSV
|
|
|
177
177
|
new(buf)
|
|
178
178
|
end
|
|
179
179
|
|
|
180
|
+
# Construct a PushDrop locking script.
|
|
181
|
+
#
|
|
182
|
+
# Pushes arbitrary data fields onto the stack, then drops them all
|
|
183
|
+
# before the locking condition executes. Used for token protocols
|
|
184
|
+
# where data must be embedded in spendable outputs.
|
|
185
|
+
#
|
|
186
|
+
# Structure: +[field0] [field1] ... [fieldN] [OP_2DROP...] [OP_DROP?] [lock_script]+
|
|
187
|
+
#
|
|
188
|
+
# @param fields [Array<String>] data payloads to embed (binary strings)
|
|
189
|
+
# @param lock_script [Script] the underlying locking condition (e.g. P2PKH)
|
|
190
|
+
# @return [Script]
|
|
191
|
+
# @raise [ArgumentError] if fields is empty or lock_script is not a Script
|
|
192
|
+
def self.pushdrop_lock(fields, lock_script)
|
|
193
|
+
raise ArgumentError, 'fields must not be empty' if fields.empty?
|
|
194
|
+
raise ArgumentError, 'lock_script must be a Script' unless lock_script.is_a?(Script)
|
|
195
|
+
|
|
196
|
+
chunks = fields.map { |f| encode_minimally(f.b) }
|
|
197
|
+
|
|
198
|
+
remaining = fields.length
|
|
199
|
+
while remaining > 1
|
|
200
|
+
chunks << Chunk.new(opcode: Opcodes::OP_2DROP)
|
|
201
|
+
remaining -= 2
|
|
202
|
+
end
|
|
203
|
+
chunks << Chunk.new(opcode: Opcodes::OP_DROP) if remaining == 1
|
|
204
|
+
|
|
205
|
+
chunks.concat(lock_script.chunks)
|
|
206
|
+
from_chunks(chunks)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Construct a PushDrop unlocking script.
|
|
210
|
+
#
|
|
211
|
+
# Pass-through wrapper — the data fields are dropped during execution,
|
|
212
|
+
# so the unlocking script just needs to satisfy the underlying lock.
|
|
213
|
+
#
|
|
214
|
+
# @param unlock_script [Script] unlocking script for the underlying condition
|
|
215
|
+
# @return [Script]
|
|
216
|
+
def self.pushdrop_unlock(unlock_script)
|
|
217
|
+
unlock_script
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Hash type to opcode mapping for RPuzzle scripts.
|
|
221
|
+
RPUZZLE_HASH_OPS = {
|
|
222
|
+
raw: nil,
|
|
223
|
+
sha1: Opcodes::OP_SHA1,
|
|
224
|
+
ripemd160: Opcodes::OP_RIPEMD160,
|
|
225
|
+
sha256: Opcodes::OP_SHA256,
|
|
226
|
+
hash160: Opcodes::OP_HASH160,
|
|
227
|
+
hash256: Opcodes::OP_HASH256
|
|
228
|
+
}.freeze
|
|
229
|
+
|
|
230
|
+
# Reverse lookup: opcode → hash type symbol (excludes :raw).
|
|
231
|
+
RPUZZLE_OP_TO_TYPE = RPUZZLE_HASH_OPS.reject { |k, _| k == :raw }.invert.freeze
|
|
232
|
+
|
|
233
|
+
# The fixed opcode prefix shared by all RPuzzle locking scripts.
|
|
234
|
+
# OP_OVER OP_3 OP_SPLIT OP_NIP OP_1 OP_SPLIT OP_SWAP OP_SPLIT OP_DROP
|
|
235
|
+
RPUZZLE_PREFIX = [
|
|
236
|
+
Opcodes::OP_OVER, Opcodes::OP_3, Opcodes::OP_SPLIT,
|
|
237
|
+
Opcodes::OP_NIP, Opcodes::OP_1, Opcodes::OP_SPLIT,
|
|
238
|
+
Opcodes::OP_SWAP, Opcodes::OP_SPLIT, Opcodes::OP_DROP
|
|
239
|
+
].freeze
|
|
240
|
+
|
|
241
|
+
# Construct an RPuzzle locking script.
|
|
242
|
+
#
|
|
243
|
+
# RPuzzle enables hash-puzzle-based spending where the spender proves
|
|
244
|
+
# knowledge of the ECDSA K-value (nonce) that produced a signature's
|
|
245
|
+
# R component.
|
|
246
|
+
#
|
|
247
|
+
# @param hash_value [String] the R-value or hash of R-value to lock against
|
|
248
|
+
# @param hash_type [Symbol] one of +:raw+, +:sha1+, +:ripemd160+,
|
|
249
|
+
# +:sha256+, +:hash160+, +:hash256+
|
|
250
|
+
# @return [Script]
|
|
251
|
+
# @raise [ArgumentError] if hash_type is invalid
|
|
252
|
+
def self.rpuzzle_lock(hash_value, hash_type: :hash160)
|
|
253
|
+
raise ArgumentError, "unknown hash_type: #{hash_type}" unless RPUZZLE_HASH_OPS.key?(hash_type)
|
|
254
|
+
|
|
255
|
+
buf = RPUZZLE_PREFIX.pack('C*')
|
|
256
|
+
hash_op = RPUZZLE_HASH_OPS[hash_type]
|
|
257
|
+
buf << [hash_op].pack('C') if hash_op
|
|
258
|
+
buf << encode_push_data(hash_value.b)
|
|
259
|
+
buf << [Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG].pack('CC')
|
|
260
|
+
new(buf)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# Construct an RPuzzle unlocking script.
|
|
264
|
+
#
|
|
265
|
+
# Same wire format as P2PKH: signature + public key.
|
|
266
|
+
#
|
|
267
|
+
# @param signature_der [String] DER-encoded signature with sighash byte
|
|
268
|
+
# @param pubkey_bytes [String] compressed or uncompressed public key bytes
|
|
269
|
+
# @return [Script]
|
|
270
|
+
def self.rpuzzle_unlock(signature_der, pubkey_bytes)
|
|
271
|
+
p2pkh_unlock(signature_der, pubkey_bytes)
|
|
272
|
+
end
|
|
273
|
+
|
|
180
274
|
# --- Serialisation ---
|
|
181
275
|
|
|
182
276
|
# @return [String] a copy of the raw script bytes
|
|
@@ -256,6 +350,76 @@ module BSV
|
|
|
256
350
|
([0x04, 0x06, 0x07].include?(version) && pubkey.bytesize == 65)
|
|
257
351
|
end
|
|
258
352
|
|
|
353
|
+
# Whether this is a PushDrop script.
|
|
354
|
+
#
|
|
355
|
+
# Detects scripts with one or more data pushes followed by a
|
|
356
|
+
# OP_DROP/OP_2DROP chain and a recognisable locking condition.
|
|
357
|
+
#
|
|
358
|
+
# @return [Boolean]
|
|
359
|
+
def pushdrop?
|
|
360
|
+
c = chunks
|
|
361
|
+
return false if c.length < 3
|
|
362
|
+
|
|
363
|
+
# Find the first DROP/2DROP — everything before is data fields
|
|
364
|
+
drop_start = c.index { |ch| [Opcodes::OP_DROP, Opcodes::OP_2DROP].include?(ch.opcode) }
|
|
365
|
+
return false unless drop_start&.positive?
|
|
366
|
+
|
|
367
|
+
# All chunks before first drop must be data pushes or minimal push opcodes
|
|
368
|
+
field_chunks = c[0...drop_start]
|
|
369
|
+
return false unless field_chunks.all? { |ch| ch.data? || minimal_push_opcode?(ch.opcode) }
|
|
370
|
+
|
|
371
|
+
# Count fields and verify the drop sequence
|
|
372
|
+
num_fields = field_chunks.length
|
|
373
|
+
expected_drops = []
|
|
374
|
+
remaining = num_fields
|
|
375
|
+
while remaining > 1
|
|
376
|
+
expected_drops << Opcodes::OP_2DROP
|
|
377
|
+
remaining -= 2
|
|
378
|
+
end
|
|
379
|
+
expected_drops << Opcodes::OP_DROP if remaining == 1
|
|
380
|
+
|
|
381
|
+
drop_end = drop_start + expected_drops.length
|
|
382
|
+
return false if drop_end > c.length
|
|
383
|
+
|
|
384
|
+
actual_drops = c[drop_start...drop_end].map(&:opcode)
|
|
385
|
+
return false unless actual_drops == expected_drops
|
|
386
|
+
|
|
387
|
+
# Must have at least one chunk after the drops (the lock script)
|
|
388
|
+
drop_end < c.length
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
# Whether this is an RPuzzle script.
|
|
392
|
+
#
|
|
393
|
+
# Detects the fixed R-value extraction prefix followed by an optional
|
|
394
|
+
# hash opcode, a data push, OP_EQUALVERIFY, and OP_CHECKSIG.
|
|
395
|
+
#
|
|
396
|
+
# @return [Boolean]
|
|
397
|
+
def rpuzzle?
|
|
398
|
+
c = chunks
|
|
399
|
+
# Minimum: 9 prefix + hash_data + OP_EQUALVERIFY + OP_CHECKSIG = 12
|
|
400
|
+
# With hash op: 13
|
|
401
|
+
return false unless c.length >= 12
|
|
402
|
+
|
|
403
|
+
# Verify the 9-opcode prefix
|
|
404
|
+
RPUZZLE_PREFIX.each_with_index do |op, i|
|
|
405
|
+
return false unless c[i].opcode == op
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# After prefix: optional hash op, then data push, OP_EQUALVERIFY, OP_CHECKSIG
|
|
409
|
+
return false unless c[-1].opcode == Opcodes::OP_CHECKSIG
|
|
410
|
+
return false unless c[-2].opcode == Opcodes::OP_EQUALVERIFY
|
|
411
|
+
return false unless c[-3].data?
|
|
412
|
+
|
|
413
|
+
# Either exactly 12 chunks (raw) or 13 chunks (with hash op)
|
|
414
|
+
if c.length == 12
|
|
415
|
+
true
|
|
416
|
+
elsif c.length == 13
|
|
417
|
+
RPUZZLE_HASH_OPS.values.compact.include?(c[9].opcode)
|
|
418
|
+
else
|
|
419
|
+
false
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
259
423
|
# Whether this is a bare multisig script.
|
|
260
424
|
#
|
|
261
425
|
# Pattern: +OP_M <pubkey1> ... <pubkeyN> OP_N OP_CHECKMULTISIG+
|
|
@@ -275,7 +439,8 @@ module BSV
|
|
|
275
439
|
# Classify the script as a standard type.
|
|
276
440
|
#
|
|
277
441
|
# @return [String] one of +"empty"+, +"pubkeyhash"+, +"pubkey"+,
|
|
278
|
-
# +"scripthash"+, +"nulldata"+, +"multisig"+,
|
|
442
|
+
# +"scripthash"+, +"nulldata"+, +"multisig"+, +"pushdrop"+,
|
|
443
|
+
# +"rpuzzle"+, or +"nonstandard"+
|
|
279
444
|
def type
|
|
280
445
|
if @bytes.empty? then 'empty'
|
|
281
446
|
elsif p2pkh? then 'pubkeyhash'
|
|
@@ -283,6 +448,8 @@ module BSV
|
|
|
283
448
|
elsif p2sh? then 'scripthash'
|
|
284
449
|
elsif op_return? then 'nulldata'
|
|
285
450
|
elsif multisig? then 'multisig'
|
|
451
|
+
elsif pushdrop? then 'pushdrop'
|
|
452
|
+
elsif rpuzzle? then 'rpuzzle'
|
|
286
453
|
else 'nonstandard'
|
|
287
454
|
end
|
|
288
455
|
end
|
|
@@ -317,6 +484,49 @@ module BSV
|
|
|
317
484
|
Script.new(@bytes.byteslice(start..)).chunks.select(&:data?).map(&:data)
|
|
318
485
|
end
|
|
319
486
|
|
|
487
|
+
# Extract the hash value from an RPuzzle script.
|
|
488
|
+
#
|
|
489
|
+
# @return [String, nil] the locked hash/R-value, or +nil+ if not RPuzzle
|
|
490
|
+
def rpuzzle_hash
|
|
491
|
+
return unless rpuzzle?
|
|
492
|
+
|
|
493
|
+
chunks[-3].data
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
# Detect the hash type used in an RPuzzle script.
|
|
497
|
+
#
|
|
498
|
+
# @return [Symbol, nil] the hash type (e.g. +:hash160+, +:raw+), or +nil+ if not RPuzzle
|
|
499
|
+
def rpuzzle_hash_type
|
|
500
|
+
return unless rpuzzle?
|
|
501
|
+
|
|
502
|
+
chunks.length == 12 ? :raw : RPUZZLE_OP_TO_TYPE[chunks[9].opcode]
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Extract the embedded data fields from a PushDrop script.
|
|
506
|
+
#
|
|
507
|
+
# @return [Array<String>, nil] array of field data, or +nil+ if not PushDrop
|
|
508
|
+
def pushdrop_fields
|
|
509
|
+
return unless pushdrop?
|
|
510
|
+
|
|
511
|
+
c = chunks
|
|
512
|
+
drop_start = c.index { |ch| [Opcodes::OP_DROP, Opcodes::OP_2DROP].include?(ch.opcode) }
|
|
513
|
+
c[0...drop_start].map { |ch| decode_minimal_push(ch) }
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
# Extract the underlying lock script from a PushDrop script.
|
|
517
|
+
#
|
|
518
|
+
# @return [Script, nil] the lock script portion, or +nil+ if not PushDrop
|
|
519
|
+
def pushdrop_lock_script
|
|
520
|
+
return unless pushdrop?
|
|
521
|
+
|
|
522
|
+
c = chunks
|
|
523
|
+
drop_start = c.index { |ch| [Opcodes::OP_DROP, Opcodes::OP_2DROP].include?(ch.opcode) }
|
|
524
|
+
num_fields = drop_start
|
|
525
|
+
num_drops = (num_fields / 2) + (num_fields.odd? ? 1 : 0)
|
|
526
|
+
lock_start = drop_start + num_drops
|
|
527
|
+
self.class.from_chunks(c[lock_start..])
|
|
528
|
+
end
|
|
529
|
+
|
|
320
530
|
# Derive Bitcoin addresses from this script.
|
|
321
531
|
#
|
|
322
532
|
# Currently supports P2PKH scripts only.
|
|
@@ -366,6 +576,26 @@ module BSV
|
|
|
366
576
|
end
|
|
367
577
|
end
|
|
368
578
|
|
|
579
|
+
def encode_minimally(data)
|
|
580
|
+
len = data.bytesize
|
|
581
|
+
|
|
582
|
+
if len.zero? || (len == 1 && data.getbyte(0).zero?)
|
|
583
|
+
Chunk.new(opcode: Opcodes::OP_0)
|
|
584
|
+
elsif len == 1 && data.getbyte(0).between?(1, 16)
|
|
585
|
+
Chunk.new(opcode: 0x50 + data.getbyte(0))
|
|
586
|
+
elsif len == 1 && data.getbyte(0) == 0x81
|
|
587
|
+
Chunk.new(opcode: Opcodes::OP_1NEGATE)
|
|
588
|
+
elsif len <= 0x4b
|
|
589
|
+
Chunk.new(opcode: len, data: data)
|
|
590
|
+
elsif len <= 0xff
|
|
591
|
+
Chunk.new(opcode: Opcodes::OP_PUSHDATA1, data: data)
|
|
592
|
+
elsif len <= 0xffff
|
|
593
|
+
Chunk.new(opcode: Opcodes::OP_PUSHDATA2, data: data)
|
|
594
|
+
else
|
|
595
|
+
Chunk.new(opcode: Opcodes::OP_PUSHDATA4, data: data)
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
369
599
|
def resolve_opcode(token)
|
|
370
600
|
return nil unless token.start_with?('OP_')
|
|
371
601
|
|
|
@@ -381,6 +611,23 @@ module BSV
|
|
|
381
611
|
opcode == Opcodes::OP_0 || opcode.between?(Opcodes::OP_1, Opcodes::OP_16)
|
|
382
612
|
end
|
|
383
613
|
|
|
614
|
+
def minimal_push_opcode?(opcode)
|
|
615
|
+
opcode == Opcodes::OP_0 ||
|
|
616
|
+
opcode == Opcodes::OP_1NEGATE ||
|
|
617
|
+
opcode.between?(Opcodes::OP_1, Opcodes::OP_16)
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def decode_minimal_push(chunk)
|
|
621
|
+
return chunk.data if chunk.data?
|
|
622
|
+
|
|
623
|
+
case chunk.opcode
|
|
624
|
+
when Opcodes::OP_0 then ''.b
|
|
625
|
+
when Opcodes::OP_1NEGATE then "\x81".b
|
|
626
|
+
when Opcodes::OP_1..Opcodes::OP_16
|
|
627
|
+
[chunk.opcode - 0x50].pack('C')
|
|
628
|
+
end
|
|
629
|
+
end
|
|
630
|
+
|
|
384
631
|
def parse_chunks
|
|
385
632
|
result = []
|
|
386
633
|
pos = 0
|
|
@@ -391,22 +638,30 @@ module BSV
|
|
|
391
638
|
pos += 1
|
|
392
639
|
|
|
393
640
|
if opcode.positive? && opcode <= 0x4b
|
|
641
|
+
raise ArgumentError, "truncated script: need #{opcode} data bytes at offset #{pos}" if pos + opcode > raw.bytesize
|
|
642
|
+
|
|
394
643
|
data = raw.byteslice(pos, opcode)
|
|
395
644
|
pos += opcode
|
|
396
645
|
result << Chunk.new(opcode: opcode, data: data)
|
|
397
646
|
elsif opcode == Opcodes::OP_PUSHDATA1
|
|
647
|
+
raise ArgumentError, "truncated script: OP_PUSHDATA1 missing length byte at offset #{pos}" if pos >= raw.bytesize
|
|
648
|
+
|
|
398
649
|
len = raw.getbyte(pos)
|
|
399
650
|
pos += 1
|
|
400
651
|
data = raw.byteslice(pos, len)
|
|
401
652
|
pos += len
|
|
402
653
|
result << Chunk.new(opcode: opcode, data: data)
|
|
403
654
|
elsif opcode == Opcodes::OP_PUSHDATA2
|
|
655
|
+
raise ArgumentError, "truncated script: OP_PUSHDATA2 needs 2 length bytes at offset #{pos}" if pos + 2 > raw.bytesize
|
|
656
|
+
|
|
404
657
|
len = raw.byteslice(pos, 2).unpack1('v')
|
|
405
658
|
pos += 2
|
|
406
659
|
data = raw.byteslice(pos, len)
|
|
407
660
|
pos += len
|
|
408
661
|
result << Chunk.new(opcode: opcode, data: data)
|
|
409
662
|
elsif opcode == Opcodes::OP_PUSHDATA4
|
|
663
|
+
raise ArgumentError, "truncated script: OP_PUSHDATA4 needs 4 length bytes at offset #{pos}" if pos + 4 > raw.bytesize
|
|
664
|
+
|
|
410
665
|
len = raw.byteslice(pos, 4).unpack1('V')
|
|
411
666
|
pos += 4
|
|
412
667
|
data = raw.byteslice(pos, len)
|
data/lib/bsv/transaction/beef.rb
CHANGED
|
@@ -63,7 +63,7 @@ module BSV
|
|
|
63
63
|
|
|
64
64
|
# The transaction ID for this entry.
|
|
65
65
|
#
|
|
66
|
-
# @return [String, nil] 32-byte txid in
|
|
66
|
+
# @return [String, nil] 32-byte txid in display byte order
|
|
67
67
|
def txid
|
|
68
68
|
case @format
|
|
69
69
|
when FORMAT_TXID_ONLY
|
|
@@ -106,10 +106,7 @@ module BSV
|
|
|
106
106
|
# @param data [String] raw BEEF binary
|
|
107
107
|
# @return [Beef] the parsed BEEF bundle
|
|
108
108
|
def self.from_binary(data)
|
|
109
|
-
if data.bytesize < 4
|
|
110
|
-
raise ArgumentError,
|
|
111
|
-
"truncated BEEF: need at least 4 bytes for version, got #{data.bytesize}"
|
|
112
|
-
end
|
|
109
|
+
raise ArgumentError, "truncated BEEF: need at least 4 bytes for version, got #{data.bytesize}" if data.bytesize < 4
|
|
113
110
|
|
|
114
111
|
offset = 0
|
|
115
112
|
|
|
@@ -210,7 +207,7 @@ module BSV
|
|
|
210
207
|
|
|
211
208
|
# Find a transaction in the bundle by its transaction ID.
|
|
212
209
|
#
|
|
213
|
-
# @param txid [String] 32-byte txid in
|
|
210
|
+
# @param txid [String] 32-byte txid in display byte order
|
|
214
211
|
# @return [Transaction, nil] the matching transaction, or nil
|
|
215
212
|
def find_transaction(txid)
|
|
216
213
|
@transactions.each do |beef_tx|
|
|
@@ -221,7 +218,7 @@ module BSV
|
|
|
221
218
|
|
|
222
219
|
# Find the merkle path (BUMP) for a transaction by its txid.
|
|
223
220
|
#
|
|
224
|
-
# @param txid [String] 32-byte txid in
|
|
221
|
+
# @param txid [String] 32-byte txid in display byte order
|
|
225
222
|
# @return [MerklePath, nil] the merkle path, or nil if not found
|
|
226
223
|
def find_bump(txid)
|
|
227
224
|
bt = @transactions.find { |entry| entry.txid == txid && entry.format == FORMAT_RAW_TX_AND_BUMP }
|
|
@@ -232,7 +229,7 @@ module BSV
|
|
|
232
229
|
|
|
233
230
|
# Find a transaction with all source_transactions wired for signing.
|
|
234
231
|
#
|
|
235
|
-
# @param txid [String] 32-byte txid in
|
|
232
|
+
# @param txid [String] 32-byte txid in display byte order
|
|
236
233
|
# @return [Transaction, nil] the transaction with wired inputs, or nil
|
|
237
234
|
def find_transaction_for_signing(txid)
|
|
238
235
|
tx = find_transaction(txid)
|
|
@@ -245,7 +242,7 @@ module BSV
|
|
|
245
242
|
# Find a transaction and recursively wire its ancestry (source transactions
|
|
246
243
|
# and merkle paths) for atomic proof validation.
|
|
247
244
|
#
|
|
248
|
-
# @param txid [String] 32-byte txid in
|
|
245
|
+
# @param txid [String] 32-byte txid in display byte order
|
|
249
246
|
# @return [Transaction, nil] the transaction with full proof tree, or nil
|
|
250
247
|
def find_atomic_transaction(txid)
|
|
251
248
|
tx = find_transaction(txid)
|
|
@@ -381,7 +378,7 @@ module BSV
|
|
|
381
378
|
|
|
382
379
|
# Convert a transaction entry to TXID-only format.
|
|
383
380
|
#
|
|
384
|
-
# @param txid [String] 32-byte txid in
|
|
381
|
+
# @param txid [String] 32-byte txid in display byte order
|
|
385
382
|
# @return [BeefTx, nil] the converted entry, or nil if not found
|
|
386
383
|
def make_txid_only(txid)
|
|
387
384
|
idx = @transactions.index { |bt| bt.txid == txid }
|
|
@@ -557,7 +554,7 @@ module BSV
|
|
|
557
554
|
|
|
558
555
|
# Wire inputs to ancestors already in the map (BEEF is dependency-ordered)
|
|
559
556
|
beef_tx.transaction.inputs.each do |input|
|
|
560
|
-
# prev_tx_id is
|
|
557
|
+
# prev_tx_id is wire byte order; txid keys are display byte order (reversed)
|
|
561
558
|
source = tx_map[input.prev_tx_id.reverse]
|
|
562
559
|
input.source_transaction = source if source
|
|
563
560
|
end
|