diff options
| author | bitflash-coin <bookofshamers@outlook.com> | 2026-08-04 06:34:32 -0700 |
|---|---|---|
| committer | bitflash-coin <bookofshamers@outlook.com> | 2026-08-04 06:34:32 -0700 |
| commit | 5ce650382352c1503f40abf08129ada69402af55 (patch) | |
| tree | 6a18198901f1e2c4933f1ac53c9365eec512bf63 /docs/derivation.md | |
| parent | f34113291da7e5e3dc7d2468d4f9c212c05c584b (diff) | |
public commit
Diffstat (limited to 'docs/derivation.md')
| -rw-r--r-- | docs/derivation.md | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/docs/derivation.md b/docs/derivation.md new file mode 100644 index 0000000..c5ee3ad --- /dev/null +++ b/docs/derivation.md | |||
| @@ -0,0 +1,125 @@ | |||
| 1 | # Key derivation and address format | ||
| 2 | |||
| 3 | This document exists so that a Bitflash wallet can be rebuilt **without this | ||
| 4 | software**. If the project stops, the binaries are lost, or you simply do not | ||
| 5 | trust them, the twelve words plus what is written here are enough to recover | ||
| 6 | your private keys with ordinary cryptographic tools. | ||
| 7 | |||
| 8 | Everything below is verified by `docs/verify-derivation.py`, a self-contained | ||
| 9 | script that implements this document from scratch — no Bitflash code, no | ||
| 10 | libraries beyond Python's standard library — and checks that it reproduces the | ||
| 11 | addresses the wallet produces. If the two ever disagree, this document is wrong | ||
| 12 | and the script will say so. | ||
| 13 | |||
| 14 | ## Addresses | ||
| 15 | |||
| 16 | An address is the Base58Check encoding of a version byte followed by | ||
| 17 | `RIPEMD160(SHA256(public key))`. | ||
| 18 | |||
| 19 | | | | | ||
| 20 | |---|---| | ||
| 21 | | curve | secp256k1 | | ||
| 22 | | public key encoding | **uncompressed**, 65 bytes, `0x04 ‖ X ‖ Y` | | ||
| 23 | | address version byte | **25** (`0x19`), which is why addresses begin with `B` | | ||
| 24 | | checksum | first 4 bytes of `SHA256(SHA256(payload))`, appended before Base58 | | ||
| 25 | |||
| 26 | The public key is uncompressed. This matters more than anything else here: the | ||
| 27 | same private key hashed in compressed form gives a completely different, valid | ||
| 28 | looking address that holds nothing. Bitflash descends from Bitcoin 0.1.0, which | ||
| 29 | predates compressed keys. | ||
| 30 | |||
| 31 | Outputs paying an address use the usual script: | ||
| 32 | |||
| 33 | ``` | ||
| 34 | OP_DUP OP_HASH160 <hash160> OP_EQUALVERIFY OP_CHECKSIG | ||
| 35 | ``` | ||
| 36 | |||
| 37 | Mined coins and payments made directly to a public key use the older form | ||
| 38 | `<pubkey> OP_CHECKSIG`. A wallet scanning the chain must recognise both. | ||
| 39 | |||
| 40 | ## From twelve words to a master key | ||
| 41 | |||
| 42 | Standard BIP-39 and BIP-32, with no modifications: | ||
| 43 | |||
| 44 | 1. **Seed.** `PBKDF2-HMAC-SHA512(password = mnemonic, salt = "mnemonic" + passphrase, iterations = 2048, length = 64 bytes)`. Bitflash always uses an empty passphrase, so the salt is the literal string `mnemonic`. | ||
| 45 | 2. **Master key.** `I = HMAC-SHA512(key = "Bitcoin seed", data = seed)`. The master private key is `I[0..32]`, the master chain code is `I[32..64]`. | ||
| 46 | |||
| 47 | Child derivation is BIP-32 `CKDpriv`, hardened and non-hardened as usual. | ||
| 48 | |||
| 49 | ## Two layouts | ||
| 50 | |||
| 51 | A wallet records which layout it uses in `wallet.dat` under the key `hdschema`. | ||
| 52 | Wallets created before that field existed are treated as legacy. | ||
| 53 | |||
| 54 | ### Legacy (`hdschema` absent or 1) | ||
| 55 | |||
| 56 | ``` | ||
| 57 | m/i' | ||
| 58 | ``` | ||
| 59 | |||
| 60 | Hardened children of the master key, one flat sequence. Index 0 is the wallet's | ||
| 61 | default receiving address; the key pool and change take the following indices. | ||
| 62 | |||
| 63 | Used by every wallet created before BIP-44 support existed, and kept forever: | ||
| 64 | those phrases must keep working. | ||
| 65 | |||
| 66 | ### BIP-44 (`hdschema` = 2) | ||
| 67 | |||
| 68 | ``` | ||
| 69 | m/44'/4346950'/0'/0/i external chain (receiving) | ||
| 70 | m/44'/4346950'/0'/1/i internal chain (change) | ||
| 71 | ``` | ||
| 72 | |||
| 73 | Coin type **4346950** is `0x425446`, the ASCII bytes `BTF`. It is | ||
| 74 | [registered for Bitflash under the symbol BITFLASH](https://github.com/satoshilabs/slips/blob/master/slip-0044.md). | ||
| 75 | The account level is fixed at `0'`; Bitflash does not expose multiple accounts. | ||
| 76 | |||
| 77 | A wallet restored from a phrase scans **both** layouts, because the words alone | ||
| 78 | do not say which one produced them. | ||
| 79 | |||
| 80 | ## Test vectors | ||
| 81 | |||
| 82 | Using the public BIP-39 test mnemonic: | ||
| 83 | |||
| 84 | ``` | ||
| 85 | abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about | ||
| 86 | ``` | ||
| 87 | |||
| 88 | ### Legacy | ||
| 89 | |||
| 90 | | path | address | | ||
| 91 | |---|---| | ||
| 92 | | `m/0'` | `BHJNmVvkVv59fgn7gmgP78BtciQi2HY8Hy` | | ||
| 93 | | `m/1'` | `B5qh9opG8sH8Mv9k2xzHoTeSUd9BkT6XEZ` | | ||
| 94 | | `m/2'` | `BCNH8uGyvkiz3uXjhA9iiF27PWAaTStNin` | | ||
| 95 | | `m/3'` | `BC1QNaXuTPGDgLZy53PghZMHEmZcp5GLgD` | | ||
| 96 | |||
| 97 | ### BIP-44 external chain | ||
| 98 | |||
| 99 | | path | address | | ||
| 100 | |---|---| | ||
| 101 | | `m/44'/4346950'/0'/0/0` | `BF1ukigB1afmqxH64gJymPwUqFBrRqWbrb` | | ||
| 102 | | `m/44'/4346950'/0'/0/1` | `BACY8pcuCapNQr897eRHroNrgN38GSsEzV` | | ||
| 103 | | `m/44'/4346950'/0'/0/2` | `BMzkMN2k1phNBqy8RPb4Pv1i2QpwsWkKPa` | | ||
| 104 | | `m/44'/4346950'/0'/0/3` | `BNPZ8U6FHXr9Ererk8psyM2RqEVtF6sfBQ` | | ||
| 105 | |||
| 106 | Check them yourself: | ||
| 107 | |||
| 108 | ``` | ||
| 109 | python docs/verify-derivation.py | ||
| 110 | ``` | ||
| 111 | |||
| 112 | Both tables were produced by the wallet — the legacy one by the v1.2.15 binary, | ||
| 113 | which only knows that layout — and then reproduced independently by the script. | ||
| 114 | |||
| 115 | ## What this document does not give you | ||
| 116 | |||
| 117 | Deriving the keys is not the same as spending. To move recovered coins you still | ||
| 118 | need a node that knows the network: the chain, the peer protocol, and the | ||
| 119 | transaction format. This document is about **not losing the keys**, which is the | ||
| 120 | part that cannot be reconstructed later by reading source code that no longer | ||
| 121 | exists anywhere. | ||
| 122 | |||
| 123 | Note also that `wallet.dat` stores private keys unencrypted. A recovery phrase | ||
| 124 | protects you against losing the file; it does not protect the file. Anyone who | ||
| 125 | can read it can spend the coins. | ||