check / check (push) Waiting to run
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
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
// Package derive turns a mnemonic into the bytes a key is made from.
|
|
package derive
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"sneak.berlin/go/keyfunc/internal/bip39"
|
|
"sneak.berlin/go/secret/pkg/bip85"
|
|
)
|
|
|
|
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
|
|
}
|