Drop the BIP39 passphrase from cbk
The seed is always computed with an empty passphrase; Params is just the mnemonic and the BIP85 index.
This commit is contained in:
@@ -66,9 +66,9 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same mnemonic (and optional passphrase) always yields the same
|
// The same mnemonic always yields the same keypair, on any machine.
|
||||||
// keypair, on any machine. The keys are plain crypto_box keys for use
|
// The keys are plain crypto_box keys for use with
|
||||||
// with golang.org/x/crypto/nacl/box.
|
// golang.org/x/crypto/nacl/box.
|
||||||
keypair, err := cbk.New(cbk.Params{Mnemonic: mnemonic})
|
keypair, err := cbk.New(cbk.Params{Mnemonic: mnemonic})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -138,8 +138,8 @@ independent keypairs:
|
|||||||
1. The mnemonic is validated and canonicalized with
|
1. The mnemonic is validated and canonicalized with
|
||||||
`bip39wordlist.NormalizeWordlist`.
|
`bip39wordlist.NormalizeWordlist`.
|
||||||
2. The standard 64-byte BIP39 seed is computed with PBKDF2-HMAC-SHA512 (2048
|
2. The standard 64-byte BIP39 seed is computed with PBKDF2-HMAC-SHA512 (2048
|
||||||
rounds, salt `"mnemonic" + passphrase`), exactly as a BIP39 wallet would, so
|
rounds, salt `"mnemonic"`), exactly as a BIP39 wallet would with an empty
|
||||||
the optional passphrase works as the usual "25th word".
|
passphrase. BIP39 passphrases are deliberately not supported.
|
||||||
3. The seed becomes a BIP32 master key.
|
3. The seed becomes a BIP32 master key.
|
||||||
4. BIP85's HEX application derives 32 bytes of entropy at
|
4. BIP85's HEX application derives 32 bytes of entropy at
|
||||||
`m/83696968'/128169'/32'/{index}'`; those bytes are the private key.
|
`m/83696968'/128169'/32'/{index}'`; those bytes are the private key.
|
||||||
@@ -148,8 +148,8 @@ independent keypairs:
|
|||||||
The resulting keys are plain 32-byte crypto_box keys for use with
|
The resulting keys are plain 32-byte crypto_box keys for use with
|
||||||
`golang.org/x/crypto/nacl/box`. The API:
|
`golang.org/x/crypto/nacl/box`. The API:
|
||||||
|
|
||||||
- `cbk.New(cbk.Params{Mnemonic, Passphrase, Index})` and `cbk.FromMnemonic(m)`
|
- `cbk.New(cbk.Params{Mnemonic, Index})` and `cbk.FromMnemonic(m)` (index 0)
|
||||||
(no passphrase, index 0) return a `CryptoBoxKeypair`.
|
return a `CryptoBoxKeypair`.
|
||||||
- `CryptoBoxKey.Hex()`, `CryptoBoxKey.Bytes()`, and the `PublicKeyHex`,
|
- `CryptoBoxKey.Hex()`, `CryptoBoxKey.Bytes()`, and the `PublicKeyHex`,
|
||||||
`PrivateKeyHex`, `PublicKeyBytes`, `PrivateKeyBytes` accessors on the keypair.
|
`PrivateKeyHex`, `PublicKeyBytes`, `PrivateKeyBytes` accessors on the keypair.
|
||||||
|
|
||||||
|
|||||||
+14
-17
@@ -3,13 +3,14 @@
|
|||||||
// resulting keys are plain 32-byte crypto_box keys for use with
|
// resulting keys are plain 32-byte crypto_box keys for use with
|
||||||
// golang.org/x/crypto/nacl/box.
|
// golang.org/x/crypto/nacl/box.
|
||||||
//
|
//
|
||||||
// The same mnemonic, passphrase, and index always produce the same keypair:
|
// The same mnemonic and index always produce the same keypair:
|
||||||
//
|
//
|
||||||
// 1. The mnemonic is validated and canonicalized with
|
// 1. The mnemonic is validated and canonicalized with
|
||||||
// bip39wordlist.NormalizeWordlist (lowercase, single spaces).
|
// bip39wordlist.NormalizeWordlist (lowercase, single spaces).
|
||||||
// 2. The standard 64-byte BIP39 seed is computed from the canonical
|
// 2. The standard 64-byte BIP39 seed is computed from the canonical
|
||||||
// mnemonic and the optional passphrase with PBKDF2-HMAC-SHA512 (2048
|
// mnemonic with PBKDF2-HMAC-SHA512 (2048 rounds, salt "mnemonic"),
|
||||||
// rounds, salt "mnemonic" + passphrase), exactly as a wallet would.
|
// exactly as a wallet would with an empty passphrase. BIP39
|
||||||
|
// passphrases are deliberately not supported.
|
||||||
// 3. The seed becomes a BIP32 master key.
|
// 3. The seed becomes a BIP32 master key.
|
||||||
// 4. BIP85 (sneak.berlin/go/secret/pkg/bip85) derives 32 bytes of
|
// 4. BIP85 (sneak.berlin/go/secret/pkg/bip85) derives 32 bytes of
|
||||||
// application entropy from the master key with the HEX application,
|
// application entropy from the master key with the HEX application,
|
||||||
@@ -70,11 +71,6 @@ type Params struct {
|
|||||||
// whitespace between words is accepted; it is normalized before use.
|
// whitespace between words is accepted; it is normalized before use.
|
||||||
Mnemonic string
|
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
|
|
||||||
|
|
||||||
// Index selects which of the keypairs derivable from the mnemonic to
|
// Index selects which of the keypairs derivable from the mnemonic to
|
||||||
// return. It is the final component of the BIP85 derivation path, so
|
// return. It is the final component of the BIP85 derivation path, so
|
||||||
// each index yields an independent keypair. Zero is the first.
|
// each index yields an independent keypair. Zero is the first.
|
||||||
@@ -87,9 +83,9 @@ type CryptoBoxKeypair struct {
|
|||||||
PrivateKey CryptoBoxKey
|
PrivateKey CryptoBoxKey
|
||||||
}
|
}
|
||||||
|
|
||||||
// New derives the keypair for the mnemonic, passphrase, and index in p. If
|
// New derives the keypair for the mnemonic and index in p. If the mnemonic
|
||||||
// the mnemonic is not valid it returns an error wrapping one of the
|
// is not valid it returns an error wrapping one of the bip39wordlist
|
||||||
// bip39wordlist sentinel errors.
|
// sentinel errors.
|
||||||
func New(p Params) (CryptoBoxKeypair, error) {
|
func New(p Params) (CryptoBoxKeypair, error) {
|
||||||
if p.Index > MaxIndex {
|
if p.Index > MaxIndex {
|
||||||
return CryptoBoxKeypair{}, fmt.Errorf("%w: %d", ErrIndexTooLarge, p.Index)
|
return CryptoBoxKeypair{}, fmt.Errorf("%w: %d", ErrIndexTooLarge, p.Index)
|
||||||
@@ -100,10 +96,11 @@ func New(p Params) (CryptoBoxKeypair, error) {
|
|||||||
return CryptoBoxKeypair{}, err
|
return CryptoBoxKeypair{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The network parameters only choose the version bytes used when a
|
// The seed is always computed with an empty BIP39 passphrase. The
|
||||||
// key is serialized; they do not affect derivation.
|
// network parameters only choose the version bytes used when a key is
|
||||||
|
// serialized; they do not affect derivation.
|
||||||
masterKey, err := hdkeychain.NewMaster(
|
masterKey, err := hdkeychain.NewMaster(
|
||||||
bip39.NewSeed(mnemonic, p.Passphrase), &chaincfg.MainNetParams,
|
bip39.NewSeed(mnemonic, ""), &chaincfg.MainNetParams,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return CryptoBoxKeypair{}, fmt.Errorf("cbk: deriving BIP32 master key: %w", err)
|
return CryptoBoxKeypair{}, fmt.Errorf("cbk: deriving BIP32 master key: %w", err)
|
||||||
@@ -130,10 +127,10 @@ func New(p Params) (CryptoBoxKeypair, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromMnemonic derives the keypair for mnemonic with an empty passphrase
|
// FromMnemonic derives the keypair for mnemonic at index zero. It is
|
||||||
// at index zero. It is shorthand for New(Params{Mnemonic: mnemonic}).
|
// shorthand for New(Params{Mnemonic: mnemonic}).
|
||||||
func FromMnemonic(mnemonic string) (CryptoBoxKeypair, error) {
|
func FromMnemonic(mnemonic string) (CryptoBoxKeypair, error) {
|
||||||
return New(Params{Mnemonic: mnemonic, Passphrase: "", Index: 0})
|
return New(Params{Mnemonic: mnemonic, Index: 0})
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicKeyBytes returns a copy of the public key as a byte slice.
|
// PublicKeyBytes returns a copy of the public key as a byte slice.
|
||||||
|
|||||||
+9
-53
@@ -16,9 +16,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// The BIP85 specification publishes its test vectors against one master
|
// The BIP85 specification publishes its test vectors against one master
|
||||||
// key. specMnemonic is the BIP39 phrase that produces that master key with
|
// key. specMnemonic is the BIP39 phrase that produces that master key, so
|
||||||
// an empty passphrase, so the whole mnemonic-to-entropy chain this package
|
// the whole mnemonic-to-entropy chain this package relies on can be
|
||||||
// relies on can be checked against the specification.
|
// checked against the specification.
|
||||||
const (
|
const (
|
||||||
specMnemonic = "install scatter logic circle pencil average fall shoe " +
|
specMnemonic = "install scatter logic circle pencil average fall shoe " +
|
||||||
"quantum disease suspect usage"
|
"quantum disease suspect usage"
|
||||||
@@ -72,11 +72,7 @@ func TestPrivateKeyIsBIP85HexApplicationOutput(t *testing.T) {
|
|||||||
expected, err := bip85.DeriveHex(masterKey, cbk.KeySize, index)
|
expected, err := bip85.DeriveHex(masterKey, cbk.KeySize, index)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
keypair, err := cbk.New(cbk.Params{
|
keypair, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: index})
|
||||||
Mnemonic: specMnemonic,
|
|
||||||
Passphrase: "",
|
|
||||||
Index: index,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, expected, keypair.PrivateKeyHex(), "index %d", index)
|
assert.Equal(t, expected, keypair.PrivateKeyHex(), "index %d", index)
|
||||||
@@ -86,10 +82,10 @@ func TestPrivateKeyIsBIP85HexApplicationOutput(t *testing.T) {
|
|||||||
func TestNewIndexSelectsIndependentKeypairs(t *testing.T) {
|
func TestNewIndexSelectsIndependentKeypairs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
first, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Passphrase: "", Index: 0})
|
first, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: 0})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
second, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Passphrase: "", Index: 1})
|
second, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: 1})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.NotEqual(t, first.PublicKey, second.PublicKey)
|
assert.NotEqual(t, first.PublicKey, second.PublicKey)
|
||||||
@@ -103,18 +99,10 @@ func TestNewIndexSelectsIndependentKeypairs(t *testing.T) {
|
|||||||
func TestNewRejectsIndexBeyondHardenedRange(t *testing.T) {
|
func TestNewRejectsIndexBeyondHardenedRange(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
_, err := cbk.New(cbk.Params{
|
_, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: cbk.MaxIndex + 1})
|
||||||
Mnemonic: specMnemonic,
|
|
||||||
Passphrase: "",
|
|
||||||
Index: cbk.MaxIndex + 1,
|
|
||||||
})
|
|
||||||
require.ErrorIs(t, err, cbk.ErrIndexTooLarge)
|
require.ErrorIs(t, err, cbk.ErrIndexTooLarge)
|
||||||
|
|
||||||
_, err = cbk.New(cbk.Params{
|
_, err = cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: cbk.MaxIndex})
|
||||||
Mnemonic: specMnemonic,
|
|
||||||
Passphrase: "",
|
|
||||||
Index: cbk.MaxIndex,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,44 +116,12 @@ func TestNewNormalizesMnemonic(t *testing.T) {
|
|||||||
messy := " " + strings.ToUpper(words[0]) + "\t" +
|
messy := " " + strings.ToUpper(words[0]) + "\t" +
|
||||||
strings.Join(words[1:], " ") + "\n"
|
strings.Join(words[1:], " ") + "\n"
|
||||||
|
|
||||||
normalized, err := cbk.New(cbk.Params{
|
normalized, err := cbk.New(cbk.Params{Mnemonic: messy, Index: 0})
|
||||||
Mnemonic: messy,
|
|
||||||
Passphrase: "",
|
|
||||||
Index: 0,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, canonical, normalized)
|
assert.Equal(t, canonical, normalized)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewPassphraseChangesKeys(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
plain, err := cbk.FromMnemonic(specMnemonic)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
extraWord := "TREZOR"
|
|
||||||
|
|
||||||
withExtraWord, err := cbk.New(cbk.Params{
|
|
||||||
Mnemonic: specMnemonic,
|
|
||||||
Passphrase: extraWord,
|
|
||||||
Index: 0,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.NotEqual(t, plain.PublicKey, withExtraWord.PublicKey)
|
|
||||||
assert.NotEqual(t, plain.PrivateKey, withExtraWord.PrivateKey)
|
|
||||||
|
|
||||||
again, err := cbk.New(cbk.Params{
|
|
||||||
Mnemonic: specMnemonic,
|
|
||||||
Passphrase: extraWord,
|
|
||||||
Index: 0,
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, withExtraWord, again, "derivation is deterministic")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewDifferentMnemonicsGiveDifferentKeys(t *testing.T) {
|
func TestNewDifferentMnemonicsGiveDifferentKeys(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user