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 }