Skip to content

Commit 1c7582e

Browse files
committed
tests: add decryption test to bip324_tests
1 parent 990f0f8 commit 1c7582e

File tree

3 files changed

+79
-12
lines changed

3 files changed

+79
-12
lines changed

src/bip324.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ BIP324Cipher::BIP324Cipher(const CKey& key, Span<const std::byte> ent32) noexcep
3333
BIP324Cipher::BIP324Cipher(const CKey& key, const EllSwiftPubKey& pubkey) noexcept :
3434
m_key(key), m_our_pubkey(pubkey) {}
3535

36-
void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator) noexcept
36+
void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt) noexcept
3737
{
3838
// Determine salt (fixed string + network magic bytes)
3939
const auto& message_header = Params().MessageStart();
@@ -43,16 +43,17 @@ void BIP324Cipher::Initialize(const EllSwiftPubKey& their_pubkey, bool initiator
4343
ECDHSecret ecdh_secret = m_key.ComputeBIP324ECDHSecret(their_pubkey, m_our_pubkey, initiator);
4444

4545
// Derive encryption keys from shared secret, and initialize stream ciphers and AEADs.
46+
bool side = (initiator != self_decrypt);
4647
CHKDF_HMAC_SHA256_L32 hkdf(UCharCast(ecdh_secret.data()), ecdh_secret.size(), salt);
4748
std::array<std::byte, 32> hkdf_32_okm;
4849
hkdf.Expand32("initiator_L", UCharCast(hkdf_32_okm.data()));
49-
(initiator ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
50+
(side ? m_send_l_cipher : m_recv_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
5051
hkdf.Expand32("initiator_P", UCharCast(hkdf_32_okm.data()));
51-
(initiator ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
52+
(side ? m_send_p_cipher : m_recv_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
5253
hkdf.Expand32("responder_L", UCharCast(hkdf_32_okm.data()));
53-
(initiator ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
54+
(side ? m_recv_l_cipher : m_send_l_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
5455
hkdf.Expand32("responder_P", UCharCast(hkdf_32_okm.data()));
55-
(initiator ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
56+
(side ? m_recv_p_cipher : m_send_p_cipher).emplace(hkdf_32_okm, REKEY_INTERVAL);
5657

5758
// Derive garbage terminators from shared secret.
5859
hkdf.Expand32("garbage_terminators", UCharCast(hkdf_32_okm.data()));

src/bip324.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,12 @@ class BIP324Cipher
5252
/** Retrieve our public key. */
5353
const EllSwiftPubKey& GetOurPubKey() const noexcept { return m_our_pubkey; }
5454

55-
/** Initialize when the other side's public key is received. Can only be called once. */
56-
void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator) noexcept;
55+
/** Initialize when the other side's public key is received. Can only be called once.
56+
*
57+
* self_decrypt is only for testing, and swaps encryption/decryption keys, so that encryption
58+
* and decryption can be tested without knowing the other side's private key.
59+
*/
60+
void Initialize(const EllSwiftPubKey& their_pubkey, bool initiator, bool self_decrypt = false) noexcept;
5761

5862
/** Determine whether this cipher is fully initialized. */
5963
explicit operator bool() const noexcept { return m_send_l_cipher.has_value(); }

src/test/bip324_tests.cpp

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,13 @@ void TestBIP324PacketVector(
7070
BOOST_CHECK(Span{mid_send_garbage} == cipher.GetSendGarbageTerminator());
7171
BOOST_CHECK(Span{mid_recv_garbage} == cipher.GetReceiveGarbageTerminator());
7272

73+
// Vector of encrypted empty messages, encrypted in order to seek to the right position.
74+
std::vector<std::vector<std::byte>> dummies(in_idx);
75+
7376
// Seek to the numbered packet.
7477
for (uint32_t i = 0; i < in_idx; ++i) {
75-
std::vector<std::byte> dummy(cipher.EXPANSION);
76-
cipher.Encrypt({}, {}, false, dummy);
78+
dummies[i].resize(cipher.EXPANSION);
79+
cipher.Encrypt({}, {}, true, dummies[i]);
7780
}
7881

7982
// Construct contents and encrypt it.
@@ -93,9 +96,68 @@ void TestBIP324PacketVector(
9396
BOOST_CHECK(Span{out_ciphertext_endswith} == Span{ciphertext}.last(out_ciphertext_endswith.size()));
9497
}
9598

96-
// Note that we don't test decryption here, as the test vectors don't provide the other party's
97-
// private key, so we cannot act like them. See the bip324_cipher_roundtrip fuzz test for a test
98-
// that does cover decryption.
99+
for (unsigned error = 0; error <= 12; ++error) {
100+
// error selects a type of error introduced:
101+
// - error=0: no errors, decryption should be successful
102+
// - error=1: wrong side
103+
// - error=2..9: bit error in ciphertext
104+
// - error=10: bit error in aad
105+
// - error=11: extra 0x00 at end of aad
106+
// - error=12: message index wrong
107+
108+
// Instantiate self-decrypting BIP324 cipher.
109+
BIP324Cipher dec_cipher(key, ellswift_ours);
110+
BOOST_CHECK(!dec_cipher);
111+
BOOST_CHECK(dec_cipher.GetOurPubKey() == ellswift_ours);
112+
dec_cipher.Initialize(ellswift_theirs, (error == 1) ^ in_initiating, /*self_decrypt=*/true);
113+
BOOST_CHECK(dec_cipher);
114+
115+
// Compare session variables.
116+
BOOST_CHECK((Span{out_session_id} == dec_cipher.GetSessionID()) == (error != 1));
117+
BOOST_CHECK((Span{mid_send_garbage} == dec_cipher.GetSendGarbageTerminator()) == (error != 1));
118+
BOOST_CHECK((Span{mid_recv_garbage} == dec_cipher.GetReceiveGarbageTerminator()) == (error != 1));
119+
120+
// Seek to the numbered packet.
121+
if (in_idx == 0 && error == 12) continue;
122+
uint32_t dec_idx = in_idx ^ (error == 12 ? (1U << InsecureRandRange(16)) : 0);
123+
for (uint32_t i = 0; i < dec_idx; ++i) {
124+
unsigned use_idx = i < in_idx ? i : 0;
125+
bool dec_ignore{false};
126+
dec_cipher.DecryptLength(Span{dummies[use_idx]}.first(cipher.LENGTH_LEN));
127+
dec_cipher.Decrypt(Span{dummies[use_idx]}.subspan(cipher.LENGTH_LEN), {}, dec_ignore, {});
128+
}
129+
130+
// Construct copied (and possibly damaged) copy of ciphertext.
131+
// Decrypt length
132+
auto to_decrypt = ciphertext;
133+
if (error >= 2 && error <= 9) {
134+
to_decrypt[InsecureRandRange(to_decrypt.size())] ^= std::byte(1U << InsecureRandRange(8));
135+
}
136+
137+
// Decrypt length and resize ciphertext to accomodate.
138+
uint32_t dec_len = dec_cipher.DecryptLength(MakeByteSpan(to_decrypt).first(cipher.LENGTH_LEN));
139+
to_decrypt.resize(dec_len + cipher.EXPANSION);
140+
141+
// Construct copied (and possibly damaged) copy of aad.
142+
auto dec_aad = in_aad;
143+
if (error == 10) {
144+
if (in_aad.size() == 0) continue;
145+
dec_aad[InsecureRandRange(dec_aad.size())] ^= std::byte(1U << InsecureRandRange(8));
146+
}
147+
if (error == 11) dec_aad.push_back({});
148+
149+
// Decrypt contents.
150+
std::vector<std::byte> decrypted(dec_len);
151+
bool dec_ignore{false};
152+
bool dec_ok = dec_cipher.Decrypt(Span{to_decrypt}.subspan(cipher.LENGTH_LEN), dec_aad, dec_ignore, decrypted);
153+
154+
// Verify result.
155+
BOOST_CHECK(dec_ok == !error);
156+
if (dec_ok) {
157+
BOOST_CHECK(decrypted == contents);
158+
BOOST_CHECK(dec_ignore == in_ignore);
159+
}
160+
}
99161
}
100162

101163
} // namespace

0 commit comments

Comments
 (0)