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 provides signing, encryption, HMAC, and key derivation without transactions, storage, or blockchain interaction. It is the direct implementation of the BRC-100 crypto methods, not a delegating client. This makes it suitable for use in the SDK’s Auth module without depending on the bsv-wallet gem.

Includes BSV::Wallet::Interface::BRC100 — methods it supports are overridden; unsupported methods (transactions, blockchain, authentication) fall through to NotImplementedError.

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:



25
26
27
# File 'lib/bsv/wallet/proto_wallet.rb', line 25

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> }



89
90
91
92
93
94
95
# File 'lib/bsv/wallet/proto_wallet.rb', line 89

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



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/bsv/wallet/proto_wallet.rb', line 126

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'
  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> }



74
75
76
77
78
79
80
# File 'lib/bsv/wallet/proto_wallet.rb', line 74

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> }



59
60
61
62
63
64
65
# File 'lib/bsv/wallet/proto_wallet.rb', line 59

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



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/bsv/wallet/proto_wallet.rb', line 37

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: [] }



280
281
282
283
# File 'lib/bsv/wallet/proto_wallet.rb', line 280

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:



288
289
290
291
# File 'lib/bsv/wallet/proto_wallet.rb', line 288

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:



184
185
186
187
188
189
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
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/bsv/wallet/proto_wallet.rb', line 184

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:



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
272
273
# File 'lib/bsv/wallet/proto_wallet.rb', line 239

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:



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

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:



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/bsv/wallet/proto_wallet.rb', line 153

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'

  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)

  raise InvalidSignatureError unless valid

  { valid: true }
end