Browse Source

Add support for diversified zaddrs

These changes are merged from the following commits on YEC from miodrag:

b9f0860fb4
53a3e17170
divzaddrs
fekt 3 months ago
parent
commit
d7192b56ff
  1. 39
      src/key_io.cpp
  2. 3
      src/key_io.h
  3. 1
      src/rpc/client.cpp
  4. 275
      src/wallet/rpcdump.cpp
  5. 8
      src/wallet/rpcwallet.cpp
  6. 42
      src/wallet/wallet.cpp

39
src/key_io.cpp

@ -337,6 +337,45 @@ libzcash::ViewingKey DecodeViewingKey(const std::string& str)
return libzcash::InvalidEncoding();
}
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;
}
std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey)
{
return boost::apply_visitor(SpendingKeyEncoder(Params()), zkey);

3
src/key_io.h

@ -39,6 +39,9 @@ bool IsValidPaymentAddressString(const std::string& str, uint32_t consensusBranc
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);

1
src/rpc/client.cpp

@ -160,6 +160,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "kvupdate", 4 },
{ "z_importkey", 2 },
{ "z_importviewingkey", 2 },
{ "z_importivk", 2 },
{ "z_listsentbyaddress", 1},
{ "z_listsentbyaddress", 2},
{ "z_listsentbyaddress", 3},

275
src/wallet/rpcdump.cpp

@ -798,6 +798,140 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
return exportfilepath.string();
}
UniValue z_getalldiversifiedaddresses(const UniValue& params, bool fHelp) {
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 1)
throw runtime_error(
"z_getalldiversifiedaddresses z_address\n"
"\nReturns the list of all Sapling shielded addresses that share the same spending key as this address.\nThese are all peer diversified addresses."
"\nArguments:\n"
"1. z_address (String) The z_address to lookup\n"
"\nResult:\n"
"[ (json array of string)\n"
" \"zaddr\" (string) a zaddr belonging to the wallet which shares the same spending key\n"
" ,...\n"
"]\n"
"\nExamples:\n"
+ HelpExampleCli("z_getalldiversifiedaddresses", "my_z_address")
+ HelpExampleRpc("z_getalldiversifiedaddresses", "my_z_address")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
KeyIO keyIO(Params());
string strAddress = params[0].get_str();
auto in_address = keyIO.DecodePaymentAddress(strAddress);
if (!IsValidPaymentAddress(in_address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
}
if (!IsValidSaplingAddress(in_address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Sapling zaddr");
}
UniValue ret(UniValue::VARR);
// Get the incoming viewing key for the given address
libzcash::SaplingIncomingViewingKey in_ivk;
libzcash::SaplingExtendedFullViewingKey in_xfvk;
pwalletMain->GetSaplingIncomingViewingKey(std::get<libzcash::SaplingPaymentAddress>(in_address), in_ivk);
pwalletMain->GetSaplingFullViewingKey(in_ivk, in_xfvk);
std::set<libzcash::SaplingPaymentAddress> addresses;
pwalletMain->GetSaplingPaymentAddresses(addresses);
for (auto addr : addresses) {
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingExtendedFullViewingKey xfvk;
pwalletMain->GetSaplingIncomingViewingKey(addr, ivk);
pwalletMain->GetSaplingFullViewingKey(ivk, xfvk);
if (ivk == in_ivk && xfvk == in_xfvk) {
ret.push_back(keyIO.EncodePaymentAddress(addr));
}
}
return ret;
}
UniValue z_getnewdiversifiedaddress(const UniValue& params, bool fHelp) {
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() < 1 || params.size() > 3)
throw runtime_error(
"z_getnewdiversifiedaddress \"z_address\"\n"
"\nReturns a new diversified address based on the given z_address, and adds it to your wallet.\n"
"\nArguments:\n"
"1. \"z_address\" (string, required) An existing z address in the wallet.(see z_listaddresses)\n"
"\nExamples:\n"
"\nGet a new z address\n"
+ HelpExampleCli("z_getnewdiversifiedaddress", "\"my_z_address\"") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("z_getnewdiversifiedaddress", "\"my_z_address\"")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
KeyIO keyIO(Params());
string strAddress = params[0].get_str();
auto in_address = keyIO.DecodePaymentAddress(strAddress);
if (!IsValidPaymentAddress(in_address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
}
if (!IsValidSaplingAddress(in_address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Sapling zaddr");
}
if (!std::visit(HaveSpendingKeyForPaymentAddress(pwalletMain), in_address)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private zkey for this zaddr");
}
// Get Sapling Address
auto sk = std::visit(GetSpendingKeyForPaymentAddress(pwalletMain), in_address).value();
// Now, get a new diversified address from the private key
auto espk = std::get<libzcash::SaplingExtendedSpendingKey>(sk);
arith_uint88 div;
libzcash::PaymentAddress address;
// Iterate over the diversified addresses
while (true) {
div++;
// Try to obtain an address with the default diversifier
auto try_address = espk.ToXFVK().Address(ArithToUint88(div));
// If there is no address, that means the diversifier was incompatible (~50% chance)
if (!try_address.has_value()) {
// Increment the diversifier and try again
continue;
}
// Update the diversifier from the one that was returned
div = UintToArith88(try_address.value().first);
// Check if the address exists
if (std::visit(PaymentAddressBelongsToWallet(pwalletMain), libzcash::PaymentAddress(try_address.value().second))) {
continue;
}
// If it doesn't exist, then add it.
pwalletMain->AddSaplingIncomingViewingKey(espk.expsk.full_viewing_key().in_viewing_key(), try_address.value().second);
address = try_address.value().second;
break;
}
return keyIO.EncodePaymentAddress(address);
}
UniValue z_importkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
@ -1064,6 +1198,147 @@ UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& m
return EncodeViewingKey(ivk);
}
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;
}
extern int32_t HUSH_NSPV;
#ifndef HUSH_NSPV_FULLNODE
#define HUSH_NSPV_FULLNODE (HUSH_NSPV <= 0)

8
src/wallet/rpcwallet.cpp

@ -6216,6 +6216,10 @@ extern UniValue z_importkey(const UniValue& params, bool fHelp, const CPubKey& m
extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue z_exportwallet(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue z_getnewdiversifiedaddress(const UniValue& params, bool fHelp);
extern UniValue z_getalldiversifiedaddresses(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_importwallet(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue rescan(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue getrescaninfo(const UniValue& params, bool fHelp, const CPubKey& mypk);
@ -6295,6 +6299,10 @@ static const CRPCCommand commands[] =
{ "wallet", "z_exportviewingkey", &z_exportviewingkey, true },
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
{ "wallet", "z_exportwallet", &z_exportwallet, true },
{ "wallet", "z_getnewdiversifiedaddress", &z_getnewdiversifiedaddress, true},
{ "wallet", "z_getalldiversifiedaddresses", &z_getalldiversifiedaddresses, true},
{ "wallet", "z_exportivk", &z_exportivk, true },
{ "wallet", "z_importivk", &z_importivk, true },
{ "wallet", "z_importwallet", &z_importwallet, true },
{ "wallet", "z_viewtransaction", &z_viewtransaction, true },
{ "wallet", "z_getinfo", &z_getinfo, true },

42
src/wallet/wallet.cpp

@ -1407,32 +1407,36 @@ void CWallet::UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx) {
if (nd.witnesses.empty()) {
// If there are no witnesses, erase the nullifier and associated mapping.
if (item.second.nullifier) {
mapSaplingNullifiersToNotes.erase(item.second.nullifier.get());
mapSaplingNullifiersToNotes.erase(item.second.nullifier.value());
}
item.second.nullifier = boost::none;
item.second.nullifier = std::nullopt;
}
else {
uint64_t position = nd.witnesses.front().position();
// Skip if we only have incoming viewing key
if (mapSaplingFullViewingKeys.count(nd.ivk) != 0) {
SaplingFullViewingKey fvk = mapSaplingFullViewingKeys.at(nd.ivk);
auto extfvk = mapSaplingFullViewingKeys.at(nd.ivk);
OutputDescription output = wtx.vShieldedOutput[op.n];
auto optPlaintext = SaplingNotePlaintext::decrypt(output.encCiphertext, nd.ivk, output.ephemeralKey, output.cm);
if (!optPlaintext) {
// An item in mapSaplingNoteData must have already been successfully decrypted,
// otherwise the item would not exist in the first place.
assert(false);
}
auto optNote = optPlaintext.get().note(nd.ivk);
if (!optNote) {
assert(false);
}
auto optNullifier = optNote.get().nullifier(fvk, position);
if (!optNullifier) {
// This should not happen. If it does, maybe the position has been corrupted or miscalculated?
assert(false);
}
uint256 nullifier = optNullifier.get();
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);
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);
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;
}

Loading…
Cancel
Save