2 Commits
Author SHA1 Message Date
sneak dc3a0150f3 Read --index and --words as decimal numbers (closes #75)
check / check (push) Successful in 3m14s
Both flags were uint32 flags, which the flag library reads with Go's
base-0 rules, so `-n 010` derived key 8 and `0x10` or `1_0` were
accepted. They are now string flags read by options.Decimal, which
takes decimal digits only: a leading zero stays decimal, and a base
prefix, an underscore or a sign is refused with an error naming the
flag, before the mnemonic is looked for. --help shows both as a
number, and the README says the index is a decimal number.

Model: opus-5-5
2026-10-12 23:10:13 +00:00
clawbot 45463fa35b Require secret at the commit that drops go-bip39 (closes #70)
check / check (push) Successful in 5m8s
sneak/secret now carries its own copy of go-bip39
(sneak/secret#130), so requiring it at that
commit, ef0ae90, lets go mod tidy drop go-bip39 from go.sum. Earlier,
secret renamed its module to sneak.berlin/go/secret
(sneak/secret#43), so the requirement, the
two imports of its bip85 package and the README sentence naming that
package move to the new path. The bip85 functions keyfunc calls are
unchanged, and no other requirement moves.

Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
2026-10-07 13:43:37 +02:00
4 changed files with 78 additions and 12 deletions
+3 -1
View File
@@ -100,7 +100,9 @@ m/83696968'/<app>'/<n>'
to one tool. Each key type's number is spelled the way BIP-85 spells 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): 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`). 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, a decimal number: flag `--index` / `-n`, default `0`. A
leading zero is still decimal (`-n 010` is index 10); a base prefix, an
underscore or a sign (`0x10`, `1_0`, `+1`) is refused.
## Giving it the mnemonic ## Giving it the mnemonic
+47
View File
@@ -2,6 +2,7 @@ package cli_test
import ( import (
"bytes" "bytes"
"strconv"
"strings" "strings"
"testing" "testing"
@@ -106,6 +107,36 @@ func TestAnIndexWithNoHardenedChildIsRefused(t *testing.T) {
require.Empty(t, out) require.Empty(t, out)
} }
func TestAnIndexWithALeadingZeroIsDecimal(t *testing.T) {
t.Setenv(mnemonic.Variable, example())
line := run(t, "ssh", "pub", "-n", "010")
require.Equal(t, run(t, "ssh", "pub", "-n", "10"), line)
require.True(t,
strings.HasSuffix(strings.TrimSpace(line), " keyfunc/ssh/10"),
)
}
// The mnemonic command in these runs fails, so a refusal naming the
// flag shows that the number was refused before the mnemonic was
// looked for.
func TestAnIndexThatIsNotDecimalDigitsIsRefused(t *testing.T) {
t.Parallel()
for _, index := range []string{"0x10", "1_0", "+1", "-1"} {
t.Run(index, func(t *testing.T) {
t.Parallel()
out, err := execute(t,
"ssh", "pub", "-n", index, "--mnemonic-command", "exit 1",
)
require.ErrorIs(t, err, strconv.ErrSyntax)
require.ErrorContains(t, err, "--index")
require.Empty(t, out)
})
}
}
func TestTheMnemonicCommandIsUsed(t *testing.T) { func TestTheMnemonicCommandIsUsed(t *testing.T) {
t.Setenv(mnemonic.Variable, "") t.Setenv(mnemonic.Variable, "")
@@ -138,6 +169,22 @@ func TestALengthTheToolDoesNotOfferIsRefused(t *testing.T) {
require.Empty(t, out) require.Empty(t, out)
} }
func TestTheWordCountIsDecimal(t *testing.T) {
t.Setenv(mnemonic.Variable, example())
words := strings.Fields(run(t, "mnemonic", "--words", "012"))
require.Len(t, words, twelve)
// As for the index, the failing mnemonic command shows that the
// number was refused first.
out, err := execute(t,
"mnemonic", "--words", "0x18", "--mnemonic-command", "exit 1",
)
require.ErrorIs(t, err, strconv.ErrSyntax)
require.ErrorContains(t, err, "--words")
require.Empty(t, out)
}
// run executes the tool with the given arguments and returns what it // run executes the tool with the given arguments and returns what it
// wrote to standard output. // wrote to standard output.
func run(t *testing.T, args ...string) string { func run(t *testing.T, args ...string) string {
+6 -5
View File
@@ -3,6 +3,7 @@ package mnemonic
import ( import (
"fmt" "fmt"
"strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sneak.berlin/go/keyfunc/internal/childmnemonic" "sneak.berlin/go/keyfunc/internal/childmnemonic"
@@ -31,9 +32,9 @@ func Command() *cobra.Command {
}, },
} }
cmd.Flags().Uint32( cmd.Flags().String(
"words", childmnemonic.DefaultWords, "words", strconv.Itoa(childmnemonic.DefaultWords),
"how long the child mnemonic is: 12, 18 or 24 words", "the `number` of words in the child mnemonic: 12, 18 or 24",
) )
return cmd return cmd
@@ -46,9 +47,9 @@ func derived(cmd *cobra.Command) (string, error) {
return "", err return "", err
} }
words, err := cmd.Flags().GetUint32("words") words, err := options.Decimal(cmd, "words")
if err != nil { if err != nil {
return "", fmt.Errorf("reading the word count: %w", err) return "", err
} }
parent, err := options.Mnemonic(cmd) parent, err := options.Mnemonic(cmd)
+22 -6
View File
@@ -4,6 +4,7 @@ package options
import ( import (
"fmt" "fmt"
"strconv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sneak.berlin/go/keyfunc/internal/mnemonic" "sneak.berlin/go/keyfunc/internal/mnemonic"
@@ -16,20 +17,35 @@ func Add(cmd *cobra.Command) {
"mnemonic-command", "", "mnemonic-command", "",
"shell command whose output is the mnemonic", "shell command whose output is the mnemonic",
) )
cmd.PersistentFlags().Uint32P( cmd.PersistentFlags().StringP(
"index", "n", 0, "index", "n", "0",
"which key to derive", "which key to derive, as a decimal `number`",
) )
} }
// Index returns the key index the user asked for. // Index returns the key index the user asked for.
func Index(cmd *cobra.Command) (uint32, error) { func Index(cmd *cobra.Command) (uint32, error) {
index, err := cmd.Flags().GetUint32("index") return Decimal(cmd, "index")
}
// Decimal reads the named string flag as a number written in decimal
// digits only: a leading zero is still decimal, and a base prefix, an
// underscore or a sign is refused. The back-quoted word in the usage
// of such a flag is what --help shows as its type.
func Decimal(cmd *cobra.Command, name string) (uint32, error) {
text, err := cmd.Flags().GetString(name)
if err != nil { if err != nil {
return 0, fmt.Errorf("reading the index: %w", err) return 0, fmt.Errorf("reading --%s: %w", name, err)
} }
return index, nil number, err := strconv.ParseUint(text, 10, 32)
if err != nil {
return 0, fmt.Errorf(
"reading --%s as a decimal number: %w", name, err,
)
}
return uint32(number), nil
} }
// Mnemonic returns the mnemonic, from the first source that has one. // Mnemonic returns the mnemonic, from the first source that has one.