From d3daa14d54a6833dd346b4656e33e64ca1b5e02e Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 6 Sep 2026 14:19:32 +0000 Subject: [PATCH 1/6] 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 +``` -- 2.49.1 From 8439d6bfad2e2246915793a2b7a7935a98bb610e Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 6 Sep 2026 14:29:22 +0000 Subject: [PATCH 2/6] bip85keys: rename from bip85ssh and group commands by key type The tool will cover more key types than SSH, so the directory, module and binary become bip85keys and the commands are grouped by type: "bip85keys ssh pub|priv|install|ssh" now, "bip85keys age pub|priv" planned. The README states the age derivation (agehd's path in sneak/secret) and that adding a type is one package plus one subcommand. The SSH application id and everything else stay as before. Model: fable-5-1 --- {bip85ssh => bip85keys}/README.md | 103 +++++++++++++++++++----------- 1 file changed, 64 insertions(+), 39 deletions(-) rename {bip85ssh => bip85keys}/README.md (56%) diff --git a/bip85ssh/README.md b/bip85keys/README.md similarity index 56% rename from bip85ssh/README.md rename to bip85keys/README.md index 7a21fbf..b4e4e4c 100644 --- a/bip85ssh/README.md +++ b/bip85keys/README.md @@ -1,11 +1,14 @@ -# bip85ssh +# bip85keys -`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. +`bip85keys` turns a BIP-39 mnemonic (or a BIP-32 `xprv`) into key pairs that can +be recreated from that secret at any time. The same secret, 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 -follows the same steps as that repository's `agehd` package. +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 @@ -15,20 +18,44 @@ The secret is turned into a key like this: 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. +5. those 32 bytes become the key in the way the key type needs. The path is: ``` -m/83696968'/592366788'/1822331379'/' +m/83696968'/592366788'/'/' ``` - `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`. +- `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 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 `BIP85KEYS_MNEMONIC` or `BIP85KEYS_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. + +Every command takes `--index` / `-n` and the secret flags above, 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`: @@ -37,26 +64,7 @@ index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA1esgfi4OeaywgKh0o5r/8lMOUlUD/N+Yo 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` +### `bip85keys ssh pub` Prints one `authorized_keys` line to standard output: @@ -64,16 +72,16 @@ Prints one `authorized_keys` line to standard output: ssh-ed25519 ``` -The comment defaults to `bip85ssh/`; change it with `--comment`. +The comment defaults to `bip85keys/ssh/`; change it with `--comment`. -### `bip85ssh priv` +### `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`. -### `bip85ssh install <[user@]host> [-- ssh options...]` +### `bip85keys ssh install <[user@]host> [-- ssh options...]` Runs the system `ssh` to the host and, on the host: @@ -85,7 +93,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, password). Anything after `--` is passed to `ssh` unchanged. -### `bip85ssh ssh [ssh arguments...]` +### `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` @@ -93,23 +101,40 @@ 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 the `ssh` -command, which passes through `ssh`'s own exit status. +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 ./bip85ssh +make build # produces ./bip85keys 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 +bip85keys ssh pub -n 3 +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 ``` -- 2.49.1 From 8e24b0ae886117b5d2d8f190026debc5c4acb023 Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 6 Sep 2026 14:35:30 +0000 Subject: [PATCH 3/6] bip85keys: mnemonic only, and a command that produces it Per sneak: no xprv input. The mnemonic comes from a shell command given as a flag or an environment variable (for example `secret get foo`), from an environment variable holding it, or from a no-echo prompt. The file flag is dropped since the command covers it. Model: fable-5-1 --- bip85keys/README.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/bip85keys/README.md b/bip85keys/README.md index b4e4e4c..b32d9c9 100644 --- a/bip85keys/README.md +++ b/bip85keys/README.md @@ -1,8 +1,8 @@ # bip85keys -`bip85keys` turns a BIP-39 mnemonic (or a BIP-32 `xprv`) into key pairs that can -be recreated from that secret at any time. The same secret, key type and index -always give the same key. +`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. @@ -12,9 +12,9 @@ this PR) and `bip85keys age ...` for age identities (planned, see below). ## Derivation -The secret is turned into a key like this: +The mnemonic is turned into a key like this: -1. mnemonic -> BIP-39 seed (empty passphrase), or the `xprv` is parsed directly; +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; @@ -32,22 +32,26 @@ m/83696968'/592366788'/'/' - `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 secret +## Giving it the mnemonic -The secret is never taken as a command-line argument. It is looked for in this +The mnemonic itself is never 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 `BIP85KEYS_MNEMONIC` or `BIP85KEYS_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`. +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. +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 the secret flags above, and has -`--help`. `bip85keys --version` prints the version set at build time. +Every command takes `--index` / `-n` and `--mnemonic-command`, and has `--help`. +`bip85keys --version` prints the version set at build time. ## SSH keys: `bip85keys ssh` @@ -133,7 +137,7 @@ make check # fmt-check, lint (golangci-lint) and tests Examples: ``` -bip85keys ssh pub -n 3 +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 -- 2.49.1 From 933fa164c145a59416a5b8e891fba77722b69eff Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 6 Sep 2026 22:06:27 +0000 Subject: [PATCH 4/6] 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 --- bip85keys/README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/bip85keys/README.md b/bip85keys/README.md index b32d9c9..ad95c9b 100644 --- a/bip85keys/README.md +++ b/bip85keys/README.md @@ -23,13 +23,15 @@ The mnemonic is turned into a key like this: The path is: ``` -m/83696968'/592366788'/'/' +m/83696968'/'/' ``` - `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. +- `app` is the application number of the key type. There is no vendor id: the + path is meant as a standard any implementation can follow, not something tied + 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`. ## Giving it the mnemonic @@ -55,17 +57,16 @@ Every command takes `--index` / `-n` and `--mnemonic-command`, and has `--help`. ## 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. +Only ed25519 keys are produced. The application number is `838372`, so the +path is `m/83696968'/838372'/'`. 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 +index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZOtOczrc/7CQytcuFwt7s4r8KjkZWkwjLZWBaFKD+7 +index 1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEWY8+/gmHYVC4u0Y0I4FKs+eVUulTPHfk9VtXw1tMF ``` ### `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, password). Anything after `--` is passed to `ssh` unchanged. -### `bip85keys ssh ssh [ssh arguments...]` +### `bip85keys ssh to [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` @@ -108,13 +109,12 @@ 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. +`bip85keys age pub` and `bip85keys age priv` will use application number +`657169`, path `m/83696968'/657169'/'`; the 32 bytes from step 4 are clamped +as X25519 requires and encoded as an `AGE-SECRET-KEY-1...` identity, the same +steps `sneak/secret` takes in its `agehd` package. `secret` derives at a +vendor-specific path today; for its keys to equal this tool's it moves to this +path, which is a change in `secret`, not here. ## Adding a key type @@ -124,7 +124,7 @@ groups its commands. ## 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. ## Building and running @@ -140,5 +140,5 @@ 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 +bip85keys ssh to -n 3 user@example.com uptime ``` -- 2.49.1 From b2214ca5c5b83a0dac06f1a2e105139d0a0ed450 Mon Sep 17 00:00:00 2001 From: clawbot Date: Mon, 7 Sep 2026 13:29:02 +0000 Subject: [PATCH 5/6] keyfunc: the tool's name, chosen by sneak The name says what the tool is: a key is a function of the mnemonic and an index, computed when asked for and stored nowhere. Directory, module, binary and environment variables take it. Model: fable-5-1 --- {bip85keys => keyfunc}/README.md | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) rename {bip85keys => keyfunc}/README.md (80%) diff --git a/bip85keys/README.md b/keyfunc/README.md similarity index 80% rename from bip85keys/README.md rename to keyfunc/README.md index ad95c9b..74147ef 100644 --- a/bip85keys/README.md +++ b/keyfunc/README.md @@ -1,14 +1,14 @@ -# bip85keys +# keyfunc -`bip85keys` turns a BIP-39 mnemonic into key pairs that can be recreated from +`keyfunc` 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). +Commands are grouped by key type: `keyfunc ssh ...` for ed25519 SSH keys (in +this PR) and `keyfunc age ...` for age identities (planned, see below). ## Derivation @@ -43,9 +43,9 @@ order; the first one found wins: 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 +2. Environment variable `KEYFUNC_MNEMONIC_COMMAND`: the same, as a shell command held in the environment. -3. Environment variable `BIP85KEYS_MNEMONIC`: the mnemonic itself. +3. Environment variable `KEYFUNC_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 @@ -53,9 +53,9 @@ 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. +`keyfunc --version` prints the version set at build time. -## SSH keys: `bip85keys ssh` +## SSH keys: `keyfunc ssh` Only ed25519 keys are produced. The application number is `838372`, so the path is `m/83696968'/838372'/'`. The 32 bytes from step 4 are the ed25519 @@ -69,7 +69,7 @@ index 0: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJZOtOczrc/7CQytcuFwt7s4r8KjkZWkwjL index 1: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOEWY8+/gmHYVC4u0Y0I4FKs+eVUulTPHfk9VtXw1tMF ``` -### `bip85keys ssh pub` +### `keyfunc ssh pub` Prints one `authorized_keys` line to standard output: @@ -77,16 +77,16 @@ Prints one `authorized_keys` line to standard output: ssh-ed25519 ``` -The comment defaults to `bip85keys/ssh/`; change it with `--comment`. +The comment defaults to `keyfunc/ssh/`; change it with `--comment`. -### `bip85keys ssh priv` +### `keyfunc 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...]` +### `keyfunc ssh install <[user@]host> [-- ssh options...]` Runs the system `ssh` to the host and, on the host: @@ -98,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, password). Anything after `--` is passed to `ssh` unchanged. -### `bip85keys ssh to [ssh arguments...]` +### `keyfunc ssh to [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` @@ -106,10 +106,10 @@ 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) +## age identities: `keyfunc 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 use application number +`keyfunc age pub` and `keyfunc age priv` will use application number `657169`, path `m/83696968'/657169'/'`; the 32 bytes from step 4 are clamped as X25519 requires and encoded as an `AGE-SECRET-KEY-1...` identity, the same steps `sneak/secret` takes in its `agehd` package. `secret` derives at a @@ -130,15 +130,15 @@ which passes through `ssh`'s own exit status. ## Building and running ``` -make build # produces ./bip85keys +make build # produces ./keyfunc 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 to -n 3 user@example.com uptime +keyfunc ssh pub -n 3 --mnemonic-command 'secret get foo' +keyfunc ssh priv -n 3 > ~/.ssh/id_bip85_3 +keyfunc ssh install -n 3 user@example.com +keyfunc ssh to -n 3 user@example.com uptime ``` -- 2.49.1 From 7f7c9c67a024f5c888f809b28313621caaf7d480 Mon Sep 17 00:00:00 2001 From: clawbot Date: Mon, 7 Sep 2026 14:16:29 +0000 Subject: [PATCH 6/6] keyfunc: age keys, age encrypt and decrypt, and derived mnemonics are in scope Per sneak. The age commands derive the identity at the generic path and encrypt or decrypt with it, always including the identity's own recipient. The mnemonic command derives child mnemonics through BIP-85's own mnemonic application. Model: fable-5-1 --- keyfunc/README.md | 56 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/keyfunc/README.md b/keyfunc/README.md index 74147ef..a5b03d6 100644 --- a/keyfunc/README.md +++ b/keyfunc/README.md @@ -7,8 +7,10 @@ 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: `keyfunc ssh ...` for ed25519 SSH keys (in -this PR) and `keyfunc age ...` for age identities (planned, see below). +Commands are grouped by what is derived: `keyfunc ssh ...` for ed25519 SSH +keys, `keyfunc age ...` for age identities and for encrypting and decrypting +with them, and `keyfunc mnemonic ...` for child mnemonics derived from the +main one. ## Derivation @@ -106,15 +108,45 @@ 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: `keyfunc age` (planned) +## age identities: `keyfunc age` -Not in this PR, which delivers the SSH type first; it is the next type to add. -`keyfunc age pub` and `keyfunc age priv` will use application number -`657169`, path `m/83696968'/657169'/'`; the 32 bytes from step 4 are clamped -as X25519 requires and encoded as an `AGE-SECRET-KEY-1...` identity, the same -steps `sneak/secret` takes in its `agehd` package. `secret` derives at a -vendor-specific path today; for its keys to equal this tool's it moves to this -path, which is a change in `secret`, not here. +The application number is `657169`, path `m/83696968'/657169'/'`. The 32 +bytes from step 4 are clamped as X25519 requires and become an age identity, +the same steps `sneak/secret` takes in its `agehd` package. `secret` derives at +a vendor-specific path today; for its keys to equal this tool's it moves to +this path, which is a change in `secret`, not here. + +### `keyfunc age pub` + +Prints the recipient, the `age1...` public key, on one line. + +### `keyfunc age priv` + +Prints the identity, the `AGE-SECRET-KEY-1...` line, and nothing else. + +### `keyfunc age encrypt [-n N] [--to ...] [-o ] []` + +Encrypts the file (or standard input) with age. The recipients are the derived +identity's own recipient, plus any given with `--to`, so the same mnemonic can +always decrypt what it encrypted. Output goes to `-o` or standard output; +`--armor` writes the text form. Nothing is written except the output. + +### `keyfunc age decrypt [-n N] [-o ] []` + +Decrypts the file (or standard input) with the derived identity. Output goes to +`-o` or standard output. If the identity is not one of the recipients, the tool +says so and exits with status 1. + +## Derived mnemonics: `keyfunc mnemonic` + +### `keyfunc mnemonic [-n N] [--words 12|18|24]` + +Prints a child mnemonic derived from the main one, using BIP-85's own mnemonic +application (number `39`, English, path +`m/83696968'/39'/0'/'/'`, entropy taken as the specification says, +not through step 4). Default 12 words. A child mnemonic is a full mnemonic in +its own right: it can seed another `keyfunc`, another wallet, or `secret`, and +it never has to be written down, since it can be derived again. ## Adding a key type @@ -141,4 +173,8 @@ keyfunc ssh pub -n 3 --mnemonic-command 'secret get foo' keyfunc ssh priv -n 3 > ~/.ssh/id_bip85_3 keyfunc ssh install -n 3 user@example.com keyfunc ssh to -n 3 user@example.com uptime +keyfunc age pub -n 0 +keyfunc age encrypt -n 0 --armor -o notes.age notes.txt +keyfunc age decrypt -n 0 notes.age +keyfunc mnemonic -n 1 --words 24 ``` -- 2.49.1