Skip to content

Commit

Permalink
RFC8439Encrypt can take multiple plaintexts
Browse files Browse the repository at this point in the history
  • Loading branch information
dhruv committed Jun 29, 2022
1 parent aceb08e commit 5c3dce7
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 27 deletions.
5 changes: 3 additions & 2 deletions src/bench/rfc8439.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ std::array<std::byte, 12> nonce = {std::byte{0x00}, std::byte{0x01}, std::byte{0

static void RFC8439_AEAD(benchmark::Bench& bench, size_t plaintext_size, bool include_decryption)
{
std::vector<std::byte> plaintext_in(plaintext_size, std::byte{0x00});
const std::vector<std::byte> plaintext_in(plaintext_size, std::byte{0x00});
std::vector<Span<const std::byte>> ins{plaintext_in};

bench.batch(plaintext_size).unit("byte").run([&] {
auto encrypted = RFC8439Encrypt(aad, zero_key, nonce, plaintext_in);
auto encrypted = RFC8439Encrypt(aad, zero_key, nonce, ins);

if (include_decryption) {
auto decrypted = RFC8439Decrypt(aad, zero_key, nonce, encrypted);
Expand Down
15 changes: 2 additions & 13 deletions src/crypto/bip324_suite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,8 @@ bool BIP324CipherSuite::Crypt(Span<const std::byte> input, Span<std::byte> outpu
uint32_t ciphertext_len = BIP324_HEADER_LEN + input.size();
WriteLE32(reinterpret_cast<unsigned char*>(&ciphertext_len), ciphertext_len);

std::vector<std::byte> input_vec;
input_vec.resize(BIP324_HEADER_LEN + input.size());

// TODO: this can be optimized by changing the RFC8439Encrypt interface to accept a list of inputs.
// But, at the moment, there's a potential bug in out ChaCha20 implementation for plaintexts that
// are not a multiple of 64 bytes -- the rest of the "block" is discarded. An update is in progress
// which will help here.
memcpy(input_vec.data(), &flags, BIP324_HEADER_LEN);
if (!input.empty()) {
memcpy(input_vec.data() + BIP324_HEADER_LEN, input.data(), input.size());
}

auto encrypted = RFC8439Encrypt({}, payload_key, nonce, input_vec);
std::vector<Span<const std::byte>> ins{Span{reinterpret_cast<std::byte*>(&flags), BIP324_HEADER_LEN}, input};
auto encrypted = RFC8439Encrypt({}, payload_key, nonce, ins);

auto write_pos = output.data();
fsc20.Crypt({reinterpret_cast<std::byte*>(&ciphertext_len), BIP324_LENGTH_FIELD_LEN},
Expand Down
30 changes: 23 additions & 7 deletions src/crypto/rfc8439.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,23 @@ std::array<std::byte, POLY1305_KEYLEN> GetPoly1305Key(ChaCha20& c20)
return polykey;
}

void RFC8439Crypt(ChaCha20& c20, Span<const std::byte> in_bytes, Span<std::byte> out_bytes)
void RFC8439Crypt(ChaCha20& c20, const std::vector<Span<const std::byte>>& in_bytes, Span<std::byte> out_bytes)
{
assert(in_bytes.size() == out_bytes.size());
size_t total_bytes = 0;
for (auto in: in_bytes) {
total_bytes += in.size();
}
assert(total_bytes == out_bytes.size());
c20.SeekRFC8439(1);
c20.Crypt(reinterpret_cast<const unsigned char*>(in_bytes.data()), reinterpret_cast<unsigned char*>(out_bytes.data()), in_bytes.size());

auto write_pos = out_bytes.data();
for (auto in: in_bytes) {
c20.Crypt(reinterpret_cast<const unsigned char*>(in.data()), reinterpret_cast<unsigned char*>(write_pos), in.size());
write_pos += in.size();
}
}

RFC8439Encrypted RFC8439Encrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, Span<const std::byte> plaintext)
RFC8439Encrypted RFC8439Encrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, const std::vector<Span<const std::byte>>& plaintexts)
{
assert(key.size() == RFC8439_KEYLEN);
RFC8439Encrypted ret;
Expand All @@ -74,8 +83,13 @@ RFC8439Encrypted RFC8439Encrypt(Span<const std::byte> aad, Span<const std::byte>

std::array<std::byte, POLY1305_KEYLEN> polykey{GetPoly1305Key(c20)};

ret.ciphertext.resize(plaintext.size());
RFC8439Crypt(c20, plaintext, ret.ciphertext);
size_t total_bytes = 0;
for (auto plaintext: plaintexts) {
total_bytes += plaintext.size();
}

ret.ciphertext.resize(total_bytes);
RFC8439Crypt(c20, plaintexts, ret.ciphertext);
ret.tag = ComputeRFC8439Tag(polykey, aad, ret.ciphertext);
return ret;
}
Expand All @@ -101,6 +115,8 @@ RFC8439Decrypted RFC8439Decrypt(Span<const std::byte> aad, Span<const std::byte>

ret.success = true;
ret.plaintext.resize(encrypted.ciphertext.size());
RFC8439Crypt(c20, encrypted.ciphertext, ret.plaintext);

std::vector<Span<const std::byte>> ins{encrypted.ciphertext};
RFC8439Crypt(c20, ins, ret.plaintext);
return ret;
}
2 changes: 1 addition & 1 deletion src/crypto/rfc8439.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct RFC8439Decrypted {
std::vector<std::byte> plaintext;
};

RFC8439Encrypted RFC8439Encrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, Span<const std::byte> plaintext);
RFC8439Encrypted RFC8439Encrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, const std::vector<Span<const std::byte>>& plaintexts);

RFC8439Decrypted RFC8439Decrypt(Span<const std::byte> aad, Span<const std::byte> key, const std::array<std::byte, 12>& nonce, const RFC8439Encrypted& encrypted);
#endif // BITCOIN_CRYPTO_RFC8439_H
5 changes: 2 additions & 3 deletions src/test/crypto_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -807,8 +807,6 @@ static void TestBIP324CipherSuite(const std::string& hex_input, const std::strin

BOOST_AUTO_TEST_CASE(bip324_cipher_suite_testvectors)
{
/* test bip324 cipher suite */

// encrypting an empty message should result in 20 bytes:
// 3 bytes of encrypted length, 1 byte header and 16 bytes MAC
TestBIP324CipherSuite(/* plaintext */ "",
Expand Down Expand Up @@ -1080,7 +1078,8 @@ static void TestRFC8439AEAD(const std::string& hex_aad, const std::string& hex_k
std::array<std::byte, 12> nonce_arr;
memcpy(nonce_arr.data(), nonce.data(), 12);
auto plaintext = ParseHex(hex_plaintext);
auto encrypted = RFC8439Encrypt(MakeByteSpan(aad), MakeByteSpan(key), nonce_arr, MakeByteSpan(plaintext));
std::vector<Span<const std::byte>> ins{MakeByteSpan(plaintext)};
auto encrypted = RFC8439Encrypt(MakeByteSpan(aad), MakeByteSpan(key), nonce_arr, ins);

BOOST_CHECK_EQUAL(HexStr(MakeByteSpan(encrypted.ciphertext)), hex_expected_ciphertext);
BOOST_CHECK_EQUAL(HexStr(MakeByteSpan(encrypted.tag)), hex_expected_auth_tag);
Expand Down
4 changes: 3 additions & 1 deletion src/test/fuzz/crypto_rfc8439.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ FUZZ_TARGET(crypto_rfc8439)
auto plaintext = fdp.ConsumeBytes<std::byte>(plaintext_len);
plaintext.resize(plaintext_len);

auto encrypted = RFC8439Encrypt(aad, key, nonce, plaintext);
std::vector<Span<const std::byte>> ins{plaintext};

auto encrypted = RFC8439Encrypt(aad, key, nonce, ins);
assert(encrypted.ciphertext.size() == plaintext.size());

auto bit_flip_attack = !plaintext.empty() && fdp.ConsumeBool();
Expand Down

0 comments on commit 5c3dce7

Please sign in to comment.