-
Notifications
You must be signed in to change notification settings - Fork 37k
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
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
8579374
to
c22f607
Compare
Rebased |
There was a problem hiding this 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?
@sipa @jonasschnelli Is there a reason why this PR has stalled? |
There was a problem hiding this 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
* | ||
* When receiving a packet, the length must be decrypted first. When 3 bytes of | ||
* ciphertext length have been received, they MUST be decrypted. | ||
* |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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/test/crypto_tests.cpp
Outdated
BOOST_CHECK_EQUAL(len_cmp, expected_aad_length); | ||
|
||
// encrypt / decrypt 1000 packets | ||
BOOST_CHECK(aead_in.DecryptLength(&out_len, ciphertext_buf.data())); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
1a868b1
to
424e010
Compare
I will be taking on this PR. Rebased with master. Addressed my own comments. Ready for further review. |
There was a problem hiding this 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.
src/crypto/chacha_poly_aead.h
Outdated
@@ -6,6 +6,7 @@ | |||
#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H | |||
|
|||
#include <crypto/chacha20.h> | |||
#include <crypto/poly1305.h> | |||
|
|||
#include <cmath> | |||
|
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. Fixed.
src/crypto/chacha_poly_aead.cpp
Outdated
m_ctx.SetKey(key, keylen); | ||
|
||
// set initial sequence number | ||
m_seqnr = 0; |
There was a problem hiding this comment.
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
,
bitcoin/src/crypto/chacha_poly_aead.h
Line 107 in 424e010
uint64_t m_seqnr{0}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/crypto/chacha_poly_aead.cpp
Outdated
|
||
// precompute first chunk of keystream | ||
m_ctx.Keystream(m_keystream, KEYSTREAM_SIZE); | ||
m_keystream_pos = 0; |
There was a problem hiding this comment.
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
,
bitcoin/src/crypto/chacha_poly_aead.h
Line 108 in 424e010
size_t m_keystream_pos{0}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/crypto/chacha_poly_aead.cpp
Outdated
assert(K_1_len == CHACHA20_POLY1305_AEAD_KEY_LEN); | ||
assert(K_2_len == CHACHA20_POLY1305_AEAD_KEY_LEN); |
There was a problem hiding this comment.
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
bitcoin/src/crypto/chacha_poly_aead.cpp
Line 33 in 424e010
assert(keylen == 32); |
m_chacha_header
and m_chacha_main
get called. So couldn't we remove these 2 lines?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
424e010
to
8a9daf5
Compare
Comments from @stratospher addressed. Ready for further review. |
8a9daf5
to
0b93e3d
Compare
Addressed #23233 (comment) - ready for further review |
There was a problem hiding this 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 |
|
Defined in:
|
Encryption | ![]() |
Crypt() is in line with the BIP's specifications. |
Decryption | ![]() |
Crypt() handles both encryption and decryption. |
I noticed some more refactoring which could be done.
src/crypto/chacha_poly_aead.h
Outdated
@@ -6,13 +6,13 @@ | |||
#define BITCOIN_CRYPTO_CHACHA_POLY_AEAD_H | |||
|
|||
#include <crypto/chacha20.h> | |||
#include <crypto/poly1305.h> | |||
|
|||
#include <cmath> |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/crypto/chacha_poly_aead.h
Outdated
* 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a typo for semantics? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:) done.
src/crypto/chacha_poly_aead.cpp
Outdated
@@ -4,6 +4,7 @@ | |||
|
|||
#include <crypto/chacha_poly_aead.h> | |||
|
|||
#include <crypto/common.h> | |||
#include <crypto/poly1305.h> | |||
#include <support/cleanse.h> | |||
|
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/bench/chacha_poly_aead.cpp
Outdated
@@ -19,43 +19,29 @@ static constexpr uint64_t BUFFER_SIZE_LARGE = 1024 * 1024; | |||
static const unsigned char k1[32] = {0}; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/bench/chacha_poly_aead.cpp
Outdated
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()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 !
0b93e3d
to
0de3cb9
Compare
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. |
6a651b5
to
41deee4
Compare
Addressed some comments for this PR left over at #23233 by @laanwj: #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. |
41deee4
to
8b474d1
Compare
Rebased. Ready for further review.
|
Concept ACK, will review soon. |
Thanks to @stratospher for finding a bug in the test code. We were using
Ready for further review. |
src/crypto/chacha_poly_aead.cpp
Outdated
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/bench/chacha_poly_aead.cpp
Outdated
static const unsigned char k2[32] = {0}; | ||
|
||
static ChaCha20Poly1305AEAD aead(k1, 32, k2, 32); | ||
static std::vector<unsigned char> zero_key(32, 0x00); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
src/crypto/chacha_poly_aead.cpp
Outdated
@@ -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) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done across the board.
src/crypto/chacha_poly_aead.h
Outdated
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. |
There was a problem hiding this comment.
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".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
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. |
Superseded by #25361 because:
|
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