136 changed files with 3750 additions and 9479 deletions
@ -0,0 +1,5 @@ |
|||
#!/bin/bash |
|||
# Copyright (c) 2019-2020 The Hush developers |
|||
|
|||
set -eu -o pipefail |
|||
./zcutil/build.sh $@ |
@ -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. |
@ -1,192 +0,0 @@ |
|||
#!/usr/bin/env python2 |
|||
# Copyright (c) 2018 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.mininode import ( |
|||
NodeConn, |
|||
NodeConnCB, |
|||
NetworkThread, |
|||
msg_ping, |
|||
SPROUT_PROTO_VERSION, |
|||
OVERWINTER_PROTO_VERSION, |
|||
SAPLING_PROTO_VERSION, |
|||
) |
|||
from test_framework.test_framework import BitcoinTestFramework |
|||
from test_framework.util import initialize_chain_clean, start_nodes, \ |
|||
p2p_port, assert_equal |
|||
|
|||
import time |
|||
|
|||
# |
|||
# In this test we connect Sprout, Overwinter, and Sapling mininodes to a Zcashd |
|||
# node which will activate Overwinter at block 10 and Sapling at block 15. |
|||
# |
|||
# We test: |
|||
# 1. the mininodes stay connected to Zcash with Sprout consensus rules |
|||
# 2. when Overwinter activates, the Sprout mininodes are dropped |
|||
# 3. new Overwinter and Sapling nodes can connect to Zcash |
|||
# 4. new Sprout nodes cannot connect to Zcash |
|||
# 5. when Sapling activates, the Overwinter mininodes are dropped |
|||
# 6. new Sapling nodes can connect to Zcash |
|||
# 7. new Sprout and Overwinter nodes cannot connect to Zcash |
|||
# |
|||
# This test *does not* verify that prior to each activation, the Zcashd |
|||
# node will prefer connections with NU-aware nodes, with an eviction process |
|||
# that prioritizes non-NU-aware connections. |
|||
# |
|||
|
|||
|
|||
class TestManager(NodeConnCB): |
|||
def __init__(self): |
|||
NodeConnCB.__init__(self) |
|||
self.create_callback_map() |
|||
|
|||
def on_close(self, conn): |
|||
pass |
|||
|
|||
def on_reject(self, conn, message): |
|||
conn.rejectMessage = message |
|||
|
|||
|
|||
class NUPeerManagementTest(BitcoinTestFramework): |
|||
|
|||
def setup_chain(self): |
|||
print "Initializing test directory "+self.options.tmpdir |
|||
initialize_chain_clean(self.options.tmpdir, 1) |
|||
|
|||
def setup_network(self): |
|||
self.nodes = start_nodes(1, self.options.tmpdir, extra_args=[[ |
|||
'-nuparams=5ba81b19:10', # Overwinter |
|||
'-nuparams=76b809bb:15', # Sapling |
|||
'-debug', |
|||
'-whitelist=127.0.0.1', |
|||
]]) |
|||
|
|||
def run_test(self): |
|||
test = TestManager() |
|||
|
|||
# Launch Sprout, Overwinter, and Sapling mininodes |
|||
nodes = [] |
|||
for x in xrange(10): |
|||
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], |
|||
test, "regtest", SPROUT_PROTO_VERSION)) |
|||
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], |
|||
test, "regtest", OVERWINTER_PROTO_VERSION)) |
|||
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], |
|||
test, "regtest", SAPLING_PROTO_VERSION)) |
|||
|
|||
# Start up network handling in another thread |
|||
NetworkThread().start() |
|||
|
|||
# Sprout consensus rules apply at block height 9 |
|||
self.nodes[0].generate(9) |
|||
assert_equal(9, self.nodes[0].getblockcount()) |
|||
|
|||
# Verify mininodes are still connected to zcashd node |
|||
peerinfo = self.nodes[0].getpeerinfo() |
|||
versions = [x["version"] for x in peerinfo] |
|||
assert_equal(10, versions.count(SPROUT_PROTO_VERSION)) |
|||
assert_equal(10, versions.count(OVERWINTER_PROTO_VERSION)) |
|||
assert_equal(10, versions.count(SAPLING_PROTO_VERSION)) |
|||
|
|||
# Overwinter consensus rules activate at block height 10 |
|||
self.nodes[0].generate(1) |
|||
assert_equal(10, self.nodes[0].getblockcount()) |
|||
print('Overwinter active') |
|||
|
|||
# Mininodes send ping message to zcashd node. |
|||
pingCounter = 1 |
|||
for node in nodes: |
|||
node.send_message(msg_ping(pingCounter)) |
|||
pingCounter = pingCounter + 1 |
|||
|
|||
time.sleep(3) |
|||
|
|||
# Verify Sprout mininodes have been dropped, while Overwinter and |
|||
# Sapling mininodes are still connected. |
|||
peerinfo = self.nodes[0].getpeerinfo() |
|||
versions = [x["version"] for x in peerinfo] |
|||
assert_equal(0, versions.count(SPROUT_PROTO_VERSION)) |
|||
assert_equal(10, versions.count(OVERWINTER_PROTO_VERSION)) |
|||
assert_equal(10, versions.count(SAPLING_PROTO_VERSION)) |
|||
|
|||
# Extend the Overwinter chain with another block. |
|||
self.nodes[0].generate(1) |
|||
|
|||
# Connect a new Overwinter mininode to the zcashd node, which is accepted. |
|||
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", OVERWINTER_PROTO_VERSION)) |
|||
time.sleep(3) |
|||
assert_equal(21, len(self.nodes[0].getpeerinfo())) |
|||
|
|||
# Connect a new Sapling mininode to the zcashd node, which is accepted. |
|||
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SAPLING_PROTO_VERSION)) |
|||
time.sleep(3) |
|||
assert_equal(22, len(self.nodes[0].getpeerinfo())) |
|||
|
|||
# Try to connect a new Sprout mininode to the zcashd node, which is rejected. |
|||
sprout = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SPROUT_PROTO_VERSION) |
|||
nodes.append(sprout) |
|||
time.sleep(3) |
|||
assert("Version must be 170003 or greater" in str(sprout.rejectMessage)) |
|||
|
|||
# Verify that only Overwinter and Sapling mininodes are connected. |
|||
peerinfo = self.nodes[0].getpeerinfo() |
|||
versions = [x["version"] for x in peerinfo] |
|||
assert_equal(0, versions.count(SPROUT_PROTO_VERSION)) |
|||
assert_equal(11, versions.count(OVERWINTER_PROTO_VERSION)) |
|||
assert_equal(11, versions.count(SAPLING_PROTO_VERSION)) |
|||
|
|||
# Sapling consensus rules activate at block height 15 |
|||
self.nodes[0].generate(4) |
|||
assert_equal(15, self.nodes[0].getblockcount()) |
|||
print('Sapling active') |
|||
|
|||
# Mininodes send ping message to zcashd node. |
|||
pingCounter = 1 |
|||
for node in nodes: |
|||
node.send_message(msg_ping(pingCounter)) |
|||
pingCounter = pingCounter + 1 |
|||
|
|||
time.sleep(3) |
|||
|
|||
# Verify Sprout and Overwinter mininodes have been dropped, while |
|||
# Sapling mininodes are still connected. |
|||
peerinfo = self.nodes[0].getpeerinfo() |
|||
versions = [x["version"] for x in peerinfo] |
|||
assert_equal(0, versions.count(SPROUT_PROTO_VERSION)) |
|||
assert_equal(0, versions.count(OVERWINTER_PROTO_VERSION)) |
|||
assert_equal(11, versions.count(SAPLING_PROTO_VERSION)) |
|||
|
|||
# Extend the Sapling chain with another block. |
|||
self.nodes[0].generate(1) |
|||
|
|||
# Connect a new Sapling mininode to the zcashd node, which is accepted. |
|||
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SAPLING_PROTO_VERSION)) |
|||
time.sleep(3) |
|||
assert_equal(12, len(self.nodes[0].getpeerinfo())) |
|||
|
|||
# Try to connect a new Sprout mininode to the zcashd node, which is rejected. |
|||
sprout = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SPROUT_PROTO_VERSION) |
|||
nodes.append(sprout) |
|||
time.sleep(3) |
|||
assert("Version must be 170006 or greater" in str(sprout.rejectMessage)) |
|||
|
|||
# Try to connect a new Overwinter mininode to the zcashd node, which is rejected. |
|||
sprout = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", OVERWINTER_PROTO_VERSION) |
|||
nodes.append(sprout) |
|||
time.sleep(3) |
|||
assert("Version must be 170006 or greater" in str(sprout.rejectMessage)) |
|||
|
|||
# Verify that only Sapling mininodes are connected. |
|||
peerinfo = self.nodes[0].getpeerinfo() |
|||
versions = [x["version"] for x in peerinfo] |
|||
assert_equal(0, versions.count(SPROUT_PROTO_VERSION)) |
|||
assert_equal(0, versions.count(OVERWINTER_PROTO_VERSION)) |
|||
assert_equal(12, versions.count(SAPLING_PROTO_VERSION)) |
|||
|
|||
for node in nodes: |
|||
node.disconnect_node() |
|||
|
|||
if __name__ == '__main__': |
|||
NUPeerManagementTest().main() |
@ -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() |
@ -1,59 +0,0 @@ |
|||
#!/usr/bin/env python2 |
|||
|
|||
# |
|||
# Test joinsplit semantics |
|||
# |
|||
|
|||
from test_framework.test_framework import BitcoinTestFramework |
|||
from test_framework.util import assert_equal, start_node, \ |
|||
gather_inputs |
|||
|
|||
|
|||
class JoinSplitTest(BitcoinTestFramework): |
|||
def setup_network(self): |
|||
self.nodes = [] |
|||
self.is_network_split = False |
|||
self.nodes.append(start_node(0, self.options.tmpdir)) |
|||
|
|||
def run_test(self): |
|||
zckeypair = self.nodes[0].zcrawkeygen() |
|||
zcsecretkey = zckeypair["zcsecretkey"] |
|||
zcaddress = zckeypair["zcaddress"] |
|||
|
|||
(total_in, inputs) = gather_inputs(self.nodes[0], 40) |
|||
protect_tx = self.nodes[0].createrawtransaction(inputs, {}) |
|||
joinsplit_result = self.nodes[0].zcrawjoinsplit(protect_tx, {}, {zcaddress:39.99}, 39.99, 0) |
|||
|
|||
receive_result = self.nodes[0].zcrawreceive(zcsecretkey, joinsplit_result["encryptednote1"]) |
|||
assert_equal(receive_result["exists"], False) |
|||
|
|||
protect_tx = self.nodes[0].signrawtransaction(joinsplit_result["rawtxn"]) |
|||
self.nodes[0].sendrawtransaction(protect_tx["hex"]) |
|||
self.nodes[0].generate(1) |
|||
self.sync_all() |
|||
|
|||
receive_result = self.nodes[0].zcrawreceive(zcsecretkey, joinsplit_result["encryptednote1"]) |
|||
assert_equal(receive_result["exists"], True) |
|||
|
|||
# The pure joinsplit we create should be mined in the next block |
|||
# despite other transactions being in the mempool. |
|||
addrtest = self.nodes[0].getnewaddress() |
|||
for xx in range(0,10): |
|||
self.nodes[0].generate(1) |
|||
self.sync_all() |
|||
for x in range(0,50): |
|||
self.nodes[0].sendtoaddress(addrtest, 0.01); |
|||
|
|||
joinsplit_tx = self.nodes[0].createrawtransaction([], {}) |
|||
joinsplit_result = self.nodes[0].zcrawjoinsplit(joinsplit_tx, {receive_result["note"] : zcsecretkey}, {zcaddress: 39.98}, 0, 0.01) |
|||
|
|||
self.nodes[0].sendrawtransaction(joinsplit_result["rawtxn"]) |
|||
self.nodes[0].generate(1) |
|||
self.sync_all() |
|||
|
|||
print "Done!" |
|||
receive_result = self.nodes[0].zcrawreceive(zcsecretkey, joinsplit_result["encryptednote1"]) |
|||
assert_equal(receive_result["exists"], True) |
|||
|
|||
if __name__ == '__main__': |
|||
JoinSplitTest().main() |
@ -1,182 +0,0 @@ |
|||
#!/usr/bin/env python2 |
|||
|
|||
# |
|||
# Tests a joinsplit double-spend and a subsequent reorg. |
|||
# |
|||
|
|||
from test_framework.test_framework import BitcoinTestFramework |
|||
from test_framework.authproxy import JSONRPCException |
|||
from test_framework.util import assert_equal, connect_nodes, \ |
|||
gather_inputs, sync_blocks |
|||
|
|||
import time |
|||
|
|||
class JoinSplitTest(BitcoinTestFramework): |
|||
def setup_network(self): |
|||
# Start with split network: |
|||
return super(JoinSplitTest, self).setup_network(True) |
|||
|
|||
def txid_in_mempool(self, node, txid): |
|||
exception_triggered = False |
|||
|
|||
try: |
|||
node.getrawtransaction(txid) |
|||
except JSONRPCException: |
|||
exception_triggered = True |
|||
|
|||
return not exception_triggered |
|||
|
|||
def cannot_joinsplit(self, node, txn): |
|||
exception_triggered = False |
|||
|
|||
try: |
|||
node.sendrawtransaction(txn) |
|||
except JSONRPCException: |
|||
exception_triggered = True |
|||
|
|||
return exception_triggered |
|||
|
|||
def expect_cannot_joinsplit(self, node, txn): |
|||
assert_equal(self.cannot_joinsplit(node, txn), True) |
|||
|
|||
def run_test(self): |
|||
# All nodes should start with 250 HUSH: |
|||
starting_balance = 250 |
|||
for i in range(4): |
|||
assert_equal(self.nodes[i].getbalance(), starting_balance) |
|||
self.nodes[i].getnewaddress("") # bug workaround, coins generated assigned to first getnewaddress! |
|||
|
|||
# Generate zcaddress keypairs |
|||
zckeypair = self.nodes[0].zcrawkeygen() |
|||
zcsecretkey = zckeypair["zcsecretkey"] |
|||
zcaddress = zckeypair["zcaddress"] |
|||
|
|||
pool = [0, 1, 2, 3] |
|||
for i in range(4): |
|||
(total_in, inputs) = gather_inputs(self.nodes[i], 40) |
|||
pool[i] = self.nodes[i].createrawtransaction(inputs, {}) |
|||
pool[i] = self.nodes[i].zcrawjoinsplit(pool[i], {}, {zcaddress:39.99}, 39.99, 0) |
|||
signed = self.nodes[i].signrawtransaction(pool[i]["rawtxn"]) |
|||
|
|||
# send the tx to both halves of the network |
|||
self.nodes[0].sendrawtransaction(signed["hex"]) |
|||
self.nodes[0].generate(1) |
|||
self.nodes[2].sendrawtransaction(signed["hex"]) |
|||
self.nodes[2].generate(1) |
|||
pool[i] = pool[i]["encryptednote1"] |
|||
|
|||
sync_blocks(self.nodes[0:2]) |
|||
sync_blocks(self.nodes[2:4]) |
|||
|
|||
# Confirm that the protects have taken place |
|||
for i in range(4): |
|||
enc_note = pool[i] |
|||
receive_result = self.nodes[0].zcrawreceive(zcsecretkey, enc_note) |
|||
assert_equal(receive_result["exists"], True) |
|||
pool[i] = receive_result["note"] |
|||
|
|||
# Extra confirmations |
|||
receive_result = self.nodes[1].zcrawreceive(zcsecretkey, enc_note) |
|||
assert_equal(receive_result["exists"], True) |
|||
|
|||
receive_result = self.nodes[2].zcrawreceive(zcsecretkey, enc_note) |
|||
assert_equal(receive_result["exists"], True) |
|||
|
|||
receive_result = self.nodes[3].zcrawreceive(zcsecretkey, enc_note) |
|||
assert_equal(receive_result["exists"], True) |
|||
|
|||
blank_tx = self.nodes[0].createrawtransaction([], {}) |
|||
# Create joinsplit {A, B}->{*} |
|||
joinsplit_AB = self.nodes[0].zcrawjoinsplit(blank_tx, |
|||
{pool[0] : zcsecretkey, pool[1] : zcsecretkey}, |
|||
{zcaddress:(39.99*2)-0.01}, |
|||
0, 0.01) |
|||
|
|||
# Create joinsplit {B, C}->{*} |
|||
joinsplit_BC = self.nodes[0].zcrawjoinsplit(blank_tx, |
|||
{pool[1] : zcsecretkey, pool[2] : zcsecretkey}, |
|||
{zcaddress:(39.99*2)-0.01}, |
|||
0, 0.01) |
|||
|
|||
# Create joinsplit {C, D}->{*} |
|||
joinsplit_CD = self.nodes[0].zcrawjoinsplit(blank_tx, |
|||
{pool[2] : zcsecretkey, pool[3] : zcsecretkey}, |
|||
{zcaddress:(39.99*2)-0.01}, |
|||
0, 0.01) |
|||
|
|||
# Create joinsplit {A, D}->{*} |
|||
joinsplit_AD = self.nodes[0].zcrawjoinsplit(blank_tx, |
|||
{pool[0] : zcsecretkey, pool[3] : zcsecretkey}, |
|||
{zcaddress:(39.99*2)-0.01}, |
|||
0, 0.01) |
|||
|
|||
# (a) Node 0 will spend joinsplit AB, then attempt to |
|||
# double-spend it with BC. It should fail before and |
|||
# after Node 0 mines blocks. |
|||
# |
|||
# (b) Then, Node 2 will spend BC, and mine 5 blocks. |
|||
# Node 1 connects, and AB will be reorg'd from the chain. |
|||
# Any attempts to spend AB or CD should fail for |
|||
# both nodes. |
|||
# |
|||
# (c) Then, Node 0 will spend AD, which should work |
|||
# because the previous spend for A (AB) is considered |
|||
# invalid due to the reorg. |
|||
|
|||
# (a) |
|||
|
|||
AB_txid = self.nodes[0].sendrawtransaction(joinsplit_AB["rawtxn"]) |
|||
|
|||
self.expect_cannot_joinsplit(self.nodes[0], joinsplit_BC["rawtxn"]) |
|||
|
|||
# Wait until node[1] receives AB before we attempt to double-spend |
|||
# with BC. |
|||
print "Waiting for AB_txid...\n" |
|||
while True: |
|||
if self.txid_in_mempool(self.nodes[1], AB_txid): |
|||
break |
|||
time.sleep(0.2) |
|||
print "Done!\n" |
|||
|
|||
self.expect_cannot_joinsplit(self.nodes[1], joinsplit_BC["rawtxn"]) |
|||
|
|||
# Generate a block |
|||
self.nodes[0].generate(1) |
|||
sync_blocks(self.nodes[0:2]) |
|||
|
|||
self.expect_cannot_joinsplit(self.nodes[0], joinsplit_BC["rawtxn"]) |
|||
self.expect_cannot_joinsplit(self.nodes[1], joinsplit_BC["rawtxn"]) |
|||
|
|||
# (b) |
|||
self.nodes[2].sendrawtransaction(joinsplit_BC["rawtxn"]) |
|||
self.nodes[2].generate(5) |
|||
|
|||
# Connect the two nodes |
|||
|
|||
connect_nodes(self.nodes[1], 2) |
|||
sync_blocks(self.nodes) |
|||
|
|||
# AB and CD should all be impossible to spend for each node. |
|||
self.expect_cannot_joinsplit(self.nodes[0], joinsplit_AB["rawtxn"]) |
|||
self.expect_cannot_joinsplit(self.nodes[0], joinsplit_CD["rawtxn"]) |
|||
|
|||
self.expect_cannot_joinsplit(self.nodes[1], joinsplit_AB["rawtxn"]) |
|||
self.expect_cannot_joinsplit(self.nodes[1], joinsplit_CD["rawtxn"]) |
|||
|
|||
self.expect_cannot_joinsplit(self.nodes[2], joinsplit_AB["rawtxn"]) |
|||
self.expect_cannot_joinsplit(self.nodes[2], joinsplit_CD["rawtxn"]) |
|||
|
|||
self.expect_cannot_joinsplit(self.nodes[3], joinsplit_AB["rawtxn"]) |
|||
self.expect_cannot_joinsplit(self.nodes[3], joinsplit_CD["rawtxn"]) |
|||
|
|||
# (c) |
|||
# AD should be possible to send due to the reorg that |
|||
# tossed out AB. |
|||
|
|||
self.nodes[0].sendrawtransaction(joinsplit_AD["rawtxn"]) |
|||
self.nodes[0].generate(1) |
|||
|
|||
sync_blocks(self.nodes) |
|||
|
|||
if __name__ == '__main__': |
|||
JoinSplitTest().main() |
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,27 @@ |
|||
{ |
|||
"authorized": [ |
|||
{"chmex":"030754bffcf6dfcb34a20c486ff5a5be5546b9cc16fba9692165272b3f8e98c4af" }, |
|||
{"SHossain":"03c8657bd57b6ceb14514a10e99fe8a0cec5a9bc24592df7f66f050e670e4f6bac" }, |
|||
{"satinder":"03732f8ef851ff234c74d0df575c2c5b159e2bab3faca4ec52b3f217d5cda5361d" }, |
|||
{"ml777":"02453d028c74cb9551e1aaf35113383b6ecbd9f06ff23a4ab1a953429b9763e345" }, |
|||
{"tonylhub":"0218e0f435d4544404c25a7759b7f7174d821215085ef936218c5569d975af468b" }, |
|||
{"gthub":"036c7de9a5090fbad78b9eea41549ccacc07bd0e9e7f8d290c88f470f3569e1a35" }, |
|||
{"zkTrader":"026c6b0b35ec0adc2f8a5c648da1fce634f798c69d5e9fe518400447e88398b830" }, |
|||
{"nutellalicka":"03aee08860e0340f0f490a3ef3718d6676882f2d63d4f536dfebb1d348b82c79ee" }, |
|||
{"gcharang":"02d3431950c2f0f9654217b6ce3d44468d3a9ca7255741767fdeee7c5ec6b47567" }, |
|||
{"jl777":"02b27de3ee5335518b06f69f4fbabb029cfc737613b100996841d5532b324a5a61" } |
|||
], |
|||
"tokens":[ |
|||
{"RICK.demo":"2b1feef719ecb526b07416dd432bce603ac6dc8bfe794cddf105cb52f6aae3cd"} |
|||
], |
|||
"files":[ |
|||
{"filename":"hushd","prices":[{"HUSH":0.1}, {"PIRATE":1}]} |
|||
], |
|||
"externalcoins":[ |
|||
{ "BTC":"bitcoin-cli" }, |
|||
{ "KMD":"komodod-cli" }, |
|||
{ "CHIPS":"chips-cli" }, |
|||
{ "PIRATE":"pirate-cli" } |
|||
] |
|||
} |
|||
|
@ -1,585 +0,0 @@ |
|||
#include <gtest/gtest.h> |
|||
|
|||
#include "utilstrencodings.h" |
|||
|
|||
#include <boost/foreach.hpp> |
|||
#include <boost/variant/get.hpp> |
|||
|
|||
#include "zcash/prf.h" |
|||
#include "util.h" |
|||
#include "streams.h" |
|||
#include "version.h" |
|||
#include "serialize.h" |
|||
#include "primitives/transaction.h" |
|||
#include "zcash/JoinSplit.hpp" |
|||
#include "zcash/Note.hpp" |
|||
#include "zcash/NoteEncryption.hpp" |
|||
#include "zcash/IncrementalMerkleTree.hpp" |
|||
|
|||
#include <array> |
|||
|
|||
using namespace libzcash; |
|||
|
|||
extern ZCJoinSplit* params; |
|||
|
|||
// Make the Groth proof for a Sprout statement,
|
|||
// and store the result in a JSDescription object.
|
|||
JSDescription makeSproutProof( |
|||
ZCJoinSplit& js, |
|||
const std::array<JSInput, 2>& inputs, |
|||
const std::array<JSOutput, 2>& outputs, |
|||
const uint256& joinSplitPubKey, |
|||
uint64_t vpub_old, |
|||
uint64_t vpub_new, |
|||
const uint256& rt |
|||
){ |
|||
return JSDescription(js, joinSplitPubKey, rt, inputs, outputs, vpub_old, vpub_new); |
|||
} |
|||
|
|||
bool verifySproutProof( |
|||
ZCJoinSplit& js, |
|||
const JSDescription& jsdesc, |
|||
const uint256& joinSplitPubKey |
|||
) |
|||
{ |
|||
auto verifier = libzcash::ProofVerifier::Strict(); |
|||
return jsdesc.Verify(js, verifier, joinSplitPubKey); |
|||
} |
|||
|
|||
|
|||
void test_full_api(ZCJoinSplit* js) |
|||
{ |
|||
// Create verification context.
|
|||
auto verifier = libzcash::ProofVerifier::Strict(); |
|||
|
|||
// The recipient's information.
|
|||
SproutSpendingKey recipient_key = SproutSpendingKey::random(); |
|||
SproutPaymentAddress recipient_addr = recipient_key.address(); |
|||
|
|||
// Create the commitment tree
|
|||
SproutMerkleTree tree; |
|||
|
|||
// Set up a JoinSplit description
|
|||
uint64_t vpub_old = 10; |
|||
uint64_t vpub_new = 0; |
|||
uint256 joinSplitPubKey = random_uint256(); |
|||
uint256 rt = tree.root(); |
|||
JSDescription jsdesc; |
|||
|
|||
{ |
|||
std::array<JSInput, 2> inputs = { |
|||
JSInput(), // dummy input
|
|||
JSInput() // dummy input
|
|||
}; |
|||
|
|||
std::array<JSOutput, 2> outputs = { |
|||
JSOutput(recipient_addr, 10), |
|||
JSOutput() // dummy output
|
|||
}; |
|||
|
|||
std::array<SproutNote, 2> output_notes; |
|||
|
|||
// Perform the proofs
|
|||
jsdesc = makeSproutProof( |
|||
*js, |
|||
inputs, |
|||
outputs, |
|||
joinSplitPubKey, |
|||
vpub_old, |
|||
vpub_new, |
|||
rt |
|||
); |
|||
} |
|||
|
|||
// Verify both PHGR and Groth Proof:
|
|||
ASSERT_TRUE(verifySproutProof(*js, jsdesc, joinSplitPubKey)); |
|||
|
|||
{ |
|||
SproutMerkleTree tree; |
|||
JSDescription jsdesc2; |
|||
// Recipient should decrypt
|
|||
// Now the recipient should spend the money again
|
|||
auto h_sig = js->h_sig(jsdesc.randomSeed, jsdesc.nullifiers, joinSplitPubKey); |
|||
ZCNoteDecryption decryptor(recipient_key.receiving_key()); |
|||
|
|||
auto note_pt = SproutNotePlaintext::decrypt( |
|||
decryptor, |
|||
jsdesc.ciphertexts[0], |
|||
jsdesc.ephemeralKey, |
|||
h_sig, |
|||
0 |
|||
); |
|||
|
|||
auto decrypted_note = note_pt.note(recipient_addr); |
|||
|
|||
ASSERT_TRUE(decrypted_note.value() == 10); |
|||
|
|||
// Insert the commitments from the last tx into the tree
|
|||
tree.append(jsdesc.commitments[0]); |
|||
auto witness_recipient = tree.witness(); |
|||
tree.append(jsdesc.commitments[1]); |
|||
witness_recipient.append(jsdesc.commitments[1]); |
|||
vpub_old = 0; |
|||
vpub_new = 1; |
|||
rt = tree.root(); |
|||
auto joinSplitPubKey2 = random_uint256(); |
|||
|
|||
{ |
|||
std::array<JSInput, 2> inputs = { |
|||
JSInput(), // dummy input
|
|||
JSInput(witness_recipient, decrypted_note, recipient_key) |
|||
}; |
|||
|
|||
SproutSpendingKey second_recipient = SproutSpendingKey::random(); |
|||
SproutPaymentAddress second_addr = second_recipient.address(); |
|||
|
|||
std::array<JSOutput, 2> outputs = { |
|||
JSOutput(second_addr, 9), |
|||
JSOutput() // dummy output
|
|||
}; |
|||
|
|||
std::array<SproutNote, 2> output_notes; |
|||
|
|||
|
|||
// Perform the proofs
|
|||
jsdesc2 = makeSproutProof( |
|||
*js, |
|||
inputs, |
|||
outputs, |
|||
joinSplitPubKey2, |
|||
vpub_old, |
|||
vpub_new, |
|||
rt |
|||
); |
|||
|
|||
} |
|||
|
|||
|
|||
// Verify Groth Proof:
|
|||
ASSERT_TRUE(verifySproutProof(*js, jsdesc2, joinSplitPubKey2)); |
|||
} |
|||
} |
|||
|
|||
// Invokes the API (but does not compute a proof)
|
|||
// to test exceptions
|
|||
void invokeAPI( |
|||
ZCJoinSplit* js, |
|||
const std::array<JSInput, 2>& inputs, |
|||
const std::array<JSOutput, 2>& outputs, |
|||
uint64_t vpub_old, |
|||
uint64_t vpub_new, |
|||
const uint256& rt |
|||
) { |
|||
uint256 ephemeralKey; |
|||
uint256 randomSeed; |
|||
uint256 joinSplitPubKey = random_uint256(); |
|||
std::array<uint256, 2> macs; |
|||
std::array<uint256, 2> nullifiers; |
|||
std::array<uint256, 2> commitments; |
|||
std::array<ZCNoteEncryption::Ciphertext, 2> ciphertexts; |
|||
|
|||
std::array<SproutNote, 2> output_notes; |
|||
|
|||
// Groth
|
|||
SproutProof proof = js->prove( |
|||
inputs, |
|||
outputs, |
|||
output_notes, |
|||
ciphertexts, |
|||
ephemeralKey, |
|||
joinSplitPubKey, |
|||
randomSeed, |
|||
macs, |
|||
nullifiers, |
|||
commitments, |
|||
vpub_old, |
|||
vpub_new, |
|||
rt, |
|||
false |
|||
); |
|||
} |
|||
|
|||
void invokeAPIFailure( |
|||
ZCJoinSplit* js, |
|||
const std::array<JSInput, 2>& inputs, |
|||
const std::array<JSOutput, 2>& outputs, |
|||
uint64_t vpub_old, |
|||
uint64_t vpub_new, |
|||
const uint256& rt, |
|||
std::string reason |
|||
) |
|||
{ |
|||
try { |
|||
invokeAPI(js, inputs, outputs, vpub_old, vpub_new, rt); |
|||
FAIL() << "It worked, when it shouldn't have!"; |
|||
} catch(std::invalid_argument const & err) { |
|||
EXPECT_EQ(err.what(), reason); |
|||
} catch(...) { |
|||
FAIL() << "Expected invalid_argument exception."; |
|||
} |
|||
} |
|||
|
|||
TEST(joinsplit, h_sig) |
|||
{ |
|||
/*
|
|||
// by Taylor Hornby
|
|||
|
|||
import pyblake2 |
|||
import binascii |
|||
|
|||
def hSig(randomSeed, nf1, nf2, joinSplitPubKey): |
|||
return pyblake2.blake2b( |
|||
data=(randomSeed + nf1 + nf2 + joinSplitPubKey), |
|||
digest_size=32, |
|||
person=b"ZcashComputehSig" |
|||
).digest() |
|||
|
|||
INCREASING = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F" |
|||
|
|||
TEST_VECTORS = [ |
|||
[b"a" * 32, b"b" * 32, b"c" * 32, b"d" * 32], |
|||
[b"\x00" * 32, b"\x00" * 32, b"\x00" * 32, b"\x00" * 32], |
|||
[b"\xFF" * 32, b"\xFF" * 32, b"\xFF" * 32, b"\xFF" * 32], |
|||
[INCREASING, INCREASING, INCREASING, INCREASING] |
|||
] |
|||
|
|||
for test_input in TEST_VECTORS: |
|||
print "---" |
|||
print "\"" + binascii.hexlify(test_input[0][::-1]) + "\"" |
|||
print "\"" + binascii.hexlify(test_input[1][::-1]) + "\"" |
|||
print "\"" + binascii.hexlify(test_input[2][::-1]) + "\"" |
|||
print "\"" + binascii.hexlify(test_input[3][::-1]) + "\"" |
|||
print "\"" + binascii.hexlify(hSig(test_input[0], test_input[1], test_input[2], test_input[3])[::-1]) + "\"" |
|||
*/ |
|||
|
|||
std::vector<std::vector<std::string>> tests = { |
|||
{ |
|||
"6161616161616161616161616161616161616161616161616161616161616161", |
|||
"6262626262626262626262626262626262626262626262626262626262626262", |
|||
"6363636363636363636363636363636363636363636363636363636363636363", |
|||
"6464646464646464646464646464646464646464646464646464646464646464", |
|||
"a8cba69f1fa329c055756b4af900f8a00b61e44f4cb8a1824ceb58b90a5b8113" |
|||
}, |
|||
{ |
|||
"0000000000000000000000000000000000000000000000000000000000000000", |
|||
"0000000000000000000000000000000000000000000000000000000000000000", |
|||
"0000000000000000000000000000000000000000000000000000000000000000", |
|||
"0000000000000000000000000000000000000000000000000000000000000000", |
|||
"697322276b5dd93b12fb1fcbd2144b2960f24c73aac6c6a0811447be1e7f1e19" |
|||
}, |
|||
{ |
|||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", |
|||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", |
|||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", |
|||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", |
|||
"4961048919f0ca79d49c9378c36a91a8767060001f4212fe6f7d426f3ccf9f32" |
|||
}, |
|||
{ |
|||
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100", |
|||
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100", |
|||
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100", |
|||
"1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100", |
|||
"b61110ec162693bc3d9ca7fb0eec3afd2e278e2f41394b3ff11d7cb761ad4b27" |
|||
} |
|||
}; |
|||
|
|||
BOOST_FOREACH(std::vector<std::string>& v, tests) { |
|||
auto expected = ZCJoinSplit::h_sig( |
|||
uint256S(v[0]), |
|||
{uint256S(v[1]), uint256S(v[2])}, |
|||
uint256S(v[3]) |
|||
); |
|||
|
|||
EXPECT_EQ(expected, uint256S(v[4])); |
|||
} |
|||
} |
|||
|
|||
void increment_note_witnesses( |
|||
const uint256& element, |
|||
std::vector<SproutWitness>& witnesses, |
|||
SproutMerkleTree& tree |
|||
) |
|||
{ |
|||
tree.append(element); |
|||
for (SproutWitness& w : witnesses) { |
|||
w.append(element); |
|||
} |
|||
witnesses.push_back(tree.witness()); |
|||
} |
|||
|
|||
TEST(joinsplit, full_api_test) |
|||
{ |
|||
{ |
|||
std::vector<SproutWitness> witnesses; |
|||
SproutMerkleTree tree; |
|||
increment_note_witnesses(uint256(), witnesses, tree); |
|||
SproutSpendingKey sk = SproutSpendingKey::random(); |
|||
SproutPaymentAddress addr = sk.address(); |
|||
SproutNote note1(addr.a_pk, 100, random_uint256(), random_uint256()); |
|||
increment_note_witnesses(note1.cm(), witnesses, tree); |
|||
SproutNote note2(addr.a_pk, 100, random_uint256(), random_uint256()); |
|||
increment_note_witnesses(note2.cm(), witnesses, tree); |
|||
SproutNote note3(addr.a_pk, 2100000000000001, random_uint256(), random_uint256()); |
|||
increment_note_witnesses(note3.cm(), witnesses, tree); |
|||
SproutNote note4(addr.a_pk, 1900000000000000, random_uint256(), random_uint256()); |
|||
increment_note_witnesses(note4.cm(), witnesses, tree); |
|||
SproutNote note5(addr.a_pk, 1900000000000000, random_uint256(), random_uint256()); |
|||
increment_note_witnesses(note5.cm(), witnesses, tree); |
|||
|
|||
// Should work
|
|||
invokeAPI(params, |
|||
{ |
|||
JSInput(), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
0, |
|||
tree.root()); |
|||
|
|||
// lhs > MAX_MONEY
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
2100000000000001, |
|||
0, |
|||
tree.root(), |
|||
"nonsensical vpub_old value"); |
|||
|
|||
// rhs > MAX_MONEY
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
2100000000000001, |
|||
tree.root(), |
|||
"nonsensical vpub_new value"); |
|||
|
|||
// input witness for the wrong element
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(witnesses[0], note1, sk), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
100, |
|||
tree.root(), |
|||
"witness of wrong element for joinsplit input"); |
|||
|
|||
// input witness doesn't match up with
|
|||
// real root
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(witnesses[1], note1, sk), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
100, |
|||
uint256(), |
|||
"joinsplit not anchored to the correct root"); |
|||
|
|||
// input is in the tree now! this should work
|
|||
invokeAPI(params, |
|||
{ |
|||
JSInput(witnesses[1], note1, sk), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
100, |
|||
tree.root()); |
|||
|
|||
// Wrong secret key
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(witnesses[1], note1, SproutSpendingKey::random()), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
0, |
|||
tree.root(), |
|||
"input note not authorized to spend with given key"); |
|||
|
|||
// Absurd input value
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(witnesses[3], note3, sk), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
0, |
|||
tree.root(), |
|||
"nonsensical input note value"); |
|||
|
|||
// Absurd total input value
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(witnesses[4], note4, sk), |
|||
JSInput(witnesses[5], note5, sk) |
|||
}, |
|||
{ |
|||
JSOutput(), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
0, |
|||
tree.root(), |
|||
"nonsensical left hand size of joinsplit balance"); |
|||
|
|||
// Absurd output value
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(addr, 2100000000000001), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
0, |
|||
tree.root(), |
|||
"nonsensical output value"); |
|||
|
|||
// Absurd total output value
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(addr, 1900000000000000), |
|||
JSOutput(addr, 1900000000000000) |
|||
}, |
|||
0, |
|||
0, |
|||
tree.root(), |
|||
"nonsensical right hand side of joinsplit balance"); |
|||
|
|||
// Absurd total output value
|
|||
invokeAPIFailure(params, |
|||
{ |
|||
JSInput(), |
|||
JSInput() |
|||
}, |
|||
{ |
|||
JSOutput(addr, 1900000000000000), |
|||
JSOutput() |
|||
}, |
|||
0, |
|||
0, |
|||
tree.root(), |
|||
"invalid joinsplit balance"); |
|||
} |
|||
|
|||
test_full_api(params); |
|||
} |
|||
|
|||
TEST(joinsplit, note_plaintexts) |
|||
{ |
|||
uint252 a_sk = uint252(uint256S("f6da8716682d600f74fc16bd0187faad6a26b4aa4c24d5c055b216d94516840e")); |
|||
uint256 a_pk = PRF_addr_a_pk(a_sk); |
|||
uint256 sk_enc = ZCNoteEncryption::generate_privkey(a_sk); |
|||
uint256 pk_enc = ZCNoteEncryption::generate_pubkey(sk_enc); |
|||
SproutPaymentAddress addr_pk(a_pk, pk_enc); |
|||
|
|||
uint256 h_sig; |
|||
|
|||
ZCNoteEncryption encryptor(h_sig); |
|||
uint256 epk = encryptor.get_epk(); |
|||
|
|||
SproutNote note(a_pk, |
|||
1945813, |
|||
random_uint256(), |
|||
random_uint256() |
|||
); |
|||
|
|||
std::array<unsigned char, ZC_MEMO_SIZE> memo; |
|||
|
|||
SproutNotePlaintext note_pt(note, memo); |
|||
|
|||
ZCNoteEncryption::Ciphertext ct = note_pt.encrypt(encryptor, pk_enc); |
|||
|
|||
ZCNoteDecryption decryptor(sk_enc); |
|||
|
|||
auto decrypted = SproutNotePlaintext::decrypt(decryptor, ct, epk, h_sig, 0); |
|||
auto decrypted_note = decrypted.note(addr_pk); |
|||
|
|||
ASSERT_TRUE(decrypted_note.a_pk == note.a_pk); |
|||
ASSERT_TRUE(decrypted_note.rho == note.rho); |
|||
ASSERT_TRUE(decrypted_note.r == note.r); |
|||
ASSERT_TRUE(decrypted_note.value() == note.value()); |
|||
|
|||
ASSERT_TRUE(decrypted.memo() == note_pt.memo()); |
|||
|
|||
// Check memo() returns by reference, not return by value, for use cases such as:
|
|||
// std::string data(plaintext.memo().begin(), plaintext.memo().end());
|
|||
ASSERT_TRUE(decrypted.memo().data() == decrypted.memo().data()); |
|||
|
|||
// Check serialization of note plaintext
|
|||
CDataStream ss(SER_DISK, PROTOCOL_VERSION); |
|||
ss << note_pt; |
|||
SproutNotePlaintext note_pt2; |
|||
ss >> note_pt2; |
|||
ASSERT_EQ(note_pt.value(), note.value()); |
|||
ASSERT_EQ(note_pt.value(), note_pt2.value()); |
|||
ASSERT_EQ(note_pt.memo(), note_pt2.memo()); |
|||
ASSERT_EQ(note_pt.rho, note_pt2.rho); |
|||
ASSERT_EQ(note_pt.r, note_pt2.r); |
|||
} |
|||
|
|||
TEST(joinsplit, note_class) |
|||
{ |
|||
uint252 a_sk = uint252(uint256S("f6da8716682d600f74fc16bd0187faad6a26b4aa4c24d5c055b216d94516840e")); |
|||
uint256 a_pk = PRF_addr_a_pk(a_sk); |
|||
uint256 sk_enc = ZCNoteEncryption::generate_privkey(a_sk); |
|||
uint256 pk_enc = ZCNoteEncryption::generate_pubkey(sk_enc); |
|||
SproutPaymentAddress addr_pk(a_pk, pk_enc); |
|||
|
|||
SproutNote note(a_pk, |
|||
1945813, |
|||
random_uint256(), |
|||
random_uint256()); |
|||
|
|||
SproutNote clone = note; |
|||
ASSERT_NE(¬e, &clone); |
|||
ASSERT_EQ(note.value(), clone.value()); |
|||
ASSERT_EQ(note.cm(), clone.cm()); |
|||
ASSERT_EQ(note.rho, clone.rho); |
|||
ASSERT_EQ(note.r, clone.r); |
|||
ASSERT_EQ(note.a_pk, clone.a_pk); |
|||
} |
@ -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,90 +0,0 @@ |
|||
#include <gtest/gtest.h> |
|||
|
|||
#include "primitives/transaction.h" |
|||
#include "zcash/Note.hpp" |
|||
#include "zcash/Address.hpp" |
|||
|
|||
#include <array> |
|||
|
|||
extern ZCJoinSplit* params; |
|||
extern int GenZero(int n); |
|||
extern int GenMax(int n); |
|||
|
|||
TEST(Transaction, JSDescriptionRandomized) { |
|||
// construct a merkle tree
|
|||
SproutMerkleTree merkleTree; |
|||
|
|||
libzcash::SproutSpendingKey k = libzcash::SproutSpendingKey::random(); |
|||
libzcash::SproutPaymentAddress addr = k.address(); |
|||
|
|||
libzcash::SproutNote note(addr.a_pk, 100, uint256(), uint256()); |
|||
|
|||
// commitment from coin
|
|||
uint256 commitment = note.cm(); |
|||
|
|||
// insert commitment into the merkle tree
|
|||
merkleTree.append(commitment); |
|||
|
|||
// compute the merkle root we will be working with
|
|||
uint256 rt = merkleTree.root(); |
|||
|
|||
auto witness = merkleTree.witness(); |
|||
|
|||
// create JSDescription
|
|||
uint256 joinSplitPubKey; |
|||
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs = { |
|||
libzcash::JSInput(witness, note, k), |
|||
libzcash::JSInput() // dummy input of zero value
|
|||
}; |
|||
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs = { |
|||
libzcash::JSOutput(addr, 50), |
|||
libzcash::JSOutput(addr, 50) |
|||
}; |
|||
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap; |
|||
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap; |
|||
|
|||
{ |
|||
auto jsdesc = JSDescription::Randomized( |
|||
false, |
|||
*params, joinSplitPubKey, rt, |
|||
inputs, outputs, |
|||
inputMap, outputMap, |
|||
0, 0, false); |
|||
|
|||
std::set<size_t> inputSet(inputMap.begin(), inputMap.end()); |
|||
std::set<size_t> expectedInputSet {0, 1}; |
|||
EXPECT_EQ(expectedInputSet, inputSet); |
|||
|
|||
std::set<size_t> outputSet(outputMap.begin(), outputMap.end()); |
|||
std::set<size_t> expectedOutputSet {0, 1}; |
|||
EXPECT_EQ(expectedOutputSet, outputSet); |
|||
} |
|||
|
|||
{ |
|||
auto jsdesc = JSDescription::Randomized( |
|||
false, |
|||
*params, joinSplitPubKey, rt, |
|||
inputs, outputs, |
|||
inputMap, outputMap, |
|||
0, 0, false, nullptr, GenZero); |
|||
|
|||
std::array<size_t, ZC_NUM_JS_INPUTS> expectedInputMap {1, 0}; |
|||
std::array<size_t, ZC_NUM_JS_OUTPUTS> expectedOutputMap {1, 0}; |
|||
EXPECT_EQ(expectedInputMap, inputMap); |
|||
EXPECT_EQ(expectedOutputMap, outputMap); |
|||
} |
|||
|
|||
{ |
|||
auto jsdesc = JSDescription::Randomized( |
|||
false, |
|||
*params, joinSplitPubKey, rt, |
|||
inputs, outputs, |
|||
inputMap, outputMap, |
|||
0, 0, false, nullptr, GenMax); |
|||
|
|||
std::array<size_t, ZC_NUM_JS_INPUTS> expectedInputMap {0, 1}; |
|||
std::array<size_t, ZC_NUM_JS_OUTPUTS> expectedOutputMap {0, 1}; |
|||
EXPECT_EQ(expectedInputMap, inputMap); |
|||
EXPECT_EQ(expectedOutputMap, outputMap); |
|||
} |
|||
} |
@ -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,148 +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_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
|
@ -1,24 +0,0 @@ |
|||
// Copyright (c) 2019 The Hush Developers
|
|||
// Copyright (c) 2018 Michael Toutonghi
|
|||
// 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 "hash.h" |
|||
#include "nonce.h" |
|||
#include <cstring> |
|||
|
@ -1,29 +0,0 @@ |
|||
// Copyright (c) 2019 Hush Developers
|
|||
// Copyright (c) 2018 Michael Toutonghi
|
|||
// 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. * |
|||
* * |
|||
******************************************************************************/ |
|||
|
|||
#ifndef BITCOIN_PRIMITIVES_NONCE_H |
|||
#define BITCOIN_PRIMITIVES_NONCE_H |
|||
|
|||
#include "serialize.h" |
|||
#include "uint256.h" |
|||
#include "arith_uint256.h" |
|||
|
|||
|
|||
#endif // BITCOIN_PRIMITIVES_NONCE_H
|
File diff suppressed because it is too large
@ -1,154 +0,0 @@ |
|||
// Copyright (c) 2016 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.
|
|||
|
|||
#include "utiltest.h" |
|||
|
|||
#include "consensus/upgrades.h" |
|||
|
|||
#include <array> |
|||
|
|||
CWalletTx GetValidReceive(ZCJoinSplit& params, |
|||
const libzcash::SproutSpendingKey& sk, CAmount value, |
|||
bool randomInputs, |
|||
int32_t version /* = 2 */) { |
|||
CMutableTransaction mtx; |
|||
mtx.nVersion = version; |
|||
mtx.vin.resize(2); |
|||
if (randomInputs) { |
|||
mtx.vin[0].prevout.hash = GetRandHash(); |
|||
mtx.vin[1].prevout.hash = GetRandHash(); |
|||
} else { |
|||
mtx.vin[0].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000001"); |
|||
mtx.vin[1].prevout.hash = uint256S("0000000000000000000000000000000000000000000000000000000000000002"); |
|||
} |
|||
mtx.vin[0].prevout.n = 0; |
|||
mtx.vin[1].prevout.n = 0; |
|||
|
|||
// Generate an ephemeral keypair.
|
|||
uint256 joinSplitPubKey; |
|||
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES]; |
|||
crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey); |
|||
mtx.joinSplitPubKey = joinSplitPubKey; |
|||
|
|||
std::array<libzcash::JSInput, 2> inputs = { |
|||
libzcash::JSInput(), // dummy input
|
|||
libzcash::JSInput() // dummy input
|
|||
}; |
|||
|
|||
std::array<libzcash::JSOutput, 2> outputs = { |
|||
libzcash::JSOutput(sk.address(), value), |
|||
libzcash::JSOutput(sk.address(), value) |
|||
}; |
|||
|
|||
// Prepare JoinSplits
|
|||
uint256 rt; |
|||
JSDescription jsdesc {params, mtx.joinSplitPubKey, rt, |
|||
inputs, outputs, 2*value, 0, false}; |
|||
mtx.vjoinsplit.push_back(jsdesc); |
|||
|
|||
if (version >= 4) { |
|||
// Shielded Output
|
|||
OutputDescription od; |
|||
mtx.vShieldedOutput.push_back(od); |
|||
} |
|||
|
|||
// Empty output script.
|
|||
uint32_t consensusBranchId = SPROUT_BRANCH_ID; |
|||
CScript scriptCode; |
|||
CTransaction signTx(mtx); |
|||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId); |
|||
|
|||
// Add the signature
|
|||
assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, |
|||
dataToBeSigned.begin(), 32, |
|||
joinSplitPrivKey |
|||
) == 0); |
|||
|
|||
CTransaction tx {mtx}; |
|||
CWalletTx wtx {NULL, tx}; |
|||
return wtx; |
|||
} |
|||
|
|||
libzcash::SproutNote GetNote(ZCJoinSplit& params, |
|||
const libzcash::SproutSpendingKey& sk, |
|||
const CTransaction& tx, size_t js, size_t n) { |
|||
ZCNoteDecryption decryptor {sk.receiving_key()}; |
|||
auto hSig = tx.vjoinsplit[js].h_sig(params, tx.joinSplitPubKey); |
|||
auto note_pt = libzcash::SproutNotePlaintext::decrypt( |
|||
decryptor, |
|||
tx.vjoinsplit[js].ciphertexts[n], |
|||
tx.vjoinsplit[js].ephemeralKey, |
|||
hSig, |
|||
(unsigned char) n); |
|||
return note_pt.note(sk.address()); |
|||
} |
|||
|
|||
CWalletTx GetValidSpend(ZCJoinSplit& params, |
|||
const libzcash::SproutSpendingKey& sk, |
|||
const libzcash::SproutNote& note, CAmount value) { |
|||
CMutableTransaction mtx; |
|||
mtx.vout.resize(2); |
|||
mtx.vout[0].nValue = value; |
|||
mtx.vout[1].nValue = 0; |
|||
|
|||
// Generate an ephemeral keypair.
|
|||
uint256 joinSplitPubKey; |
|||
unsigned char joinSplitPrivKey[crypto_sign_SECRETKEYBYTES]; |
|||
crypto_sign_keypair(joinSplitPubKey.begin(), joinSplitPrivKey); |
|||
mtx.joinSplitPubKey = joinSplitPubKey; |
|||
|
|||
// Fake tree for the unused witness
|
|||
SproutMerkleTree tree; |
|||
|
|||
libzcash::JSOutput dummyout; |
|||
libzcash::JSInput dummyin; |
|||
|
|||
{ |
|||
if (note.value() > value) { |
|||
libzcash::SproutSpendingKey dummykey = libzcash::SproutSpendingKey::random(); |
|||
libzcash::SproutPaymentAddress dummyaddr = dummykey.address(); |
|||
dummyout = libzcash::JSOutput(dummyaddr, note.value() - value); |
|||
} else if (note.value() < value) { |
|||
libzcash::SproutSpendingKey dummykey = libzcash::SproutSpendingKey::random(); |
|||
libzcash::SproutPaymentAddress dummyaddr = dummykey.address(); |
|||
libzcash::SproutNote dummynote(dummyaddr.a_pk, (value - note.value()), uint256(), uint256()); |
|||
tree.append(dummynote.cm()); |
|||
dummyin = libzcash::JSInput(tree.witness(), dummynote, dummykey); |
|||
} |
|||
} |
|||
|
|||
tree.append(note.cm()); |
|||
|
|||
std::array<libzcash::JSInput, 2> inputs = { |
|||
libzcash::JSInput(tree.witness(), note, sk), |
|||
dummyin |
|||
}; |
|||
|
|||
std::array<libzcash::JSOutput, 2> outputs = { |
|||
dummyout, // dummy output
|
|||
libzcash::JSOutput() // dummy output
|
|||
}; |
|||
|
|||
// Prepare JoinSplits
|
|||
uint256 rt = tree.root(); |
|||
JSDescription jsdesc {params, mtx.joinSplitPubKey, rt, |
|||
inputs, outputs, 0, value, false}; |
|||
mtx.vjoinsplit.push_back(jsdesc); |
|||
|
|||
// Empty output script.
|
|||
uint32_t consensusBranchId = SPROUT_BRANCH_ID; |
|||
CScript scriptCode; |
|||
CTransaction signTx(mtx); |
|||
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId); |
|||
|
|||
// Add the signature
|
|||
assert(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, |
|||
dataToBeSigned.begin(), 32, |
|||
joinSplitPrivKey |
|||
) == 0); |
|||
CTransaction tx {mtx}; |
|||
CWalletTx wtx {NULL, tx}; |
|||
return wtx; |
|||
} |
@ -1,19 +0,0 @@ |
|||
// Copyright (c) 2016 The Zcash developers
|
|||
// Distributed under the MIT software license, see the accompanying
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|||
|
|||
#include "wallet/wallet.h" |
|||
#include "zcash/JoinSplit.hpp" |
|||
#include "zcash/Note.hpp" |
|||
#include "zcash/NoteEncryption.hpp" |
|||
|
|||
CWalletTx GetValidReceive(ZCJoinSplit& params, |
|||
const libzcash::SproutSpendingKey& sk, CAmount value, |
|||
bool randomInputs, |
|||
int32_t version = 2); |
|||
libzcash::SproutNote GetNote(ZCJoinSplit& params, |
|||
const libzcash::SproutSpendingKey& sk, |
|||
const CTransaction& tx, size_t js, size_t n); |
|||
CWalletTx GetValidSpend(ZCJoinSplit& params, |
|||
const libzcash::SproutSpendingKey& sk, |
|||
const libzcash::SproutNote& note, CAmount value); |
@ -1,319 +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.
|
|||
|
|||
/******************************************************************************
|
|||
* 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."); |
|||
} |
|||
|
|||
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
|
|||
SproutPaymentAddress 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; |
|||
} |
Loading…
Reference in new issue