bsv-sdk 0.23.1 → 0.24.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +51 -0
  3. data/README.md +1 -1
  4. data/lib/bsv/auth/auth_payload.rb +5 -0
  5. data/lib/bsv/identity/client.rb +9 -5
  6. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -2
  7. data/lib/bsv/mcp/tools/decode_tx.rb +1 -1
  8. data/lib/bsv/mcp/tools/fetch_tx.rb +1 -1
  9. data/lib/bsv/mcp/tools/helpers.rb +1 -1
  10. data/lib/bsv/network/protocol.rb +12 -1
  11. data/lib/bsv/network/util.rb +13 -5
  12. data/lib/bsv/overlay/admin_token_template.rb +2 -2
  13. data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
  14. data/lib/bsv/overlay/types.rb +37 -0
  15. data/lib/bsv/registry/client.rb +11 -6
  16. data/lib/bsv/script/interpreter/interpreter.rb +1 -1
  17. data/lib/bsv/script/push_drop_template.rb +2 -2
  18. data/lib/bsv/transaction/beef.rb +14 -14
  19. data/lib/bsv/transaction/chain_tracker.rb +1 -1
  20. data/lib/bsv/transaction/fee_model.rb +1 -1
  21. data/lib/bsv/transaction/fee_models/live_policy.rb +1 -1
  22. data/lib/bsv/transaction/fee_models/satoshis_per_kilobyte.rb +1 -1
  23. data/lib/bsv/transaction/merkle_path.rb +1 -1
  24. data/lib/bsv/transaction/p2pkh.rb +1 -1
  25. data/lib/bsv/transaction/transaction_input.rb +1 -1
  26. data/lib/bsv/transaction/{transaction.rb → tx.rb} +14 -14
  27. data/lib/bsv/transaction/unlocking_script_template.rb +2 -2
  28. data/lib/bsv/transaction.rb +2 -2
  29. data/lib/bsv/version.rb +1 -1
  30. data/lib/bsv/wallet/proto_wallet/key_deriver.rb +13 -0
  31. data/lib/bsv/wallet/proto_wallet.rb +12 -2
  32. data/lib/bsv/wallet/serializer/create_signature.rb +7 -0
  33. data/lib/bsv/wallet/serializer/get_public_key.rb +5 -1
  34. data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +6 -3
  35. data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +6 -3
  36. data/lib/bsv/wallet/serializer/verify_signature.rb +7 -0
  37. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce9ccc589a407c6ad98d92e40b9ac05925f3fbf32cc73aa235b9fd9a35b7ad43
4
- data.tar.gz: 46992e9ea4f2465c4ca80ad2bb84e2591426610b46d1eea72e11171efdc271bf
3
+ metadata.gz: ac0ee4456475effef9fa92627f77e4b973cfe834efda2f936725074c35060f95
4
+ data.tar.gz: acd91340c476db84531efd41003ea068e3b5029cbb63e74bab782d17b48694d5
5
5
  SHA512:
6
- metadata.gz: ac7da8fbd4e5cb9e0385bb3035dc34d44460232afe826b095d19cccc04faece15cd090045c49d7d176f68d7ba98b5a16105812c66ae8ac27d657df8682b8c499
7
- data.tar.gz: 1b6946dd9762ac777aa8d1fff4392c96e30cfabd5c6b14f3eba7a8997e7ef0f8d5eca86383c131dfb479ce7f6ac94b7049bc50490edde5b0ff6b6994d8392629
6
+ metadata.gz: 02fffbfda10161282e87c26d8be157377a3b3861830b2891fe1331a3ea4b51e2743bc707fd01c8f3b1a5921173037d70322259c3d8dda39ea7caeffd2ee3cfd5
7
+ data.tar.gz: 0b4d5dea8e5b447d98603150a8873f14f8e24b7b75377fa5baa43bf497fedb62c11865ccc96eb15f78c49323987b896290955b483169dec065a307f468f3e584
data/CHANGELOG.md CHANGED
@@ -5,6 +5,57 @@ All notable changes to the `bsv-sdk` gem are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
6
6
  and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 0.24.0 — 2026-06-11
9
+
10
+ ### Breaking Changes
11
+ - **`BSV::Transaction::Transaction` renamed to `BSV::Transaction::Tx`** (#795). Class only;
12
+ module `BSV::Transaction` and peer classes (`TransactionInput`, `TransactionOutput`,
13
+ `ChainTracker`, `Beef`, etc.) unchanged. No compat alias — pre-1.0 clean break.
14
+ Downstream consumers update with `s/BSV::Transaction::Transaction/BSV::Transaction::Tx/g`.
15
+ - **`BSV::Wallet::WalletWireTransceiver` pubkey results are now 66-char hex** (#798),
16
+ matching the BRC-100 `PubKeyHex` contract and `ProtoWallet`'s direct-call shape.
17
+ Affects `get_public_key`, `reveal_counterparty_key_linkage`, `reveal_specific_key_linkage`.
18
+ Wire bytes remain 33-byte compressed binary; only the deserialised Ruby return shape
19
+ changed. See ADR-001 for the rationale.
20
+ - **`BSV::Identity::Client` and `BSV::Registry::Client` raise `BSV::Overlay::OverlayError`
21
+ subclasses on overlay broadcast failure** (#802) — `publicly_reveal_attributes`,
22
+ `revoke_certificate_revelation`, `register_definition`, `sign_and_broadcast` previously
23
+ returned a result hash and required callers to inspect `status` themselves.
24
+
25
+ ### Added
26
+ - `BSV::Wallet::ProtoWallet::KeyDeriver#identity_key_bytes` — binary accessor for the
27
+ identity pubkey, alongside the hex `identity_key`. Mirrors the planned bsv-wallet
28
+ KeyDeriver shape.
29
+ - `BSV::Overlay::OverlayBroadcastResult#success?` and `#raise_if_error!` — let callers
30
+ treat broadcast failure as an exception with the appropriate `OverlayError` subclass.
31
+ - ADR-001 documenting the SDK's binary-internal rule and the public-key hex exception
32
+ (`docs/guides/wtxid-dtxid.md` extension + `.architecture/decisions/adrs/ADR-001-…`).
33
+
34
+ ### Fixed
35
+ - `BSV::Network::Util.resolve_tx_hex` rejects empty input rather than silently producing
36
+ an empty `rawTx` broadcast (#799).
37
+ - `BSV::Network::Protocol#default_call` converts transport-level exceptions
38
+ (`SocketError`, `Errno::ECONNREFUSED`, `Net::OpenTimeout`, `OpenSSL::SSL::SSLError`,
39
+ etc.) into structured `ProtocolResponse` with `http_success: false` and descriptive
40
+ `error_message`, rather than propagating raw exceptions to callers (#807).
41
+ - `BSV::Auth::AuthPayload.serialize_request` raises `ArgumentError` for `request_nonce`
42
+ that isn't exactly 32 bytes, preventing malformed BRC-103 wire frames (#800).
43
+ - `BSV::MCP::Tools::BroadcastP2pkh` rescues `StandardError` rather than just
44
+ `ArgumentError`, so non-`ArgumentError` exceptions from invalid WIF input (e.g.
45
+ `NoMethodError` on `nil`) return a structured MCP error instead of crashing the tool
46
+ handler (#801).
47
+ - `BSV::Wallet::ProtoWallet#create_signature` / `#verify_signature` and matching wire
48
+ serialisers validate that `hash_to_directly_sign` / `hash_to_directly_verify` are
49
+ exactly 32 bytes, preventing malformed signatures (#805).
50
+ - `BSV::Attest.verify` asserts that the provider's `fetch_transaction` returns a Tx-like
51
+ object before iterating, producing a clear `ArgumentError` instead of a low-signal
52
+ `NoMethodError` (#803).
53
+
54
+ ### Changed
55
+ - Bitcoin Core script-vectors spec now enforces a bidirectional regression gate:
56
+ previously-failing vectors that now pass surface as failures so `known_failures.json`
57
+ stays honest. Cleaned out 101 stale entries (#806).
58
+
8
59
  ## 0.23.1 — 2026-06-06
9
60
 
10
61
  ### Fixed
data/README.md CHANGED
@@ -86,7 +86,7 @@ pubkey_hash = priv_key.public_key.hash160
86
86
  locking_script = BSV::Script::Script.p2pkh_lock(pubkey_hash)
87
87
 
88
88
  # Create a transaction spending a UTXO
89
- tx = BSV::Transaction::Transaction.new
89
+ tx = BSV::Transaction::Tx.new
90
90
 
91
91
  # Add an input referencing a previous transaction output
92
92
  input = BSV::Transaction::TransactionInput.new(
@@ -34,6 +34,11 @@ module BSV
34
34
  # @param body [String, nil] request body bytes; nil encodes as ABSENT
35
35
  # @return [String] binary payload (ASCII-8BIT encoding)
36
36
  def serialize_request(request_nonce:, method:, path:, query:, headers:, body:)
37
+ unless request_nonce.is_a?(String) && request_nonce.bytesize == 32
38
+ got = request_nonce.respond_to?(:bytesize) ? "#{request_nonce.class}/#{request_nonce.bytesize}" : request_nonce.class
39
+ raise ArgumentError, "request_nonce must be a 32-byte binary string, got #{got}"
40
+ end
41
+
37
42
  buf = ''.b
38
43
 
39
44
  # 32-byte request nonce — written raw, no length prefix
@@ -107,6 +107,8 @@ module BSV
107
107
  # @return [BSV::Overlay::OverlayBroadcastResult]
108
108
  # @raise [ArgumentError] if the certificate has no fields or fields_to_reveal is empty
109
109
  # @raise [RuntimeError] if certificate verification fails or create_action returns no tx
110
+ # @raise [BSV::Overlay::OverlayError] (or a subclass) if the overlay broadcast fails —
111
+ # see {BSV::Overlay::OverlayBroadcastResult#raise_if_error!} for the mapping
110
112
  def publicly_reveal_attributes(certificate, fields_to_reveal:)
111
113
  fields = certificate[:fields] || certificate['fields'] || {}
112
114
  raise ArgumentError, 'Public reveal failed: Certificate has no fields to reveal!' if fields.empty?
@@ -169,8 +171,8 @@ module BSV
169
171
 
170
172
  raise 'Public reveal failed: failed to create action!' if create_result[:tx].nil?
171
173
 
172
- tx = BSV::Transaction::Transaction.from_beef(create_result[:tx])
173
- broadcaster_for_action.broadcast(tx)
174
+ tx = BSV::Transaction::Tx.from_beef(create_result[:tx])
175
+ broadcaster_for_action.broadcast(tx).raise_if_error!
174
176
  end
175
177
 
176
178
  # Revokes a publicly revealed certificate by spending the identity token.
@@ -182,6 +184,8 @@ module BSV
182
184
  # @param serial_number [String] Base64 serial number of the certificate revelation to revoke
183
185
  # @return [void]
184
186
  # @raise [RuntimeError] if the revelation cannot be found or the transaction cannot be created
187
+ # @raise [BSV::Overlay::OverlayError] (or a subclass) if the overlay broadcast fails —
188
+ # see {BSV::Overlay::OverlayBroadcastResult#raise_if_error!} for the mapping
185
189
  def revoke_certificate_revelation(serial_number)
186
190
  question = BSV::Overlay::LookupQuestion.new(
187
191
  service: Constants::SERVICE,
@@ -232,7 +236,7 @@ module BSV
232
236
  raise 'Revoke failed: failed to create signable transaction' if create_result[:signable_transaction].nil?
233
237
 
234
238
  signable = create_result[:signable_transaction]
235
- partial_tx = BSV::Transaction::Transaction.from_beef(signable[:tx])
239
+ partial_tx = BSV::Transaction::Tx.from_beef(signable[:tx])
236
240
 
237
241
  # Unlock via PushDrop
238
242
  template = BSV::Script::PushDropTemplate.new(wallet: @wallet, originator: @originator)
@@ -255,8 +259,8 @@ module BSV
255
259
 
256
260
  raise 'Revoke failed: failed to sign transaction' if sign_result[:tx].nil?
257
261
 
258
- signed_tx = BSV::Transaction::Transaction.from_beef(sign_result[:tx])
259
- broadcaster_for_action.broadcast(signed_tx)
262
+ signed_tx = BSV::Transaction::Tx.from_beef(sign_result[:tx])
263
+ broadcaster_for_action.broadcast(signed_tx).raise_if_error!
260
264
  end
261
265
 
262
266
  private
@@ -127,7 +127,10 @@ module BSV
127
127
  [::MCP::Content::Text.new(result.to_json)],
128
128
  structured_content: result
129
129
  )
130
- rescue ArgumentError => e
130
+ rescue StandardError => e
131
+ # Catch broadly so MCP returns a structured error rather than
132
+ # crashing the tool handler on unexpected exception types
133
+ # (e.g. NoMethodError on nil WIF input, encoding errors, etc.).
131
134
  Helpers.error_response(e.message)
132
135
  end
133
136
 
@@ -150,7 +153,7 @@ module BSV
150
153
  # Build, fee-compute, and sign the transaction.
151
154
  # @api private
152
155
  def self.build_transaction(selected_utxos, satoshis, to_address, sender_address, private_key)
153
- tx = BSV::Transaction::Transaction.new
156
+ tx = BSV::Transaction::Tx.new
154
157
 
155
158
  # Wire inputs with EF metadata (source_satoshis + source_locking_script)
156
159
  selected_utxos.each do |utxo|
@@ -48,7 +48,7 @@ module BSV
48
48
  )
49
49
 
50
50
  def self.call(hex:, **)
51
- tx = BSV::Transaction::Transaction.from_hex(hex)
51
+ tx = BSV::Transaction::Tx.from_hex(hex)
52
52
  result = Helpers.transaction_to_h(tx)
53
53
 
54
54
  ::MCP::Tool::Response.new(
@@ -59,7 +59,7 @@ module BSV
59
59
  return Helpers.error_response(msg)
60
60
  end
61
61
 
62
- tx = BSV::Transaction::Transaction.from_hex(fetch_result.data)
62
+ tx = BSV::Transaction::Tx.from_hex(fetch_result.data)
63
63
 
64
64
  result = {
65
65
  hex: tx.to_hex,
@@ -21,7 +21,7 @@ module BSV
21
21
 
22
22
  # Convert a Transaction to a hash suitable for JSON responses.
23
23
  #
24
- # @param tx [BSV::Transaction::Transaction]
24
+ # @param tx [BSV::Transaction::Tx]
25
25
  # @return [Hash]
26
26
  def self.transaction_to_h(tx)
27
27
  {
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'net/http'
4
4
  require 'json'
5
+ require 'openssl'
5
6
  require 'uri'
6
7
 
7
8
  module BSV
@@ -179,7 +180,17 @@ module BSV
179
180
 
180
181
  BSV.logger&.debug { "[Protocol] #{defn[:method].upcase} #{uri}" }
181
182
 
182
- response = execute(uri, request)
183
+ begin
184
+ response = execute(uri, request)
185
+ rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT,
186
+ Errno::EHOSTUNREACH, Errno::ENETUNREACH,
187
+ Net::OpenTimeout, Net::ReadTimeout,
188
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
189
+ OpenSSL::SSL::SSLError => e
190
+ BSV.logger&.debug { "[Protocol] transport error: #{e.class}: #{e.message}" }
191
+ return ProtocolResponse.new(nil, http_success: false,
192
+ error_message: "transport error: #{e.class}: #{e.message}")
193
+ end
183
194
 
184
195
  build_response(response, defn[:response])
185
196
  end
@@ -25,19 +25,27 @@ module BSV
25
25
  # length and contains only hex characters. This handles hex strings
26
26
  # tagged as ASCII-8BIT (e.g. read from IO in binary mode).
27
27
  #
28
+ # Empty input is rejected — an empty transaction cannot meaningfully
29
+ # be broadcast and any downstream `rawTx: ''` body is a sign of a
30
+ # caller bug, not valid traffic.
31
+ #
28
32
  # @param tx [String, #to_ef_hex, #to_hex] transaction in any supported form
29
33
  # @return [String] hex-encoded transaction
34
+ # @raise [ArgumentError] if `tx` is an empty string
30
35
  def self.resolve_tx_hex(tx)
31
36
  if tx.is_a?(String)
32
- return tx if tx.match?(/\A[0-9a-fA-F]*\z/) && tx.length.even?
37
+ raise ArgumentError, 'tx cannot be empty — refusing to construct an empty broadcast' if tx.empty?
38
+ return tx if tx.match?(/\A[0-9a-fA-F]+\z/) && tx.length.even?
33
39
 
34
40
  return tx.unpack1('H*')
35
41
  end
36
42
 
37
- tx.to_ef_hex
38
- rescue ArgumentError => e
39
- BSV.logger&.debug { "[Network::Util] EF serialisation failed: #{e.message} — falling back to raw hex" }
40
- tx.to_hex
43
+ begin
44
+ tx.to_ef_hex
45
+ rescue ArgumentError => e
46
+ BSV.logger&.debug { "[Network::Util] EF serialisation failed: #{e.message} — falling back to raw hex" }
47
+ tx.to_hex
48
+ end
41
49
  end
42
50
  end
43
51
  end
@@ -80,7 +80,7 @@ module BSV
80
80
  # Computes the BIP-143 sighash (SIGHASH_ALL|FORK_ID) and signs it
81
81
  # using the wallet's derived key for the protocol.
82
82
  #
83
- # @param tx [BSV::Transaction::Transaction] the spending transaction
83
+ # @param tx [BSV::Transaction::Tx] the spending transaction
84
84
  # @param input_index [Integer] which input to sign
85
85
  # @return [BSV::Script::Script] the unlocking script
86
86
  def sign(tx, input_index)
@@ -101,7 +101,7 @@ module BSV
101
101
 
102
102
  # Estimated byte length of the unlocking script.
103
103
  #
104
- # @param _tx [BSV::Transaction::Transaction] unused
104
+ # @param _tx [BSV::Transaction::Tx] unused
105
105
  # @param _input_index [Integer] unused
106
106
  # @return [Integer]
107
107
  def estimated_length(_tx, _input_index)
@@ -74,7 +74,7 @@ module BSV
74
74
 
75
75
  # Broadcast a transaction to all interested overlay hosts.
76
76
  #
77
- # @param tx [BSV::Transaction::Transaction] the transaction to broadcast
77
+ # @param tx [BSV::Transaction::Tx] the transaction to broadcast
78
78
  # @return [OverlayBroadcastResult]
79
79
  def broadcast(tx)
80
80
  beef = serialise_beef(tx)
@@ -108,6 +108,43 @@ module BSV
108
108
  @code = code
109
109
  @description = description
110
110
  end
111
+
112
+ # @return [Boolean] true when the broadcast reached at least one
113
+ # competent host and satisfied any acknowledgement requirements.
114
+ def success?
115
+ @status == 'success'
116
+ end
117
+
118
+ # Raise the appropriate {OverlayError} subclass when {#success?} is
119
+ # false. Lets callers treat a broadcast failure as an exception rather
120
+ # than silently swallowing the result object — used by Identity and
121
+ # Registry clients which terminate on broadcast failure regardless.
122
+ #
123
+ # @return [self] when the result is a success (chainable)
124
+ # @raise [NoCompetentHostsError] for +ERR_NO_HOSTS_INTERESTED+
125
+ # @raise [AllHostsRejectedError] for +ERR_ALL_HOSTS_REJECTED+
126
+ # @raise [AcknowledgementError] for +ERR_REQUIRE_ACK_*_FAILED+
127
+ # @raise [OverlayError] for any other non-success status
128
+ def raise_if_error!
129
+ return self if success?
130
+
131
+ message = [@code, @description].compact.join(': ')
132
+ raise error_class_for_code, message
133
+ end
134
+
135
+ private
136
+
137
+ def error_class_for_code
138
+ case @code
139
+ when 'ERR_NO_HOSTS_INTERESTED' then NoCompetentHostsError
140
+ when 'ERR_ALL_HOSTS_REJECTED' then AllHostsRejectedError
141
+ when 'ERR_REQUIRE_ACK_FROM_ANY_HOST_FAILED',
142
+ 'ERR_REQUIRE_ACK_FROM_ALL_HOSTS_FAILED',
143
+ 'ERR_REQUIRE_ACK_FROM_SPECIFIC_HOSTS_FAILED'
144
+ AcknowledgementError
145
+ else OverlayError
146
+ end
147
+ end
111
148
  end
112
149
  end
113
150
  end
@@ -50,7 +50,9 @@ module BSV
50
50
  # @param data [BasketDefinitionData, ProtocolDefinitionData, CertificateDefinitionData]
51
51
  # structured definition data
52
52
  # @return [BSV::Overlay::OverlayBroadcastResult]
53
- # @raise [RuntimeError] if the transaction cannot be created or broadcast
53
+ # @raise [RuntimeError] if the transaction cannot be created
54
+ # @raise [BSV::Overlay::OverlayError] (or a subclass) if the overlay broadcast fails —
55
+ # see {BSV::Overlay::OverlayBroadcastResult#raise_if_error!} for the mapping
54
56
  def register_definition(definition_type, data)
55
57
  registry_operator = identity_key
56
58
  fields = build_pushdrop_fields(definition_type, data, registry_operator)
@@ -81,8 +83,8 @@ module BSV
81
83
 
82
84
  raise "Register failed: could not create #{definition_type} registration transaction" if create_result[:tx].nil?
83
85
 
84
- tx = BSV::Transaction::Transaction.from_beef(normalise_beef(create_result[:tx]))
85
- broadcaster_for(definition_type).broadcast(tx)
86
+ tx = BSV::Transaction::Tx.from_beef(normalise_beef(create_result[:tx]))
87
+ broadcaster_for(definition_type).broadcast(tx).raise_if_error!
86
88
  end
87
89
 
88
90
  # Resolves registry definitions of a given type using the overlay lookup service.
@@ -463,9 +465,12 @@ module BSV
463
465
  # @param definition_type [String]
464
466
  # @param create_result [Hash] result from wallet.create_action containing :signable_transaction
465
467
  # @return [BSV::Overlay::OverlayBroadcastResult]
468
+ # @raise [RuntimeError] if the transaction cannot be signed
469
+ # @raise [BSV::Overlay::OverlayError] (or a subclass) if the overlay broadcast fails —
470
+ # see {BSV::Overlay::OverlayBroadcastResult#raise_if_error!} for the mapping
466
471
  def sign_and_broadcast(definition_type, create_result)
467
472
  signable = create_result[:signable_transaction]
468
- partial_tx = BSV::Transaction::Transaction.from_beef(normalise_beef(signable[:tx]))
473
+ partial_tx = BSV::Transaction::Tx.from_beef(normalise_beef(signable[:tx]))
469
474
 
470
475
  template = BSV::Script::PushDropTemplate.new(wallet: @wallet, originator: @originator)
471
476
  unlocker = template.unlock(
@@ -488,8 +493,8 @@ module BSV
488
493
 
489
494
  raise 'Revoke failed: could not sign transaction' if sign_result[:tx].nil?
490
495
 
491
- signed_tx = BSV::Transaction::Transaction.from_beef(normalise_beef(sign_result[:tx]))
492
- broadcaster_for(definition_type).broadcast(signed_tx)
496
+ signed_tx = BSV::Transaction::Tx.from_beef(normalise_beef(sign_result[:tx]))
497
+ broadcaster_for(definition_type).broadcast(signed_tx).raise_if_error!
493
498
  end
494
499
 
495
500
  # Verifies that the registered definition belongs to the current wallet.
@@ -70,7 +70,7 @@ module BSV
70
70
 
71
71
  # Verify a transaction input by evaluating its scripts.
72
72
  #
73
- # @param tx [Transaction::Transaction] the transaction being verified
73
+ # @param tx [Transaction::Tx] the transaction being verified
74
74
  # @param input_index [Integer] the input index within the transaction
75
75
  # @param unlock_script [Script] the input's unlocking script
76
76
  # @param lock_script [Script] the previous output's locking script
@@ -93,7 +93,7 @@ module BSV
93
93
  # the wallet's derived key, then returns a P2PKH unlock wrapped in a
94
94
  # PushDrop unlock (which is a pass-through).
95
95
  #
96
- # @param tx [BSV::Transaction::Transaction] the spending transaction
96
+ # @param tx [BSV::Transaction::Tx] the spending transaction
97
97
  # @param input_index [Integer] which input to sign
98
98
  # @return [BSV::Script::Script] the unlocking script
99
99
  def sign(tx, input_index)
@@ -125,7 +125,7 @@ module BSV
125
125
 
126
126
  # Estimated byte length of the unlocking script.
127
127
  #
128
- # @param _tx [BSV::Transaction::Transaction] unused
128
+ # @param _tx [BSV::Transaction::Tx] unused
129
129
  # @param _input_index [Integer] unused
130
130
  # @return [Integer]
131
131
  def estimated_length(_tx, _input_index)
@@ -72,10 +72,10 @@ module BSV
72
72
 
73
73
  # A BEEF entry containing a raw transaction without a merkle proof.
74
74
  class RawTxEntry < BeefTx
75
- # @return [Transaction] the transaction
75
+ # @return [Tx] the transaction
76
76
  attr_reader :transaction
77
77
 
78
- # @param transaction [Transaction] the transaction
78
+ # @param transaction [Tx] the transaction
79
79
  # @raise [ArgumentError] if transaction is nil
80
80
  def initialize(transaction:)
81
81
  raise ArgumentError, 'RawTxEntry requires a transaction' if transaction.nil?
@@ -97,13 +97,13 @@ module BSV
97
97
 
98
98
  # A BEEF entry containing a raw transaction with an associated BUMP index.
99
99
  class ProvenTxEntry < BeefTx
100
- # @return [Transaction] the transaction
100
+ # @return [Tx] the transaction
101
101
  attr_reader :transaction
102
102
 
103
103
  # @return [Integer] index into the BEEF bumps array
104
104
  attr_reader :bump_index
105
105
 
106
- # @param transaction [Transaction] the transaction
106
+ # @param transaction [Tx] the transaction
107
107
  # @param bump_index [Integer] index into the bumps array
108
108
  # @raise [ArgumentError] if transaction or bump_index is nil
109
109
  def initialize(transaction:, bump_index:)
@@ -320,7 +320,7 @@ module BSV
320
320
  # Find a transaction in the bundle by its wire-order transaction ID.
321
321
  #
322
322
  # @param wtxid [String] 32-byte wire-order wtxid
323
- # @return [Transaction, nil] the matching transaction, or nil
323
+ # @return [Tx, nil] the matching transaction, or nil
324
324
  def find_transaction(wtxid)
325
325
  BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
326
326
  BSV.logger&.debug { "[Beef] find_transaction: #{wtxid.reverse.unpack1('H*')} in #{@transactions.length} entries" }
@@ -353,7 +353,7 @@ module BSV
353
353
  # Find a transaction with all source_transactions wired for signing.
354
354
  #
355
355
  # @param wtxid [String] 32-byte wire-order wtxid
356
- # @return [Transaction, nil] the transaction with wired inputs, or nil
356
+ # @return [Tx, nil] the transaction with wired inputs, or nil
357
357
  def find_transaction_for_signing(wtxid)
358
358
  BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
359
359
  tx = find_transaction(wtxid)
@@ -367,7 +367,7 @@ module BSV
367
367
  # and merkle paths) for atomic proof validation.
368
368
  #
369
369
  # @param wtxid [String] 32-byte wire-order wtxid
370
- # @return [Transaction, nil] the transaction with full proof tree, or nil
370
+ # @return [Tx, nil] the transaction with full proof tree, or nil
371
371
  def find_atomic_transaction(wtxid)
372
372
  BSV::Primitives::Hex.validate_wtxid!(wtxid, name: 'wtxid')
373
373
  tx = find_transaction(wtxid)
@@ -440,7 +440,7 @@ module BSV
440
440
  # (same txid) are upgraded if a stronger format is now available (F5.7):
441
441
  # TXID_ONLY → RAW_TX or RAW_TX_AND_BUMP; RAW_TX → RAW_TX_AND_BUMP.
442
442
  #
443
- # @param tx [Transaction] the transaction to merge
443
+ # @param tx [Tx] the transaction to merge
444
444
  # @return [BeefTx] the (possibly existing or upgraded) BeefTx entry
445
445
  def merge_transaction(tx)
446
446
  wtxid = tx.wtxid
@@ -479,7 +479,7 @@ module BSV
479
479
  # @param bump_index [Integer, nil] optional BUMP index
480
480
  # @return [BeefTx] the new or upgraded BeefTx entry
481
481
  def merge_raw_tx(raw_bytes, bump_index: nil)
482
- tx = Transaction.from_binary(raw_bytes)
482
+ tx = Tx.from_binary(raw_bytes)
483
483
 
484
484
  if bump_index
485
485
  unless bump_index.is_a?(Integer) && bump_index >= 0 && bump_index < @bumps.length
@@ -740,7 +740,7 @@ module BSV
740
740
  case format
741
741
  when FORMAT_TXID_ONLY
742
742
  # Wire stores txid in internal (little-endian / wire) byte order;
743
- # store as-is in known_wtxid so it matches Transaction#wtxid.
743
+ # store as-is in known_wtxid so it matches Tx#wtxid.
744
744
  raise ArgumentError, 'truncated BEEF: not enough bytes for TXID_ONLY entry' if offset + 32 > data.bytesize
745
745
 
746
746
  known_wtxid = data.byteslice(offset, 32)
@@ -749,12 +749,12 @@ module BSV
749
749
  when FORMAT_RAW_TX_AND_BUMP
750
750
  bump_index, vi_size = VarInt.decode(data, offset)
751
751
  offset += vi_size
752
- tx, consumed = Transaction.from_binary_with_offset(data, offset)
752
+ tx, consumed = Tx.from_binary_with_offset(data, offset)
753
753
  offset += consumed
754
754
  tx.merkle_path = beef.bumps[bump_index] if bump_index < beef.bumps.length
755
755
  beef.transactions << ProvenTxEntry.new(transaction: tx, bump_index: bump_index)
756
756
  when FORMAT_RAW_TX
757
- tx, consumed = Transaction.from_binary_with_offset(data, offset)
757
+ tx, consumed = Tx.from_binary_with_offset(data, offset)
758
758
  offset += consumed
759
759
  beef.transactions << RawTxEntry.new(transaction: tx)
760
760
  end
@@ -768,7 +768,7 @@ module BSV
768
768
  offset += vi_size
769
769
 
770
770
  num_txs.times do
771
- tx, consumed = Transaction.from_binary_with_offset(data, offset)
771
+ tx, consumed = Tx.from_binary_with_offset(data, offset)
772
772
  offset += consumed
773
773
 
774
774
  has_bump = data.getbyte(offset)
@@ -821,7 +821,7 @@ module BSV
821
821
  # RAW_TX → RAW_TX_AND_BUMP (if bump now available)
822
822
  #
823
823
  # @param existing [BeefTx] the current entry in @transactions
824
- # @param tx [Transaction, nil] the raw transaction (may be nil for TXID_ONLY → TXID_ONLY)
824
+ # @param tx [Tx, nil] the raw transaction (may be nil for TXID_ONLY → TXID_ONLY)
825
825
  # @param bump_index [Integer, nil] a BUMP index already validated against @bumps
826
826
  # @return [BeefTx, nil] the upgraded entry, or nil when no upgrade is needed
827
827
  def upgrade_beef_tx(existing, tx, bump_index: nil)
@@ -4,7 +4,7 @@ module BSV
4
4
  module Transaction
5
5
  # Duck type for block header lookups used by the SDK's verify methods.
6
6
  #
7
- # {Beef#verify}, {MerklePath#verify}, and {Transaction#verify} define
7
+ # {Beef#verify}, {MerklePath#verify}, and {Tx#verify} define
8
8
  # what a valid structure *is* — they walk trees, check proofs, and
9
9
  # compare roots. But they have no data source of their own. The
10
10
  # chain tracker is the data source: an object the consumer provides
@@ -17,7 +17,7 @@ module BSV
17
17
  class FeeModel
18
18
  # Compute the fee for a transaction.
19
19
  #
20
- # @param transaction [Transaction] the transaction to compute the fee for
20
+ # @param transaction [Tx] the transaction to compute the fee for
21
21
  # @return [Integer] the fee in satoshis
22
22
  # @raise [NotImplementedError] if not overridden by a subclass
23
23
  def compute_fee(_transaction)
@@ -66,7 +66,7 @@ module BSV
66
66
 
67
67
  # Compute the fee for a transaction using the latest ARC rate.
68
68
  #
69
- # @param transaction [Transaction] the transaction to compute the fee for
69
+ # @param transaction [Tx] the transaction to compute the fee for
70
70
  # @return [Integer] the fee in satoshis
71
71
  def compute_fee(transaction)
72
72
  rate = current_rate
@@ -23,7 +23,7 @@ module BSV
23
23
 
24
24
  # Compute the fee for a transaction based on its estimated size.
25
25
  #
26
- # @param transaction [Transaction] the transaction to compute the fee for
26
+ # @param transaction [Tx] the transaction to compute the fee for
27
27
  # @return [Integer] the fee in satoshis
28
28
  def compute_fee(transaction)
29
29
  size = transaction.estimated_size
@@ -450,7 +450,7 @@ module BSV
450
450
  # that the extracted path computes the same merkle root as the
451
451
  # source.
452
452
  #
453
- # The primary use case is +Transaction#to_beef+: when a BUMP loaded
453
+ # The primary use case is +Tx#to_beef+: when a BUMP loaded
454
454
  # from a proof store carries +txid: true+ flags for transactions
455
455
  # that are not part of the current BEEF bundle, extracting only the
456
456
  # bundled txids strips the phantom flags (and the now-unneeded
@@ -24,7 +24,7 @@ module BSV
24
24
 
25
25
  # Generate the P2PKH unlocking script for the given input.
26
26
  #
27
- # @param tx [Transaction] the transaction being signed
27
+ # @param tx [Tx] the transaction being signed
28
28
  # @param input_index [Integer] the input index to sign
29
29
  # @return [Script::Script] the unlocking script (signature + pubkey)
30
30
  def sign(tx, input_index)
@@ -26,7 +26,7 @@ module BSV
26
26
  # @return [Script::Script, nil] locking script of the source output (needed for sighash)
27
27
  attr_accessor :source_locking_script
28
28
 
29
- # @return [Transaction, nil] the full source transaction (for BEEF wiring)
29
+ # @return [Tx, nil] the full source transaction (for BEEF wiring)
30
30
  attr_accessor :source_transaction
31
31
 
32
32
  # @return [UnlockingScriptTemplate, nil] template for deferred signing
@@ -10,12 +10,12 @@ module BSV
10
10
  # estimation.
11
11
  #
12
12
  # @example Build, sign, and serialise a transaction
13
- # tx = BSV::Transaction::Transaction.new
13
+ # tx = BSV::Transaction::Tx.new
14
14
  # tx.add_input(input)
15
15
  # tx.add_output(output)
16
16
  # tx.sign(0, private_key)
17
17
  # tx.to_hex #=> "0100000001..."
18
- class Transaction
18
+ class Tx
19
19
  # Estimated size of an unsigned P2PKH input in bytes.
20
20
  UNSIGNED_P2PKH_INPUT_SIZE = 148
21
21
 
@@ -144,7 +144,7 @@ module BSV
144
144
  # Deserialise a transaction from binary data.
145
145
  #
146
146
  # @param data [String] raw binary transaction
147
- # @return [Transaction] the parsed transaction
147
+ # @return [Tx] the parsed transaction
148
148
  def self.from_binary(data)
149
149
  raise ArgumentError, "truncated transaction: need at least 10 bytes, got #{data.bytesize}" if data.bytesize < 10
150
150
 
@@ -182,7 +182,7 @@ module BSV
182
182
  # Deserialise a transaction from a hex string.
183
183
  #
184
184
  # @param hex [String] hex-encoded transaction
185
- # @return [Transaction] the parsed transaction
185
+ # @return [Tx] the parsed transaction
186
186
  def self.from_hex(hex)
187
187
  from_binary(BSV::Primitives::Hex.decode(hex, name: 'transaction hex'))
188
188
  end
@@ -190,7 +190,7 @@ module BSV
190
190
  # Deserialise a transaction from Extended Format (BRC-30) binary data.
191
191
  #
192
192
  # @param data [String] raw EF binary
193
- # @return [Transaction] the parsed transaction with source data on inputs
193
+ # @return [Tx] the parsed transaction with source data on inputs
194
194
  # @raise [ArgumentError] if the EF marker is invalid
195
195
  def self.from_ef(data)
196
196
  raise ArgumentError, "truncated EF transaction: need at least 10 bytes, got #{data.bytesize}" if data.bytesize < 10
@@ -250,7 +250,7 @@ module BSV
250
250
  # Deserialise a transaction from an Extended Format hex string.
251
251
  #
252
252
  # @param hex [String] hex-encoded EF transaction
253
- # @return [Transaction] the parsed transaction with source data on inputs
253
+ # @return [Tx] the parsed transaction with source data on inputs
254
254
  def self.from_ef_hex(hex)
255
255
  from_ef(BSV::Primitives::Hex.decode(hex, name: 'EF transaction hex'))
256
256
  end
@@ -260,7 +260,7 @@ module BSV
260
260
  #
261
261
  # @param data [String] binary data containing the transaction
262
262
  # @param offset [Integer] byte offset to start reading from
263
- # @return [Array(Transaction, Integer)] the transaction and bytes consumed
263
+ # @return [Array(Tx, Integer)] the transaction and bytes consumed
264
264
  def self.from_binary_with_offset(data, offset = 0)
265
265
  if data.bytesize < offset + 10
266
266
  raise ArgumentError, "truncated transaction: need at least 10 bytes at offset #{offset}, got #{data.bytesize - offset}"
@@ -331,7 +331,7 @@ module BSV
331
331
  bump_index_by_height = build_beef_bumps(beef, ancestors)
332
332
  BSV.logger&.debug do
333
333
  proven = ancestors.count(&:merkle_path)
334
- "[Transaction] BEEF: #{ancestors.length} ancestors, #{proven} proven " \
334
+ "[Tx] BEEF: #{ancestors.length} ancestors, #{proven} proven " \
335
335
  "across #{bump_index_by_height.length} block heights"
336
336
  end
337
337
 
@@ -370,7 +370,7 @@ module BSV
370
370
  # +wire_source_transactions+ pass in +Beef.from_binary+.
371
371
  #
372
372
  # @param data [String] raw BEEF binary
373
- # @return [Transaction, nil] the subject transaction with ancestry wired,
373
+ # @return [Tx, nil] the subject transaction with ancestry wired,
374
374
  # or nil if the BEEF is empty or contains no raw transaction entries
375
375
  def self.from_beef(data)
376
376
  beef = Beef.from_binary(data)
@@ -384,7 +384,7 @@ module BSV
384
384
  # Parse a BEEF hex string and return the subject transaction.
385
385
  #
386
386
  # @param hex [String] hex-encoded BEEF
387
- # @return [Transaction, nil] the subject transaction with ancestry wired,
387
+ # @return [Tx, nil] the subject transaction with ancestry wired,
388
388
  # or nil if the BEEF is empty or contains no raw transaction entries
389
389
  def self.from_beef_hex(hex)
390
390
  from_beef(BSV::Primitives::Hex.decode(hex, name: 'BEEF hex'))
@@ -400,7 +400,7 @@ module BSV
400
400
  # @return [String] 32-byte transaction ID in wire byte order
401
401
  def wtxid
402
402
  id = BSV::Primitives::Digest.sha256d(to_binary)
403
- BSV.logger&.debug { "[Transaction] wtxid computed (dtxid=#{id.reverse.unpack1('H*')})" }
403
+ BSV.logger&.debug { "[Tx] wtxid computed (dtxid=#{id.reverse.unpack1('H*')})" }
404
404
  id
405
405
  end
406
406
 
@@ -729,7 +729,7 @@ module BSV
729
729
  # @return [Integer] estimated fee in satoshis (rounded up)
730
730
  def estimated_fee(satoshis_per_byte: 0.1)
731
731
  unless self.class.instance_variable_get(:@_estimated_fee_warned)
732
- warn '[DEPRECATION] BSV::Transaction::Transaction#estimated_fee is deprecated. ' \
732
+ warn '[DEPRECATION] BSV::Transaction::Tx#estimated_fee is deprecated. ' \
733
733
  'Use BSV::Transaction::FeeModels::SatoshisPerKilobyte.new.compute_fee(tx) instead.', uplevel: 1
734
734
  self.class.instance_variable_set(:@_estimated_fee_warned, true)
735
735
  end
@@ -913,12 +913,12 @@ module BSV
913
913
 
914
914
  if tx.merkle_path
915
915
  BSV.logger&.debug do
916
- "[Transaction] ancestor: #{tx.dtxid_hex} proven at height #{tx.merkle_path.block_height} (leaf stop)"
916
+ "[Tx] ancestor: #{tx.dtxid_hex} proven at height #{tx.merkle_path.block_height} (leaf stop)"
917
917
  end
918
918
  else
919
919
  tx.inputs.each_with_index do |input, idx|
920
920
  unless input.source_transaction
921
- BSV.logger&.debug { "[Transaction] ancestor: #{tx.dtxid_hex} input #{idx} has no source_transaction (skipped)" }
921
+ BSV.logger&.debug { "[Tx] ancestor: #{tx.dtxid_hex} input #{idx} has no source_transaction (skipped)" }
922
922
  next
923
923
  end
924
924
 
@@ -14,7 +14,7 @@ module BSV
14
14
  class UnlockingScriptTemplate
15
15
  # Generate the unlocking script for a transaction input.
16
16
  #
17
- # @param _tx [Transaction] the transaction being signed
17
+ # @param _tx [Tx] the transaction being signed
18
18
  # @param _input_index [Integer] the input index to sign
19
19
  # @return [Script::Script] the generated unlocking script
20
20
  # @raise [NotImplementedError] if not overridden by a subclass
@@ -24,7 +24,7 @@ module BSV
24
24
 
25
25
  # Estimate the unlocking script length in bytes (for fee estimation).
26
26
  #
27
- # @param _tx [Transaction] the transaction
27
+ # @param _tx [Tx] the transaction
28
28
  # @param _input_index [Integer] the input index
29
29
  # @return [Integer] estimated script length in bytes
30
30
  # @raise [NotImplementedError] if not overridden by a subclass
@@ -3,7 +3,7 @@
3
3
  module BSV
4
4
  # Transaction building, signing, serialisation, and verification.
5
5
  #
6
- # Provides the {Transaction::Transaction} class for constructing and signing
6
+ # Provides the {Transaction::Tx} class for constructing and signing
7
7
  # transactions, {Transaction::Beef} for BEEF (BRC-62/95/96) serialisation,
8
8
  # {Transaction::MerklePath} for BRC-74 merkle proofs, and
9
9
  # {Transaction::Sighash} constants for BIP-143 sighash computation.
@@ -21,6 +21,6 @@ module BSV
21
21
  autoload :Beef, 'bsv/transaction/beef'
22
22
  autoload :UnlockingScriptTemplate, 'bsv/transaction/unlocking_script_template'
23
23
  autoload :P2PKH, 'bsv/transaction/p2pkh'
24
- autoload :Transaction, 'bsv/transaction/transaction'
24
+ autoload :Tx, 'bsv/transaction/tx'
25
25
  end
26
26
  end
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.23.1'
4
+ VERSION = '0.24.0'
5
5
  end
@@ -34,6 +34,19 @@ module BSV
34
34
  @identity_key ||= @root_key.public_key.to_hex
35
35
  end
36
36
 
37
+ # Returns the identity public key as a 33-byte compressed binary string.
38
+ #
39
+ # Pubkeys are hex-canonical in this SDK (see ADR-001 — the documented
40
+ # exception to binary-internal), so {#identity_key} (hex) is the
41
+ # default. This binary accessor exists for the rare sites that need
42
+ # raw bytes (e.g. feeding +BSV::Primitives::Hash#hash160+) without
43
+ # round-tripping hex → binary at each call.
44
+ #
45
+ # @return [String] 33-byte compressed public key binary
46
+ def identity_key_bytes
47
+ @identity_key_bytes ||= @root_key.public_key.compressed
48
+ end
49
+
37
50
  # Derives a public key using BRC-42 key derivation.
38
51
  #
39
52
  # @param protocol_id [Array] [security_level, protocol_name]
@@ -167,7 +167,7 @@ module BSV
167
167
  priv_key = @key_deriver.derive_private_key(protocol_id, key_id, counterparty)
168
168
 
169
169
  hash = if hash_to_directly_sign
170
- bytes_to_string(hash_to_directly_sign)
170
+ validate_hash_32!(hash_to_directly_sign, 'hash_to_directly_sign')
171
171
  else
172
172
  BSV::Primitives::Digest.sha256(bytes_to_string(data))
173
173
  end
@@ -203,7 +203,7 @@ module BSV
203
203
  )
204
204
 
205
205
  hash = if hash_to_directly_verify
206
- bytes_to_string(hash_to_directly_verify)
206
+ validate_hash_32!(hash_to_directly_verify, 'hash_to_directly_verify')
207
207
  else
208
208
  BSV::Primitives::Digest.sha256(bytes_to_string(data))
209
209
  end
@@ -347,6 +347,16 @@ module BSV
347
347
  bytes.is_a?(String) ? bytes.b : bytes.pack('C*')
348
348
  end
349
349
 
350
+ # Coerce a hash parameter to a 32-byte binary string, raising
351
+ # InvalidParameterError if the input has the wrong length. Accepts
352
+ # either a binary String or an Array<Integer> per BRC-100 convention.
353
+ def validate_hash_32!(value, name)
354
+ bytes = bytes_to_string(value)
355
+ raise InvalidParameterError.new(name, "exactly 32 bytes, got #{bytes.bytesize}") unless bytes.bytesize == 32
356
+
357
+ bytes
358
+ end
359
+
350
360
  # Normalise a public key argument to a 66-character compressed hex string.
351
361
  # Accepts either a 33-byte binary string or a 66-character hex string.
352
362
  def normalise_pubkey_hex(value)
@@ -30,6 +30,13 @@ module BSV
30
30
  end
31
31
  raise BSV::Wallet::InvalidParameterError.new('data or hash_to_directly_sign', 'present') unless data || hash
32
32
 
33
+ if hash && hash.bytesize != HASH_SIZE
34
+ raise BSV::Wallet::InvalidParameterError.new(
35
+ 'hash_to_directly_sign',
36
+ "exactly #{HASH_SIZE} bytes, got #{hash.bytesize}"
37
+ )
38
+ end
39
+
33
40
  w = BSV::Wallet::Wire::Writer.new
34
41
  Common.write_key_related_params(
35
42
  w,
@@ -66,6 +66,10 @@ module BSV
66
66
 
67
67
  # Result wire layout:
68
68
  # [33 bytes: compressed public key]
69
+ #
70
+ # The wire bytes are 33-byte compressed binary by the BRC-103 protocol,
71
+ # but the Ruby return shape is hex per the BRC-100 PubKeyHex contract
72
+ # (ADR-001 — pubkeys are the documented hex exception to binary-internal).
69
73
  module Result
70
74
  module_function
71
75
 
@@ -77,7 +81,7 @@ module BSV
77
81
  def deserialize(bytes)
78
82
  raise ArgumentError, "public key too short: #{bytes.bytesize}" if bytes.bytesize < PUBKEY_SIZE
79
83
 
80
- { public_key: bytes.byteslice(0, PUBKEY_SIZE) }
84
+ { public_key: bytes.byteslice(0, PUBKEY_SIZE).unpack1('H*') }
81
85
  end
82
86
  end
83
87
  end
@@ -75,11 +75,14 @@ module BSV
75
75
  w.buf
76
76
  end
77
77
 
78
+ # Pubkey fields are returned as 66-char hex per the BRC-100 PubKeyHex
79
+ # contract (ADR-001 — pubkeys are the documented hex exception to
80
+ # binary-internal). Wire bytes stay 33-byte compressed binary.
78
81
  def deserialize(bytes)
79
82
  r = BSV::Wallet::Wire::Reader.new(bytes)
80
- prover = r.read_bytes(PUBKEY_SIZE)
81
- verifier = r.read_bytes(PUBKEY_SIZE)
82
- counterparty = r.read_bytes(PUBKEY_SIZE)
83
+ prover = r.read_bytes(PUBKEY_SIZE).unpack1('H*')
84
+ verifier = r.read_bytes(PUBKEY_SIZE).unpack1('H*')
85
+ counterparty = r.read_bytes(PUBKEY_SIZE).unpack1('H*')
83
86
  revelation_time = r.read_str_with_varint_len
84
87
  el_len = r.read_varint
85
88
  encrypted_linkage = r.read_bytes(el_len)
@@ -78,11 +78,14 @@ module BSV
78
78
  w.buf
79
79
  end
80
80
 
81
+ # Pubkey fields are returned as 66-char hex per the BRC-100 PubKeyHex
82
+ # contract (ADR-001 — pubkeys are the documented hex exception to
83
+ # binary-internal). Wire bytes stay 33-byte compressed binary.
81
84
  def deserialize(bytes)
82
85
  r = BSV::Wallet::Wire::Reader.new(bytes)
83
- prover = r.read_bytes(PUBKEY_SIZE)
84
- verifier = r.read_bytes(PUBKEY_SIZE)
85
- counterparty = r.read_bytes(PUBKEY_SIZE)
86
+ prover = r.read_bytes(PUBKEY_SIZE).unpack1('H*')
87
+ verifier = r.read_bytes(PUBKEY_SIZE).unpack1('H*')
88
+ counterparty = r.read_bytes(PUBKEY_SIZE).unpack1('H*')
86
89
  protocol_id = Common.read_protocol(r)
87
90
  key_id_len = r.read_varint
88
91
  key_id = r.read_bytes(key_id_len).force_encoding('UTF-8')
@@ -34,6 +34,13 @@ module BSV
34
34
  raise BSV::Wallet::InvalidParameterError.new('data or hash_to_directly_verify', 'present') unless data || hash
35
35
  raise BSV::Wallet::InvalidParameterError.new('signature', 'present') unless sig
36
36
 
37
+ if hash && hash.bytesize != HASH_SIZE
38
+ raise BSV::Wallet::InvalidParameterError.new(
39
+ 'hash_to_directly_verify',
40
+ "exactly #{HASH_SIZE} bytes, got #{hash.bytesize}"
41
+ )
42
+ end
43
+
37
44
  w = BSV::Wallet::Wire::Writer.new
38
45
  Common.write_key_related_params(
39
46
  w,
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bsv-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.1
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Simon Bettison
@@ -181,9 +181,9 @@ files:
181
181
  - lib/bsv/transaction/merkle_path.rb
182
182
  - lib/bsv/transaction/p2pkh.rb
183
183
  - lib/bsv/transaction/sighash.rb
184
- - lib/bsv/transaction/transaction.rb
185
184
  - lib/bsv/transaction/transaction_input.rb
186
185
  - lib/bsv/transaction/transaction_output.rb
186
+ - lib/bsv/transaction/tx.rb
187
187
  - lib/bsv/transaction/unlocking_script_template.rb
188
188
  - lib/bsv/transaction/var_int.rb
189
189
  - lib/bsv/transaction/verification_error.rb