bsv-sdk 0.15.0 → 0.17.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 +38 -0
- data/lib/bsv/auth/auth_middleware.rb +6 -6
- data/lib/bsv/auth/certificate.rb +22 -18
- data/lib/bsv/auth/master_certificate.rb +5 -5
- data/lib/bsv/auth/nonce.rb +13 -13
- data/lib/bsv/auth/peer.rb +53 -53
- data/lib/bsv/auth/verifiable_certificate.rb +1 -1
- data/lib/bsv/identity/client.rb +27 -32
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +18 -12
- data/lib/bsv/mcp/tools/check_balance.rb +16 -4
- data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
- data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
- data/lib/bsv/mcp/tools/helpers.rb +2 -2
- data/lib/bsv/network/arc.rb +13 -153
- data/lib/bsv/network/broadcast_error.rb +1 -0
- data/lib/bsv/network/broadcast_response.rb +1 -0
- data/lib/bsv/network/protocols/arc.rb +4 -3
- data/lib/bsv/network/protocols/taal_binary.rb +1 -0
- data/lib/bsv/network/protocols/woc_rest.rb +2 -1
- data/lib/bsv/network/whats_on_chain.rb +13 -107
- data/lib/bsv/overlay/admin_token_template.rb +4 -4
- data/lib/bsv/overlay/lookup_resolver.rb +1 -0
- data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
- data/lib/bsv/overlay/types.rb +1 -0
- data/lib/bsv/primitives/hex.rb +64 -0
- data/lib/bsv/registry/client.rb +26 -28
- data/lib/bsv/registry/types.rb +1 -0
- data/lib/bsv/script/interpreter/interpreter.rb +7 -0
- data/lib/bsv/script/interpreter/operations/crypto.rb +7 -1
- data/lib/bsv/script/push_drop_template.rb +4 -4
- data/lib/bsv/transaction/beef.rb +122 -83
- data/lib/bsv/transaction/merkle_path.rb +54 -38
- data/lib/bsv/transaction/transaction.rb +81 -30
- data/lib/bsv/transaction/transaction_input.rb +23 -18
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/errors.rb +47 -0
- data/lib/bsv/wallet/interface/brc100.rb +270 -0
- data/lib/bsv/wallet/interface.rb +9 -0
- data/lib/bsv/wallet/proto_wallet/key_deriver.rb +152 -0
- data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
- data/lib/bsv/wallet/proto_wallet.rb +327 -0
- data/lib/bsv/wallet.rb +16 -0
- data/lib/bsv-sdk.rb +18 -1
- metadata +22 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c528b336af4735e247429423231a5c6a0dd681718e94d7a3d9598b65b75a6a0
|
|
4
|
+
data.tar.gz: 21efe7a43b3ba356953af1668cb35227d9c72bd3b17a1f08a54fac2f55f0d577
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 94b507d613413fbb96ab08f72d5db324ea33626f0f32af34f248b7d3133bd847aff1527de7ea1e68c1a1cbc1f5140f7654d4be99726348d85b852483ea00519a
|
|
7
|
+
data.tar.gz: 71c1fd8a86414cce69c1c4ec4fe1df52bab863c624ba70b806f37e2e016968eca9ac0244c42b0d52407c1817f56d9133403d3aec04a41bb39766776eb4b8c108
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,44 @@ 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.17.0 — 2026-05-02
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
- Renamed `TransactionInput#prev_tx_id` to `#prev_wtxid` — all call sites must update (#678)
|
|
12
|
+
- Renamed MerklePath parameters from `txid_hex` to `dtxid_hex` throughout (#681)
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `Transaction#wtxid` — wire-order transaction ID (raw SHA-256d bytes) (#673)
|
|
16
|
+
- `Transaction#dtxid` / `#dtxid_hex` — display-order hex aliases (#680)
|
|
17
|
+
- `TransactionInput#dtxid_hex` — display-order hex of the referenced transaction (#678)
|
|
18
|
+
- `TransactionInput.wtxid_from_hex` — convert display-order hex to wire-order bytes (#678)
|
|
19
|
+
- `Hex.validate_wtxid!` / `Hex.validate_dtxid_hex!` — runtime validation for wire/display txid formats (#685)
|
|
20
|
+
- `Hex.validate_hash32!` — general-purpose 32-byte binary hash validator (#686)
|
|
21
|
+
- `BSV.logger` — opt-in debug instrumentation with zero overhead when unused (#686)
|
|
22
|
+
- Debug logging at txid conversions, sighash preimage, script interpreter, ARC broadcast, BEEF ancestry, and ProtoWallet key derivation (#686, #688)
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- All internal txid variables renamed to `wtxid` (wire-order) convention (#679)
|
|
26
|
+
- BEEF internals enforce wire-order throughout — no byte-reversals inside the bundle (#675)
|
|
27
|
+
- MerklePath `compute_root_hex` and `from_tsc` now validate hex input (#686)
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- `BeefTx#dtxid` now returns hex string (was returning binary) (#677)
|
|
31
|
+
- Certificate field naming aligned with BRC-52 convention (#677)
|
|
32
|
+
|
|
33
|
+
## 0.16.0 — 2026-05-01
|
|
34
|
+
|
|
35
|
+
### Breaking Changes
|
|
36
|
+
- Removed `bsv-wallet` and `bsv-wallet-postgres` gems from the monorepo (#662)
|
|
37
|
+
- Unified `BSV::Wallet` namespace with canonical BRC-100 interface, error reconciliation, and idiomatic ProtoWallet (#664)
|
|
38
|
+
- Hard-deprecated `Network::ARC` and `Network::WhatsOnChain` facades (#659)
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- `BSV::Wallet::ProtoWallet` for SDK-native cryptographic operations (signing, encryption, HMAC, key derivation) (#662)
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
- Guard nil `status_code` in MCP error messages (#659)
|
|
45
|
+
|
|
8
46
|
## 0.15.0 — 2026-04-29
|
|
9
47
|
|
|
10
48
|
### Added
|
|
@@ -180,12 +180,12 @@ module BSV
|
|
|
180
180
|
response_nonce = ::Base64.strict_encode64(::SecureRandom.random_bytes(32))
|
|
181
181
|
key_id = "#{response_nonce} #{session.peer_nonce}"
|
|
182
182
|
|
|
183
|
-
sig_result = @wallet.create_signature(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
183
|
+
sig_result = @wallet.create_signature(
|
|
184
|
+
data: response_payload_bin.bytes,
|
|
185
|
+
protocol_id: Peer::AUTH_PROTOCOL,
|
|
186
|
+
key_id: key_id,
|
|
187
|
+
counterparty: identity_key
|
|
188
|
+
)
|
|
189
189
|
|
|
190
190
|
our_identity_key = @peer.identity_key
|
|
191
191
|
sig_hex_out = sig_result[:signature].pack('C*').unpack1('H*')
|
data/lib/bsv/auth/certificate.rb
CHANGED
|
@@ -81,6 +81,9 @@ module BSV
|
|
|
81
81
|
buf << [@subject].pack('H*')
|
|
82
82
|
buf << [@certifier].pack('H*')
|
|
83
83
|
|
|
84
|
+
# Certificate binary format: revocation outpoint txid stored in display byte
|
|
85
|
+
# order (matching TS and Go SDKs). Go encodes via WriteBytesReverse(wire_order),
|
|
86
|
+
# TS encodes via toArray(display_hex) — both produce identical display-order bytes.
|
|
84
87
|
txid_hex, output_index_str = @revocation_outpoint.to_s.split('.', 2)
|
|
85
88
|
buf << [txid_hex].pack('H*')
|
|
86
89
|
buf << BSV::Transaction::VarInt.encode(output_index_str.to_i)
|
|
@@ -124,7 +127,8 @@ module BSV
|
|
|
124
127
|
certifier_bytes = data.byteslice(pos, 33)
|
|
125
128
|
pos += 33
|
|
126
129
|
|
|
127
|
-
|
|
130
|
+
# Outpoint txid bytes — stored in display byte order (matching TS and Go SDKs).
|
|
131
|
+
outpoint_txid = data.byteslice(pos, 32)
|
|
128
132
|
pos += 32
|
|
129
133
|
output_index, vi_len = BSV::Transaction::VarInt.decode(data, pos)
|
|
130
134
|
pos += vi_len
|
|
@@ -158,7 +162,7 @@ module BSV
|
|
|
158
162
|
serial_number: Base64.strict_encode64(serial_bytes),
|
|
159
163
|
subject: subject_bytes.unpack1('H*'),
|
|
160
164
|
certifier: certifier_bytes.unpack1('H*'),
|
|
161
|
-
revocation_outpoint: "#{
|
|
165
|
+
revocation_outpoint: "#{outpoint_txid.unpack1('H*')}.#{output_index}",
|
|
162
166
|
fields: fields,
|
|
163
167
|
signature: signature
|
|
164
168
|
)
|
|
@@ -166,27 +170,27 @@ module BSV
|
|
|
166
170
|
|
|
167
171
|
# Verify the certificate's signature.
|
|
168
172
|
#
|
|
169
|
-
# Uses a fresh +'anyone'+
|
|
173
|
+
# Uses a fresh +'anyone'+ ProtoWallet as the verifier, which matches the
|
|
170
174
|
# TS SDK behaviour. If no signature is present, raises +ArgumentError+.
|
|
171
175
|
#
|
|
172
176
|
# @param verifier_wallet [#verify_signature, nil] wallet to verify with;
|
|
173
|
-
# defaults to +BSV::Wallet::
|
|
177
|
+
# defaults to +BSV::Wallet::ProtoWallet.new('anyone')+
|
|
174
178
|
# @return [Boolean] +true+ if the signature is valid
|
|
175
179
|
# @raise [ArgumentError] if the certificate has no signature
|
|
176
180
|
def verify(verifier_wallet = nil)
|
|
177
181
|
raise ArgumentError, 'certificate has no signature to verify' if @signature.nil? || @signature.empty?
|
|
178
182
|
|
|
179
|
-
verifier_wallet ||= BSV::Wallet::
|
|
183
|
+
verifier_wallet ||= BSV::Wallet::ProtoWallet.new('anyone')
|
|
180
184
|
preimage = to_binary(include_signature: false)
|
|
181
185
|
sig_bytes = [@signature].pack('H*').unpack('C*')
|
|
182
186
|
|
|
183
|
-
result = verifier_wallet.verify_signature(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
187
|
+
result = verifier_wallet.verify_signature(
|
|
188
|
+
data: preimage.unpack('C*'),
|
|
189
|
+
signature: sig_bytes,
|
|
190
|
+
protocol_id: CERT_SIG_PROTOCOL,
|
|
191
|
+
key_id: "#{@type} #{@serial_number}",
|
|
192
|
+
counterparty: @certifier
|
|
193
|
+
)
|
|
190
194
|
|
|
191
195
|
result.is_a?(Hash) && result[:valid] == true
|
|
192
196
|
rescue BSV::Wallet::InvalidSignatureError
|
|
@@ -203,14 +207,14 @@ module BSV
|
|
|
203
207
|
def sign(certifier_wallet)
|
|
204
208
|
raise ArgumentError, "certificate has already been signed: #{@signature}" if @signature && !@signature.empty?
|
|
205
209
|
|
|
206
|
-
@certifier = certifier_wallet.get_public_key(
|
|
210
|
+
@certifier = certifier_wallet.get_public_key(identity_key: true)[:public_key]
|
|
207
211
|
|
|
208
212
|
preimage = to_binary(include_signature: false)
|
|
209
|
-
result = certifier_wallet.create_signature(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
result = certifier_wallet.create_signature(
|
|
214
|
+
data: preimage.unpack('C*'),
|
|
215
|
+
protocol_id: CERT_SIG_PROTOCOL,
|
|
216
|
+
key_id: "#{@type} #{@serial_number}"
|
|
217
|
+
)
|
|
214
218
|
@signature = result[:signature].pack('C*').unpack1('H*')
|
|
215
219
|
end
|
|
216
220
|
|
|
@@ -128,7 +128,7 @@ module BSV
|
|
|
128
128
|
privileged_reason: privileged_reason
|
|
129
129
|
}.merge(Certificate.certificate_field_encryption_details(field_name))
|
|
130
130
|
|
|
131
|
-
result = creator_wallet.encrypt(enc_args)
|
|
131
|
+
result = creator_wallet.encrypt(**enc_args)
|
|
132
132
|
master_keyring[field_name] = Base64.strict_encode64(result[:ciphertext].pack('C*'))
|
|
133
133
|
end
|
|
134
134
|
|
|
@@ -187,7 +187,7 @@ module BSV
|
|
|
187
187
|
privileged_reason: privileged_reason
|
|
188
188
|
}.merge(Certificate.certificate_field_encryption_details(field_name, serial_number))
|
|
189
189
|
|
|
190
|
-
result = subject_wallet.encrypt(enc_args)
|
|
190
|
+
result = subject_wallet.encrypt(**enc_args)
|
|
191
191
|
keyring[field_name] = Base64.strict_encode64(result[:ciphertext].pack('C*'))
|
|
192
192
|
end
|
|
193
193
|
|
|
@@ -218,7 +218,7 @@ module BSV
|
|
|
218
218
|
keyring = result[:master_keyring]
|
|
219
219
|
|
|
220
220
|
subject_key = if subject == 'self'
|
|
221
|
-
certifier_wallet.get_public_key(
|
|
221
|
+
certifier_wallet.get_public_key(identity_key: true)[:public_key]
|
|
222
222
|
else
|
|
223
223
|
subject
|
|
224
224
|
end
|
|
@@ -229,7 +229,7 @@ module BSV
|
|
|
229
229
|
"#{'00' * 32}.0"
|
|
230
230
|
end
|
|
231
231
|
|
|
232
|
-
certifier_key = certifier_wallet.get_public_key(
|
|
232
|
+
certifier_key = certifier_wallet.get_public_key(identity_key: true)[:public_key]
|
|
233
233
|
|
|
234
234
|
cert = new(
|
|
235
235
|
type: certificate_type,
|
|
@@ -309,7 +309,7 @@ module BSV
|
|
|
309
309
|
privileged_reason: privileged_reason
|
|
310
310
|
}.merge(Certificate.certificate_field_encryption_details(field_name))
|
|
311
311
|
|
|
312
|
-
field_revelation_key = wallet.decrypt(dec_args)[:plaintext]
|
|
312
|
+
field_revelation_key = wallet.decrypt(**dec_args)[:plaintext]
|
|
313
313
|
|
|
314
314
|
sym_key = BSV::Primitives::SymmetricKey.new(field_revelation_key.pack('C*'))
|
|
315
315
|
decrypted_bytes = sym_key.decrypt(Base64.strict_decode64(field_value))
|
data/lib/bsv/auth/nonce.rb
CHANGED
|
@@ -34,12 +34,12 @@ module BSV
|
|
|
34
34
|
first_half = SecureRandom.random_bytes(RANDOM_BYTES)
|
|
35
35
|
key_id = decode_as_utf8(first_half)
|
|
36
36
|
|
|
37
|
-
result = wallet.create_hmac(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
result = wallet.create_hmac(
|
|
38
|
+
data: first_half.bytes,
|
|
39
|
+
protocol_id: PROTOCOL_ID,
|
|
40
|
+
key_id: key_id,
|
|
41
|
+
counterparty: counterparty
|
|
42
|
+
)
|
|
43
43
|
|
|
44
44
|
nonce_bytes = first_half + result[:hmac].pack('C*')
|
|
45
45
|
::Base64.strict_encode64(nonce_bytes)
|
|
@@ -60,13 +60,13 @@ module BSV
|
|
|
60
60
|
hmac_bytes = nonce_bytes.byteslice(RANDOM_BYTES, nonce_bytes.bytesize - RANDOM_BYTES)
|
|
61
61
|
key_id = decode_as_utf8(first_half)
|
|
62
62
|
|
|
63
|
-
wallet.verify_hmac(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
wallet.verify_hmac(
|
|
64
|
+
data: first_half.bytes,
|
|
65
|
+
hmac: hmac_bytes.bytes,
|
|
66
|
+
protocol_id: PROTOCOL_ID,
|
|
67
|
+
key_id: key_id,
|
|
68
|
+
counterparty: counterparty
|
|
69
|
+
)
|
|
70
70
|
|
|
71
71
|
true
|
|
72
72
|
rescue BSV::Wallet::InvalidHmacError
|
data/lib/bsv/auth/peer.rb
CHANGED
|
@@ -180,7 +180,7 @@ module BSV
|
|
|
180
180
|
#
|
|
181
181
|
# @return [String] compressed public key hex
|
|
182
182
|
def identity_key
|
|
183
|
-
@identity_key ||= @wallet.get_public_key(
|
|
183
|
+
@identity_key ||= @wallet.get_public_key(identity_key: true)[:public_key]
|
|
184
184
|
end
|
|
185
185
|
|
|
186
186
|
# Checks whether we have an authenticated session with a peer.
|
|
@@ -318,12 +318,12 @@ module BSV
|
|
|
318
318
|
key_id = key_id_for(request_nonce, session.peer_nonce)
|
|
319
319
|
data = JSON.generate(certificates_to_request).encode('UTF-8').bytes
|
|
320
320
|
|
|
321
|
-
sig_result = @wallet.create_signature(
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
321
|
+
sig_result = @wallet.create_signature(
|
|
322
|
+
data: data,
|
|
323
|
+
protocol_id: AUTH_PROTOCOL,
|
|
324
|
+
key_id: key_id,
|
|
325
|
+
counterparty: session.peer_identity_key
|
|
326
|
+
)
|
|
327
327
|
|
|
328
328
|
session.last_update = current_time_ms
|
|
329
329
|
@session_manager.update_session(session)
|
|
@@ -355,12 +355,12 @@ module BSV
|
|
|
355
355
|
cert_data = certificates.map { |c| c.respond_to?(:to_h) ? c.to_h : c }
|
|
356
356
|
data = JSON.generate(cert_data).encode('UTF-8').bytes
|
|
357
357
|
|
|
358
|
-
sig_result = @wallet.create_signature(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
358
|
+
sig_result = @wallet.create_signature(
|
|
359
|
+
data: data,
|
|
360
|
+
protocol_id: AUTH_PROTOCOL,
|
|
361
|
+
key_id: key_id,
|
|
362
|
+
counterparty: session.peer_identity_key
|
|
363
|
+
)
|
|
364
364
|
|
|
365
365
|
session.last_update = current_time_ms
|
|
366
366
|
@session_manager.update_session(session)
|
|
@@ -394,12 +394,12 @@ module BSV
|
|
|
394
394
|
request_nonce = ::Base64.strict_encode64(SecureRandom.random_bytes(32))
|
|
395
395
|
key_id = key_id_for(request_nonce, session.peer_nonce)
|
|
396
396
|
|
|
397
|
-
sig_result = @wallet.create_signature(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
397
|
+
sig_result = @wallet.create_signature(
|
|
398
|
+
data: payload,
|
|
399
|
+
protocol_id: AUTH_PROTOCOL,
|
|
400
|
+
key_id: key_id,
|
|
401
|
+
counterparty: session.peer_identity_key
|
|
402
|
+
)
|
|
403
403
|
|
|
404
404
|
session.last_update = current_time_ms
|
|
405
405
|
@session_manager.update_session(session)
|
|
@@ -455,12 +455,12 @@ module BSV
|
|
|
455
455
|
sig_data = b64_decode(their_nonce) + b64_decode(our_nonce)
|
|
456
456
|
key_id = key_id_for(their_nonce, our_nonce)
|
|
457
457
|
|
|
458
|
-
sig_result = @wallet.create_signature(
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
458
|
+
sig_result = @wallet.create_signature(
|
|
459
|
+
data: sig_data,
|
|
460
|
+
protocol_id: AUTH_PROTOCOL,
|
|
461
|
+
key_id: key_id,
|
|
462
|
+
counterparty: peer_key
|
|
463
|
+
)
|
|
464
464
|
|
|
465
465
|
@last_interacted_peer ||= peer_key if @auto_persist_last_session
|
|
466
466
|
|
|
@@ -498,13 +498,13 @@ module BSV
|
|
|
498
498
|
sig_data = b64_decode(our_nonce) + b64_decode(their_nonce)
|
|
499
499
|
key_id = key_id_for(our_nonce, their_nonce)
|
|
500
500
|
|
|
501
|
-
@wallet.verify_signature(
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
501
|
+
@wallet.verify_signature(
|
|
502
|
+
data: sig_data,
|
|
503
|
+
signature: signature,
|
|
504
|
+
protocol_id: AUTH_PROTOCOL,
|
|
505
|
+
key_id: key_id,
|
|
506
|
+
counterparty: peer_key
|
|
507
|
+
)
|
|
508
508
|
|
|
509
509
|
# Authentication complete
|
|
510
510
|
session.peer_nonce = their_nonce
|
|
@@ -574,13 +574,13 @@ module BSV
|
|
|
574
574
|
# Verify signature: signed over payload with key_id "msg_nonce session_nonce"
|
|
575
575
|
key_id = key_id_for(msg_nonce, session.session_nonce)
|
|
576
576
|
|
|
577
|
-
@wallet.verify_signature(
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
577
|
+
@wallet.verify_signature(
|
|
578
|
+
data: payload,
|
|
579
|
+
signature: signature,
|
|
580
|
+
protocol_id: AUTH_PROTOCOL,
|
|
581
|
+
key_id: key_id,
|
|
582
|
+
counterparty: session.peer_identity_key
|
|
583
|
+
)
|
|
584
584
|
|
|
585
585
|
session.last_update = current_time_ms
|
|
586
586
|
@session_manager.update_session(session)
|
|
@@ -610,13 +610,13 @@ module BSV
|
|
|
610
610
|
data = JSON.generate(requested).encode('UTF-8').bytes
|
|
611
611
|
key_id = key_id_for(msg_nonce, session.session_nonce)
|
|
612
612
|
|
|
613
|
-
@wallet.verify_signature(
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
613
|
+
@wallet.verify_signature(
|
|
614
|
+
data: data,
|
|
615
|
+
signature: signature,
|
|
616
|
+
protocol_id: AUTH_PROTOCOL,
|
|
617
|
+
key_id: key_id,
|
|
618
|
+
counterparty: session.peer_identity_key
|
|
619
|
+
)
|
|
620
620
|
|
|
621
621
|
session.last_update = current_time_ms
|
|
622
622
|
@session_manager.update_session(session)
|
|
@@ -655,13 +655,13 @@ module BSV
|
|
|
655
655
|
data = JSON.generate(certs).encode('UTF-8').bytes
|
|
656
656
|
key_id = key_id_for(msg_nonce, session.session_nonce)
|
|
657
657
|
|
|
658
|
-
@wallet.verify_signature(
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
658
|
+
@wallet.verify_signature(
|
|
659
|
+
data: data,
|
|
660
|
+
signature: signature,
|
|
661
|
+
protocol_id: AUTH_PROTOCOL,
|
|
662
|
+
key_id: key_id,
|
|
663
|
+
counterparty: session.peer_identity_key
|
|
664
|
+
)
|
|
665
665
|
|
|
666
666
|
if certs.is_a?(Array) && !certs.empty?
|
|
667
667
|
validation_msg = { identity_key: session.peer_identity_key, certificates: certs }
|
|
@@ -130,7 +130,7 @@ module BSV
|
|
|
130
130
|
privileged_reason: privileged_reason
|
|
131
131
|
}.merge(Certificate.certificate_field_encryption_details(field_name, @serial_number))
|
|
132
132
|
|
|
133
|
-
field_revelation_key = verifier_wallet.decrypt(dec_args)[:plaintext]
|
|
133
|
+
field_revelation_key = verifier_wallet.decrypt(**dec_args)[:plaintext]
|
|
134
134
|
|
|
135
135
|
sym_key = BSV::Primitives::SymmetricKey.new(field_revelation_key.pack('C*'))
|
|
136
136
|
decrypted_bytes = sym_key.decrypt(Base64.strict_decode64(@fields[field_name]))
|
data/lib/bsv/identity/client.rb
CHANGED
|
@@ -71,7 +71,7 @@ module BSV
|
|
|
71
71
|
args[:limit] = limit unless limit.nil?
|
|
72
72
|
args[:offset] = offset unless offset.nil?
|
|
73
73
|
|
|
74
|
-
result = @wallet.discover_by_identity_key(args, originator: @originator)
|
|
74
|
+
result = @wallet.discover_by_identity_key(**args, originator: @originator)
|
|
75
75
|
parse_certificates(result)
|
|
76
76
|
end
|
|
77
77
|
|
|
@@ -89,7 +89,7 @@ module BSV
|
|
|
89
89
|
args[:limit] = limit unless limit.nil?
|
|
90
90
|
args[:offset] = offset unless offset.nil?
|
|
91
91
|
|
|
92
|
-
result = @wallet.discover_by_attributes(args, originator: @originator)
|
|
92
|
+
result = @wallet.discover_by_attributes(**args, originator: @originator)
|
|
93
93
|
parse_certificates(result)
|
|
94
94
|
end
|
|
95
95
|
|
|
@@ -117,7 +117,7 @@ module BSV
|
|
|
117
117
|
# Prove the certificate to the "anyone" verifier (PrivateKey(1) public key)
|
|
118
118
|
anyone_pubkey = BSV::Script::PushDropTemplate::GENERATOR_PUBKEY_HEX
|
|
119
119
|
prove_result = @wallet.prove_certificate(
|
|
120
|
-
|
|
120
|
+
certificate: certificate, fields_to_reveal: fields_to_reveal, verifier: anyone_pubkey,
|
|
121
121
|
originator: @originator
|
|
122
122
|
)
|
|
123
123
|
keyring = prove_result[:keyring_for_verifier]
|
|
@@ -155,17 +155,15 @@ module BSV
|
|
|
155
155
|
|
|
156
156
|
# Create the transaction
|
|
157
157
|
create_result = @wallet.create_action(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
options: { randomize_outputs: false }
|
|
168
|
-
},
|
|
158
|
+
description: 'Create a new Identity Token',
|
|
159
|
+
outputs: [
|
|
160
|
+
{
|
|
161
|
+
satoshis: @options.token_amount,
|
|
162
|
+
locking_script: locking_script.to_hex,
|
|
163
|
+
output_description: 'Identity Token'
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
options: { randomize_outputs: false },
|
|
169
167
|
originator: @originator
|
|
170
168
|
)
|
|
171
169
|
|
|
@@ -209,24 +207,23 @@ module BSV
|
|
|
209
207
|
raise 'Revoke failed: no transaction found in BEEF' unless tx
|
|
210
208
|
raise 'Revoke failed: outputIndex out of range' if output_idx >= tx.outputs.length
|
|
211
209
|
|
|
210
|
+
# Overlay API boundary: outpoint uses display-order hex txid as per overlay convention
|
|
212
211
|
txid = tx.txid_hex
|
|
213
212
|
outpoint = "#{txid}.#{output_idx}"
|
|
214
213
|
|
|
215
214
|
# Create a spending transaction; use unlocking_script_length so the wallet
|
|
216
215
|
# produces a signable transaction that can then be signed and broadcast.
|
|
217
216
|
create_result = @wallet.create_action(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
options: { randomize_outputs: false, no_send: true }
|
|
229
|
-
},
|
|
217
|
+
description: 'Spend certificate revelation token',
|
|
218
|
+
input_beef: beef_bytes,
|
|
219
|
+
inputs: [
|
|
220
|
+
{
|
|
221
|
+
input_description: 'Revelation token',
|
|
222
|
+
outpoint: outpoint,
|
|
223
|
+
unlocking_script_length: BSV::Script::PushDropTemplate::Unlocker::ESTIMATED_LENGTH
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
options: { randomize_outputs: false, no_send: true },
|
|
230
227
|
originator: @originator
|
|
231
228
|
)
|
|
232
229
|
|
|
@@ -248,11 +245,9 @@ module BSV
|
|
|
248
245
|
unlocking_script = unlocker.sign(partial_tx, spending_input_idx)
|
|
249
246
|
|
|
250
247
|
sign_result = @wallet.sign_action(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
options: { no_send: true }
|
|
255
|
-
},
|
|
248
|
+
reference: signable[:reference],
|
|
249
|
+
spends: { spending_input_idx => { unlocking_script: unlocking_script.to_hex } },
|
|
250
|
+
options: { no_send: true },
|
|
256
251
|
originator: @originator
|
|
257
252
|
)
|
|
258
253
|
|
|
@@ -343,7 +338,7 @@ module BSV
|
|
|
343
338
|
#
|
|
344
339
|
# @return [Symbol] :mainnet or :testnet
|
|
345
340
|
def wallet_network
|
|
346
|
-
result = @wallet.get_network(
|
|
341
|
+
result = @wallet.get_network(originator: @originator)
|
|
347
342
|
net_str = result[:network] || result['network'] || 'mainnet'
|
|
348
343
|
net_str.to_sym
|
|
349
344
|
end
|
|
@@ -91,8 +91,16 @@ module BSV
|
|
|
91
91
|
private_key = BSV::Primitives::PrivateKey.from_wif(wif)
|
|
92
92
|
sender_address = private_key.public_key.address(network: net_sym)
|
|
93
93
|
|
|
94
|
-
woc = BSV::Network::WhatsOnChain.
|
|
95
|
-
|
|
94
|
+
woc = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
|
|
95
|
+
utxo_result = woc.call(:get_utxos_all, sender_address)
|
|
96
|
+
return Helpers.error_response("UTXO fetch failed: #{utxo_result.message}") unless utxo_result.success?
|
|
97
|
+
|
|
98
|
+
all_utxos = utxo_result.data.map do |entry|
|
|
99
|
+
BSV::Network::UTXO.new(
|
|
100
|
+
tx_hash: entry[:tx_hash], tx_pos: entry[:tx_pos],
|
|
101
|
+
satoshis: entry[:satoshis], height: entry[:height]
|
|
102
|
+
)
|
|
103
|
+
end
|
|
96
104
|
|
|
97
105
|
return Helpers.error_response('No UTXOs found for sender address — the address may have no funds') if all_utxos.empty?
|
|
98
106
|
|
|
@@ -104,11 +112,12 @@ module BSV
|
|
|
104
112
|
tx = build_transaction(selected, satoshis, to_address, sender_address, private_key)
|
|
105
113
|
|
|
106
114
|
arc = build_arc(net_sym, server_context)
|
|
107
|
-
|
|
115
|
+
arc_result = arc.call(:broadcast, tx)
|
|
116
|
+
return Helpers.error_response("Broadcast failed: #{arc_result.message}") unless arc_result.success?
|
|
108
117
|
|
|
109
118
|
result = {
|
|
110
|
-
txid:
|
|
111
|
-
tx_status:
|
|
119
|
+
txid: arc_result.data[:txid], # MCP tool boundary: display-order hex from ARC response
|
|
120
|
+
tx_status: arc_result.data[:tx_status],
|
|
112
121
|
hex: tx.to_hex
|
|
113
122
|
}
|
|
114
123
|
|
|
@@ -118,10 +127,6 @@ module BSV
|
|
|
118
127
|
)
|
|
119
128
|
rescue ArgumentError => e
|
|
120
129
|
Helpers.error_response(e.message)
|
|
121
|
-
rescue BSV::Network::ChainProviderError => e
|
|
122
|
-
Helpers.error_response("UTXO fetch failed: #{e.message}")
|
|
123
|
-
rescue BSV::Network::BroadcastError => e
|
|
124
|
-
Helpers.error_response("Broadcast failed: #{e.message}")
|
|
125
130
|
end
|
|
126
131
|
|
|
127
132
|
# Select UTXOs greedily until the total meets or exceeds the target.
|
|
@@ -149,7 +154,7 @@ module BSV
|
|
|
149
154
|
selected_utxos.each do |utxo|
|
|
150
155
|
locking_script = p2pkh_lock_for(sender_address)
|
|
151
156
|
input = BSV::Transaction::TransactionInput.new(
|
|
152
|
-
|
|
157
|
+
prev_wtxid: BSV::Transaction::TransactionInput.wtxid_from_hex(utxo.tx_hash),
|
|
153
158
|
prev_tx_out_index: utxo.tx_pos
|
|
154
159
|
)
|
|
155
160
|
input.source_satoshis = utxo.satoshis
|
|
@@ -196,13 +201,14 @@ module BSV
|
|
|
196
201
|
end
|
|
197
202
|
private_class_method :p2pkh_lock_for
|
|
198
203
|
|
|
199
|
-
# Build an ARC
|
|
204
|
+
# Build an ARC protocol instance using server config when available.
|
|
200
205
|
# @api private
|
|
201
206
|
def self.build_arc(net_sym, server_context)
|
|
202
207
|
testnet = net_sym == :testnet
|
|
203
208
|
opts = {}
|
|
204
209
|
opts[:api_key] = server_context[:arc_api_key] if server_context.is_a?(Hash) && server_context[:arc_api_key]
|
|
205
|
-
BSV::Network::
|
|
210
|
+
provider = BSV::Network::Providers::GorillaPool.default(testnet: testnet, **opts)
|
|
211
|
+
provider.protocol_for(:broadcast)
|
|
206
212
|
end
|
|
207
213
|
private_class_method :build_arc
|
|
208
214
|
end
|
|
@@ -59,8 +59,22 @@ module BSV
|
|
|
59
59
|
net_sym = Helpers.resolve_network_sym(network, server_context)
|
|
60
60
|
address = resolve_address(address_or_wif, net_sym)
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
provider = BSV::Network::Providers::WhatsOnChain.default(network: net_sym)
|
|
63
|
+
utxo_result = provider.call(:get_utxos_all, address)
|
|
64
|
+
|
|
65
|
+
unless utxo_result.success?
|
|
66
|
+
code = utxo_result.metadata[:status_code]
|
|
67
|
+
msg = utxo_result.message
|
|
68
|
+
msg = "#{msg} (HTTP #{code})" if code
|
|
69
|
+
return Helpers.error_response(msg)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
utxos = utxo_result.data.map do |entry|
|
|
73
|
+
BSV::Network::UTXO.new(
|
|
74
|
+
tx_hash: entry[:tx_hash], tx_pos: entry[:tx_pos],
|
|
75
|
+
satoshis: entry[:satoshis], height: entry[:height]
|
|
76
|
+
)
|
|
77
|
+
end
|
|
64
78
|
|
|
65
79
|
balance = utxos.sum(&:satoshis)
|
|
66
80
|
result = {
|
|
@@ -77,8 +91,6 @@ module BSV
|
|
|
77
91
|
)
|
|
78
92
|
rescue ArgumentError => e
|
|
79
93
|
Helpers.error_response(e.message)
|
|
80
|
-
rescue BSV::Network::ChainProviderError => e
|
|
81
|
-
Helpers.error_response("#{e.message} (HTTP #{e.status_code})")
|
|
82
94
|
end
|
|
83
95
|
|
|
84
96
|
# Resolve a WIF key or address string to a P2PKH address.
|