From 34bf166e5fd50fdd87135095d14c7ef19c1252c7 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 11 Jul 2018 22:03:04 -0700 Subject: [PATCH 1/5] Add encryption of SaplingNotePlaintext and SaplingOutgoingPlaintext classes. This is part of #3061 to add Sapling note functionality. --- src/zcash/Note.cpp | 65 ++++++++++++++++++++++++++++++++++++++++++++++ src/zcash/Note.hpp | 62 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/src/zcash/Note.cpp b/src/zcash/Note.cpp index d84a98067..7e776c756 100644 --- a/src/zcash/Note.cpp +++ b/src/zcash/Note.cpp @@ -137,3 +137,68 @@ ZCNoteEncryption::Ciphertext SproutNotePlaintext::encrypt(ZCNoteEncryption& encr return encryptor.encrypt(pk_enc, pt); } + + + +// Construct and populate SaplingNotePlaintext for a given note and memo. +SaplingNotePlaintext::SaplingNotePlaintext( + const SaplingNote& note, + std::array memo) : BaseNotePlaintext(note, memo) +{ + d = note.d; + rcm = note.r; +} + + +boost::optional SaplingNotePlaintext::note(const SaplingIncomingViewingKey& ivk) const +{ + auto addr = ivk.address( d ); + if (addr) { + return SaplingNote(addr.get(), value_); + } else { + return boost::none; + } +} + + +boost::optional SaplingNotePlaintext::encrypt(const uint256& pk_d) const +{ + // Get the encryptor + auto sne = SaplingNoteEncryption::FromDiversifier(d); + if (!sne) { + return boost::none; + } + auto enc = sne.get(); + + // Create the plaintext + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << (*this); + SaplingEncPlaintext pt; + assert(pt.size() == ss.size()); + memcpy(&pt[0], &ss[0], pt.size()); + + // Encrypt the plaintext + auto encciphertext = enc.encrypt_to_recipient(pk_d, pt); + if (!encciphertext) { + return boost::none; + } + return SaplingNotePlaintextEncryptionResult(encciphertext.get(), enc); +} + + +SaplingOutCiphertext SaplingOutgoingPlaintext::encrypt( + const uint256& ovk, + const uint256& cv, + const uint256& cm, + SaplingNoteEncryption& enc + ) const +{ + // Create the plaintext + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << (*this); + SaplingOutPlaintext pt; + assert(pt.size() == ss.size()); + memcpy(&pt[0], &ss[0], pt.size()); + + return enc.encrypt_to_ourselves(ovk, cv, cm, pt); +} diff --git a/src/zcash/Note.hpp b/src/zcash/Note.hpp index 39610fce0..8bdc2de3b 100644 --- a/src/zcash/Note.hpp +++ b/src/zcash/Note.hpp @@ -116,6 +116,68 @@ public: ) const; }; +typedef std::pair, std::reference_wrapper> SaplingNotePlaintextEncryptionResult; + +class SaplingNotePlaintext : public BaseNotePlaintext { +public: + diversifier_t d; + uint256 rcm; + + SaplingNotePlaintext() {} + + SaplingNotePlaintext(const SaplingNote& note, std::array memo); + + boost::optional note(const SaplingIncomingViewingKey& ivk) const; + + virtual ~SaplingNotePlaintext() {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + unsigned char leadingByte = 0x01; + READWRITE(leadingByte); + + if (leadingByte != 0x01) { + throw std::ios_base::failure("lead byte of SaplingNotePlaintext is not recognized"); + } + + READWRITE(d); // 11 bytes + READWRITE(value_); // 8 bytes + READWRITE(rcm); // 32 bytes + READWRITE(memo_); // 512 bytes + } + + boost::optional encrypt(const uint256& pk_d) const; +}; + +class SaplingOutgoingPlaintext +{ +public: + uint256 pk_d; + uint256 esk; + + SaplingOutgoingPlaintext() {}; + + SaplingOutgoingPlaintext(uint256 pk_d, uint256 esk) : pk_d(pk_d), esk(esk) {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(pk_d); // 8 bytes + READWRITE(esk); // 8 bytes + } + + SaplingOutCiphertext encrypt( + const uint256& ovk, + const uint256& cv, + const uint256& cm, + SaplingNoteEncryption& enc + ) const; +}; + + } #endif // ZC_NOTE_H_ From f23e783600f958efd66b496e888558a4e91e46c8 Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Mon, 16 Jul 2018 18:34:14 -0600 Subject: [PATCH 2/5] Decryption and tests of note/outgoing encryption. --- src/gtest/test_noteencryption.cpp | 94 +++++++++++++++++++++++++++++++ src/zcash/Note.cpp | 49 +++++++++++++++- src/zcash/Note.hpp | 16 +++++- 3 files changed, 157 insertions(+), 2 deletions(-) diff --git a/src/gtest/test_noteencryption.cpp b/src/gtest/test_noteencryption.cpp index 1aded18f2..8c7a1cb48 100644 --- a/src/gtest/test_noteencryption.cpp +++ b/src/gtest/test_noteencryption.cpp @@ -4,6 +4,7 @@ #include #include +#include "zcash/Note.hpp" #include "zcash/NoteEncryption.hpp" #include "zcash/prf.h" #include "zcash/Address.hpp" @@ -19,6 +20,99 @@ public: } }; +TEST(noteencryption, NotePlaintext) +{ + using namespace libzcash; + auto sk = SaplingSpendingKey(uint256()).expanded_spending_key(); + auto vk = sk.full_viewing_key(); + auto ivk = vk.in_viewing_key(); + SaplingPaymentAddress addr = *ivk.address({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); + + std::array memo; + for (size_t i = 0; i < ZC_MEMO_SIZE; i++) { + // Fill the message with dummy data + memo[i] = (unsigned char) i; + } + + SaplingNote note(addr, 39393); + SaplingNotePlaintext pt(note, memo); + + auto res = pt.encrypt(addr.pk_d); + if (!res) { + FAIL(); + } + + auto enc = res.get(); + + auto ct = enc.first; + auto encryptor = enc.second; + auto epk = encryptor.get_epk(); + + // Try to decrypt + auto foo = SaplingNotePlaintext::decrypt( + ct, + ivk, + epk + ); + + if (!foo) { + FAIL(); + } + + auto bar = foo.get(); + + ASSERT_TRUE(bar.value() == pt.value()); + ASSERT_TRUE(bar.memo() == pt.memo()); + ASSERT_TRUE(bar.d == pt.d); + ASSERT_TRUE(bar.rcm == pt.rcm); + + auto foobar = bar.note(ivk); + + if (!foobar) { + FAIL(); + } + + auto new_note = foobar.get(); + + ASSERT_TRUE(note.value() == new_note.value()); + ASSERT_TRUE(note.d == new_note.d); + ASSERT_TRUE(note.pk_d == new_note.pk_d); + ASSERT_TRUE(note.r == new_note.r); + ASSERT_TRUE(note.cm() == new_note.cm()); + + SaplingOutgoingPlaintext out_pt; + out_pt.pk_d = note.pk_d; + out_pt.esk = encryptor.get_esk(); + + auto ovk = random_uint256(); + auto cv = random_uint256(); + auto cm = random_uint256(); + + auto out_ct = out_pt.encrypt( + ovk, + cv, + cm, + encryptor + ); + + auto decrypted_out_ct = out_pt.decrypt( + out_ct, + ovk, + cv, + cm, + encryptor.get_epk() + ); + + if (!decrypted_out_ct) { + FAIL(); + } + + auto decrypted_out_ct_unwrapped = decrypted_out_ct.get(); + + ASSERT_TRUE(decrypted_out_ct_unwrapped.pk_d == out_pt.pk_d); + ASSERT_TRUE(decrypted_out_ct_unwrapped.esk == out_pt.esk); +} + TEST(noteencryption, SaplingApi) { using namespace libzcash; diff --git a/src/zcash/Note.cpp b/src/zcash/Note.cpp index 7e776c756..42df3bf2e 100644 --- a/src/zcash/Note.cpp +++ b/src/zcash/Note.cpp @@ -154,12 +154,59 @@ boost::optional SaplingNotePlaintext::note(const SaplingIncomingVie { auto addr = ivk.address( d ); if (addr) { - return SaplingNote(addr.get(), value_); + return SaplingNote(d, addr.get().pk_d, value_, rcm); } else { return boost::none; } } +boost::optional SaplingOutgoingPlaintext::decrypt( + const SaplingOutCiphertext &ciphertext, + const uint256& ovk, + const uint256& cv, + const uint256& cm, + const uint256& epk +) +{ + auto pt = AttemptSaplingOutDecryption(ciphertext, ovk, cv, cm, epk); + if (!pt) { + return boost::none; + } + + // Deserialize from the plaintext + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << pt.get(); + + SaplingOutgoingPlaintext ret; + ss >> ret; + + assert(ss.size() == 0); + + return ret; +} + +boost::optional SaplingNotePlaintext::decrypt( + const SaplingEncCiphertext &ciphertext, + const uint256 &ivk, + const uint256 &epk +) +{ + auto pt = AttemptSaplingEncDecryption(ciphertext, ivk, epk); + if (!pt) { + return boost::none; + } + + // Deserialize from the plaintext + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << pt.get(); + + SaplingNotePlaintext ret; + ss >> ret; + + assert(ss.size() == 0); + + return ret; +} boost::optional SaplingNotePlaintext::encrypt(const uint256& pk_d) const { diff --git a/src/zcash/Note.hpp b/src/zcash/Note.hpp index 8bdc2de3b..99d34f47b 100644 --- a/src/zcash/Note.hpp +++ b/src/zcash/Note.hpp @@ -116,7 +116,7 @@ public: ) const; }; -typedef std::pair, std::reference_wrapper> SaplingNotePlaintextEncryptionResult; +typedef std::pair SaplingNotePlaintextEncryptionResult; class SaplingNotePlaintext : public BaseNotePlaintext { public: @@ -127,6 +127,12 @@ public: SaplingNotePlaintext(const SaplingNote& note, std::array memo); + static boost::optional decrypt( + const SaplingEncCiphertext &ciphertext, + const uint256 &ivk, + const uint256 &epk + ); + boost::optional note(const SaplingIncomingViewingKey& ivk) const; virtual ~SaplingNotePlaintext() {} @@ -169,6 +175,14 @@ public: READWRITE(esk); // 8 bytes } + static boost::optional decrypt( + const SaplingOutCiphertext &ciphertext, + const uint256& ovk, + const uint256& cv, + const uint256& cm, + const uint256& epk + ); + SaplingOutCiphertext encrypt( const uint256& ovk, const uint256& cv, From 687bd96cbdbb7ab12451e073f5dc379dc4398804 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 17 Jul 2018 14:26:38 -0700 Subject: [PATCH 3/5] Minor update to address nits in review. --- src/gtest/test_noteencryption.cpp | 6 +++--- src/zcash/Note.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gtest/test_noteencryption.cpp b/src/gtest/test_noteencryption.cpp index 8c7a1cb48..9fa7c466f 100644 --- a/src/gtest/test_noteencryption.cpp +++ b/src/gtest/test_noteencryption.cpp @@ -23,9 +23,9 @@ public: TEST(noteencryption, NotePlaintext) { using namespace libzcash; - auto sk = SaplingSpendingKey(uint256()).expanded_spending_key(); - auto vk = sk.full_viewing_key(); - auto ivk = vk.in_viewing_key(); + auto xsk = SaplingSpendingKey(uint256()).expanded_spending_key(); + auto fvk = xsk.full_viewing_key(); + auto ivk = fvk.in_viewing_key(); SaplingPaymentAddress addr = *ivk.address({0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}); std::array memo; diff --git a/src/zcash/Note.cpp b/src/zcash/Note.cpp index 42df3bf2e..148fe8553 100644 --- a/src/zcash/Note.cpp +++ b/src/zcash/Note.cpp @@ -152,7 +152,7 @@ SaplingNotePlaintext::SaplingNotePlaintext( boost::optional SaplingNotePlaintext::note(const SaplingIncomingViewingKey& ivk) const { - auto addr = ivk.address( d ); + auto addr = ivk.address(d); if (addr) { return SaplingNote(d, addr.get().pk_d, value_, rcm); } else { From 7b913c3e5e920e98b4dabd4c9eed678b7c34f68a Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 20 Jul 2018 16:31:48 -0700 Subject: [PATCH 4/5] Implement Sapling note decryption using full viewing key. --- src/gtest/test_noteencryption.cpp | 19 ++++++++++++++++ src/zcash/Note.cpp | 24 ++++++++++++++++++++ src/zcash/Note.hpp | 7 ++++++ src/zcash/NoteEncryption.cpp | 37 +++++++++++++++++++++++++++++++ src/zcash/NoteEncryption.hpp | 9 ++++++++ 5 files changed, 96 insertions(+) diff --git a/src/gtest/test_noteencryption.cpp b/src/gtest/test_noteencryption.cpp index 9fa7c466f..2861e0983 100644 --- a/src/gtest/test_noteencryption.cpp +++ b/src/gtest/test_noteencryption.cpp @@ -111,6 +111,25 @@ TEST(noteencryption, NotePlaintext) ASSERT_TRUE(decrypted_out_ct_unwrapped.pk_d == out_pt.pk_d); ASSERT_TRUE(decrypted_out_ct_unwrapped.esk == out_pt.esk); + + // Test sender can decrypt the note ciphertext. + foo = SaplingNotePlaintext::decryptUsingFullViewingKey( + ct, + epk, + decrypted_out_ct_unwrapped.esk, + decrypted_out_ct_unwrapped.pk_d + ); + + if (!foo) { + FAIL(); + } + + bar = foo.get(); + + ASSERT_TRUE(bar.value() == pt.value()); + ASSERT_TRUE(bar.memo() == pt.memo()); + ASSERT_TRUE(bar.d == pt.d); + ASSERT_TRUE(bar.rcm == pt.rcm); } TEST(noteencryption, SaplingApi) diff --git a/src/zcash/Note.cpp b/src/zcash/Note.cpp index 148fe8553..81d5457c1 100644 --- a/src/zcash/Note.cpp +++ b/src/zcash/Note.cpp @@ -208,6 +208,30 @@ boost::optional SaplingNotePlaintext::decrypt( return ret; } +boost::optional SaplingNotePlaintext::decryptUsingFullViewingKey( + const SaplingEncCiphertext &ciphertext, + const uint256 &epk, + const uint256 &esk, + const uint256 &pk_d +) +{ + auto pt = AttemptSaplingEncDecryptionUsingFullViewingKey(ciphertext, epk, esk, pk_d); + if (!pt) { + return boost::none; + } + + // Deserialize from the plaintext + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << pt.get(); + + SaplingNotePlaintext ret; + ss >> ret; + + assert(ss.size() == 0); + + return ret; +} + boost::optional SaplingNotePlaintext::encrypt(const uint256& pk_d) const { // Get the encryptor diff --git a/src/zcash/Note.hpp b/src/zcash/Note.hpp index 99d34f47b..b9f24f2e2 100644 --- a/src/zcash/Note.hpp +++ b/src/zcash/Note.hpp @@ -133,6 +133,13 @@ public: const uint256 &epk ); + static boost::optional decryptUsingFullViewingKey( + const SaplingEncCiphertext &ciphertext, + const uint256 &epk, + const uint256 &esk, + const uint256 &pk_d + ); + boost::optional note(const SaplingIncomingViewingKey& ivk) const; virtual ~SaplingNotePlaintext() {} diff --git a/src/zcash/NoteEncryption.cpp b/src/zcash/NoteEncryption.cpp index a00999bc3..fe8962dbb 100644 --- a/src/zcash/NoteEncryption.cpp +++ b/src/zcash/NoteEncryption.cpp @@ -187,6 +187,43 @@ boost::optional AttemptSaplingEncDecryption( return plaintext; } +boost::optional AttemptSaplingEncDecryptionUsingFullViewingKey ( + const SaplingEncCiphertext &ciphertext, + const uint256 &epk, + const uint256 &esk, + const uint256 &pk_d +) +{ + 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] = {}; + + 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, diff --git a/src/zcash/NoteEncryption.hpp b/src/zcash/NoteEncryption.hpp index 82f8b2abf..202d9a715 100644 --- a/src/zcash/NoteEncryption.hpp +++ b/src/zcash/NoteEncryption.hpp @@ -73,6 +73,15 @@ boost::optional AttemptSaplingEncDecryption( const uint256 &epk ); +// Attempts to decrypt a Sapling note using full viewing key. +// This will not check that the contents of the ciphertext are correct. +boost::optional AttemptSaplingEncDecryptionUsingFullViewingKey ( + const SaplingEncCiphertext &ciphertext, + const uint256 &epk, + const uint256 &esk, + const uint256 &pk_d +); + // Attempts to decrypt a Sapling note. This will not check that the contents // of the ciphertext are correct. boost::optional AttemptSaplingOutDecryption( From e739ca2ef2748cab9577274eff88e32921ab9dc3 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 23 Jul 2018 10:48:26 -0700 Subject: [PATCH 5/5] Rename AttemptSaplingEncDecryptionUsingFullViewingKey and use function overloading. --- src/gtest/test_noteencryption.cpp | 2 +- src/zcash/Note.cpp | 4 ++-- src/zcash/Note.hpp | 2 +- src/zcash/NoteEncryption.cpp | 2 +- src/zcash/NoteEncryption.hpp | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gtest/test_noteencryption.cpp b/src/gtest/test_noteencryption.cpp index 2861e0983..a674daf65 100644 --- a/src/gtest/test_noteencryption.cpp +++ b/src/gtest/test_noteencryption.cpp @@ -113,7 +113,7 @@ TEST(noteencryption, NotePlaintext) ASSERT_TRUE(decrypted_out_ct_unwrapped.esk == out_pt.esk); // Test sender can decrypt the note ciphertext. - foo = SaplingNotePlaintext::decryptUsingFullViewingKey( + foo = SaplingNotePlaintext::decrypt( ct, epk, decrypted_out_ct_unwrapped.esk, diff --git a/src/zcash/Note.cpp b/src/zcash/Note.cpp index 81d5457c1..1108e1db5 100644 --- a/src/zcash/Note.cpp +++ b/src/zcash/Note.cpp @@ -208,14 +208,14 @@ boost::optional SaplingNotePlaintext::decrypt( return ret; } -boost::optional SaplingNotePlaintext::decryptUsingFullViewingKey( +boost::optional SaplingNotePlaintext::decrypt( const SaplingEncCiphertext &ciphertext, const uint256 &epk, const uint256 &esk, const uint256 &pk_d ) { - auto pt = AttemptSaplingEncDecryptionUsingFullViewingKey(ciphertext, epk, esk, pk_d); + auto pt = AttemptSaplingEncDecryption(ciphertext, epk, esk, pk_d); if (!pt) { return boost::none; } diff --git a/src/zcash/Note.hpp b/src/zcash/Note.hpp index b9f24f2e2..5a3e55dfe 100644 --- a/src/zcash/Note.hpp +++ b/src/zcash/Note.hpp @@ -133,7 +133,7 @@ public: const uint256 &epk ); - static boost::optional decryptUsingFullViewingKey( + static boost::optional decrypt( const SaplingEncCiphertext &ciphertext, const uint256 &epk, const uint256 &esk, diff --git a/src/zcash/NoteEncryption.cpp b/src/zcash/NoteEncryption.cpp index fe8962dbb..63e073265 100644 --- a/src/zcash/NoteEncryption.cpp +++ b/src/zcash/NoteEncryption.cpp @@ -187,7 +187,7 @@ boost::optional AttemptSaplingEncDecryption( return plaintext; } -boost::optional AttemptSaplingEncDecryptionUsingFullViewingKey ( +boost::optional AttemptSaplingEncDecryption ( const SaplingEncCiphertext &ciphertext, const uint256 &epk, const uint256 &esk, diff --git a/src/zcash/NoteEncryption.hpp b/src/zcash/NoteEncryption.hpp index 202d9a715..f6e692028 100644 --- a/src/zcash/NoteEncryption.hpp +++ b/src/zcash/NoteEncryption.hpp @@ -73,9 +73,9 @@ boost::optional AttemptSaplingEncDecryption( const uint256 &epk ); -// Attempts to decrypt a Sapling note using full viewing key. +// Attempts to decrypt a Sapling note using outgoing plaintext. // This will not check that the contents of the ciphertext are correct. -boost::optional AttemptSaplingEncDecryptionUsingFullViewingKey ( +boost::optional AttemptSaplingEncDecryption ( const SaplingEncCiphertext &ciphertext, const uint256 &epk, const uint256 &esk,