bsv-sdk 0.7.0 → 0.8.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +406 -146
  3. data/lib/bsv/identity/client.rb +11 -5
  4. data/lib/bsv/overlay/topic_broadcaster.rb +1 -3
  5. data/lib/bsv/primitives/openssl_ec_shim.rb +8 -4
  6. data/lib/bsv/primitives/secp256k1.rb +11 -1
  7. data/lib/bsv/transaction/beef.rb +45 -38
  8. data/lib/bsv/transaction/merkle_path.rb +64 -0
  9. data/lib/bsv/transaction/transaction.rb +3 -2
  10. data/lib/bsv/version.rb +1 -1
  11. metadata +2 -32
  12. data/lib/bsv/attest/configuration.rb +0 -9
  13. data/lib/bsv/attest/response.rb +0 -19
  14. data/lib/bsv/attest/verification_error.rb +0 -7
  15. data/lib/bsv/attest/version.rb +0 -7
  16. data/lib/bsv/attest.rb +0 -71
  17. data/lib/bsv/wallet_interface/chain_provider.rb +0 -37
  18. data/lib/bsv/wallet_interface/errors/invalid_hmac_error.rb +0 -11
  19. data/lib/bsv/wallet_interface/errors/invalid_parameter_error.rb +0 -14
  20. data/lib/bsv/wallet_interface/errors/invalid_signature_error.rb +0 -11
  21. data/lib/bsv/wallet_interface/errors/unsupported_action_error.rb +0 -11
  22. data/lib/bsv/wallet_interface/errors/wallet_error.rb +0 -14
  23. data/lib/bsv/wallet_interface/file_store.rb +0 -222
  24. data/lib/bsv/wallet_interface/interface.rb +0 -384
  25. data/lib/bsv/wallet_interface/key_deriver.rb +0 -144
  26. data/lib/bsv/wallet_interface/local_proof_store.rb +0 -42
  27. data/lib/bsv/wallet_interface/memory_store.rb +0 -149
  28. data/lib/bsv/wallet_interface/null_chain_provider.rb +0 -22
  29. data/lib/bsv/wallet_interface/proof_store.rb +0 -32
  30. data/lib/bsv/wallet_interface/proto_wallet.rb +0 -361
  31. data/lib/bsv/wallet_interface/storage_adapter.rb +0 -71
  32. data/lib/bsv/wallet_interface/validators.rb +0 -126
  33. data/lib/bsv/wallet_interface/version.rb +0 -7
  34. data/lib/bsv/wallet_interface/wallet_client.rb +0 -874
  35. data/lib/bsv/wallet_interface/wire/reader.rb +0 -238
  36. data/lib/bsv/wallet_interface/wire/serializer.rb +0 -1993
  37. data/lib/bsv/wallet_interface/wire/writer.rb +0 -214
  38. data/lib/bsv/wallet_interface/wire.rb +0 -19
  39. data/lib/bsv/wallet_interface.rb +0 -31
  40. data/lib/bsv-attest.rb +0 -4
  41. data/lib/bsv-wallet.rb +0 -4
@@ -1,361 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'openssl'
4
-
5
- module BSV
6
- module Wallet
7
- # Cryptographic wallet implementing the 8 key/crypto BRC-100 methods.
8
- #
9
- # ProtoWallet handles key derivation, encryption, decryption, HMAC,
10
- # and signature operations using BRC-42/43 key derivation. Transaction,
11
- # certificate, blockchain, and authentication methods raise
12
- # {UnsupportedActionError} via the included {Interface}.
13
- #
14
- # @example Encrypt and decrypt a message
15
- # wallet = BSV::Wallet::ProtoWallet.new(BSV::Primitives::PrivateKey.generate)
16
- # args = { protocol_id: [0, 'hello world'], key_id: '1', counterparty: 'self' }
17
- # result = wallet.encrypt(args.merge(plaintext: [104, 101, 108, 108, 111]))
18
- # wallet.decrypt(args.merge(ciphertext: result[:ciphertext]))[:plaintext]
19
- class ProtoWallet
20
- include Interface
21
-
22
- # @return [KeyDeriver] the underlying key deriver
23
- attr_reader :key_deriver
24
-
25
- # @param key [BSV::Primitives::PrivateKey, String, KeyDeriver]
26
- # A private key, the string +'anyone'+, or a pre-built {KeyDeriver}
27
- def initialize(key)
28
- @key_deriver = if key.is_a?(KeyDeriver)
29
- key
30
- else
31
- KeyDeriver.new(key)
32
- end
33
- end
34
-
35
- # Returns a derived or identity public key.
36
- #
37
- # When +args[:identity_key]+ is true, returns the wallet's identity key.
38
- # Otherwise derives a key for the given protocol, key ID, and counterparty.
39
- #
40
- # @param args [Hash]
41
- # @option args [Boolean] :identity_key return the identity key instead of deriving
42
- # @option args [Array] :protocol_id [security_level, protocol_name]
43
- # @option args [String] :key_id key identifier
44
- # @option args [String] :counterparty public key hex, 'self', or 'anyone'
45
- # @option args [Boolean] :for_self derive from own identity
46
- # @param originator [String, nil] FQDN of the originating application
47
- # @return [Hash] { public_key: String } hex-encoded compressed public key
48
- def get_public_key(args, originator: nil)
49
- if args[:identity_key]
50
- { public_key: @key_deriver.identity_key }
51
- else
52
- counterparty = args[:counterparty] || 'self'
53
- pub = @key_deriver.derive_public_key(
54
- args[:protocol_id],
55
- args[:key_id],
56
- counterparty,
57
- for_self: args[:for_self] || false
58
- )
59
- { public_key: pub.to_hex }
60
- end
61
- end
62
-
63
- # Encrypts plaintext using AES-256-GCM with a derived symmetric key.
64
- #
65
- # @param args [Hash]
66
- # @option args [Array<Integer>] :plaintext byte array to encrypt
67
- # @option args [Array] :protocol_id [security_level, protocol_name]
68
- # @option args [String] :key_id key identifier
69
- # @option args [String] :counterparty public key hex, 'self', or 'anyone'
70
- # @param originator [String, nil] FQDN of the originating application
71
- # @return [Hash] { ciphertext: Array<Integer> }
72
- def encrypt(args, originator: nil)
73
- sym_key = derive_sym_key(args)
74
- ciphertext = sym_key.encrypt(bytes_to_string(args[:plaintext]))
75
- { ciphertext: string_to_bytes(ciphertext) }
76
- end
77
-
78
- # Decrypts ciphertext using AES-256-GCM with a derived symmetric key.
79
- #
80
- # @param args [Hash]
81
- # @option args [Array<Integer>] :ciphertext byte array to decrypt
82
- # @option args [Array] :protocol_id [security_level, protocol_name]
83
- # @option args [String] :key_id key identifier
84
- # @option args [String] :counterparty public key hex, 'self', or 'anyone'
85
- # @param originator [String, nil] FQDN of the originating application
86
- # @return [Hash] { plaintext: Array<Integer> }
87
- def decrypt(args, originator: nil)
88
- sym_key = derive_sym_key(args)
89
- plaintext = sym_key.decrypt(bytes_to_string(args[:ciphertext]))
90
- { plaintext: string_to_bytes(plaintext) }
91
- end
92
-
93
- # Creates an HMAC-SHA256 using a derived symmetric key.
94
- #
95
- # @param args [Hash]
96
- # @option args [Array<Integer>] :data byte array to authenticate
97
- # @option args [Array] :protocol_id [security_level, protocol_name]
98
- # @option args [String] :key_id key identifier
99
- # @option args [String] :counterparty public key hex, 'self', or 'anyone'
100
- # @param originator [String, nil] FQDN of the originating application
101
- # @return [Hash] { hmac: Array<Integer> }
102
- def create_hmac(args, originator: nil)
103
- sym_key = derive_sym_key(args)
104
- hmac = BSV::Primitives::Digest.hmac_sha256(sym_key.to_bytes, bytes_to_string(args[:data]))
105
- { hmac: string_to_bytes(hmac) }
106
- end
107
-
108
- # Verifies an HMAC-SHA256 using a derived symmetric key.
109
- #
110
- # @param args [Hash]
111
- # @option args [Array<Integer>] :data byte array that was authenticated
112
- # @option args [Array<Integer>] :hmac HMAC to verify
113
- # @option args [Array] :protocol_id [security_level, protocol_name]
114
- # @option args [String] :key_id key identifier
115
- # @option args [String] :counterparty public key hex, 'self', or 'anyone'
116
- # @param originator [String, nil] FQDN of the originating application
117
- # @return [Hash] { valid: true }
118
- # @raise [InvalidHmacError] if the HMAC does not match
119
- def verify_hmac(args, originator: nil)
120
- sym_key = derive_sym_key(args)
121
- expected = BSV::Primitives::Digest.hmac_sha256(sym_key.to_bytes, bytes_to_string(args[:data]))
122
- provided = bytes_to_string(args[:hmac])
123
-
124
- raise InvalidHmacError unless secure_compare(expected, provided)
125
-
126
- { valid: true }
127
- end
128
-
129
- # Creates an ECDSA signature using a derived private key.
130
- #
131
- # Either +:data+ or +:hash_to_directly_sign+ must be provided.
132
- # If +:data+ is given it is SHA-256 hashed before signing.
133
- # If +:hash_to_directly_sign+ is given it is used as the 32-byte hash directly.
134
- #
135
- # @param args [Hash]
136
- # @option args [Array<Integer>] :data data to hash and sign
137
- # @option args [Array<Integer>] :hash_to_directly_sign pre-computed 32-byte hash to sign
138
- # @option args [Array] :protocol_id [security_level, protocol_name]
139
- # @option args [String] :key_id key identifier
140
- # @option args [String] :counterparty public key hex, 'self', or 'anyone'
141
- # @param originator [String, nil] FQDN of the originating application
142
- # @return [Hash] { signature: Array<Integer> } DER-encoded signature as byte array
143
- def create_signature(args, originator: nil)
144
- counterparty = args[:counterparty] || 'self'
145
- priv_key = @key_deriver.derive_private_key(args[:protocol_id], args[:key_id], counterparty)
146
-
147
- hash = if args[:hash_to_directly_sign]
148
- bytes_to_string(args[:hash_to_directly_sign])
149
- else
150
- BSV::Primitives::Digest.sha256(bytes_to_string(args[:data]))
151
- end
152
-
153
- sig = priv_key.sign(hash)
154
- { signature: string_to_bytes(sig.to_der) }
155
- end
156
-
157
- # Verifies an ECDSA signature using a derived public key.
158
- #
159
- # Either +:data+ or +:hash_to_directly_verify+ must be provided.
160
- # If +:data+ is given it is SHA-256 hashed before verification.
161
- #
162
- # @param args [Hash]
163
- # @option args [Array<Integer>] :data original data that was signed
164
- # @option args [Array<Integer>] :hash_to_directly_verify pre-computed 32-byte hash
165
- # @option args [Array<Integer>] :signature DER-encoded signature as byte array
166
- # @option args [Array] :protocol_id [security_level, protocol_name]
167
- # @option args [String] :key_id key identifier
168
- # @option args [String] :counterparty public key hex, 'self', or 'anyone'
169
- # @option args [Boolean] :for_self verify own derived key (default false)
170
- # @param originator [String, nil] FQDN of the originating application
171
- # @return [Hash] { valid: true }
172
- # @raise [InvalidSignatureError] if the signature does not verify
173
- def verify_signature(args, originator: nil)
174
- counterparty = args[:counterparty] || 'self'
175
- for_self = args[:for_self] || false
176
-
177
- pub_key = @key_deriver.derive_public_key(
178
- args[:protocol_id],
179
- args[:key_id],
180
- counterparty,
181
- for_self: for_self
182
- )
183
-
184
- hash = if args[:hash_to_directly_verify]
185
- bytes_to_string(args[:hash_to_directly_verify])
186
- else
187
- BSV::Primitives::Digest.sha256(bytes_to_string(args[:data]))
188
- end
189
-
190
- sig = BSV::Primitives::Signature.from_der(bytes_to_string(args[:signature]))
191
- valid = pub_key.verify(hash, sig)
192
-
193
- raise InvalidSignatureError unless valid
194
-
195
- { valid: true }
196
- end
197
-
198
- # Reveals counterparty key linkage to a verifier (BRC-69 Method 1).
199
- #
200
- # Encrypts the ECDH shared secret between this wallet and the counterparty
201
- # for the verifier using a BRC-72 protocol-derived key. Also generates a
202
- # BRC-94 Schnorr zero-knowledge proof of the linkage and encrypts it for
203
- # the verifier.
204
- #
205
- # @param args [Hash]
206
- # @option args [String] :counterparty counterparty public key hex (not 'self')
207
- # @option args [String] :verifier verifier public key hex
208
- # @param originator [String, nil] FQDN of the originating application
209
- # @return [Hash] with :prover, :verifier, :counterparty, :revelation_time,
210
- # :encrypted_linkage, :encrypted_linkage_proof
211
- def reveal_counterparty_key_linkage(args, originator: nil)
212
- counterparty = args[:counterparty]
213
- verifier = args[:verifier]
214
-
215
- raise InvalidParameterError.new('counterparty', 'a specific public key hex, not "anyone"') if counterparty == 'anyone'
216
-
217
- Validators.validate_pub_key_hex!(verifier, 'verifier')
218
-
219
- linkage = @key_deriver.reveal_counterparty_secret(counterparty)
220
- revelation_time = Time.now.utc.iso8601
221
-
222
- encrypted_linkage_result = encrypt({
223
- plaintext: string_to_bytes(linkage),
224
- protocol_id: [2, 'counterparty linkage revelation'],
225
- key_id: revelation_time,
226
- counterparty: verifier
227
- })
228
-
229
- counterparty_pub = BSV::Primitives::PublicKey.from_hex(counterparty)
230
- linkage_point = BSV::Primitives::PublicKey.from_bytes(linkage)
231
- schnorr_proof = BSV::Primitives::Schnorr.generate_proof(
232
- @key_deriver.root_key,
233
- @key_deriver.root_key.public_key,
234
- counterparty_pub,
235
- linkage_point
236
- )
237
-
238
- z_bytes = schnorr_proof.z.to_s(2)
239
- z_bytes = ("\x00".b * (32 - z_bytes.length)) + z_bytes if z_bytes.length < 32
240
- proof_bin = schnorr_proof.r.compressed + schnorr_proof.s_prime.compressed + z_bytes
241
-
242
- encrypted_proof_result = encrypt({
243
- plaintext: string_to_bytes(proof_bin),
244
- protocol_id: [2, 'counterparty linkage revelation'],
245
- key_id: revelation_time,
246
- counterparty: verifier
247
- })
248
-
249
- {
250
- prover: @key_deriver.identity_key,
251
- verifier: verifier,
252
- counterparty: counterparty,
253
- revelation_time: revelation_time,
254
- encrypted_linkage: encrypted_linkage_result[:ciphertext],
255
- encrypted_linkage_proof: encrypted_proof_result[:ciphertext]
256
- }
257
- end
258
-
259
- # Reveals specific key linkage for a particular interaction (BRC-69 Method 2).
260
- #
261
- # Encrypts the HMAC-derived key offset for the given protocol/key combination
262
- # for the verifier using a BRC-72 protocol-derived key. Proof type 0 means
263
- # no cryptographic proof is provided (consistent with the ts-sdk behaviour).
264
- #
265
- # @param args [Hash]
266
- # @option args [String] :counterparty counterparty public key hex
267
- # @option args [String] :verifier verifier public key hex
268
- # @option args [Array] :protocol_id [security_level, protocol_name]
269
- # @option args [String] :key_id key identifier
270
- # @param originator [String, nil] FQDN of the originating application
271
- # @return [Hash] with :prover, :verifier, :counterparty, :protocol_id, :key_id,
272
- # :encrypted_linkage, :encrypted_linkage_proof, :proof_type
273
- def reveal_specific_key_linkage(args, originator: nil)
274
- counterparty = args[:counterparty]
275
- verifier = args[:verifier]
276
- protocol_id = args[:protocol_id]
277
- key_id = args[:key_id]
278
-
279
- raise InvalidParameterError.new('counterparty', 'a specific public key hex, not "anyone"') if counterparty == 'anyone'
280
-
281
- Validators.validate_pub_key_hex!(verifier, 'verifier')
282
-
283
- linkage = @key_deriver.reveal_specific_secret(counterparty, protocol_id, key_id)
284
-
285
- derived_protocol = "specific linkage revelation #{protocol_id[0]} #{protocol_id[1]}"
286
-
287
- encrypted_linkage_result = encrypt({
288
- plaintext: string_to_bytes(linkage),
289
- protocol_id: [2, derived_protocol],
290
- key_id: key_id,
291
- counterparty: verifier
292
- })
293
-
294
- encrypted_proof_result = encrypt({
295
- plaintext: [0],
296
- protocol_id: [2, derived_protocol],
297
- key_id: key_id,
298
- counterparty: verifier
299
- })
300
-
301
- {
302
- prover: @key_deriver.identity_key,
303
- verifier: verifier,
304
- counterparty: counterparty,
305
- protocol_id: protocol_id,
306
- key_id: key_id,
307
- encrypted_linkage: encrypted_linkage_result[:ciphertext],
308
- encrypted_linkage_proof: encrypted_proof_result[:ciphertext],
309
- proof_type: 0
310
- }
311
- end
312
-
313
- private
314
-
315
- # Derives a symmetric key from the args hash.
316
- #
317
- # @param args [Hash] must contain :protocol_id, :key_id; :counterparty defaults to 'self'
318
- # @return [BSV::Primitives::SymmetricKey]
319
- def derive_sym_key(args)
320
- counterparty = args[:counterparty] || 'self'
321
- @key_deriver.derive_symmetric_key(args[:protocol_id], args[:key_id], counterparty)
322
- end
323
-
324
- # Converts a byte array (Array of Integers 0..255) to a binary string.
325
- #
326
- # @param bytes [Array<Integer>] byte array
327
- # @return [String] binary string
328
- def bytes_to_string(bytes)
329
- bytes.pack('C*')
330
- end
331
-
332
- # Converts a binary string to a byte array (Array of Integers 0..255).
333
- #
334
- # @param str [String] binary string
335
- # @return [Array<Integer>] byte array
336
- def string_to_bytes(str)
337
- str.unpack('C*')
338
- end
339
-
340
- # Constant-time string comparison to prevent timing attacks.
341
- #
342
- # Falls back to a manual XOR loop on platforms where
343
- # +OpenSSL.fixed_length_secure_compare+ is unavailable.
344
- #
345
- # @param a [String] first binary string
346
- # @param b [String] second binary string
347
- # @return [Boolean]
348
- def secure_compare(a, b)
349
- return false unless a.bytesize == b.bytesize
350
-
351
- if OpenSSL.respond_to?(:fixed_length_secure_compare)
352
- OpenSSL.fixed_length_secure_compare(a, b)
353
- else
354
- result = 0
355
- a.bytes.zip(b.bytes) { |x, y| result |= x ^ y }
356
- result.zero?
357
- end
358
- end
359
- end
360
- end
361
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module BSV
4
- module Wallet
5
- # Duck-typed storage interface for wallet persistence.
6
- #
7
- # Include this module in storage adapters and override all methods.
8
- # The default implementations raise NotImplementedError.
9
- module StorageAdapter
10
- def store_action(_action_data)
11
- raise NotImplementedError, "#{self.class}#store_action not implemented"
12
- end
13
-
14
- def find_actions(_query)
15
- raise NotImplementedError, "#{self.class}#find_actions not implemented"
16
- end
17
-
18
- def store_output(_output_data)
19
- raise NotImplementedError, "#{self.class}#store_output not implemented"
20
- end
21
-
22
- def find_outputs(_query)
23
- raise NotImplementedError, "#{self.class}#find_outputs not implemented"
24
- end
25
-
26
- def delete_output(_outpoint)
27
- raise NotImplementedError, "#{self.class}#delete_output not implemented"
28
- end
29
-
30
- def store_certificate(_cert_data)
31
- raise NotImplementedError, "#{self.class}#store_certificate not implemented"
32
- end
33
-
34
- def find_certificates(_query)
35
- raise NotImplementedError, "#{self.class}#find_certificates not implemented"
36
- end
37
-
38
- def delete_certificate(type:, serial_number:, certifier:)
39
- raise NotImplementedError, "#{self.class}#delete_certificate not implemented"
40
- end
41
-
42
- def count_actions(_query)
43
- raise NotImplementedError, "#{self.class}#count_actions not implemented"
44
- end
45
-
46
- def count_outputs(_query)
47
- raise NotImplementedError, "#{self.class}#count_outputs not implemented"
48
- end
49
-
50
- def count_certificates(_query)
51
- raise NotImplementedError, "#{self.class}#count_certificates not implemented"
52
- end
53
-
54
- def store_proof(_txid, _bump_hex)
55
- raise NotImplementedError, "#{self.class}#store_proof not implemented"
56
- end
57
-
58
- def find_proof(_txid)
59
- raise NotImplementedError, "#{self.class}#find_proof not implemented"
60
- end
61
-
62
- def store_transaction(_txid, _tx_hex)
63
- raise NotImplementedError, "#{self.class}#store_transaction not implemented"
64
- end
65
-
66
- def find_transaction(_txid)
67
- raise NotImplementedError, "#{self.class}#find_transaction not implemented"
68
- end
69
- end
70
- end
71
- end
@@ -1,126 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module BSV
4
- module Wallet
5
- module Validators
6
- module_function
7
-
8
- # BRC-100 protocol ID rules:
9
- # - Array of [security_level, protocol_name]
10
- # - security_level: Integer 0, 1, or 2
11
- # - protocol_name: 5-400 chars (up to 430 for 'specific linkage revelation' protocol)
12
- # - lowercase letters, numbers, and spaces only
13
- # - no consecutive spaces
14
- # - must not end with ' protocol'
15
- # - must not start with 'admin' (BRC-44)
16
- # - must not start with 'p ' (BRC-98 reserved)
17
- def validate_protocol_id!(protocol_id)
18
- unless protocol_id.is_a?(Array) && protocol_id.length == 2
19
- raise InvalidParameterError.new('protocol_id',
20
- 'an Array of [security_level, protocol_name]')
21
- end
22
-
23
- level, name = protocol_id
24
- raise InvalidParameterError.new('protocol_id security level', '0, 1, or 2') unless [0, 1, 2].include?(level)
25
- raise InvalidParameterError.new('protocol_id name', 'a String') unless name.is_a?(String)
26
-
27
- max_length = name.start_with?('specific linkage revelation') ? 430 : 400
28
- raise InvalidParameterError.new('protocol_id name', "between 5 and #{max_length} characters") if name.length < 5 || name.length > max_length
29
- raise InvalidParameterError.new('protocol_id name', 'lowercase letters, numbers, and spaces only') unless name.match?(/\A[a-z0-9 ]+\z/)
30
- raise InvalidParameterError.new('protocol_id name', 'free of consecutive spaces') if name.include?(' ')
31
- raise InvalidParameterError.new('protocol_id name', 'not ending with " protocol"') if name.end_with?(' protocol')
32
- raise InvalidParameterError.new('protocol_id name', 'not starting with "admin"') if name.start_with?('admin')
33
- raise InvalidParameterError.new('protocol_id name', 'not starting with "p "') if name.start_with?('p ')
34
- end
35
-
36
- # Key ID: 1-800 bytes
37
- def validate_key_id!(key_id)
38
- raise InvalidParameterError.new('key_id', 'a String') unless key_id.is_a?(String)
39
-
40
- byte_length = key_id.bytesize
41
- raise InvalidParameterError.new('key_id', 'between 1 and 800 bytes') if byte_length < 1 || byte_length > 800
42
- end
43
-
44
- # Counterparty: 'self', 'anyone', or 66-char hex (compressed pubkey)
45
- def validate_counterparty!(counterparty)
46
- return if %w[self anyone].include?(counterparty)
47
-
48
- validate_pub_key_hex!(counterparty, 'counterparty')
49
- end
50
-
51
- # Description: 5-50 characters
52
- def validate_description!(description, name = 'description')
53
- raise InvalidParameterError.new(name, 'a String') unless description.is_a?(String)
54
- raise InvalidParameterError.new(name, 'between 5 and 50 characters') if description.length < 5 || description.length > 50
55
- end
56
-
57
- # Basket name rules (BRC-99):
58
- # - 5-300 chars
59
- # - lowercase letters, numbers, and spaces only
60
- # - no consecutive spaces
61
- # - must not end with ' basket'
62
- # - must not start with 'admin'
63
- # - must not be 'default'
64
- # - must not start with 'p ' (BRC-99 reserved)
65
- def validate_basket!(basket)
66
- raise InvalidParameterError.new('basket', 'a String') unless basket.is_a?(String)
67
- raise InvalidParameterError.new('basket', 'between 5 and 300 characters') if basket.length < 5 || basket.length > 300
68
- raise InvalidParameterError.new('basket', 'lowercase letters, numbers, and spaces only') unless basket.match?(/\A[a-z0-9 ]+\z/)
69
- raise InvalidParameterError.new('basket', 'free of consecutive spaces') if basket.include?(' ')
70
- raise InvalidParameterError.new('basket', 'not ending with " basket"') if basket.end_with?(' basket')
71
- raise InvalidParameterError.new('basket', 'not starting with "admin"') if basket.start_with?('admin')
72
- raise InvalidParameterError.new('basket', 'not equal to "default"') if basket == 'default'
73
- raise InvalidParameterError.new('basket', 'not starting with "p "') if basket.start_with?('p ')
74
- end
75
-
76
- # Label: 1-300 characters
77
- def validate_label!(label)
78
- raise InvalidParameterError.new('label', 'a String') unless label.is_a?(String)
79
- raise InvalidParameterError.new('label', 'between 1 and 300 characters') if label.empty? || label.length > 300
80
- end
81
-
82
- # Tag: 1-300 characters
83
- def validate_tag!(tag)
84
- raise InvalidParameterError.new('tag', 'a String') unless tag.is_a?(String)
85
- raise InvalidParameterError.new('tag', 'between 1 and 300 characters') if tag.empty? || tag.length > 300
86
- end
87
-
88
- # Outpoint: "<64-hex-txid>.<non-negative-integer>"
89
- def validate_outpoint!(outpoint)
90
- raise InvalidParameterError.new('outpoint', 'a String') unless outpoint.is_a?(String)
91
-
92
- parts = outpoint.split('.')
93
- raise InvalidParameterError.new('outpoint', 'in format "<txid>.<index>"') unless parts.length == 2
94
-
95
- txid, index = parts
96
- raise InvalidParameterError.new('outpoint txid', 'a 64-character hex string') unless txid.match?(/\A[0-9a-f]{64}\z/)
97
- raise InvalidParameterError.new('outpoint index', 'a non-negative integer') unless index.match?(/\A\d+\z/)
98
- end
99
-
100
- # Satoshis: 1 to 2_100_000_000_000_000
101
- def validate_satoshis!(value, name = 'satoshis')
102
- raise InvalidParameterError.new(name, 'an Integer') unless value.is_a?(Integer)
103
- raise InvalidParameterError.new(name, 'between 1 and 2100000000000000') if value < 1 || value > 2_100_000_000_000_000
104
- end
105
-
106
- # Compressed public key hex: exactly 66 hex characters
107
- def validate_pub_key_hex!(value, name = 'public_key')
108
- raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
109
- raise InvalidParameterError.new(name, 'a 66-character hex string (compressed public key)') unless value.match?(/\A[0-9a-f]{66}\z/)
110
- end
111
-
112
- # Hex string: even-length hex characters only
113
- def validate_hex_string!(value, name = 'hex_string')
114
- raise InvalidParameterError.new(name, 'a String') unless value.is_a?(String)
115
- raise InvalidParameterError.new(name, 'a valid hex string') unless value.match?(/\A[0-9a-f]*\z/) && value.length.even?
116
- end
117
-
118
- # Integer within bounds
119
- def validate_integer!(value, name, min: nil, max: nil)
120
- raise InvalidParameterError.new(name, 'an Integer') unless value.is_a?(Integer)
121
- raise InvalidParameterError.new(name, "at least #{min}") if min && value < min
122
- raise InvalidParameterError.new(name, "at most #{max}") if max && value > max
123
- end
124
- end
125
- end
126
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module BSV
4
- module WalletInterface
5
- VERSION = '0.3.0'
6
- end
7
- end