From d3daa14d54a6833dd346b4656e33e64ca1b5e02e Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 6 Sep 2026 14:19:32 +0000 Subject: [PATCH] bip85ssh: add spec for deterministic SSH keys from BIP-85 README for a new tool that derives ed25519 SSH key pairs from a BIP-39 mnemonic or xprv, using pkg/bip85 from sneak/secret with the same steps as agehd. Path m/83696968'/592366788'/1822331379'/n'. Four commands: pub, priv, install, ssh. The README is the spec; the code follows in later commits. Model: fable-5-1 --- bip85ssh/README.md | 115 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 bip85ssh/README.md diff --git a/bip85ssh/README.md b/bip85ssh/README.md new file mode 100644 index 0000000..7a21fbf --- /dev/null +++ b/bip85ssh/README.md @@ -0,0 +1,115 @@ +# bip85ssh + +`bip85ssh` turns a BIP-39 mnemonic (or a BIP-32 `xprv`) into SSH key pairs that +can be recreated from that secret at any time. The same secret and the same +index always give the same key. Only ed25519 keys are produced. + +It uses the BIP-85 entropy deriver from `git.eeqj.de/sneak/secret/pkg/bip85` and +follows the same steps as that repository's `agehd` package. + +## Derivation + +The secret is turned into a key like this: + +1. mnemonic -> BIP-39 seed (empty passphrase), or the `xprv` is parsed directly; +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 are the ed25519 seed. + +The path is: + +``` +m/83696968'/592366788'/1822331379'/' +``` + +- `83696968` is the fixed BIP-85 purpose. +- `592366788` is the vendor id, `sha256("berlin.sneak") & 0x7fffffff`, the same + one `agehd` uses. +- `1822331379` is the application id, `sha256("bip85ssh") & 0x7fffffff`. +- `n` is the key index: flag `--index` / `-n`, default `0`. + +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 +``` + +## Giving it the secret + +The secret is never taken as a command-line argument. It is looked for in this +order; the first one found wins: + +1. `--mnemonic-file ` or `--xprv-file `: the file holds the secret + on one line. +2. Environment variable `BIP85SSH_MNEMONIC` or `BIP85SSH_XPRV`. +3. A prompt on the terminal with echo turned off. The prompt accepts either a + mnemonic or an `xprv`; a value starting with `xprv` is treated as an `xprv`. + +If none of these is available and standard input is not a terminal, the tool +refuses and exits with status 1. + +## Commands + +Every command takes `--index` / `-n` and the secret flags above, and has +`--help`. `bip85ssh --version` prints the version set at build time. + +### `bip85ssh pub` + +Prints one `authorized_keys` line to standard output: + +``` +ssh-ed25519 +``` + +The comment defaults to `bip85ssh/`; change it with `--comment`. + +### `bip85ssh 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`. + +### `bip85ssh 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. + +### `bip85ssh 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. + +## Errors + +Errors go to standard error and the exit status is 1, except for the `ssh` +command, which passes through `ssh`'s own exit status. + +## Building and running + +``` +make build # produces ./bip85ssh +make check # fmt-check, lint (golangci-lint) and tests +``` + +Examples: + +``` +bip85ssh pub -n 3 +bip85ssh priv > ~/.ssh/id_bip85_3 +bip85ssh install -n 3 user@example.com +bip85ssh ssh -n 3 user@example.com uptime +```