204 lines
5.4 KiB
Go
204 lines
5.4 KiB
Go
package cbk_test
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"golang.org/x/crypto/nacl/box"
|
|
|
|
"sneak.berlin/go/x/pkg/bip39wordlist"
|
|
"sneak.berlin/go/x/pkg/cbk"
|
|
)
|
|
|
|
// vectorRepeats is how many times "abandon" appears in the standard BIP39
|
|
// test vector for all-zero 128-bit entropy, which ends with "about".
|
|
const vectorRepeats = bip39wordlist.MinWordCount - 1
|
|
|
|
// The keys below were pinned when the derivation was first implemented.
|
|
// If this test starts failing, the derivation has changed and every key
|
|
// anyone has derived with this package is no longer reproducible.
|
|
const (
|
|
vectorPublicKeyHex = "db9c1ec248805579d0b0fe2d5a4814f96009f66a11c91260487b79167f881b20"
|
|
|
|
vectorPrivateKeyHex = "eec3d5da4e348bd16df93b61c595bb5f75645f9aa468e72513be033a72b3d053"
|
|
)
|
|
|
|
// vectorMnemonic returns the standard BIP39 test vector for all-zero
|
|
// 128-bit entropy.
|
|
func vectorMnemonic() string {
|
|
return strings.Repeat("abandon ", vectorRepeats) + "about"
|
|
}
|
|
|
|
func TestNewMatchesPinnedVector(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
keypair, err := cbk.FromMnemonic(vectorMnemonic())
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, vectorPublicKeyHex, keypair.PublicKeyHex())
|
|
assert.Equal(t, vectorPrivateKeyHex, keypair.PrivateKeyHex())
|
|
}
|
|
|
|
func TestNewNormalizesMnemonic(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
canonical, err := cbk.FromMnemonic(vectorMnemonic())
|
|
require.NoError(t, err)
|
|
|
|
messy, err := cbk.New(cbk.Params{
|
|
Mnemonic: " ABANDON\tAbandon " + strings.Repeat("abandon ", 9) +
|
|
"\nabout\n",
|
|
Passphrase: "",
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, canonical, messy)
|
|
}
|
|
|
|
func TestNewPassphraseChangesKeys(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
plain, err := cbk.FromMnemonic(vectorMnemonic())
|
|
require.NoError(t, err)
|
|
|
|
extraWord := "TREZOR"
|
|
|
|
withExtraWord, err := cbk.New(cbk.Params{
|
|
Mnemonic: vectorMnemonic(),
|
|
Passphrase: extraWord,
|
|
})
|
|
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: vectorMnemonic(),
|
|
Passphrase: extraWord,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, withExtraWord, again, "derivation is deterministic")
|
|
}
|
|
|
|
func TestNewRejectsInvalidMnemonic(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := cbk.FromMnemonic("abandon bogus about")
|
|
require.ErrorIs(t, err, bip39wordlist.ErrInvalidWordCount)
|
|
|
|
_, err = cbk.FromMnemonic(strings.Repeat("abandon ", vectorRepeats) + "bogus")
|
|
require.ErrorIs(t, err, bip39wordlist.ErrUnknownWord)
|
|
}
|
|
|
|
func TestKeyAccessors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
keypair, err := cbk.FromMnemonic(vectorMnemonic())
|
|
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())
|
|
}
|
|
|
|
func TestKeyFromHexRoundTrip(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
keypair, err := cbk.FromMnemonic(vectorMnemonic())
|
|
require.NoError(t, err)
|
|
|
|
parsed, err := cbk.KeyFromHex(keypair.PublicKeyHex())
|
|
require.NoError(t, err)
|
|
assert.Equal(t, keypair.PublicKey, parsed)
|
|
}
|
|
|
|
func TestKeyFromHexRejectsBadInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
_, err := cbk.KeyFromHex("abcd")
|
|
require.ErrorIs(t, err, cbk.ErrInvalidKeyHex)
|
|
|
|
notHex := "zz" + keypairForTest(t).PublicKeyHex()[2:]
|
|
|
|
_, err = cbk.KeyFromHex(notHex)
|
|
require.ErrorIs(t, err, cbk.ErrInvalidKeyHex)
|
|
}
|
|
|
|
func TestSealOpenRoundTrip(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alice := keypairForTest(t)
|
|
bob := keypairForTest(t)
|
|
message := []byte("the quick brown fox jumps over the lazy dog")
|
|
|
|
sealed, err := alice.Seal(message, bob.PublicKey)
|
|
require.NoError(t, err)
|
|
assert.Len(t, sealed, cbk.NonceSize+len(message)+box.Overhead)
|
|
|
|
opened, err := bob.Open(sealed, alice.PublicKey)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, message, opened)
|
|
}
|
|
|
|
func TestSealUsesFreshNonces(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alice := keypairForTest(t)
|
|
bob := keypairForTest(t)
|
|
message := []byte("same message twice")
|
|
|
|
first, err := alice.Seal(message, bob.PublicKey)
|
|
require.NoError(t, err)
|
|
|
|
second, err := alice.Seal(message, bob.PublicKey)
|
|
require.NoError(t, err)
|
|
|
|
assert.NotEqual(t, first, second)
|
|
}
|
|
|
|
func TestOpenRejectsTamperingAndWrongKeys(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
alice := keypairForTest(t)
|
|
bob := keypairForTest(t)
|
|
eve := keypairForTest(t)
|
|
message := []byte("for bob only")
|
|
|
|
sealed, err := alice.Seal(message, bob.PublicKey)
|
|
require.NoError(t, err)
|
|
|
|
_, err = eve.Open(sealed, alice.PublicKey)
|
|
require.ErrorIs(t, err, cbk.ErrOpenFailed, "wrong recipient")
|
|
|
|
_, err = bob.Open(sealed, eve.PublicKey)
|
|
require.ErrorIs(t, err, cbk.ErrOpenFailed, "wrong sender")
|
|
|
|
tampered := append([]byte(nil), sealed...)
|
|
tampered[len(tampered)-1] ^= 0x01
|
|
|
|
_, err = bob.Open(tampered, alice.PublicKey)
|
|
require.ErrorIs(t, err, cbk.ErrOpenFailed, "modified ciphertext")
|
|
|
|
_, err = bob.Open(sealed[:cbk.NonceSize-1], alice.PublicKey)
|
|
require.ErrorIs(t, err, cbk.ErrSealedTooShort)
|
|
}
|
|
|
|
// keypairForTest derives a keypair from a freshly generated mnemonic.
|
|
func keypairForTest(t *testing.T) cbk.CryptoBoxKeypair {
|
|
t.Helper()
|
|
|
|
mnemonic, err := bip39wordlist.GenerateMnemonic(bip39wordlist.MaxWordCount)
|
|
require.NoError(t, err)
|
|
|
|
keypair, err := cbk.FromMnemonic(mnemonic)
|
|
require.NoError(t, err)
|
|
|
|
return keypair
|
|
}
|