diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index c59a0f6a9..bb4b76403 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3553,6 +3553,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp) " \"jsOutput\" : n, (numeric, sprout) the index of the output within the JSDescription\n" " \"output\" : n, (numeric, sapling) the index of the output within the vShieldedOutput\n" " \"address\" : \"zcashaddress\", (string) The Zcash address involved in the transaction\n" + " \"outgoing\" : true|false (boolean, sapling) True if the output is not for an address in the wallet\n" " \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n" " \"valueZat\" : xxxx (numeric) The amount in zatoshis\n" " \"memo\" : \"hexmemo\", (string) Hexademical string representation of the memo field\n" @@ -3651,6 +3652,14 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp) outputs.push_back(entry); } + // Collect OutgoingViewingKeys for recovering output information + std::set ovks; + { + // Generate the common ovk for recovering t->z outputs. + HDSeed seed = pwalletMain->GetHDSeedForRPC(); + ovks.insert(ovkForShieldingFromTaddr(seed)); + } + // Sapling spends for (size_t i = 0; i < wtx.vShieldedSpend.size(); ++i) { auto spend = wtx.vShieldedSpend[i]; @@ -3663,10 +3672,15 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp) auto op = res->second; auto wtxPrev = pwalletMain->mapWallet.at(op.hash); - auto decrypted = wtxPrev.DecryptSaplingNote(op); + auto decrypted = wtxPrev.DecryptSaplingNote(op).get(); auto notePt = decrypted.first; auto pa = decrypted.second; + // Store the OutgoingViewingKey for recovering outputs + libzcash::SaplingFullViewingKey fvk; + assert(pwalletMain->GetSaplingFullViewingKey(wtxPrev.mapSaplingNoteData.at(op).ivk, fvk)); + ovks.insert(fvk.ovk); + UniValue entry(UniValue::VOBJ); entry.push_back(Pair("type", ADDR_TYPE_SAPLING)); entry.push_back(Pair("spend", (int)i)); @@ -3679,17 +3693,36 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp) } // Sapling outputs - for (auto & pair : wtx.mapSaplingNoteData) { - SaplingOutPoint op = pair.first; + for (uint32_t i = 0; i < wtx.vShieldedOutput.size(); ++i) { + auto op = SaplingOutPoint(hash, i); + + SaplingNotePlaintext notePt; + SaplingPaymentAddress pa; + bool isOutgoing; auto decrypted = wtx.DecryptSaplingNote(op); - auto notePt = decrypted.first; - auto pa = decrypted.second; + if (decrypted) { + notePt = decrypted->first; + pa = decrypted->second; + isOutgoing = false; + } else { + // Try recovering the output + auto recovered = wtx.RecoverSaplingNote(op, ovks); + if (recovered) { + notePt = recovered->first; + pa = recovered->second; + isOutgoing = true; + } else { + // Unreadable + continue; + } + } auto memo = notePt.memo(); UniValue entry(UniValue::VOBJ); entry.push_back(Pair("type", ADDR_TYPE_SAPLING)); entry.push_back(Pair("output", (int)op.n)); + entry.push_back(Pair("outgoing", isOutgoing)); entry.push_back(Pair("address", EncodePaymentAddress(pa))); entry.push_back(Pair("value", ValueFromAmount(notePt.value()))); entry.push_back(Pair("valueZat", notePt.value())); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index e6fbbc526..77a0dec3b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -2258,9 +2258,15 @@ std::pair CWalletTx::DecryptSproutNot } } -std::pair CWalletTx::DecryptSaplingNote( - SaplingOutPoint op) const +boost::optional> CWalletTx::DecryptSaplingNote(SaplingOutPoint op) const { + // Check whether we can decrypt this SaplingOutPoint + if (this->mapSaplingNoteData.count(op) == 0) { + return boost::none; + } + auto output = this->vShieldedOutput[op.n]; auto nd = this->mapSaplingNoteData.at(op); @@ -2279,6 +2285,40 @@ std::pair CWalletTx::DecryptSapling return std::make_pair(notePt, pa); } +boost::optional> CWalletTx::RecoverSaplingNote( + SaplingOutPoint op, std::set& ovks) const +{ + auto output = this->vShieldedOutput[op.n]; + + for (auto ovk : ovks) { + auto outPt = SaplingOutgoingPlaintext::decrypt( + output.outCiphertext, + ovk, + output.cv, + output.cm, + output.ephemeralKey); + if (!outPt) { + continue; + } + + auto maybe_pt = SaplingNotePlaintext::decrypt( + output.encCiphertext, + output.ephemeralKey, + outPt->esk, + outPt->pk_d, + output.cm); + assert(static_cast(maybe_pt)); + auto notePt = maybe_pt.get(); + + return std::make_pair(notePt, SaplingPaymentAddress(notePt.d, outPt->pk_d)); + } + + // Couldn't recover with any of the provided OutgoingViewingKeys + return boost::none; +} + int64_t CWalletTx::GetTxTime() const { int64_t n = nTimeSmart; diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index f45b42a05..5f3b13e02 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -557,8 +557,13 @@ public: std::pair DecryptSproutNote( JSOutPoint jsop) const; - std::pair DecryptSaplingNote( - SaplingOutPoint op) const; + boost::optional> DecryptSaplingNote(SaplingOutPoint op) const; + boost::optional> RecoverSaplingNote( + SaplingOutPoint op, std::set& ovks) const; //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const;