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
This commit is contained in:
clawbot
2026-09-06 14:19:32 +00:00
parent fcb956124b
commit d3daa14d54

115
bip85ssh/README.md Normal file
View File

@@ -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'/<n>'
```
- `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 <path>` or `--xprv-file <path>`: 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 <key> <comment>
```
The comment defaults to `bip85ssh/<n>`; 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 <host> [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=<that socket>` 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
```