Auto merge of #3391 - bitcartel:3061_sapling_note_encryption, r=ebfull
Sapling note plaintext (encryption and decryption) Add encryption and decryption of SaplingNotePlaintext and SaplingOutgoingPlaintext classes. This is part of #3061 to add Sapling note functionality.
This commit is contained in:
commit
a7a62b724b
|
@ -4,6 +4,7 @@
|
|||
#include <array>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "zcash/Note.hpp"
|
||||
#include "zcash/NoteEncryption.hpp"
|
||||
#include "zcash/prf.h"
|
||||
#include "zcash/Address.hpp"
|
||||
|
@ -19,6 +20,118 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
TEST(noteencryption, NotePlaintext)
|
||||
{
|
||||
using namespace libzcash;
|
||||
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<unsigned char, ZC_MEMO_SIZE> 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 sender can decrypt the note ciphertext.
|
||||
foo = SaplingNotePlaintext::decrypt(
|
||||
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)
|
||||
{
|
||||
using namespace libzcash;
|
||||
|
|
|
@ -137,3 +137,139 @@ 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<unsigned char, ZC_MEMO_SIZE> memo) : BaseNotePlaintext(note, memo)
|
||||
{
|
||||
d = note.d;
|
||||
rcm = note.r;
|
||||
}
|
||||
|
||||
|
||||
boost::optional<SaplingNote> SaplingNotePlaintext::note(const SaplingIncomingViewingKey& ivk) const
|
||||
{
|
||||
auto addr = ivk.address(d);
|
||||
if (addr) {
|
||||
return SaplingNote(d, addr.get().pk_d, value_, rcm);
|
||||
} else {
|
||||
return boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<SaplingOutgoingPlaintext> 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> 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> SaplingNotePlaintext::decrypt(
|
||||
const SaplingEncCiphertext &ciphertext,
|
||||
const uint256 &epk,
|
||||
const uint256 &esk,
|
||||
const uint256 &pk_d
|
||||
)
|
||||
{
|
||||
auto pt = AttemptSaplingEncDecryption(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<SaplingNotePlaintextEncryptionResult> 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);
|
||||
}
|
||||
|
|
|
@ -116,6 +116,89 @@ public:
|
|||
) const;
|
||||
};
|
||||
|
||||
typedef std::pair<SaplingEncCiphertext, SaplingNoteEncryption> SaplingNotePlaintextEncryptionResult;
|
||||
|
||||
class SaplingNotePlaintext : public BaseNotePlaintext {
|
||||
public:
|
||||
diversifier_t d;
|
||||
uint256 rcm;
|
||||
|
||||
SaplingNotePlaintext() {}
|
||||
|
||||
SaplingNotePlaintext(const SaplingNote& note, std::array<unsigned char, ZC_MEMO_SIZE> memo);
|
||||
|
||||
static boost::optional<SaplingNotePlaintext> decrypt(
|
||||
const SaplingEncCiphertext &ciphertext,
|
||||
const uint256 &ivk,
|
||||
const uint256 &epk
|
||||
);
|
||||
|
||||
static boost::optional<SaplingNotePlaintext> decrypt(
|
||||
const SaplingEncCiphertext &ciphertext,
|
||||
const uint256 &epk,
|
||||
const uint256 &esk,
|
||||
const uint256 &pk_d
|
||||
);
|
||||
|
||||
boost::optional<SaplingNote> note(const SaplingIncomingViewingKey& ivk) const;
|
||||
|
||||
virtual ~SaplingNotePlaintext() {}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
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<SaplingNotePlaintextEncryptionResult> 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 <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
||||
READWRITE(pk_d); // 8 bytes
|
||||
READWRITE(esk); // 8 bytes
|
||||
}
|
||||
|
||||
static boost::optional<SaplingOutgoingPlaintext> decrypt(
|
||||
const SaplingOutCiphertext &ciphertext,
|
||||
const uint256& ovk,
|
||||
const uint256& cv,
|
||||
const uint256& cm,
|
||||
const uint256& epk
|
||||
);
|
||||
|
||||
SaplingOutCiphertext encrypt(
|
||||
const uint256& ovk,
|
||||
const uint256& cv,
|
||||
const uint256& cm,
|
||||
SaplingNoteEncryption& enc
|
||||
) const;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif // ZC_NOTE_H_
|
||||
|
|
|
@ -187,6 +187,43 @@ boost::optional<SaplingEncPlaintext> AttemptSaplingEncDecryption(
|
|||
return plaintext;
|
||||
}
|
||||
|
||||
boost::optional<SaplingEncPlaintext> AttemptSaplingEncDecryption (
|
||||
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,
|
||||
|
|
|
@ -73,6 +73,15 @@ boost::optional<SaplingEncPlaintext> AttemptSaplingEncDecryption(
|
|||
const uint256 &epk
|
||||
);
|
||||
|
||||
// Attempts to decrypt a Sapling note using outgoing plaintext.
|
||||
// This will not check that the contents of the ciphertext are correct.
|
||||
boost::optional<SaplingEncPlaintext> AttemptSaplingEncDecryption (
|
||||
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<SaplingOutPlaintext> AttemptSaplingOutDecryption(
|
||||
|
|
Loading…
Reference in New Issue