Implementation of Sapling in-band secret distribution.

This commit is contained in:
Sean Bowe 2018-06-14 11:58:45 -06:00
parent 9e8e121c9f
commit 9e1c2c4049
5 changed files with 411 additions and 2 deletions

View File

@ -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<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_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")));

View File

@ -4,6 +4,7 @@
#include "uint256.h"
#include "uint252.h"
#include "serialize.h"
#include "Zcash.h"
#include <boost/variant.hpp>
@ -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:

View File

@ -3,6 +3,7 @@
#include "sodium.h"
#include <boost/static_assert.hpp>
#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> 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<SaplingEncCiphertext> 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<SaplingEncPlaintext> 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<SaplingOutPlaintext> 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<size_t MLEN>
NoteEncryption<MLEN>::NoteEncryption(uint256 hSig) : nonce(0), hSig(hSig) {
// All of this code assumes crypto_scalarmult_BYTES is 32

View File

@ -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 <array>
namespace libzcash {
#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;
// 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;
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<SaplingNoteEncryption> FromDiversifier(diversifier_t d);
boost::optional<SaplingEncCiphertext> 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<SaplingEncPlaintext> 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<SaplingOutPlaintext> AttemptSaplingOutDecryption(
const SaplingOutCiphertext &ciphertext,
const uint256 &ovk,
const uint256 &cv,
const uint256 &cm,
const uint256 &epk
);
template<size_t MLEN>
class NoteEncryption {

View File

@ -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_