package cbk_test import ( "strings" "testing" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tyler-smith/go-bip39" "sneak.berlin/go/secret/pkg/bip85" "sneak.berlin/go/x/pkg/bip39wordlist" "sneak.berlin/go/x/pkg/cbk" ) // The BIP85 specification publishes its test vectors against one master // key. specMnemonic is the BIP39 phrase that produces that master key, so // the whole mnemonic-to-entropy chain this package relies on can be // checked against the specification. const ( specMnemonic = "install scatter logic circle pencil average fall shoe " + "quantum disease suspect usage" specMasterKey = "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8" + "kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb" // specHex64 is the specification's HEX application vector for // m/83696968'/128169'/64'/0'. specHex64 = "492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e7" + "1aa2d645442f878555d078fd1f1f67e368976f04137b1f7a" + "0d19232136ca50c44614af72b5582a5c" ) // The keys below are the keypair for specMnemonic at index 0, pinned when // the BIP85 derivation was implemented. If this test starts failing, the // derivation has changed and existing keys are no longer reproducible. const ( vectorPublicKeyHex = "dbef711b3f340d336eb99cd8d0dbd491e1edce8eaad71cd66be228032a9a743e" vectorPrivateKeyHex = "ea3ceb0b02ee8e587779c63f4b7b3a21e950a213f1ec53cab608d13e8796e6dc" ) func TestSpecMnemonicProducesSpecMasterKey(t *testing.T) { t.Parallel() masterKey := specMasterKeyFromMnemonic(t) assert.Equal(t, specMasterKey, masterKey.String()) hex64, err := bip85.DeriveHex(masterKey, 64, 0) require.NoError(t, err) assert.Equal(t, specHex64, hex64) } func TestNewMatchesPinnedVector(t *testing.T) { t.Parallel() keypair, err := cbk.FromMnemonic(specMnemonic) require.NoError(t, err) assert.Equal(t, vectorPublicKeyHex, keypair.PublicKeyHex()) assert.Equal(t, vectorPrivateKeyHex, keypair.PrivateKeyHex()) } func TestPrivateKeyIsBIP85HexApplicationOutput(t *testing.T) { t.Parallel() masterKey := specMasterKeyFromMnemonic(t) for _, index := range []uint32{0, 1, 7} { expected, err := bip85.DeriveHex(masterKey, cbk.KeySize, index) require.NoError(t, err) keypair, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: index}) require.NoError(t, err) assert.Equal(t, expected, keypair.PrivateKeyHex(), "index %d", index) } } func TestNewIndexSelectsIndependentKeypairs(t *testing.T) { t.Parallel() first, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: 0}) require.NoError(t, err) second, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: 1}) require.NoError(t, err) assert.NotEqual(t, first.PublicKey, second.PublicKey) assert.NotEqual(t, first.PrivateKey, second.PrivateKey) fromMnemonic, err := cbk.FromMnemonic(specMnemonic) require.NoError(t, err) assert.Equal(t, first, fromMnemonic, "FromMnemonic uses index 0") } func TestNewRejectsIndexBeyondHardenedRange(t *testing.T) { t.Parallel() _, err := cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: cbk.MaxIndex + 1}) require.ErrorIs(t, err, cbk.ErrIndexTooLarge) _, err = cbk.New(cbk.Params{Mnemonic: specMnemonic, Index: cbk.MaxIndex}) require.NoError(t, err) } func TestNewNormalizesMnemonic(t *testing.T) { t.Parallel() canonical, err := cbk.FromMnemonic(specMnemonic) require.NoError(t, err) words := strings.Fields(specMnemonic) messy := " " + strings.ToUpper(words[0]) + "\t" + strings.Join(words[1:], " ") + "\n" normalized, err := cbk.New(cbk.Params{Mnemonic: messy, Index: 0}) require.NoError(t, err) assert.Equal(t, canonical, normalized) } func TestNewDifferentMnemonicsGiveDifferentKeys(t *testing.T) { t.Parallel() other, err := bip39wordlist.GenerateMnemonic(bip39wordlist.MaxWordCount) require.NoError(t, err) specKeypair, err := cbk.FromMnemonic(specMnemonic) require.NoError(t, err) otherKeypair, err := cbk.FromMnemonic(other) require.NoError(t, err) assert.NotEqual(t, specKeypair.PublicKey, otherKeypair.PublicKey) assert.NotEqual(t, specKeypair.PrivateKey, otherKeypair.PrivateKey) } func TestNewRejectsInvalidMnemonic(t *testing.T) { t.Parallel() _, err := cbk.FromMnemonic("abandon bogus about") require.ErrorIs(t, err, bip39wordlist.ErrInvalidWordCount) words := strings.Fields(specMnemonic) words[len(words)-1] = "bogus" _, err = cbk.FromMnemonic(strings.Join(words, " ")) require.ErrorIs(t, err, bip39wordlist.ErrUnknownWord) } func TestKeyAccessors(t *testing.T) { t.Parallel() keypair, err := cbk.FromMnemonic(specMnemonic) require.NoError(t, err) assert.Equal(t, keypair.PublicKey[:], keypair.PublicKeyBytes()) assert.Equal(t, keypair.PrivateKey[:], keypair.PrivateKeyBytes()) assert.Len(t, keypair.PublicKeyBytes(), cbk.KeySize) assert.Len(t, keypair.PrivateKeyBytes(), cbk.KeySize) assert.Equal(t, keypair.PublicKey.Hex(), keypair.PublicKeyHex()) assert.Equal(t, keypair.PrivateKey.Hex(), keypair.PrivateKeyHex()) assert.Len(t, keypair.PublicKeyHex(), 2*cbk.KeySize) } // specMasterKeyFromMnemonic builds the BIP32 master key from specMnemonic // the same way the package does, independently of it. func specMasterKeyFromMnemonic(t *testing.T) *hdkeychain.ExtendedKey { t.Helper() seed := bip39.NewSeed(specMnemonic, "") masterKey, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams) require.NoError(t, err) return masterKey }