Class: BSV::Wallet::ProtoWallet

Inherits:
Object
  • Object
show all
Includes:
Interface::BRC100
Defined in:
lib/bsv/wallet/proto_wallet.rb,
lib/bsv/wallet/proto_wallet/validators.rb,
lib/bsv/wallet/proto_wallet/key_deriver.rb

Overview

Minimal cryptographic wallet implementing the BRC-100 interface.

ProtoWallet is a direct, in-process implementation of the BRC-100 crypto operations. It requires no external gem, no storage, and no blockchain access — making it suitable for use inside the SDK itself (e.g. the Auth module) as well as lightweight applications that need cryptographic operations without full wallet infrastructure.

Supported BRC-100 areas

NOT supported

The following BRC-100 areas are not implemented and will raise NotImplementedError:

  • Transactions (create_action, sign_action, list_actions, etc.)

  • Output management (list_outputs, relinquish_output, etc.)

  • Authentication (authenticated?, wait_for_authentication)

  • Blockchain / network data (get_height, get_header_for_height, etc.)

  • Certificate acquisition / discovery (acquire_certificate, discover_by_identity_key, etc.)

For full wallet functionality, use the bsv-wallet gem. See docs/sdk/wallet.md for usage guidance.

Construction

Pass a Primitives::PrivateKey or the special string 'anyone'. The 'anyone' variant uses a well-known key (private key = 1) and is suitable for verifying or encrypting data that should be readable by any party — it must not be used where secrecy is required.

Examples:

Normal usage

wallet = BSV::Wallet::ProtoWallet.new(BSV::Primitives::PrivateKey.generate)
sig = wallet.create_signature(protocol_id: [1, 'my-app'], key_id: 'msg-1',
                              data: 'hello'.bytes)

Anyone wallet

wallet = BSV::Wallet::ProtoWallet.new('anyone')
pub = wallet.get_public_key(identity_key: true)

Defined Under Namespace

Modules: Validators Classes: KeyDeriver

Instance Method Summary collapse

Methods included from Interface::BRC100

#abort_action, #acquire_certificate, #authenticated?, #create_action, #discover_by_attributes, #discover_by_identity_key, #get_header_for_height, #get_height, #get_network, #get_version, #internalize_action, #list_actions, #list_outputs, #relinquish_certificate, #relinquish_output, #sign_action, #wait_for_authentication

Constructor Details

#initialize(root_key) ⇒ ProtoWallet

Returns a new instance of ProtoWallet.

Parameters:



61
62
63
# File 'lib/bsv/wallet/proto_wallet.rb', line 61

def initialize(root_key)
  @key_deriver = KeyDeriver.new(root_key)
end

Instance Method Details

#create_hmac(data:, protocol_id:, key_id:, counterparty: nil, privileged: false, privileged_reason: nil, seek_permission: true, originator: nil) ⇒ Hash

Creates an HMAC-SHA256 using a derived symmetric key.

Parameters:

  • data (Array<Integer>)

    byte array to authenticate

  • protocol_id (Array)
    security_level, protocol_name
  • key_id (String)

    key identifier

  • counterparty (String) (defaults to: nil)

    pubkey hex, ‘self’, or ‘anyone’

Returns:

  • (Hash)

    { hmac: Array<Integer> }



125
126
127
128
129
130
131
# File 'lib/bsv/wallet/proto_wallet.rb', line 125

def create_hmac(data:, protocol_id:, key_id:,
                counterparty: nil, privileged: false, privileged_reason: nil,
                seek_permission: true, originator: nil)
  sym_key = derive_sym_key(protocol_id, key_id, counterparty)
  hmac    = BSV::Primitives::Digest.hmac_sha256(sym_key.to_bytes, bytes_to_string(data))
  { hmac: string_to_bytes(hmac) }
end

#create_signature(protocol_id:, key_id:, data: nil, hash_to_directly_sign: nil, counterparty: nil, privileged: false, privileged_reason: nil, seek_permission: true, originator: nil) ⇒ Hash

Creates an ECDSA signature using a derived private key.

Parameters:

  • protocol_id (Array)
    security_level, protocol_name
  • key_id (String)

    key identifier

  • data (Array<Integer>) (defaults to: nil)

    data to hash and sign

  • hash_to_directly_sign (Array<Integer>) (defaults to: nil)

    pre-computed 32-byte hash

  • counterparty (String) (defaults to: nil)

    pubkey hex, ‘self’, or ‘anyone’

Returns:

  • (Hash)

    { signature: Array<Integer> } DER-encoded signature as byte array



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/bsv/wallet/proto_wallet.rb', line 162

def create_signature(protocol_id:, key_id:, data: nil, hash_to_directly_sign: nil,
                     counterparty: nil, privileged: false, privileged_reason: nil,
                     seek_permission: true, originator: nil)
  counterparty ||= 'anyone'
  BSV.logger&.debug { "[ProtoWallet] create_signature: protocol=#{protocol_id} key_id=#{key_id.inspect} counterparty=#{counterparty}" }
  priv_key = @key_deriver.derive_private_key(protocol_id, key_id, counterparty)

  hash = if hash_to_directly_sign
           bytes_to_string(hash_to_directly_sign)
         else
           BSV::Primitives::Digest.sha256(bytes_to_string(data))
         end

  sig = priv_key.sign(hash)
  { signature: string_to_bytes(sig.to_der) }
end

#decrypt(ciphertext:, protocol_id:, key_id:, counterparty: nil, privileged: false, privileged_reason: nil, seek_permission: true, originator: nil) ⇒ Hash

Decrypts ciphertext using AES-256-GCM with a derived symmetric key.

Parameters:

  • ciphertext (Array<Integer>)

    byte array to decrypt

  • protocol_id (Array)
    security_level, protocol_name
  • key_id (String)

    key identifier

  • counterparty (String) (defaults to: nil)

    pubkey hex, ‘self’, or ‘anyone’

Returns:

  • (Hash)

    { plaintext: Array<Integer> }



110
111
112
113
114
115
116
# File 'lib/bsv/wallet/proto_wallet.rb', line 110

def decrypt(ciphertext:, protocol_id:, key_id:,
            counterparty: nil, privileged: false, privileged_reason: nil,
            seek_permission: true, originator: nil)
  sym_key   = derive_sym_key(protocol_id, key_id, counterparty)
  plaintext = sym_key.decrypt(bytes_to_string(ciphertext))
  { plaintext: string_to_bytes(plaintext) }
end

#encrypt(plaintext:, protocol_id:, key_id:, counterparty: nil, privileged: false, privileged_reason: nil, seek_permission: true, originator: nil) ⇒ Hash

Encrypts plaintext using AES-256-GCM with a derived symmetric key.

Parameters:

  • plaintext (Array<Integer>)

    byte array to encrypt

  • protocol_id (Array)
    security_level, protocol_name
  • key_id (String)

    key identifier

  • counterparty (String) (defaults to: nil)

    pubkey hex, ‘self’, or ‘anyone’

Returns:

  • (Hash)

    { ciphertext: Array<Integer> }



95
96
97
98
99
100
101
# File 'lib/bsv/wallet/proto_wallet.rb', line 95

def encrypt(plaintext:, protocol_id:, key_id:,
            counterparty: nil, privileged: false, privileged_reason: nil,
            seek_permission: true, originator: nil)
  sym_key    = derive_sym_key(protocol_id, key_id, counterparty)
  ciphertext = sym_key.encrypt(bytes_to_string(plaintext))
  { ciphertext: string_to_bytes(ciphertext) }
end

#get_public_key(identity_key: false, protocol_id: nil, key_id: nil, counterparty: nil, for_self: false, privileged: false, privileged_reason: nil, seek_permission: true, originator: nil) ⇒ Hash

Returns a derived or identity public key.

Parameters:

  • identity_key (Boolean) (defaults to: false)

    return the root identity key

  • protocol_id (Array) (defaults to: nil)
    security_level, protocol_name
  • key_id (String) (defaults to: nil)

    key identifier

  • counterparty (String) (defaults to: nil)

    pubkey hex, ‘self’, or ‘anyone’

  • for_self (Boolean) (defaults to: false)

    derive from own identity

Returns:

  • (Hash)

    { public_key: String } hex-encoded compressed public key



73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/bsv/wallet/proto_wallet.rb', line 73

def get_public_key(identity_key: false, protocol_id: nil, key_id: nil,
                   counterparty: nil, for_self: false,
                   privileged: false, privileged_reason: nil,
                   seek_permission: true, originator: nil)
  if identity_key
    { public_key: @key_deriver.identity_key }
  else
    counterparty ||= 'self'
    pub = @key_deriver.derive_public_key(
      protocol_id, key_id, counterparty, for_self: for_self
    )
    { public_key: pub.to_hex }
  end
end

#list_certificates(certifiers: nil, types: nil, limit: 10, offset: 0, privileged: false, privileged_reason: nil, originator: nil) ⇒ Hash

Returns an empty certificate list.

ProtoWallet has no storage, so there are never any certificates.

Returns:

  • (Hash)

    { certificates: [] }



322
323
324
325
# File 'lib/bsv/wallet/proto_wallet.rb', line 322

def list_certificates(certifiers: nil, types: nil, limit: 10, offset: 0,
                      privileged: false, privileged_reason: nil, originator: nil)
  { certificates: [] }
end

#prove_certificate(certificate: nil, fields_to_reveal: nil, verifier: nil, privileged: false, privileged_reason: nil, originator: nil) ⇒ Object

Not supported — ProtoWallet has no certificate storage.

Raises:



330
331
332
333
# File 'lib/bsv/wallet/proto_wallet.rb', line 330

def prove_certificate(certificate: nil, fields_to_reveal: nil, verifier: nil,
                      privileged: false, privileged_reason: nil, originator: nil)
  raise UnsupportedActionError, 'prove_certificate'
end

#reveal_counterparty_key_linkage(counterparty:, verifier:, privileged: false, privileged_reason: nil, originator: nil) ⇒ Hash

Reveals counterparty key linkage to a verifier (BRC-69 Method 1).

Parameters:

  • counterparty (String)

    counterparty public key hex (not ‘self’ or ‘anyone’)

  • verifier (String)

    verifier public key hex

Returns:

  • (Hash)

    { prover:, verifier:, counterparty:, revelation_time:, encrypted_linkage:, encrypted_linkage_proof: }

Raises:



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/bsv/wallet/proto_wallet.rb', line 226

def reveal_counterparty_key_linkage(counterparty:, verifier:,
                                    privileged: false, privileged_reason: nil,
                                    originator: nil)
  raise InvalidParameterError.new('counterparty', 'a specific public key hex, not "anyone"') if counterparty == 'anyone'

  Validators.validate_pub_key_hex!(verifier, 'verifier')

  linkage         = @key_deriver.reveal_counterparty_secret(counterparty)
  revelation_time = Time.now.utc.iso8601

  encrypted_linkage_result = encrypt(
    plaintext: string_to_bytes(linkage),
    protocol_id: [2, 'counterparty linkage revelation'],
    key_id: revelation_time,
    counterparty: verifier
  )

  counterparty_pub = BSV::Primitives::PublicKey.from_hex(counterparty)
  linkage_point    = BSV::Primitives::PublicKey.from_bytes(linkage)
  schnorr_proof    = BSV::Primitives::Schnorr.generate_proof(
    @key_deriver.root_key,
    @key_deriver.root_key.public_key,
    counterparty_pub,
    linkage_point
  )

  z_bytes = schnorr_proof.z.to_s(2)
  z_bytes = ("\x00".b * (32 - z_bytes.length)) + z_bytes if z_bytes.length < 32
  proof_bin = schnorr_proof.r.compressed + schnorr_proof.s_prime.compressed + z_bytes

  encrypted_proof_result = encrypt(
    plaintext: string_to_bytes(proof_bin),
    protocol_id: [2, 'counterparty linkage revelation'],
    key_id: revelation_time,
    counterparty: verifier
  )

  {
    prover: @key_deriver.identity_key,
    verifier: verifier,
    counterparty: counterparty,
    revelation_time: revelation_time,
    encrypted_linkage: encrypted_linkage_result[:ciphertext],
    encrypted_linkage_proof: encrypted_proof_result[:ciphertext]
  }
end

#reveal_specific_key_linkage(counterparty:, verifier:, protocol_id:, key_id:, privileged: false, privileged_reason: nil, originator: nil) ⇒ Hash

Reveals specific key linkage for a particular interaction (BRC-69 Method 2).

Parameters:

  • counterparty (String)

    counterparty public key hex

  • verifier (String)

    verifier public key hex

  • protocol_id (Array)
    security_level, protocol_name
  • key_id (String)

    key identifier

Returns:

  • (Hash)

    { prover:, verifier:, counterparty:, protocol_id:, key_id:, encrypted_linkage:, encrypted_linkage_proof:, proof_type: }

Raises:



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/bsv/wallet/proto_wallet.rb', line 281

def reveal_specific_key_linkage(counterparty:, verifier:, protocol_id:, key_id:,
                                privileged: false, privileged_reason: nil,
                                originator: nil)
  raise InvalidParameterError.new('counterparty', 'a specific public key hex, not "anyone"') if counterparty == 'anyone'

  Validators.validate_pub_key_hex!(verifier, 'verifier')

  linkage          = @key_deriver.reveal_specific_secret(counterparty, protocol_id, key_id)
  derived_protocol = "specific linkage revelation #{protocol_id[0]} #{protocol_id[1]}"

  encrypted_linkage_result = encrypt(
    plaintext: string_to_bytes(linkage),
    protocol_id: [2, derived_protocol],
    key_id: key_id,
    counterparty: verifier
  )

  encrypted_proof_result = encrypt(
    plaintext: [0],
    protocol_id: [2, derived_protocol],
    key_id: key_id,
    counterparty: verifier
  )

  {
    prover: @key_deriver.identity_key,
    verifier: verifier,
    counterparty: counterparty,
    protocol_id: protocol_id,
    key_id: key_id,
    encrypted_linkage: encrypted_linkage_result[:ciphertext],
    encrypted_linkage_proof: encrypted_proof_result[:ciphertext],
    proof_type: 0
  }
end

#verify_hmac(data:, hmac:, protocol_id:, key_id:, counterparty: nil, privileged: false, privileged_reason: nil, seek_permission: true, originator: nil) ⇒ Hash

Verifies an HMAC-SHA256 using a derived symmetric key.

Parameters:

  • data (Array<Integer>)

    byte array that was authenticated

  • hmac (Array<Integer>)

    HMAC to verify

  • protocol_id (Array)
    security_level, protocol_name
  • key_id (String)

    key identifier

  • counterparty (String) (defaults to: nil)

    pubkey hex, ‘self’, or ‘anyone’

Returns:

  • (Hash)

    { valid: true }

Raises:



142
143
144
145
146
147
148
149
150
151
152
# File 'lib/bsv/wallet/proto_wallet.rb', line 142

def verify_hmac(data:, hmac:, protocol_id:, key_id:,
                counterparty: nil, privileged: false, privileged_reason: nil,
                seek_permission: true, originator: nil)
  sym_key  = derive_sym_key(protocol_id, key_id, counterparty)
  expected = BSV::Primitives::Digest.hmac_sha256(sym_key.to_bytes, bytes_to_string(data))
  provided = bytes_to_string(hmac)

  raise InvalidHmacError unless secure_compare(expected, provided)

  { valid: true }
end

#verify_signature(signature:, protocol_id:, key_id:, data: nil, hash_to_directly_verify: nil, counterparty: nil, for_self: false, privileged: false, privileged_reason: nil, seek_permission: true, originator: nil) ⇒ Hash

Verifies an ECDSA signature using a derived public key.

Parameters:

  • signature (Array<Integer>)

    DER-encoded signature as byte array

  • protocol_id (Array)
    security_level, protocol_name
  • key_id (String)

    key identifier

  • data (Array<Integer>) (defaults to: nil)

    original data that was signed

  • hash_to_directly_verify (Array<Integer>) (defaults to: nil)

    pre-computed 32-byte hash

  • counterparty (String) (defaults to: nil)

    pubkey hex, ‘self’, or ‘anyone’

  • for_self (Boolean) (defaults to: false)

    verify own derived key (default false)

Returns:

  • (Hash)

    { valid: true }

Raises:



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/bsv/wallet/proto_wallet.rb', line 190

def verify_signature(signature:, protocol_id:, key_id:, data: nil,
                     hash_to_directly_verify: nil,
                     counterparty: nil, for_self: false,
                     privileged: false, privileged_reason: nil,
                     seek_permission: true, originator: nil)
  counterparty ||= 'self'
  BSV.logger&.debug do
    "[ProtoWallet] verify_signature: protocol=#{protocol_id} key_id=#{key_id.inspect} " \
      "counterparty=#{counterparty} for_self=#{for_self}"
  end

  pub_key = @key_deriver.derive_public_key(
    protocol_id, key_id, counterparty, for_self: for_self
  )

  hash = if hash_to_directly_verify
           bytes_to_string(hash_to_directly_verify)
         else
           BSV::Primitives::Digest.sha256(bytes_to_string(data))
         end

  sig   = BSV::Primitives::Signature.from_der(bytes_to_string(signature))
  valid = pub_key.verify(hash, sig)
  BSV.logger&.debug { "[ProtoWallet] verify_signature result=#{valid}" }

  raise InvalidSignatureError unless valid

  { valid: true }
end