bip85keys: generic derivation path, ssh to, new test vectors

Per sneak: the path carries no vendor id, since this is meant as a
standard others can follow. Application numbers follow BIP-85's own
spelling for RSA: SSH is 838372, age is 657169. The ssh subcommand that
runs the system ssh is now "to". Test vectors recomputed for the new
path. secret's age keys move to this path in a change there.

Model: fable-5-1
This commit is contained in:
clawbot
2026-09-06 22:06:27 +00:00
parent 8e24b0ae88
commit 933fa164c1

View File

@@ -23,13 +23,15 @@ The mnemonic is turned into a key like this:
The path is: The path is:
``` ```
m/83696968'/592366788'/<app>'/<n>' m/83696968'/<app>'/<n>'
``` ```
- `83696968` is the fixed BIP-85 purpose. - `83696968` is the fixed BIP-85 purpose.
- `592366788` is the vendor id, `sha256("berlin.sneak") & 0x7fffffff`, the same - `app` is the application number of the key type. There is no vendor id: the
one `agehd` uses. path is meant as a standard any implementation can follow, not something tied
- `app` is the application id of the key type; each type has its own. to one tool. Each key type's number is spelled the way BIP-85 spells its own
RSA application (`828365` is the ASCII codes of `R`, `S`, `A` written out):
SSH is `838372` (`S` `S` `H`), age is `657169` (`A` `G` `E`).
- `n` is the key index: flag `--index` / `-n`, default `0`. - `n` is the key index: flag `--index` / `-n`, default `0`.
## Giving it the mnemonic ## Giving it the mnemonic
@@ -55,17 +57,16 @@ Every command takes `--index` / `-n` and `--mnemonic-command`, and has `--help`.
## SSH keys: `bip85keys ssh` ## SSH keys: `bip85keys ssh`
Only ed25519 keys are produced. The application id is `1822331379`, Only ed25519 keys are produced. The application number is `838372`, so the
`sha256("bip85ssh") & 0x7fffffff`, so the path is path is `m/83696968'/838372'/<n>'`. The 32 bytes from step 4 are the ed25519
`m/83696968'/592366788'/1822331379'/<n>'`. The 32 bytes from step 4 are the seed.
ed25519 seed.
Test vector, mnemonic Test vector, mnemonic
`abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`: `abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about`:
``` ```
index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA1esgfi4OeaywgKh0o5r/8lMOUlUD/N+YoAiC8SNEML index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZOtOczrc/7CQytcuFwt7s4r8KjkZWkwjLZWBaFKD+7
index 1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB26T7hdDuUF6wfTQ7NpIpyeTgGha4NlhQjaAhap5dqs index 1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEWY8+/gmHYVC4u0Y0I4FKs+eVUulTPHfk9VtXw1tMF
``` ```
### `bip85keys ssh pub` ### `bip85keys ssh pub`
@@ -97,7 +98,7 @@ It then prints `added` or `already present`. How this `ssh` connection
authenticates is up to the user's normal `ssh` setup (existing keys, agent, authenticates is up to the user's normal `ssh` setup (existing keys, agent,
password). Anything after `--` is passed to `ssh` unchanged. password). Anything after `--` is passed to `ssh` unchanged.
### `bip85keys ssh ssh <host> [ssh arguments...]` ### `bip85keys ssh to <host> [ssh arguments...]`
Derives the key, serves it from an SSH agent that runs inside the tool on a unix 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` socket in a new private `0700` temporary directory, then runs the system `ssh`
@@ -108,13 +109,12 @@ socket and directory on the way out. The private key is never written to disk.
## age identities: `bip85keys age` (planned) ## age identities: `bip85keys age` (planned)
Not in this PR, which delivers the SSH type first; it is the next type to add. 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 `bip85keys age pub` and `bip85keys age priv` will use application number
`sneak/secret` derives with its `agehd` package: application id `733482323` `657169`, path `m/83696968'/657169'/<n>'`; the 32 bytes from step 4 are clamped
(`sha256("secret") & 0x7fffffff`), so the path is as X25519 requires and encoded as an `AGE-SECRET-KEY-1...` identity, the same
`m/83696968'/592366788'/733482323'/<n>'`; the 32 bytes from step 4 are clamped steps `sneak/secret` takes in its `agehd` package. `secret` derives at a
as X25519 requires and encoded as an `AGE-SECRET-KEY-1...` identity. An age key vendor-specific path today; for its keys to equal this tool's it moves to this
from this tool for a given mnemonic and index will equal the one `secret` path, which is a change in `secret`, not here.
derives.
## Adding a key type ## Adding a key type
@@ -124,7 +124,7 @@ groups its commands.
## Errors ## Errors
Errors go to standard error and the exit status is 1, except for `ssh ssh`, Errors go to standard error and the exit status is 1, except for `ssh to`,
which passes through `ssh`'s own exit status. which passes through `ssh`'s own exit status.
## Building and running ## Building and running
@@ -140,5 +140,5 @@ Examples:
bip85keys ssh pub -n 3 --mnemonic-command 'secret get foo' bip85keys ssh pub -n 3 --mnemonic-command 'secret get foo'
bip85keys ssh priv -n 3 > ~/.ssh/id_bip85_3 bip85keys ssh priv -n 3 > ~/.ssh/id_bip85_3
bip85keys ssh install -n 3 user@example.com bip85keys ssh install -n 3 user@example.com
bip85keys ssh ssh -n 3 user@example.com uptime bip85keys ssh to -n 3 user@example.com uptime
``` ```