Merge z_viewtransaction from str4d zcash PR4146
This commit is contained in:
committed by
Duke Leto
parent
ac8e1c966e
commit
7920cbc7e7
@@ -53,6 +53,7 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <utf8.h>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
@@ -4121,6 +4122,216 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp)
|
||||
return result;
|
||||
}
|
||||
|
||||
UniValue z_viewtransaction(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"z_viewtransaction \"txid\"\n"
|
||||
"\nGet detailed shielded information about in-wallet transaction <txid>\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"txid\" (string, required) The transaction id\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"txid\" : \"transactionid\", (string) The transaction id\n"
|
||||
" \"spends\" : [\n"
|
||||
" {\n"
|
||||
" \"type\" : \"sprout|sapling\", (string) The type of address\n"
|
||||
" \"js\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
|
||||
" \"jsSpend\" : n, (numeric, sprout) the index of the spend within the JSDescription\n"
|
||||
" \"spend\" : n, (numeric, sapling) the index of the spend within vShieldedSpend\n"
|
||||
" \"txidPrev\" : \"transactionid\", (string) The id for the transaction this note was created in\n"
|
||||
" \"jsPrev\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
|
||||
" \"jsOutputPrev\" : n, (numeric, sprout) the index of the output within the JSDescription\n"
|
||||
" \"outputPrev\" : n, (numeric, sapling) the index of the output within the vShieldedOutput\n"
|
||||
" \"address\" : \"zcashaddress\", (string) The Zcash address involved in the transaction\n"
|
||||
" \"value\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n"
|
||||
" \"valueZat\" : xxxx (numeric) The amount in zatoshis\n"
|
||||
" }\n"
|
||||
" ,...\n"
|
||||
" ],\n"
|
||||
" \"outputs\" : [\n"
|
||||
" {\n"
|
||||
" \"type\" : \"sprout|sapling\", (string) The type of address\n"
|
||||
" \"js\" : n, (numeric, sprout) the index of the JSDescription within vJoinSplit\n"
|
||||
" \"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"
|
||||
" \"recovered\" : 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"
|
||||
" \"memoStr\" : \"memo\", (string) Only returned if memo contains valid UTF-8 text.\n"
|
||||
" }\n"
|
||||
" ,...\n"
|
||||
" ],\n"
|
||||
"}\n"
|
||||
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
+ HelpExampleCli("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\" true")
|
||||
+ HelpExampleRpc("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
);
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
uint256 hash;
|
||||
hash.SetHex(params[0].get_str());
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
if (!pwalletMain->mapWallet.count(hash))
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
|
||||
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
|
||||
|
||||
entry.push_back(Pair("txid", hash.GetHex()));
|
||||
|
||||
UniValue spends(UniValue::VARR);
|
||||
UniValue outputs(UniValue::VARR);
|
||||
|
||||
// Sprout spends
|
||||
for (size_t i = 0; i < wtx.vJoinSplit.size(); ++i) {
|
||||
for (size_t j = 0; j < wtx.vJoinSplit[i].nullifiers.size(); ++j) {
|
||||
auto nullifier = wtx.vJoinSplit[i].nullifiers[j];
|
||||
|
||||
// Fetch the note that is being spent, if ours
|
||||
auto res = pwalletMain->mapSproutNullifiersToNotes.find(nullifier);
|
||||
if (res == pwalletMain->mapSproutNullifiersToNotes.end()) {
|
||||
continue;
|
||||
}
|
||||
auto jsop = res->second;
|
||||
auto wtxPrev = pwalletMain->mapWallet.at(jsop.hash);
|
||||
|
||||
auto decrypted = wtxPrev.DecryptSproutNote(jsop);
|
||||
auto notePt = decrypted.first;
|
||||
auto pa = decrypted.second;
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.push_back(Pair("type", ADDR_TYPE_SPROUT));
|
||||
entry.push_back(Pair("js", (int)i));
|
||||
entry.push_back(Pair("jsSpend", (int)j));
|
||||
entry.push_back(Pair("txidPrev", jsop.hash.GetHex()));
|
||||
entry.push_back(Pair("jsPrev", (int)jsop.js));
|
||||
entry.push_back(Pair("jsOutputPrev", (int)jsop.n));
|
||||
entry.push_back(Pair("address", EncodePaymentAddress(pa)));
|
||||
entry.push_back(Pair("value", ValueFromAmount(notePt.value())));
|
||||
entry.push_back(Pair("valueZat", notePt.value()));
|
||||
outputs.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Sprout outputs
|
||||
for (auto & pair : wtx.mapSproutNoteData) {
|
||||
JSOutPoint jsop = pair.first;
|
||||
|
||||
auto decrypted = wtx.DecryptSproutNote(jsop);
|
||||
auto notePt = decrypted.first;
|
||||
auto pa = decrypted.second;
|
||||
auto memo = notePt.memo();
|
||||
|
||||
UniValue entry(UniValue::VOBJ);
|
||||
entry.push_back(Pair("type", ADDR_TYPE_SPROUT));
|
||||
entry.push_back(Pair("js", (int)jsop.js));
|
||||
entry.push_back(Pair("jsOutput", (int)jsop.n));
|
||||
entry.push_back(Pair("address", EncodePaymentAddress(pa)));
|
||||
entry.push_back(Pair("value", ValueFromAmount(notePt.value())));
|
||||
entry.push_back(Pair("valueZat", notePt.value()));
|
||||
entry.push_back(Pair("memo", HexStr(memo)));
|
||||
if (memo[0] <= 0xf4) {
|
||||
auto end = std::find_if(memo.rbegin(), memo.rend(), [](unsigned char v) { return v != 0; });
|
||||
std::string memoStr(memo.begin(), end.base());
|
||||
if (utf8::is_valid(memoStr)) {
|
||||
entry.push_back(Pair("memoStr", memoStr));
|
||||
}
|
||||
}
|
||||
outputs.push_back(entry);
|
||||
}
|
||||
|
||||
// Sapling spends
|
||||
std::set<uint256> ovks;
|
||||
for (size_t i = 0; i < wtx.vShieldedSpend.size(); ++i) {
|
||||
auto spend = wtx.vShieldedSpend[i];
|
||||
|
||||
// Fetch the note that is being spent
|
||||
auto res = pwalletMain->mapSaplingNullifiersToNotes.find(spend.nullifier);
|
||||
if (res == pwalletMain->mapSaplingNullifiersToNotes.end()) {
|
||||
continue;
|
||||
}
|
||||
auto op = res->second;
|
||||
auto wtxPrev = pwalletMain->mapWallet.at(op.hash);
|
||||
|
||||
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));
|
||||
entry.push_back(Pair("txidPrev", op.hash.GetHex()));
|
||||
entry.push_back(Pair("outputPrev", (int)op.n));
|
||||
entry.push_back(Pair("address", EncodePaymentAddress(pa)));
|
||||
entry.push_back(Pair("value", ValueFromAmount(notePt.value())));
|
||||
entry.push_back(Pair("valueZat", notePt.value()));
|
||||
spends.push_back(entry);
|
||||
}
|
||||
|
||||
// Sapling outputs
|
||||
for (uint32_t i = 0; i < wtx.vShieldedOutput.size(); ++i) {
|
||||
auto op = SaplingOutPoint(hash, i);
|
||||
|
||||
SaplingNotePlaintext notePt;
|
||||
SaplingPaymentAddress pa;
|
||||
bool isRecovered;
|
||||
|
||||
auto decrypted = wtx.DecryptSaplingNote(op);
|
||||
if (decrypted) {
|
||||
notePt = decrypted->first;
|
||||
pa = decrypted->second;
|
||||
isRecovered = false;
|
||||
} else {
|
||||
// Try recovering the output
|
||||
auto recovered = wtx.RecoverSaplingNote(op, ovks);
|
||||
if (recovered) {
|
||||
notePt = recovered->first;
|
||||
pa = recovered->second;
|
||||
isRecovered = 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("recovered", isRecovered));
|
||||
entry.push_back(Pair("address", EncodePaymentAddress(pa)));
|
||||
entry.push_back(Pair("value", ValueFromAmount(notePt.value())));
|
||||
entry.push_back(Pair("valueZat", notePt.value()));
|
||||
entry.push_back(Pair("memo", HexStr(memo)));
|
||||
if (memo[0] <= 0xf4) {
|
||||
auto end = std::find_if(memo.rbegin(), memo.rend(), [](unsigned char v) { return v != 0; });
|
||||
std::string memoStr(memo.begin(), end.base());
|
||||
if (utf8::is_valid(memoStr)) {
|
||||
entry.push_back(Pair("memoStr", memoStr));
|
||||
}
|
||||
}
|
||||
outputs.push_back(entry);
|
||||
}
|
||||
|
||||
entry.push_back(Pair("spends", spends));
|
||||
entry.push_back(Pair("outputs", outputs));
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
UniValue z_getoperationresult(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
@@ -8287,6 +8498,7 @@ static const CRPCCommand commands[] =
|
||||
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
|
||||
{ "wallet", "z_exportwallet", &z_exportwallet, true },
|
||||
{ "wallet", "z_importwallet", &z_importwallet, true },
|
||||
{ "wallet", "z_viewtransaction", &z_viewtransaction, false },
|
||||
// TODO: rearrange into another category
|
||||
{ "disclosure", "z_getpaymentdisclosure", &z_getpaymentdisclosure, true },
|
||||
{ "disclosure", "z_validatepaymentdisclosure", &z_validatepaymentdisclosure, true }
|
||||
|
||||
@@ -2487,6 +2487,107 @@ void CWalletTx::SetSaplingNoteData(mapSaplingNoteData_t ¬eData)
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<SproutNotePlaintext, SproutPaymentAddress> CWalletTx::DecryptSproutNote(
|
||||
JSOutPoint jsop) const
|
||||
{
|
||||
LOCK(pwallet->cs_wallet);
|
||||
|
||||
auto nd = this->mapSproutNoteData.at(jsop);
|
||||
SproutPaymentAddress pa = nd.address;
|
||||
|
||||
// Get cached decryptor
|
||||
ZCNoteDecryption decryptor;
|
||||
if (!pwallet->GetNoteDecryptor(pa, decryptor)) {
|
||||
// Note decryptors are created when the wallet is loaded, so it should always exist
|
||||
throw std::runtime_error(strprintf(
|
||||
"Could not find note decryptor for payment address %s",
|
||||
EncodePaymentAddress(pa)));
|
||||
}
|
||||
|
||||
auto hSig = this->vJoinSplit[jsop.js].h_sig(*pzcashParams, this->joinSplitPubKey);
|
||||
try {
|
||||
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
|
||||
decryptor,
|
||||
this->vJoinSplit[jsop.js].ciphertexts[jsop.n],
|
||||
this->vJoinSplit[jsop.js].ephemeralKey,
|
||||
hSig,
|
||||
(unsigned char) jsop.n);
|
||||
|
||||
return std::make_pair(plaintext, pa);
|
||||
} catch (const note_decryption_failed &err) {
|
||||
// Couldn't decrypt with this spending key
|
||||
throw std::runtime_error(strprintf(
|
||||
"Could not decrypt note for payment address %s",
|
||||
EncodePaymentAddress(pa)));
|
||||
} catch (const std::exception &exc) {
|
||||
// Unexpected failure
|
||||
throw std::runtime_error(strprintf(
|
||||
"Error while decrypting note for payment address %s: %s",
|
||||
EncodePaymentAddress(pa), exc.what()));
|
||||
}
|
||||
}
|
||||
|
||||
boost::optional<std::pair<
|
||||
SaplingNotePlaintext,
|
||||
SaplingPaymentAddress>> 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);
|
||||
|
||||
auto maybe_pt = SaplingNotePlaintext::decrypt(
|
||||
output.encCiphertext,
|
||||
nd.ivk,
|
||||
output.ephemeralKey,
|
||||
output.cm);
|
||||
assert(static_cast<bool>(maybe_pt));
|
||||
auto notePt = maybe_pt.get();
|
||||
|
||||
auto maybe_pa = nd.ivk.address(notePt.d);
|
||||
assert(static_cast<bool>(maybe_pa));
|
||||
auto pa = maybe_pa.get();
|
||||
|
||||
return std::make_pair(notePt, pa);
|
||||
}
|
||||
|
||||
boost::optional<std::pair<
|
||||
SaplingNotePlaintext,
|
||||
SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote(
|
||||
SaplingOutPoint op, std::set<uint256>& 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<bool>(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;
|
||||
|
||||
@@ -567,6 +567,16 @@ public:
|
||||
void SetSproutNoteData(mapSproutNoteData_t ¬eData);
|
||||
void SetSaplingNoteData(mapSaplingNoteData_t ¬eData);
|
||||
|
||||
std::pair<libzcash::SproutNotePlaintext, libzcash::SproutPaymentAddress> DecryptSproutNote(
|
||||
JSOutPoint jsop) const;
|
||||
boost::optional<std::pair<
|
||||
libzcash::SaplingNotePlaintext,
|
||||
libzcash::SaplingPaymentAddress>> DecryptSaplingNote(SaplingOutPoint op) const;
|
||||
boost::optional<std::pair<
|
||||
libzcash::SaplingNotePlaintext,
|
||||
libzcash::SaplingPaymentAddress>> RecoverSaplingNote(
|
||||
SaplingOutPoint op, std::set<uint256>& ovks) const;
|
||||
|
||||
//! filter decides which addresses will count towards the debit
|
||||
CAmount GetDebit(const isminefilter& filter) const;
|
||||
CAmount GetCredit(const isminefilter& filter) const;
|
||||
|
||||
Reference in New Issue
Block a user