Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alter the ChaCha20Poly1305@Bitcoin AEAD to the new specification #20962

Closed
wants to merge 6 commits into from

Conversation

jonasschnelli
Copy link
Contributor

@jonasschnelli jonasschnelli commented Jan 19, 2021

During discussions and reviews of BIP324 (p2p transport encryption), a more efficient AEAD was proposed.

The main difference is how the construct implements re-keying. Since both, the original and the new BIP324 AEAD will not repeat the key handshake over time (no re-keying on the ECDH level), a simpler form of direct re-keying has been proposed.

The new AEAD uses a ChaCha20 stream cipher where byte 4064 till byte 4096 (last 32 bytes in a 4096 window) are used to re-key the same instance. Re-keying in that context means re-setting the ChaCha20 key (thus setting the constant "expand 32-byte k" again, resetting the counter) to the last 32bytes of the current 4096 chunk. We never encrypt more than 4096bytes with the same key. On every re-key, we increase the IV by 1 (a sequence number).
This should allow forward secrecy in the same way as the old (and slightly cumbersome) rekeying approach.

The AEAD is currently only used in tests. The serializer and deserializer for the V2 transport are in #23233.

BIP324 proposal can be found here: https://gist.github.com/dhruv/5b1275751bc98f3b64bcafce7876b489

@DrahtBot
Copy link
Contributor

DrahtBot commented Jan 24, 2021

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #25172 (refactor: use std:: prefix for std lib funcs by fanquake)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@jonasschnelli
Copy link
Contributor Author

Rebased

Copy link
Contributor

@PastaPastaPasta PastaPastaPasta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK;

this PR matches to the best of my understanding what is outlined in the BIP.
I saw no significant formatting problems

That's basically where my expertise stops. However, I'm not sure that we should be using the p2p tag since this is pure crypto but #15649 had p2p tag so I guess that's okay.

Is this PR waiting on some other PR / BIP change, or is the blocker here review by sipa and others?

@PastaPastaPasta
Copy link
Contributor

@sipa @jonasschnelli Is there a reason why this PR has stalled?

Copy link
Contributor

@dhruv dhruv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach ACK c22f607

I reviewed the code to reconcile with the new, accepted recommendation in BIP324 and ran the test.

The test vector changes are harder to review. I am happy to re-implement the AEAD in python so we can fuzz the C++ implementation against it(different language and author) if that is useful.

src/crypto/chacha_poly_aead.h Outdated Show resolved Hide resolved
src/crypto/chacha_poly_aead.h Outdated Show resolved Hide resolved
src/crypto/chacha_poly_aead.h Outdated Show resolved Hide resolved
src/crypto/chacha_poly_aead.h Outdated Show resolved Hide resolved
src/crypto/chacha_poly_aead.h Outdated Show resolved Hide resolved
src/crypto/chacha_poly_aead.h Outdated Show resolved Hide resolved
*
* When receiving a packet, the length must be decrypted first. When 3 bytes of
* ciphertext length have been received, they MUST be decrypted.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #18242, I see that the bytes are accessible as a Span and Span::size() seems available. Would it be useful to say something like: "If the decrypted length does not match the payload length, the connection MUST be immediately terminated?"

At first I thought that is implicitly delegated to the MAC, but we don't seem to confirm that the length is correct when encrypting either.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking through this more, I realized that:

  • Sender errors in encrypted length cannot be corrected since the bytes for multiple p2p messages are in a single TCP stream. Such sender errors are protocol errors.
  • MITM errors/attacks will be caught by the MAC check.

src/crypto/chacha_poly_aead.h Outdated Show resolved Hide resolved
src/crypto/chacha_poly_aead.h Outdated Show resolved Hide resolved
BOOST_CHECK_EQUAL(len_cmp, expected_aad_length);

// encrypt / decrypt 1000 packets
BOOST_CHECK(aead_in.DecryptLength(&out_len, ciphertext_buf.data()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line implies that the decrypted length is len(AAD) + len(payload). I interpreted, this to mean that the decrypted length is the length of the ciphertext of the payload alone.

If this is intentional, can we make it clearer in the BIP? IIUC, typically, in other protocols, the length in the preamble is the length of the payload that follows. Did we want to do it that way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BIP, the unit test and the bench test have been updated to be consistently clear that the encrypted length is len(ciphertext_payload) and not len(ciphertext_payload + ciphertext_aad)

@dhruv dhruv force-pushed the 2020/12/aead_v2 branch 2 times, most recently from 1a868b1 to 424e010 Compare August 23, 2021 20:05
@dhruv
Copy link
Contributor

dhruv commented Aug 23, 2021

I will be taking on this PR. Rebased with master. Addressed my own comments. Ready for further review.

Copy link
Contributor

@stratospher stratospher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approach ACK.
It would be nice to remove a few redundant lines of code.

@@ -6,6 +6,7 @@
#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H

#include <crypto/chacha20.h>
#include <crypto/poly1305.h>

#include <cmath>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AAD_PACKAGES_PER_ROUND is defined but not used anywhere. Couldn't we remove it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Fixed.

m_ctx.SetKey(key, keylen);

// set initial sequence number
m_seqnr = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're already initialising m_seqnr with 0 in the class definition of ChaCha20Forward4064,

uint64_t m_seqnr{0};
Couldn't we remove this line from the constructor?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


// precompute first chunk of keystream
m_ctx.Keystream(m_keystream, KEYSTREAM_SIZE);
m_keystream_pos = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're already initialising m_keystream_pos with 0 in the class definition of ChaCha20Forward4064,

size_t m_keystream_pos{0};
Couldn't we remove this line from the constructor?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment on lines 78 to 75
assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are checking whether the key length is 32 on this line

assert(keylen == 32);
when the constructors of m_chacha_header and m_chacha_main get called. So couldn't we remove these 2 lines?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find these asserts clarifying and afaict, C++ asserts are optimized away in release builds so it's not slowing anything down. Leaving these in here for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: Turns out this was mistaken. assert() is not optimized away in optimized Bitcoin Core builds.

@dhruv
Copy link
Contributor

dhruv commented Sep 12, 2021

Comments from @stratospher addressed. Ready for further review.

@dhruv
Copy link
Contributor

dhruv commented Oct 10, 2021

Addressed #23233 (comment) - ready for further review

Copy link
Contributor

@stratospher stratospher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK 0b93e3d

It’s super awesome to see BIP 324 shaping up! This PR captures the ChaCha20Forward4064-Poly1305@Bitcoin cipher suite proposed in BIP 324 beautifully.

BIP 324 PR
Prerequisites
  1. ChaCha20 PRF
  2. Poly1305 MAC
  3. ChaCha20Forward4064-Poly1305@Bitcoin cipher suite
  4. 2 separate instances of ChaCha20Forward4064 DRBG using keys k1 and k2 called F and V respectively.
Defined in:
  1. chacha20.h
  2. poly1305.h
  3. chacha_poly_aead.h
  4. F is m_chacha_header and V is m_chacha_main
Encryption 1 Crypt() is in line with the BIP's specifications.
Decryption 2 Crypt() handles both encryption and decryption.

I noticed some more refactoring which could be done.

@@ -6,13 +6,13 @@
#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H

#include <crypto/chacha20.h>
#include <crypto/poly1305.h>

#include <cmath>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#include <cmath> isn't being used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* a decryption oracle can learn nothing about the payload contents or its MAC
* (assuming key derivation, ChaCha20 and Poly1305 are secure). Active observers
* can still obtain the message length (ex. active ciphertext bit flipping or
* traffic shemantics analysis)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a typo for semantics? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:) done.

@@ -4,6 +4,7 @@

#include <crypto/chacha_poly_aead.h>

#include <crypto/common.h>
#include <crypto/poly1305.h>
#include <support/cleanse.h>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These imports can be removed since they aren't used:

  • #include <string.h>
  • #include <cstdio>
  • #include <limits>

Copy link
Contributor

@dhruv dhruv Oct 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need string.h for size_t: https://en.cppreference.com/w/c/types/size_t
Added cstring for memset: https://en.cppreference.com/w/cpp/string/byte/memset

Removed cstdio and limits

@@ -21,9 +21,6 @@ FUZZ_TARGET(crypto_chacha20_poly1305_aead)
const std::vector<uint8_t> k2 = ConsumeFixedLengthByteVector(fuzzed_data_provider, CHACHA20_POLY1305_AEAD_KEY_LEN);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These header files aren't used too and can be removed:

EDIT: I'm a bit confused about whether cassert needs to be removed. Even though an assert statement isn't used anywhere in the fuzz test file, it maybe needed in the fuzzing environment?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -19,43 +19,29 @@ static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024;
static const unsigned char k1[32] = {0};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#include <limits> can be removed since it's usage in the file has been removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const bool get_length_ok = aead.GetLength(&len, seqnr_aad, aad_pos, in.data());
assert(get_length_ok);
const bool crypt_ok_2 = aead.Crypt(seqnr_payload, seqnr_aad, aad_pos, out.data(), out.size(), in.data(), buffersize, true);
auto len = aead_in.DecryptLength(out.data());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment suggestion can be applied here too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I was looking for another place this was useful and didn't remember correctly. Thanks, @stratospher !

@dhruv
Copy link
Contributor

dhruv commented Oct 22, 2021

Thank you for the great diagrams, @stratospher ! They'll come in handy at a future review club meeting and in docs.

Review comments addressed. Rebased. Ready for further review.

@dhruv
Copy link
Contributor

dhruv commented Jan 21, 2022

Addressed some comments for this PR left over at #23233 by @laanwj:

#23233 (comment)
#23233 (comment)

I also rebased against master because at one point I had trouble running fuzz tests(but it turned out to be unrelated).

Ready for further review.

@dhruv
Copy link
Contributor

dhruv commented Feb 2, 2022

Rebased. Ready for further review.

git range-diff 02e1d8d06f 41deee4 8b474d1

@jonatack
Copy link
Contributor

jonatack commented Mar 3, 2022

Concept ACK, will review soon.

@dhruv
Copy link
Contributor

dhruv commented Mar 21, 2022

Thanks to @stratospher for finding a bug in the test code. We were using WriteLE32 to write the 3 byte header plaintext after the payload. This was overwriting the first byte in the payload.

git range-diff 91d12344b 8b474d1 97b768f

Ready for further review.

m_chacha_main.Crypt(poly_key, poly_key, sizeof(poly_key));
// 1. AAD (the encrypted packet length), use the header-keystream
if (is_encrypt) {
m_chacha_header.Crypt(src, dest, 3);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ChaCha20Poly1305AEAD::Crypt function uses argument order dest, len, src
The ChaCha20Forward4064::Crypt function uses argument order input, output, bytes
Let's standardize on one, please.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. New implementation uses spans and I went with input, output everywhere.

static const unsigned char k2[32] = {0};

static ChaCha20Poly1305AEAD aead(k1, 32, k2, 32);
static std::vector<unsigned char> zero_key(32, 0x00);
Copy link
Member

@laanwj laanwj Jun 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having this mutable is slightly risky. Can we add const or constexpr?
Edit: ok, this is "only" bench code, but still.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -27,20 +26,53 @@ int timingsafe_bcmp(const unsigned char* b1, const unsigned char* b2, size_t n)

#endif // TIMINGSAFE_BCMP

ChaCha20Poly1305AEAD::ChaCha20Poly1305AEAD(const unsigned char* K_1, size_t K_1_len, const unsigned char* K_2, size_t K_2_len)
ChaCha20Forward4064::ChaCha20Forward4064(const Span<unsigned char> key)
Copy link
Member

@laanwj laanwj Jun 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If your intent is to have the contents of the Span const (which I think it is), this should be Span<const unsigned char>.
(more of these below)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done across the board.

dest, output buffer, must be of a size equal or larger then CHACHA20_POLY1305_AEAD_AAD_LEN + payload (+ POLY1305_TAG_LEN in encryption) bytes
destlen, length of the destination buffer
src, the AAD+payload to encrypt or the AAD+payload+MAC to decrypt
src_len, the length of the source buffer
is_encrypt, set to true if we encrypt (creates and appends the MAC instead of verifying it)

Returns true if encipher succeeds. Upon failure, the data at dest should not be used.
Copy link
Member

@laanwj laanwj Jun 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "encipher" a word? If it means the same, I prefer "encrypt or decrypt".

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@dhruv
Copy link
Contributor

dhruv commented Jun 13, 2022

First commit is from #25354 and should be reviewed there. This PR just depends on it.

Updated the implementation of the BIP324 Cipher Suite to be in accordance with the latest draft in the working group(still pending public release). The changes to intent and code from the original PR are large and since I didn't originally open this PR, I can't change the PR description. I suspect it makes sense to close this PR and open a new one, but we'd lose the history. I'd welcome thoughts on that from reviewers and maintainers.

Ready for further review.

@dhruv dhruv mentioned this pull request Jun 13, 2022
@dhruv
Copy link
Contributor

dhruv commented Jun 13, 2022

Superseded by #25361 because:

  • I'd like to keep the PR description up to date myself
  • The code and the purpose of the PR have deviated significantly

@dhruv dhruv closed this Jun 13, 2022
@bitcoin bitcoin locked and limited conversation to collaborators Jun 13, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
No open projects
Status: Closed unmerged (Superseded)
Development

Successfully merging this pull request may close these issues.

None yet

10 participants