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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +406 -146
- data/lib/bsv/identity/client.rb +11 -5
- data/lib/bsv/overlay/topic_broadcaster.rb +1 -3
- data/lib/bsv/primitives/openssl_ec_shim.rb +8 -4
- data/lib/bsv/primitives/secp256k1.rb +11 -1
- data/lib/bsv/transaction/beef.rb +45 -38
- data/lib/bsv/transaction/merkle_path.rb +64 -0
- data/lib/bsv/transaction/transaction.rb +3 -2
- data/lib/bsv/version.rb +1 -1
- metadata +2 -32
- data/lib/bsv/attest/configuration.rb +0 -9
- data/lib/bsv/attest/response.rb +0 -19
- data/lib/bsv/attest/verification_error.rb +0 -7
- data/lib/bsv/attest/version.rb +0 -7
- data/lib/bsv/attest.rb +0 -71
- data/lib/bsv/wallet_interface/chain_provider.rb +0 -37
- data/lib/bsv/wallet_interface/errors/invalid_hmac_error.rb +0 -11
- data/lib/bsv/wallet_interface/errors/invalid_parameter_error.rb +0 -14
- data/lib/bsv/wallet_interface/errors/invalid_signature_error.rb +0 -11
- data/lib/bsv/wallet_interface/errors/unsupported_action_error.rb +0 -11
- data/lib/bsv/wallet_interface/errors/wallet_error.rb +0 -14
- data/lib/bsv/wallet_interface/file_store.rb +0 -222
- data/lib/bsv/wallet_interface/interface.rb +0 -384
- data/lib/bsv/wallet_interface/key_deriver.rb +0 -144
- data/lib/bsv/wallet_interface/local_proof_store.rb +0 -42
- data/lib/bsv/wallet_interface/memory_store.rb +0 -149
- data/lib/bsv/wallet_interface/null_chain_provider.rb +0 -22
- data/lib/bsv/wallet_interface/proof_store.rb +0 -32
- data/lib/bsv/wallet_interface/proto_wallet.rb +0 -361
- data/lib/bsv/wallet_interface/storage_adapter.rb +0 -71
- data/lib/bsv/wallet_interface/validators.rb +0 -126
- data/lib/bsv/wallet_interface/version.rb +0 -7
- data/lib/bsv/wallet_interface/wallet_client.rb +0 -874
- data/lib/bsv/wallet_interface/wire/reader.rb +0 -238
- data/lib/bsv/wallet_interface/wire/serializer.rb +0 -1993
- data/lib/bsv/wallet_interface/wire/writer.rb +0 -214
- data/lib/bsv/wallet_interface/wire.rb +0 -19
- data/lib/bsv/wallet_interface.rb +0 -31
- data/lib/bsv-attest.rb +0 -4
- 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
|