Auto merge of #4146 - str4d:z_viewtransaction, r=str4d
z_viewtransaction This RPC method returns all decryptable information for any transaction in the wallet. Several values are conditionally included in the output for convenience: - `recovered`: True if an output is not for a Sapling address in the wallet. - `memoStr`: The text form of an output's memo, if it is valid UTF-8. - Values are provided both in decimal currency units, and integer zatoshis.
This commit is contained in:
commit
27c04c8f9c
|
@ -120,6 +120,10 @@ Files: depends/sources/qpid-proton-*.tar.gz
|
|||
Copyright: 2012-2017 The Apache Software Foundation
|
||||
License: Apache-Qpid-Proton-with-BSD-Subcomponents
|
||||
|
||||
Files: depends/sources/utfcpp-*.tar.gz
|
||||
Copyright: 2006 Nemanja Trifunovic
|
||||
License: Boost-Software-License-1.0
|
||||
|
||||
Files: src/secp256k1/build-aux/m4/ax_jni_include_dir.m4
|
||||
Copyright: 2008 Don Anderson <dda@sleepycat.com>
|
||||
License: GNU-All-permissive-License
|
||||
|
|
|
@ -67,7 +67,7 @@ rust_crates := \
|
|||
crate_winapi_x86_64_pc_windows_gnu
|
||||
rust_packages := rust $(rust_crates) librustzcash
|
||||
proton_packages := proton
|
||||
zcash_packages := libsodium
|
||||
zcash_packages := libsodium utfcpp
|
||||
packages := boost openssl libevent zeromq $(zcash_packages) googletest
|
||||
native_packages := native_ccache
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package=utfcpp
|
||||
$(package)_version=3.1
|
||||
$(package)_download_path=https://github.com/nemtrif/$(package)/archive/
|
||||
$(package)_file_name=$(package)-$($(package)_version).tar.gz
|
||||
$(package)_download_file=v$($(package)_version).tar.gz
|
||||
$(package)_sha256_hash=ab531c3fd5d275150430bfaca01d7d15e017a188183be932322f2f651506b096
|
||||
|
||||
define $(package)_stage_cmds
|
||||
cp -a ./source $($(package)_staging_dir)$(host_prefix)/include
|
||||
endef
|
|
@ -8,7 +8,8 @@ from test_framework.util import assert_equal, assert_true, assert_false
|
|||
from test_framework.util import wait_and_assert_operationid_status
|
||||
from decimal import Decimal
|
||||
|
||||
my_memo = 'c0ffee' # stay awake
|
||||
my_memo_str = 'c0ffee' # stay awake
|
||||
my_memo = '633066666565'
|
||||
my_memo = my_memo + '0'*(1024-len(my_memo))
|
||||
|
||||
no_memo = 'f6' + ('0'*1022) # see section 5.5 of the protocol spec
|
||||
|
@ -28,15 +29,73 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
self.generate_and_sync(height+1)
|
||||
taddr = self.nodes[1].getnewaddress()
|
||||
zaddr1 = self.nodes[1].z_getnewaddress(release)
|
||||
zaddrExt = self.nodes[3].z_getnewaddress(release)
|
||||
|
||||
self.nodes[0].sendtoaddress(taddr, 2.0)
|
||||
self.nodes[0].sendtoaddress(taddr, 4.0)
|
||||
self.generate_and_sync(height+2)
|
||||
|
||||
# Send 1 ZEC to zaddr1
|
||||
opid = self.nodes[1].z_sendmany(taddr,
|
||||
[{'address': zaddr1, 'amount': 1, 'memo': my_memo}])
|
||||
opid = self.nodes[1].z_sendmany(taddr, [
|
||||
{'address': zaddr1, 'amount': 1, 'memo': my_memo},
|
||||
{'address': zaddrExt, 'amount': 2},
|
||||
])
|
||||
txid = wait_and_assert_operationid_status(self.nodes[1], opid)
|
||||
self.sync_all()
|
||||
|
||||
# Decrypted transaction details should be correct
|
||||
pt = self.nodes[1].z_viewtransaction(txid)
|
||||
assert_equal(pt['txid'], txid)
|
||||
assert_equal(len(pt['spends']), 0)
|
||||
assert_equal(len(pt['outputs']), 1 if release == 'sprout' else 2)
|
||||
|
||||
# Output orders can be randomized, so we check the output
|
||||
# positions and contents separately
|
||||
outputs = []
|
||||
|
||||
assert_equal(pt['outputs'][0]['type'], release)
|
||||
if release == 'sprout':
|
||||
assert_equal(pt['outputs'][0]['js'], 0)
|
||||
jsOutputPrev = pt['outputs'][0]['jsOutput']
|
||||
elif pt['outputs'][0]['address'] == zaddr1:
|
||||
assert_equal(pt['outputs'][0]['outgoing'], False)
|
||||
assert_equal(pt['outputs'][0]['memoStr'], my_memo_str)
|
||||
else:
|
||||
assert_equal(pt['outputs'][0]['outgoing'], True)
|
||||
outputs.append({
|
||||
'address': pt['outputs'][0]['address'],
|
||||
'value': pt['outputs'][0]['value'],
|
||||
'valueZat': pt['outputs'][0]['valueZat'],
|
||||
'memo': pt['outputs'][0]['memo'],
|
||||
})
|
||||
|
||||
if release != 'sprout':
|
||||
assert_equal(pt['outputs'][1]['type'], release)
|
||||
if pt['outputs'][1]['address'] == zaddr1:
|
||||
assert_equal(pt['outputs'][1]['outgoing'], False)
|
||||
assert_equal(pt['outputs'][1]['memoStr'], my_memo_str)
|
||||
else:
|
||||
assert_equal(pt['outputs'][1]['outgoing'], True)
|
||||
outputs.append({
|
||||
'address': pt['outputs'][1]['address'],
|
||||
'value': pt['outputs'][1]['value'],
|
||||
'valueZat': pt['outputs'][1]['valueZat'],
|
||||
'memo': pt['outputs'][1]['memo'],
|
||||
})
|
||||
|
||||
assert({
|
||||
'address': zaddr1,
|
||||
'value': Decimal('1'),
|
||||
'valueZat': 100000000,
|
||||
'memo': my_memo,
|
||||
} in outputs)
|
||||
if release != 'sprout':
|
||||
assert({
|
||||
'address': zaddrExt,
|
||||
'value': Decimal('2'),
|
||||
'valueZat': 200000000,
|
||||
'memo': no_memo,
|
||||
} in outputs)
|
||||
|
||||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1)
|
||||
assert_equal(0, len(r), "Should have received no confirmed note")
|
||||
|
||||
|
@ -55,6 +114,7 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
assert_equal(r, self.nodes[1].z_listreceivedbyaddress(zaddr1))
|
||||
|
||||
# Generate some change by sending part of zaddr1 to zaddr2
|
||||
txidPrev = txid
|
||||
zaddr2 = self.nodes[1].z_getnewaddress(release)
|
||||
opid = self.nodes[1].z_sendmany(zaddr1,
|
||||
[{'address': zaddr2, 'amount': 0.6}])
|
||||
|
@ -62,6 +122,65 @@ class ListReceivedTest (BitcoinTestFramework):
|
|||
self.sync_all()
|
||||
self.generate_and_sync(height+4)
|
||||
|
||||
# Decrypted transaction details should be correct
|
||||
pt = self.nodes[1].z_viewtransaction(txid)
|
||||
assert_equal(pt['txid'], txid)
|
||||
assert_equal(len(pt['spends']), 1)
|
||||
assert_equal(len(pt['outputs']), 2)
|
||||
|
||||
assert_equal(pt['spends'][0]['type'], release)
|
||||
assert_equal(pt['spends'][0]['txidPrev'], txidPrev)
|
||||
if release == 'sprout':
|
||||
assert_equal(pt['spends'][0]['js'], 0)
|
||||
# jsSpend is randomised during transaction creation
|
||||
assert_equal(pt['spends'][0]['jsPrev'], 0)
|
||||
assert_equal(pt['spends'][0]['jsOutputPrev'], jsOutputPrev)
|
||||
else:
|
||||
assert_equal(pt['spends'][0]['spend'], 0)
|
||||
assert_equal(pt['spends'][0]['outputPrev'], 0)
|
||||
assert_equal(pt['spends'][0]['address'], zaddr1)
|
||||
assert_equal(pt['spends'][0]['value'], Decimal('1.0'))
|
||||
assert_equal(pt['spends'][0]['valueZat'], 100000000)
|
||||
|
||||
# Output orders can be randomized, so we check the output
|
||||
# positions and contents separately
|
||||
outputs = []
|
||||
|
||||
assert_equal(pt['outputs'][0]['type'], release)
|
||||
if release == 'sapling':
|
||||
assert_equal(pt['outputs'][0]['output'], 0)
|
||||
assert_equal(pt['outputs'][0]['outgoing'], False)
|
||||
outputs.append({
|
||||
'address': pt['outputs'][0]['address'],
|
||||
'value': pt['outputs'][0]['value'],
|
||||
'valueZat': pt['outputs'][0]['valueZat'],
|
||||
'memo': pt['outputs'][0]['memo'],
|
||||
})
|
||||
|
||||
assert_equal(pt['outputs'][1]['type'], release)
|
||||
if release == 'sapling':
|
||||
assert_equal(pt['outputs'][1]['output'], 1)
|
||||
assert_equal(pt['outputs'][1]['outgoing'], False)
|
||||
outputs.append({
|
||||
'address': pt['outputs'][1]['address'],
|
||||
'value': pt['outputs'][1]['value'],
|
||||
'valueZat': pt['outputs'][1]['valueZat'],
|
||||
'memo': pt['outputs'][1]['memo'],
|
||||
})
|
||||
|
||||
assert({
|
||||
'address': zaddr2,
|
||||
'value': Decimal('0.6'),
|
||||
'valueZat': 60000000,
|
||||
'memo': no_memo,
|
||||
} in outputs)
|
||||
assert({
|
||||
'address': zaddr1,
|
||||
'value': Decimal('0.3999'),
|
||||
'valueZat': 39990000,
|
||||
'memo': no_memo,
|
||||
} in outputs)
|
||||
|
||||
# zaddr1 should have a note with change
|
||||
r = self.nodes[1].z_listreceivedbyaddress(zaddr1, 0)
|
||||
r = sorted(r, key = lambda received: received['amount'])
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <utf8.h>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
|
@ -3524,6 +3525,226 @@ 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"
|
||||
" \"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"
|
||||
" \"memoStr\" : \"memo\", (string) Only returned if memo contains valid UTF-8 text.\n"
|
||||
" }\n"
|
||||
" ,...\n"
|
||||
" ],\n"
|
||||
"}\n"
|
||||
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("z_viewtransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
|
||||
+ 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);
|
||||
|
||||
auto addMemo = [](UniValue &entry, std::array<unsigned char, ZC_MEMO_SIZE> &memo) {
|
||||
entry.push_back(Pair("memo", HexStr(memo)));
|
||||
|
||||
// If the leading byte is 0xF4 or lower, the memo field should be interpreted as a
|
||||
// UTF-8-encoded text string.
|
||||
if (memo[0] <= 0xf4) {
|
||||
// Trim off trailing zeroes
|
||||
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));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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()));
|
||||
spends.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()));
|
||||
addMemo(entry, memo);
|
||||
outputs.push_back(entry);
|
||||
}
|
||||
|
||||
// Collect OutgoingViewingKeys for recovering output information
|
||||
std::set<uint256> 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];
|
||||
|
||||
// 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 isOutgoing;
|
||||
|
||||
auto decrypted = wtx.DecryptSaplingNote(op);
|
||||
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()));
|
||||
addMemo(entry, memo);
|
||||
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))
|
||||
|
@ -4809,6 +5030,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 }
|
||||
|
|
|
@ -2244,6 +2244,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;
|
||||
|
|
|
@ -563,6 +563,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;
|
||||
|
|
Loading…
Reference in New Issue