Bitcoin Core  29.1.0
P2P Digital Currency
bip324.cpp
Go to the documentation of this file.
1 // Copyright (c) 2023 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>
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 {
21  static ECC_Context ecc_context{};
23 }
24 
25 } // namespace
26 
27 FUZZ_TARGET(bip324_cipher_roundtrip, .init=Initialize)
28 {
29  // Test that BIP324Cipher's encryption and decryption agree.
30 
31  // Load keys from fuzzer.
32  FuzzedDataProvider provider(buffer.data(), buffer.size());
33  // Initiator key
34  CKey init_key = ConsumePrivateKey(provider, /*compressed=*/true);
35  if (!init_key.IsValid()) return;
36  // Initiator entropy
37  auto init_ent = provider.ConsumeBytes<std::byte>(32);
38  init_ent.resize(32);
39  // Responder key
40  CKey resp_key = ConsumePrivateKey(provider, /*compressed=*/true);
41  if (!resp_key.IsValid()) return;
42  // Responder entropy
43  auto resp_ent = provider.ConsumeBytes<std::byte>(32);
44  resp_ent.resize(32);
45 
46  // Initialize ciphers by exchanging public keys.
47  BIP324Cipher initiator(init_key, init_ent);
48  assert(!initiator);
49  BIP324Cipher responder(resp_key, resp_ent);
50  assert(!responder);
51  initiator.Initialize(responder.GetOurPubKey(), true);
52  assert(initiator);
53  responder.Initialize(initiator.GetOurPubKey(), false);
54  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  InsecureRandomContext rng(provider.ConsumeIntegral<uint64_t>());
61 
62  // Compare session IDs and garbage terminators.
63  assert(std::ranges::equal(initiator.GetSessionID(), responder.GetSessionID()));
64  assert(std::ranges::equal(initiator.GetSendGarbageTerminator(), responder.GetReceiveGarbageTerminator()));
65  assert(std::ranges::equal(initiator.GetReceiveGarbageTerminator(), responder.GetSendGarbageTerminator()));
66 
67  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  unsigned mode = provider.ConsumeIntegral<uint8_t>();
75  bool ignore = mode & 1;
76  bool from_init = mode & 2;
77  bool damage = mode & 4;
78  unsigned aad_length_bits = 4 * ((mode >> 3) & 3);
79  unsigned aad_length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << aad_length_bits) - 1);
80  unsigned length_bits = 2 * ((mode >> 5) & 7);
81  unsigned length = provider.ConsumeIntegralInRange<unsigned>(0, (1 << length_bits) - 1);
82  // Generate aad and content.
83  auto aad = rng.randbytes<std::byte>(aad_length);
84  auto contents = rng.randbytes<std::byte>(length);
85 
86  // Pick sides.
87  auto& sender{from_init ? initiator : responder};
88  auto& receiver{from_init ? responder : initiator};
89 
90  // Encrypt
91  std::vector<std::byte> ciphertext(length + initiator.EXPANSION);
92  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  if (damage) {
97  unsigned damage_bit = provider.ConsumeIntegralInRange<unsigned>(0,
98  (ciphertext.size() + aad.size()) * 8U - 1U);
99  unsigned damage_pos = damage_bit >> 3;
100  std::byte damage_val{(uint8_t)(1U << (damage_bit & 7))};
101  if (damage_pos >= ciphertext.size()) {
102  aad[damage_pos - ciphertext.size()] ^= damage_val;
103  } else {
104  ciphertext[damage_pos] ^= damage_val;
105  }
106  }
107 
108  // Decrypt length
109  uint32_t dec_length = receiver.DecryptLength(Span{ciphertext}.first(initiator.LENGTH_LEN));
110  if (!damage) {
111  assert(dec_length == length);
112  } else {
113  // For performance reasons, don't try to decode if length got increased too much.
114  if (dec_length > 16384 + length) break;
115  // Otherwise, just append zeros if dec_length > length.
116  ciphertext.resize(dec_length + initiator.EXPANSION);
117  }
118 
119  // Decrypt
120  std::vector<std::byte> decrypt(dec_length);
121  bool dec_ignore{false};
122  bool ok = receiver.Decrypt(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  assert(!ok == damage);
125  if (!ok) break;
126  assert(ignore == dec_ignore);
127  assert(decrypt == contents);
128  }
129 }
std::vector< B > randbytes(size_t len) noexcept
Generate random bytes.
Definition: random.h:297
Span< const std::byte > GetSendGarbageTerminator() const noexcept
Get the Garbage Terminator to send.
Definition: bip324.h:90
CONSTEXPR_IF_NOT_DEBUG Span< C > first(std::size_t count) const noexcept
Definition: span.h:205
CONSTEXPR_IF_NOT_DEBUG Span< C > subspan(std::size_t offset) const noexcept
Definition: span.h:195
assert(!tx.IsCoinBase())
RAII class initializing and deinitializing global state for elliptic curve support.
Definition: key.h:321
ECC_Context ecc_context
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
void Initialize(const EllSwiftPubKey &their_pubkey, bool initiator, bool self_decrypt=false) noexcept
Initialize when the other side&#39;s public key is received.
Definition: bip324.cpp:34
The BIP324 packet cipher, encapsulating its key derivation, stream cipher, and AEAD.
Definition: bip324.h:19
Span< const std::byte > GetSessionID() const noexcept
Get the Session ID.
Definition: bip324.h:87
const EllSwiftPubKey & GetOurPubKey() const noexcept
Retrieve our public key.
Definition: bip324.h:54
xoroshiro128++ PRNG.
Definition: random.h:415
FUZZ_TARGET(bip324_cipher_roundtrip,.init=Initialize)
Definition: bip324.cpp:27
An encapsulated private key.
Definition: key.h:34
A Span is an object that can refer to a contiguous sequence of objects.
Definition: span.h:97
Span< const std::byte > GetReceiveGarbageTerminator() const noexcept
Get the expected Garbage Terminator to receive.
Definition: bip324.h:93
static constexpr unsigned EXPANSION
Definition: bip324.h:27
static constexpr unsigned LENGTH_LEN
Definition: bip324.h:25
void SelectParams(const ChainType chain)
Sets the params returned by Params() to those for the given chain type.
CKey ConsumePrivateKey(FuzzedDataProvider &fuzzed_data_provider, std::optional< bool > compressed) noexcept
Definition: util.cpp:230
bool IsValid() const
Check whether this private key is valid.
Definition: key.h:123