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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -0
- data/README.md +1 -1
- data/lib/bsv/auth/auth_payload.rb +5 -0
- data/lib/bsv/identity/client.rb +9 -5
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -2
- data/lib/bsv/mcp/tools/decode_tx.rb +1 -1
- data/lib/bsv/mcp/tools/fetch_tx.rb +1 -1
- data/lib/bsv/mcp/tools/helpers.rb +1 -1
- data/lib/bsv/network/protocol.rb +12 -1
- data/lib/bsv/network/util.rb +13 -5
- data/lib/bsv/overlay/admin_token_template.rb +2 -2
- data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
- data/lib/bsv/overlay/types.rb +37 -0
- data/lib/bsv/registry/client.rb +11 -6
- data/lib/bsv/script/interpreter/interpreter.rb +1 -1
- data/lib/bsv/script/push_drop_template.rb +2 -2
- data/lib/bsv/transaction/beef.rb +14 -14
- data/lib/bsv/transaction/chain_tracker.rb +1 -1
- data/lib/bsv/transaction/fee_model.rb +1 -1
- data/lib/bsv/transaction/fee_models/live_policy.rb +1 -1
- data/lib/bsv/transaction/fee_models/satoshis_per_kilobyte.rb +1 -1
- data/lib/bsv/transaction/merkle_path.rb +1 -1
- data/lib/bsv/transaction/p2pkh.rb +1 -1
- data/lib/bsv/transaction/transaction_input.rb +1 -1
- data/lib/bsv/transaction/{transaction.rb → tx.rb} +14 -14
- data/lib/bsv/transaction/unlocking_script_template.rb +2 -2
- data/lib/bsv/transaction.rb +2 -2
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/proto_wallet/key_deriver.rb +13 -0
- data/lib/bsv/wallet/proto_wallet.rb +12 -2
- data/lib/bsv/wallet/serializer/create_signature.rb +7 -0
- data/lib/bsv/wallet/serializer/get_public_key.rb +5 -1
- data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +6 -3
- data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +6 -3
- data/lib/bsv/wallet/serializer/verify_signature.rb +7 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac0ee4456475effef9fa92627f77e4b973cfe834efda2f936725074c35060f95
|
|
4
|
+
data.tar.gz: acd91340c476db84531efd41003ea068e3b5029cbb63e74bab782d17b48694d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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::
|
|
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
|
data/lib/bsv/identity/client.rb
CHANGED
|
@@ -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::
|
|
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::
|
|
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::
|
|
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
|
|
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::
|
|
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|
|
data/lib/bsv/network/protocol.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
data/lib/bsv/network/util.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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::
|
|
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::
|
|
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::
|
|
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)
|
data/lib/bsv/overlay/types.rb
CHANGED
|
@@ -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
|
data/lib/bsv/registry/client.rb
CHANGED
|
@@ -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
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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::
|
|
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)
|
data/lib/bsv/transaction/beef.rb
CHANGED
|
@@ -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 [
|
|
75
|
+
# @return [Tx] the transaction
|
|
76
76
|
attr_reader :transaction
|
|
77
77
|
|
|
78
|
-
# @param 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 [
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 [
|
|
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 {
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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 +
|
|
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 [
|
|
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 [
|
|
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::
|
|
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
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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 [
|
|
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(
|
|
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
|
-
"[
|
|
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 [
|
|
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 [
|
|
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 { "[
|
|
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::
|
|
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
|
-
"[
|
|
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 { "[
|
|
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 [
|
|
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 [
|
|
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
|
data/lib/bsv/transaction.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module BSV
|
|
4
4
|
# Transaction building, signing, serialisation, and verification.
|
|
5
5
|
#
|
|
6
|
-
# Provides the {Transaction::
|
|
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 :
|
|
24
|
+
autoload :Tx, 'bsv/transaction/tx'
|
|
25
25
|
end
|
|
26
26
|
end
|
data/lib/bsv/version.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|