Generate an ovk to encrypt outCiphertext for t-addr senders
Closes #3506.
This commit is contained in:
@@ -1254,6 +1254,108 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
|
||||
LOCK(pwalletMain->cs_wallet);
|
||||
|
||||
if (!pwalletMain->HaveHDSeed()) {
|
||||
pwalletMain->GenerateNewSeed();
|
||||
}
|
||||
|
||||
UniValue retValue;
|
||||
|
||||
// add keys manually
|
||||
auto taddr = pwalletMain->GenerateNewKey().GetID();
|
||||
std::string taddr1 = EncodeDestination(taddr);
|
||||
auto pa = pwalletMain->GenerateNewSaplingZKey();
|
||||
std::string zaddr1 = EncodePaymentAddress(pa);
|
||||
|
||||
auto consensusParams = Params().GetConsensus();
|
||||
retValue = CallRPC("getblockcount");
|
||||
int nextBlockHeight = retValue.get_int() + 1;
|
||||
|
||||
// Add a fake transaction to the wallet
|
||||
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
|
||||
CScript scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(taddr) << OP_EQUALVERIFY << OP_CHECKSIG;
|
||||
mtx.vout.push_back(CTxOut(5 * COIN, scriptPubKey));
|
||||
CWalletTx wtx(pwalletMain, mtx);
|
||||
pwalletMain->AddToWallet(wtx, true, NULL);
|
||||
|
||||
// Fake-mine the transaction
|
||||
BOOST_CHECK_EQUAL(0, chainActive.Height());
|
||||
CBlock block;
|
||||
block.hashPrevBlock = chainActive.Tip()->GetBlockHash();
|
||||
block.vtx.push_back(wtx);
|
||||
block.hashMerkleRoot = block.BuildMerkleTree();
|
||||
auto blockHash = block.GetHash();
|
||||
CBlockIndex fakeIndex {block};
|
||||
fakeIndex.nHeight = 1;
|
||||
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
|
||||
chainActive.SetTip(&fakeIndex);
|
||||
BOOST_CHECK(chainActive.Contains(&fakeIndex));
|
||||
BOOST_CHECK_EQUAL(1, chainActive.Height());
|
||||
wtx.SetMerkleBranch(block);
|
||||
pwalletMain->AddToWallet(wtx, true, NULL);
|
||||
|
||||
// Context that z_sendmany requires
|
||||
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, pwalletMain);
|
||||
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
|
||||
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 1 * COIN, "ABCD") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(builder, mtx, taddr1, {}, recipients, 0) );
|
||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||
|
||||
// Enable test mode so tx is not sent
|
||||
static_cast<AsyncRPCOperation_sendmany *>(operation.get())->testmode = true;
|
||||
|
||||
// Generate the Sapling shielding transaction
|
||||
operation->main();
|
||||
BOOST_CHECK(operation->isSuccess());
|
||||
|
||||
// Get the transaction
|
||||
auto result = operation->getResult();
|
||||
BOOST_ASSERT(result.isObject());
|
||||
auto hexTx = result["hex"].getValStr();
|
||||
CDataStream ss(ParseHex(hexTx), SER_NETWORK, PROTOCOL_VERSION);
|
||||
CTransaction tx;
|
||||
ss >> tx;
|
||||
BOOST_ASSERT(!tx.vShieldedOutput.empty());
|
||||
|
||||
// We shouldn't be able to decrypt with the empty ovk
|
||||
BOOST_CHECK(!AttemptSaplingOutDecryption(
|
||||
tx.vShieldedOutput[0].outCiphertext,
|
||||
uint256(),
|
||||
tx.vShieldedOutput[0].cv,
|
||||
tx.vShieldedOutput[0].cm,
|
||||
tx.vShieldedOutput[0].ephemeralKey));
|
||||
|
||||
// We should be able to decrypt the outCiphertext with the ovk
|
||||
// generated for transparent addresses
|
||||
HDSeed seed;
|
||||
BOOST_ASSERT(pwalletMain->GetHDSeed(seed));
|
||||
BOOST_CHECK(AttemptSaplingOutDecryption(
|
||||
tx.vShieldedOutput[0].outCiphertext,
|
||||
ovkForShieldingFromTaddr(seed),
|
||||
tx.vShieldedOutput[0].cv,
|
||||
tx.vShieldedOutput[0].cm,
|
||||
tx.vShieldedOutput[0].ephemeralKey));
|
||||
|
||||
// Tear down
|
||||
chainActive.SetTip(NULL);
|
||||
mapBlockIndex.erase(blockHash);
|
||||
mapArgs.erase("-developersapling");
|
||||
mapArgs.erase("-experimentalfeatures");
|
||||
|
||||
// Revert to default
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* This test covers storing encrypted zkeys in the wallet.
|
||||
*/
|
||||
|
||||
@@ -386,7 +386,17 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||
expsk = sk.expsk;
|
||||
ovk = expsk.full_viewing_key().ovk;
|
||||
} else {
|
||||
// TODO: Set "from" to something!
|
||||
// Sending from a t-address, which we don't have an ovk for. Instead,
|
||||
// generate a common one from the HD seed. This ensures the data is
|
||||
// recoverable, while keeping it logically separate from the ZIP 32
|
||||
// Sapling key hierarchy, which the user might not be using.
|
||||
HDSeed seed;
|
||||
if (!pwalletMain->GetHDSeed(seed)) {
|
||||
throw JSONRPCError(
|
||||
RPC_WALLET_ERROR,
|
||||
"CWallet::GenerateNewSaplingZKey(): HD seed not found");
|
||||
}
|
||||
ovk = ovkForShieldingFromTaddr(seed);
|
||||
}
|
||||
|
||||
// Set change address if we are using transparent funds
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "random.h"
|
||||
#include "streams.h"
|
||||
#include "version.h"
|
||||
#include "zcash/prf.h"
|
||||
|
||||
#include <librustzcash.h>
|
||||
#include <sodium.h>
|
||||
@@ -15,6 +16,9 @@
|
||||
const unsigned char ZCASH_HD_SEED_FP_PERSONAL[crypto_generichash_blake2b_PERSONALBYTES] =
|
||||
{'Z', 'c', 'a', 's', 'h', '_', 'H', 'D', '_', 'S', 'e', 'e', 'd', '_', 'F', 'P'};
|
||||
|
||||
const unsigned char ZCASH_TADDR_OVK_PERSONAL[crypto_generichash_blake2b_PERSONALBYTES] =
|
||||
{'Z', 'c', 'T', 'a', 'd', 'd', 'r', 'T', 'o', 'S', 'a', 'p', 'l', 'i', 'n', 'g'};
|
||||
|
||||
HDSeed HDSeed::Random(size_t len)
|
||||
{
|
||||
assert(len >= 32);
|
||||
@@ -30,6 +34,29 @@ uint256 HDSeed::Fingerprint() const
|
||||
return h.GetHash();
|
||||
}
|
||||
|
||||
uint256 ovkForShieldingFromTaddr(HDSeed& seed) {
|
||||
auto rawSeed = seed.RawSeed();
|
||||
|
||||
// I = BLAKE2b-512("ZcTaddrToSapling", seed)
|
||||
crypto_generichash_blake2b_state state;
|
||||
assert(crypto_generichash_blake2b_init_salt_personal(
|
||||
&state,
|
||||
NULL, 0, // No key.
|
||||
64,
|
||||
NULL, // No salt.
|
||||
ZCASH_TADDR_OVK_PERSONAL) == 0);
|
||||
crypto_generichash_blake2b_update(&state, rawSeed.data(), rawSeed.size());
|
||||
auto intermediate = std::array<unsigned char, 64>();
|
||||
crypto_generichash_blake2b_final(&state, intermediate.data(), 64);
|
||||
|
||||
// I_L = I[0..32]
|
||||
uint256 intermediate_L;
|
||||
memcpy(intermediate_L.begin(), intermediate.data(), 32);
|
||||
|
||||
// ovk = truncate_32(PRF^expand(I_L, [0x02]))
|
||||
return PRF_ovk(intermediate_L);
|
||||
}
|
||||
|
||||
namespace libzcash {
|
||||
|
||||
boost::optional<SaplingExtendedFullViewingKey> SaplingExtendedFullViewingKey::Derive(uint32_t i) const
|
||||
|
||||
@@ -42,6 +42,9 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
// This is not part of ZIP 32, but is here because it's linked to the HD seed.
|
||||
uint256 ovkForShieldingFromTaddr(HDSeed& seed);
|
||||
|
||||
namespace libzcash {
|
||||
|
||||
typedef blob88 diversifier_index_t;
|
||||
|
||||
Reference in New Issue
Block a user