Primitives¶
The BSV::Primitives module provides cryptographic building blocks: keys, signatures, hashing, encryption, and HD key derivation.
Private Keys¶
Generating and Importing¶
# Generate a new random key
key = BSV::Primitives::PrivateKey.generate
# Import from WIF
key = BSV::Primitives::PrivateKey.from_wif('L56Q9sRtBaL...')
# Import from hex (32-byte scalar)
key = BSV::Primitives::PrivateKey.from_hex('eaf02ca348c524e6...')
# Import from raw bytes
key = BSV::Primitives::PrivateKey.from_bytes(bytes)
Exporting¶
key.to_wif # mainnet, compressed
key.to_wif(compressed: false) # mainnet, uncompressed
key.to_wif(network: :testnet) # testnet
key.to_hex # 64-char hex string
key.to_bytes # 32-byte binary
Public Keys¶
Public keys are derived from private keys. They're cached — calling private_key.public_key multiple times returns the same object.
pubkey = private_key.public_key
# Serialisation
pubkey.compressed # 33 bytes (02/03 prefix)
pubkey.uncompressed # 65 bytes (04 prefix)
pubkey.to_hex # compressed hex (default)
pubkey.to_hex(compressed: false)
# Hash160 (RIPEMD-160 of SHA-256)
pubkey.hash160 # 20 bytes
# Address (Base58Check-encoded Hash160)
pubkey.address # mainnet
pubkey.address(network: :testnet)
Importing Public Keys¶
pubkey = BSV::Primitives::PublicKey.from_hex('025ceeba2ab4a635...')
pubkey = BSV::Primitives::PublicKey.from_bytes(bytes)
Signing and Verification¶
Signatures use deterministic ECDSA (RFC 6979) on the secp256k1 curve.
# Sign a 32-byte hash
hash = BSV::Primitives::Digest.sha256('message')
signature = private_key.sign(hash)
# DER-encoded signature
der = signature.to_der
# Verify
valid = private_key.public_key.verify(hash, signature)
Low-Level ECDSA¶
For recoverable signatures and public key recovery:
# Sign with recovery ID
sig, recovery_id = BSV::Primitives::ECDSA.sign_recoverable(hash, private_key)
# Recover public key from signature
pubkey = BSV::Primitives::ECDSA.recover_public_key(hash, signature, recovery_id)
Bitcoin Signed Messages (BSM)¶
Sign and verify messages with the standard Bitcoin message format:
# Sign — returns base64-encoded 65-byte compact signature
sig_b64 = BSV::Primitives::BSM.sign('Hello BSV', private_key)
# Verify — recovers the public key and checks against the address
address = private_key.public_key.address
valid = BSV::Primitives::BSM.verify('Hello BSV', sig_b64, address)
ECIES Encryption¶
Encrypt data to a recipient's public key using the BIE1 (Electrum-compatible ECIES) format:
recipient = BSV::Primitives::PrivateKey.generate
# Encrypt (ephemeral sender key)
ciphertext = BSV::Primitives::ECIES.encrypt(
'secret data',
recipient.public_key
)
# Encrypt with known sender (deterministic shared secret)
ciphertext = BSV::Primitives::ECIES.encrypt(
'secret data',
recipient.public_key,
private_key: sender_key
)
# Decrypt
plaintext = BSV::Primitives::ECIES.decrypt(ciphertext, recipient)
The ciphertext format is: BIE1 magic (4 bytes) + ephemeral public key (33 bytes) + AES-256-CBC encrypted data + HMAC-SHA256 (32 bytes).
Hashing¶
The Digest module provides all hash functions used in Bitcoin:
BSV::Primitives::Digest.sha256(data) # SHA-256
BSV::Primitives::Digest.sha256d(data) # double SHA-256
BSV::Primitives::Digest.ripemd160(data) # RIPEMD-160
BSV::Primitives::Digest.hash160(data) # RIPEMD-160(SHA-256(data))
BSV::Primitives::Digest.sha1(data) # SHA-1
BSV::Primitives::Digest.sha512(data) # SHA-512
# HMAC variants
BSV::Primitives::Digest.hmac_sha256(key, data)
BSV::Primitives::Digest.hmac_sha512(key, data)
# PBKDF2 (used by BIP-39)
BSV::Primitives::Digest.pbkdf2_hmac_sha512(password, salt, iterations, length)
HD Keys (BIP-32)¶
Hierarchical Deterministic keys allow deriving an entire tree of key pairs from a single seed.
From Seed¶
seed = ['000102030405060708090a0b0c0d0e0f'].pack('H*')
master = BSV::Primitives::ExtendedKey.from_seed(seed)
master = BSV::Primitives::ExtendedKey.from_seed(seed, network: :testnet)
Derivation¶
# Single child
child = master.child(0) # normal child 0
hardened = master.child(0x80000000) # hardened child 0'
# Path derivation (m/44'/0'/0'/0/0)
key = master.derive_path("m/44'/0'/0'/0/0")
# Alternate syntax: H instead of '
key = master.derive_path("m/44H/0H/0H/0/0")
Public vs Private Extended Keys¶
# Convert to public-only (neutering)
xpub = master.neuter
# Check type
master.private? #=> true
xpub.private? #=> false
# Access underlying keys
master.private_key #=> BSV::Primitives::PrivateKey
xpub.public_key #=> BSV::Primitives::PublicKey
Serialisation¶
# Extended key strings (xprv/xpub/tprv/tpub)
master.to_s #=> "xprv9s21ZrQH143K..."
xpub.to_s #=> "xpub661MyMwAqRbc..."
# Import
key = BSV::Primitives::ExtendedKey.from_string('xprv9s21ZrQH143K...')
Key Metadata¶
key.depth # derivation depth (0 for master)
key.child_number # child index
key.fingerprint # 4-byte key fingerprint
key.parent_fingerprint # 4-byte parent fingerprint
key.identifier # 20-byte key identifier (Hash160 of public key)
BIP-39 Mnemonics¶
Generate human-readable seed phrases:
# Generate a 12-word mnemonic
mnemonic = BSV::Primitives::Mnemonic.generate
# 24-word mnemonic
mnemonic = BSV::Primitives::Mnemonic.generate(strength: 256)
# Import existing phrase
mnemonic = BSV::Primitives::Mnemonic.from_phrase('abandon abandon ... about')
# Derive seed (64 bytes)
seed = mnemonic.to_seed
seed = mnemonic.to_seed(passphrase: 'optional passphrase')
# Derive HD master key directly
master = mnemonic.to_extended_key