From 9e1c2c4049b84deb6ec0eff1e7a0e505f542848f Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Thu, 14 Jun 2018 11:58:45 -0600 Subject: [PATCH] Implementation of Sapling in-band secret distribution. --- src/gtest/test_noteencryption.cpp | 149 +++++++++++++++++++++++ src/zcash/Address.hpp | 3 +- src/zcash/NoteEncryption.cpp | 190 ++++++++++++++++++++++++++++++ src/zcash/NoteEncryption.hpp | 60 +++++++++- src/zcash/Zcash.h | 11 ++ 5 files changed, 411 insertions(+), 2 deletions(-) diff --git a/src/gtest/test_noteencryption.cpp b/src/gtest/test_noteencryption.cpp index 754832179..6c22daeeb 100644 --- a/src/gtest/test_noteencryption.cpp +++ b/src/gtest/test_noteencryption.cpp @@ -6,6 +6,7 @@ #include "zcash/NoteEncryption.hpp" #include "zcash/prf.h" +#include "zcash/Address.hpp" #include "crypto/sha256.h" class TestNoteDecryption : public ZCNoteDecryption { @@ -17,6 +18,154 @@ public: } }; +TEST(noteencryption, sapling_api) +{ + using namespace libzcash; + + // Create recipient addresses + 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}); + SaplingPaymentAddress pk_2 = *ivk.address({4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + + // Blob of stuff we're encrypting + std::array message; + for (size_t i = 0; i < ZC_SAPLING_ENCPLAINTEXT_SIZE; i++) { + // Fill the message with dummy data + message[i] = (unsigned char) i; + } + + std::array small_message; + for (size_t i = 0; i < ZC_SAPLING_OUTPLAINTEXT_SIZE; i++) { + // Fill the message with dummy data + small_message[i] = (unsigned char) i; + } + + // Invalid diversifier + ASSERT_FALSE(SaplingNoteEncryption::FromDiversifier({1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0})); + + // Encrypt to pk_1 + auto enc = *SaplingNoteEncryption::FromDiversifier(pk_1.d); + auto ciphertext_1 = *enc.encrypt_to_recipient( + pk_1.pk_d, + message + ); + auto epk_1 = enc.get_epk(); + auto cv_1 = random_uint256(); + auto cm_1 = random_uint256(); + auto out_ciphertext_1 = enc.encrypt_to_ourselves( + sk.ovk, + cv_1, + cm_1, + small_message + ); + + // Encrypt to pk_2 + enc = *SaplingNoteEncryption::FromDiversifier(pk_2.d); + auto ciphertext_2 = *enc.encrypt_to_recipient( + pk_2.pk_d, + message + ); + auto epk_2 = enc.get_epk(); + + auto cv_2 = random_uint256(); + auto cm_2 = random_uint256(); + auto out_ciphertext_2 = enc.encrypt_to_ourselves( + sk.ovk, + cv_2, + cm_2, + small_message + ); + + // Try to decrypt + auto plaintext_1 = *AttemptSaplingEncDecryption( + ciphertext_1, + ivk, + epk_1 + ); + ASSERT_TRUE(message == plaintext_1); + + auto small_plaintext_1 = *AttemptSaplingOutDecryption( + out_ciphertext_1, + sk.ovk, + cv_1, + cm_1, + epk_1 + ); + ASSERT_TRUE(small_message == small_plaintext_1); + + auto plaintext_2 = *AttemptSaplingEncDecryption( + ciphertext_2, + ivk, + epk_2 + ); + ASSERT_TRUE(message == plaintext_2); + + auto small_plaintext_2 = *AttemptSaplingOutDecryption( + out_ciphertext_2, + sk.ovk, + cv_2, + cm_2, + epk_2 + ); + ASSERT_TRUE(small_message == small_plaintext_2); + + // Try to decrypt out ciphertext with wrong key material + ASSERT_FALSE(AttemptSaplingOutDecryption( + out_ciphertext_1, + random_uint256(), + cv_1, + cm_1, + epk_1 + )); + ASSERT_FALSE(AttemptSaplingOutDecryption( + out_ciphertext_1, + sk.ovk, + random_uint256(), + cm_1, + epk_1 + )); + ASSERT_FALSE(AttemptSaplingOutDecryption( + out_ciphertext_1, + sk.ovk, + cv_1, + random_uint256(), + epk_1 + )); + ASSERT_FALSE(AttemptSaplingOutDecryption( + out_ciphertext_1, + sk.ovk, + cv_1, + cm_1, + random_uint256() + )); + + // Try to decrypt with wrong ephemeral key + ASSERT_FALSE(AttemptSaplingEncDecryption( + ciphertext_1, + ivk, + epk_2 + )); + ASSERT_FALSE(AttemptSaplingEncDecryption( + ciphertext_2, + ivk, + epk_1 + )); + + // Try to decrypt with wrong ivk + ASSERT_FALSE(AttemptSaplingEncDecryption( + ciphertext_1, + uint256(), + epk_1 + )); + ASSERT_FALSE(AttemptSaplingEncDecryption( + ciphertext_2, + uint256(), + epk_2 + )); +} + TEST(noteencryption, api) { uint256 sk_enc = ZCNoteEncryption::generate_privkey(uint252(uint256S("21035d60bc1983e37950ce4803418a8fb33ea68d5b937ca382ecbae7564d6a07"))); diff --git a/src/zcash/Address.hpp b/src/zcash/Address.hpp index 6b8c310f4..b19a0b899 100644 --- a/src/zcash/Address.hpp +++ b/src/zcash/Address.hpp @@ -4,6 +4,7 @@ #include "uint256.h" #include "uint252.h" #include "serialize.h" +#include "Zcash.h" #include @@ -18,7 +19,7 @@ const size_t SerializedPaymentAddressSize = 64; const size_t SerializedViewingKeySize = 64; const size_t SerializedSpendingKeySize = 32; -typedef std::array diversifier_t; +typedef std::array diversifier_t; class SproutPaymentAddress { public: diff --git a/src/zcash/NoteEncryption.cpp b/src/zcash/NoteEncryption.cpp index 9ae0ba5c3..27e8b0b88 100644 --- a/src/zcash/NoteEncryption.cpp +++ b/src/zcash/NoteEncryption.cpp @@ -3,6 +3,7 @@ #include "sodium.h" #include #include "prf.h" +#include "librustzcash.h" #define NOTEENCRYPTION_CIPHER_KEYSIZE 32 @@ -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], + const uint256 &ovk, + const uint256 &cv, + const uint256 &cm, + const uint256 &epk +) +{ + unsigned char block[128] = {}; + memcpy(block+0, ovk.begin(), 32); + memcpy(block+32, cv.begin(), 32); + memcpy(block+64, cm.begin(), 32); + memcpy(block+96, epk.begin(), 32); + + unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {}; + memcpy(personalization, "Zcash_Derive_ock", 16); + + if (crypto_generichash_blake2b_salt_personal(K, NOTEENCRYPTION_CIPHER_KEYSIZE, + block, 128, + NULL, 0, // No key. + NULL, // No salt. + personalization + ) != 0) + { + throw std::logic_error("hash function failure"); + } +} + +void KDF_Sapling( + unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE], + const uint256 &dhsecret, + const uint256 &epk +) +{ + unsigned char block[64] = {}; + memcpy(block+0, dhsecret.begin(), 32); + memcpy(block+32, epk.begin(), 32); + + unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES] = {}; + memcpy(personalization, "Zcash_SaplingKDF", 16); + + if (crypto_generichash_blake2b_salt_personal(K, NOTEENCRYPTION_CIPHER_KEYSIZE, + block, 64, + NULL, 0, // No key. + NULL, // No salt. + personalization + ) != 0) + { + throw std::logic_error("hash function failure"); + } +} + void KDF(unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE], const uint256 &dhsecret, const uint256 &epk, @@ -48,6 +101,143 @@ void KDF(unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE], namespace libzcash { +boost::optional SaplingNoteEncryption::FromDiversifier(diversifier_t d) { + uint256 epk; + uint256 esk; + + // Pick random esk + librustzcash_sapling_generate_r(esk.begin()); + + // Compute epk given the diversifier + if (!librustzcash_sapling_ka_derivepublic(d.begin(), esk.begin(), epk.begin())) { + return boost::none; + } + + return SaplingNoteEncryption(epk, esk); +} + +boost::optional SaplingNoteEncryption::encrypt_to_recipient( + const uint256 &pk_d, + const SaplingEncPlaintext &message +) +{ + uint256 dhsecret; + + if (!librustzcash_sapling_ka_agree(pk_d.begin(), esk.begin(), dhsecret.begin())) { + return boost::none; + } + + // Construct the symmetric key + unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; + KDF_Sapling(K, dhsecret, epk); + + // The nonce is zero because we never reuse keys + unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {}; + + SaplingEncCiphertext ciphertext; + + crypto_aead_chacha20poly1305_ietf_encrypt( + ciphertext.begin(), NULL, + message.begin(), ZC_SAPLING_ENCPLAINTEXT_SIZE, + NULL, 0, // no "additional data" + NULL, cipher_nonce, K + ); + + return ciphertext; +} + +boost::optional AttemptSaplingEncDecryption( + const SaplingEncCiphertext &ciphertext, + const uint256 &ivk, + const uint256 &epk +) +{ + uint256 dhsecret; + + if (!librustzcash_sapling_ka_agree(epk.begin(), ivk.begin(), dhsecret.begin())) { + return boost::none; + } + + // Construct the symmetric key + unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; + KDF_Sapling(K, dhsecret, epk); + + // The nonce is zero because we never reuse keys + unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {}; + + SaplingEncPlaintext plaintext; + + if (crypto_aead_chacha20poly1305_ietf_decrypt( + plaintext.begin(), NULL, + NULL, + ciphertext.begin(), ZC_SAPLING_ENCCIPHERTEXT_SIZE, + NULL, + 0, + cipher_nonce, K) != 0) + { + return boost::none; + } + + return plaintext; +} + +SaplingOutCiphertext SaplingNoteEncryption::encrypt_to_ourselves( + const uint256 &ovk, + const uint256 &cv, + const uint256 &cm, + const SaplingOutPlaintext &message +) +{ + // Construct the symmetric key + unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; + PRF_ock(K, ovk, cv, cm, epk); + + // The nonce is zero because we never reuse keys + unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {}; + + SaplingOutCiphertext ciphertext; + + crypto_aead_chacha20poly1305_ietf_encrypt( + ciphertext.begin(), NULL, + message.begin(), ZC_SAPLING_OUTPLAINTEXT_SIZE, + NULL, 0, // no "additional data" + NULL, cipher_nonce, K + ); + + return ciphertext; +} + +boost::optional AttemptSaplingOutDecryption( + const SaplingOutCiphertext &ciphertext, + const uint256 &ovk, + const uint256 &cv, + const uint256 &cm, + const uint256 &epk +) +{ + // Construct the symmetric key + unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; + PRF_ock(K, ovk, cv, cm, epk); + + // The nonce is zero because we never reuse keys + unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {}; + + SaplingOutPlaintext plaintext; + + if (crypto_aead_chacha20poly1305_ietf_decrypt( + plaintext.begin(), NULL, + NULL, + ciphertext.begin(), ZC_SAPLING_OUTCIPHERTEXT_SIZE, + NULL, + 0, + cipher_nonce, K) != 0) + { + return boost::none; + } + + return plaintext; +} + template NoteEncryption::NoteEncryption(uint256 hSig) : nonce(0), hSig(hSig) { // All of this code assumes crypto_scalarmult_BYTES is 32 diff --git a/src/zcash/NoteEncryption.hpp b/src/zcash/NoteEncryption.hpp index 86de8f44b..47fc93f7e 100644 --- a/src/zcash/NoteEncryption.hpp +++ b/src/zcash/NoteEncryption.hpp @@ -10,12 +10,70 @@ https://github.com/zcash/zips/blob/master/protocol/protocol.pdf #include "uint252.h" #include "zcash/Zcash.h" +#include "zcash/Address.hpp" #include namespace libzcash { -#define NOTEENCRYPTION_AUTH_BYTES 16 +// Ciphertext for the recipient to decrypt +typedef std::array SaplingEncCiphertext; +typedef std::array SaplingEncPlaintext; + +// Ciphertext for outgoing viewing key to decrypt +typedef std::array SaplingOutCiphertext; +typedef std::array SaplingOutPlaintext; + +class SaplingNoteEncryption { +protected: + // Ephemeral public key + uint256 epk; + + // Ephemeral secret key + uint256 esk; + + SaplingNoteEncryption(uint256 epk, uint256 esk) : epk(epk), esk(esk) { + + } + +public: + + static boost::optional FromDiversifier(diversifier_t d); + + boost::optional encrypt_to_recipient( + const uint256 &pk_d, + const SaplingEncPlaintext &message + ); + + SaplingOutCiphertext encrypt_to_ourselves( + const uint256 &ovk, + const uint256 &cv, + const uint256 &cm, + const SaplingOutPlaintext &message + ); + + uint256 get_epk() const { + return epk; + } +}; + +// Attempts to decrypt a Sapling note. This will not check that the contents +// of the ciphertext are correct. +boost::optional AttemptSaplingEncDecryption( + const SaplingEncCiphertext &ciphertext, + const uint256 &ivk, + const uint256 &epk +); + +// Attempts to decrypt a Sapling note. This will not check that the contents +// of the ciphertext are correct. +boost::optional AttemptSaplingOutDecryption( + const SaplingOutCiphertext &ciphertext, + const uint256 &ovk, + const uint256 &cv, + const uint256 &cm, + const uint256 &epk +); template class NoteEncryption { diff --git a/src/zcash/Zcash.h b/src/zcash/Zcash.h index bb805eef5..84dfe9525 100644 --- a/src/zcash/Zcash.h +++ b/src/zcash/Zcash.h @@ -8,12 +8,23 @@ #define SAPLING_INCREMENTAL_MERKLE_TREE_DEPTH 32 +#define NOTEENCRYPTION_AUTH_BYTES 16 + #define ZC_NOTEPLAINTEXT_LEADING 1 #define ZC_V_SIZE 8 #define ZC_RHO_SIZE 32 #define ZC_R_SIZE 32 #define ZC_MEMO_SIZE 512 +#define ZC_DIVERSIFIER_SIZE 11 +#define ZC_JUBJUB_POINT_SIZE 32 +#define ZC_JUBJUB_SCALAR_SIZE 32 #define ZC_NOTEPLAINTEXT_SIZE (ZC_NOTEPLAINTEXT_LEADING + ZC_V_SIZE + ZC_RHO_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE) +#define ZC_SAPLING_ENCPLAINTEXT_SIZE (ZC_NOTEPLAINTEXT_LEADING + ZC_DIVERSIFIER_SIZE + ZC_V_SIZE + ZC_R_SIZE + ZC_MEMO_SIZE) +#define ZC_SAPLING_OUTPLAINTEXT_SIZE (ZC_JUBJUB_POINT_SIZE + ZC_JUBJUB_SCALAR_SIZE) + +#define ZC_SAPLING_ENCCIPHERTEXT_SIZE (ZC_SAPLING_ENCPLAINTEXT_SIZE + NOTEENCRYPTION_AUTH_BYTES) +#define ZC_SAPLING_OUTCIPHERTEXT_SIZE (ZC_SAPLING_OUTPLAINTEXT_SIZE + NOTEENCRYPTION_AUTH_BYTES) + #endif // ZC_ZCASH_H_