Delete Payment Disclosures
This code will be "experimental" forever, only supports Sprout, and is a piece of shit. Deleting with fire. Sapling viewing keys are the replacement, which already exist.
This commit is contained in:
@@ -1,107 +0,0 @@
|
|||||||
# Payment Disclosure (Experimental Feature)
|
|
||||||
|
|
||||||
**Summary**
|
|
||||||
|
|
||||||
Use RPC calls `z_getpaymentdisclosure` and `z_validatepaymentdisclosure` to reveal details of a shielded payment.
|
|
||||||
|
|
||||||
**Who should read this document**
|
|
||||||
|
|
||||||
Frequent users of shielded transactions, payment processors, exchanges, block explorer
|
|
||||||
|
|
||||||
### Experimental Feature
|
|
||||||
|
|
||||||
This is an experimental feature. Enable it by launching `zcashd` with flags:
|
|
||||||
|
|
||||||
zcashd -experimentalfeatures -paymentdisclosure -debug=paymentdisclosure -txindex=1
|
|
||||||
|
|
||||||
These flags can also be set as options in `zcash.conf`.
|
|
||||||
|
|
||||||
All nodes that generate or validate payment disclosures must run with `txindex=1` enabled.
|
|
||||||
|
|
||||||
### Background
|
|
||||||
|
|
||||||
Payment Disclosure is an implementation of the work-in-progress Payment Disclosure ZIP [1].
|
|
||||||
|
|
||||||
The ZIP describes a method of proving that a payment was sent to a shielded address. In the typical case, this means enabling a sender to present a proof that they transferred funds to a recipient's shielded address.
|
|
||||||
|
|
||||||
[1] https://github.com/zcash/zips/pull/119
|
|
||||||
|
|
||||||
### Example Use Case
|
|
||||||
|
|
||||||
Alice the customer sends 10 HUSH to Bob the merchant at the shielded address shown on their website. However, Bob is not sure if he received the funds.
|
|
||||||
|
|
||||||
Alice's node is running with payment disclosure enabled, so Alice generates a payment disclosure and provides it to Bob, who verifies the payment was made.
|
|
||||||
|
|
||||||
If Bob is a bad merchant, Alice can present the payment disclosure to a third party to validate that payment was indeed made.
|
|
||||||
|
|
||||||
### Solution
|
|
||||||
|
|
||||||
A payment disclosure can be generated for any output of a JoinSplit using the RPC call:
|
|
||||||
|
|
||||||
z_getpaymentdisclosure txid js_index output_index (message)
|
|
||||||
|
|
||||||
An optional message can be supplied. This could be used for a refund address or some other reference, as currently it is not common practice to (ahead of time) include a refund address in the memo field when making a payment.
|
|
||||||
|
|
||||||
To validate a payment disclosure, the following RPC call can be used:
|
|
||||||
|
|
||||||
z_validatepaymentdisclosure hexdata
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
Generate a payment disclosure for the first joinsplit, second output (index starts from zero):
|
|
||||||
|
|
||||||
hush-cli z_getpaymentdisclosure 79189528d611e811a1c7bb0358dd31343033d14b4c1e998d7c4799c40f8b652b 0 1 "Hello"
|
|
||||||
|
|
||||||
This returns a payment disclosure in the form of a hex string:
|
|
||||||
|
|
||||||
706462ff000a3722aafa8190cdf9710bfad6da2af6d3a74262c1fc96ad47df814b0cd5641c2b658b0fc499477c8d991e4c4bd133303431dd5803bbc7a111e811d6289518790000000000000000017e861adb829d8cb1cbcf6330b8c2e25fb0d08041a67a857815a136f0227f8a5342bce5b3c0d894e2983000eb594702d3c1580817d0374e15078528e56bb6f80c0548656c6c6f59a7085395c9e706d82afe3157c54ad4ae5bf144fcc774a8d9c921c58471402019c156ec5641e2173c4fb6467df5f28530dc4636fa71f4d0e48fc5c560fac500
|
|
||||||
|
|
||||||
To validate the payment disclosure:
|
|
||||||
|
|
||||||
hush-cli z_validatepaymentdisclosure HEXDATA
|
|
||||||
|
|
||||||
This returns data related to the payment and the payment disclosure:
|
|
||||||
|
|
||||||
{
|
|
||||||
"txid": "79189528d611e811a1c7bb0358dd31343033d14b4c1e998d7c4799c40f8b652b",
|
|
||||||
"jsIndex": 0,
|
|
||||||
"outputIndex": 1,
|
|
||||||
"version": 0,
|
|
||||||
"onetimePrivKey": "1c64d50c4b81df47ad96fcc16242a7d3f62adad6fa0b71f9cd9081faaa22370a",
|
|
||||||
"message": "Hello",
|
|
||||||
"joinSplitPubKey": "d1c465d16166b602992479acfac18e87dc18065f6cefde6a002e70bc371b9faf",
|
|
||||||
"signatureVerified": true,
|
|
||||||
"paymentAddress": "ztaZJXy8iX8nrk2ytXKDBoTWqPkhQcj6E2ifARnD3wfkFwsxXs5SoX7NGmrjkzSiSKn8VtLHTJae48vX5NakvmDhtGNY5eb",
|
|
||||||
"memo": "f600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"value": 12.49900000,
|
|
||||||
"commitmentMatch": true,
|
|
||||||
"valid": true
|
|
||||||
}
|
|
||||||
|
|
||||||
The `signatureVerified` field confirms that the payment disclosure was generated and signed with the joinSplitPrivKey, which should only be known by the node generating and sending the transaction 7918...652b in question.
|
|
||||||
|
|
||||||
### Where is the data stored?
|
|
||||||
|
|
||||||
For all nodes, payment disclosure does not touch `wallet.dat` in any way.
|
|
||||||
|
|
||||||
For nodes that only validate payment disclosures, no data is stored locally.
|
|
||||||
|
|
||||||
For nodes that generate payment disclosures, a LevelDB database is created in the node's datadir. For most users, this would be in the folder:
|
|
||||||
|
|
||||||
$HOME/.zcash/paymentdisclosure
|
|
||||||
|
|
||||||
If you decide you don't want to use payment disclosure, it is safe to shut down your node and delete the database folder.
|
|
||||||
|
|
||||||
### Security Properties
|
|
||||||
|
|
||||||
Please consult the work-in-progress ZIP for details about the protocol, security properties and caveats.
|
|
||||||
|
|
||||||
### Reminder
|
|
||||||
|
|
||||||
Feedback is most welcome!
|
|
||||||
|
|
||||||
This is an experimental feature so there are no guarantees that the protocol, database format, RPC interface etc. will remain the same in the future.
|
|
||||||
|
|
||||||
### Notes
|
|
||||||
|
|
||||||
Currently there is no user friendly way to help senders identify which joinsplit output index maps to a given payment they made. It is possible to construct this from `debug.log`. Ideas and feedback are most welcome on how to improve the user experience.
|
|
||||||
@@ -17,7 +17,6 @@ testScripts=(
|
|||||||
'dpow.py'
|
'dpow.py'
|
||||||
'dpowconfs.py'
|
'dpowconfs.py'
|
||||||
'ac_private.py'
|
'ac_private.py'
|
||||||
'paymentdisclosure.py'
|
|
||||||
'prioritisetransaction.py'
|
'prioritisetransaction.py'
|
||||||
'wallet_treestate.py'
|
'wallet_treestate.py'
|
||||||
'wallet_anchorfork.py'
|
'wallet_anchorfork.py'
|
||||||
|
|||||||
@@ -1,215 +0,0 @@
|
|||||||
#!/usr/bin/env python2
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
|
||||||
from test_framework.authproxy import JSONRPCException
|
|
||||||
from test_framework.util import assert_equal, initialize_chain_clean, \
|
|
||||||
start_node, connect_nodes_bi, wait_and_assert_operationid_status
|
|
||||||
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
class PaymentDisclosureTest (BitcoinTestFramework):
|
|
||||||
|
|
||||||
def setup_chain(self):
|
|
||||||
print("Initializing test directory "+self.options.tmpdir)
|
|
||||||
initialize_chain_clean(self.options.tmpdir, 4)
|
|
||||||
|
|
||||||
def setup_network(self, split=False):
|
|
||||||
args = ['-debug=zrpcunsafe,paymentdisclosure', '-experimentalfeatures', '-paymentdisclosure', '-txindex=1']
|
|
||||||
self.nodes = []
|
|
||||||
self.nodes.append(start_node(0, self.options.tmpdir, args))
|
|
||||||
self.nodes.append(start_node(1, self.options.tmpdir, args))
|
|
||||||
# node 2 does not enable payment disclosure
|
|
||||||
args2 = ['-debug=zrpcunsafe', '-experimentalfeatures', '-txindex=1']
|
|
||||||
self.nodes.append(start_node(2, self.options.tmpdir, args2))
|
|
||||||
connect_nodes_bi(self.nodes,0,1)
|
|
||||||
connect_nodes_bi(self.nodes,1,2)
|
|
||||||
connect_nodes_bi(self.nodes,0,2)
|
|
||||||
self.is_network_split=False
|
|
||||||
self.sync_all()
|
|
||||||
|
|
||||||
def run_test (self):
|
|
||||||
print "Mining blocks..."
|
|
||||||
|
|
||||||
self.nodes[0].generate(4)
|
|
||||||
walletinfo = self.nodes[0].getwalletinfo()
|
|
||||||
assert_equal(walletinfo['immature_balance'], 40)
|
|
||||||
assert_equal(walletinfo['balance'], 0)
|
|
||||||
self.sync_all()
|
|
||||||
self.nodes[2].generate(3)
|
|
||||||
self.sync_all()
|
|
||||||
self.nodes[1].generate(101)
|
|
||||||
self.sync_all()
|
|
||||||
assert_equal(self.nodes[0].getbalance(), 40)
|
|
||||||
assert_equal(self.nodes[1].getbalance(), 10)
|
|
||||||
assert_equal(self.nodes[2].getbalance(), 30)
|
|
||||||
|
|
||||||
mytaddr = self.nodes[0].getnewaddress()
|
|
||||||
myzaddr = self.nodes[0].z_getnewaddress()
|
|
||||||
|
|
||||||
# Check that Node 2 has payment disclosure disabled.
|
|
||||||
try:
|
|
||||||
self.nodes[2].z_getpaymentdisclosure("invalidtxid", 0, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("payment disclosure is disabled" in errorString)
|
|
||||||
|
|
||||||
# Check that Node 0 returns an error for an unknown txid
|
|
||||||
try:
|
|
||||||
self.nodes[0].z_getpaymentdisclosure("invalidtxid", 0, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("No information available about transaction" in errorString)
|
|
||||||
|
|
||||||
# Shield coinbase utxos from node 0 of value 40, standard fee of 0.00010000
|
|
||||||
recipients = [{"address":myzaddr, "amount":Decimal('40.0')-Decimal('0.0001')}]
|
|
||||||
myopid = self.nodes[0].z_sendmany(mytaddr, recipients)
|
|
||||||
txid = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
|
||||||
|
|
||||||
# Check the tx has joinsplits
|
|
||||||
assert( len(self.nodes[0].getrawtransaction("" + txid, 1)["vjoinsplit"]) > 0 )
|
|
||||||
|
|
||||||
# Sync mempools
|
|
||||||
self.sync_all()
|
|
||||||
|
|
||||||
# Confirm that you can't create a payment disclosure for an unconfirmed tx
|
|
||||||
try:
|
|
||||||
self.nodes[0].z_getpaymentdisclosure(txid, 0, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Transaction has not been confirmed yet" in errorString)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.nodes[1].z_getpaymentdisclosure(txid, 0, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Transaction has not been confirmed yet" in errorString)
|
|
||||||
|
|
||||||
# Mine tx
|
|
||||||
self.nodes[0].generate(1)
|
|
||||||
self.sync_all()
|
|
||||||
|
|
||||||
# Confirm that Node 1 cannot create a payment disclosure for a transaction which does not impact its wallet
|
|
||||||
try:
|
|
||||||
self.nodes[1].z_getpaymentdisclosure(txid, 0, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Transaction does not belong to the wallet" in errorString)
|
|
||||||
|
|
||||||
# Check that an invalid joinsplit index is rejected
|
|
||||||
try:
|
|
||||||
self.nodes[0].z_getpaymentdisclosure(txid, 1, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Invalid js_index" in errorString)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.nodes[0].z_getpaymentdisclosure(txid, -1, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Invalid js_index" in errorString)
|
|
||||||
|
|
||||||
# Check that an invalid output index is rejected
|
|
||||||
try:
|
|
||||||
self.nodes[0].z_getpaymentdisclosure(txid, 0, 2)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Invalid output_index" in errorString)
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.nodes[0].z_getpaymentdisclosure(txid, 0, -1)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Invalid output_index" in errorString)
|
|
||||||
|
|
||||||
# Ask Node 0 to create and validate a payment disclosure for output 0
|
|
||||||
message = "Here is proof of my payment!"
|
|
||||||
pd = self.nodes[0].z_getpaymentdisclosure(txid, 0, 0, message)
|
|
||||||
result = self.nodes[0].z_validatepaymentdisclosure(pd)
|
|
||||||
assert(result["valid"])
|
|
||||||
output_value_sum = Decimal(result["value"])
|
|
||||||
|
|
||||||
# Ask Node 1 to confirm the payment disclosure is valid
|
|
||||||
result = self.nodes[1].z_validatepaymentdisclosure(pd)
|
|
||||||
assert(result["valid"])
|
|
||||||
assert_equal(result["message"], message)
|
|
||||||
assert_equal(result["value"], output_value_sum)
|
|
||||||
|
|
||||||
# Confirm that payment disclosure begins with prefix zpd:
|
|
||||||
assert(pd.startswith("zpd:"))
|
|
||||||
|
|
||||||
# Confirm that payment disclosure without prefix zpd: fails validation
|
|
||||||
try:
|
|
||||||
self.nodes[1].z_validatepaymentdisclosure(pd[4:])
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("payment disclosure prefix not found" in errorString)
|
|
||||||
|
|
||||||
# Check that total value of output index 0 and index 1 should equal shielding amount of 40 less standard fee.
|
|
||||||
pd = self.nodes[0].z_getpaymentdisclosure(txid, 0, 1)
|
|
||||||
result = self.nodes[0].z_validatepaymentdisclosure(pd)
|
|
||||||
output_value_sum += Decimal(result["value"])
|
|
||||||
assert_equal(output_value_sum, Decimal('39.99990000'))
|
|
||||||
|
|
||||||
# Create a z->z transaction, sending shielded funds from node 0 to node 1
|
|
||||||
node1zaddr = self.nodes[1].z_getnewaddress()
|
|
||||||
recipients = [{"address":node1zaddr, "amount":Decimal('1')}]
|
|
||||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
|
|
||||||
txid = wait_and_assert_operationid_status(self.nodes[0], myopid)
|
|
||||||
self.sync_all()
|
|
||||||
self.nodes[0].generate(1)
|
|
||||||
self.sync_all()
|
|
||||||
|
|
||||||
# Confirm that Node 0 can create a valid payment disclosure
|
|
||||||
pd = self.nodes[0].z_getpaymentdisclosure(txid, 0, 0, "a message of your choice")
|
|
||||||
result = self.nodes[0].z_validatepaymentdisclosure(pd)
|
|
||||||
assert(result["valid"])
|
|
||||||
|
|
||||||
# Confirm that Node 1, even as recipient of shielded funds, cannot create a payment disclosure
|
|
||||||
# as the transaction was created by Node 0 and Node 1's payment disclosure database does not
|
|
||||||
# contain the necessary data to do so, where the data would only have been available on Node 0
|
|
||||||
# when executing z_shieldcoinbase.
|
|
||||||
try:
|
|
||||||
self.nodes[1].z_getpaymentdisclosure(txid, 0, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Could not find payment disclosure info for the given joinsplit output" in errorString)
|
|
||||||
|
|
||||||
# Payment disclosures cannot be created for transparent transactions.
|
|
||||||
txid = self.nodes[2].sendtoaddress(mytaddr, 1.0)
|
|
||||||
self.sync_all()
|
|
||||||
|
|
||||||
# No matter the type of transaction, if it has not been confirmed, it is ignored.
|
|
||||||
try:
|
|
||||||
self.nodes[0].z_getpaymentdisclosure(txid, 0, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Transaction has not been confirmed yet" in errorString)
|
|
||||||
|
|
||||||
self.nodes[0].generate(1)
|
|
||||||
self.sync_all()
|
|
||||||
|
|
||||||
# Confirm that a payment disclosure can only be generated for a shielded transaction.
|
|
||||||
try:
|
|
||||||
self.nodes[0].z_getpaymentdisclosure(txid, 0, 0)
|
|
||||||
assert(False)
|
|
||||||
except JSONRPCException as e:
|
|
||||||
errorString = e.error['message']
|
|
||||||
assert("Transaction is not a shielded transaction" in errorString)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
PaymentDisclosureTest().main()
|
|
||||||
@@ -181,8 +181,6 @@ BITCOIN_CORE_H = \
|
|||||||
netbase.h \
|
netbase.h \
|
||||||
notaries_staked.h \
|
notaries_staked.h \
|
||||||
noui.h \
|
noui.h \
|
||||||
paymentdisclosure.h \
|
|
||||||
paymentdisclosuredb.h \
|
|
||||||
policy/fees.h \
|
policy/fees.h \
|
||||||
pow.h \
|
pow.h \
|
||||||
prevector.h \
|
prevector.h \
|
||||||
@@ -303,8 +301,6 @@ libbitcoin_server_a_SOURCES = \
|
|||||||
notaries_staked.cpp \
|
notaries_staked.cpp \
|
||||||
noui.cpp \
|
noui.cpp \
|
||||||
notarisationdb.cpp \
|
notarisationdb.cpp \
|
||||||
paymentdisclosure.cpp \
|
|
||||||
paymentdisclosuredb.cpp \
|
|
||||||
policy/fees.cpp \
|
policy/fees.cpp \
|
||||||
pow.cpp \
|
pow.cpp \
|
||||||
rest.cpp \
|
rest.cpp \
|
||||||
@@ -355,10 +351,7 @@ libbitcoin_wallet_a_SOURCES = \
|
|||||||
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
wallet/asyncrpcoperation_shieldcoinbase.cpp \
|
||||||
wallet/crypter.cpp \
|
wallet/crypter.cpp \
|
||||||
wallet/db.cpp \
|
wallet/db.cpp \
|
||||||
paymentdisclosure.cpp \
|
|
||||||
paymentdisclosuredb.cpp \
|
|
||||||
transaction_builder.cpp \
|
transaction_builder.cpp \
|
||||||
wallet/rpcdisclosure.cpp \
|
|
||||||
wallet/rpcdump.cpp \
|
wallet/rpcdump.cpp \
|
||||||
cc/CCtokens.cpp \
|
cc/CCtokens.cpp \
|
||||||
cc/CCassetsCore.cpp \
|
cc/CCassetsCore.cpp \
|
||||||
|
|||||||
@@ -1,211 +0,0 @@
|
|||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "main.h"
|
|
||||||
#include "utilmoneystr.h"
|
|
||||||
#include "chainparams.h"
|
|
||||||
#include "utilstrencodings.h"
|
|
||||||
#include "zcash/Address.hpp"
|
|
||||||
#include "wallet/wallet.h"
|
|
||||||
#include "amount.h"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <set>
|
|
||||||
#include <vector>
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
#include <iostream>
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
#include "paymentdisclosure.h"
|
|
||||||
#include "paymentdisclosuredb.h"
|
|
||||||
|
|
||||||
#include "sodium.h"
|
|
||||||
|
|
||||||
#include <boost/uuid/uuid.hpp>
|
|
||||||
#include <boost/uuid/uuid_generators.hpp>
|
|
||||||
#include <boost/uuid/uuid_io.hpp>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
/*
|
|
||||||
To run tests:
|
|
||||||
./zcash-gtest --gtest_filter="paymentdisclosure.*"
|
|
||||||
|
|
||||||
Note: As an experimental feature, writing your own tests may require option flags to be set.
|
|
||||||
mapArgs["-experimentalfeatures"] = true;
|
|
||||||
mapArgs["-paymentdisclosure"] = true;
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define NUM_TRIES 10000
|
|
||||||
|
|
||||||
#define DUMP_DATABASE_TO_STDOUT false
|
|
||||||
|
|
||||||
static boost::uuids::random_generator uuidgen;
|
|
||||||
|
|
||||||
static uint256 random_uint256()
|
|
||||||
{
|
|
||||||
uint256 ret;
|
|
||||||
randombytes_buf(ret.begin(), 32);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subclass of PaymentDisclosureDB to add debugging methods
|
|
||||||
class PaymentDisclosureDBTest : public PaymentDisclosureDB {
|
|
||||||
public:
|
|
||||||
PaymentDisclosureDBTest(const boost::filesystem::path& dbPath) : PaymentDisclosureDB(dbPath) {}
|
|
||||||
|
|
||||||
void DebugDumpAllStdout() {
|
|
||||||
ASSERT_NE(db, nullptr);
|
|
||||||
std::lock_guard<std::mutex> guard(lock_);
|
|
||||||
|
|
||||||
// Iterate over each item in the database and print them
|
|
||||||
leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());
|
|
||||||
|
|
||||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
|
||||||
cout << it->key().ToString() << " : ";
|
|
||||||
// << it->value().ToString() << endl;
|
|
||||||
try {
|
|
||||||
std::string strValue = it->value().ToString();
|
|
||||||
PaymentDisclosureInfo info;
|
|
||||||
CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION);
|
|
||||||
ssValue >> info;
|
|
||||||
cout << info.ToString() << std::endl;
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
cout << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (false == it->status().ok()) {
|
|
||||||
cerr << "An error was found iterating over the database" << endl;
|
|
||||||
cerr << it->status().ToString() << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete it;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// This test creates random payment disclosure blobs and checks that they can be
|
|
||||||
// 1. inserted and retrieved from a database
|
|
||||||
// 2. serialized and deserialized without corruption
|
|
||||||
// Note that the zpd: prefix is not part of the payment disclosure blob itself. It is only
|
|
||||||
// used as convention to improve the user experience when sharing payment disclosure blobs.
|
|
||||||
TEST(paymentdisclosure, mainnet) {
|
|
||||||
SelectParams(CBaseChainParams::MAIN);
|
|
||||||
|
|
||||||
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
|
||||||
boost::filesystem::create_directories(pathTemp);
|
|
||||||
mapArgs["-datadir"] = pathTemp.string();
|
|
||||||
|
|
||||||
std::cout << "Test payment disclosure database created in folder: " << pathTemp.string() << std::endl;
|
|
||||||
|
|
||||||
PaymentDisclosureDBTest mydb(pathTemp);
|
|
||||||
|
|
||||||
for (int i=0; i<NUM_TRIES; i++) {
|
|
||||||
// Generate an ephemeral keypair for joinsplit sig.
|
|
||||||
uint256 joinSplitPubKey;
|
|
||||||
unsigned char buffer[crypto_sign_SECRETKEYBYTES] = {0};
|
|
||||||
crypto_sign_keypair(joinSplitPubKey.begin(), &buffer[0]);
|
|
||||||
|
|
||||||
// First 32 bytes contain private key, second 32 bytes contain public key.
|
|
||||||
ASSERT_EQ(0, memcmp(joinSplitPubKey.begin(), &buffer[0]+32, 32));
|
|
||||||
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
|
|
||||||
uint256 joinSplitPrivKey = uint256(vch);
|
|
||||||
|
|
||||||
// Create payment disclosure key and info data to store in test database
|
|
||||||
size_t js = random_uint256().GetCheapHash() % std::numeric_limits<size_t>::max();
|
|
||||||
uint8_t n = random_uint256().GetCheapHash() % std::numeric_limits<uint8_t>::max();
|
|
||||||
PaymentDisclosureKey key { random_uint256(), js, n};
|
|
||||||
PaymentDisclosureInfo info;
|
|
||||||
info.esk = random_uint256();
|
|
||||||
info.joinSplitPrivKey = joinSplitPrivKey;
|
|
||||||
info.zaddr = libzcash::SproutSpendingKey::random().address();
|
|
||||||
ASSERT_TRUE(mydb.Put(key, info));
|
|
||||||
|
|
||||||
// Retrieve info from test database into new local variable and test it matches
|
|
||||||
PaymentDisclosureInfo info2;
|
|
||||||
ASSERT_TRUE(mydb.Get(key, info2));
|
|
||||||
ASSERT_EQ(info, info2);
|
|
||||||
|
|
||||||
// Modify this local variable and confirm it no longer matches
|
|
||||||
info2.esk = random_uint256();
|
|
||||||
info2.joinSplitPrivKey = random_uint256();
|
|
||||||
info2.zaddr = libzcash::SproutSpendingKey::random().address();
|
|
||||||
ASSERT_NE(info, info2);
|
|
||||||
|
|
||||||
// Using the payment info object, let's create a dummy payload
|
|
||||||
PaymentDisclosurePayload payload;
|
|
||||||
payload.version = PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL;
|
|
||||||
payload.esk = info.esk;
|
|
||||||
payload.txid = key.hash;
|
|
||||||
payload.js = key.js;
|
|
||||||
payload.n = key.n;
|
|
||||||
payload.message = "random-" + boost::uuids::to_string(uuidgen()); // random message
|
|
||||||
payload.zaddr = info.zaddr;
|
|
||||||
|
|
||||||
// Serialize and hash the payload to generate a signature
|
|
||||||
uint256 dataToBeSigned = SerializeHash(payload, SER_GETHASH, 0);
|
|
||||||
|
|
||||||
// Compute the payload signature
|
|
||||||
unsigned char payloadSig[64];
|
|
||||||
if (!(crypto_sign_detached(&payloadSig[0], NULL,
|
|
||||||
dataToBeSigned.begin(), 32,
|
|
||||||
&buffer[0] // buffer containing both private and public key required
|
|
||||||
) == 0))
|
|
||||||
{
|
|
||||||
throw std::runtime_error("crypto_sign_detached failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check
|
|
||||||
if (!(crypto_sign_verify_detached(&payloadSig[0],
|
|
||||||
dataToBeSigned.begin(), 32,
|
|
||||||
joinSplitPubKey.begin()
|
|
||||||
) == 0))
|
|
||||||
{
|
|
||||||
throw std::runtime_error("crypto_sign_verify_detached failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert signature buffer to boost array
|
|
||||||
std::array<unsigned char, 64> arrayPayloadSig;
|
|
||||||
memcpy(arrayPayloadSig.data(), &payloadSig[0], 64);
|
|
||||||
|
|
||||||
// Payment disclosure blob to pass around
|
|
||||||
PaymentDisclosure pd = {payload, arrayPayloadSig};
|
|
||||||
|
|
||||||
// Test payment disclosure constructors
|
|
||||||
PaymentDisclosure pd2(payload, arrayPayloadSig);
|
|
||||||
ASSERT_EQ(pd, pd2);
|
|
||||||
PaymentDisclosure pd3(joinSplitPubKey, key, info, payload.message);
|
|
||||||
ASSERT_EQ(pd, pd3);
|
|
||||||
|
|
||||||
// Verify serialization and deserialization works
|
|
||||||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
ss << pd;
|
|
||||||
std::string ssHexString = HexStr(ss.begin(), ss.end());
|
|
||||||
|
|
||||||
PaymentDisclosure pdTmp;
|
|
||||||
CDataStream ssTmp(ParseHex(ssHexString), SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
ssTmp >> pdTmp;
|
|
||||||
ASSERT_EQ(pd, pdTmp);
|
|
||||||
|
|
||||||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
|
|
||||||
ss2 << pdTmp;
|
|
||||||
std::string ss2HexString = HexStr(ss2.begin(), ss2.end());
|
|
||||||
ASSERT_EQ(ssHexString, ss2HexString);
|
|
||||||
|
|
||||||
// Verify marker
|
|
||||||
ASSERT_EQ(pd.payload.marker, PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES);
|
|
||||||
ASSERT_EQ(pdTmp.payload.marker, PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES);
|
|
||||||
ASSERT_EQ(0, ssHexString.find("706462ff")); // Little endian encoding of PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES value
|
|
||||||
|
|
||||||
// Sanity check
|
|
||||||
PaymentDisclosure pdDummy;
|
|
||||||
ASSERT_NE(pd, pdDummy);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DUMP_DATABASE_TO_STDOUT == true
|
|
||||||
mydb.DebugDumpAllStdout();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
// 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 "paymentdisclosure.h"
|
|
||||||
|
|
||||||
#include "key_io.h"
|
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
std::string PaymentDisclosureInfo::ToString() const {
|
|
||||||
return strprintf("PaymentDisclosureInfo(version=%d, esk=%s, joinSplitPrivKey=<omitted>, address=%s)",
|
|
||||||
version, esk.ToString(), EncodePaymentAddress(zaddr));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string PaymentDisclosure::ToString() const {
|
|
||||||
std::string s = HexStr(payloadSig.begin(), payloadSig.end());
|
|
||||||
return strprintf("PaymentDisclosure(payload=%s, payloadSig=%s)", payload.ToString(), s);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string PaymentDisclosurePayload::ToString() const {
|
|
||||||
return strprintf("PaymentDisclosurePayload(version=%d, esk=%s, txid=%s, js=%d, n=%d, address=%s, message=%s)",
|
|
||||||
version, esk.ToString(), txid.ToString(), js, n, EncodePaymentAddress(zaddr), message);
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentDisclosure::PaymentDisclosure(const uint256 &joinSplitPubKey, const PaymentDisclosureKey &key, const PaymentDisclosureInfo &info, const std::string &message)
|
|
||||||
{
|
|
||||||
// Populate payload member variable
|
|
||||||
payload.version = info.version; // experimental = 0, production = 1 etc.
|
|
||||||
payload.esk = info.esk;
|
|
||||||
payload.txid = key.hash;
|
|
||||||
payload.js = key.js;
|
|
||||||
payload.n = key.n;
|
|
||||||
payload.zaddr = info.zaddr;
|
|
||||||
payload.message = message;
|
|
||||||
|
|
||||||
// Serialize and hash the payload to generate a signature
|
|
||||||
uint256 dataToBeSigned = SerializeHash(payload, SER_GETHASH, 0);
|
|
||||||
|
|
||||||
LogPrint("paymentdisclosure", "Payment Disclosure: signing raw payload = %s\n", dataToBeSigned.ToString());
|
|
||||||
|
|
||||||
// Prepare buffer to store ed25519 key pair in libsodium-compatible format
|
|
||||||
unsigned char bufferKeyPair[64];
|
|
||||||
memcpy(&bufferKeyPair[0], info.joinSplitPrivKey.begin(), 32);
|
|
||||||
memcpy(&bufferKeyPair[32], joinSplitPubKey.begin(), 32);
|
|
||||||
|
|
||||||
// Compute payload signature member variable
|
|
||||||
if (!(crypto_sign_detached(payloadSig.data(), NULL,
|
|
||||||
dataToBeSigned.begin(), 32,
|
|
||||||
&bufferKeyPair[0]
|
|
||||||
) == 0))
|
|
||||||
{
|
|
||||||
throw std::runtime_error("crypto_sign_detached failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanity check
|
|
||||||
if (!(crypto_sign_verify_detached(payloadSig.data(),
|
|
||||||
dataToBeSigned.begin(), 32,
|
|
||||||
joinSplitPubKey.begin()) == 0))
|
|
||||||
{
|
|
||||||
throw std::runtime_error("crypto_sign_verify_detached failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string sigString = HexStr(payloadSig.data(), payloadSig.data() + payloadSig.size());
|
|
||||||
LogPrint("paymentdisclosure", "Payment Disclosure: signature = %s\n", sigString);
|
|
||||||
}
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
// Copyright (c) 2017 The Zcash developers
|
|
||||||
// Copyright (c) 2019-2020 The Hush developers
|
|
||||||
// Distributed under the MIT software license, see the accompanying
|
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
||||||
|
|
||||||
#ifndef ZCASH_PAYMENTDISCLOSURE_H
|
|
||||||
#define ZCASH_PAYMENTDISCLOSURE_H
|
|
||||||
|
|
||||||
#include "uint256.h"
|
|
||||||
#include "clientversion.h"
|
|
||||||
#include "serialize.h"
|
|
||||||
#include "streams.h"
|
|
||||||
#include "version.h"
|
|
||||||
|
|
||||||
// For JSOutPoint
|
|
||||||
#include "wallet/wallet.h"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
|
|
||||||
// Ensure that the two different protocol messages, payment disclosure blobs and transactions,
|
|
||||||
// which are signed with the same key, joinSplitPrivKey, have disjoint encodings such that an
|
|
||||||
// encoding from one context will be rejected in the other. We know that the set of valid
|
|
||||||
// transaction versions is currently ({1..INT32_MAX}) so we will use a negative value for
|
|
||||||
// payment disclosure of -10328976 which in hex is 0xFF626470. Serialization is in little endian
|
|
||||||
// format, so a payment disclosure hex string begins 706462FF, which in ISO-8859-1 is "pdbÿ".
|
|
||||||
#define PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES -10328976
|
|
||||||
|
|
||||||
#define PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL 0
|
|
||||||
|
|
||||||
#define PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX "zpd:"
|
|
||||||
|
|
||||||
typedef JSOutPoint PaymentDisclosureKey;
|
|
||||||
|
|
||||||
struct PaymentDisclosureInfo {
|
|
||||||
uint8_t version; // 0 = experimental, 1 = first production version, etc.
|
|
||||||
uint256 esk; // zcash/NoteEncryption.cpp
|
|
||||||
uint256 joinSplitPrivKey; // primitives/transaction.h
|
|
||||||
// ed25519 - not tied to implementation e.g. libsodium, see ed25519 rfc
|
|
||||||
|
|
||||||
libzcash::SproutPaymentAddress zaddr;
|
|
||||||
|
|
||||||
PaymentDisclosureInfo() : version(PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentDisclosureInfo(uint8_t v, uint256 esk, uint256 key, libzcash::SproutPaymentAddress zaddr) : version(v), esk(esk), joinSplitPrivKey(key), zaddr(zaddr) { }
|
|
||||||
|
|
||||||
ADD_SERIALIZE_METHODS;
|
|
||||||
|
|
||||||
template <typename Stream, typename Operation>
|
|
||||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
|
||||||
READWRITE(version);
|
|
||||||
READWRITE(esk);
|
|
||||||
READWRITE(joinSplitPrivKey);
|
|
||||||
READWRITE(zaddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ToString() const;
|
|
||||||
|
|
||||||
friend bool operator==(const PaymentDisclosureInfo& a, const PaymentDisclosureInfo& b) {
|
|
||||||
return (a.version == b.version && a.esk == b.esk && a.joinSplitPrivKey == b.joinSplitPrivKey && a.zaddr == b.zaddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator!=(const PaymentDisclosureInfo& a, const PaymentDisclosureInfo& b) {
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
struct PaymentDisclosurePayload {
|
|
||||||
int32_t marker = PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES; // to be disjoint from transaction encoding
|
|
||||||
uint8_t version; // 0 = experimental, 1 = first production version, etc.
|
|
||||||
uint256 esk; // zcash/NoteEncryption.cpp
|
|
||||||
uint256 txid; // primitives/transaction.h
|
|
||||||
uint64_t js; // Index into CTransaction.vjoinsplit
|
|
||||||
uint8_t n; // Index into JSDescription fields of length ZC_NUM_JS_OUTPUTS
|
|
||||||
libzcash::SproutPaymentAddress zaddr; // zcash/Address.hpp
|
|
||||||
std::string message; // parameter to RPC call
|
|
||||||
|
|
||||||
ADD_SERIALIZE_METHODS;
|
|
||||||
|
|
||||||
template <typename Stream, typename Operation>
|
|
||||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
|
||||||
READWRITE(marker);
|
|
||||||
READWRITE(version);
|
|
||||||
READWRITE(esk);
|
|
||||||
READWRITE(txid);
|
|
||||||
READWRITE(js);
|
|
||||||
READWRITE(n);
|
|
||||||
READWRITE(zaddr);
|
|
||||||
READWRITE(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ToString() const;
|
|
||||||
|
|
||||||
friend bool operator==(const PaymentDisclosurePayload& a, const PaymentDisclosurePayload& b) {
|
|
||||||
return (
|
|
||||||
a.version == b.version &&
|
|
||||||
a.esk == b.esk &&
|
|
||||||
a.txid == b.txid &&
|
|
||||||
a.js == b.js &&
|
|
||||||
a.n == b.n &&
|
|
||||||
a.zaddr == b.zaddr &&
|
|
||||||
a.message == b.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator!=(const PaymentDisclosurePayload& a, const PaymentDisclosurePayload& b) {
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PaymentDisclosure {
|
|
||||||
PaymentDisclosurePayload payload;
|
|
||||||
std::array<unsigned char, 64> payloadSig;
|
|
||||||
// We use boost array because serialize doesn't like char buffer, otherwise we could do: unsigned char payloadSig[64];
|
|
||||||
|
|
||||||
PaymentDisclosure() {};
|
|
||||||
PaymentDisclosure(const PaymentDisclosurePayload payload, const std::array<unsigned char, 64> sig) : payload(payload), payloadSig(sig) {};
|
|
||||||
PaymentDisclosure(const uint256& joinSplitPubKey, const PaymentDisclosureKey& key, const PaymentDisclosureInfo& info, const std::string& message);
|
|
||||||
|
|
||||||
ADD_SERIALIZE_METHODS;
|
|
||||||
|
|
||||||
template <typename Stream, typename Operation>
|
|
||||||
inline void SerializationOp(Stream& s, Operation ser_action) {
|
|
||||||
READWRITE(payload);
|
|
||||||
READWRITE(payloadSig);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ToString() const;
|
|
||||||
|
|
||||||
friend bool operator==(const PaymentDisclosure& a, const PaymentDisclosure& b) {
|
|
||||||
return (a.payload == b.payload && a.payloadSig == b.payloadSig);
|
|
||||||
}
|
|
||||||
|
|
||||||
friend bool operator!=(const PaymentDisclosure& a, const PaymentDisclosure& b) {
|
|
||||||
return !(a == b);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
typedef std::pair<PaymentDisclosureKey, PaymentDisclosureInfo> PaymentDisclosureKeyInfo;
|
|
||||||
|
|
||||||
|
|
||||||
#endif // ZCASH_PAYMENTDISCLOSURE_H
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
// 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 "paymentdisclosuredb.h"
|
|
||||||
|
|
||||||
#include "util.h"
|
|
||||||
#include "dbwrapper.h"
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
static boost::filesystem::path emptyPath;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static method to return the shared/default payment disclosure database.
|
|
||||||
*/
|
|
||||||
shared_ptr<PaymentDisclosureDB> PaymentDisclosureDB::sharedInstance() {
|
|
||||||
// Thread-safe in C++11 and gcc 4.3
|
|
||||||
static shared_ptr<PaymentDisclosureDB> ptr = std::make_shared<PaymentDisclosureDB>();
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// C++11 delegated constructor
|
|
||||||
PaymentDisclosureDB::PaymentDisclosureDB() : PaymentDisclosureDB(emptyPath) {
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentDisclosureDB::PaymentDisclosureDB(const boost::filesystem::path& dbPath) {
|
|
||||||
boost::filesystem::path path(dbPath);
|
|
||||||
if (path.empty()) {
|
|
||||||
path = GetDataDir() / "paymentdisclosure";
|
|
||||||
LogPrintf("PaymentDisclosure: using default path for database: %s\n", path.string());
|
|
||||||
} else {
|
|
||||||
LogPrintf("PaymentDisclosure: using custom path for database: %s\n", path.string());
|
|
||||||
}
|
|
||||||
|
|
||||||
TryCreateDirectory(path);
|
|
||||||
options.create_if_missing = true;
|
|
||||||
leveldb::Status status = leveldb::DB::Open(options, path.string(), &db);
|
|
||||||
dbwrapper_private::HandleError(status); // throws exception
|
|
||||||
LogPrintf("PaymentDisclosure: Opened LevelDB successfully\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
PaymentDisclosureDB::~PaymentDisclosureDB() {
|
|
||||||
if (db != nullptr) {
|
|
||||||
delete db;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PaymentDisclosureDB::Put(const PaymentDisclosureKey& key, const PaymentDisclosureInfo& info)
|
|
||||||
{
|
|
||||||
if (db == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(lock_);
|
|
||||||
|
|
||||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
|
||||||
ssValue.reserve(GetSerializeSize(ssValue, info));
|
|
||||||
ssValue << info;
|
|
||||||
leveldb::Slice slice(&ssValue[0], ssValue.size());
|
|
||||||
|
|
||||||
leveldb::Status status = db->Put(writeOptions, key.ToString(), slice);
|
|
||||||
dbwrapper_private::HandleError(status);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PaymentDisclosureDB::Get(const PaymentDisclosureKey& key, PaymentDisclosureInfo& info)
|
|
||||||
{
|
|
||||||
if (db == nullptr) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> guard(lock_);
|
|
||||||
|
|
||||||
std::string strValue;
|
|
||||||
leveldb::Status status = db->Get(readOptions, key.ToString(), &strValue);
|
|
||||||
if (!status.ok()) {
|
|
||||||
if (status.IsNotFound())
|
|
||||||
return false;
|
|
||||||
LogPrintf("PaymentDisclosure: LevelDB read failure: %s\n", status.ToString());
|
|
||||||
dbwrapper_private::HandleError(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION);
|
|
||||||
ssValue >> info;
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// 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.
|
|
||||||
|
|
||||||
#ifndef ZCASH_PAYMENTDISCLOSUREDB_H
|
|
||||||
#define ZCASH_PAYMENTDISCLOSUREDB_H
|
|
||||||
|
|
||||||
#include "paymentdisclosure.h"
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <mutex>
|
|
||||||
#include <future>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <boost/optional.hpp>
|
|
||||||
|
|
||||||
#include <leveldb/db.h>
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentDisclosureDB
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
leveldb::DB* db = nullptr;
|
|
||||||
leveldb::Options options;
|
|
||||||
leveldb::ReadOptions readOptions;
|
|
||||||
leveldb::WriteOptions writeOptions;
|
|
||||||
mutable std::mutex lock_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
static std::shared_ptr<PaymentDisclosureDB> sharedInstance();
|
|
||||||
|
|
||||||
PaymentDisclosureDB();
|
|
||||||
PaymentDisclosureDB(const boost::filesystem::path& dbPath);
|
|
||||||
~PaymentDisclosureDB();
|
|
||||||
|
|
||||||
bool Put(const PaymentDisclosureKey& key, const PaymentDisclosureInfo& info);
|
|
||||||
bool Get(const PaymentDisclosureKey& key, PaymentDisclosureInfo& info);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif // ZCASH_PAYMENTDISCLOSUREDB_H
|
|
||||||
@@ -133,9 +133,6 @@ saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), context
|
|||||||
// Lock UTXOs
|
// Lock UTXOs
|
||||||
lock_utxos();
|
lock_utxos();
|
||||||
lock_notes();
|
lock_notes();
|
||||||
|
|
||||||
// Enable payment disclosure if requested
|
|
||||||
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncRPCOperation_mergetoaddress::~AsyncRPCOperation_mergetoaddress()
|
AsyncRPCOperation_mergetoaddress::~AsyncRPCOperation_mergetoaddress()
|
||||||
@@ -210,25 +207,10 @@ void AsyncRPCOperation_mergetoaddress::main()
|
|||||||
|
|
||||||
unlock_utxos(); // clean up
|
unlock_utxos(); // clean up
|
||||||
unlock_notes(); // clean up
|
unlock_notes(); // 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notes:
|
// Notes:
|
||||||
// 1. #1359 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
|
// 1. #1359 Currently there is no limit set on the number of inputs+outputs, so size of tx could be invalid.
|
||||||
// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them.
|
// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them.
|
||||||
bool AsyncRPCOperation_mergetoaddress::main_impl()
|
bool AsyncRPCOperation_mergetoaddress::main_impl()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
// Copyright (c) 2017 The Zcash developers
|
// Copyright (c) 2017 The Zcash developers
|
||||||
|
// Copyright (c) 2019-2020 The Hush developers
|
||||||
|
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -22,7 +24,6 @@
|
|||||||
|
|
||||||
#include "amount.h"
|
#include "amount.h"
|
||||||
#include "asyncrpcoperation.h"
|
#include "asyncrpcoperation.h"
|
||||||
#include "paymentdisclosure.h"
|
|
||||||
#include "primitives/transaction.h"
|
#include "primitives/transaction.h"
|
||||||
#include "transaction_builder.h"
|
#include "transaction_builder.h"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
@@ -93,8 +94,6 @@ public:
|
|||||||
|
|
||||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||||
|
|
||||||
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class TEST_FRIEND_AsyncRPCOperation_mergetoaddress; // class for unit testing
|
friend class TEST_FRIEND_AsyncRPCOperation_mergetoaddress; // class for unit testing
|
||||||
|
|
||||||
@@ -148,8 +147,6 @@ private:
|
|||||||
|
|
||||||
void unlock_notes();
|
void unlock_notes();
|
||||||
|
|
||||||
// payment disclosure!
|
|
||||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -211,25 +211,10 @@ void AsyncRPCOperation_sendmany::main() {
|
|||||||
s += strprintf(", error=%s)\n", getErrorMessage());
|
s += strprintf(", error=%s)\n", getErrorMessage());
|
||||||
}
|
}
|
||||||
LogPrintf("%s",s);
|
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:
|
// Notes:
|
||||||
// 1. #1159 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
|
// 1. #1159 Currently there is no limit set on the number of shielded spends, so size of tx could be invalid.
|
||||||
// 2. #1360 Note selection is not optimal
|
// 2. #1360 Note selection is not optimal
|
||||||
// 3. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them
|
// 3. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them
|
||||||
bool AsyncRPCOperation_sendmany::main_impl() {
|
bool AsyncRPCOperation_sendmany::main_impl() {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// Copyright (c) 2016 The Zcash developers
|
// Copyright (c) 2016 The Zcash developers
|
||||||
|
// Copyright (c) 2019-2020 The Hush developers
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
@@ -27,7 +28,6 @@
|
|||||||
#include "zcash/JoinSplit.hpp"
|
#include "zcash/JoinSplit.hpp"
|
||||||
#include "zcash/Address.hpp"
|
#include "zcash/Address.hpp"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
#include "paymentdisclosure.h"
|
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
@@ -90,8 +90,6 @@ public:
|
|||||||
|
|
||||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||||
|
|
||||||
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
|
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
|
||||||
|
|
||||||
@@ -143,9 +141,6 @@ private:
|
|||||||
uint256 anchor);
|
uint256 anchor);
|
||||||
|
|
||||||
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
|
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
|
||||||
|
|
||||||
// payment disclosure!
|
|
||||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,6 @@
|
|||||||
|
|
||||||
#include "asyncrpcoperation_shieldcoinbase.h"
|
#include "asyncrpcoperation_shieldcoinbase.h"
|
||||||
|
|
||||||
#include "paymentdisclosure.h"
|
|
||||||
#include "paymentdisclosuredb.h"
|
|
||||||
|
|
||||||
using namespace libzcash;
|
using namespace libzcash;
|
||||||
extern uint64_t ASSETCHAINS_TIMELOCKGTE;
|
extern uint64_t ASSETCHAINS_TIMELOCKGTE;
|
||||||
|
|
||||||
@@ -108,8 +105,6 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
|
|||||||
// Lock UTXOs
|
// Lock UTXOs
|
||||||
lock_utxos();
|
lock_utxos();
|
||||||
|
|
||||||
// Enable payment disclosure if requested
|
|
||||||
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncRPCOperation_shieldcoinbase::~AsyncRPCOperation_shieldcoinbase() {
|
AsyncRPCOperation_shieldcoinbase::~AsyncRPCOperation_shieldcoinbase() {
|
||||||
@@ -181,20 +176,6 @@ void AsyncRPCOperation_shieldcoinbase::main() {
|
|||||||
|
|
||||||
unlock_utxos(); // clean up
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
|
||||||
|
|||||||
@@ -34,8 +34,6 @@
|
|||||||
|
|
||||||
#include <univalue.h>
|
#include <univalue.h>
|
||||||
|
|
||||||
#include "paymentdisclosure.h"
|
|
||||||
|
|
||||||
// Default transaction fee if caller does not specify one.
|
// Default transaction fee if caller does not specify one.
|
||||||
#define SHIELD_COINBASE_DEFAULT_MINERS_FEE 10000
|
#define SHIELD_COINBASE_DEFAULT_MINERS_FEE 10000
|
||||||
|
|
||||||
@@ -81,8 +79,6 @@ public:
|
|||||||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
||||||
bool cheatSpend = false; // set when this is shielding a cheating coinbase
|
bool cheatSpend = false; // set when this is shielding a cheating coinbase
|
||||||
|
|
||||||
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class ShieldToAddress;
|
friend class ShieldToAddress;
|
||||||
friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing
|
friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing
|
||||||
@@ -110,9 +106,6 @@ private:
|
|||||||
void lock_utxos();
|
void lock_utxos();
|
||||||
|
|
||||||
void unlock_utxos();
|
void unlock_utxos();
|
||||||
|
|
||||||
// payment disclosure!
|
|
||||||
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class ShieldToAddress : public boost::static_visitor<bool>
|
class ShieldToAddress : public boost::static_visitor<bool>
|
||||||
|
|||||||
@@ -1,324 +0,0 @@
|
|||||||
// Copyright (c) 2017 The Zcash developers
|
|
||||||
// Copyright (c) 2019-2020 The Hush developers
|
|
||||||
// Distributed under the MIT software license, see the accompanying
|
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
* Copyright © 2014-2019 The SuperNET Developers. *
|
|
||||||
* *
|
|
||||||
* See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at *
|
|
||||||
* the top-level directory of this distribution for the individual copyright *
|
|
||||||
* holder information and the developer policies on copyright and licensing. *
|
|
||||||
* *
|
|
||||||
* Unless otherwise agreed in a custom licensing agreement, no part of the *
|
|
||||||
* SuperNET software, including this file may be copied, modified, propagated *
|
|
||||||
* or distributed except according to the terms contained in the LICENSE file *
|
|
||||||
* *
|
|
||||||
* Removal or modification of this copyright notice is prohibited. *
|
|
||||||
* *
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
#include "rpc/server.h"
|
|
||||||
#include "init.h"
|
|
||||||
#include "key_io.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, const CPubKey& mypk)
|
|
||||||
{
|
|
||||||
if (!EnsureWalletIsAvailable(fHelp))
|
|
||||||
return NullUniValue;
|
|
||||||
|
|
||||||
string enableArg = "paymentdisclosure";
|
|
||||||
auto fEnablePaymentDisclosure = fExperimentalMode && GetBoolArg("-" + enableArg, true);
|
|
||||||
string strPaymentDisclosureDisabledMsg = "";
|
|
||||||
if (!fEnablePaymentDisclosure) {
|
|
||||||
strPaymentDisclosureDisabledMsg = experimentalDisabledHelpMsg("z_getpaymentdisclosure", enableArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
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"
|
|
||||||
"\"paymentdisclosure\" (string) Hex data string, with \"zpd:\" prefix.\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 PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX + strHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RPC call to validate a payment disclosure data blob.
|
|
||||||
*/
|
|
||||||
UniValue z_validatepaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk)
|
|
||||||
{
|
|
||||||
if (!EnsureWalletIsAvailable(fHelp))
|
|
||||||
return NullUniValue;
|
|
||||||
|
|
||||||
string enableArg = "paymentdisclosure";
|
|
||||||
auto fEnablePaymentDisclosure = fExperimentalMode && GetBoolArg("-" + enableArg, true);
|
|
||||||
string strPaymentDisclosureDisabledMsg = "";
|
|
||||||
if (!fEnablePaymentDisclosure) {
|
|
||||||
strPaymentDisclosureDisabledMsg = experimentalDisabledHelpMsg("z_validatepaymentdisclosure", enableArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
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, with \"zpd:\" prefix.\n"
|
|
||||||
"\nExamples:\n"
|
|
||||||
+ HelpExampleCli("z_validatepaymentdisclosure", "\"zpd:706462ff004c561a0447ba2ec51184e6c204...\"")
|
|
||||||
+ HelpExampleRpc("z_validatepaymentdisclosure", "\"zpd:706462ff004c561a0447ba2ec51184e6c204...\"")
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!fEnablePaymentDisclosure) {
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: payment disclosure is disabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
throw JSONRPCError(RPC_WALLET_ERROR, "Error: payment disclosures not implemented for Sapling yet");
|
|
||||||
|
|
||||||
LOCK2(cs_main, pwalletMain->cs_wallet);
|
|
||||||
|
|
||||||
EnsureWalletIsUnlocked();
|
|
||||||
|
|
||||||
// Verify the payment disclosure input begins with "zpd:" prefix.
|
|
||||||
string strInput = params[0].get_str();
|
|
||||||
size_t pos = strInput.find(PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX);
|
|
||||||
if (pos != 0) {
|
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, payment disclosure prefix not found.");
|
|
||||||
}
|
|
||||||
string hexInput = strInput.substr(strlen(PAYMENT_DISCLOSURE_BLOB_STRING_PREFIX));
|
|
||||||
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;
|
|
||||||
{
|
|
||||||
o.push_back(Pair("paymentAddress", EncodePaymentAddress(zaddr)));
|
|
||||||
|
|
||||||
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;
|
|
||||||
SproutNotePlaintext 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];
|
|
||||||
SproutNote 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;
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Copyright (c) 2019-2020 The Hush developers
|
||||||
|
|
||||||
#include "NoteEncryption.hpp"
|
#include "NoteEncryption.hpp"
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include "sodium.h"
|
#include "sodium.h"
|
||||||
@@ -374,52 +376,6 @@ typename NoteDecryption<MLEN>::Plaintext NoteDecryption<MLEN>::decrypt
|
|||||||
return plaintext;
|
return plaintext;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Payment disclosure - decrypt with esk
|
|
||||||
//
|
|
||||||
template<size_t MLEN>
|
|
||||||
typename PaymentDisclosureNoteDecryption<MLEN>::Plaintext PaymentDisclosureNoteDecryption<MLEN>::decryptWithEsk
|
|
||||||
(const PaymentDisclosureNoteDecryption<MLEN>::Ciphertext &ciphertext,
|
|
||||||
const uint256 &pk_enc,
|
|
||||||
const uint256 &esk,
|
|
||||||
const uint256 &hSig,
|
|
||||||
unsigned char nonce
|
|
||||||
) const
|
|
||||||
{
|
|
||||||
uint256 dhsecret;
|
|
||||||
|
|
||||||
if (crypto_scalarmult(dhsecret.begin(), esk.begin(), pk_enc.begin()) != 0) {
|
|
||||||
throw std::logic_error("Could not create DH secret");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regenerate keypair
|
|
||||||
uint256 epk = NoteEncryption<MLEN>::generate_pubkey(esk);
|
|
||||||
|
|
||||||
unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE];
|
|
||||||
KDF(K, dhsecret, epk, pk_enc, hSig, nonce);
|
|
||||||
|
|
||||||
// The nonce is zero because we never reuse keys
|
|
||||||
unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {};
|
|
||||||
|
|
||||||
PaymentDisclosureNoteDecryption<MLEN>::Plaintext plaintext;
|
|
||||||
|
|
||||||
// Message length is always NOTEENCRYPTION_AUTH_BYTES less than
|
|
||||||
// the ciphertext length.
|
|
||||||
if (crypto_aead_chacha20poly1305_ietf_decrypt(plaintext.begin(), NULL,
|
|
||||||
NULL,
|
|
||||||
ciphertext.begin(), PaymentDisclosureNoteDecryption<MLEN>::CLEN,
|
|
||||||
NULL,
|
|
||||||
0,
|
|
||||||
cipher_nonce, K) != 0) {
|
|
||||||
throw note_decryption_failed();
|
|
||||||
}
|
|
||||||
|
|
||||||
return plaintext;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
template<size_t MLEN>
|
template<size_t MLEN>
|
||||||
uint256 NoteEncryption<MLEN>::generate_privkey(const uint252 &a_sk)
|
uint256 NoteEncryption<MLEN>::generate_privkey(const uint252 &a_sk)
|
||||||
{
|
{
|
||||||
@@ -461,6 +417,4 @@ uint252 random_uint252()
|
|||||||
template class NoteEncryption<ZC_NOTEPLAINTEXT_SIZE>;
|
template class NoteEncryption<ZC_NOTEPLAINTEXT_SIZE>;
|
||||||
template class NoteDecryption<ZC_NOTEPLAINTEXT_SIZE>;
|
template class NoteDecryption<ZC_NOTEPLAINTEXT_SIZE>;
|
||||||
|
|
||||||
template class PaymentDisclosureNoteDecryption<ZC_NOTEPLAINTEXT_SIZE>;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
/*
|
// Copyright (c) 2019-2020 The Hush developers
|
||||||
See the Zcash protocol specification for more information.
|
|
||||||
https://github.com/zcash/zips/blob/master/protocol/protocol.pdf
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef ZC_NOTE_ENCRYPTION_H_
|
#ifndef ZC_NOTE_ENCRYPTION_H_
|
||||||
#define ZC_NOTE_ENCRYPTION_H_
|
#define ZC_NOTE_ENCRYPTION_H_
|
||||||
@@ -169,33 +166,9 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Subclass PaymentDisclosureNoteDecryption provides a method to decrypt a note with esk.
|
|
||||||
template<size_t MLEN>
|
|
||||||
class PaymentDisclosureNoteDecryption : public NoteDecryption<MLEN> {
|
|
||||||
protected:
|
|
||||||
public:
|
|
||||||
enum { CLEN=MLEN+NOTEENCRYPTION_AUTH_BYTES };
|
|
||||||
typedef std::array<unsigned char, CLEN> Ciphertext;
|
|
||||||
typedef std::array<unsigned char, MLEN> Plaintext;
|
|
||||||
|
|
||||||
PaymentDisclosureNoteDecryption() : NoteDecryption<MLEN>() {}
|
|
||||||
PaymentDisclosureNoteDecryption(uint256 sk_enc) : NoteDecryption<MLEN>(sk_enc) {}
|
|
||||||
|
|
||||||
Plaintext decryptWithEsk(
|
|
||||||
const Ciphertext &ciphertext,
|
|
||||||
const uint256 &pk_enc,
|
|
||||||
const uint256 &esk,
|
|
||||||
const uint256 &hSig,
|
|
||||||
unsigned char nonce
|
|
||||||
) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef libzcash::NoteEncryption<ZC_NOTEPLAINTEXT_SIZE> ZCNoteEncryption;
|
typedef libzcash::NoteEncryption<ZC_NOTEPLAINTEXT_SIZE> ZCNoteEncryption;
|
||||||
typedef libzcash::NoteDecryption<ZC_NOTEPLAINTEXT_SIZE> ZCNoteDecryption;
|
typedef libzcash::NoteDecryption<ZC_NOTEPLAINTEXT_SIZE> ZCNoteDecryption;
|
||||||
|
|
||||||
typedef libzcash::PaymentDisclosureNoteDecryption<ZC_NOTEPLAINTEXT_SIZE> ZCPaymentDisclosureNoteDecryption;
|
|
||||||
|
|
||||||
#endif /* ZC_NOTE_ENCRYPTION_H_ */
|
#endif /* ZC_NOTE_ENCRYPTION_H_ */
|
||||||
|
|||||||
Reference in New Issue
Block a user