/root/bitcoin/src/test/fuzz/bip324.cpp
Line | Count | Source |
1 | | // Copyright (c) 2023-present The Bitcoin Core developers |
2 | | // Distributed under the MIT software license, see the accompanying |
3 | | // file COPYING or http://www.opensource.org/licenses/mit-license.php. |
4 | | |
5 | | #include <bip324.h> |
6 | | #include <chainparams.h> |
7 | | #include <random.h> |
8 | | #include <span.h> |
9 | | #include <test/fuzz/FuzzedDataProvider.h> |
10 | | #include <test/fuzz/fuzz.h> |
11 | | #include <test/fuzz/util.h> |
12 | | |
13 | | #include <algorithm> |
14 | | #include <cstdint> |
15 | | #include <vector> |
16 | | |
17 | | namespace { |
18 | | |
19 | | void Initialize() |
20 | 0 | { |
21 | 0 | static ECC_Context ecc_context{}; |
22 | 0 | SelectParams(ChainType::MAIN); |
23 | 0 | } |
24 | | |
25 | | } // namespace |
26 | | |
27 | | FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize) |
28 | 0 | { |
29 | | // Test that BIP324Cipher's encryption and decryption agree. |
30 | | |
31 | | // Load keys from fuzzer. |
32 | 0 | FuzzedDataProvider provider(buffer.data(), buffer.size()); |
33 | | // Initiator key |
34 | 0 | CKey init_key = ConsumePrivateKey(provider, /*compressed=*/true); |
35 | 0 | if (!init_key.IsValid()) return; |
36 | | // Initiator entropy |
37 | 0 | auto init_ent = provider.ConsumeBytes<std::byte>(32); |
38 | 0 | init_ent.resize(32); |
39 | | // Responder key |
40 | 0 | CKey resp_key = ConsumePrivateKey(provider, /*compressed=*/true); |
41 | 0 | if (!resp_key.IsValid()) return; |
42 | | // Responder entropy |
43 | 0 | auto resp_ent = provider.ConsumeBytes<std::byte>(32); |
44 | 0 | resp_ent.resize(32); |
45 | | |
46 | | // Initialize ciphers by exchanging public keys. |
47 | 0 | BIP324Cipher initiator(init_key, init_ent); |
48 | 0 | assert(!initiator); |
49 | 0 | BIP324Cipher responder(resp_key, resp_ent); |
50 | 0 | assert(!responder); |
51 | 0 | initiator.Initialize(responder.GetOurPubKey(), true); |
52 | 0 | assert(initiator); |
53 | 0 | responder.Initialize(initiator.GetOurPubKey(), false); |
54 | 0 | assert(responder); |
55 | | |
56 | | // Initialize RNG deterministically, to generate contents and AAD. We assume that there are no |
57 | | // (potentially buggy) edge cases triggered by specific values of contents/AAD, so we can avoid |
58 | | // reading the actual data for those from the fuzzer input (which would need large amounts of |
59 | | // data). |
60 | 0 | InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>()); |
61 | | |
62 | | // Compare session IDs and garbage terminators. |
63 | 0 | assert(std::ranges::equal(initiator.GetSessionID(), responder.GetSessionID())); |
64 | 0 | assert(std::ranges::equal(initiator.GetSendGarbageTerminator(), responder.GetReceiveGarbageTerminator())); |
65 | 0 | assert(std::ranges::equal(initiator.GetReceiveGarbageTerminator(), responder.GetSendGarbageTerminator())); |
66 | | |
67 | 0 | LIMITED_WHILE(provider.remaining_bytes(), 1000) { |
68 | | // Mode: |
69 | | // - Bit 0: whether the ignore bit is set in message |
70 | | // - Bit 1: whether the responder (0) or initiator (1) sends |
71 | | // - Bit 2: whether this ciphertext will be corrupted (making it the last sent one) |
72 | | // - Bit 3-4: controls the maximum aad length (max 4095 bytes) |
73 | | // - Bit 5-7: controls the maximum content length (max 16383 bytes, for performance reasons) |
74 | 0 | unsigned mode = provider.ConsumeIntegral<uint8_t>(); |
75 | 0 | bool ignore = mode & 1; |
76 | 0 | bool from_init = mode & 2; |
77 | 0 | bool damage = mode & 4; |
78 | 0 | unsigned aad_length_bits = 4 * ((mode >> 3) & 3); |
79 | 0 | unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1); |
80 | 0 | unsigned length_bits = 2 * ((mode >> 5) & 7); |
81 | 0 | unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1); |
82 | | // Generate aad and content. |
83 | 0 | auto aad = rng.randbytes<std::byte>(aad_length); |
84 | 0 | auto contents = rng.randbytes<std::byte>(length); |
85 | | |
86 | | // Pick sides. |
87 | 0 | auto& sender{from_init ? initiator : responder}; |
88 | 0 | auto& receiver{from_init ? responder : initiator}; |
89 | | |
90 | | // Encrypt |
91 | 0 | std::vector<std::byte> ciphertext(length + initiator.EXPANSION); |
92 | 0 | sender.Encrypt(contents, aad, ignore, ciphertext); |
93 | | |
94 | | // Optionally damage 1 bit in either the ciphertext (corresponding to a change in transit) |
95 | | // or the aad (to make sure that decryption will fail if the AAD mismatches). |
96 | 0 | if (damage) { |
97 | 0 | unsigned damage_bit = provider.ConsumeIntegralInRange<unsigned>(0, |
98 | 0 | (ciphertext.size() + aad.size()) * 8U - 1U); |
99 | 0 | unsigned damage_pos = damage_bit >> 3; |
100 | 0 | std::byte damage_val{(uint8_t)(1U << (damage_bit & 7))}; |
101 | 0 | if (damage_pos >= ciphertext.size()) { |
102 | 0 | aad[damage_pos - ciphertext.size()] ^= damage_val; |
103 | 0 | } else { |
104 | 0 | ciphertext[damage_pos] ^= damage_val; |
105 | 0 | } |
106 | 0 | } |
107 | | |
108 | | // Decrypt length |
109 | 0 | uint32_t dec_length = receiver.DecryptLength(std::span{ciphertext}.first(initiator.LENGTH_LEN)); |
110 | 0 | if (!damage) { |
111 | 0 | assert(dec_length == length); |
112 | 0 | } else { |
113 | | // For performance reasons, don't try to decode if length got increased too much. |
114 | 0 | if (dec_length > 16384 + length) break; |
115 | | // Otherwise, just append zeros if dec_length > length. |
116 | 0 | ciphertext.resize(dec_length + initiator.EXPANSION); |
117 | 0 | } |
118 | | |
119 | | // Decrypt |
120 | 0 | std::vector<std::byte> decrypt(dec_length); |
121 | 0 | bool dec_ignore{false}; |
122 | 0 | bool ok = receiver.Decrypt(std::span{ciphertext}.subspan(initiator.LENGTH_LEN), aad, dec_ignore, decrypt); |
123 | | // Decryption *must* fail if the packet was damaged, and succeed if it wasn't. |
124 | 0 | assert(!ok == damage); |
125 | 0 | if (!ok) break; |
126 | 0 | assert(ignore == dec_ignore); |
127 | 0 | assert(decrypt == contents); |
128 | 0 | } |
129 | 0 | } |