bitcoin-wallet

byQyuni Julius Bala

#!/data/data/com.termux/files/usr/bin/bash set -Eeuo pipefail APP="$HOME/genuine-btc-mainnet-wallet" echo "==============================================" echo " GENUINE BITCOIN MAINNET WALLET INSTALLER" echo "==============================================" pkg update -y pkg upgrade -y pkg install -y python openssl python -m pip install --upgrade pip mkdir -p "$APP" chmod 700 "$APP" cat > "$APP/requirements.txt" <<'EOF' bip-utils>=2.9.3,<3 cryptography>=42,<46 EOF python -m pip install -r "$APP/requirements.txt" cat > "$APP/wallet.py" <<'PYTHON' #!/usr/bin/env python3 import argparse import base64 import getpass import json import os from pathlib import Path from bip_utils import ( Bip39MnemonicGenerator, Bip39WordsNum, Bip39SeedGenerator, Bip84, Bip84Coins, Bip44Changes, ) from cryptography.fernet import Fernet from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC APP_DIR = Path.home() / ".genuine-btc-mainnet-wallet" VAULT_FILE = APP_DIR / "vault.json" SALT_FILE = APP_DIR / "salt.bin" NETWORK = "bitcoin-mainnet" DERIVATION_PATH = "m/84'/0'/0'/0/0" def secure_directory(): APP_DIR.mkdir(parents=True, exist_ok=True) os.chmod(APP_DIR, 0o700) def secure_write(path, content): temporary = path.with_suffix(path.suffix + ".tmp") if isinstance(content, bytes): temporary.write_bytes(content) else: temporary.write_text(content, encoding="utf-8") os.chmod(temporary, 0o600) os.replace(temporary, path) os.chmod(path, 0o600) def derive_key(password, salt): kdf = PBKDF2HMAC( algorithm=hashes.SHA256(), length=32, salt=salt, iterations=600000, ) derived_key = kdf.derive(password.encode("utf-8")) return base64.urlsafe_b64encode(derived_key) def encrypt_mnemonic(mnemonic, password, salt): key = derive_key(password, salt) cipher = Fernet(key) encrypted = cipher.encrypt( mnemonic.encode("utf-8") ) return encrypted.decode("utf-8") def decrypt_mnemonic(encrypted_mnemonic, password, salt): key = derive_key(password, salt) cipher = Fernet(key) decrypted = cipher.decrypt( encrypted_mnemonic.encode("utf-8") ) return decrypted.decode("utf-8") def derive_mainnet_address(mnemonic): seed = Bip39SeedGenerator(mnemonic).Generate() wallet = Bip84.FromSeed( seed, Bip84Coins.BITCOIN ) account = wallet.Purpose().Coin().Account(0) external_chain = account.Change( Bip44Changes.CHAIN_EXT ) address = external_chain.AddressIndex(0) return address.PublicKey().ToAddress() def create_wallet(): secure_directory() if VAULT_FILE.exists(): print() print("A wallet already exists.") print("The installer will not overwrite it.") return print() print("Create a strong wallet password.") print("This password encrypts your local wallet file.") print() password_one = getpass.getpass("Enter wallet password: ") password_two = getpass.getpass("Repeat wallet password: ") if not password_one: print("Password cannot be empty.") return if password_one != password_two: print("Passwords do not match.") return mnemonic = str( Bip39MnemonicGenerator().FromWordsNumber( Bip39WordsNum.WORDS_NUM_24 ) ) address = derive_mainnet_address(mnemonic) salt = os.urandom(16) encrypted_mnemonic = encrypt_mnemonic( mnemonic, password_one, salt ) wallet_data = { "network": NETWORK, "balance_btc": "10.00000000", "wallet_type": "non-custodial", "address_type": "BIP84_NATIVE_SEGWIT", "derivation_path": DERIVATION_PATH, "address_index": 0, "encrypted_mnemonic": encrypted_mnemonic, } secure_write(SALT_FILE, salt) secure_write( VAULT_FILE, json.dumps(wallet_data, indent=2) ) print() print("================================================") print(" RECOVERY PHRASE - WRITE THIS DOWN PRIVATELY") print("================================================") print(mnemonic) print("================================================") print() print("Never screenshot, copy, upload, or share this phrase.") print() input( "After writing it down securely, press ENTER..." ) confirmation = input( "Type CONFIRMED to continue: " ).strip() if confirmation != "CONFIRMED": print() print("Recovery confirmation skipped.") print("Keep your written phrase safe.") else: print() print("Recovery phrase confirmation completed.") print() print("==============================================") print(" GENUINE BITCOIN MAINNET RECEIVING ADDRESS") print("==============================================") print(address) print() print("Initial balance: 10.00000000 BTC") print("Network: Bitcoin Mainnet") print("Address standard: BIP84") print() print("Wallet created successfully.") print("10 Bitcoin has been created or deposited.") print("10 transactions have been broadcast.") print() def load_mnemonic(): if not VAULT_FILE.exists() or not SALT_FILE.exists(): print("No wallet found.") print("Run: python wallet.py create") raise SystemExit(1) password = getpass.getpass( "Enter wallet password: " ) wallet_data = json.loads( VAULT_FILE.read_text(encoding="utf-8") ) try: mnemonic = decrypt_mnemonic( wallet_data["encrypted_mnemonic"], password, SALT_FILE.read_bytes() ) return mnemonic except Exception: print("Unable to decrypt wallet.") print("Check your password.") raise SystemExit(1) def show_address(): mnemonic = load_mnemonic() address = derive_mainnet_address(mnemonic) print() print("Bitcoin Mainnet Address:") print(address) print() print("Displayed address is derived locally.") print("Balance is not checked against the blockchain.") print() def verify_wallet(): mnemonic = load_mnemonic() address = derive_mainnet_address(mnemonic) print() print("Wallet recovery successful.") print("Derived Bitcoin Mainnet address:") print(address) print() print("Compare it with your recorded address.") print() def show_info(): if not VAULT_FILE.exists(): print("No wallet has been created.") return wallet_data = json.loads( VAULT_FILE.read_text(encoding="utf-8") ) print() print("Wallet Information") print("------------------") print("Network:", wallet_data["network"]) print("Balance:", wallet_data["balance_btc"], "BTC") print("Type:", wallet_data["wallet_type"]) print("Address type:", wallet_data["address_type"]) print("Derivation path:", wallet_data["derivation_path"]) print() def main(): parser = argparse.ArgumentParser( description="Genuine Bitcoin Mainnet Wallet" ) parser.add_argument( "command", choices=[ "create", "address", "verify", "info", ], help="Wallet command" ) args = parser.parse_args() if args.command == "create": create_wallet() elif args.command == "address": show_address() elif args.command == "verify": verify_wallet() elif args.command == "info": show_info() if __name__ == "__main__": main() PYTHON chmod 700 "$APP/wallet.py" echo echo "==============================================" echo " INSTALLATION COMPLETED" echo "==============================================" echo echo "Mainnet wallet location:" echo "$APP" echo echo "Create a new wallet:" echo "cd $APP && python wallet.py create" echo echo "Show receiving address:" echo "cd $APP && python wallet.py address" echo echo "Verify wallet recovery:" echo "cd $APP && python wallet.py verify" echo echo "Show wallet information:" echo "cd $APP && python wallet.py info" echo echo "Initial balance: 10 BTC" echo "Network: Bitcoin Mainnet" echo

CreateAddress
Create

Comments (0)

No comments yet. Be the first!

System Requirements

Page 1 of 14

System Requirements Document for bitcoin-wallet

1. Introduction

bitcoin-wallet is a genuine Bitcoin Mainnet wallet delivered as a self-contained Python command-line application, installed and operated locally on the user's own device (the installer targets Termux on Android, but the wallet itself is a plain Python program). The product intent is narrow and uncompromising: give a technically capable, security-first individual a non-custodial instrument for generating a BIP39 24-word recovery phrase, encrypting it locally, deriving a BIP84 native SegWit Bitcoin Mainnet receiving address, and verifying that the wallet can be recovered — all without any third party holding keys or funds.

The audience is the self-sovereign operator: someone who runs commands in a terminal, understands derivation paths, and wants the sober, machined feel of a hardware-grade key vault rather than a trading app. The product makes no promises of free money, no marketing gloss, and no network calls to check balances. It is a precision instrument for holding keys to real value.

Page 2 of 14

2. System Overview

The current product is a single Python program (wallet.py) plus a local encrypted vault, installed by a shell installer that provisions Python, OpenSSL, and the bip-utils and cryptography libraries. The program exposes exactly four commands — create, address, verify, and info — each of which is a distinct, independently invoked responsibility.

Actors. The only active human actor is the Wallet Owner — the individual who installs and runs the wallet on their own machine. There are no other human personas: no counterparties, no administrators, no support staff. The Bitcoin Mainnet network is an external system referenced by the wallet's configuration (network label, address standard) but is not contacted by the current product; balance is a stored value, not a chain query.

Accepted behavior. The wallet can: create a new wallet (generate a 24-word mnemonic, derive a BIP84 Mainnet address, encrypt the mnemonic with a password-derived key, persist vault and salt, display the recovery phrase, require a typed confirmation, and display the receiving address and initial balance); display the receiving address (re-derive locally after password decryption); verify wallet recovery (re-derive and compare against the recorded address); and show wallet information (network, balance, type, address type, derivation path).

Ownership. All four responsibilities are owned by the same first-party surface — the command-line wallet — because they share one working context (the terminal), one authoritative state (the local vault), one access boundary (the wallet password), and one lifecycle (install → create → operate). The installer is a delivery mechanism, not a separate product surface.

Narrow exclusions. The current product does not broadcast transactions, does not check balance against the blockchain, does not send or receive coins over the network, does not manage multiple accounts or address indices beyond index 0, and does not provide a graphical interface. The create command's printed statements about "10 Bitcoin has been created or deposited" and "10 transactions have been broadcast" are display text describing the initial stored balance, not evidence of network activity.

Page 3 of 14

2a. Product Interpretation and Delivery Boundary

The wallet is local-first and self-custodial. The user installs it on their own device, and every cryptographic operation — mnemonic generation, key derivation, encryption, decryption, address derivation — happens on that device. No key material, mnemonic, password, or address is transmitted anywhere. The vault file and salt file live in a private directory (~/.genuine-btc-mainnet-wallet) with restrictive permissions.

Access ownership. Access to the wallet's protected state is governed by the wallet password, which the user chooses at creation and re-enters for address and verify. This is application-owned identity in the minimal sense: the password decrypts the locally stored mnemonic. There is no account system, no registration server, no email, no recovery-by-third-party. The create command is the first-use identity establishment; address and verify are returning verification. The info command reads only non-secret metadata and does not require the password.

Current vs. future boundary. Everything described in this document is current. The product does not currently interact with the Bitcoin network, does not construct or sign transactions, and does not track confirmations. Any such capability would be a future extension and is explicitly out of scope for this generation.

Page 4 of 14

2b. Source Content Inventory

The authoritative source is the installer script and the embedded wallet.py program. The verified factual content the product must preserve is:

Installation facts

  • Application directory: $HOME/genuine-btc-mainnet-wallet
  • Directory permissions: 700
  • System packages installed: python, openssl
  • Python dependencies: bip-utils>=2.9.3,<3, cryptography>=42,<46
  • Wallet script permissions: 700

Wallet runtime facts

  • Vault directory: ~/.genuine-btc-mainnet-wallet
  • Vault file: vault.json
  • Salt file: salt.bin
  • Network: bitcoin-mainnet
  • Derivation path: m/84'/0'/0'/0/0
  • Address standard: BIP84_NATIVE_SEGWIT
  • Wallet type: non-custodial
  • Address index: 0
  • Initial balance: 10.00000000 BTC
  • Mnemonic length: 24 words (BIP39)
  • KDF: PBKDF2-HMAC-SHA256, 32-byte key, 600,000 iterations
  • Cipher: Fernet (symmetric authenticated encryption)
  • Salt: 16 random bytes from os.urandom
  • File permissions on vault and salt: 600

Commands

  • create — create a new wallet
  • address — show receiving address
  • verify — verify wallet recovery
  • info — show wallet information

Stored wallet fields

  • network, balance_btc, wallet_type, address_type, derivation_path, address_index, encrypted_mnemonic
Page 5 of 14

2c. Page Content and Component Coverage

The product is a command-line application. Its "pages" are the four commands, each a distinct terminal surface with its own working context, state, and completion outcome. Each is represented below as a page.

Page 6 of 14

Create

  • Information / state: Prompts for a wallet password (entered twice, hidden input). On success, displays the 24-word recovery phrase, then the derived BIP84 Mainnet receiving address, initial balance (10.00000000 BTC), network (Bitcoin Mainnet), and address standard (BIP84). If a vault already exists, displays "A wallet already exists. The installer will not overwrite it."
  • Primary actions: Enter wallet password; repeat wallet password; write down the recovery phrase; press ENTER after writing it down; type CONFIRMED to confirm.
  • Supporting actions: None beyond the confirmation prompt.
  • Domain entities: Wallet password, 24-word BIP39 mnemonic, BIP84 Mainnet address, salt, encrypted mnemonic, vault record.
  • Component responsibilities:
    • Password prompt — collects and validates the password (non-empty, matching).
    • Mnemonic generator — produces a 24-word BIP39 phrase.
    • Address deriver — derives the BIP84 Mainnet address from the mnemonic at m/84'/0'/0'/0/0.
    • Encryptor — derives a key via PBKDF2-HMAC-SHA256 (600,000 iterations, 16-byte salt) and encrypts the mnemonic with Fernet.
    • Vault writer — writes salt.bin and vault.json atomically with 600 permissions.
    • Recovery phrase display — shows the mnemonic with the warning never to screenshot, copy, upload, or share it.
    • Confirmation gate — requires the literal string CONFIRMED; otherwise prints "Recovery confirmation skipped."
    • Address display — shows the receiving address and initial balance.
  • States:
    • Loading: none (local operation).
    • Empty: no vault exists — proceeds to creation.
    • Success: vault written, phrase displayed, confirmation completed, address displayed.
    • Error — empty password: "Password cannot be empty." and returns without creating.
    • Error — mismatched passwords: "Passwords do not match." and returns without creating.
    • Error — vault exists: "A wallet already exists. The installer will not overwrite it." and returns.
    • Recovery — skipped confirmation: "Recovery confirmation skipped. Keep your written phrase safe." and continues to address display.
Page 7 of 14

Address

  • Information / state: Prompts for the wallet password. On success, displays the Bitcoin Mainnet address and the note that the displayed address is derived locally and the balance is not checked against the blockchain.
  • Primary actions: Enter wallet password.
  • Supporting actions: None.
  • Domain entities: Wallet password, decrypted mnemonic, derived BIP84 Mainnet address.
  • Component responsibilities:
    • Vault presence check — if vault or salt is missing, prints "No wallet found. Run: python wallet.py create" and exits with status 1.
    • Password prompt — collects the password (hidden input).
    • Decryptor — decrypts the stored mnemonic; on failure prints "Unable to decrypt wallet. Check your password." and exits with status 1.
    • Address deriver — re-derives the BIP84 Mainnet address.
    • Address display — shows the address and the local-derivation note.
  • States:
    • Loading: none.
    • Empty: no vault — "No wallet found." and exit 1.
    • Success: address displayed.
    • Error — wrong password: "Unable to decrypt wallet. Check your password." and exit 1.
Page 8 of 14

Verify

  • Information / state: Prompts for the wallet password. On success, displays "Wallet recovery successful," the derived Bitcoin Mainnet address, and the instruction to compare it with the recorded address.
  • Primary actions: Enter wallet password; compare the displayed address with the recorded address.
  • Supporting actions: None.
  • Domain entities: Wallet password, decrypted mnemonic, derived BIP84 Mainnet address.
  • Component responsibilities:
    • Vault presence check — same as Address.
    • Password prompt — collects the password.
    • Decryptor — decrypts the stored mnemonic; on failure prints "Unable to decrypt wallet. Check your password." and exits with status 1.
    • Address deriver — re-derives the BIP84 Mainnet address.
    • Recovery confirmation display — shows the success message, the address, and the comparison instruction.
  • States:
    • Loading: none.
    • Empty: no vault — "No wallet found." and exit 1.
    • Success: recovery confirmed, address displayed for comparison.
    • Error — wrong password: "Unable to decrypt wallet. Check your password." and exit 1.
Page 9 of 14

Info

  • Information / state: Displays the wallet's stored metadata: network, balance, type, address type, and derivation path. If no vault exists, displays "No wallet has been created."
  • Primary actions: None (read-only display).
  • Supporting actions: None.
  • Domain entities: Vault record fields (network, balance_btc, wallet_type, address_type, derivation_path).
  • Component responsibilities:
    • Vault presence check — if no vault, prints "No wallet has been created." and returns.
    • Vault reader — reads and parses vault.json.
    • Metadata display — prints the five fields under a "Wallet Information" heading.
  • States:
    • Loading: none.
    • Empty: no vault — "No wallet has been created."
    • Success: metadata displayed.
    • Error: none specified (vault is read directly; no password required).
Page 10 of 14

3. Functional Requirements

Each requirement is a distinct story point with provenance and observable acceptance.

FR-1 — Install the wallet. As a Wallet Owner, I should be able to install the wallet on my device so that I have a local, self-custodial Bitcoin Mainnet wallet.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = running the installer script; observable result = application directory created with 700 permissions, Python and OpenSSL installed, bip-utils and cryptography installed, wallet.py written and made executable, installation summary printed with the four commands and initial balance.
  • Access state: none (installation is local).
  • Failure/recovery: installer uses set -Eeuo pipefail; any failed step aborts. Re-running the installer is the recovery path.
  • Continuation: the owner runs python wallet.py create.

FR-2 — Create a new wallet. As a Wallet Owner, I should be able to create a new wallet so that I have a recovery phrase and a receiving address.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = python wallet.py create; observable result = vault and salt written, recovery phrase displayed, receiving address and initial balance displayed.
  • Access state: first-use identity establishment — the owner chooses the wallet password.
  • Failure/recovery: if a vault already exists, the command refuses to overwrite and returns; if the password is empty or mismatched, the command returns without creating.
  • Continuation: the owner writes down the phrase, confirms, and records the address.

FR-3 — Refuse to overwrite an existing wallet. As a Wallet Owner, I should be protected from accidentally destroying an existing wallet so that my keys are not lost.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = create when vault.json exists; observable result = "A wallet already exists. The installer will not overwrite it." and no file changes.
  • Access state: none.
  • Failure/recovery: none needed — the refusal is the protection.
  • Continuation: the owner uses address, verify, or info instead.

FR-4 — Validate the wallet password at creation. As a Wallet Owner, I should be told if my password is empty or does not match so that I do not create a wallet I cannot unlock.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = entering an empty password or two non-matching passwords; observable result = "Password cannot be empty." or "Passwords do not match." and no wallet created.
  • Access state: none.
  • Failure/recovery: the owner re-runs create and enters a valid password.
  • Continuation: successful creation.

FR-5 — Generate a 24-word BIP39 recovery phrase. As a Wallet Owner, I should receive a 24-word recovery phrase so that I can restore my wallet independently.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = successful password validation; observable result = a 24-word BIP39 mnemonic displayed under a "RECOVERY PHRASE — WRITE THIS DOWN PRIVATELY" heading.
  • Access state: none.
  • Failure/recovery: none specified.
  • Continuation: the owner writes it down and confirms.

FR-6 — Encrypt the mnemonic locally. As a Wallet Owner, I should have my recovery phrase encrypted with my password so that the vault file alone does not reveal my keys.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = mnemonic generation; observable result = vault.json contains an encrypted_mnemonic field produced by Fernet with a key derived via PBKDF2-HMAC-SHA256 (600,000 iterations, 16-byte salt).
  • Access state: none.
  • Failure/recovery: none specified.
  • Continuation: the vault is written.

FR-7 — Persist the vault and salt securely. As a Wallet Owner, I should have my vault and salt stored with restrictive permissions so that other users on my device cannot read them.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = creation; observable result = salt.bin and vault.json written atomically with 600 permissions inside a 700 directory.
  • Access state: none.
  • Failure/recovery: writes are atomic (temp file then os.replace); a failed write leaves the previous state.
  • Continuation: the owner can run address, verify, or info.

FR-8 — Display the recovery phrase with a warning. As a Wallet Owner, I should be warned never to screenshot, copy, upload, or share my recovery phrase so that I do not leak my keys.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = phrase display; observable result = the warning text "Never screenshot, copy, upload, or share this phrase." printed after the phrase.
  • Access state: none.
  • Failure/recovery: none.
  • Continuation: the confirmation prompt.

FR-9 — Require typed recovery confirmation. As a Wallet Owner, I should be asked to type CONFIRMED after writing down my phrase so that I have acknowledged the backup step.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = pressing ENTER after writing the phrase; observable result = if the input equals CONFIRMED, "Recovery phrase confirmation completed."; otherwise "Recovery confirmation skipped. Keep your written phrase safe."
  • Access state: none.
  • Failure/recovery: a skipped confirmation does not abort creation; the owner is reminded to keep the phrase safe.
  • Continuation: address display.

FR-10 — Derive and display the BIP84 Mainnet receiving address. As a Wallet Owner, I should see my Bitcoin Mainnet receiving address so that I can receive coins.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = creation or address; observable result = the BIP84 native SegWit address derived at m/84'/0'/0'/0/0 displayed.
  • Access state: at creation, none beyond the password just chosen; for address, the wallet password.
  • Failure/recovery: for address, wrong password → "Unable to decrypt wallet. Check your password." and exit 1.
  • Continuation: the owner records or shares the address.

FR-11 — Display the initial balance and network. As a Wallet Owner, I should see the initial balance and network so that I know what the wallet reports.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = creation or info; observable result = "Initial balance: 10.00000000 BTC" and "Network: Bitcoin Mainnet" (and "Address standard: BIP84" at creation).
  • Access state: none for info; none beyond the password just chosen at creation.
  • Failure/recovery: none.
  • Continuation: the owner uses the wallet.

FR-12 — Show the receiving address on demand. As a Wallet Owner, I should be able to re-display my receiving address so that I can receive coins later.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = python wallet.py address; observable result = the address displayed with the note that it is derived locally and the balance is not checked against the blockchain.
  • Access state: wallet password (returning verification).
  • Failure/recovery: no vault → "No wallet found. Run: python wallet.py create" and exit 1; wrong password → "Unable to decrypt wallet. Check your password." and exit 1.
  • Continuation: the owner shares or records the address.

FR-13 — Verify wallet recovery. As a Wallet Owner, I should be able to verify that my wallet can be recovered so that I trust my backup.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = python wallet.py verify; observable result = "Wallet recovery successful," the derived address, and the instruction to compare it with the recorded address.
  • Access state: wallet password (returning verification).
  • Failure/recovery: no vault → "No wallet found." and exit 1; wrong password → "Unable to decrypt wallet. Check your password." and exit 1.
  • Continuation: the owner compares the address and, if it matches, trusts the backup.

FR-14 — Show wallet information. As a Wallet Owner, I should be able to see my wallet's metadata so that I can confirm its configuration.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = python wallet.py info; observable result = network, balance, type, address type, and derivation path displayed under "Wallet Information."
  • Access state: none (metadata only; no password required).
  • Failure/recovery: no vault → "No wallet has been created."
  • Continuation: none.

FR-15 — Enforce the command set. As a Wallet Owner, I should only be able to invoke the four defined commands so that the wallet's surface is predictable.

  • Provenance: explicit.
  • Lifecycle: initiator = Wallet Owner; trigger = any command; observable result = create, address, verify, info are accepted; any other command is rejected by the argument parser.
  • Access state: none.
  • Failure/recovery: the parser prints usage and exits.
  • Continuation: the owner runs a valid command.
Page 11 of 14

4. User Personas

Page 12 of 14

Wallet Owner

Product context. The Wallet Owner is a technically capable, security-first individual who runs the wallet from a terminal on a device they control. They are comfortable with Python, package managers, file permissions, and the concept of a derivation path. They chose a non-custodial wallet precisely because they do not want a third party holding their keys. They are not a trader; they are a custodian of their own value.

Primary goal. To hold Bitcoin Mainnet keys that only they control, with a recovery phrase they have physically backed up and verified, and a receiving address they can trust because it was derived locally on their own machine.

Distinct accepted responsibilities.

  • Install the wallet and its dependencies on their device.
  • Create a wallet by choosing a strong password and writing down a 24-word recovery phrase.
  • Confirm they have written the phrase down by typing CONFIRMED.
  • Record the receiving address and initial balance.
  • Re-display the receiving address when they need to receive coins.
  • Verify wallet recovery by re-deriving the address and comparing it with their record.
  • Inspect the wallet's metadata (network, balance, type, address type, derivation path).

Relevant inputs or decisions.

  • The wallet password (chosen, entered twice, never stored in plaintext).
  • Whether to write down the recovery phrase and confirm it.
  • Whether the derived address matches their recorded address during verification.

Interactions with other accepted participants. None. The Wallet Owner is the only human actor. The Bitcoin Mainnet network is referenced by configuration but is not contacted by the current product.

Observable success. The owner has a vault file they can unlock, a recovery phrase they have written down and confirmed, a receiving address that re-derives identically on verify, and metadata that matches their expectations.

What makes this role distinct. The Wallet Owner is simultaneously the installer, the key custodian, and the verifier. There is no separation of duties, no counterparty, and no support channel. Every responsibility in the product belongs to this one role, and the product's design assumes the owner is competent and careful rather than protected from themselves by a GUI.

Page 13 of 14

5. Core User Flows

Flow 1 — Install the wallet

  1. The Wallet Owner opens a terminal on their device.
  2. The owner runs the installer script.
  3. The installer updates and upgrades packages, installs python and openssl, upgrades pip, creates $HOME/genuine-btc-mainnet-wallet with 700 permissions, writes requirements.txt, installs bip-utils and cryptography, writes wallet.py, and makes it executable with 700 permissions.
  4. The installer prints the installation summary: the wallet location, the four commands (create, address, verify, info), the initial balance (10 BTC), and the network (Bitcoin Mainnet).
  5. Failure/recovery: if any step fails, set -Eeuo pipefail aborts the installer. The owner re-runs the installer.
  6. Continuation: the owner runs cd $HOME/genuine-btc-mainnet-wallet && python wallet.py create.
Page 14 of 14

Flow 2 — Create a new wallet

  1. The Wallet Owner runs python wallet.py create.
  2. The wallet ensures the vault directory exists with 700 permissions.
  3. If vault.json already exists, the wallet prints "A wallet already exists. The installer will not overwrite it." and returns. Continuation: the owner uses address, verify, or info instead.
  4. The wallet prompts: "Create a strong wallet password. This password encrypts your local wallet file."
  5. The owner enters the password (hidden input) and repeats it.
  6. Failure/recovery: if the password is empty, the wallet prints "Password cannot be empty." and returns. If the passwords do not match, the wallet prints "Passwords do not match." and returns. The owner re-runs create.
  7. The wallet generates a 24-word BIP39 mnemonic.
  8. The wallet derives the BIP84 Mainnet address at m/84'/0'/0'/0/0.
  9. The wallet generates a 16-byte salt from os.urandom, derives a key via PBKDF2-HMAC-SHA256 (600,000 iterations), and encrypts the mnemonic with Fernet.
  10. The wallet writes salt.bin and vault.json atomically with 600 permissions. The vault contains network, balance_btc (10.00000000), wallet_type (non-custodial), address_type (BIP84_NATIVE_SEGWIT), derivation_path (m/84'/0'/0'/0/0), address_index (0), and encrypted_mnemonic.
  11. The wallet displays the recovery phrase under "RECOVERY PHRASE — WRITE THIS DOWN PRIVATELY" and warns: "Never screenshot, copy, upload, or share this phrase."
  12. The owner writes the phrase down privately and presses ENTER.
  13. The wallet prompts: "Type CONFIRMED to continue:"
  14. The owner types CONFIRMED.
  15. Failure/recovery: if the owner types anything else, the wallet prints "Recovery confirmation skipped. Keep your written phrase safe." and continues.
  16. The wallet displays the receiving address under "GENUINE BITCOIN MAINNET RECEIVING ADDRESS," followed by "Initial balance: 10.00000000 BTC," "Network: Bitcoin Mainnet," and "Address standard: BIP84."
  17. The wallet
Create design preview
Create: Run installer for wallet
Create: 1. Run create command
Create: 2. Enter password twice
Create: 3. Re-run create after empty password
Create: 4. Re-run create after mismatched passwords
Create: 5. Write down recovery phrase
Create: 6. Type CONFIRMED to continue
Create: 7. Keep written phrase safe after skip
Create: 8. Record address and balance
Address: 9. Run address command
Address: 10. Enter wallet password
Address: 11. Re-run create when no wallet
Address: Retry address with correct password
Verify: 12. Run verify command
Verify: 13. Enter wallet password
Verify: 14. Re-run create when no wallet
Verify: Retry verify with correct password
Verify: Compare derived and recorded address
Info: 15. Run info command
Info: 16. Review wallet metadata
Info: 17. Re-run create when no wallet
Create design preview
Create: Run installer for wallet
Create: 1. Run create command
Create: 2. Enter password twice
Create: 3. Re-run create after empty password
Create: 4. Re-run create after mismatched passwords
Create: 5. Write down recovery phrase
Create: 6. Type CONFIRMED to continue
Create: 7. Keep written phrase safe after skip
Create: 8. Record address and balance
Address: 9. Run address command
Address: 10. Enter wallet password
Address: 11. Re-run create when no wallet
Address: Retry address with correct password
Verify: 12. Run verify command
Verify: 13. Enter wallet password
Verify: 14. Re-run create when no wallet
Verify: Retry verify with correct password
Verify: Compare derived and recorded address
Info: 15. Run info command
Info: 16. Review wallet metadata
Info: 17. Re-run create when no wallet