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
Sapling note encryption implementation #3324
Conversation
1b79f3b
to
9526d2c
Compare
9526d2c
to
90073ae
Compare
@zkbot try |
⌛ Trying commit c03e226 with merge b69131dbf4e882acdf33909c012e8406bf4a8229... |
💔 Test failed - pr-try |
@zkbot retry |
⌛ Trying commit c03e226 with merge ef181f08fc326e29442b99b2b6eb234808d761b2... |
☀️ Test successful - pr-try |
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.
ut(ACK+cov), assuming that my comments are addressed (either in this PR, or in a subsequent one).
#define NOTEENCRYPTION_AUTH_BYTES 16 | ||
// Ciphertext for the recipient to decrypt | ||
typedef std::array<unsigned char, ZC_SAPLING_ENCCIPHERTEXT_SIZE> SaplingEncCiphertext; | ||
typedef std::array<unsigned char, ZC_SAPLING_ENCPLAINTEXT_SIZE> SaplingEncPlaintext; |
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.
SaplingEncPlaintext
should be a subclass of BaseNotePlaintext
, with appropriate internal structure and serialization.
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 are typedefs just like the Sprout ciphertexts/plaintexts effectively are.
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.
Sprout ciphertexts are typedefs. Sprout plaintexts are classes, specifically subclasses of BaseNotePlaintext
.
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 think @ebfull is talking about these Sprout typedefs: https://github.com/zcash/zcash/blob/master/src/zcash/NoteEncryption.hpp#L31
typedef std::array<unsigned char, CLEN> Ciphertext;
typedef std::array<unsigned char, MLEN> Plaintext;
@str4d Yes, there will be a subclass, I had one sketched out before this PR started, see snippet here:
class SaplingNotePlaintext : public BaseNotePlaintext {
public:
diversifier_t d;
uint256 r;
|
||
// Ciphertext for outgoing viewing key to decrypt | ||
typedef std::array<unsigned char, ZC_SAPLING_OUTCIPHERTEXT_SIZE> SaplingOutCiphertext; | ||
typedef std::array<unsigned char, ZC_SAPLING_OUTPLAINTEXT_SIZE> SaplingOutPlaintext; |
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.
SaplingOutPlaintext
should be a class with appropriate internal structure and serialization.
@@ -13,6 +14,58 @@ void clamp_curve25519(unsigned char key[crypto_scalarmult_SCALARBYTES]) | |||
key[31] |= 64; | |||
} | |||
|
|||
void PRF_ock( |
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.
Move this into src/zcash/prf.cpp
(below PRF_ovk
).
@@ -13,6 +14,58 @@ void clamp_curve25519(unsigned char key[crypto_scalarmult_SCALARBYTES]) | |||
key[31] |= 64; | |||
} | |||
|
|||
void PRF_ock( | |||
unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE], |
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.
Name this ock
.
src/gtest/test_noteencryption.cpp
Outdated
@@ -17,6 +18,154 @@ class TestNoteDecryption : public ZCNoteDecryption { | |||
} | |||
}; | |||
|
|||
TEST(noteencryption, sapling_api) |
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.
No underscores in test names.
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.
That gets us every time.
src/gtest/test_noteencryption.cpp
Outdated
} | ||
|
||
// Invalid diversifier | ||
ASSERT_FALSE(SaplingNoteEncryption::FromDiversifier({1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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.
Does this work? I seem to recall that boost::optional
doesn't get correctly coerced to a boolean in Boost Test's macros; does it work in Google Test?
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 test passes but is the behaviour correct? Hmm. To remove any doubt, consider using a variable to store the boost::optional and then assert on the value of the variable itself.
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.
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.
Looks good, some minor points raised.
auto sk = SaplingSpendingKey(uint256()).expanded_spending_key(); | ||
auto vk = sk.full_viewing_key(); | ||
auto ivk = vk.in_viewing_key(); | ||
SaplingPaymentAddress pk_1 = *ivk.address({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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.
Style. I prefer using .get()
rather than *ivk
src/gtest/test_noteencryption.cpp
Outdated
} | ||
|
||
// Invalid diversifier | ||
ASSERT_FALSE(SaplingNoteEncryption::FromDiversifier({1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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.
The test passes but is the behaviour correct? Hmm. To remove any doubt, consider using a variable to store the boost::optional and then assert on the value of the variable itself.
ivk, | ||
epk_1 | ||
); | ||
ASSERT_TRUE(message == plaintext_1); |
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.
==
on arrays is deprecated in C++20 but since we use this syntax everywhere, we can deal with it later when we get to C++17...
memcpy(block+96, epk.begin(), 32); | ||
|
||
unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {}; | ||
memcpy(personalization, "Zcash_Derive_ock", 16); |
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.
Where is this in the protocol spec? Don't see it in the current revision (from 28 days ago, dated May 22)
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.
See page 55 of https://github.com/daira/zips/blob/sapling-wip2/protocol/sapling.pdf (draft spec)
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.
https://github.com/zcash/zips/blob/master/protocol/sapling.pdf has now been updated.
memcpy(block+32, epk.begin(), 32); | ||
|
||
unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {}; | ||
memcpy(personalization, "Zcash_SaplingKDF", 16); |
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.
Use constexpr or macro for "Zcash_SaplingKDF"
memcpy(block+96, epk.begin(), 32); | ||
|
||
unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {}; | ||
memcpy(personalization, "Zcash_Derive_ock", 16); |
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.
Use constexpr or macro for "Zcash_Derive_ock"
src/zcash/NoteEncryption.hpp
Outdated
uint256 esk; | ||
|
||
SaplingNoteEncryption(uint256 epk, uint256 esk) : epk(epk), esk(esk) { | ||
|
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.
nit: remove redundant line of whitespace, and have { }
on the same line after function sig.
); | ||
|
||
ASSERT_THROW(tmp_enc.encrypt_to_recipient( | ||
pk_1.pk_d, |
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 we change this line to pk_2.pk_d
the call still throws the same error due to already_encrypted_enc
having been set, so we're not really testing nonce reuse, but the fact that encrypt_to_recipient
can be only called once successfully.
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.
That's the intent of the nonce reuse resistant API; if you call encrypt_to_recipient
twice (which you should never do, ephemeral keys are not reused) then the ChaCha20 nonce will be reused, which we cannot allow.
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.
Ok. Note that encrypt_to_recipient
could be called twice because the method is not thread safe with regards to the boolean flag. Perhaps document that the note encryption class should not be used by multiple threads concurrently.
Currently reviewing this for consistency with the spec (since there were fairly recent spec changes), so please hold off merging for a bit. |
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.
Flushing pending comments.
src/gtest/test_noteencryption.cpp
Outdated
} | ||
|
||
// Invalid diversifier | ||
ASSERT_FALSE(SaplingNoteEncryption::FromDiversifier({1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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.
uint256(), | ||
epk_2 | ||
)); | ||
} |
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.
Also test malleating the ciphertext.
unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE], | ||
const uint256 &ovk, | ||
const uint256 &cv, | ||
const uint256 &cm, |
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 is now called cmu
. (It was always the u-coordinate.)
memcpy(block+96, epk.begin(), 32); | ||
|
||
unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {}; | ||
memcpy(personalization, "Zcash_Derive_ock", 16); |
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.
https://github.com/zcash/zips/blob/master/protocol/sapling.pdf has now been updated.
uint256 esk; | ||
|
||
// Pick random esk | ||
librustzcash_sapling_generate_r(esk.begin()); |
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.
Note that the beta-21 spec says that esk should not be 0. (This is a consequence of the implementation already checking that epk is not of small order.) This has negligible probability so it's not so important.
|
||
// Construct the symmetric key | ||
unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; | ||
KDF_Sapling(K, dhsecret, epk); |
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 puts the output parameter first, whereas for librustzcash_sapling_ka_agree
for example it is last. I don't know which one should change; crypto_aead_chacha20poly1305_ietf_encrypt
also puts the output parameter first.
SaplingOutCiphertext SaplingNoteEncryption::encrypt_to_ourselves( | ||
const uint256 &ovk, | ||
const uint256 &cv, | ||
const uint256 &cm, |
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 appears to also be cmu
, given that it's passed directly to PRF_ock
.
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.
ACK as far as it goes, but missing a lot of the checks in the spec. I assume these will be in another PR?
const SaplingOutCiphertext &ciphertext, | ||
const uint256 &ovk, | ||
const uint256 &cv, | ||
const uint256 &cm, |
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.
Also cmu
.
return boost::none; | ||
} | ||
|
||
return plaintext; |
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 only does the Cout decryption. In the spec (section 4.17.3), that is combined with:
- checking esk and pkd
- reconstructing the sharedSecret and using it to decrypt Cenc
- checking rcm and gd
- checking that epk is consistent with gd and esk
- checking the commitment of the reconstructed note against cmu.
return boost::none; | ||
} | ||
|
||
return plaintext; |
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 only does the Cenc decryption. In the spec (section 4.17.2), that is combined with:
- checking rcm and gd
- checking the commitment of the reconstructed note against cmu.
Yup, this abstraction does not deal with checks of the contents, only encrypting and decrypting. |
I've reviewed minor edits in pairing with @ebfull |
Comments about renaming and refactorings will be addressed at a later date, and this PR is not responsible for plaintext serialization and the other kinds of @zkbot r+ |
📌 Commit 7478876 has been approved by |
Sapling note encryption implementation Closes #3055 Implemented along with @gtank and @Eirik0 DH key exchange was implemented in zcash/librustzcash#18
Closes #3055
Implemented along with @gtank and @Eirik0
DH key exchange was implemented in zcash/librustzcash#18