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

Sapling note encryption implementation #3324

Merged
merged 6 commits into from Jul 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions depends/packages/librustzcash.mk
Expand Up @@ -3,8 +3,8 @@ $(package)_version=0.1
$(package)_download_path=https://github.com/zcash/$(package)/archive/
$(package)_file_name=$(package)-$($(package)_git_commit).tar.gz
$(package)_download_file=$($(package)_git_commit).tar.gz
$(package)_sha256_hash=5231145ea6abf61092c21b6770baf3af65994f83dff96b10118ba5dd53451f26
$(package)_git_commit=0af1ce8bf121e1ad367db907c39d214581e270a6
$(package)_sha256_hash=5a50aae38a9ef4823cd319278ad95706a129cc091e1cca9342802f1ff75aba15
$(package)_git_commit=93e26d1d8716ac88f8bb372d442315edcd2deabd
$(package)_dependencies=rust $(rust_crates)
$(package)_patches=cargo.config

Expand Down
185 changes: 185 additions & 0 deletions src/gtest/test_noteencryption.cpp
Expand Up @@ -6,7 +6,9 @@

#include "zcash/NoteEncryption.hpp"
#include "zcash/prf.h"
#include "zcash/Address.hpp"
#include "crypto/sha256.h"
#include "librustzcash.h"

class TestNoteDecryption : public ZCNoteDecryption {
public:
Expand All @@ -17,6 +19,189 @@ class TestNoteDecryption : public ZCNoteDecryption {
}
};

TEST(noteencryption, SaplingApi)
{
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});
Copy link
Contributor

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

SaplingPaymentAddress pk_2 = *ivk.address({4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});

// Blob of stuff we're encrypting
std::array<unsigned char, ZC_SAPLING_ENCPLAINTEXT_SIZE> 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<unsigned char, ZC_SAPLING_OUTPLAINTEXT_SIZE> 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_EQ(boost::none, 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();
{
uint256 test_epk;
uint256 test_esk = enc.get_esk();
ASSERT_TRUE(librustzcash_sapling_ka_derivepublic(pk_1.d.begin(), test_esk.begin(), test_epk.begin()));
ASSERT_TRUE(test_epk == epk_1);
}
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
);

// Test nonce-reuse resistance of API
{
auto tmp_enc = *SaplingNoteEncryption::FromDiversifier(pk_1.d);

tmp_enc.encrypt_to_recipient(
pk_1.pk_d,
message
);

ASSERT_THROW(tmp_enc.encrypt_to_recipient(
pk_1.pk_d,
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

message
), std::logic_error);

tmp_enc.encrypt_to_ourselves(
sk.ovk,
cv_2,
cm_2,
small_message
);

ASSERT_THROW(tmp_enc.encrypt_to_ourselves(
sk.ovk,
cv_2,
cm_2,
small_message
), std::logic_error);
}

// Try to decrypt
auto plaintext_1 = *AttemptSaplingEncDecryption(
ciphertext_1,
ivk,
epk_1
);
ASSERT_TRUE(message == plaintext_1);
Copy link
Contributor

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...


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
));
}
Copy link
Contributor

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.


TEST(noteencryption, api)
{
uint256 sk_enc = ZCNoteEncryption::generate_privkey(uint252(uint256S("21035d60bc1983e37950ce4803418a8fb33ea68d5b937ca382ecbae7564d6a07")));
Expand Down
20 changes: 2 additions & 18 deletions src/primitives/transaction.h
Expand Up @@ -84,33 +84,17 @@ class SpendDescription
}
};

static constexpr size_t SAPLING_ENC_CIPHERTEXT_SIZE = (
1 + // leading byte
11 + // d
8 + // value
32 + // rcm
ZC_MEMO_SIZE + // memo
NOTEENCRYPTION_AUTH_BYTES);

static constexpr size_t SAPLING_OUT_CIPHERTEXT_SIZE = (
32 + // pkd_new
32 + // esk
NOTEENCRYPTION_AUTH_BYTES);

/**
* A shielded output to a transaction. It contains data that describes an Output transfer.
*/
class OutputDescription
{
public:
typedef std::array<unsigned char, SAPLING_ENC_CIPHERTEXT_SIZE> sapling_enc_ct_t; // TODO: Replace with actual type
typedef std::array<unsigned char, SAPLING_OUT_CIPHERTEXT_SIZE> sapling_out_ct_t; // TODO: Replace with actual type

uint256 cv; //!< A value commitment to the value of the output note.
uint256 cm; //!< The note commitment for the output note.
uint256 ephemeralKey; //!< A Jubjub public key.
sapling_enc_ct_t encCiphertext; //!< A ciphertext component for the encrypted output note.
sapling_out_ct_t outCiphertext; //!< A ciphertext component for the encrypted output note.
libzcash::SaplingEncCiphertext encCiphertext; //!< A ciphertext component for the encrypted output note.
libzcash::SaplingOutCiphertext outCiphertext; //!< A ciphertext component for the encrypted output note.
libzcash::GrothProof zkproof; //!< A zero-knowledge proof using the output circuit.

OutputDescription() { }
Expand Down
3 changes: 2 additions & 1 deletion src/zcash/Address.hpp
Expand Up @@ -4,6 +4,7 @@
#include "uint256.h"
#include "uint252.h"
#include "serialize.h"
#include "Zcash.h"

#include <boost/variant.hpp>

Expand All @@ -18,7 +19,7 @@ const size_t SerializedPaymentAddressSize = 64;
const size_t SerializedViewingKeySize = 64;
const size_t SerializedSpendingKeySize = 32;

typedef std::array<unsigned char, 11> diversifier_t;
typedef std::array<unsigned char, ZC_DIVERSIFIER_SIZE> diversifier_t;

class SproutPaymentAddress {
public:
Expand Down