bsv-sdk 0.19.1 → 0.22.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 +89 -0
- data/README.md +2 -2
- data/lib/bsv/auth/transport.rb +1 -1
- data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +5 -3
- data/lib/bsv/network/protocol.rb +4 -5
- data/lib/bsv/network/protocols/arc.rb +4 -30
- data/lib/bsv/network/protocols/arcade.rb +163 -0
- data/lib/bsv/network/protocols/chaintracks.rb +6 -3
- data/lib/bsv/network/protocols/jungle_bus.rb +6 -0
- data/lib/bsv/network/protocols.rb +1 -0
- data/lib/bsv/network/provider.rb +7 -9
- data/lib/bsv/network/providers/gorilla_pool.rb +18 -18
- data/lib/bsv/network/util.rb +44 -0
- data/lib/bsv/network.rb +1 -0
- data/lib/bsv/overlay/lookup_resolver.rb +0 -1
- data/lib/bsv/overlay/topic_broadcaster.rb +0 -1
- data/lib/bsv/primitives/curve.rb +1 -11
- data/lib/bsv/primitives/ecies.rb +1 -8
- data/lib/bsv/primitives/hex.rb +1 -1
- data/lib/bsv/script/script.rb +1 -1
- data/lib/bsv/transaction/beef.rb +0 -2
- data/lib/bsv/transaction/chain_tracker.rb +74 -13
- data/lib/bsv/transaction/chain_trackers/whats_on_chain.rb +3 -3
- data/lib/bsv/transaction/chain_trackers.rb +0 -10
- data/lib/bsv/transaction/fee_models/live_policy.rb +10 -8
- data/lib/bsv/transaction/merkle_path.rb +0 -2
- data/lib/bsv/version.rb +1 -1
- data/lib/bsv/wallet/errors.rb +65 -21
- data/lib/bsv/wallet/proto_wallet/validators.rb +7 -49
- data/lib/bsv/wallet/proto_wallet.rb +15 -8
- data/lib/bsv/wallet/serializer/abort_action.rb +38 -0
- data/lib/bsv/wallet/serializer/acquire_certificate.rb +171 -0
- data/lib/bsv/wallet/serializer/certificate.rb +184 -0
- data/lib/bsv/wallet/serializer/common.rb +207 -0
- data/lib/bsv/wallet/serializer/create_action_args.rb +259 -0
- data/lib/bsv/wallet/serializer/create_action_result.rb +85 -0
- data/lib/bsv/wallet/serializer/create_hmac.rb +67 -0
- data/lib/bsv/wallet/serializer/create_signature.rb +90 -0
- data/lib/bsv/wallet/serializer/decrypt.rb +60 -0
- data/lib/bsv/wallet/serializer/discover_by_attributes.rb +61 -0
- data/lib/bsv/wallet/serializer/discover_by_identity_key.rb +49 -0
- data/lib/bsv/wallet/serializer/discover_certificates_result.rb +39 -0
- data/lib/bsv/wallet/serializer/encrypt.rb +60 -0
- data/lib/bsv/wallet/serializer/get_header_for_height.rb +71 -0
- data/lib/bsv/wallet/serializer/get_height.rb +46 -0
- data/lib/bsv/wallet/serializer/get_network.rb +65 -0
- data/lib/bsv/wallet/serializer/get_public_key.rb +86 -0
- data/lib/bsv/wallet/serializer/get_version.rb +44 -0
- data/lib/bsv/wallet/serializer/internalize_action.rb +151 -0
- data/lib/bsv/wallet/serializer/list_actions.rb +348 -0
- data/lib/bsv/wallet/serializer/list_certificates.rb +124 -0
- data/lib/bsv/wallet/serializer/list_outputs.rb +167 -0
- data/lib/bsv/wallet/serializer/prove_certificate.rb +146 -0
- data/lib/bsv/wallet/serializer/relinquish_certificate.rb +56 -0
- data/lib/bsv/wallet/serializer/relinquish_output.rb +44 -0
- data/lib/bsv/wallet/serializer/reveal_counterparty_key_linkage.rb +108 -0
- data/lib/bsv/wallet/serializer/reveal_specific_key_linkage.rb +116 -0
- data/lib/bsv/wallet/serializer/sign_action_args.rb +94 -0
- data/lib/bsv/wallet/serializer/sign_action_result.rb +49 -0
- data/lib/bsv/wallet/serializer/status.rb +85 -0
- data/lib/bsv/wallet/serializer/verify_hmac.rb +67 -0
- data/lib/bsv/wallet/serializer/verify_signature.rb +101 -0
- data/lib/bsv/wallet/serializer.rb +180 -0
- data/lib/bsv/wallet/substrates/http_wallet_json.rb +129 -0
- data/lib/bsv/wallet/substrates/http_wallet_wire.rb +99 -0
- data/lib/bsv/wallet/wallet_wire.rb +20 -0
- data/lib/bsv/wallet/wallet_wire_processor.rb +61 -0
- data/lib/bsv/wallet/wallet_wire_transceiver.rb +61 -0
- data/lib/bsv/wallet/wire/calls.rb +79 -0
- data/lib/bsv/wallet/wire/frame.rb +181 -0
- data/lib/bsv/wallet/wire/reader_writer.rb +402 -0
- data/lib/bsv/wallet/wire/validation.rb +213 -0
- data/lib/bsv/wallet/wire.rb +13 -0
- data/lib/bsv/wallet.rb +17 -0
- metadata +47 -3
- data/lib/bsv/transaction/chain_trackers/chaintracks.rb +0 -83
data/lib/bsv/primitives/curve.rb
CHANGED
|
@@ -98,21 +98,11 @@ module BSV
|
|
|
98
98
|
|
|
99
99
|
# Add two curve points together.
|
|
100
100
|
#
|
|
101
|
-
# Uses +Point#add+ where available (Ruby 3.0+ / OpenSSL 3), falling
|
|
102
|
-
# back to multi-scalar multiplication for Ruby 2.7 compatibility.
|
|
103
|
-
#
|
|
104
101
|
# @param point_a [OpenSSL::PKey::EC::Point] first point
|
|
105
102
|
# @param point_b [OpenSSL::PKey::EC::Point] second point
|
|
106
103
|
# @return [OpenSSL::PKey::EC::Point] the sum of the two points
|
|
107
104
|
def add_points(point_a, point_b)
|
|
108
|
-
|
|
109
|
-
point_a.add(point_b)
|
|
110
|
-
else
|
|
111
|
-
# Ruby 2.7 / OpenSSL < 3: use multi-scalar mul
|
|
112
|
-
# point_a.mul(bns, points) = bns[0]*point_a + bns[1]*points[0] + ...
|
|
113
|
-
one = OpenSSL::BN.new('1')
|
|
114
|
-
point_a.mul([one, one], [point_b])
|
|
115
|
-
end
|
|
105
|
+
point_a.add(point_b)
|
|
116
106
|
end
|
|
117
107
|
|
|
118
108
|
# Extract the x-coordinate from a curve point as a big number.
|
data/lib/bsv/primitives/ecies.rb
CHANGED
|
@@ -210,14 +210,7 @@ module BSV
|
|
|
210
210
|
def secure_compare(mac, expected)
|
|
211
211
|
return false unless mac.bytesize == expected.bytesize
|
|
212
212
|
|
|
213
|
-
|
|
214
|
-
OpenSSL.fixed_length_secure_compare(mac, expected)
|
|
215
|
-
else
|
|
216
|
-
# Constant-time comparison for Ruby < 3.2
|
|
217
|
-
result = 0
|
|
218
|
-
mac.bytes.zip(expected.bytes) { |x, y| result |= x ^ y }
|
|
219
|
-
result.zero?
|
|
220
|
-
end
|
|
213
|
+
OpenSSL.fixed_length_secure_compare(mac, expected)
|
|
221
214
|
end
|
|
222
215
|
|
|
223
216
|
def derive_keys(private_key, public_key)
|
data/lib/bsv/primitives/hex.rb
CHANGED
|
@@ -22,7 +22,7 @@ module BSV
|
|
|
22
22
|
module Hex
|
|
23
23
|
# Matches an even number of hex characters (case-insensitive).
|
|
24
24
|
# Empty string is valid (decodes to empty bytes).
|
|
25
|
-
HEX_RE = /\A(?:[0-9a-fA-F]{2})*\z/n
|
|
25
|
+
HEX_RE = /\A(?:[0-9a-fA-F]{2})*\z/n
|
|
26
26
|
private_constant :HEX_RE
|
|
27
27
|
|
|
28
28
|
# Test whether +str+ is valid hex (even-length, hex-only).
|
data/lib/bsv/script/script.rb
CHANGED
|
@@ -334,7 +334,7 @@ module BSV
|
|
|
334
334
|
}.freeze
|
|
335
335
|
|
|
336
336
|
# Reverse lookup: opcode → hash type symbol (excludes :raw).
|
|
337
|
-
RPUZZLE_OP_TO_TYPE = RPUZZLE_HASH_OPS.
|
|
337
|
+
RPUZZLE_OP_TO_TYPE = RPUZZLE_HASH_OPS.except(:raw).invert.freeze
|
|
338
338
|
|
|
339
339
|
# The fixed opcode prefix shared by all RPuzzle locking scripts.
|
|
340
340
|
# OP_OVER OP_3 OP_SPLIT OP_NIP OP_1 OP_SPLIT OP_SWAP OP_SPLIT OP_DROP
|
data/lib/bsv/transaction/beef.rb
CHANGED
|
@@ -10,18 +10,21 @@ module BSV
|
|
|
10
10
|
# chain tracker is the data source: an object the consumer provides
|
|
11
11
|
# that can answer "is this merkle root valid for this block height?"
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
13
|
+
# This class is a working default implementation that wraps a
|
|
14
|
+
# {BSV::Network::Provider} and dispatches via {Provider#call}. The
|
|
15
|
+
# provider must serve the +:get_block_header+ and +:current_height+
|
|
16
|
+
# commands (e.g. a provider configured with {Protocols::JungleBus}).
|
|
17
|
+
#
|
|
18
|
+
# Subclasses may override either or both methods to supply their own
|
|
19
|
+
# data source (in-memory hash, database cache, etc.) without touching
|
|
20
|
+
# the provider at all. The +provider+ argument is optional precisely to
|
|
21
|
+
# preserve this pattern — a subclass that overrides both methods never
|
|
22
|
+
# reaches the provider-dispatch path.
|
|
20
23
|
#
|
|
21
24
|
# Any object responding to +valid_root_for_height?+ and
|
|
22
25
|
# +current_height+ satisfies this interface. Inheriting from this
|
|
23
26
|
# class is optional — it exists to document the contract and provide
|
|
24
|
-
#
|
|
27
|
+
# a ready-to-use provider-backed implementation.
|
|
25
28
|
#
|
|
26
29
|
# @example In-memory chain tracker (test / declarative)
|
|
27
30
|
# class HashTracker < BSV::Transaction::ChainTracker
|
|
@@ -38,23 +41,81 @@ module BSV
|
|
|
38
41
|
# header.merkle_root == root
|
|
39
42
|
# end
|
|
40
43
|
# end
|
|
44
|
+
#
|
|
45
|
+
# @example Provider-backed (default impl)
|
|
46
|
+
# tracker = BSV::Transaction::ChainTracker.default
|
|
47
|
+
# tracker.valid_root_for_height?('abcd...', 800_000)
|
|
48
|
+
#
|
|
49
|
+
# @example Testnet
|
|
50
|
+
# tracker = BSV::Transaction::ChainTracker.default(testnet: true)
|
|
51
|
+
# tracker.current_height
|
|
41
52
|
class ChainTracker
|
|
53
|
+
# @return [BSV::Network::Provider, nil] the underlying provider, if any
|
|
54
|
+
attr_reader :provider
|
|
55
|
+
|
|
56
|
+
# Return a default ChainTracker backed by the GorillaPool provider.
|
|
57
|
+
#
|
|
58
|
+
# @param testnet [Boolean] when true, uses the testnet provider
|
|
59
|
+
# @return [ChainTracker]
|
|
60
|
+
def self.default(testnet: false)
|
|
61
|
+
new(BSV::Network::Providers::GorillaPool.default(testnet: testnet))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @param provider [BSV::Network::Provider, nil] provider serving +:get_block_header+
|
|
65
|
+
# and +:current_height+. Optional when the subclass overrides both methods.
|
|
66
|
+
def initialize(provider = nil)
|
|
67
|
+
@provider = provider
|
|
68
|
+
end
|
|
69
|
+
|
|
42
70
|
# Verify that a merkle root is valid for the given block height.
|
|
43
71
|
#
|
|
72
|
+
# Dispatches +:get_block_header+ to the configured provider. Returns +false+
|
|
73
|
+
# on 404 (block not found). Normalises the merkle root field name from any
|
|
74
|
+
# of +merkleroot+, +merkleRoot+, or +merkle_root+.
|
|
75
|
+
#
|
|
44
76
|
# @param root [String] merkle root as a hex string
|
|
45
77
|
# @param height [Integer] block height
|
|
46
78
|
# @return [Boolean] true if the root matches the block at the given height
|
|
47
|
-
# @raise [
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
# @raise [RuntimeError] when no provider is configured
|
|
80
|
+
# @raise [RuntimeError] on network or API error
|
|
81
|
+
def valid_root_for_height?(root, height)
|
|
82
|
+
raise 'ChainTracker requires a provider when used directly' if @provider.nil?
|
|
83
|
+
|
|
84
|
+
result = @provider.call(:get_block_header, height)
|
|
85
|
+
return false if result.http_not_found?
|
|
86
|
+
raise result.error_message.to_s unless result.http_success?
|
|
87
|
+
|
|
88
|
+
actual = normalise_merkle_root(result.data)
|
|
89
|
+
return false unless actual
|
|
90
|
+
|
|
91
|
+
actual.casecmp(root).zero?
|
|
50
92
|
end
|
|
51
93
|
|
|
52
94
|
# Return the current blockchain height.
|
|
53
95
|
#
|
|
96
|
+
# Dispatches +:current_height+ to the configured provider.
|
|
97
|
+
#
|
|
54
98
|
# @return [Integer] the height of the chain tip
|
|
55
|
-
# @raise [
|
|
99
|
+
# @raise [RuntimeError] when no provider is configured
|
|
100
|
+
# @raise [RuntimeError] on network or API error
|
|
56
101
|
def current_height
|
|
57
|
-
raise
|
|
102
|
+
raise 'ChainTracker requires a provider when used directly' if @provider.nil?
|
|
103
|
+
|
|
104
|
+
result = @provider.call(:current_height)
|
|
105
|
+
return result.data if result.http_success?
|
|
106
|
+
|
|
107
|
+
raise result.error_message.to_s
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
# Field-name diversity belongs at the Protocols::* layer; this is a
|
|
113
|
+
# transitional shim until the wire protocols return canonical shapes.
|
|
114
|
+
# See #791.
|
|
115
|
+
def normalise_merkle_root(body)
|
|
116
|
+
return nil unless body.is_a?(Hash)
|
|
117
|
+
|
|
118
|
+
body['merkleroot'] || body['merkleRoot'] || body['merkle_root']
|
|
58
119
|
end
|
|
59
120
|
end
|
|
60
121
|
end
|
|
@@ -20,10 +20,10 @@ module BSV
|
|
|
20
20
|
# Returns a WhatsOnChain chain tracker using the provider default.
|
|
21
21
|
#
|
|
22
22
|
# @param testnet [Boolean] when true, uses the testnet endpoint
|
|
23
|
-
# @param
|
|
23
|
+
# @param ** [Hash] forwarded to the underlying protocol (e.g. +api_key:+, +http_client:+)
|
|
24
24
|
# @return [WhatsOnChain]
|
|
25
|
-
def self.default(testnet: false, **
|
|
26
|
-
provider = BSV::Network::Providers::WhatsOnChain.default(testnet: testnet, **
|
|
25
|
+
def self.default(testnet: false, **)
|
|
26
|
+
provider = BSV::Network::Providers::WhatsOnChain.default(testnet: testnet, **)
|
|
27
27
|
new(protocol: provider.protocol_for(:valid_root))
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -5,16 +5,6 @@ module BSV
|
|
|
5
5
|
# Namespace for chain tracker implementations.
|
|
6
6
|
module ChainTrackers
|
|
7
7
|
autoload :WhatsOnChain, 'bsv/transaction/chain_trackers/whats_on_chain'
|
|
8
|
-
autoload :Chaintracks, 'bsv/transaction/chain_trackers/chaintracks'
|
|
9
|
-
|
|
10
|
-
# Return a default chain tracker backed by the Arcade/GorillaPool Chaintracks API.
|
|
11
|
-
#
|
|
12
|
-
# @param testnet [Boolean] use the testnet endpoint when true
|
|
13
|
-
# @param opts [Hash] forwarded to the underlying tracker (e.g. +api_key:+)
|
|
14
|
-
# @return [Chaintracks]
|
|
15
|
-
def self.default(testnet: false, **opts)
|
|
16
|
-
Chaintracks.default(testnet: testnet, **opts)
|
|
17
|
-
end
|
|
18
8
|
end
|
|
19
9
|
end
|
|
20
10
|
end
|
|
@@ -16,7 +16,7 @@ module BSV
|
|
|
16
16
|
#
|
|
17
17
|
# @example
|
|
18
18
|
# model = BSV::Transaction::FeeModels::LivePolicy.new(
|
|
19
|
-
# arc_url: 'https://
|
|
19
|
+
# arc_url: 'https://arc.taal.com',
|
|
20
20
|
# fallback_rate: 50
|
|
21
21
|
# )
|
|
22
22
|
# fee = model.compute_fee(transaction)
|
|
@@ -34,18 +34,20 @@ module BSV
|
|
|
34
34
|
|
|
35
35
|
DEFAULT_FALLBACK_RATE = 100
|
|
36
36
|
|
|
37
|
-
#
|
|
38
|
-
#
|
|
37
|
+
# TAAL still runs a public ARC instance that serves /v1/policy.
|
|
38
|
+
# Full policy access may require a TAAL API key.
|
|
39
|
+
TAAL_ARC_URL = 'https://arc.taal.com'
|
|
40
|
+
|
|
41
|
+
# Returns a LivePolicy using TAAL ARC for fee policy, 100 sat/kB fallback,
|
|
42
|
+
# and a 5-minute cache.
|
|
39
43
|
#
|
|
40
|
-
# @param api_key [String, nil] optional
|
|
44
|
+
# @param api_key [String, nil] optional TAAL API key for authenticated policy access
|
|
41
45
|
# @return [LivePolicy]
|
|
42
46
|
def self.default(api_key: nil)
|
|
43
|
-
|
|
44
|
-
arc_protocol = provider.protocol_for(:broadcast)
|
|
45
|
-
new(arc_url: arc_protocol.base_url, fallback_rate: DEFAULT_FALLBACK_RATE, api_key: api_key)
|
|
47
|
+
new(arc_url: TAAL_ARC_URL, fallback_rate: DEFAULT_FALLBACK_RATE, api_key: api_key)
|
|
46
48
|
end
|
|
47
49
|
|
|
48
|
-
# @param arc_url [String] ARC base URL (e.g. 'https://
|
|
50
|
+
# @param arc_url [String] ARC base URL (e.g. 'https://arc.taal.com')
|
|
49
51
|
# @param fallback_rate [Integer] sat/kB to use when fetch fails (default: 100)
|
|
50
52
|
# @param cache_ttl [Integer] seconds to cache a fetched rate (default: 300)
|
|
51
53
|
# @param api_key [String, nil] optional Bearer token for ARC authentication
|
data/lib/bsv/version.rb
CHANGED
data/lib/bsv/wallet/errors.rb
CHANGED
|
@@ -3,44 +3,88 @@
|
|
|
3
3
|
module BSV
|
|
4
4
|
module Wallet
|
|
5
5
|
# Base error for all wallet operations. Carries a machine-readable code
|
|
6
|
-
# per the BRC-100 error structure.
|
|
6
|
+
# per the BRC-100 error structure and a wire-protocol stack string.
|
|
7
7
|
class Error < StandardError
|
|
8
|
-
attr_reader :code
|
|
8
|
+
attr_reader :code, :wallet_stack
|
|
9
9
|
|
|
10
|
-
def initialize(message, code
|
|
10
|
+
def initialize(message = nil, code: 1, stack: '')
|
|
11
11
|
@code = code
|
|
12
|
-
|
|
12
|
+
@wallet_stack = stack
|
|
13
|
+
super(message || '')
|
|
13
14
|
end
|
|
14
|
-
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
# Serialise to a hash suitable for embedding in a wire result frame.
|
|
17
|
+
# Never leaks Ruby's internal backtrace — only the wallet_stack string.
|
|
18
|
+
def to_wire
|
|
19
|
+
{ code: code, message: message.to_s, stack: wallet_stack.to_s }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
# Code 2 — operation not supported by this wallet implementation.
|
|
24
|
+
class UnsupportedActionError < Error
|
|
25
|
+
def initialize(message = 'this method is not supported by this wallet implementation', stack: '')
|
|
26
|
+
super(message, code: 2, stack: stack)
|
|
23
27
|
end
|
|
24
28
|
end
|
|
25
29
|
|
|
26
|
-
#
|
|
30
|
+
# Code 3 — HMAC verification failed.
|
|
27
31
|
class InvalidHmacError < Error
|
|
28
|
-
def initialize(message = 'the provided HMAC is invalid')
|
|
29
|
-
super(message, 3)
|
|
32
|
+
def initialize(message = 'the provided HMAC is invalid', stack: '')
|
|
33
|
+
super(message, code: 3, stack: stack)
|
|
30
34
|
end
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
#
|
|
37
|
+
# Code 4 — signature verification failed.
|
|
34
38
|
class InvalidSignatureError < Error
|
|
35
|
-
def initialize(message = 'the provided signature is invalid')
|
|
36
|
-
super(message, 4)
|
|
39
|
+
def initialize(message = 'the provided signature is invalid', stack: '')
|
|
40
|
+
super(message, code: 4, stack: stack)
|
|
37
41
|
end
|
|
38
42
|
end
|
|
39
43
|
|
|
40
|
-
#
|
|
41
|
-
class
|
|
42
|
-
def initialize(
|
|
43
|
-
super(
|
|
44
|
+
# Code 5 — wallet has insufficient funds for the requested operation.
|
|
45
|
+
class InsufficientFundsError < Error
|
|
46
|
+
def initialize(message = 'insufficient funds', stack: '')
|
|
47
|
+
super(message, code: 5, stack: stack)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Code 6 — a required parameter is missing or invalid.
|
|
52
|
+
#
|
|
53
|
+
# Two calling conventions:
|
|
54
|
+
# InvalidParameterError.new('pubkey', 'a hex string') # raises "the pubkey parameter must be ..."
|
|
55
|
+
# InvalidParameterError.new('raw message') # wire-rehydration path
|
|
56
|
+
class InvalidParameterError < Error
|
|
57
|
+
attr_reader :parameter
|
|
58
|
+
|
|
59
|
+
def initialize(parameter, must_be = nil, stack: '')
|
|
60
|
+
@parameter = must_be ? parameter : nil
|
|
61
|
+
message = must_be ? "the #{parameter} parameter must be #{must_be}" : parameter
|
|
62
|
+
super(message, code: 6, stack: stack)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Code 7 — actions require review before they can be processed.
|
|
67
|
+
class ReviewActionsError < Error
|
|
68
|
+
def initialize(message = 'actions require review', stack: '')
|
|
69
|
+
super(message, code: 7, stack: stack)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Rehydrate a wire error frame into the appropriate subclass.
|
|
74
|
+
#
|
|
75
|
+
# @param code [Integer] error code byte from the result frame
|
|
76
|
+
# @param message [String] error message from the frame
|
|
77
|
+
# @param stack [String] stack trace from the frame (may be empty)
|
|
78
|
+
# @return [Error] an instance of the matching subclass
|
|
79
|
+
def self.error_from_wire(code, message, stack = '')
|
|
80
|
+
case code
|
|
81
|
+
when 2 then UnsupportedActionError.new(message, stack: stack)
|
|
82
|
+
when 3 then InvalidHmacError.new(message, stack: stack)
|
|
83
|
+
when 4 then InvalidSignatureError.new(message, stack: stack)
|
|
84
|
+
when 5 then InsufficientFundsError.new(message, stack: stack)
|
|
85
|
+
when 6 then InvalidParameterError.new(message, nil, stack: stack)
|
|
86
|
+
when 7 then ReviewActionsError.new(message, stack: stack)
|
|
87
|
+
else Error.new(message, code: code, stack: stack)
|
|
44
88
|
end
|
|
45
89
|
end
|
|
46
90
|
end
|
|
@@ -5,68 +5,26 @@ module BSV
|
|
|
5
5
|
class ProtoWallet
|
|
6
6
|
# Validation helpers for BRC-100 wallet method parameters.
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
8
|
+
# Delegates to Wire::Validation — the single source of truth for all
|
|
9
|
+
# BRC-100 parameter validation. This module is retained for backwards
|
|
10
|
+
# compatibility with ProtoWallet's internal call sites.
|
|
10
11
|
module Validators
|
|
11
12
|
module_function
|
|
12
13
|
|
|
13
|
-
# Validates a BRC-43 protocol ID.
|
|
14
|
-
#
|
|
15
|
-
# Must be an Array of [Integer(0-2), String(5-400 chars)]. The name is
|
|
16
|
-
# normalized (stripped and downcased) before length/content checks.
|
|
17
|
-
#
|
|
18
|
-
# @param protocol_id [Object] the value to validate
|
|
19
|
-
# @raise [InvalidParameterError]
|
|
20
14
|
def validate_protocol_id!(protocol_id)
|
|
21
|
-
|
|
22
|
-
raise InvalidParameterError.new('protocol_id', 'an Array of [security_level, protocol_name]')
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
level, name = protocol_id
|
|
26
|
-
raise InvalidParameterError.new('protocol_id security level', '0, 1, or 2') unless [0, 1, 2].include?(level)
|
|
27
|
-
raise InvalidParameterError.new('protocol_id name', 'a String') unless name.is_a?(String)
|
|
28
|
-
|
|
29
|
-
name = name.strip.downcase
|
|
30
|
-
max_length = name.start_with?('specific linkage revelation') ? 430 : 400
|
|
31
|
-
raise InvalidParameterError.new('protocol_id name', "between 5 and #{max_length} characters") if name.length < 5 || name.length > max_length
|
|
32
|
-
|
|
33
|
-
raise InvalidParameterError.new('protocol_id name', 'lowercase letters, numbers, and spaces only') unless name.match?(/\A[a-z0-9 ]+\z/)
|
|
34
|
-
|
|
35
|
-
raise InvalidParameterError.new('protocol_id name', 'free of consecutive spaces') if name.include?(' ')
|
|
15
|
+
Wire::Validation.wallet_protocol!('protocol_id', protocol_id)
|
|
36
16
|
end
|
|
37
17
|
|
|
38
|
-
# Validates a BRC-43 key ID.
|
|
39
|
-
#
|
|
40
|
-
# Must be a non-empty String of at most 800 bytes.
|
|
41
|
-
#
|
|
42
|
-
# @param key_id [Object] the value to validate
|
|
43
|
-
# @raise [InvalidParameterError]
|
|
44
18
|
def validate_key_id!(key_id)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
byte_length = key_id.bytesize
|
|
48
|
-
raise InvalidParameterError.new('key_id', 'between 1 and 800 bytes') if byte_length < 1 || byte_length > 800
|
|
19
|
+
Wire::Validation.key_id_string_1_to_800!('key_id', key_id)
|
|
49
20
|
end
|
|
50
21
|
|
|
51
|
-
# Validates a counterparty: 'self', 'anyone', or a 66-char hex pubkey.
|
|
52
|
-
#
|
|
53
|
-
# @param counterparty [Object] the value to validate
|
|
54
|
-
# @raise [InvalidParameterError]
|
|
55
22
|
def validate_counterparty!(counterparty)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
validate_pub_key_hex!(counterparty, 'counterparty')
|
|
23
|
+
Wire::Validation.wallet_counterparty!('counterparty', counterparty)
|
|
59
24
|
end
|
|
60
25
|
|
|
61
|
-
# Validates a compressed public key in hex form (66 chars, 02/03/04 prefix).
|
|
62
|
-
#
|
|
63
|
-
# @param value [Object] the value to validate
|
|
64
|
-
# @param name [String] parameter name for error messages
|
|
65
|
-
# @raise [InvalidParameterError]
|
|
66
26
|
def validate_pub_key_hex!(value, name = 'public_key')
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
raise InvalidParameterError.new(name, 'a 66-character hex string (compressed public key)') unless value.match?(/\A[0-9a-f]{66}\z/)
|
|
27
|
+
Wire::Validation.pub_key_hex!(name, value)
|
|
70
28
|
end
|
|
71
29
|
end
|
|
72
30
|
end
|
|
@@ -226,6 +226,8 @@ module BSV
|
|
|
226
226
|
def reveal_counterparty_key_linkage(counterparty:, verifier:,
|
|
227
227
|
privileged: false, privileged_reason: nil,
|
|
228
228
|
originator: nil)
|
|
229
|
+
counterparty = normalise_pubkey_hex(counterparty)
|
|
230
|
+
verifier = normalise_pubkey_hex(verifier)
|
|
229
231
|
raise InvalidParameterError.new('counterparty', 'a specific public key hex, not "anyone"') if counterparty == 'anyone'
|
|
230
232
|
|
|
231
233
|
Validators.validate_pub_key_hex!(verifier, 'verifier')
|
|
@@ -281,6 +283,8 @@ module BSV
|
|
|
281
283
|
def reveal_specific_key_linkage(counterparty:, verifier:, protocol_id:, key_id:,
|
|
282
284
|
privileged: false, privileged_reason: nil,
|
|
283
285
|
originator: nil)
|
|
286
|
+
counterparty = normalise_pubkey_hex(counterparty)
|
|
287
|
+
verifier = normalise_pubkey_hex(verifier)
|
|
284
288
|
raise InvalidParameterError.new('counterparty', 'a specific public key hex, not "anyone"') if counterparty == 'anyone'
|
|
285
289
|
|
|
286
290
|
Validators.validate_pub_key_hex!(verifier, 'verifier')
|
|
@@ -340,7 +344,16 @@ module BSV
|
|
|
340
344
|
end
|
|
341
345
|
|
|
342
346
|
def bytes_to_string(bytes)
|
|
343
|
-
bytes.pack('C*')
|
|
347
|
+
bytes.is_a?(String) ? bytes.b : bytes.pack('C*')
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
# Normalise a public key argument to a 66-character compressed hex string.
|
|
351
|
+
# Accepts either a 33-byte binary string or a 66-character hex string.
|
|
352
|
+
def normalise_pubkey_hex(value)
|
|
353
|
+
return value if value.is_a?(String) && value.length == 66
|
|
354
|
+
return value.unpack1('H*') if value.is_a?(String) && value.bytesize == 33
|
|
355
|
+
|
|
356
|
+
value
|
|
344
357
|
end
|
|
345
358
|
|
|
346
359
|
def string_to_bytes(str)
|
|
@@ -350,13 +363,7 @@ module BSV
|
|
|
350
363
|
def secure_compare(a, b)
|
|
351
364
|
return false unless a.bytesize == b.bytesize
|
|
352
365
|
|
|
353
|
-
|
|
354
|
-
OpenSSL.fixed_length_secure_compare(a, b)
|
|
355
|
-
else
|
|
356
|
-
result = 0
|
|
357
|
-
a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
|
|
358
|
-
result.zero?
|
|
359
|
-
end
|
|
366
|
+
OpenSSL.fixed_length_secure_compare(a, b)
|
|
360
367
|
end
|
|
361
368
|
end
|
|
362
369
|
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BSV
|
|
4
|
+
module Wallet
|
|
5
|
+
module Serializer
|
|
6
|
+
# BRC-103 wire codec for the +abort_action+ call (call byte 3).
|
|
7
|
+
#
|
|
8
|
+
# Args wire layout:
|
|
9
|
+
# [remaining bytes: reference (raw binary)]
|
|
10
|
+
#
|
|
11
|
+
# Result wire layout:
|
|
12
|
+
# [empty — success is implicit from the frame error byte]
|
|
13
|
+
module AbortAction
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def serialize_args(args)
|
|
17
|
+
ref = args[:reference]
|
|
18
|
+
return ''.b if ref.nil? || ref.empty?
|
|
19
|
+
|
|
20
|
+
ref.b
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def deserialize_args(bytes)
|
|
24
|
+
ref = bytes.b
|
|
25
|
+
{ reference: ref.empty? ? nil : ref }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def serialize_result(_result)
|
|
29
|
+
''.b
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def deserialize_result(_bytes)
|
|
33
|
+
{ aborted: true }
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|