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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -0
- data/lib/bsv/auth/get_verifiable_certificates.rb +6 -6
- data/lib/bsv/auth/peer.rb +10 -4
- data/lib/bsv/auth/session_manager.rb +81 -5
- data/lib/bsv/identity/client.rb +4 -2
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +2 -2
- data/lib/bsv/mcp/tools/check_balance.rb +2 -2
- data/lib/bsv/mcp/tools/fetch_utxos.rb +2 -2
- data/lib/bsv/network/broadcast_error.rb +1 -0
- data/lib/bsv/network/broadcast_response.rb +3 -1
- data/lib/bsv/network/protocol.rb +56 -4
- data/lib/bsv/network/protocols/arc.rb +31 -11
- data/lib/bsv/network/protocols/chaintracks.rb +6 -2
- data/lib/bsv/network/protocols/jungle_bus.rb +52 -0
- data/lib/bsv/network/protocols/ordinals.rb +110 -8
- data/lib/bsv/network/protocols/taal_binary.rb +17 -4
- data/lib/bsv/network/protocols/woc_rest.rb +164 -84
- data/lib/bsv/network/protocols.rb +3 -3
- data/lib/bsv/network/provider.rb +36 -5
- data/lib/bsv/network/providers/gorilla_pool.rb +42 -20
- data/lib/bsv/network/providers/taal.rb +38 -15
- data/lib/bsv/network/providers/whats_on_chain.rb +42 -21
- data/lib/bsv/network/utxo.rb +8 -2
- data/lib/bsv/overlay/lookup_resolver.rb +5 -5
- data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
- data/lib/bsv/overlay/types.rb +1 -0
- data/lib/bsv/registry/client.rb +8 -8
- data/lib/bsv/registry/types.rb +1 -0
- data/lib/bsv/transaction/beef.rb +139 -102
- data/lib/bsv/transaction/transaction.rb +31 -19
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/proto_wallet.rb +44 -8
- data/lib/bsv/wire_format.rb +40 -14
- metadata +4 -3
data/lib/bsv/transaction/beef.rb
CHANGED
|
@@ -36,60 +36,119 @@ module BSV
|
|
|
36
36
|
|
|
37
37
|
# @!endgroup
|
|
38
38
|
|
|
39
|
-
#
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
#
|
|
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
|
-
# @
|
|
48
|
-
|
|
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 [
|
|
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
|
|
54
|
-
# @param
|
|
55
|
-
# @
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
69
|
-
# @return [String, nil] 32-byte wtxid
|
|
120
|
+
# @return [String] wire-order wtxid delegated to the transaction
|
|
70
121
|
def wtxid
|
|
71
|
-
|
|
72
|
-
when FORMAT_TXID_ONLY
|
|
73
|
-
@known_wtxid
|
|
74
|
-
else
|
|
75
|
-
@transaction&.wtxid
|
|
76
|
-
end
|
|
122
|
+
@transaction.wtxid
|
|
77
123
|
end
|
|
78
124
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
# @
|
|
91
|
-
|
|
92
|
-
|
|
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?
|
|
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.
|
|
293
|
-
return bt.transaction
|
|
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.
|
|
375
|
-
next unless level0_internal.include?(bt.
|
|
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] =
|
|
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
|
-
|
|
467
|
+
ProvenTxEntry.new(transaction: tx, bump_index: bump_idx)
|
|
418
468
|
else
|
|
419
|
-
|
|
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
|
-
|
|
504
|
+
ProvenTxEntry.new(transaction: tx, bump_index: bump_index)
|
|
455
505
|
else
|
|
456
|
-
|
|
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
|
|
483
|
-
when
|
|
532
|
+
case beef_tx
|
|
533
|
+
when TxidOnlyEntry
|
|
484
534
|
next if @transactions.any? { |bt| bt.wtxid == beef_tx.known_wtxid }
|
|
485
535
|
|
|
486
|
-
@transactions <<
|
|
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.
|
|
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 <<
|
|
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 <<
|
|
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] =
|
|
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?
|
|
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.
|
|
595
|
+
next unless bt.is_a?(ProvenTxEntry)
|
|
550
596
|
|
|
551
597
|
# Must have a BUMP
|
|
552
|
-
bump = bt.transaction
|
|
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.
|
|
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.
|
|
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
|
|
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 <<
|
|
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 <<
|
|
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 <<
|
|
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 <<
|
|
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 <<
|
|
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
|
|
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.
|
|
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
|
|
794
|
-
when
|
|
835
|
+
case existing
|
|
836
|
+
when TxidOnlyEntry
|
|
795
837
|
if effective_bump_idx
|
|
796
|
-
|
|
838
|
+
ProvenTxEntry.new(transaction: tx, bump_index: effective_bump_idx)
|
|
797
839
|
elsif tx
|
|
798
|
-
|
|
840
|
+
RawTxEntry.new(transaction: tx)
|
|
799
841
|
end
|
|
800
|
-
when
|
|
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
|
-
|
|
846
|
+
ProvenTxEntry.new(transaction: tx_to_use, bump_index: effective_bump_idx)
|
|
805
847
|
end
|
|
806
848
|
end
|
|
807
|
-
#
|
|
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
|
-
|
|
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.
|
|
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
|
|
860
|
-
when
|
|
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
|
|
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::
|
|
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::
|
|
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
|
|
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
|
-
#
|
|
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(
|
|
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
|
-
|
|
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
|
@@ -8,15 +8,51 @@ module BSV
|
|
|
8
8
|
module Wallet
|
|
9
9
|
# Minimal cryptographic wallet implementing the BRC-100 interface.
|
|
10
10
|
#
|
|
11
|
-
# ProtoWallet
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
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
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
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
|