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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -0
  3. data/lib/bsv/auth/auth_middleware.rb +6 -6
  4. data/lib/bsv/auth/certificate.rb +22 -18
  5. data/lib/bsv/auth/master_certificate.rb +5 -5
  6. data/lib/bsv/auth/nonce.rb +13 -13
  7. data/lib/bsv/auth/peer.rb +53 -53
  8. data/lib/bsv/auth/verifiable_certificate.rb +1 -1
  9. data/lib/bsv/identity/client.rb +27 -32
  10. data/lib/bsv/mcp/tools/broadcast_p2pkh.rb +18 -12
  11. data/lib/bsv/mcp/tools/check_balance.rb +16 -4
  12. data/lib/bsv/mcp/tools/fetch_tx.rb +11 -4
  13. data/lib/bsv/mcp/tools/fetch_utxos.rb +16 -4
  14. data/lib/bsv/mcp/tools/helpers.rb +2 -2
  15. data/lib/bsv/network/arc.rb +13 -153
  16. data/lib/bsv/network/broadcast_error.rb +1 -0
  17. data/lib/bsv/network/broadcast_response.rb +1 -0
  18. data/lib/bsv/network/protocols/arc.rb +4 -3
  19. data/lib/bsv/network/protocols/taal_binary.rb +1 -0
  20. data/lib/bsv/network/protocols/woc_rest.rb +2 -1
  21. data/lib/bsv/network/whats_on_chain.rb +13 -107
  22. data/lib/bsv/overlay/admin_token_template.rb +4 -4
  23. data/lib/bsv/overlay/lookup_resolver.rb +1 -0
  24. data/lib/bsv/overlay/topic_broadcaster.rb +1 -1
  25. data/lib/bsv/overlay/types.rb +1 -0
  26. data/lib/bsv/primitives/hex.rb +64 -0
  27. data/lib/bsv/registry/client.rb +26 -28
  28. data/lib/bsv/registry/types.rb +1 -0
  29. data/lib/bsv/script/interpreter/interpreter.rb +7 -0
  30. data/lib/bsv/script/interpreter/operations/crypto.rb +7 -1
  31. data/lib/bsv/script/push_drop_template.rb +4 -4
  32. data/lib/bsv/transaction/beef.rb +122 -83
  33. data/lib/bsv/transaction/merkle_path.rb +54 -38
  34. data/lib/bsv/transaction/transaction.rb +81 -30
  35. data/lib/bsv/transaction/transaction_input.rb +23 -18
  36. data/lib/bsv/version.rb +1 -1
  37. data/lib/bsv/wallet/errors.rb +47 -0
  38. data/lib/bsv/wallet/interface/brc100.rb +270 -0
  39. data/lib/bsv/wallet/interface.rb +9 -0
  40. data/lib/bsv/wallet/proto_wallet/key_deriver.rb +152 -0
  41. data/lib/bsv/wallet/proto_wallet/validators.rb +74 -0
  42. data/lib/bsv/wallet/proto_wallet.rb +327 -0
  43. data/lib/bsv/wallet.rb +16 -0
  44. data/lib/bsv-sdk.rb +18 -1
  45. metadata +22 -1
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ # BRC-100 abstract wallet interface — all 28 methods.
6
+ #
7
+ # Include this module and override the methods your implementation supports.
8
+ # Unimplemented methods raise +NotImplementedError+.
9
+ #
10
+ # The 28 methods are grouped into six functional areas matching
11
+ # the BRC-100 Interface Structure specification.
12
+ #
13
+ # @example
14
+ # class MyWallet
15
+ # include BSV::Wallet::Interface::BRC100
16
+ #
17
+ # def get_height(originator: nil)
18
+ # { height: 800_000 }
19
+ # end
20
+ # end
21
+ module Interface
22
+ module BRC100
23
+ # --- Transaction Operations (codes 1-7) ---
24
+
25
+ # Creates a new Bitcoin transaction.
26
+ #
27
+ # @param description [String] human-readable description (5-50 chars)
28
+ # @param inputs [Array<Hash>] optional inputs to consume
29
+ # - :outpoint [String] txid.index being consumed
30
+ # - :unlocking_script [String] hex unlocking script
31
+ # - :unlocking_script_length [Integer] length, if script provided later via {#sign_action}
32
+ # - :input_description [String] what this input consumes (5-50 chars)
33
+ # - :sequence_number [Integer] optional sequence number
34
+ # @param outputs [Array<Hash>] optional outputs to create
35
+ # - :locking_script [String] hex locking script
36
+ # - :satoshis [Integer] output value
37
+ # - :output_description [String] what this output represents (5-50 chars)
38
+ # - :basket [String] optional basket name for UTXO tracking
39
+ # - :custom_instructions [String] application-specific context
40
+ # - :tags [Array<String>] output tags for filtering
41
+ # @return [Hash] BRC-100 spec-mandated keys: :txid (display-order hex), :tx, :no_send_change,
42
+ # :send_with_results, :signable_transaction
43
+ def create_action(description:, input_beef: nil, inputs: nil, outputs: nil,
44
+ lock_time: nil, version: nil, labels: nil,
45
+ sign_and_process: true, accept_delayed_broadcast: true,
46
+ # BRC-100 spec-mandated parameter names — display-order hex txids
47
+ trust_self: nil, known_txids: nil, return_txid_only: false,
48
+ no_send: false, no_send_change: nil, send_with: nil,
49
+ randomize_outputs: true, originator: nil)
50
+ raise NotImplementedError
51
+ end
52
+
53
+ # Signs a transaction previously created with {#create_action}.
54
+ #
55
+ # @param spends [Hash{Integer => Hash}] input index => { unlocking_script:, sequence_number: }
56
+ # @param reference [String] reference returned by {#create_action}
57
+ def sign_action(spends:, reference:,
58
+ accept_delayed_broadcast: true,
59
+ return_txid_only: false, # BRC-100 spec-mandated parameter name
60
+ no_send: false, send_with: nil, originator: nil)
61
+ raise NotImplementedError
62
+ end
63
+
64
+ # Aborts a transaction that has not yet been finalized.
65
+ def abort_action(reference:, originator: nil)
66
+ raise NotImplementedError
67
+ end
68
+
69
+ # Lists transactions matching the specified labels.
70
+ #
71
+ # @return [Hash] :total_actions, :actions
72
+ def list_actions(labels:, label_query_mode: :any,
73
+ include_labels: false, include_inputs: false,
74
+ include_input_source_locking_scripts: false,
75
+ include_input_unlocking_scripts: false,
76
+ include_outputs: false, include_output_locking_scripts: false,
77
+ limit: 10, offset: 0, seek_permission: true, originator: nil)
78
+ raise NotImplementedError
79
+ end
80
+
81
+ # Internalizes a transaction — labels it, pays outputs to the wallet balance,
82
+ # inserts outputs into baskets, and/or tags them.
83
+ #
84
+ # @param tx [Array<Integer>] Atomic BEEF-formatted transaction (byte array)
85
+ # @param outputs [Array<Hash>] metadata per output
86
+ # - :output_index [Integer] index within the transaction
87
+ # - :protocol [Symbol] :wallet_payment or :basket_insertion
88
+ # - :payment_remittance [Hash] for payments: { derivation_prefix:, derivation_suffix:, sender_identity_key: }
89
+ # - :insertion_remittance [Hash] for insertions: { basket:, custom_instructions:, tags: }
90
+ def internalize_action(tx:, outputs:, description:, labels: nil,
91
+ seek_permission: true, originator: nil)
92
+ raise NotImplementedError
93
+ end
94
+
95
+ # Lists spendable outputs in a basket.
96
+ #
97
+ # @param include [Symbol] nil, :locking_scripts, or :entire_transactions
98
+ # @return [Hash] :total_outputs, :beef, :outputs
99
+ def list_outputs(basket:, tags: nil, tag_query_mode: :any, include: nil,
100
+ include_custom_instructions: false, include_tags: false,
101
+ include_labels: false, limit: 10, offset: 0,
102
+ seek_permission: true, originator: nil)
103
+ raise NotImplementedError
104
+ end
105
+
106
+ # Removes an output from a basket without spending it.
107
+ def relinquish_output(basket:, output:, originator: nil)
108
+ raise NotImplementedError
109
+ end
110
+
111
+ # --- Public Key Management (codes 8-10) ---
112
+
113
+ # Retrieves a derived or identity public key.
114
+ #
115
+ # @param protocol_id [Array(Integer, String)] security level (0-2) and protocol string
116
+ # @param counterparty [String] public key hex, 'self', or 'anyone'
117
+ # @return [Hash] :public_key
118
+ def get_public_key(identity_key: false, protocol_id: nil, key_id: nil,
119
+ privileged: false, privileged_reason: nil,
120
+ counterparty: nil, for_self: false,
121
+ seek_permission: true, originator: nil)
122
+ raise NotImplementedError
123
+ end
124
+
125
+ # Reveals key linkage with a counterparty to a verifier, across all interactions.
126
+ def reveal_counterparty_key_linkage(counterparty:, verifier:,
127
+ privileged: false, privileged_reason: nil,
128
+ originator: nil)
129
+ raise NotImplementedError
130
+ end
131
+
132
+ # Reveals key linkage for a specific protocol and key interaction.
133
+ def reveal_specific_key_linkage(counterparty:, verifier:, protocol_id:, key_id:,
134
+ privileged: false, privileged_reason: nil,
135
+ originator: nil)
136
+ raise NotImplementedError
137
+ end
138
+
139
+ # --- Cryptography Operations (codes 11-16) ---
140
+
141
+ # Encrypts plaintext using derived keys.
142
+ def encrypt(plaintext:, protocol_id:, key_id:,
143
+ privileged: false, privileged_reason: nil,
144
+ counterparty: nil, seek_permission: true, originator: nil)
145
+ raise NotImplementedError
146
+ end
147
+
148
+ # Decrypts ciphertext using derived keys.
149
+ def decrypt(ciphertext:, protocol_id:, key_id:,
150
+ privileged: false, privileged_reason: nil,
151
+ counterparty: nil, seek_permission: true, originator: nil)
152
+ raise NotImplementedError
153
+ end
154
+
155
+ # Creates an HMAC for the provided data.
156
+ def create_hmac(data:, protocol_id:, key_id:,
157
+ privileged: false, privileged_reason: nil,
158
+ counterparty: nil, seek_permission: true, originator: nil)
159
+ raise NotImplementedError
160
+ end
161
+
162
+ # Verifies an HMAC against the provided data.
163
+ def verify_hmac(data:, hmac:, protocol_id:, key_id:,
164
+ privileged: false, privileged_reason: nil,
165
+ counterparty: nil, seek_permission: true, originator: nil)
166
+ raise NotImplementedError
167
+ end
168
+
169
+ # Creates a digital signature (ECDSA) for data or a pre-computed hash.
170
+ def create_signature(protocol_id:, key_id:, data: nil, hash_to_directly_sign: nil,
171
+ privileged: false, privileged_reason: nil,
172
+ counterparty: nil, seek_permission: true, originator: nil)
173
+ raise NotImplementedError
174
+ end
175
+
176
+ # Verifies a digital signature against data or a pre-computed hash.
177
+ def verify_signature(signature:, protocol_id:, key_id:, data: nil,
178
+ hash_to_directly_verify: nil,
179
+ privileged: false, privileged_reason: nil,
180
+ counterparty: nil, for_self: false,
181
+ seek_permission: true, originator: nil)
182
+ raise NotImplementedError
183
+ end
184
+
185
+ # --- Identity and Certificate Management (codes 17-22) ---
186
+
187
+ # Acquires an identity certificate from a certifier or by direct receipt.
188
+ #
189
+ # @param acquisition_protocol [Symbol] :direct or :issuance
190
+ # @param fields [Hash{String => String}] certificate field names to values
191
+ def acquire_certificate(type:, certifier:, acquisition_protocol:, fields:,
192
+ serial_number: nil, revocation_outpoint: nil,
193
+ signature: nil, certifier_url: nil,
194
+ keyring_revealer: nil, keyring_for_subject: nil,
195
+ privileged: false, privileged_reason: nil, originator: nil)
196
+ raise NotImplementedError
197
+ end
198
+
199
+ # Lists identity certificates filtered by certifier(s) and type(s).
200
+ def list_certificates(certifiers:, types:, limit: 10, offset: 0,
201
+ privileged: false, privileged_reason: nil, originator: nil)
202
+ raise NotImplementedError
203
+ end
204
+
205
+ # Proves select fields of a certificate to a verifier.
206
+ #
207
+ # @param certificate [Hash] the full certificate (type, subject, serial_number,
208
+ # certifier, revocation_outpoint, signature, fields)
209
+ # @param fields_to_reveal [Array<String>] field names to disclose
210
+ def prove_certificate(certificate:, fields_to_reveal:, verifier:,
211
+ privileged: false, privileged_reason: nil, originator: nil)
212
+ raise NotImplementedError
213
+ end
214
+
215
+ # Removes a certificate from the wallet.
216
+ def relinquish_certificate(type:, serial_number:, certifier:, originator: nil)
217
+ raise NotImplementedError
218
+ end
219
+
220
+ # Discovers certificates issued to a given identity key.
221
+ def discover_by_identity_key(identity_key:, limit: 10, offset: 0,
222
+ seek_permission: true, originator: nil)
223
+ raise NotImplementedError
224
+ end
225
+
226
+ # Discovers certificates matching specific attribute values.
227
+ #
228
+ # @param attributes [Hash{String => String}] field name/value pairs to match
229
+ def discover_by_attributes(attributes:, limit: 10, offset: 0,
230
+ seek_permission: true, originator: nil)
231
+ raise NotImplementedError
232
+ end
233
+
234
+ # --- Authentication (codes 23-24) ---
235
+
236
+ # Checks whether the user is authenticated.
237
+ def authenticated?(originator: nil)
238
+ raise NotImplementedError
239
+ end
240
+
241
+ # Blocks until the user is authenticated.
242
+ def wait_for_authentication(originator: nil)
243
+ raise NotImplementedError
244
+ end
245
+
246
+ # --- Blockchain and Network Data (codes 25-28) ---
247
+
248
+ # Returns the current blockchain height.
249
+ def get_height(originator: nil)
250
+ raise NotImplementedError
251
+ end
252
+
253
+ # Returns the 80-byte block header at the given height.
254
+ def get_header_for_height(height:, originator: nil)
255
+ raise NotImplementedError
256
+ end
257
+
258
+ # Returns the network (:mainnet or :testnet).
259
+ def get_network(originator: nil)
260
+ raise NotImplementedError
261
+ end
262
+
263
+ # Returns the wallet version string.
264
+ def get_version(originator: nil)
265
+ raise NotImplementedError
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ module Interface
6
+ autoload :BRC100, 'bsv/wallet/interface/brc100'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+
5
+ module BSV
6
+ module Wallet
7
+ class ProtoWallet
8
+ # BRC-42/43 key derivation.
9
+ #
10
+ # Derives child keys from a root private key using BKDS (BSV Key Derivation
11
+ # Scheme). Supports protocol IDs, key IDs, counterparties, and security
12
+ # levels as defined in BRC-43.
13
+ class KeyDeriver
14
+ include Validators
15
+
16
+ ANYONE_BN = OpenSSL::BN.new(1)
17
+
18
+ attr_reader :root_key
19
+
20
+ # @param root_key [BSV::Primitives::PrivateKey, String] a private key or 'anyone'
21
+ def initialize(root_key)
22
+ @root_key = if root_key == 'anyone'
23
+ BSV::Primitives::PrivateKey.new(ANYONE_BN)
24
+ elsif root_key.is_a?(BSV::Primitives::PrivateKey)
25
+ root_key
26
+ else
27
+ raise ArgumentError, "expected a BSV::Primitives::PrivateKey or 'anyone', got #{root_key.class}"
28
+ end
29
+ end
30
+
31
+ # Returns the identity public key as a hex string.
32
+ # @return [String] 66-character compressed public key hex
33
+ def identity_key
34
+ @identity_key ||= @root_key.public_key.to_hex
35
+ end
36
+
37
+ # Derives a public key using BRC-42 key derivation.
38
+ #
39
+ # @param protocol_id [Array] [security_level, protocol_name]
40
+ # @param key_id [String] key identifier
41
+ # @param counterparty [String] public key hex, 'self', or 'anyone'
42
+ # @param for_self [Boolean] derive from own identity rather than counterparty's
43
+ # @return [BSV::Primitives::PublicKey]
44
+ def derive_public_key(protocol_id, key_id, counterparty, for_self: false)
45
+ Validators.validate_protocol_id!(protocol_id)
46
+ Validators.validate_key_id!(key_id)
47
+ invoice = compute_invoice_number(protocol_id, key_id)
48
+ BSV.logger&.debug { "[KeyDeriver] derive_public_key: invoice=#{invoice.inspect} for_self=#{for_self}" }
49
+ counterparty_pub = resolve_counterparty(counterparty)
50
+
51
+ if for_self
52
+ @root_key.derive_child(counterparty_pub, invoice).public_key
53
+ else
54
+ counterparty_pub.derive_child(@root_key, invoice)
55
+ end
56
+ end
57
+
58
+ # Derives a private key using BRC-42 key derivation.
59
+ #
60
+ # @param protocol_id [Array] [security_level, protocol_name]
61
+ # @param key_id [String] key identifier
62
+ # @param counterparty [String] public key hex, 'self', or 'anyone'
63
+ # @return [BSV::Primitives::PrivateKey]
64
+ def derive_private_key(protocol_id, key_id, counterparty)
65
+ Validators.validate_protocol_id!(protocol_id)
66
+ Validators.validate_key_id!(key_id)
67
+ invoice = compute_invoice_number(protocol_id, key_id)
68
+ BSV.logger&.debug { "[KeyDeriver] derive_private_key: invoice=#{invoice.inspect}" }
69
+ counterparty_pub = resolve_counterparty(counterparty)
70
+ @root_key.derive_child(counterparty_pub, invoice)
71
+ end
72
+
73
+ # Derives a symmetric key for encryption/HMAC operations.
74
+ #
75
+ # Uses ECDH between the derived private and public child keys to
76
+ # produce a shared secret, then uses the X-coordinate as the key.
77
+ #
78
+ # @param protocol_id [Array] [security_level, protocol_name]
79
+ # @param key_id [String] key identifier
80
+ # @param counterparty [String] public key hex, 'self', or 'anyone'
81
+ # @return [BSV::Primitives::SymmetricKey]
82
+ def derive_symmetric_key(protocol_id, key_id, counterparty)
83
+ Validators.validate_protocol_id!(protocol_id)
84
+ Validators.validate_key_id!(key_id)
85
+ invoice = compute_invoice_number(protocol_id, key_id)
86
+ counterparty_pub = resolve_counterparty(counterparty)
87
+
88
+ derived_private = @root_key.derive_child(counterparty_pub, invoice)
89
+ derived_public = counterparty_pub.derive_child(@root_key, invoice)
90
+
91
+ BSV::Primitives::SymmetricKey.from_ecdh(derived_private, derived_public)
92
+ end
93
+
94
+ # Reveals the ECDH shared secret between this wallet and a counterparty.
95
+ # Used for BRC-69 Method 1 (counterparty key linkage).
96
+ #
97
+ # @param counterparty [String] public key hex (not 'self')
98
+ # @return [String] compressed shared secret bytes
99
+ def reveal_counterparty_secret(counterparty)
100
+ raise InvalidParameterError.new('counterparty', 'not "self" for key linkage revelation') if counterparty == 'self'
101
+
102
+ counterparty_pub = resolve_counterparty(counterparty)
103
+ @root_key.derive_shared_secret(counterparty_pub).compressed
104
+ end
105
+
106
+ # Reveals the specific key offset for a particular derived key.
107
+ # Used for BRC-69 Method 2 (specific key linkage).
108
+ #
109
+ # @param counterparty [String] public key hex
110
+ # @param protocol_id [Array] [security_level, protocol_name]
111
+ # @param key_id [String] key identifier
112
+ # @return [String] HMAC-SHA256 bytes (the key offset)
113
+ def reveal_specific_secret(counterparty, protocol_id, key_id)
114
+ Validators.validate_protocol_id!(protocol_id)
115
+ Validators.validate_key_id!(key_id)
116
+ counterparty_pub = resolve_counterparty(counterparty)
117
+ shared = @root_key.derive_shared_secret(counterparty_pub)
118
+ invoice = compute_invoice_number(protocol_id, key_id)
119
+ BSV::Primitives::Digest.hmac_sha256(shared.compressed, invoice.encode('UTF-8'))
120
+ end
121
+
122
+ private
123
+
124
+ # Resolves a counterparty identifier to a PublicKey.
125
+ #
126
+ # @param counterparty [String] 'self', 'anyone', or a hex public key
127
+ # @return [BSV::Primitives::PublicKey]
128
+ def resolve_counterparty(counterparty)
129
+ case counterparty
130
+ when 'self'
131
+ @root_key.public_key
132
+ when 'anyone'
133
+ BSV::Primitives::PrivateKey.new(ANYONE_BN).public_key
134
+ else
135
+ Validators.validate_counterparty!(counterparty)
136
+ BSV::Primitives::PublicKey.from_hex(counterparty)
137
+ end
138
+ end
139
+
140
+ # Computes the invoice number from a protocol ID and key ID.
141
+ # Format: "#{security_level}-#{protocol_name}-#{key_id}"
142
+ #
143
+ # @param protocol_id [Array] [security_level, protocol_name]
144
+ # @param key_id [String]
145
+ # @return [String]
146
+ def compute_invoice_number(protocol_id, key_id)
147
+ "#{protocol_id[0]}-#{protocol_id[1].downcase.strip}-#{key_id}"
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BSV
4
+ module Wallet
5
+ class ProtoWallet
6
+ # Validation helpers for BRC-100 wallet method parameters.
7
+ #
8
+ # Provides the subset of validators required by KeyDeriver and ProtoWallet.
9
+ # Raises +InvalidParameterError+ for any invalid input.
10
+ module Validators
11
+ module_function
12
+
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
+ def validate_protocol_id!(protocol_id)
21
+ unless protocol_id.is_a?(Array) && protocol_id.length == 2
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?(' ')
36
+ end
37
+
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
+ def validate_key_id!(key_id)
45
+ raise InvalidParameterError.new('key_id', 'a String') unless key_id.is_a?(String)
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
49
+ end
50
+
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
+ def validate_counterparty!(counterparty)
56
+ return if %w[self anyone].include?(counterparty)
57
+
58
+ validate_pub_key_hex!(counterparty, 'counterparty')
59
+ end
60
+
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
+ def validate_pub_key_hex!(value, name = 'public_key')
67
+ raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
68
+
69
+ raise InvalidParameterError.new(name, 'a 66-character hex string (compressed public key)') unless value.match?(/\A[0-9a-f]{66}\z/)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end