bsv-sdk 0.17.0 → 0.18.1

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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +37 -0
  3. data/lib/bsv/auth/get_verifiable_certificates.rb +6 -6
  4. data/lib/bsv/auth/peer.rb +10 -4
  5. data/lib/bsv/auth/session_manager.rb +81 -5
  6. data/lib/bsv/identity/client.rb +4 -2
  7. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +2 -2
  8. data/lib/bsv/mcp/tools/check_balance.rb +2 -2
  9. data/lib/bsv/mcp/tools/fetch_utxos.rb +2 -2
  10. data/lib/bsv/network/broadcast_error.rb +1 -0
  11. data/lib/bsv/network/broadcast_response.rb +3 -1
  12. data/lib/bsv/network/protocol.rb +56 -4
  13. data/lib/bsv/network/protocols/arc.rb +31 -11
  14. data/lib/bsv/network/protocols/chaintracks.rb +6 -2
  15. data/lib/bsv/network/protocols/jungle_bus.rb +52 -0
  16. data/lib/bsv/network/protocols/ordinals.rb +110 -8
  17. data/lib/bsv/network/protocols/taal_binary.rb +17 -4
  18. data/lib/bsv/network/protocols/woc_rest.rb +164 -84
  19. data/lib/bsv/network/protocols.rb +3 -3
  20. data/lib/bsv/network/provider.rb +36 -5
  21. data/lib/bsv/network/providers/gorilla_pool.rb +42 -20
  22. data/lib/bsv/network/providers/taal.rb +38 -15
  23. data/lib/bsv/network/providers/whats_on_chain.rb +42 -21
  24. data/lib/bsv/network/utxo.rb +8 -2
  25. data/lib/bsv/overlay/lookup_resolver.rb +5 -5
  26. data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
  27. data/lib/bsv/overlay/types.rb +1 -0
  28. data/lib/bsv/registry/client.rb +8 -8
  29. data/lib/bsv/registry/types.rb +1 -0
  30. data/lib/bsv/transaction/beef.rb +139 -102
  31. data/lib/bsv/transaction/transaction.rb +31 -19
  32. data/lib/bsv/version.rb +1 -1
  33. data/lib/bsv/wallet/proto_wallet.rb +44 -8
  34. data/lib/bsv/wire_format.rb +40 -14
  35. metadata +4 -3
@@ -36,60 +36,119 @@ module BSV
36
36
 
37
37
  # @!endgroup
38
38
 
39
- # A single entry in a BEEF bundle, wrapping a transaction with its format metadata.
39
+ # Abstract base class for a single entry in a BEEF bundle.
40
+ #
41
+ # Subclasses represent the three wire formats:
42
+ # - {RawTxEntry} — raw transaction without a merkle proof
43
+ # - {ProvenTxEntry} — raw transaction with an associated BUMP index
44
+ # - {TxidOnlyEntry} — transaction ID only (no raw data)
45
+ #
46
+ # @abstract Subclass and implement {#wtxid} and {#format_flag}.
40
47
  class BeefTx
41
- # @return [Integer] format flag (FORMAT_RAW_TX, FORMAT_RAW_TX_AND_BUMP, or FORMAT_TXID_ONLY)
42
- attr_reader :format
48
+ def initialize; end
49
+
50
+ # Wire-order transaction ID.
51
+ # @return [String, nil] 32-byte wtxid
52
+ # @abstract
53
+ def wtxid
54
+ raise NotImplementedError, "#{self.class}#wtxid is not implemented"
55
+ end
43
56
 
44
- # @return [Transaction, nil] the transaction (nil for TXID-only entries)
57
+ # Display-order transaction ID as a hex string.
58
+ #
59
+ # +dtxid+ always returns a 64-char hex string suitable for JSON
60
+ # and UI boundaries.
61
+ #
62
+ # @return [String, nil] hex-encoded transaction ID (display order)
63
+ def dtxid
64
+ wtxid&.reverse&.unpack1('H*')
65
+ end
66
+
67
+ # Wire-protocol format integer for serialisation.
68
+ # @return [Integer]
69
+ # @abstract
70
+ def format_flag
71
+ raise NotImplementedError, "#{self.class}#format_flag is not implemented"
72
+ end
73
+ end
74
+
75
+ # A BEEF entry containing a raw transaction without a merkle proof.
76
+ class RawTxEntry < BeefTx
77
+ # @return [Transaction] the transaction
45
78
  attr_reader :transaction
46
79
 
47
- # @return [String, nil] 32-byte wire-order wtxid for TXID-only entries
48
- attr_reader :known_wtxid
80
+ # @param transaction [Transaction] the transaction
81
+ # @raise [ArgumentError] if transaction is nil
82
+ def initialize(transaction:)
83
+ raise ArgumentError, 'RawTxEntry requires a transaction' if transaction.nil?
84
+
85
+ super()
86
+ @transaction = transaction
87
+ end
49
88
 
50
- # @return [Integer, nil] index into the BEEF bumps array
89
+ # @return [String] wire-order wtxid delegated to the transaction
90
+ def wtxid
91
+ @transaction.wtxid
92
+ end
93
+
94
+ # @return [Integer] FORMAT_RAW_TX wire-protocol flag
95
+ def format_flag
96
+ FORMAT_RAW_TX
97
+ end
98
+ end
99
+
100
+ # A BEEF entry containing a raw transaction with an associated BUMP index.
101
+ class ProvenTxEntry < BeefTx
102
+ # @return [Transaction] the transaction
103
+ attr_reader :transaction
104
+
105
+ # @return [Integer] index into the BEEF bumps array
51
106
  attr_reader :bump_index
52
107
 
53
- # @param format [Integer] format flag
54
- # @param transaction [Transaction, nil] the transaction
55
- # @param known_wtxid [String, nil] 32-byte wire-order wtxid for TXID-only entries
56
- # @param bump_index [Integer, nil] index into the bumps array
57
- # @raise [ArgumentError] if format is FORMAT_RAW_TX_AND_BUMP without a bump_index
58
- def initialize(format:, transaction: nil, known_wtxid: nil, bump_index: nil)
59
- raise ArgumentError, 'FORMAT_RAW_TX_AND_BUMP requires a bump_index' if format == FORMAT_RAW_TX_AND_BUMP && bump_index.nil?
108
+ # @param transaction [Transaction] the transaction
109
+ # @param bump_index [Integer] index into the bumps array
110
+ # @raise [ArgumentError] if transaction or bump_index is nil
111
+ def initialize(transaction:, bump_index:)
112
+ raise ArgumentError, 'ProvenTxEntry requires a transaction' if transaction.nil?
113
+ raise ArgumentError, 'ProvenTxEntry requires a bump_index' if bump_index.nil?
60
114
 
61
- BSV::Primitives::Hex.validate_wtxid!(known_wtxid, name: 'known_wtxid') if known_wtxid
62
- @format = format
115
+ super()
63
116
  @transaction = transaction
64
- @known_wtxid = known_wtxid
65
117
  @bump_index = bump_index
66
118
  end
67
119
 
68
- # Wire-order transaction ID.
69
- # @return [String, nil] 32-byte wtxid
120
+ # @return [String] wire-order wtxid delegated to the transaction
70
121
  def wtxid
71
- case @format
72
- when FORMAT_TXID_ONLY
73
- @known_wtxid
74
- else
75
- @transaction&.wtxid
76
- end
122
+ @transaction.wtxid
77
123
  end
78
124
 
79
- # Display-order transaction ID as binary bytes.
80
- # @return [String, nil] 32-byte display-order txid
81
- def txid
82
- wtxid&.reverse
125
+ # @return [Integer] FORMAT_RAW_TX_AND_BUMP wire-protocol flag
126
+ def format_flag
127
+ FORMAT_RAW_TX_AND_BUMP
83
128
  end
129
+ end
84
130
 
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*')
131
+ # A BEEF entry containing only a transaction ID (no raw data).
132
+ class TxidOnlyEntry < BeefTx
133
+ # @return [String] 32-byte wire-order wtxid
134
+ attr_reader :known_wtxid
135
+
136
+ # @param known_wtxid [String] 32-byte wire-order wtxid
137
+ # @raise [ArgumentError] if known_wtxid is invalid
138
+ def initialize(known_wtxid:)
139
+ BSV::Primitives::Hex.validate_wtxid!(known_wtxid, name: 'known_wtxid')
140
+ super()
141
+ @known_wtxid = known_wtxid
142
+ end
143
+
144
+ # @return [String] the stored wire-order wtxid
145
+ def wtxid
146
+ @known_wtxid
147
+ end
148
+
149
+ # @return [Integer] FORMAT_TXID_ONLY wire-protocol flag
150
+ def format_flag
151
+ FORMAT_TXID_ONLY
93
152
  end
94
153
  end
95
154
 
@@ -105,12 +164,6 @@ module BSV
105
164
  # @return [String, nil] 32-byte wire-order subject txid (Atomic BEEF only)
106
165
  attr_reader :subject_wtxid
107
166
 
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
167
  # Display-order subject txid as a hex string (Atomic BEEF only).
115
168
  #
116
169
  # @return [String, nil] hex-encoded display-order txid, or nil
@@ -214,7 +267,7 @@ module BSV
214
267
  # any FORMAT_TXID_ONLY entries (V1 / BRC-62 has no TXID-only format;
215
268
  # pass +version: BEEF_V2+ to serialise such bundles)
216
269
  def to_binary(version: BEEF_V1)
217
- if version == BEEF_V1 && @transactions.any? { |bt| bt.format == FORMAT_TXID_ONLY }
270
+ if version == BEEF_V1 && @transactions.any?(TxidOnlyEntry)
218
271
  raise ArgumentError,
219
272
  'BEEF V1 (BRC-62) does not support FORMAT_TXID_ONLY entries; pass version: BEEF_V2 to serialise this bundle'
220
273
  end
@@ -274,6 +327,7 @@ module BSV
274
327
  BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
275
328
  BSV.logger&.debug { "[Beef] find_transaction: #{wtxid.reverse.unpack1('H*')} in #{@transactions.length} entries" }
276
329
  @transactions.each do |beef_tx|
330
+ next if beef_tx.is_a?(TxidOnlyEntry)
277
331
  return beef_tx.transaction if beef_tx.wtxid == wtxid
278
332
  end
279
333
  nil
@@ -289,8 +343,8 @@ module BSV
289
343
  def find_bump(wtxid)
290
344
  BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
291
345
  # Check transaction-table entries first (fast path)
292
- bt = @transactions.find { |entry| entry.wtxid == wtxid && entry.format == FORMAT_RAW_TX_AND_BUMP }
293
- return bt.transaction&.merkle_path || (bt.bump_index && @bumps[bt.bump_index]) if bt
346
+ bt = @transactions.find { |entry| entry.wtxid == wtxid && entry.is_a?(ProvenTxEntry) }
347
+ return bt.transaction.merkle_path || @bumps[bt.bump_index] if bt
294
348
 
295
349
  # F5.8: also scan @bumps directly for a path containing the wtxid leaf
296
350
  @bumps.find do |bump|
@@ -371,15 +425,11 @@ module BSV
371
425
  level0_leaves = bump.path[0] || []
372
426
  level0_internal = level0_leaves.map(&:hash).compact.to_set
373
427
  @transactions.each_with_index do |bt, i|
374
- next unless bt.format == FORMAT_RAW_TX && bt.transaction
375
- next unless level0_internal.include?(bt.transaction.wtxid)
428
+ next unless bt.is_a?(RawTxEntry)
429
+ next unless level0_internal.include?(bt.wtxid)
376
430
 
377
431
  bt.transaction.merkle_path ||= bump
378
- @transactions[i] = BeefTx.new(
379
- format: FORMAT_RAW_TX_AND_BUMP,
380
- transaction: bt.transaction,
381
- bump_index: idx
382
- )
432
+ @transactions[i] = ProvenTxEntry.new(transaction: bt.transaction, bump_index: idx)
383
433
  end
384
434
 
385
435
  idx
@@ -414,9 +464,9 @@ module BSV
414
464
  # Merge this transaction's BUMP if it has one
415
465
  entry = if tx.merkle_path
416
466
  bump_idx = merge_bump(tx.merkle_path)
417
- BeefTx.new(format: FORMAT_RAW_TX_AND_BUMP, transaction: tx, bump_index: bump_idx)
467
+ ProvenTxEntry.new(transaction: tx, bump_index: bump_idx)
418
468
  else
419
- BeefTx.new(format: FORMAT_RAW_TX, transaction: tx)
469
+ RawTxEntry.new(transaction: tx)
420
470
  end
421
471
  @transactions << entry
422
472
  entry
@@ -451,9 +501,9 @@ module BSV
451
501
  end
452
502
 
453
503
  entry = if bump_index
454
- BeefTx.new(format: FORMAT_RAW_TX_AND_BUMP, transaction: tx, bump_index: bump_index)
504
+ ProvenTxEntry.new(transaction: tx, bump_index: bump_index)
455
505
  else
456
- BeefTx.new(format: FORMAT_RAW_TX, transaction: tx)
506
+ RawTxEntry.new(transaction: tx)
457
507
  end
458
508
  @transactions << entry
459
509
  entry
@@ -479,15 +529,15 @@ module BSV
479
529
  # Merge transactions with remapped BUMP indices, constructing new
480
530
  # BeefTx instances rather than sharing source references (F5.9).
481
531
  other.transactions.each do |beef_tx|
482
- case beef_tx.format
483
- when FORMAT_TXID_ONLY
532
+ case beef_tx
533
+ when TxidOnlyEntry
484
534
  next if @transactions.any? { |bt| bt.wtxid == beef_tx.known_wtxid }
485
535
 
486
- @transactions << BeefTx.new(format: FORMAT_TXID_ONLY, known_wtxid: beef_tx.known_wtxid)
536
+ @transactions << TxidOnlyEntry.new(known_wtxid: beef_tx.known_wtxid)
487
537
  else
488
538
  next if @transactions.any? { |bt| bt.wtxid == beef_tx.wtxid }
489
539
 
490
- if beef_tx.format == FORMAT_RAW_TX_AND_BUMP && beef_tx.bump_index
540
+ if beef_tx.is_a?(ProvenTxEntry) && beef_tx.bump_index
491
541
  new_idx = bump_remap[beef_tx.bump_index]
492
542
  if new_idx.nil?
493
543
  raise ArgumentError,
@@ -499,13 +549,9 @@ module BSV
499
549
  # so mutations to the merged bundle don't affect the source.
500
550
  tx = beef_tx.transaction.dup
501
551
  tx.merkle_path = @bumps[new_idx]
502
- @transactions << BeefTx.new(
503
- format: FORMAT_RAW_TX_AND_BUMP,
504
- transaction: tx,
505
- bump_index: new_idx
506
- )
552
+ @transactions << ProvenTxEntry.new(transaction: tx, bump_index: new_idx)
507
553
  else
508
- @transactions << BeefTx.new(format: FORMAT_RAW_TX, transaction: beef_tx.transaction.dup)
554
+ @transactions << RawTxEntry.new(transaction: beef_tx.transaction.dup)
509
555
  end
510
556
  end
511
557
  end
@@ -522,7 +568,7 @@ module BSV
522
568
  idx = @transactions.index { |bt| bt.wtxid == wtxid }
523
569
  return unless idx
524
570
 
525
- @transactions[idx] = BeefTx.new(format: FORMAT_TXID_ONLY, known_wtxid: wtxid)
571
+ @transactions[idx] = TxidOnlyEntry.new(known_wtxid: wtxid)
526
572
  end
527
573
 
528
574
  # --- Validation ---
@@ -541,20 +587,20 @@ module BSV
541
587
  # @return [Boolean] true if structurally valid
542
588
  def valid?(allow_txid_only: false)
543
589
  # TXID-only entries are invalid unless explicitly allowed
544
- has_txid_only = @transactions.any? { |bt| bt.format == FORMAT_TXID_ONLY }
590
+ has_txid_only = @transactions.any?(TxidOnlyEntry)
545
591
  return false if has_txid_only && !allow_txid_only
546
592
 
547
593
  # F5.4: verify BUMP linkage and computed root for each proven transaction
548
594
  @transactions.each do |bt|
549
- next unless bt.format == FORMAT_RAW_TX_AND_BUMP
595
+ next unless bt.is_a?(ProvenTxEntry)
550
596
 
551
597
  # Must have a BUMP
552
- bump = bt.transaction&.merkle_path || (bt.bump_index && @bumps[bt.bump_index])
598
+ bump = bt.transaction.merkle_path || @bumps[bt.bump_index]
553
599
  return false unless bump
554
600
 
555
601
  # The txid must appear as a leaf in the BUMP and compute a valid root
556
602
  begin
557
- bump.compute_root(bt.transaction.wtxid)
603
+ bump.compute_root(bt.wtxid)
558
604
  rescue ArgumentError
559
605
  return false
560
606
  end
@@ -562,7 +608,7 @@ module BSV
562
608
 
563
609
  known_wtxids = build_known_wtxids(allow_txid_only)
564
610
 
565
- pending = @transactions.select { |bt| bt.transaction && !known_wtxids.include?(bt.wtxid) }
611
+ pending = @transactions.reject { |bt| bt.is_a?(TxidOnlyEntry) || known_wtxids.include?(bt.wtxid) }
566
612
 
567
613
  # Iteratively resolve: if all inputs of a tx are known, it becomes known
568
614
  changed = true
@@ -626,7 +672,7 @@ module BSV
626
672
  dependents = Array.new(@transactions.length) { [] }
627
673
 
628
674
  @transactions.each_with_index do |bt, i|
629
- next unless bt.transaction
675
+ next if bt.is_a?(TxidOnlyEntry)
630
676
 
631
677
  bt.transaction.inputs.each do |input|
632
678
  dep_idx = wtxid_index[input.prev_wtxid]
@@ -701,20 +747,18 @@ module BSV
701
747
 
702
748
  known_wtxid = data.byteslice(offset, 32)
703
749
  offset += 32
704
- beef.transactions << BeefTx.new(format: FORMAT_TXID_ONLY, known_wtxid: known_wtxid)
750
+ beef.transactions << TxidOnlyEntry.new(known_wtxid: known_wtxid)
705
751
  when FORMAT_RAW_TX_AND_BUMP
706
752
  bump_index, vi_size = VarInt.decode(data, offset)
707
753
  offset += vi_size
708
754
  tx, consumed = Transaction.from_binary_with_offset(data, offset)
709
755
  offset += consumed
710
756
  tx.merkle_path = beef.bumps[bump_index] if bump_index < beef.bumps.length
711
- beef.transactions << BeefTx.new(
712
- format: FORMAT_RAW_TX_AND_BUMP, transaction: tx, bump_index: bump_index
713
- )
757
+ beef.transactions << ProvenTxEntry.new(transaction: tx, bump_index: bump_index)
714
758
  when FORMAT_RAW_TX
715
759
  tx, consumed = Transaction.from_binary_with_offset(data, offset)
716
760
  offset += consumed
717
- beef.transactions << BeefTx.new(format: FORMAT_RAW_TX, transaction: tx)
761
+ beef.transactions << RawTxEntry.new(transaction: tx)
718
762
  end
719
763
  end
720
764
 
@@ -733,14 +777,12 @@ module BSV
733
777
  offset += 1
734
778
 
735
779
  if has_bump.zero?
736
- beef.transactions << BeefTx.new(format: FORMAT_RAW_TX, transaction: tx)
780
+ beef.transactions << RawTxEntry.new(transaction: tx)
737
781
  else
738
782
  bump_index, vi_size = VarInt.decode(data, offset)
739
783
  offset += vi_size
740
784
  tx.merkle_path = beef.bumps[bump_index] if bump_index < beef.bumps.length
741
- beef.transactions << BeefTx.new(
742
- format: FORMAT_RAW_TX_AND_BUMP, transaction: tx, bump_index: bump_index
743
- )
785
+ beef.transactions << ProvenTxEntry.new(transaction: tx, bump_index: bump_index)
744
786
  end
745
787
  end
746
788
 
@@ -750,7 +792,7 @@ module BSV
750
792
  def wire_source_transactions(beef)
751
793
  tx_map = {}
752
794
  beef.transactions.each do |beef_tx|
753
- next unless beef_tx.transaction
795
+ next if beef_tx.is_a?(TxidOnlyEntry)
754
796
 
755
797
  # Wire inputs to ancestors already in the map (BEEF is dependency-ordered).
756
798
  # Both prev_wtxid and wtxid are wire-order — no conversion needed.
@@ -765,7 +807,7 @@ module BSV
765
807
  end
766
808
  end
767
809
 
768
- tx_map[beef_tx.transaction.wtxid] = beef_tx.transaction
810
+ tx_map[beef_tx.wtxid] = beef_tx.transaction
769
811
  end
770
812
  end
771
813
  end
@@ -790,33 +832,28 @@ module BSV
790
832
  effective_bump_idx = bump_index
791
833
  effective_bump_idx = merge_bump(tx.merkle_path) if effective_bump_idx.nil? && tx&.merkle_path
792
834
 
793
- case existing.format
794
- when FORMAT_TXID_ONLY
835
+ case existing
836
+ when TxidOnlyEntry
795
837
  if effective_bump_idx
796
- BeefTx.new(format: FORMAT_RAW_TX_AND_BUMP, transaction: tx, bump_index: effective_bump_idx)
838
+ ProvenTxEntry.new(transaction: tx, bump_index: effective_bump_idx)
797
839
  elsif tx
798
- BeefTx.new(format: FORMAT_RAW_TX, transaction: tx)
840
+ RawTxEntry.new(transaction: tx)
799
841
  end
800
- when FORMAT_RAW_TX
842
+ when RawTxEntry
801
843
  if effective_bump_idx
802
844
  tx_to_use = tx || existing.transaction
803
845
  tx_to_use.merkle_path ||= @bumps[effective_bump_idx]
804
- BeefTx.new(format: FORMAT_RAW_TX_AND_BUMP, transaction: tx_to_use, bump_index: effective_bump_idx)
846
+ ProvenTxEntry.new(transaction: tx_to_use, bump_index: effective_bump_idx)
805
847
  end
806
848
  end
807
- # FORMAT_RAW_TX_AND_BUMP is already the strongest — no upgrade needed
849
+ # ProvenTxEntry is already the strongest — no upgrade needed
808
850
  end
809
851
 
810
852
  # Build a set of wire-order wtxids that are "known" (proven or txid-only).
811
853
  def build_known_wtxids(allow_txid_only)
812
854
  known = Set.new
813
855
  @transactions.each do |bt|
814
- case bt.format
815
- when FORMAT_RAW_TX_AND_BUMP
816
- known.add(bt.wtxid)
817
- when FORMAT_TXID_ONLY
818
- known.add(bt.wtxid) if allow_txid_only
819
- end
856
+ known.add(bt.wtxid) if bt.is_a?(ProvenTxEntry) || (bt.is_a?(TxidOnlyEntry) && allow_txid_only)
820
857
  end
821
858
  known
822
859
  end
@@ -846,7 +883,7 @@ module BSV
846
883
  # V1 (BRC-62): raw_tx + has_bump(byte) [+ bump_index(varint)]
847
884
  def write_v1_tx(buf, beef_tx)
848
885
  buf << beef_tx.transaction.to_binary
849
- if beef_tx.format == FORMAT_RAW_TX_AND_BUMP
886
+ if beef_tx.is_a?(ProvenTxEntry)
850
887
  buf << [1].pack('C')
851
888
  buf << VarInt.encode(beef_tx.bump_index)
852
889
  else
@@ -856,12 +893,12 @@ module BSV
856
893
 
857
894
  # V2 (BRC-96): format_byte [+ bump_index(varint)] + raw_tx
858
895
  def write_v2_tx(buf, beef_tx)
859
- case beef_tx.format
860
- when FORMAT_TXID_ONLY
896
+ case beef_tx
897
+ when TxidOnlyEntry
861
898
  buf << [FORMAT_TXID_ONLY].pack('C')
862
899
  # known_wtxid is already wire (internal) byte order.
863
900
  buf << beef_tx.known_wtxid
864
- when FORMAT_RAW_TX_AND_BUMP
901
+ when ProvenTxEntry
865
902
  buf << [FORMAT_RAW_TX_AND_BUMP].pack('C')
866
903
  buf << VarInt.encode(beef_tx.bump_index)
867
904
  buf << beef_tx.transaction.to_binary
@@ -337,13 +337,12 @@ module BSV
337
337
 
338
338
  ancestors.each do |tx|
339
339
  entry = if tx.merkle_path
340
- Beef::BeefTx.new(
341
- format: Beef::FORMAT_RAW_TX_AND_BUMP,
340
+ Beef::ProvenTxEntry.new(
342
341
  transaction: tx,
343
342
  bump_index: bump_index_by_height.fetch(tx.merkle_path.block_height)
344
343
  )
345
344
  else
346
- Beef::BeefTx.new(format: Beef::FORMAT_RAW_TX, transaction: tx)
345
+ Beef::RawTxEntry.new(transaction: tx)
347
346
  end
348
347
  beef.transactions << entry
349
348
  end
@@ -362,11 +361,11 @@ module BSV
362
361
  # full ancestry wired, including late-bound BUMP attachment.
363
362
  #
364
363
  # For Atomic BEEFs (BRC-95), the subject transaction is identified by
365
- # the embedded subject_txid field. For plain BEEFs, the last transaction
366
- # with a raw tx entry is used as the subject.
364
+ # the embedded +subject_wtxid+ field. For plain BEEFs, the last
365
+ # transaction with a raw tx entry is used as the subject.
367
366
  #
368
367
  # Uses +find_atomic_transaction+ so that FORMAT_RAW_TX ancestors whose
369
- # txid appears as a leaf in a separately-stored BUMP get their
368
+ # wtxid appears as a leaf in a separately-stored BUMP get their
370
369
  # +merkle_path+ wired correctly — a gap not covered by the initial
371
370
  # +wire_source_transactions+ pass in +Beef.from_binary+.
372
371
  #
@@ -376,7 +375,7 @@ module BSV
376
375
  def self.from_beef(data)
377
376
  beef = Beef.from_binary(data)
378
377
  subject_wtxid = beef.subject_wtxid ||
379
- beef.transactions.reverse.find(&:transaction)&.transaction&.wtxid
378
+ beef.transactions.reverse.find { |bt| !bt.is_a?(Beef::TxidOnlyEntry) }&.wtxid
380
379
  return nil unless subject_wtxid
381
380
 
382
381
  beef.find_atomic_transaction(subject_wtxid)
@@ -405,21 +404,11 @@ module BSV
405
404
  id
406
405
  end
407
406
 
408
- # Display-order transaction ID (reversed from the natural SHA-256d hash).
409
- #
410
- # This is the conventional human-readable representation used in block
411
- # explorers, wallets, and all user-facing contexts.
412
- #
413
- # @return [String] 32-byte transaction ID in display byte order
414
- def txid
415
- wtxid.reverse
416
- end
417
-
418
407
  # The transaction ID as a hex string (display byte order).
419
408
  #
420
- # @return [String] hex-encoded transaction ID
409
+ # @return [String] 64-char hex-encoded transaction ID (display order)
421
410
  def txid_hex
422
- txid.unpack1('H*')
411
+ wtxid.reverse.unpack1('H*')
423
412
  end
424
413
 
425
414
  # Display-order transaction ID as a hex string.
@@ -450,6 +439,29 @@ module BSV
450
439
  raise ArgumentError, 'only SIGHASH_FORKID types are supported' unless sighash_type & Sighash::FORK_ID != 0
451
440
 
452
441
  input = @inputs[input_index]
442
+ raise ArgumentError, "no input at index #{input_index}" if input.nil?
443
+
444
+ # Resolve source data from wired source_transaction when not explicitly set.
445
+ if input.source_transaction
446
+ source_output = input.source_transaction.outputs[input.prev_tx_out_index]
447
+ if source_output
448
+ input.source_satoshis ||= source_output.satoshis
449
+ input.source_locking_script ||= source_output.locking_script
450
+ end
451
+ end
452
+
453
+ if input.source_satoshis.nil?
454
+ raise ArgumentError,
455
+ "input #{input_index} has nil source_satoshis — " \
456
+ 'set it or wire source_transaction before computing sighash'
457
+ end
458
+
459
+ unless subscript || input.source_locking_script
460
+ raise ArgumentError,
461
+ "input #{input_index} has nil source_locking_script — " \
462
+ 'set it or wire source_transaction before computing sighash'
463
+ end
464
+
453
465
  base_type = sighash_type & Sighash::MASK
454
466
  anyone = sighash_type.anybits?(Sighash::ANYONE_CAN_PAY)
455
467
 
data/lib/bsv/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BSV
4
- VERSION = '0.17.0'
4
+ VERSION = '0.18.1'
5
5
  end
@@ -8,15 +8,51 @@ module BSV
8
8
  module Wallet
9
9
  # Minimal cryptographic wallet implementing the BRC-100 interface.
10
10
  #
11
- # ProtoWallet provides signing, encryption, HMAC, and key derivation
12
- # without transactions, storage, or blockchain interaction. It is the
13
- # direct implementation of the BRC-100 crypto methods, not a delegating
14
- # client. This makes it suitable for use in the SDK's Auth module without
15
- # depending on the bsv-wallet gem.
11
+ # ProtoWallet is a direct, in-process implementation of the BRC-100 crypto
12
+ # operations. It requires no external gem, no storage, and no blockchain
13
+ # access making it suitable for use inside the SDK itself (e.g. the Auth
14
+ # module) as well as lightweight applications that need cryptographic
15
+ # operations without full wallet infrastructure.
16
16
  #
17
- # Includes +BSV::Wallet::Interface::BRC100+ methods it supports are
18
- # overridden; unsupported methods (transactions, blockchain, authentication)
19
- # fall through to +NotImplementedError+.
17
+ # == Supported BRC-100 areas
18
+ #
19
+ # - *Public key management* — {#get_public_key}, {#reveal_counterparty_key_linkage},
20
+ # {#reveal_specific_key_linkage}
21
+ # - *Cryptography* — {#encrypt}, {#decrypt}, {#create_hmac}, {#verify_hmac},
22
+ # {#create_signature}, {#verify_signature}
23
+ # - *Certificates (read-only stub)* — {#list_certificates} returns an empty list;
24
+ # {#prove_certificate} raises {UnsupportedActionError}
25
+ #
26
+ # == NOT supported
27
+ #
28
+ # The following BRC-100 areas are not implemented and will raise
29
+ # +NotImplementedError+:
30
+ #
31
+ # - Transactions (+create_action+, +sign_action+, +list_actions+, etc.)
32
+ # - Output management (+list_outputs+, +relinquish_output+, etc.)
33
+ # - Authentication (+authenticated?+, +wait_for_authentication+)
34
+ # - Blockchain / network data (+get_height+, +get_header_for_height+, etc.)
35
+ # - Certificate acquisition / discovery (+acquire_certificate+,
36
+ # +discover_by_identity_key+, etc.)
37
+ #
38
+ # For full wallet functionality, use the +bsv-wallet+ gem. See
39
+ # +docs/sdk/wallet.md+ for usage guidance.
40
+ #
41
+ # == Construction
42
+ #
43
+ # Pass a {BSV::Primitives::PrivateKey} or the special string <tt>'anyone'</tt>.
44
+ # The <tt>'anyone'</tt> variant uses a well-known key (private key = 1) and is
45
+ # suitable for verifying or encrypting data that should be readable by
46
+ # any party — it must not be used where secrecy is required.
47
+ #
48
+ # @example Normal usage
49
+ # wallet = BSV::Wallet::ProtoWallet.new(BSV::Primitives::PrivateKey.generate)
50
+ # sig = wallet.create_signature(protocol_id: [1, 'my-app'], key_id: 'msg-1',
51
+ # data: 'hello'.bytes)
52
+ #
53
+ # @example Anyone wallet
54
+ # wallet = BSV::Wallet::ProtoWallet.new('anyone')
55
+ # pub = wallet.get_public_key(identity_key: true)
20
56
  #
21
57
  class ProtoWallet
22
58
  include Interface::BRC100