Skip to main content

Overview

This document specifies how Cowboy private keys are stored on disk, encoded for transport, and backed up by users. Cowboy uses a self-describing, checksummed PEM envelope as the primary storage format, BIP-39 mnemonics with BIP-32/44 HD derivation as the human-readable backup and multi-key format, and an encrypted JSON keystore for production deployments. Cowboy uses secp256k1 (ECDSA) for all account keys. This is the same curve used by Ethereum and Bitcoin, which gives Cowboy native Ethereum key compatibility — the same private key can control both a Cowboy account and an EVM address.
Design Principle: Keys should be self-describing, checksummed, and familiar. A developer should be able to cat .cowboy/key and immediately understand what they’re looking at.

Key Format: PEM Envelope

File Format (.cowboy/key)

The primary on-disk format is a PEM-style envelope with base64-encoded key material:
-----BEGIN COWBOY PRIVATE KEY-----
Curve: secp256k1
Checksum: 9a3b
/XJ/awFahf1ANaMQaqjnF4UH0K98RyM0cVhitKetGGY=
-----END COWBOY PRIVATE KEY-----

Structure

+--------------------------------------------------+
|  PEM Envelope                                     |
+--------------------------------------------------+
|  Header:   -----BEGIN COWBOY PRIVATE KEY-----     |
|  Metadata: Curve: secp256k1                       |
|            Checksum: <4 hex chars>                |
|  Body:     <base64-encoded 32-byte key>           |
|  Footer:   -----END COWBOY PRIVATE KEY-----       |
+--------------------------------------------------+

Specification

1

Header and Footer

The file MUST begin with -----BEGIN COWBOY PRIVATE KEY----- and end with -----END COWBOY PRIVATE KEY-----, each on their own line. These markers make the format self-describing and prevent accidental use of non-key files.
2

Metadata Headers

Between the header and the base64 body, zero or more Key: Value metadata lines MAY appear. Defined headers:
HeaderRequiredValuesDescription
CurveYessecp256k1The elliptic curve for this key
ChecksumYes4 hex charactersFirst 4 hex characters of SHA-256(raw_key_bytes)
Unknown headers MUST be ignored by parsers (forward compatibility).
3

Body

A single line of standard base64 (RFC 4648) encoding of the raw 32-byte private key. The line MUST NOT contain whitespace.
4

Trailing Newline

The file SHOULD end with a single trailing newline after the footer. Parsers MUST tolerate missing or extra trailing newlines.

Address Derivation

From a secp256k1 private key, the Cowboy address is derived using the Ethereum-standard method:
  1. Compute the uncompressed public key (64 bytes, without the 04 prefix).
  2. Hash with Keccak-256.
  3. Take the last 20 bytes as the address.
This means a Cowboy address is an Ethereum address — the same key produces the same 0x... address on both chains.

Checksum Calculation

The checksum provides corruption detection:
import hashlib, base64

raw_key = base64.b64decode(body_line)          # 32 bytes
digest  = hashlib.sha256(raw_key).hexdigest()  # 64 hex chars
checksum = digest[:4]                          # first 4 hex chars
On load, the CLI:
  1. Decodes the base64 body to get the raw key bytes.
  2. Computes SHA-256(raw_key_bytes).
  3. Compares the first 4 hex characters against the Checksum header.
  4. If they don’t match, prints an error and refuses to use the key.
Four hex characters (16 bits) catch 99.998% of random corruptions — sufficient for a local file that is rarely edited by hand.

Parsing Rules

parse(file_contents):
  1. Strip leading/trailing whitespace from each line
  2. Verify BEGIN header is present
  3. Read Key: Value metadata lines until a non-header line
  4. Read base64 body (single line)
  5. Verify END footer is present
  6. base64-decode body → 32 bytes
  7. Verify checksum if present
  8. Return (curve, raw_key_bytes)

Example: Full Round-Trip

# Generate a new key
$ cowboy wallet create
Created wallet:
  Key file:  .cowboy/key
  Curve:     secp256k1
  Address:   0x7a3b...f92e
  Checksum:  9a3b

# Inspect the file
$ cat .cowboy/key
-----BEGIN COWBOY PRIVATE KEY-----
Curve: secp256k1
Checksum: 9a3b
/XJ/awFahf1ANaMQaqjnF4UH0K98RyM0cVhitKetGGY=
-----END COWBOY PRIVATE KEY-----

# The CLI auto-discovers and validates on use
$ cowboy wallet address
0x7a3b...f92e

Backup & Recovery: BIP-39 Mnemonic

For human-readable backup and recovery, Cowboy uses BIP-39 mnemonics as the seed format. Combined with BIP-32/44 hierarchical deterministic (HD) derivation, a single 24-word mnemonic can derive an unlimited number of Cowboy keys.

Export

$ cowboy wallet export --mnemonic
 Write these 24 words down and store them safely.
   Anyone with these words can control all derived accounts.

   1. ranch     2. erosion   3. timber    4. hollow
   5. creek     6. valley    7. blanket   8. autumn
   9. pepper   10. curious  11. gadget   12. fortune
  13. ivory    14. canyon    15. submit   16. bachelor
  17. unveil   18. liberty   19. siege    20. deputy
  21. harbor   22. toast    23. enforce   24. antenna

Import

$ cowboy wallet import --mnemonic
Enter 24-word mnemonic: ranch erosion timber hollow ...
Imported wallet:
  Key file:  .cowboy/key
  Curve:     secp256k1
  Address:   0x7a3b...f92e  (account 0)

Mnemonic Specification

The mnemonic is generated from 256 bits of cryptographically random entropy:
  1. Generate 32 bytes (256 bits) of random entropy.
  2. Compute SHA-256 of the entropy; take the first 8 bits as a checksum.
  3. Concatenate: 256 bits of entropy + 8 bits of checksum = 264 bits.
  4. Split into 24 groups of 11 bits.
  5. Each 11-bit value maps to a word in the BIP-39 English wordlist (2048 words).
The mnemonic is then converted to a 512-bit seed via PBKDF2-HMAC-SHA512 (2048 rounds) with the passphrase "mnemonic" (no user passphrase by default). This seed feeds into BIP-32 HD derivation.
Only the BIP-39 English wordlist is supported. The wordlist is well-established, widely available, and avoids internationalization complexity.

HD Derivation (BIP-32/44)

Cowboy uses standard BIP-32/44 hierarchical deterministic derivation to produce account keys from a mnemonic seed. This is the same scheme used by Ethereum wallets (MetaMask, Ledger, Trezor), which means:
  • A Cowboy mnemonic imported into MetaMask produces the same addresses
  • A MetaMask/Ledger mnemonic imported into Cowboy produces the same addresses
  • Hardware wallets work out of the box

Derivation Path

m / 44' / 60' / 0' / 0 / account_index
LevelValueMeaning
44'BIP-44Multi-account HD wallets
60'Ethereum coin typeEthereum-compatible key derivation
0'AccountHardened account group
0ChangeExternal chain (receiving addresses)
account_index0, 1, 2, …Sequential account index
Cowboy uses Ethereum’s coin type (60') rather than registering a separate coin type. This is intentional — Cowboy accounts are Ethereum addresses, and using the same derivation path means full wallet compatibility.
The default account is index 0. When cowboy wallet create --from-mnemonic or cowboy wallet import --mnemonic is used without specifying an index, account 0 is derived.

Deriving Multiple Keys

# Default: derives account 0
$ cowboy wallet create --from-mnemonic
Address: 0x7a3b...f92e  (m/44'/60'/0'/0/0)

# Derive a specific account index
$ cowboy wallet create --from-mnemonic --account 3
Address: 0x91c4...a7d1  (m/44'/60'/0'/0/3)

# List all derived accounts
$ cowboy wallet accounts --mnemonic
  0: 0x7a3b...f92e  (default)
  1: 0x52f1...b803
  2: 0x1d8e...c4f6
  3: 0x91c4...a7d1

Hardware Wallet Support

Cowboy supports signing transactions via hardware wallets (Ledger, Trezor) using the same BIP-44 derivation path. The private key never leaves the hardware device.

How It Works

+------------------+       +------------------+       +------------------+
|   Cowboy CLI     | ----> | Hardware Wallet   | ----> | Signed Tx        |
|  (builds tx)     |  USB  | (signs with key)  |       | (broadcast)      |
+------------------+       +------------------+       +------------------+
  1. The CLI constructs the transaction and computes the signing hash (keccak256(CBOR(tx))).
  2. The hash is sent to the hardware wallet over USB/HID.
  3. The user confirms on the device screen.
  4. The device signs with the private key at the specified BIP-44 path and returns the signature.
  5. The CLI attaches the signature and broadcasts the transaction.

CLI Usage

# Use a Ledger device (default account 0)
$ cowboy wallet address --ledger
Address: 0x7a3b...f92e  (Ledger, m/44'/60'/0'/0/0)

# Use a specific account index on the hardware wallet
$ cowboy wallet address --ledger --account 2
Address: 0x1d8e...c4f6  (Ledger, m/44'/60'/0'/0/2)

# Deploy an actor, signing with Ledger
$ cowboy actor deploy --code actors/hello/main.py --ledger
Confirm on your Ledger device...
 Actor deployed at 0xabcd...1234

# Send a transaction with Trezor
$ cowboy transfer --to 0x1234...5678 --amount 10 --trezor
Confirm on your Trezor device...
 Transfer sent (tx: 0xef01...7890)

Supported Devices

DeviceConnectionLibraryStatus
Ledger Nano S/X/S PlusUSB HIDledger-transport-hidPlanned
Trezor Model T/OneUSB HIDtrezor-transportPlanned
Since Cowboy uses the standard Ethereum derivation path (m/44'/60'/0'/0/n) and secp256k1 signing, hardware wallets require no custom firmware or app — the existing Ethereum app on Ledger/Trezor works directly.

Auto-Discovery with Hardware Wallets

When --ledger or --trezor is passed, the CLI skips file-based key discovery entirely and communicates with the device directly. Hardware wallet flags take priority over all other key sources.

Environment Variable Format

When using the COWBOY_PRIVATE_KEY environment variable, the value is the raw 32-byte key in hex encoding (with or without 0x prefix):
export COWBOY_PRIVATE_KEY=fd727f6b015a85fd4035a3106aa8e7178507d0af7c472334715862b4a7ad1866
Hex is the right choice for environment variables because:
  • It’s a single-line value (no PEM headers)
  • It’s commonly used for secrets in environment variables across the ecosystem
  • It’s easy to set programmatically

CLI Commands

Wallet commands:
CommandDescription
cowboy wallet createGenerate a new key in PEM format
cowboy wallet create --encryptGenerate a new key as encrypted keystore
cowboy wallet create --from-mnemonicDerive key from BIP-39 mnemonic via BIP-44
cowboy wallet addressDerive and print public address from private key
cowboy wallet address --ledgerPrint address from hardware wallet
cowboy wallet accounts --mnemonicList derived accounts from a mnemonic
cowboy wallet export --mnemonicExport key’s mnemonic (if created from one)
cowboy wallet import --mnemonicImport key from BIP-39 mnemonic, write PEM file
cowboy wallet encryptEncrypt an existing PEM key to .cowboy/key.enc
cowboy wallet decryptDecrypt a keystore back to PEM

Security Considerations

The CLI MUST set .cowboy/key to mode 0600 (owner read/write only) on creation. On load, if the file is group- or world-readable, the CLI SHOULD print a warning:
Warning: .cowboy/key is readable by others.
Run: chmod 600 .cowboy/key
When displaying mnemonics, the CLI MUST:
  • Print a warning about secure storage before showing the words.
  • Never write mnemonics to log files or shell history.
  • Clear terminal scrollback is recommended but not enforced.
When reading mnemonics for import, the CLI SHOULD use a no-echo input mode where supported.
Unencrypted PEM keys are protected by filesystem permissions (0600), which is sufficient for local development. For production or shared machines, use the encrypted keystore format (cowboy wallet encrypt), which wraps the key in AES-256-GCM with an Argon2id-derived passphrase.
Hardware wallets provide the strongest key security — the private key is generated on-device and never exposed to the host machine. For high-value accounts or production validators, hardware wallet signing is strongly recommended.

Encrypted Keystore

For production deployments and shared machines, Cowboy supports passphrase-encrypted key files. An encrypted keystore wraps the PEM key material in an AES-256-GCM envelope, with the encryption key derived from a user passphrase via Argon2id.

File Format (.cowboy/key.enc)

{
  "version": 1,
  "curve": "secp256k1",
  "kdf": {
    "algorithm": "argon2id",
    "params": {
      "m_cost": 65536,
      "t_cost": 3,
      "p_cost": 1
    },
    "salt": "base64-encoded-16-bytes"
  },
  "cipher": {
    "algorithm": "aes-256-gcm",
    "nonce": "base64-encoded-12-bytes",
    "ciphertext": "base64-encoded-encrypted-key",
    "tag": "base64-encoded-16-byte-auth-tag"
  },
  "checksum": "9a3b"
}

Encryption Specification

1

Key Derivation

The passphrase is fed into Argon2id to derive a 32-byte encryption key:
ParameterValueRationale
m_cost65536 (64 MiB)Memory-hard; resists GPU/ASIC attacks
t_cost3 iterationsBalances security with ~1s unlock time on modern hardware
p_cost1 (single-threaded)Deterministic timing; no parallelism variance
salt16 random bytesUnique per keystore; prevents rainbow tables
Argon2id is chosen over scrypt (used by Ethereum’s keystore v3) because it provides stronger resistance to both GPU and side-channel attacks.
2

Encryption

The raw 32-byte private key is encrypted with AES-256-GCM:
  1. Generate a 12-byte random nonce.
  2. Encrypt the key bytes with AES-256-GCM using the derived key and nonce.
  3. Store the ciphertext and 16-byte authentication tag separately.
AES-256-GCM provides authenticated encryption — any tampering with the ciphertext, nonce, or tag causes decryption to fail.
3

Checksum

The checksum field contains the same SHA-256-based checksum as the PEM format (first 4 hex chars of SHA-256(raw_key_bytes)). This allows verifying the key after decryption without exposing any information about the plaintext key in the encrypted file.The checksum is computed over the plaintext key bytes before encryption and verified after decryption.

CLI Commands

# Encrypt an existing key (prompts for passphrase)
$ cowboy wallet encrypt
Enter passphrase: ********
Confirm passphrase: ********
Encrypted keystore written to .cowboy/key.enc
Original key backed up to .cowboy/key.bak

# Create a new wallet directly as encrypted
$ cowboy wallet create --encrypt
Enter passphrase: ********
Confirm passphrase: ********
Created wallet:
  Keystore: .cowboy/key.enc
  Curve:    secp256k1
  Address:  0x7a3b...f92e

# Decrypt back to PEM (prompts for passphrase)
$ cowboy wallet decrypt
Enter passphrase: ********
Decrypted key written to .cowboy/key

Auto-Discovery with Encrypted Keys

The key auto-discovery order is extended to check for encrypted keystores:
  1. --ledger / --trezor flag (hardware wallet, skips file discovery)
  2. --private-key <path> flag
  3. COWBOY_PRIVATE_KEY environment variable (hex)
  4. .cowboy/key file (PEM)
  5. .cowboy/key.enc file (encrypted keystore) — prompts for passphrase
When an encrypted keystore is found, the CLI prompts for a passphrase interactively. For non-interactive environments (CI/CD), the passphrase can be provided via the COWBOY_KEY_PASSPHRASE environment variable.

Passphrase Requirements

The CLI enforces minimum passphrase quality on creation:
  • Minimum 8 characters
  • No maximum length
  • No character-class requirements (length is the primary security factor)
Weak passphrases (under 12 characters) trigger a warning but are allowed:
Warning: Short passphrase. Consider using 12+ characters for production keys.

When to Use Each Format

ScenarioRecommended Format
Local development (single user)PEM (unencrypted)
Shared development machineEncrypted keystore
CI/CD pipelineEnv var (COWBOY_PRIVATE_KEY) or secrets manager
Production validator/runnerHardware wallet or encrypted keystore
Long-term backupBIP-39 mnemonic (paper/metal)
Managing multiple accountsBIP-39 mnemonic + HD derivation

Further Reading