Remove KeyFromHex, Seal, and Open with their errors and constants. The library only derives crypto_box keypairs from BIP39 mnemonics; callers use golang.org/x/crypto/nacl/box directly for everything else.
126 lines
4.0 KiB
Go
126 lines
4.0 KiB
Go
// Package cbk derives NaCl crypto_box keypairs (Curve25519) deterministically
|
|
// from BIP39 mnemonic phrases. That is all it does; the resulting keys are
|
|
// plain 32-byte crypto_box keys for use with golang.org/x/crypto/nacl/box.
|
|
//
|
|
// The same mnemonic and passphrase always produce the same keypair:
|
|
//
|
|
// 1. The mnemonic is validated and canonicalized with
|
|
// bip39wordlist.NormalizeWordlist (lowercase, single spaces).
|
|
// 2. The standard 64-byte BIP39 seed is computed from the canonical
|
|
// mnemonic and the optional passphrase with PBKDF2-HMAC-SHA512 (2048
|
|
// rounds, salt "mnemonic" + passphrase), exactly as a BIP39 wallet
|
|
// would.
|
|
// 3. The 32-byte crypto_box private key is derived from that seed with
|
|
// HKDF-SHA256, using no salt and DerivationInfo as the info string.
|
|
// 4. The public key is the Curve25519 scalar multiplication of the
|
|
// private key with the base point, via golang.org/x/crypto/nacl/box.
|
|
package cbk
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hkdf"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
|
|
"github.com/tyler-smith/go-bip39"
|
|
"golang.org/x/crypto/nacl/box"
|
|
|
|
"sneak.berlin/go/x/pkg/bip39wordlist"
|
|
)
|
|
|
|
const (
|
|
// KeySize is the length in bytes of a crypto_box public or private key.
|
|
KeySize = 32
|
|
|
|
// DerivationInfo is the HKDF info string that binds derived keys to
|
|
// this package. Changing it changes every derived key.
|
|
DerivationInfo = "sneak.berlin/go/x/pkg/cbk crypto_box v1"
|
|
)
|
|
|
|
// CryptoBoxKey is a 32-byte crypto_box key, either public or private.
|
|
type CryptoBoxKey [KeySize]byte
|
|
|
|
// Bytes returns a copy of the key as a byte slice.
|
|
func (k CryptoBoxKey) Bytes() []byte {
|
|
return k[:]
|
|
}
|
|
|
|
// Hex returns the key as 64 lowercase hexadecimal characters.
|
|
func (k CryptoBoxKey) Hex() string {
|
|
return hex.EncodeToString(k[:])
|
|
}
|
|
|
|
// Params configures New.
|
|
type Params struct {
|
|
// Mnemonic is the BIP39 mnemonic phrase. Any letter case and any
|
|
// whitespace between words is accepted; it is normalized before use.
|
|
Mnemonic string
|
|
|
|
// Passphrase is the optional BIP39 passphrase (sometimes called the
|
|
// "25th word"). It is used verbatim: it is case sensitive and is not
|
|
// trimmed. An empty passphrase is the common case.
|
|
Passphrase string
|
|
}
|
|
|
|
// CryptoBoxKeypair is a crypto_box public and private key pair.
|
|
type CryptoBoxKeypair struct {
|
|
PublicKey CryptoBoxKey
|
|
PrivateKey CryptoBoxKey
|
|
}
|
|
|
|
// New derives the keypair for the mnemonic and passphrase in p. If the
|
|
// mnemonic is not valid it returns an error wrapping one of the
|
|
// bip39wordlist sentinel errors.
|
|
func New(p Params) (CryptoBoxKeypair, error) {
|
|
mnemonic, err := bip39wordlist.NormalizeWordlist(p.Mnemonic)
|
|
if err != nil {
|
|
return CryptoBoxKeypair{}, err
|
|
}
|
|
|
|
seed := bip39.NewSeed(mnemonic, p.Passphrase)
|
|
|
|
privateKeyBytes, err := hkdf.Key(sha256.New, seed, nil, DerivationInfo, KeySize)
|
|
if err != nil {
|
|
return CryptoBoxKeypair{}, fmt.Errorf("cbk: deriving private key: %w", err)
|
|
}
|
|
|
|
publicKey, privateKey, err := box.GenerateKey(bytes.NewReader(privateKeyBytes))
|
|
if err != nil {
|
|
return CryptoBoxKeypair{}, fmt.Errorf("cbk: computing public key: %w", err)
|
|
}
|
|
|
|
return CryptoBoxKeypair{
|
|
PublicKey: CryptoBoxKey(*publicKey),
|
|
PrivateKey: CryptoBoxKey(*privateKey),
|
|
}, nil
|
|
}
|
|
|
|
// FromMnemonic derives the keypair for mnemonic with an empty passphrase.
|
|
// It is shorthand for New(Params{Mnemonic: mnemonic}).
|
|
func FromMnemonic(mnemonic string) (CryptoBoxKeypair, error) {
|
|
return New(Params{Mnemonic: mnemonic, Passphrase: ""})
|
|
}
|
|
|
|
// PublicKeyBytes returns a copy of the public key as a byte slice.
|
|
func (kp CryptoBoxKeypair) PublicKeyBytes() []byte {
|
|
return kp.PublicKey.Bytes()
|
|
}
|
|
|
|
// PrivateKeyBytes returns a copy of the private key as a byte slice.
|
|
func (kp CryptoBoxKeypair) PrivateKeyBytes() []byte {
|
|
return kp.PrivateKey.Bytes()
|
|
}
|
|
|
|
// PublicKeyHex returns the public key as 64 lowercase hexadecimal
|
|
// characters.
|
|
func (kp CryptoBoxKeypair) PublicKeyHex() string {
|
|
return kp.PublicKey.Hex()
|
|
}
|
|
|
|
// PrivateKeyHex returns the private key as 64 lowercase hexadecimal
|
|
// characters.
|
|
func (kp CryptoBoxKeypair) PrivateKeyHex() string {
|
|
return kp.PrivateKey.Hex()
|
|
}
|