The mnemonic command: child mnemonics (closes #4)
All checks were successful
check / check (push) Successful in 19s

keyfunc mnemonic prints a child mnemonic derived from the main one with
BIP-85's own mnemonic application, in English, at 12, 18 or 24 words.
Its entropy is the BIP-85 entropy cut to the length the word count
needs, which is what that application asks for, so it does not go
through the generator the other key types read their bytes from. The
BIP-85 specification's own test vectors for the application are the
tests.

Two pieces the derivation package already had inside one function are
now named: the master key a mnemonic stands for, and the refusal of a
key index that has no hardened child. Both key types call them.

Model: opus-5
This commit is contained in:
clawbot
2026-09-07 15:43:10 +00:00
parent 279cba6bcf
commit cc68529c5f
6 changed files with 309 additions and 10 deletions

View File

@@ -0,0 +1,63 @@
// Package childmnemonic derives a mnemonic from another mnemonic.
package childmnemonic
import (
"errors"
"fmt"
"git.eeqj.de/sneak/keyfunc/internal/derive"
"git.eeqj.de/sneak/secret/pkg/bip85"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
bip39 "github.com/tyler-smith/go-bip39"
)
// english is the number BIP-85 gives the English word list.
const english = 0
// The lengths a child mnemonic may have. BIP-85 also allows 15 and 21
// words; these three are the ones this tool offers.
const (
twelve = 12
eighteen = 18
twentyFour = 24
)
// DefaultWords is how long a child mnemonic is when the user does not
// say.
const DefaultWords = twelve
// ErrWordCount is returned for a length this tool does not offer.
var ErrWordCount = errors.New("a child mnemonic has 12, 18 or 24 words")
// Derive returns the English child mnemonic of that many words at that
// key index, taken from the master key with BIP-85's own mnemonic
// application, number 39. The words come straight from the BIP-85
// entropy, cut to the length the word count needs, rather than from
// the generator the other key types read their bytes from.
func Derive(
master *hdkeychain.ExtendedKey,
words, index uint32,
) (string, error) {
switch words {
case twelve, eighteen, twentyFour:
default:
return "", fmt.Errorf("%w, not %d", ErrWordCount, words)
}
err := derive.CheckIndex(index)
if err != nil {
return "", err
}
entropy, err := bip85.DeriveBIP39Entropy(master, english, words, index)
if err != nil {
return "", fmt.Errorf("deriving entropy: %w", err)
}
child, err := bip39.NewMnemonic(entropy)
if err != nil {
return "", fmt.Errorf("turning the entropy into words: %w", err)
}
return child, nil
}