Files
keyfunc/internal/derive/derive.go
clawbot d69bed722a
All checks were successful
check / check (push) Successful in 4s
The mnemonic command: child mnemonics (closes #4)
keyfunc mnemonic derives a 12, 18 or 24 word child mnemonic through BIP-85's own mnemonic application, with the specification's test vectors as tests. One review round, passed with no findings; the reviewer reproduced the vectors from an implementation written from the specification alone.

Model: opus-5 (implementation and review); fable-5-1 (landing)
2026-09-07 18:05:08 +02:00

95 lines
2.6 KiB
Go

// Package derive turns a mnemonic into the bytes a key is made from.
package derive
import (
"errors"
"fmt"
"git.eeqj.de/sneak/secret/pkg/bip85"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
bip39 "github.com/tyler-smith/go-bip39"
)
const (
// purpose is the number BIP-85 reserves for itself.
purpose = 83696968
// Size is how many bytes every key type is given.
Size = 32
// MaxIndex is the largest key index there is. Every element of
// the path is hardened, and a hardened BIP-32 child index stops
// here.
MaxIndex = 1<<31 - 1
)
// ErrIndexTooLarge is returned for a key index above MaxIndex. Such an
// index has no hardened child to derive, so there is no key to give
// back rather than a key nobody else would reproduce.
var ErrIndexTooLarge = errors.New("the key index is too large")
// Path returns the derivation path for an application number and a key
// index.
func Path(application, index uint32) string {
return fmt.Sprintf("m/%d'/%d'/%d'", purpose, application, index)
}
// CheckIndex refuses a key index above MaxIndex, so that every key
// type turns such an index down before deriving anything.
func CheckIndex(index uint32) error {
if index > MaxIndex {
return fmt.Errorf(
"%w: %d is above %d",
ErrIndexTooLarge, index, MaxIndex,
)
}
return nil
}
// Master returns the BIP-32 master key a mnemonic stands for: the
// mnemonic becomes a seed with an empty passphrase, and the seed
// becomes the key.
func Master(words string) (*hdkeychain.ExtendedKey, error) {
seed := bip39.NewSeed(words, "")
master, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
if err != nil {
return nil, fmt.Errorf("making the master key: %w", err)
}
return master, nil
}
// Bytes returns the bytes for an application number and a key index.
// The mnemonic becomes a seed with an empty passphrase, the seed
// becomes a master key, the master key gives BIP-85 entropy at the
// path, and the entropy seeds the generator the bytes are read from.
// An index above MaxIndex is refused before any of that happens.
func Bytes(words string, application, index uint32) ([]byte, error) {
err := CheckIndex(index)
if err != nil {
return nil, err
}
master, err := Master(words)
if err != nil {
return nil, err
}
entropy, err := bip85.DeriveBIP85Entropy(master, Path(application, index))
if err != nil {
return nil, fmt.Errorf("deriving entropy: %w", err)
}
out := make([]byte, Size)
_, err = bip85.NewBIP85DRNG(entropy).Read(out)
if err != nil {
return nil, fmt.Errorf("reading derived bytes: %w", err)
}
return out, nil
}