Wallet¶
BSV::Wallet::ProtoWallet is the SDK's built-in cryptographic wallet. It is a concrete implementation of the BRC-100 interface — not a sample or duck type — providing signing, encryption, HMAC, and key derivation with no transactions, storage, or blockchain interaction.
Use ProtoWallet when you need BRC-100 crypto operations without a full wallet stack. For transaction creation, UTXO management, and blockchain interaction, use the standalone bsv-wallet gem, which builds on top of ProtoWallet's crypto layer.
When to use ProtoWallet¶
| Scenario | Use |
|---|---|
| Auth module integration (BRC-31/103) | ProtoWallet |
| Pure crypto operations (signing, encryption, HMAC) | ProtoWallet |
| Testing wallet-aware code in isolation | ProtoWallet |
| Transaction creation and signing | bsv-wallet |
| UTXO management and baskets | bsv-wallet |
| Blockchain queries | bsv-wallet |
Constructor¶
require 'bsv-sdk'
# Authenticated wallet — provide a PrivateKey
key = BSV::Primitives::PrivateKey.generate
wallet = BSV::Wallet::ProtoWallet.new(key)
# Unauthenticated wallet — the 'anyone' root key (private key scalar = 1)
wallet = BSV::Wallet::ProtoWallet.new('anyone')
The 'anyone' root key derives keys from a fixed scalar (1). It is not a security boundary — anyone can reconstruct the same keys. Use it when no identity is required and the protocol explicitly calls for unauthenticated derivation.
Key concepts¶
protocol_id¶
Operations are namespaced by a two-element array: [security_level, protocol_name].
security_level— integer0,1, or2protocol_name— string of lowercase letters, numbers, and spaces (5–400 characters)
Example: [0, 'auth message'], [2, 'invoice payment'].
key_id¶
A per-operation string identifier (1–800 bytes). Combined with protocol_id and counterparty, it produces a unique derived key per interaction.
counterparty¶
Specifies whose public key anchors the derivation:
| Value | Meaning |
|---|---|
'self' | Own identity key (self-directed derivation) |
'anyone' | Fixed public key (no counterparty, public knowledge) |
"02abc..." | A specific counterparty's compressed public key (66 hex chars) |
Counterparty defaults differ by operation — see the method tables below.
for_self¶
When true, derives the key from your own identity looking toward the counterparty, rather than deriving from the counterparty's perspective. Used in get_public_key and verify_signature to retrieve your own derived signing key's public form.
BRC-100 coverage¶
Supported operations (12)¶
| Method | Area | Default counterparty | Notes |
|---|---|---|---|
get_public_key | Key management | 'self' | Pass identity_key: true to get the root identity key |
encrypt | Cryptography | 'self' | AES-256-GCM with ECDH-derived key |
decrypt | Cryptography | 'self' | AES-256-GCM with ECDH-derived key |
create_hmac | Cryptography | 'self' | HMAC-SHA256 with derived symmetric key |
verify_hmac | Cryptography | 'self' | Raises InvalidHmacError on mismatch |
create_signature | Cryptography | 'anyone' | ECDSA, DER-encoded; accepts pre-computed hash |
verify_signature | Cryptography | 'self' | Raises InvalidSignatureError on failure |
reveal_counterparty_key_linkage | Key management | — | BRC-69 Method 1; counterparty must be a hex pubkey |
reveal_specific_key_linkage | Key management | — | BRC-69 Method 2; counterparty must be a hex pubkey |
list_certificates | Certificates | — | Always returns { certificates: [] } — no storage |
prove_certificate | Certificates | — | Always raises UnsupportedActionError |
Unsupported operations (17)¶
These methods raise NotImplementedError. Use bsv-wallet for full support.
| Method | Area |
|---|---|
create_action | Transactions |
sign_action | Transactions |
abort_action | Transactions |
list_actions | Transactions |
internalize_action | Transactions |
list_outputs | Outputs |
relinquish_output | Outputs |
acquire_certificate | Certificates |
relinquish_certificate | Certificates |
discover_by_identity_key | Identity discovery |
discover_by_attributes | Identity discovery |
authenticated? | Authentication |
wait_for_authentication | Authentication |
get_height | Blockchain |
get_header_for_height | Blockchain |
get_network | Blockchain |
get_version | Blockchain |
Usage examples¶
Identity key¶
key = BSV::Primitives::PrivateKey.generate
wallet = BSV::Wallet::ProtoWallet.new(key)
result = wallet.get_public_key(identity_key: true)
puts result[:public_key] #=> "02abc..." (66-char compressed pubkey hex)
Signing and verification¶
key = BSV::Primitives::PrivateKey.generate
wallet = BSV::Wallet::ProtoWallet.new(key)
data = 'hello world'.bytes
# Sign (counterparty defaults to 'anyone' for signatures)
sig_result = wallet.create_signature(
data: data,
protocol_id: [0, 'my protocol'],
key_id: 'message-001'
)
# Verify — for_self: true retrieves the signer's own derived public key
wallet.verify_signature(
signature: sig_result[:signature],
data: data,
protocol_id: [0, 'my protocol'],
key_id: 'message-001',
for_self: true
)
#=> { valid: true }
To verify a signature produced by another party, pass their public key as counterparty:
their_wallet = BSV::Wallet::ProtoWallet.new(BSV::Primitives::PrivateKey.generate)
their_pubkey = their_wallet.get_public_key(identity_key: true)[:public_key]
sig_result = their_wallet.create_signature(
data: data,
protocol_id: [0, 'my protocol'],
key_id: 'message-001',
counterparty: wallet.get_public_key(identity_key: true)[:public_key]
)
wallet.verify_signature(
signature: sig_result[:signature],
data: data,
protocol_id: [0, 'my protocol'],
key_id: 'message-001',
counterparty: their_pubkey
)
#=> { valid: true }
Encryption and decryption¶
alice = BSV::Wallet::ProtoWallet.new(BSV::Primitives::PrivateKey.generate)
bob = BSV::Wallet::ProtoWallet.new(BSV::Primitives::PrivateKey.generate)
bob_pubkey = bob.get_public_key(identity_key: true)[:public_key]
alice_pubkey = alice.get_public_key(identity_key: true)[:public_key]
# Alice encrypts for Bob
ciphertext = alice.encrypt(
plaintext: 'secret message'.bytes,
protocol_id: [0, 'secure chat'],
key_id: 'msg-001',
counterparty: bob_pubkey
)[:ciphertext]
# Bob decrypts from Alice
plaintext = bob.decrypt(
ciphertext: ciphertext,
protocol_id: [0, 'secure chat'],
key_id: 'msg-001',
counterparty: alice_pubkey
)[:plaintext]
plaintext.pack('C*') #=> "secret message"
HMAC round-trip¶
wallet = BSV::Wallet::ProtoWallet.new(BSV::Primitives::PrivateKey.generate)
data = 'data to authenticate'.bytes
hmac = wallet.create_hmac(
data: data,
protocol_id: [0, 'data integrity'],
key_id: 'record-42'
)[:hmac]
# Returns { valid: true } or raises InvalidHmacError
wallet.verify_hmac(
data: data,
hmac: hmac,
protocol_id: [0, 'data integrity'],
key_id: 'record-42'
)
Error handling¶
| Exception | Raised when |
|---|---|
BSV::Wallet::InvalidParameterError | A parameter fails validation (bad protocol_id, key_id, or counterparty) |
BSV::Wallet::InvalidHmacError | verify_hmac — HMAC does not match |
BSV::Wallet::InvalidSignatureError | verify_signature — signature does not verify |
BSV::Wallet::UnsupportedActionError | prove_certificate — always raised |
NotImplementedError | Any of the 17 unsupported BRC-100 methods |
All wallet errors inherit from BSV::Wallet::Error and carry a machine-readable code matching the BRC-100 error structure.
Relationship to bsv-wallet¶
ProtoWallet is the cryptographic foundation. The bsv-wallet gem implements the remaining 17 BRC-100 methods by adding transaction construction, UTXO storage, basket management, and blockchain queries — all built on top of ProtoWallet's key derivation and crypto operations.
ProtoWallet — 12 BRC-100 methods (crypto only, in bsv-sdk)
└── bsv-wallet — all 28 BRC-100 methods (+ transactions, storage, blockchain)
If your use case only requires signing, encryption, or HMAC — for example, implementing the Auth module or a message authentication layer — ProtoWallet is sufficient and keeps your dependency footprint small.
Wire layer¶
The BRC-103 wire layer lets you call any BRC-100 wallet over a binary transport. The SDK provides client, server, and HTTP substrate classes, all in the BSV::Wallet namespace.
For a full walkthrough with examples, see the BRC-103 wire layer guide.
WalletWire¶
BSV::Wallet::WalletWire is the transport abstraction — a module with a single method:
Include it in any class and implement transmit_to_wallet to produce a concrete transport.
WalletWireTransceiver¶
The client. Wraps any WalletWire and exposes the full BRC-100 interface as Ruby methods. Every call serialises its keyword arguments to a binary request frame, transmits via the wire, and deserialises the binary result frame back to a Ruby hash.
# @param wire [#transmit_to_wallet] any WalletWire implementation
client = BSV::Wallet::WalletWireTransceiver.new(wire)
result = client.get_public_key(identity_key: true)
# result[:public_key] is a 33-byte compressed pubkey as a binary string.
# To inspect it as hex: result[:public_key].unpack1('H*') # => "0279be..."
WalletWireProcessor¶
The server. Wraps any Interface::BRC100 wallet and processes binary request frames, returning binary result frames. It also includes WalletWire, so it can be passed directly to a WalletWireTransceiver for in-process loopback.
# @param wallet [Interface::BRC100] any BRC-100 wallet
processor = BSV::Wallet::WalletWireProcessor.new(proto_wallet)
Error boundary: NotImplementedError → code 2 (UnsupportedActionError); BSV::Wallet::Error subclasses → framed with their code; any other StandardError → code 1.
Substrates::HTTPWalletWire¶
HTTP transport using binary frames (Content-Type: application/octet-stream). Posts to {base_url}/wallet.
# Direct construction
wire = BSV::Wallet::Substrates::HTTPWalletWire.new(base_url: 'https://wallet.example')
client = BSV::Wallet::WalletWireTransceiver.new(wire)
# Convenience factory — returns a WalletWireTransceiver directly
client = BSV::Wallet::Substrates::HTTPWalletWire.client(base_url: 'https://wallet.example')
# With extra headers (e.g. auth)
client = BSV::Wallet::Substrates::HTTPWalletWire.client(
base_url: 'https://wallet.example',
headers: { 'Authorization' => 'Bearer token' }
)
Substrates::HTTPWalletJSON¶
JSON-over-HTTP substrate. Directly implements Interface::BRC100 — does not use the binary frame codec. Posts to {base_url}/v1/wallet/{camelCaseMethodName} with Content-Type: application/json. Request and response hashes are automatically converted between Ruby snake_case and camelCase.
client = BSV::Wallet::Substrates::HTTPWalletJSON.new(
base_url: 'https://wallet.example.com'
)
client.get_network # => { network: 'mainnet' }
Wire layer errors¶
All wallet errors inherit from BSV::Wallet::Error and carry a numeric code. When the server returns an error frame, the client raises the matching subclass automatically.
| Class | Code | Raised when |
|---|---|---|
BSV::Wallet::Error | 1 | Generic operation failure; network errors on the client side |
BSV::Wallet::UnsupportedActionError | 2 | Method not supported by this wallet implementation (NotImplementedError) |
BSV::Wallet::InvalidHmacError | 3 | verify_hmac — HMAC does not match |
BSV::Wallet::InvalidSignatureError | 4 | verify_signature — signature does not verify |
BSV::Wallet::InsufficientFundsError | 5 | Wallet has insufficient funds for the operation |
BSV::Wallet::InvalidParameterError | 6 | A required parameter is missing or invalid |
BSV::Wallet::ReviewActionsError | 7 | Actions require user review before processing |
Use BSV::Wallet.error_from_wire(code, message, stack) to manually rehydrate an error frame into the correct subclass. The transceiver calls this automatically when it reads an error frame.