Skip to content

Commit

Permalink
z_exportivk and z_importivk RPCs
Browse files Browse the repository at this point in the history
  • Loading branch information
miodragpop committed Jul 5, 2021
1 parent b9f0860 commit 53a3e17
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 18 deletions.
39 changes: 39 additions & 0 deletions src/key_io.cpp
Expand Up @@ -361,6 +361,45 @@ std::string KeyIO::EncodeViewingKey(const libzcash::ViewingKey& vk)
return std::visit(ViewingKeyEncoder(keyConstants), vk);
}

std::string KeyIO::EncodeIVK(const libzcash::SaplingIncomingViewingKey& ivk)
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << ivk;
// ConvertBits requires unsigned char, but CDataStream uses char
std::vector<unsigned char> serkey(ss.begin(), ss.end());
std::vector<unsigned char> data;
// See calculation comment below
data.reserve((serkey.size() * 8 + 4) / 5);
ConvertBits<8, 5, true>([&](unsigned char c) { data.push_back(c); }, serkey.begin(), serkey.end());
std::string ret = bech32::Encode(keyConstants.Bech32HRP(KeyConstants::SAPLING_INCOMING_VIEWING_KEY), data);
memory_cleanse(serkey.data(), serkey.size());
memory_cleanse(data.data(), data.size());
return ret;
}

libzcash::SaplingIncomingViewingKey KeyIO::DecodeIVK(const std::string& str)
{
std::vector<unsigned char> data;
auto bech = bech32::Decode(str);
if (bech.first == keyConstants.Bech32HRP(KeyConstants::SAPLING_INCOMING_VIEWING_KEY) &&
bech.second.size() == ConvertedSaplingIncomingViewingKeySize) {
// Bech32 decoding
data.reserve((bech.second.size() * 5) / 8);
if (ConvertBits<5, 8, false>([&](unsigned char c) { data.push_back(c); }, bech.second.begin(), bech.second.end())) {
CDataStream ss(data, SER_NETWORK, PROTOCOL_VERSION);
libzcash::SaplingIncomingViewingKey ret;
ss >> ret;
memory_cleanse(data.data(), data.size());
return ret;
}
}

memory_cleanse(data.data(), data.size());
libzcash::SaplingIncomingViewingKey ret;
ret.SetNull();
return ret;
}

libzcash::ViewingKey KeyIO::DecodeViewingKey(const std::string& str)
{
return DecodeAny<libzcash::ViewingKey,
Expand Down
3 changes: 3 additions & 0 deletions src/key_io.h
Expand Up @@ -45,6 +45,9 @@ class KeyIO {
std::string EncodeViewingKey(const libzcash::ViewingKey& vk);
libzcash::ViewingKey DecodeViewingKey(const std::string& str);

std::string EncodeIVK(const libzcash::SaplingIncomingViewingKey& ivk);
libzcash::SaplingIncomingViewingKey DecodeIVK(const std::string& str);

std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey);
libzcash::SpendingKey DecodeSpendingKey(const std::string& str);
};
Expand Down
1 change: 1 addition & 0 deletions src/rpc/client.cpp
Expand Up @@ -149,6 +149,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "z_getalldiversifiedaddresses", 1},
{ "z_importkey", 2 },
{ "z_importviewingkey", 2 },
{ "z_importivk", 2 },
{ "z_getpaymentdisclosure", 1},
{ "z_getpaymentdisclosure", 2},
{ "z_setmigration", 0},
Expand Down
141 changes: 141 additions & 0 deletions src/wallet/rpcdump.cpp
Expand Up @@ -1167,3 +1167,144 @@ UniValue z_exportviewingkey(const UniValue& params, bool fHelp)
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private key or viewing key for this zaddr");
}
}

UniValue z_exportivk(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;

if (fHelp || params.size() != 1)
throw runtime_error(
"z_exportivk \"zaddr\"\n"
"\nReveals the incoming viewing key corresponding to Sapling 'zaddr'.\n"
"Then the z_importivk can be used with this output\n"
"\nArguments:\n"
"1. \"zaddr\" (string, required) The Sapling zaddr for the viewing key\n"
"\nResult:\n"
"\"vkey\" (string) The viewing key\n"
"\nExamples:\n"
+ HelpExampleCli("z_exportivk", "\"myaddress\"")
+ HelpExampleRpc("z_exportivk", "\"myaddress\"")
);

LOCK2(cs_main, pwalletMain->cs_wallet);

EnsureWalletIsUnlocked();

string strAddress = params[0].get_str();

KeyIO keyIO(Params());
auto address = keyIO.DecodePaymentAddress(strAddress);
if (!IsValidPaymentAddress(address) || !IsValidSaplingAddress(address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Sapling zaddr");
}

auto spa = std::get<libzcash::SaplingPaymentAddress>(address);
libzcash::SaplingIncomingViewingKey ivk;
if (!pwalletMain->GetSaplingIncomingViewingKey(spa, ivk)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr");
} else {
return keyIO.EncodeIVK(ivk);
}
}

UniValue z_importivk(const UniValue& params, bool fHelp)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;

if (fHelp || params.size() < 1 || params.size() > 4)
throw runtime_error(
"z_importivk \"vkey\" ( rescan startHeight )\n"
"\nAdds a viewing key (as returned by z_exportivk) to your wallet.\n"
"\nArguments:\n"
"1. \"vkey\" (string, required) The viewing key (see z_exportivk)\n"
"2. rescan (string, optional, default=\"whenkeyisnew\") Rescan the wallet for transactions - can be \"yes\", \"no\" or \"whenkeyisnew\"\n"
"3. startHeight (numeric, optional, default=0) Block height to start rescan from\n"
"4. zaddr (string, optional, default=\"\") zaddr in case of importing viewing key for Sapling\n"
"\nNote: This call can take minutes to complete if rescan is true.\n"
"\nExamples:\n"
"\nImport an incoming viewing key\n"
+ HelpExampleCli("z_importivk", "\"vkey\"") +
"\nImport the incoming viewing key without rescan\n"
+ HelpExampleCli("z_importivk", "\"vkey\" no") +
"\nImport the incoming viewing key with partial rescan\n"
+ HelpExampleCli("z_importivk", "\"vkey\" whenkeyisnew 30000") +
"\nRe-import the viewing key with longer partial rescan\n"
+ HelpExampleCli("z_importivk", "\"vkey\" yes 20000") +
"\nImport the incoming viewing key for Sapling address\n"
+ HelpExampleCli("z_importivk", "\"vkey\" no 0 \"zaddr\"") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("z_importivk", "\"vkey\", \"no\"")
);

LOCK2(cs_main, pwalletMain->cs_wallet);

EnsureWalletIsUnlocked();

// Whether to perform rescan after import
bool fRescan = true;
bool fIgnoreExistingKey = true;
if (params.size() > 1) {
auto rescan = params[1].get_str();
if (rescan.compare("whenkeyisnew") != 0) {
fIgnoreExistingKey = false;
if (rescan.compare("no") == 0) {
fRescan = false;
} else if (rescan.compare("yes") != 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"rescan must be \"yes\", \"no\" or \"whenkeyisnew\"");
}
}
}

// Height to rescan from
int nRescanHeight = 0;
if (params.size() > 2) {
nRescanHeight = params[2].get_int();
}
if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
}

string strIVKey = params[0].get_str();
KeyIO keyIO(Params());
auto ivk = keyIO.DecodeIVK(strIVKey);
if (ivk.IsNull()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid incoming viewing key");
}

if (params.size() < 4) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key.");
}
string strAddress = params[3].get_str();
auto address = keyIO.DecodePaymentAddress(strAddress);
if (!IsValidSaplingAddress(address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Sapling zaddr");
}

auto addr = std::get<libzcash::SaplingPaymentAddress>(address);

if (!(addr == ivk.address(addr.d))) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Zaddr and viewing key are not consistent.");
}

if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) {
if (fIgnoreExistingKey) {
return NullUniValue;
}
} else {
pwalletMain->MarkDirty();

if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
}
}

// We want to scan for transactions and notes
if (fRescan) {
pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight], true);
}
return NullUniValue;
}
4 changes: 4 additions & 0 deletions src/wallet/rpcwallet.cpp
Expand Up @@ -5216,6 +5216,8 @@ extern UniValue z_importkey(const UniValue& params, bool fHelp);
extern UniValue getrescaninfo(const UniValue& params, bool fHelp);
extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp);
extern UniValue z_importviewingkey(const UniValue& params, bool fHelp);
extern UniValue z_exportivk(const UniValue& params, bool fHelp);
extern UniValue z_importivk(const UniValue& params, bool fHelp);
extern UniValue z_exportwallet(const UniValue& params, bool fHelp);
extern UniValue z_importwallet(const UniValue& params, bool fHelp);

Expand Down Expand Up @@ -5294,6 +5296,8 @@ static const CRPCCommand commands[] =
{ "wallet", "z_importkey", &z_importkey, true },
{ "wallet", "z_exportviewingkey", &z_exportviewingkey, true },
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
{ "wallet", "z_exportivk", &z_exportivk, true },
{ "wallet", "z_importivk", &z_importivk, true },
{ "wallet", "z_exportwallet", &z_exportwallet, true },
{ "wallet", "z_importwallet", &z_importwallet, true },
{ "wallet", "z_viewtransaction", &z_viewtransaction, false },
Expand Down
39 changes: 21 additions & 18 deletions src/wallet/wallet.cpp
Expand Up @@ -1534,30 +1534,33 @@ void CWallet::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) {
}
else {
uint64_t position = nd.witnesses.front().position();
auto extfvk = mapSaplingFullViewingKeys.at(nd.ivk);
OutputDescription output = wtx.vShieldedOutput[op.n];
// Skip if we only have incoming viewing key
if (mapSaplingFullViewingKeys.count(nd.ivk) != 0) {
auto extfvk = mapSaplingFullViewingKeys.at(nd.ivk);
OutputDescription output = wtx.vShieldedOutput[op.n];

auto optDeserialized = SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(output.encCiphertext, nd.ivk, output.ephemeralKey);
auto optDeserialized = SaplingNotePlaintext::attempt_sapling_enc_decryption_deserialization(output.encCiphertext, nd.ivk, output.ephemeralKey);

// The transaction would not have entered the wallet unless
// its plaintext had been successfully decrypted previously.
assert(optDeserialized != std::nullopt);
// The transaction would not have entered the wallet unless
// its plaintext had been successfully decrypted previously.
assert(optDeserialized != std::nullopt);

auto optPlaintext = SaplingNotePlaintext::plaintext_checks_without_height(*optDeserialized, nd.ivk, output.ephemeralKey, output.cmu);
auto optPlaintext = SaplingNotePlaintext::plaintext_checks_without_height(*optDeserialized, nd.ivk, output.ephemeralKey, output.cmu);

// An item in mapSaplingNoteData must have already been successfully decrypted,
// otherwise the item would not exist in the first place.
assert(optPlaintext != std::nullopt);
// An item in mapSaplingNoteData must have already been successfully decrypted,
// otherwise the item would not exist in the first place.
assert(optPlaintext != std::nullopt);

auto optNote = optPlaintext.value().note(nd.ivk);
assert(optNote != std::nullopt);
auto optNote = optPlaintext.value().note(nd.ivk);
assert(optNote != std::nullopt);

auto optNullifier = optNote.value().nullifier(extfvk.fvk, position);
// This should not happen. If it does, maybe the position has been corrupted or miscalculated?
assert(optNullifier != std::nullopt);
uint256 nullifier = optNullifier.value();
mapSaplingNullifiersToNotes[nullifier] = op;
item.second.nullifier = nullifier;
auto optNullifier = optNote.value().nullifier(extfvk.fvk, position);
// This should not happen. If it does, maybe the position has been corrupted or miscalculated?
assert(optNullifier != std::nullopt);
uint256 nullifier = optNullifier.value();
mapSaplingNullifiersToNotes[nullifier] = op;
item.second.nullifier = nullifier;
}
}
}
}
Expand Down

0 comments on commit 53a3e17

Please sign in to comment.