/Users/eugenesiegel/btc/bitcoin/src/crypto/chacha20poly1305.cpp
| Line | Count | Source (jump to first uncovered line) | 
| 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 <crypto/chacha20poly1305.h> | 
| 6 |  |  | 
| 7 |  | #include <crypto/chacha20.h> | 
| 8 |  | #include <crypto/common.h> | 
| 9 |  | #include <crypto/poly1305.h> | 
| 10 |  | #include <span.h> | 
| 11 |  | #include <support/cleanse.h> | 
| 12 |  |  | 
| 13 |  | #include <cassert> | 
| 14 |  | #include <cstddef> | 
| 15 |  |  | 
| 16 | 0 | AEADChaCha20Poly1305::AEADChaCha20Poly1305(std::span<const std::byte> key) noexcept : m_chacha20(key) | 
| 17 | 0 | { | 
| 18 | 0 |     assert(key.size() == KEYLEN); | 
| 19 | 0 | } | 
| 20 |  |  | 
| 21 |  | void AEADChaCha20Poly1305::SetKey(std::span<const std::byte> key) noexcept | 
| 22 | 0 | { | 
| 23 | 0 |     assert(key.size() == KEYLEN); | 
| 24 | 0 |     m_chacha20.SetKey(key); | 
| 25 | 0 | } | 
| 26 |  |  | 
| 27 |  | namespace { | 
| 28 |  |  | 
| 29 |  | int timingsafe_bcmp_internal(const unsigned char* b1, const unsigned char* b2, size_t n) noexcept | 
| 30 | 0 | { | 
| 31 | 0 |     const unsigned char *p1 = b1, *p2 = b2; | 
| 32 | 0 |     int ret = 0; | 
| 33 | 0 |     for (; n > 0; n--) | 
| 34 | 0 |         ret |= *p1++ ^ *p2++; | 
| 35 | 0 |     return (ret != 0); | 
| 36 | 0 | } | 
| 37 |  |  | 
| 38 |  | /** Compute poly1305 tag. chacha20 must be set to the right nonce, block 0. Will be at block 1 after. */ | 
| 39 |  | void ComputeTag(ChaCha20& chacha20, std::span<const std::byte> aad, std::span<const std::byte> cipher, std::span<std::byte> tag) noexcept | 
| 40 | 0 | { | 
| 41 | 0 |     static const std::byte PADDING[16] = {{}}; | 
| 42 |  |  | 
| 43 |  |     // Get block of keystream (use a full 64 byte buffer to avoid the need for chacha20's own buffering). | 
| 44 | 0 |     std::byte first_block[ChaCha20Aligned::BLOCKLEN]; | 
| 45 | 0 |     chacha20.Keystream(first_block); | 
| 46 |  |  | 
| 47 |  |     // Use the first 32 bytes of the first keystream block as poly1305 key. | 
| 48 | 0 |     Poly1305 poly1305{std::span{first_block}.first(Poly1305::KEYLEN)}; | 
| 49 |  |  | 
| 50 |  |     // Compute tag: | 
| 51 |  |     // - Process the padded AAD with Poly1305. | 
| 52 | 0 |     const unsigned aad_padding_length = (16 - (aad.size() % 16)) % 16; | 
| 53 | 0 |     poly1305.Update(aad).Update(std::span{PADDING}.first(aad_padding_length)); | 
| 54 |  |     // - Process the padded ciphertext with Poly1305. | 
| 55 | 0 |     const unsigned cipher_padding_length = (16 - (cipher.size() % 16)) % 16; | 
| 56 | 0 |     poly1305.Update(cipher).Update(std::span{PADDING}.first(cipher_padding_length)); | 
| 57 |  |     // - Process the AAD and plaintext length with Poly1305. | 
| 58 | 0 |     std::byte length_desc[Poly1305::TAGLEN]; | 
| 59 | 0 |     WriteLE64(length_desc, aad.size()); | 
| 60 | 0 |     WriteLE64(length_desc + 8, cipher.size()); | 
| 61 | 0 |     poly1305.Update(length_desc); | 
| 62 |  |  | 
| 63 |  |     // Output tag. | 
| 64 | 0 |     poly1305.Finalize(tag); | 
| 65 | 0 | } | 
| 66 |  |  | 
| 67 |  | } // namespace | 
| 68 |  |  | 
| 69 |  | void AEADChaCha20Poly1305::Encrypt(std::span<const std::byte> plain1, std::span<const std::byte> plain2, std::span<const std::byte> aad, Nonce96 nonce, std::span<std::byte> cipher) noexcept | 
| 70 | 0 | { | 
| 71 | 0 |     assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION); | 
| 72 |  |  | 
| 73 |  |     // Encrypt using ChaCha20 (starting at block 1). | 
| 74 | 0 |     m_chacha20.Seek(nonce, 1); | 
| 75 | 0 |     m_chacha20.Crypt(plain1, cipher.first(plain1.size())); | 
| 76 | 0 |     m_chacha20.Crypt(plain2, cipher.subspan(plain1.size()).first(plain2.size())); | 
| 77 |  |  | 
| 78 |  |     // Seek to block 0, and compute tag using key drawn from there. | 
| 79 | 0 |     m_chacha20.Seek(nonce, 0); | 
| 80 | 0 |     ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), cipher.last(EXPANSION)); | 
| 81 | 0 | } | 
| 82 |  |  | 
| 83 |  | bool AEADChaCha20Poly1305::Decrypt(std::span<const std::byte> cipher, std::span<const std::byte> aad, Nonce96 nonce, std::span<std::byte> plain1, std::span<std::byte> plain2) noexcept | 
| 84 | 0 | { | 
| 85 | 0 |     assert(cipher.size() == plain1.size() + plain2.size() + EXPANSION); | 
| 86 |  |  | 
| 87 |  |     // Verify tag (using key drawn from block 0). | 
| 88 | 0 |     m_chacha20.Seek(nonce, 0); | 
| 89 | 0 |     std::byte expected_tag[EXPANSION]; | 
| 90 | 0 |     ComputeTag(m_chacha20, aad, cipher.first(cipher.size() - EXPANSION), expected_tag); | 
| 91 | 0 |     if (timingsafe_bcmp_internal(UCharCast(expected_tag), UCharCast(cipher.last(EXPANSION).data()), EXPANSION)) return false; | 
| 92 |  |  | 
| 93 |  |     // Decrypt (starting at block 1). | 
| 94 | 0 |     m_chacha20.Crypt(cipher.first(plain1.size()), plain1); | 
| 95 | 0 |     m_chacha20.Crypt(cipher.subspan(plain1.size()).first(plain2.size()), plain2); | 
| 96 | 0 |     return true; | 
| 97 | 0 | } | 
| 98 |  |  | 
| 99 |  | void AEADChaCha20Poly1305::Keystream(Nonce96 nonce, std::span<std::byte> keystream) noexcept | 
| 100 | 0 | { | 
| 101 |  |     // Skip the first output block, as it's used for generating the poly1305 key. | 
| 102 | 0 |     m_chacha20.Seek(nonce, 1); | 
| 103 | 0 |     m_chacha20.Keystream(keystream); | 
| 104 | 0 | } | 
| 105 |  |  | 
| 106 |  | void FSChaCha20Poly1305::NextPacket() noexcept | 
| 107 | 0 | { | 
| 108 | 0 |     if (++m_packet_counter == m_rekey_interval) { | 
| 109 |  |         // Generate a full block of keystream, to avoid needing the ChaCha20 buffer, even though | 
| 110 |  |         // we only need KEYLEN (32) bytes. | 
| 111 | 0 |         std::byte one_block[ChaCha20Aligned::BLOCKLEN]; | 
| 112 | 0 |         m_aead.Keystream({0xFFFFFFFF, m_rekey_counter}, one_block); | 
| 113 |  |         // Switch keys. | 
| 114 | 0 |         m_aead.SetKey(std::span{one_block}.first(KEYLEN)); | 
| 115 |  |         // Wipe the generated keystream (a copy remains inside m_aead, which will be cleaned up | 
| 116 |  |         // once it cycles again, or is destroyed). | 
| 117 | 0 |         memory_cleanse(one_block, sizeof(one_block)); | 
| 118 |  |         // Update counters. | 
| 119 | 0 |         m_packet_counter = 0; | 
| 120 | 0 |         ++m_rekey_counter; | 
| 121 | 0 |     } | 
| 122 | 0 | } | 
| 123 |  |  | 
| 124 |  | void FSChaCha20Poly1305::Encrypt(std::span<const std::byte> plain1, std::span<const std::byte> plain2, std::span<const std::byte> aad, std::span<std::byte> cipher) noexcept | 
| 125 | 0 | { | 
| 126 | 0 |     m_aead.Encrypt(plain1, plain2, aad, {m_packet_counter, m_rekey_counter}, cipher); | 
| 127 | 0 |     NextPacket(); | 
| 128 | 0 | } | 
| 129 |  |  | 
| 130 |  | bool FSChaCha20Poly1305::Decrypt(std::span<const std::byte> cipher, std::span<const std::byte> aad, std::span<std::byte> plain1, std::span<std::byte> plain2) noexcept | 
| 131 | 0 | { | 
| 132 | 0 |     bool ret = m_aead.Decrypt(cipher, aad, {m_packet_counter, m_rekey_counter}, plain1, plain2); | 
| 133 | 0 |     NextPacket(); | 
| 134 | 0 |     return ret; | 
| 135 | 0 | } |