# bip85keys `bip85keys` turns a BIP-39 mnemonic into key pairs that can be recreated from that mnemonic at any time. The same mnemonic, key type and index always give the same key. It uses the BIP-85 entropy deriver from `git.eeqj.de/sneak/secret/pkg/bip85` and takes the same steps as that repository's `agehd` package. Commands are grouped by key type: `bip85keys ssh ...` for ed25519 SSH keys (in this PR) and `bip85keys age ...` for age identities (planned, see below). ## Derivation The mnemonic is turned into a key like this: 1. mnemonic -> BIP-39 seed (empty passphrase); 2. seed -> BIP-32 master key; 3. master key -> 64 bytes of BIP-85 entropy at the path below; 4. entropy -> BIP-85 DRNG (SHAKE256); read 32 bytes; 5. those 32 bytes become the key in the way the key type needs. The path is: ``` m/83696968'/592366788'/'/' ``` - `83696968` is the fixed BIP-85 purpose. - `592366788` is the vendor id, `sha256("berlin.sneak") & 0x7fffffff`, the same one `agehd` uses. - `app` is the application id of the key type; each type has its own. - `n` is the key index: flag `--index` / `-n`, default `0`. ## Giving it the mnemonic The mnemonic itself is never a command-line argument. It is looked for in this order; the first one found wins: 1. `--mnemonic-command `: a shell command, run with `sh -c`, whose standard output is the mnemonic. Example: `--mnemonic-command 'secret get foo'`. Whitespace around the output is dropped. If the command exits with a non-zero status, the tool prints its standard error and exits with status 1. 2. Environment variable `BIP85KEYS_MNEMONIC_COMMAND`: the same, as a shell command held in the environment. 3. Environment variable `BIP85KEYS_MNEMONIC`: the mnemonic itself. 4. A prompt on the terminal with echo turned off. If none of these is available and standard input is not a terminal, the tool refuses and exits with status 1. A mnemonic that fails the BIP-39 checksum is refused with a message saying so. Every command takes `--index` / `-n` and `--mnemonic-command`, and has `--help`. `bip85keys --version` prints the version set at build time. ## SSH keys: `bip85keys ssh` Only ed25519 keys are produced. The application id is `1822331379`, `sha256("bip85ssh") & 0x7fffffff`, so the path is `m/83696968'/592366788'/1822331379'/'`. The 32 bytes from step 4 are the ed25519 seed. Test vector, mnemonic `abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`: ``` index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA1esgfi4OeaywgKh0o5r/8lMOUlUD/N+YoAiC8SNEML index 1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB26T7hdDuUF6wfTQ7NpIpyeTgGha4NlhQjaAhap5dqs ``` ### `bip85keys ssh pub` Prints one `authorized_keys` line to standard output: ``` ssh-ed25519 ``` The comment defaults to `bip85keys/ssh/`; change it with `--comment`. ### `bip85keys ssh priv` Prints the unencrypted private key in OpenSSH format (the `-----BEGIN OPENSSH PRIVATE KEY-----` block that `ssh` reads) to standard output and nothing else, so it can be redirected into a file. The key's comment is the same as for `pub`. ### `bip85keys ssh install <[user@]host> [-- ssh options...]` Runs the system `ssh` to the host and, on the host: - creates `~/.ssh` with mode `0700` if it is missing; - creates `~/.ssh/authorized_keys` with mode `0600` if it is missing; - appends the `pub` line only if an identical line is not already there. It then prints `added` or `already present`. How this `ssh` connection authenticates is up to the user's normal `ssh` setup (existing keys, agent, password). Anything after `--` is passed to `ssh` unchanged. ### `bip85keys ssh ssh [ssh arguments...]` Derives the key, serves it from an SSH agent that runs inside the tool on a unix socket in a new private `0700` temporary directory, then runs the system `ssh` with `-o IdentityAgent=` followed by the host and all remaining arguments unchanged. The tool exits with `ssh`'s exit status and removes the socket and directory on the way out. The private key is never written to disk. ## age identities: `bip85keys age` (planned) Not in this PR, which delivers the SSH type first; it is the next type to add. `bip85keys age pub` and `bip85keys age priv` will derive exactly what `sneak/secret` derives with its `agehd` package: application id `733482323` (`sha256("secret") & 0x7fffffff`), so the path is `m/83696968'/592366788'/733482323'/'`; the 32 bytes from step 4 are clamped as X25519 requires and encoded as an `AGE-SECRET-KEY-1...` identity. An age key from this tool for a given mnemonic and index will equal the one `secret` derives. ## Adding a key type Adding a key type is one package under `internal/` that turns the 32 derived bytes into that type's key, plus one cobra subcommand under `internal/cli/` that groups its commands. ## Errors Errors go to standard error and the exit status is 1, except for `ssh ssh`, which passes through `ssh`'s own exit status. ## Building and running ``` make build # produces ./bip85keys make check # fmt-check, lint (golangci-lint) and tests ``` Examples: ``` bip85keys ssh pub -n 3 --mnemonic-command 'secret get foo' bip85keys ssh priv -n 3 > ~/.ssh/id_bip85_3 bip85keys ssh install -n 3 user@example.com bip85keys ssh ssh -n 3 user@example.com uptime ```