Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
YuBTC - put your money in your head.
Donations
=========
If YuBTC was useful, tips are welcome:
1KCp7GLkZpTBVCwrMfaAaWqyTWRAXYDpKq
A console BTC client. Downloads nothing, stores nothing on disk: private keys
are derived from a seed on each invocation.
Commands:
newseed Generate a new seed.
address Show the native (P2PKH) address.
balance Show the balance.
send Send BTC to an address.
dumpprivkey Show the private key in WIF format.
pushtx Broadcast a pre-signed raw transaction from stdin.
Network providers
=================
By default every command that talks to the network hits
blockchain.info. Switch providers with `--provider NAME`:
$ yubtc balance --provider blockstream
$ yubtc balance --provider mempool.space
Registered providers:
blockchain.info blockchain.info's public API (default)
blockstream blockstream.info's Esplora API
mempool.space mempool.space's Esplora API
The blockstream and mempool.space backends share the same JSON schema
(Esplora); only the base URL differs. Confirmations are computed
locally from each UTXO's block height and the chain tip, so a slow
provider can't break the gap scan.
An unknown `--provider` name is rejected by click before any network
work runs -- no silent fallback to the default.
Retries and automatic failover (v0.3)
-------------------------------------
Every HTTP request is retried up to `--retries N` times (default 3;
`0` = a single attempt) with a short capped exponential backoff
(0.5 s -> 1 s -> 2 s). Retried: connection errors, timeouts, server
faults (5xx), request timeouts (408) and rate limiting (429 -- a sane
`Retry-After` of up to 30 s is honoured). Not retried: every other 4xx
(bad address, rejected transaction, ...) and broken response bodies --
resending those would only duplicate the failure. After the retries
run out you see the same error a single attempt would have produced.
On top of that, `--provider auto` opts into failover across the three
registered providers -- they are tried in registry order
(blockchain.info -> blockstream -> mempool.space) per request, the
first one to answer wins and is remembered for the rest of the
command run (the wallet is stateless: nothing is stored between
runs). If every provider fails, the error lists what was tried:
yubtc: all backends failed: blockchain.info: ...; blockstream: ...; mempool.space: ...
The default provider stays `blockchain.info` -- `auto` and the retry
loop are opt-in resilience, not a change of data source.
Key derivation
==============
yuBTC supports two key derivation paths, gated on whether a passphrase
is set:
* **No passphrase** (the default): the legacy path. For each nonce the
32-byte secret is `sha256(keccak256(blake2b256(nonce ‖ seed)))`. Each
nonce is independent -- reusing a seed with the same passphrase at a
different nonce gives a different address, but two different seeds at
the same nonce are also independent. The wallet is compatible with
the original yubtc behaviour: an old wallet with no passphrase opens
to the same addresses as it always did.
* **With passphrase**: BIP-39 + BIP-32 + BIP-44. The mnemonic is
NFKD-normalised, stretched through PBKDF2-HMAC-SHA512 (2048 iter,
salt `b'mnemonic' + passphrase`, 64 bytes) to produce the BIP-39
seed. The BIP-32 master is derived from that seed, and the final
32-byte key is the leaf at the **main path**:
m/44'/0'/0'/0/<nonce>
(`purpose / coin_type / account / chain / address_index`).
Same mnemonic + passphrase + nonce yields the same address in Trezor,
Ledger, Electrum, etc. The wallet walks only the receiving chain
(`chain = 0`) and uses the next unused index of the same chain for
cashback -- a single-account, single-chain tree, in line with the
wallet's no-storage rescan-on-every-run model. A full BIP-44 walk
would be O(N×M) per scan with no upside for this use case.
Private key formats
===================
`dumpprivkey` returns a **WIF (Wallet Import Format)** string --
compressed, mainnet, base58check-encoded. Concretely, the bytes are:
0x80 || <32-byte secret> || 0x01
The trailing `0x01` is the compressed-pubkey flag; the prefix `0x80`
is the mainnet private-key prefix. The whole 34-byte payload is then
base58check-encoded so a typo in the WIF is detected by checksum, not
by silently signing for the wrong key.
Only compressed WIF is supported on import (`privwif2privkey`); an
uncompressed WIF (33-byte body, no trailing `0x01`) raises
`'uncompressed wif not supported'` rather than silently downgrading.
Inside the wallet the privkey is held as a 32-byte secret. yubtc does
not store anything, so the WIF is the only thing to keep between
sessions -- paste it into another BIP-39-aware wallet to recover.
Usage from a checkout (no installation needed):
$ pip install -r requirements.txt
$ ./yubtc newseed
The ./yubtc launcher runs the code straight from src/, so only the third-party
dependencies have to be installed, not YuBTC itself. Run the tests the same way:
$ pip install 'pytest>=7.0'
$ pytest
Alternatively, install it as a real package to get a yubtc command on PATH:
$ pip install .
Examples:
$ yubtc newseed
place fabric recall try between menu brush there around oil wing busy summer brush record
Address: 14bUA6ioEkUVCx8cU2zvjpK4KBVHxvEnt3
$ yubtc dumpprivkey
seed:
Address: 14bUA6ioEkUVCx8cU2zvjpK4KBVHxvEnt3
L25VQR3RxMNhAVgehEqGjMWXvNHDc4bdmPraiwoEoNQr4UVviz2n
$ yubtc balance
seed:
14bUA6ioEkUVCx8cU2zvjpK4KBVHxvEnt3: 0.99998552 BTC
$ yubtc send -c 0 -f 0.00001234 14bUA6ioEkUVCx8cU2zvjpK4KBVHxvEnt3 ALL
seed:
Address: 14bUA6ioEkUVCx8cU2zvjpK4KBVHxvEnt3
send 0.99997318 BTC to 14bUA6ioEkUVCx8cU2zvjpK4KBVHxvEnt3 (cashback=0.00000000, fee=0.00001234, txsize=192)? n