// 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 }