diff options
author | Pieter Wuille <pieter@wuille.net> | 2023-07-10 10:58:20 -0400 |
---|---|---|
committer | Pieter Wuille <pieter@wuille.net> | 2023-07-26 17:09:27 -0400 |
commit | 1c7582ead6e1119899922041c1af2b4169b0bc74 (patch) | |
tree | 62b62595c6148afa1ef56d0ccd4ba5d1c36a9aa7 | |
parent | 990f0f8da92a2d11828a7c05ca93bf0520b2a95e (diff) |
tests: add decryption test to bip324_tests
-rw-r--r-- | src/bip324.cpp | 11 | ||||
-rw-r--r-- | src/bip324.h | 8 | ||||
-rw-r--r-- | src/test/bip324_tests.cpp | 72 |
3 files changed, 79 insertions, 12 deletions
diff --git a/src/bip324.cpp b/src/bip324.cpp index eb223f1f04..7ed99e5585 100644 --- a/src/bip324.cpp +++ b/src/bip324.cpp @@ -33,7 +33,7 @@ BIP324Cipher::BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcep BIP324Cipher::BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept : m_key(key), m_our_pubkey(pubkey) {} -void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator) noexcept +void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt) noexcept { // Determine salt (fixed string + network magic bytes) const auto& message_header = Params().MessageStart(); @@ -43,16 +43,17 @@ void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator ECDHSecret ecdh_secret = m_key.ComputeBIP324ECDHSecret(their_pubkey, m_our_pubkey, initiator); // Derive encryption keys from shared secret, and initialize stream ciphers and AEADs. + bool side = (initiator != self_decrypt); CHKDF_HMAC_SHA256_L32 hkdf(UCharCast(ecdh_secret.data()), ecdh_secret.size(), salt); std::array<std::byte, 32> hkdf_32_okm; hkdf.Expand32("initiator_L", UCharCast(hkdf_32_okm.data())); - (initiator ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + (side ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); hkdf.Expand32("initiator_P", UCharCast(hkdf_32_okm.data())); - (initiator ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + (side ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); hkdf.Expand32("responder_L", UCharCast(hkdf_32_okm.data())); - (initiator ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + (side ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); hkdf.Expand32("responder_P", UCharCast(hkdf_32_okm.data())); - (initiator ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); + (side ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL); // Derive garbage terminators from shared secret. hkdf.Expand32("garbage_terminators", UCharCast(hkdf_32_okm.data())); diff --git a/src/bip324.h b/src/bip324.h index 1738356ee5..8d025c2ee3 100644 --- a/src/bip324.h +++ b/src/bip324.h @@ -52,8 +52,12 @@ public: /** Retrieve our public key. */ const EllSwiftPubKey& GetOurPubKey() const noexcept { return m_our_pubkey; } - /** Initialize when the other side's public key is received. Can only be called once. */ - void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator) noexcept; + /** Initialize when the other side's public key is received. Can only be called once. + * + * self_decrypt is only for testing, and swaps encryption/decryption keys, so that encryption + * and decryption can be tested without knowing the other side's private key. + */ + void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt = false) noexcept; /** Determine whether this cipher is fully initialized. */ explicit operator bool() const noexcept { return m_send_l_cipher.has_value(); } diff --git a/src/test/bip324_tests.cpp b/src/test/bip324_tests.cpp index 3b0ec8513e..ccb9e59e58 100644 --- a/src/test/bip324_tests.cpp +++ b/src/test/bip324_tests.cpp @@ -70,10 +70,13 @@ void TestBIP324PacketVector( BOOST_CHECK(Span{mid_send_garbage} == cipher.GetSendGarbageTerminator()); BOOST_CHECK(Span{mid_recv_garbage} == cipher.GetReceiveGarbageTerminator()); + // Vector of encrypted empty messages, encrypted in order to seek to the right position. + std::vector<std::vector<std::byte>> dummies(in_idx); + // Seek to the numbered packet. for (uint32_t i = 0; i < in_idx; ++i) { - std::vector<std::byte> dummy(cipher.EXPANSION); - cipher.Encrypt({}, {}, false, dummy); + dummies[i].resize(cipher.EXPANSION); + cipher.Encrypt({}, {}, true, dummies[i]); } // Construct contents and encrypt it. @@ -93,9 +96,68 @@ void TestBIP324PacketVector( BOOST_CHECK(Span{out_ciphertext_endswith} == Span{ciphertext}.last(out_ciphertext_endswith.size())); } - // Note that we don't test decryption here, as the test vectors don't provide the other party's - // private key, so we cannot act like them. See the bip324_cipher_roundtrip fuzz test for a test - // that does cover decryption. + for (unsigned error = 0; error <= 12; ++error) { + // error selects a type of error introduced: + // - error=0: no errors, decryption should be successful + // - error=1: wrong side + // - error=2..9: bit error in ciphertext + // - error=10: bit error in aad + // - error=11: extra 0x00 at end of aad + // - error=12: message index wrong + + // Instantiate self-decrypting BIP324 cipher. + BIP324Cipher dec_cipher(key, ellswift_ours); + BOOST_CHECK(!dec_cipher); + BOOST_CHECK(dec_cipher.GetOurPubKey() == ellswift_ours); + dec_cipher.Initialize(ellswift_theirs, (error == 1) ^ in_initiating, /*self_decrypt=*/true); + BOOST_CHECK(dec_cipher); + + // Compare session variables. + BOOST_CHECK((Span{out_session_id} == dec_cipher.GetSessionID()) == (error != 1)); + BOOST_CHECK((Span{mid_send_garbage} == dec_cipher.GetSendGarbageTerminator()) == (error != 1)); + BOOST_CHECK((Span{mid_recv_garbage} == dec_cipher.GetReceiveGarbageTerminator()) == (error != 1)); + + // Seek to the numbered packet. + if (in_idx == 0 && error == 12) continue; + uint32_t dec_idx = in_idx ^ (error == 12 ? (1U << InsecureRandRange(16)) : 0); + for (uint32_t i = 0; i < dec_idx; ++i) { + unsigned use_idx = i < in_idx ? i : 0; + bool dec_ignore{false}; + dec_cipher.DecryptLength(Span{dummies[use_idx]}.first(cipher.LENGTH_LEN)); + dec_cipher.Decrypt(Span{dummies[use_idx]}.subspan(cipher.LENGTH_LEN), {}, dec_ignore, {}); + } + + // Construct copied (and possibly damaged) copy of ciphertext. + // Decrypt length + auto to_decrypt = ciphertext; + if (error >= 2 && error <= 9) { + to_decrypt[InsecureRandRange(to_decrypt.size())] ^= std::byte(1U << InsecureRandRange(8)); + } + + // Decrypt length and resize ciphertext to accomodate. + uint32_t dec_len = dec_cipher.DecryptLength(MakeByteSpan(to_decrypt).first(cipher.LENGTH_LEN)); + to_decrypt.resize(dec_len + cipher.EXPANSION); + + // Construct copied (and possibly damaged) copy of aad. + auto dec_aad = in_aad; + if (error == 10) { + if (in_aad.size() == 0) continue; + dec_aad[InsecureRandRange(dec_aad.size())] ^= std::byte(1U << InsecureRandRange(8)); + } + if (error == 11) dec_aad.push_back({}); + + // Decrypt contents. + std::vector<std::byte> decrypted(dec_len); + bool dec_ignore{false}; + bool dec_ok = dec_cipher.Decrypt(Span{to_decrypt}.subspan(cipher.LENGTH_LEN), dec_aad, dec_ignore, decrypted); + + // Verify result. + BOOST_CHECK(dec_ok == !error); + if (dec_ok) { + BOOST_CHECK(decrypted == contents); + BOOST_CHECK(dec_ignore == in_ignore); + } + } } } // namespace |