Auto merge of #2159 - bitcartel:1.0.7_payment_disclosure, r=str4d
Payment disclosure (experimental feature)
This commit is contained in:
@@ -28,6 +28,8 @@
|
||||
#include <thread>
|
||||
#include <string>
|
||||
|
||||
#include "paymentdisclosuredb.h"
|
||||
|
||||
using namespace libzcash;
|
||||
|
||||
int find_output(UniValue obj, int n) {
|
||||
@@ -103,6 +105,10 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||
} else {
|
||||
LogPrint("zrpc", "%s: z_sendmany initialized\n", getId());
|
||||
}
|
||||
|
||||
|
||||
// Enable payment disclosure if requested
|
||||
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", false);
|
||||
}
|
||||
|
||||
AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() {
|
||||
@@ -169,6 +175,21 @@ void AsyncRPCOperation_sendmany::main() {
|
||||
s += strprintf(", error=%s)\n", getErrorMessage());
|
||||
}
|
||||
LogPrintf("%s",s);
|
||||
|
||||
// !!! Payment disclosure START
|
||||
if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) {
|
||||
uint256 txidhash = tx_.GetHash();
|
||||
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
|
||||
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
|
||||
p.first.hash = txidhash;
|
||||
if (!db->Put(p.first, p.second)) {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
|
||||
} else {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
}
|
||||
|
||||
// Notes:
|
||||
@@ -945,6 +966,9 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
{info.vjsout[0], info.vjsout[1]};
|
||||
boost::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
|
||||
boost::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
|
||||
|
||||
uint256 esk; // payment disclosure - secret
|
||||
|
||||
JSDescription jsdesc = JSDescription::Randomized(
|
||||
*pzcashParams,
|
||||
joinSplitPubKey_,
|
||||
@@ -955,8 +979,8 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
outputMap,
|
||||
info.vpub_old,
|
||||
info.vpub_new,
|
||||
!this->testmode);
|
||||
|
||||
!this->testmode,
|
||||
&esk); // parameter expects pointer to esk, so pass in address
|
||||
{
|
||||
auto verifier = libzcash::ProofVerifier::Strict();
|
||||
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
|
||||
@@ -1025,6 +1049,28 @@ UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
|
||||
arrOutputMap.push_back(outputMap[i]);
|
||||
}
|
||||
|
||||
|
||||
// !!! Payment disclosure START
|
||||
unsigned char buffer[32] = {0};
|
||||
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
|
||||
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
|
||||
uint256 joinSplitPrivKey = uint256(vch);
|
||||
size_t js_index = tx_.vjoinsplit.size() - 1;
|
||||
uint256 placeholder;
|
||||
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
uint8_t mapped_index = outputMap[i];
|
||||
// placeholder for txid will be filled in later when tx has been finalized and signed.
|
||||
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
|
||||
JSOutput output = outputs[mapped_index];
|
||||
libzcash::PaymentAddress zaddr = output.addr; // randomized output
|
||||
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
|
||||
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
|
||||
|
||||
CZCPaymentAddress address(zaddr);
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), address.ToString());
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("encryptednote1", encryptedNote1));
|
||||
obj.push_back(Pair("encryptednote2", encryptedNote2));
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "zcash/JoinSplit.hpp"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "wallet.h"
|
||||
#include "paymentdisclosure.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
@@ -65,6 +66,8 @@ public:
|
||||
|
||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||
|
||||
bool paymentDisclosureMode = false; // Set to true to save esk for encrypted notes in payment disclosure database.
|
||||
|
||||
private:
|
||||
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
|
||||
|
||||
@@ -113,6 +116,8 @@ private:
|
||||
|
||||
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
|
||||
|
||||
// payment disclosure!
|
||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
|
||||
#include "asyncrpcoperation_shieldcoinbase.h"
|
||||
|
||||
#include "paymentdisclosure.h"
|
||||
#include "paymentdisclosuredb.h"
|
||||
|
||||
using namespace libzcash;
|
||||
|
||||
static int find_output(UniValue obj, int n) {
|
||||
@@ -80,6 +83,9 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
|
||||
|
||||
// Lock UTXOs
|
||||
lock_utxos();
|
||||
|
||||
// Enable payment disclosure if requested
|
||||
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", false);
|
||||
}
|
||||
|
||||
AsyncRPCOperation_shieldcoinbase::~AsyncRPCOperation_shieldcoinbase() {
|
||||
@@ -150,6 +156,21 @@ void AsyncRPCOperation_shieldcoinbase::main() {
|
||||
LogPrintf("%s",s);
|
||||
|
||||
unlock_utxos(); // clean up
|
||||
|
||||
// !!! Payment disclosure START
|
||||
if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) {
|
||||
uint256 txidhash = tx_.GetHash();
|
||||
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
|
||||
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
|
||||
p.first.hash = txidhash;
|
||||
if (!db->Put(p.first, p.second)) {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
|
||||
} else {
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
}
|
||||
|
||||
|
||||
@@ -319,6 +340,9 @@ UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInf
|
||||
{info.vjsout[0], info.vjsout[1]};
|
||||
boost::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
|
||||
boost::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
|
||||
|
||||
uint256 esk; // payment disclosure - secret
|
||||
|
||||
JSDescription jsdesc = JSDescription::Randomized(
|
||||
*pzcashParams,
|
||||
joinSplitPubKey_,
|
||||
@@ -329,8 +353,8 @@ UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInf
|
||||
outputMap,
|
||||
info.vpub_old,
|
||||
info.vpub_new,
|
||||
!this->testmode);
|
||||
|
||||
!this->testmode,
|
||||
&esk); // parameter expects pointer to esk, so pass in address
|
||||
{
|
||||
auto verifier = libzcash::ProofVerifier::Strict();
|
||||
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
|
||||
@@ -399,6 +423,27 @@ UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInf
|
||||
arrOutputMap.push_back(outputMap[i]);
|
||||
}
|
||||
|
||||
// !!! Payment disclosure START
|
||||
unsigned char buffer[32] = {0};
|
||||
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
|
||||
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
|
||||
uint256 joinSplitPrivKey = uint256(vch);
|
||||
size_t js_index = tx_.vjoinsplit.size() - 1;
|
||||
uint256 placeholder;
|
||||
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
|
||||
uint8_t mapped_index = outputMap[i];
|
||||
// placeholder for txid will be filled in later when tx has been finalized and signed.
|
||||
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
|
||||
JSOutput output = outputs[mapped_index];
|
||||
libzcash::PaymentAddress zaddr = output.addr; // randomized output
|
||||
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
|
||||
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
|
||||
|
||||
CZCPaymentAddress address(zaddr);
|
||||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), address.ToString());
|
||||
}
|
||||
// !!! Payment disclosure END
|
||||
|
||||
UniValue obj(UniValue::VOBJ);
|
||||
obj.push_back(Pair("encryptednote1", encryptedNote1));
|
||||
obj.push_back(Pair("encryptednote2", encryptedNote2));
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
#include "paymentdisclosure.h"
|
||||
|
||||
// Default transaction fee if caller does not specify one.
|
||||
#define SHIELD_COINBASE_DEFAULT_MINERS_FEE 10000
|
||||
|
||||
@@ -55,6 +57,8 @@ public:
|
||||
|
||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||
|
||||
bool paymentDisclosureMode = false; // Set to true to save esk for encrypted notes in payment disclosure database.
|
||||
|
||||
private:
|
||||
friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing
|
||||
|
||||
@@ -80,6 +84,9 @@ private:
|
||||
void lock_utxos();
|
||||
|
||||
void unlock_utxos();
|
||||
|
||||
// payment disclosure!
|
||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -214,5 +214,7 @@ TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) {
|
||||
|
||||
wallet2.GetSpendingKey(paymentAddress2.Get(), keyOut);
|
||||
ASSERT_EQ(paymentAddress2.Get(), keyOut.address());
|
||||
|
||||
ECC_Stop();
|
||||
}
|
||||
|
||||
|
||||
299
src/wallet/rpcdisclosure.cpp
Normal file
299
src/wallet/rpcdisclosure.cpp
Normal file
@@ -0,0 +1,299 @@
|
||||
// Copyright (c) 2017 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "base58.h"
|
||||
#include "rpcserver.h"
|
||||
#include "init.h"
|
||||
#include "main.h"
|
||||
#include "script/script.h"
|
||||
#include "script/standard.h"
|
||||
#include "sync.h"
|
||||
#include "util.h"
|
||||
#include "utiltime.h"
|
||||
#include "wallet.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
#include "paymentdisclosure.h"
|
||||
#include "paymentdisclosuredb.h"
|
||||
|
||||
#include "zcash/Note.hpp"
|
||||
#include "zcash/NoteEncryption.hpp"
|
||||
|
||||
using namespace std;
|
||||
using namespace libzcash;
|
||||
|
||||
// Function declaration for function implemented in wallet/rpcwallet.cpp
|
||||
bool EnsureWalletIsAvailable(bool avoidException);
|
||||
|
||||
/**
|
||||
* RPC call to generate a payment disclosure
|
||||
*/
|
||||
UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
auto fEnablePaymentDisclosure = fExperimentalMode && GetBoolArg("-paymentdisclosure", false);
|
||||
string strPaymentDisclosureDisabledMsg = "";
|
||||
if (!fEnablePaymentDisclosure) {
|
||||
strPaymentDisclosureDisabledMsg = "\nWARNING: Payment disclosure is currently DISABLED. This call always fails.\n";
|
||||
}
|
||||
|
||||
if (fHelp || params.size() < 3 || params.size() > 4 )
|
||||
throw runtime_error(
|
||||
"z_getpaymentdisclosure \"txid\" \"js_index\" \"output_index\" (\"message\") \n"
|
||||
"\nGenerate a payment disclosure for a given joinsplit output.\n"
|
||||
"\nEXPERIMENTAL FEATURE\n"
|
||||
+ strPaymentDisclosureDisabledMsg +
|
||||
"\nArguments:\n"
|
||||
"1. \"txid\" (string, required) \n"
|
||||
"2. \"js_index\" (string, required) \n"
|
||||
"3. \"output_index\" (string, required) \n"
|
||||
"4. \"message\" (string, optional) \n"
|
||||
"\nResult:\n"
|
||||
"\"paymentblob\" (string) Hex string of payment blob\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("z_getpaymentdisclosure", "96f12882450429324d5f3b48630e3168220e49ab7b0f066e5c2935a6b88bb0f2 0 0 \"refund\"")
|
||||
+ HelpExampleRpc("z_getpaymentdisclosure", "\"96f12882450429324d5f3b48630e3168220e49ab7b0f066e5c2935a6b88bb0f2\", 0, 0, \"refund\"")
|
||||
);
|
||||
|
||||
if (!fEnablePaymentDisclosure) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: payment disclosure is disabled.");
|
||||
}
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
|
||||
// Check wallet knows about txid
|
||||
string txid = params[0].get_str();
|
||||
uint256 hash;
|
||||
hash.SetHex(txid);
|
||||
|
||||
CTransaction tx;
|
||||
uint256 hashBlock;
|
||||
|
||||
// Check txid has been seen
|
||||
if (!GetTransaction(hash, tx, hashBlock, true)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction");
|
||||
}
|
||||
|
||||
// Check tx has been confirmed
|
||||
if (hashBlock.IsNull()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction has not been confirmed yet");
|
||||
}
|
||||
|
||||
// Check is mine
|
||||
if (!pwalletMain->mapWallet.count(hash)) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not belong to the wallet");
|
||||
}
|
||||
const CWalletTx& wtx = pwalletMain->mapWallet[hash];
|
||||
|
||||
// Check if shielded tx
|
||||
if (wtx.vjoinsplit.empty()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not a shielded transaction");
|
||||
}
|
||||
|
||||
// Check js_index
|
||||
int js_index = params[1].get_int();
|
||||
if (js_index < 0 || js_index >= wtx.vjoinsplit.size()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid js_index");
|
||||
}
|
||||
|
||||
// Check output_index
|
||||
int output_index = params[2].get_int();
|
||||
if (output_index < 0 || output_index >= ZC_NUM_JS_OUTPUTS) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid output_index");
|
||||
}
|
||||
|
||||
// Get message if it exists
|
||||
string msg;
|
||||
if (params.size() == 4) {
|
||||
msg = params[3].get_str();
|
||||
}
|
||||
|
||||
// Create PaymentDisclosureKey
|
||||
PaymentDisclosureKey key = {hash, (size_t)js_index, (uint8_t)output_index };
|
||||
|
||||
// TODO: In future, perhaps init the DB in init.cpp
|
||||
shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
|
||||
PaymentDisclosureInfo info;
|
||||
if (!db->Get(key, info)) {
|
||||
throw JSONRPCError(RPC_DATABASE_ERROR, "Could not find payment disclosure info for the given joinsplit output");
|
||||
}
|
||||
|
||||
PaymentDisclosure pd( wtx.joinSplitPubKey, key, info, msg );
|
||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ss << pd;
|
||||
string strHex = HexStr(ss.begin(), ss.end());
|
||||
return strHex;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* RPC call to validate a payment disclosure data blob.
|
||||
*/
|
||||
UniValue z_validatepaymentdisclosure(const UniValue& params, bool fHelp)
|
||||
{
|
||||
if (!EnsureWalletIsAvailable(fHelp))
|
||||
return NullUniValue;
|
||||
|
||||
auto fEnablePaymentDisclosure = fExperimentalMode && GetBoolArg("-paymentdisclosure", false);
|
||||
string strPaymentDisclosureDisabledMsg = "";
|
||||
if (!fEnablePaymentDisclosure) {
|
||||
strPaymentDisclosureDisabledMsg = "\nWARNING: Payment disclosure is curretly DISABLED. This call always fails.\n";
|
||||
}
|
||||
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"z_validatepaymentdisclosure \"paymentdisclosure\"\n"
|
||||
"\nValidates a payment disclosure.\n"
|
||||
"\nEXPERIMENTAL FEATURE\n"
|
||||
+ strPaymentDisclosureDisabledMsg +
|
||||
"\nArguments:\n"
|
||||
"1. \"paymentdisclosure\" (string, required) Hex data string\n"
|
||||
"\nExamples:\n"
|
||||
+ HelpExampleCli("z_validatepaymentdisclosure", "\"hexblob\"")
|
||||
+ HelpExampleRpc("z_validatepaymentdisclosure", "\"hexblob\"")
|
||||
);
|
||||
|
||||
if (!fEnablePaymentDisclosure) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: payment disclosure is disabled.");
|
||||
}
|
||||
|
||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
||||
|
||||
EnsureWalletIsUnlocked();
|
||||
|
||||
string hexInput = params[0].get_str();
|
||||
if (!IsHex(hexInput))
|
||||
{
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected payment disclosure data in hexadecimal format.");
|
||||
}
|
||||
|
||||
// Unserialize the payment disclosure data into an object
|
||||
PaymentDisclosure pd;
|
||||
CDataStream ss(ParseHex(hexInput), SER_NETWORK, PROTOCOL_VERSION);
|
||||
try {
|
||||
ss >> pd;
|
||||
// too much data is ignored, but if not enough data, exception of type ios_base::failure is thrown,
|
||||
// CBaseDataStream::read(): end of data: iostream error
|
||||
} catch (const std::exception &e) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure data is malformed.");
|
||||
}
|
||||
|
||||
if (pd.payload.marker != PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure marker not found.");
|
||||
}
|
||||
|
||||
if (pd.payload.version != PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Payment disclosure version is unsupported.");
|
||||
}
|
||||
|
||||
uint256 hash = pd.payload.txid;
|
||||
CTransaction tx;
|
||||
uint256 hashBlock;
|
||||
// Check if we have seen the transaction
|
||||
if (!GetTransaction(hash, tx, hashBlock, true)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction");
|
||||
}
|
||||
|
||||
// Check if the transaction has been confirmed
|
||||
if (hashBlock.IsNull()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction has not been confirmed yet");
|
||||
}
|
||||
|
||||
// Check if shielded tx
|
||||
if (tx.vjoinsplit.empty()) {
|
||||
throw JSONRPCError(RPC_MISC_ERROR, "Transaction is not a shielded transaction");
|
||||
}
|
||||
|
||||
UniValue errs(UniValue::VARR);
|
||||
UniValue o(UniValue::VOBJ);
|
||||
o.push_back(Pair("txid", pd.payload.txid.ToString()));
|
||||
|
||||
// Check js_index
|
||||
if (pd.payload.js >= tx.vjoinsplit.size()) {
|
||||
errs.push_back("Payment disclosure refers to an invalid joinsplit index");
|
||||
}
|
||||
o.push_back(Pair("jsIndex", pd.payload.js));
|
||||
|
||||
if (pd.payload.n < 0 || pd.payload.n >= ZC_NUM_JS_OUTPUTS) {
|
||||
errs.push_back("Payment disclosure refers to an invalid output index");
|
||||
}
|
||||
o.push_back(Pair("outputIndex", pd.payload.n));
|
||||
o.push_back(Pair("version", pd.payload.version));
|
||||
o.push_back(Pair("onetimePrivKey", pd.payload.esk.ToString()));
|
||||
o.push_back(Pair("message", pd.payload.message));
|
||||
o.push_back(Pair("joinSplitPubKey", tx.joinSplitPubKey.ToString()));
|
||||
|
||||
// Verify the payment disclosure was signed using the same key as the transaction i.e. the joinSplitPrivKey.
|
||||
uint256 dataToBeSigned = SerializeHash(pd.payload, SER_GETHASH, 0);
|
||||
bool sigVerified = (crypto_sign_verify_detached(pd.payloadSig.data(),
|
||||
dataToBeSigned.begin(), 32,
|
||||
tx.joinSplitPubKey.begin()) == 0);
|
||||
o.push_back(Pair("signatureVerified", sigVerified));
|
||||
if (!sigVerified) {
|
||||
errs.push_back("Payment disclosure signature does not match transaction signature");
|
||||
}
|
||||
|
||||
// Check the payment address is valid
|
||||
PaymentAddress zaddr = pd.payload.zaddr;
|
||||
CZCPaymentAddress address;
|
||||
if (!address.Set(zaddr)) {
|
||||
errs.push_back("Payment disclosure refers to an invalid payment address");
|
||||
} else {
|
||||
o.push_back(Pair("paymentAddress", address.ToString()));
|
||||
|
||||
try {
|
||||
// Decrypt the note to get value and memo field
|
||||
JSDescription jsdesc = tx.vjoinsplit[pd.payload.js];
|
||||
uint256 h_sig = jsdesc.h_sig(*pzcashParams, tx.joinSplitPubKey);
|
||||
|
||||
ZCPaymentDisclosureNoteDecryption decrypter;
|
||||
|
||||
ZCNoteEncryption::Ciphertext ciphertext = jsdesc.ciphertexts[pd.payload.n];
|
||||
|
||||
uint256 pk_enc = zaddr.pk_enc;
|
||||
auto plaintext = decrypter.decryptWithEsk(ciphertext, pk_enc, pd.payload.esk, h_sig, pd.payload.n);
|
||||
|
||||
CDataStream ssPlain(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssPlain << plaintext;
|
||||
NotePlaintext npt;
|
||||
ssPlain >> npt;
|
||||
|
||||
string memoHexString = HexStr(npt.memo.data(), npt.memo.data() + npt.memo.size());
|
||||
o.push_back(Pair("memo", memoHexString));
|
||||
o.push_back(Pair("value", ValueFromAmount(npt.value)));
|
||||
|
||||
// Check the blockchain commitment matches decrypted note commitment
|
||||
uint256 cm_blockchain = jsdesc.commitments[pd.payload.n];
|
||||
Note note = npt.note(zaddr);
|
||||
uint256 cm_decrypted = note.cm();
|
||||
bool cm_match = (cm_decrypted == cm_blockchain);
|
||||
o.push_back(Pair("commitmentMatch", cm_match));
|
||||
if (!cm_match) {
|
||||
errs.push_back("Commitment derived from payment disclosure does not match blockchain commitment");
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
errs.push_back(string("Error while decrypting payment disclosure note: ") + string(e.what()) );
|
||||
}
|
||||
}
|
||||
|
||||
bool isValid = errs.empty();
|
||||
o.push_back(Pair("valid", isValid));
|
||||
if (!isValid) {
|
||||
o.push_back(Pair("errors", errs));
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
Reference in New Issue
Block a user