Browse Source

Merge pull request #117 from MyHush/dev

Hush 3.4.0
pull/118/head v3.4.1
Duke Leto 4 years ago
committed by GitHub
parent
commit
13e4ffafaf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      build.sh
  2. 4
      configure.ac
  3. 7
      contrib/block_time.pl
  4. 2
      depends/packages/boost.mk
  5. 6
      depends/packages/native_ccache.mk
  6. 8
      doc/man/hush-cli.1
  7. 8
      doc/man/hush-tx.1
  8. 18
      doc/man/hushd.1
  9. 107
      doc/payment-disclosure.md
  10. 5
      qa/pull-tester/rpc-tests.sh
  11. 192
      qa/rpc-tests/p2p_nu_peer_management.py
  12. 215
      qa/rpc-tests/paymentdisclosure.py
  13. 3
      qa/rpc-tests/regtest_signrawtransaction.py
  14. 81
      qa/rpc-tests/wallet_listnotes.py
  15. 59
      qa/rpc-tests/zcjoinsplit.py
  16. 182
      qa/rpc-tests/zcjoinsplitdoublespend.py
  17. 25
      src/Makefile.am
  18. 1
      src/amount.h
  19. 5
      src/arith_uint256.cpp
  20. 1
      src/cc/cclib.cpp
  21. 5
      src/cc/dapps/Makefile
  22. 1599
      src/cc/dapps/dappinc.h
  23. 1429
      src/cc/dapps/subatomic.c
  24. 27
      src/cc/dapps/subatomic.json
  25. 2
      src/checkpoints.cpp
  26. 4
      src/clientversion.h
  27. 64
      src/coins.cpp
  28. 11
      src/coins.h
  29. 1
      src/consensus/upgrades.cpp
  30. 6
      src/cryptoconditions/src/anon.c
  31. 19
      src/cryptoconditions/src/cryptoconditions.c
  32. 6
      src/cryptoconditions/src/ed25519.c
  33. 10
      src/cryptoconditions/src/eval.c
  34. 2
      src/cryptoconditions/src/include/secp256k1/src/ecmult_const.h
  35. 5
      src/cryptoconditions/src/internal.h
  36. 1
      src/cryptoconditions/src/json_rpc.c
  37. 8
      src/cryptoconditions/src/prefix.c
  38. 8
      src/cryptoconditions/src/preimage.c
  39. 6
      src/cryptoconditions/src/secp256k1.c
  40. 7
      src/cryptoconditions/src/threshold.c
  41. 11
      src/cryptoconditions/src/utils.c
  42. 21
      src/cryptoconditions/tests/test_failure_modes.py
  43. 585
      src/gtest/test_joinsplit.cpp
  44. 172
      src/gtest/test_keystore.cpp
  45. 211
      src/gtest/test_paymentdisclosure.cpp
  46. 90
      src/gtest/test_transaction.cpp
  47. 11
      src/httpserver.cpp
  48. 24
      src/init.cpp
  49. 4
      src/init.h
  50. 1
      src/key.h
  51. 73
      src/key_io.cpp
  52. 1
      src/key_io.h
  53. 42
      src/keystore.cpp
  54. 87
      src/keystore.h
  55. 2
      src/komodo_bitcoind.h
  56. 92
      src/komodo_defs.h
  57. 2
      src/komodo_notary.h
  58. 106
      src/main.cpp
  59. 2
      src/metrics.cpp
  60. 2
      src/miner.cpp
  61. 65
      src/paymentdisclosure.cpp
  62. 148
      src/paymentdisclosure.h
  63. 93
      src/paymentdisclosuredb.cpp
  64. 42
      src/paymentdisclosuredb.h
  65. 4
      src/primitives/block.h
  66. 24
      src/primitives/nonce.cpp
  67. 29
      src/primitives/nonce.h
  68. 78
      src/primitives/transaction.cpp
  69. 1
      src/primitives/transaction.h
  70. 7
      src/rpc/blockchain.cpp
  71. 3
      src/rpc/client.cpp
  72. 76
      src/rpc/misc.cpp
  73. 8
      src/rpc/server.cpp
  74. 1
      src/rpc/server.h
  75. 1625
      src/rpcblockchain.old
  76. 1
      src/serialize.h
  77. 27
      src/test/coins_tests.cpp
  78. 37
      src/test/key_tests.cpp
  79. 67
      src/test/rpc_wallet_tests.cpp
  80. 1
      src/test/transaction_tests.cpp
  81. 1
      src/timedata.cpp
  82. 1
      src/timedata.h
  83. 1
      src/torcontrol.cpp
  84. 1
      src/torcontrol.h
  85. 3
      src/transaction_builder.cpp
  86. 1
      src/transaction_builder.h
  87. 12
      src/txdb.cpp
  88. 2
      src/txdb.h
  89. 15
      src/txmempool.cpp
  90. 2
      src/txmempool.h
  91. 3
      src/ui_interface.h
  92. 1
      src/util.h
  93. 3
      src/utilstrencodings.cpp
  94. 3
      src/utilstrencodings.h
  95. 154
      src/utiltest.cpp
  96. 19
      src/utiltest.h
  97. 4
      src/validationinterface.cpp
  98. 565
      src/wallet/asyncrpcoperation_mergetoaddress.cpp
  99. 60
      src/wallet/asyncrpcoperation_mergetoaddress.h
  100. 18
      src/wallet/asyncrpcoperation_saplingconsolidation.cpp
  101. 2
      src/wallet/asyncrpcoperation_saplingconsolidation.h
  102. 671
      src/wallet/asyncrpcoperation_sendmany.cpp
  103. 50
      src/wallet/asyncrpcoperation_sendmany.h
  104. 208
      src/wallet/asyncrpcoperation_shieldcoinbase.cpp
  105. 9
      src/wallet/asyncrpcoperation_shieldcoinbase.h
  106. 111
      src/wallet/crypter.cpp
  107. 34
      src/wallet/crypter.h
  108. 575
      src/wallet/gtest/test_wallet.cpp
  109. 297
      src/wallet/gtest/test_wallet_zkeys.cpp
  110. 319
      src/wallet/rpcdisclosure.cpp
  111. 114
      src/wallet/rpcdump.cpp
  112. 2
      src/wallet/rpchushwallet.cpp
  113. 220
      src/wallet/rpcwallet.cpp
  114. 670
      src/wallet/wallet.cpp
  115. 116
      src/wallet/wallet.h
  116. 68
      src/wallet/walletdb.cpp
  117. 11
      src/wallet/walletdb.h
  118. 38
      src/zcash/Address.cpp
  119. 62
      src/zcash/Address.hpp
  120. 3
      src/zcash/IncrementalMerkleTree.cpp
  121. 2
      src/zcash/IncrementalMerkleTree.hpp
  122. 241
      src/zcash/JoinSplit.cpp
  123. 58
      src/zcash/JoinSplit.hpp
  124. 82
      src/zcash/Note.cpp
  125. 63
      src/zcash/Note.hpp
  126. 50
      src/zcash/NoteEncryption.cpp
  127. 29
      src/zcash/NoteEncryption.hpp
  128. 2
      src/zcash/Proof.cpp
  129. 2
      src/zcash/Proof.hpp
  130. 1
      src/zcash/Zcash.h
  131. 2
      src/zcash/prf.cpp
  132. 2
      src/zcash/util.cpp
  133. 1
      src/zcash/util.h
  134. 1
      src/zcash/zip32.cpp
  135. 3
      src/zcash/zip32.h
  136. 211
      src/zcbenchmarks.cpp

5
build.sh

@ -0,0 +1,5 @@
#!/bin/bash
# Copyright (c) 2019-2020 The Hush developers
set -eu -o pipefail
./zcutil/build.sh $@

4
configure.ac

@ -1,8 +1,8 @@
dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N)
AC_PREREQ([2.60])
define(_CLIENT_VERSION_MAJOR, 3)
define(_CLIENT_VERSION_MINOR, 3)
define(_CLIENT_VERSION_REVISION, 2)
define(_CLIENT_VERSION_MINOR, 4)
define(_CLIENT_VERSION_REVISION, 0)
define(_CLIENT_VERSION_BUILD, 50)
define(_ZC_BUILD_VAL, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, m4_incr(_CLIENT_VERSION_BUILD), m4_eval(_CLIENT_VERSION_BUILD < 50), 1, m4_eval(_CLIENT_VERSION_BUILD - 24), m4_eval(_CLIENT_VERSION_BUILD == 50), 1, , m4_eval(_CLIENT_VERSION_BUILD - 50)))
define(_CLIENT_VERSION_SUFFIX, m4_if(m4_eval(_CLIENT_VERSION_BUILD < 25), 1, _CLIENT_VERSION_REVISION-beta$1, m4_eval(_CLIENT_VERSION_BUILD < 50), 1, _CLIENT_VERSION_REVISION-rc$1, m4_eval(_CLIENT_VERSION_BUILD == 50), 1, _CLIENT_VERSION_REVISION, _CLIENT_VERSION_REVISION-$1)))

7
contrib/block_time.pl

@ -1,5 +1,5 @@
#!/usr/bin/perl
# Copyright 2019 The Hush developers
# Copyright 2019-2020 The Hush developers
# Released under the GPLv3
use warnings;
use strict;
@ -9,6 +9,11 @@ my $block = shift || die "Usage: $0 123";
my $hush = "./src/hush-cli";
my $blockcount = qx{$hush getblockcount};
unless ($blockcount = int($blockcount)) {
print "Invalid response from hush-cli\n";
exit 1;
}
if ($block <= $blockcount) {
die "That block has already happened!";
} else {

2
depends/packages/boost.mk

@ -1,7 +1,7 @@
package=boost
$(package)_version=1_72_0
$(package)_download_path=https://dl.bintray.com/boostorg/release/1.72.0/source/
$(package)_download_path=https://github.com/MyHush/boost/releases/download/v1.72.0/
$(package)_sha256_hash=59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722
$(package)_file_name=$(package)_$($(package)_version).tar.bz2

6
depends/packages/native_ccache.mk

@ -1,8 +1,8 @@
package=native_ccache
$(package)_version=3.7.7
$(package)_version=3.7.9
$(package)_download_path=https://github.com/ccache/ccache/releases/download/v$($(package)_version)
$(package)_file_name=ccache-$($(package)_version).tar.xz
$(package)_sha256_hash=b7c1d6d6fe42f18e424de92746af863e0bc85794da3d69e44300840c478c98cd
$(package)_file_name=ccache-$($(package)_version).tar.gz
$(package)_sha256_hash=92838e2133c9e704fdab9ee2608dad86c99021278b9ac47d065aa8ff2ea8ce36
define $(package)_set_vars
$(package)_config_opts=

8
doc/man/hush-cli.1

@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.10.
.TH HUSH-CLI "1" "March 2020" "hush-cli v3.3.2" "User Commands"
.TH HUSH-CLI "1" "June 2020" "hush-cli v3.4.0" "User Commands"
.SH NAME
hush-cli \- manual page for hush-cli v3.3.2
hush-cli \- manual page for hush-cli v3.4.0
.SH DESCRIPTION
Komodo RPC client version v3.3.2\-699b59037
Komodo RPC client version v3.4.0\-2fbcca416\-dirty
.PP
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.
@ -71,7 +71,7 @@ Timeout in seconds during HTTP requests, or 0 for no timeout. (default:
Read extra arguments from standard input, one per line until EOF/Ctrl\-D
(recommended for sensitive information such as passphrases)
.SH COPYRIGHT
Hush Daemon version v3.3.2-699b59037
Hush Daemon version v3.4.0-2fbcca416-dirty
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.

8
doc/man/hush-tx.1

@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.10.
.TH HUSH-TX "1" "March 2020" "hush-tx v3.3.2" "User Commands"
.TH HUSH-TX "1" "June 2020" "hush-tx v3.4.0" "User Commands"
.SH NAME
hush-tx \- manual page for hush-tx v3.3.2
hush-tx \- manual page for hush-tx v3.4.0
.SH DESCRIPTION
Hush komodo\-tx utility version v3.3.2\-699b59037
Hush komodo\-tx utility version v3.4.0\-2fbcca416\-dirty
.SS "Usage:"
.TP
komodo\-tx [options] <hex\-tx> [commands]
@ -84,7 +84,7 @@ set=NAME:JSON\-STRING
.IP
Set register NAME to given JSON\-STRING
.SH COPYRIGHT
Hush Daemon version v3.3.2-699b59037
Hush Daemon version v3.4.0-2fbcca416-dirty
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.

18
doc/man/hushd.1

@ -1,10 +1,10 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.10.
.TH HUSHD "1" "March 2020" "hushd v3.3.2" "User Commands"
.TH HUSHD "1" "June 2020" "hushd v3.4.0" "User Commands"
.SH NAME
hushd \- manual page for hushd v3.3.2
hushd \- manual page for hushd v3.4.0
.SH DESCRIPTION
Found binary: ./komodod
Hush Daemon version v3.3.2\-699b59037
Hush Daemon version v3.4.0\-2fbcca416\-dirty
.PP
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.
@ -87,6 +87,11 @@ leave that many cores free, default: 0)
.IP
Specify pid file (default: komodod.pid)
.HP
\fB\-txexpirynotify=\fR<cmd>
.IP
Execute command when transaction expires (%s in cmd is replaced by
transaction id)
.HP
\fB\-prune=\fR<n>
.IP
Reduce storage requirements by pruning (deleting) old blocks. This mode
@ -295,6 +300,11 @@ Keep the last <n> transactions (default: 200)
.IP
Keep transactions for at least <n> blocks (default: 10000)
.HP
\fB\-opretmintxfee=\fR<amt>
.IP
Minimum fee (in KMD/kB) to allow for OP_RETURN transactions (default:
400000)
.HP
\fB\-paytxfee=\fR<amt>
.IP
Fee (in KMD/kB) to add to transactions you send (default: 0.00)
@ -629,7 +639,7 @@ Starting supply, default is 0
.IP
Enforce transaction\-rate limit, default 0
.SH COPYRIGHT
Hush Daemon version v3.3.2-699b59037
Hush Daemon version v3.4.0-2fbcca416-dirty
In order to ensure you are adequately protecting your privacy when using Hush,
please see <https://myhush.org/security/>.

107
doc/payment-disclosure.md

@ -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.

5
qa/pull-tester/rpc-tests.sh

@ -17,14 +17,12 @@ testScripts=(
'dpow.py'
'dpowconfs.py'
'ac_private.py'
'paymentdisclosure.py'
'prioritisetransaction.py'
'wallet_treestate.py'
'wallet_anchorfork.py'
'wallet_changeindicator.py'
'wallet_import_export.py'
'wallet_protectcoinbase.py'
'wallet_shieldcoinbase_sprout.py'
'wallet_shieldcoinbase_sapling.py'
'wallet_listreceived.py'
'wallet_mergetoaddress.py'
@ -65,14 +63,11 @@ testScripts=(
'decodescript.py'
'blockchain.py'
'disablewallet.py'
'zcjoinsplit.py'
'zcjoinsplitdoublespend.py'
'ivk_import_export.py'
'zkey_import_export.py'
'getblocktemplate.py'
'bip65-cltv-p2p.py'
'bipdersig-p2p.py'
'p2p_nu_peer_management.py'
'rewind_index.py'
'p2p_txexpiry_dos.py'
'p2p_node_bloom.py'

192
qa/rpc-tests/p2p_nu_peer_management.py

@ -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()

215
qa/rpc-tests/paymentdisclosure.py

@ -1,215 +0,0 @@
#!/usr/bin/env python2
# Copyright (c) 2017 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException
from test_framework.util import assert_equal, initialize_chain_clean, \
start_node, connect_nodes_bi, wait_and_assert_operationid_status
from decimal import Decimal
class PaymentDisclosureTest (BitcoinTestFramework):
def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)
def setup_network(self, split=False):
args = ['-debug=zrpcunsafe,paymentdisclosure', '-experimentalfeatures', '-paymentdisclosure', '-txindex=1']
self.nodes = []
self.nodes.append(start_node(0, self.options.tmpdir, args))
self.nodes.append(start_node(1, self.options.tmpdir, args))
# node 2 does not enable payment disclosure
args2 = ['-debug=zrpcunsafe', '-experimentalfeatures', '-txindex=1']
self.nodes.append(start_node(2, self.options.tmpdir, args2))
connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
self.is_network_split=False
self.sync_all()
def run_test (self):
print "Mining blocks..."
self.nodes[0].generate(4)
walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['immature_balance'], 40)
assert_equal(walletinfo['balance'], 0)
self.sync_all()
self.nodes[2].generate(3)
self.sync_all()
self.nodes[1].generate(101)
self.sync_all()
assert_equal(self.nodes[0].getbalance(), 40)
assert_equal(self.nodes[1].getbalance(), 10)
assert_equal(self.nodes[2].getbalance(), 30)
mytaddr = self.nodes[0].getnewaddress()
myzaddr = self.nodes[0].z_getnewaddress()
# Check that Node 2 has payment disclosure disabled.
try:
self.nodes[2].z_getpaymentdisclosure("invalidtxid", 0, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("payment disclosure is disabled" in errorString)
# Check that Node 0 returns an error for an unknown txid
try:
self.nodes[0].z_getpaymentdisclosure("invalidtxid", 0, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("No information available about transaction" in errorString)
# Shield coinbase utxos from node 0 of value 40, standard fee of 0.00010000
recipients = [{"address":myzaddr, "amount":Decimal('40.0')-Decimal('0.0001')}]
myopid = self.nodes[0].z_sendmany(mytaddr, recipients)
txid = wait_and_assert_operationid_status(self.nodes[0], myopid)
# Check the tx has joinsplits
assert( len(self.nodes[0].getrawtransaction("" + txid, 1)["vjoinsplit"]) > 0 )
# Sync mempools
self.sync_all()
# Confirm that you can't create a payment disclosure for an unconfirmed tx
try:
self.nodes[0].z_getpaymentdisclosure(txid, 0, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Transaction has not been confirmed yet" in errorString)
try:
self.nodes[1].z_getpaymentdisclosure(txid, 0, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Transaction has not been confirmed yet" in errorString)
# Mine tx
self.nodes[0].generate(1)
self.sync_all()
# Confirm that Node 1 cannot create a payment disclosure for a transaction which does not impact its wallet
try:
self.nodes[1].z_getpaymentdisclosure(txid, 0, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Transaction does not belong to the wallet" in errorString)
# Check that an invalid joinsplit index is rejected
try:
self.nodes[0].z_getpaymentdisclosure(txid, 1, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Invalid js_index" in errorString)
try:
self.nodes[0].z_getpaymentdisclosure(txid, -1, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Invalid js_index" in errorString)
# Check that an invalid output index is rejected
try:
self.nodes[0].z_getpaymentdisclosure(txid, 0, 2)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Invalid output_index" in errorString)
try:
self.nodes[0].z_getpaymentdisclosure(txid, 0, -1)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Invalid output_index" in errorString)
# Ask Node 0 to create and validate a payment disclosure for output 0
message = "Here is proof of my payment!"
pd = self.nodes[0].z_getpaymentdisclosure(txid, 0, 0, message)
result = self.nodes[0].z_validatepaymentdisclosure(pd)
assert(result["valid"])
output_value_sum = Decimal(result["value"])
# Ask Node 1 to confirm the payment disclosure is valid
result = self.nodes[1].z_validatepaymentdisclosure(pd)
assert(result["valid"])
assert_equal(result["message"], message)
assert_equal(result["value"], output_value_sum)
# Confirm that payment disclosure begins with prefix zpd:
assert(pd.startswith("zpd:"))
# Confirm that payment disclosure without prefix zpd: fails validation
try:
self.nodes[1].z_validatepaymentdisclosure(pd[4:])
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("payment disclosure prefix not found" in errorString)
# Check that total value of output index 0 and index 1 should equal shielding amount of 40 less standard fee.
pd = self.nodes[0].z_getpaymentdisclosure(txid, 0, 1)
result = self.nodes[0].z_validatepaymentdisclosure(pd)
output_value_sum += Decimal(result["value"])
assert_equal(output_value_sum, Decimal('39.99990000'))
# Create a z->z transaction, sending shielded funds from node 0 to node 1
node1zaddr = self.nodes[1].z_getnewaddress()
recipients = [{"address":node1zaddr, "amount":Decimal('1')}]
myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
txid = wait_and_assert_operationid_status(self.nodes[0], myopid)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
# Confirm that Node 0 can create a valid payment disclosure
pd = self.nodes[0].z_getpaymentdisclosure(txid, 0, 0, "a message of your choice")
result = self.nodes[0].z_validatepaymentdisclosure(pd)
assert(result["valid"])
# Confirm that Node 1, even as recipient of shielded funds, cannot create a payment disclosure
# as the transaction was created by Node 0 and Node 1's payment disclosure database does not
# contain the necessary data to do so, where the data would only have been available on Node 0
# when executing z_shieldcoinbase.
try:
self.nodes[1].z_getpaymentdisclosure(txid, 0, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Could not find payment disclosure info for the given joinsplit output" in errorString)
# Payment disclosures cannot be created for transparent transactions.
txid = self.nodes[2].sendtoaddress(mytaddr, 1.0)
self.sync_all()
# No matter the type of transaction, if it has not been confirmed, it is ignored.
try:
self.nodes[0].z_getpaymentdisclosure(txid, 0, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Transaction has not been confirmed yet" in errorString)
self.nodes[0].generate(1)
self.sync_all()
# Confirm that a payment disclosure can only be generated for a shielded transaction.
try:
self.nodes[0].z_getpaymentdisclosure(txid, 0, 0)
assert(False)
except JSONRPCException as e:
errorString = e.error['message']
assert("Transaction is not a shielded transaction" in errorString)
if __name__ == '__main__':
PaymentDisclosureTest().main()

3
qa/rpc-tests/regtest_signrawtransaction.py

@ -1,4 +1,5 @@
#!/usr/bin/env python2
# Copyright (c) 2019-2020 The Hush developers
# 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.
@ -18,7 +19,7 @@ class RegtestSignrawtransactionTest (BitcoinTestFramework):
self.nodes[0].generate(1)
self.sync_all()
taddr = self.nodes[1].getnewaddress()
zaddr1 = self.nodes[1].z_getnewaddress('sprout')
zaddr1 = self.nodes[1].z_getnewaddress('sapling')
self.nodes[0].sendtoaddress(taddr, 2.0)
self.nodes[0].generate(1)

81
qa/rpc-tests/wallet_listnotes.py

@ -1,4 +1,5 @@
#!/usr/bin/env python2
# Copyright (c) 2019-2020 The Hush developers
# 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.
@ -20,89 +21,9 @@ class WalletListNotes(BitcoinTestFramework):
def run_test(self):
# Current height = 200 -> Sprout
assert_equal(200, self.nodes[0].getblockcount())
sproutzaddr = self.nodes[0].z_getnewaddress('sprout')
# test that we can create a sapling zaddr before sapling activates
saplingzaddr = self.nodes[0].z_getnewaddress('sapling')
# we've got lots of coinbase (taddr) but no shielded funds yet
assert_equal(0, Decimal(self.nodes[0].z_gettotalbalance()['private']))
# Set current height to 201 -> Sprout
self.nodes[0].generate(1)
self.sync_all()
assert_equal(201, self.nodes[0].getblockcount())
mining_addr = self.nodes[0].listunspent()[0]['address']
# Shield coinbase funds (must be a multiple of 10, no change allowed pre-sapling)
receive_amount_10 = Decimal('10.0') - Decimal('0.0001')
recipients = [{"address":sproutzaddr, "amount":receive_amount_10}]
myopid = self.nodes[0].z_sendmany(mining_addr, recipients)
txid_1 = wait_and_assert_operationid_status(self.nodes[0], myopid)
self.sync_all()
# No funds (with (default) one or more confirmations) in sproutzaddr yet
assert_equal(0, len(self.nodes[0].z_listunspent()))
assert_equal(0, len(self.nodes[0].z_listunspent(1)))
# no private balance because no confirmations yet
assert_equal(0, Decimal(self.nodes[0].z_gettotalbalance()['private']))
# list private unspent, this time allowing 0 confirmations
unspent_cb = self.nodes[0].z_listunspent(0)
assert_equal(1, len(unspent_cb))
assert_equal(False, unspent_cb[0]['change'])
assert_equal(txid_1, unspent_cb[0]['txid'])
assert_equal(True, unspent_cb[0]['spendable'])
assert_equal(sproutzaddr, unspent_cb[0]['address'])
assert_equal(receive_amount_10, unspent_cb[0]['amount'])
# list unspent, filtering by address, should produce same result
unspent_cb_filter = self.nodes[0].z_listunspent(0, 9999, False, [sproutzaddr])
assert_equal(unspent_cb, unspent_cb_filter)
# Generate a block to confirm shield coinbase tx
self.nodes[0].generate(1)
self.sync_all()
# Current height = 202 -> Overwinter. Default address type remains Sprout
assert_equal(202, self.nodes[0].getblockcount())
# Send 1.0 (actually 0.9999) from sproutzaddr to a new zaddr
sproutzaddr2 = self.nodes[0].z_getnewaddress()
receive_amount_1 = Decimal('1.0') - Decimal('0.0001')
change_amount_9 = receive_amount_10 - Decimal('1.0')
assert_equal('sprout', self.nodes[0].z_validateaddress(sproutzaddr2)['type'])
recipients = [{"address": sproutzaddr2, "amount":receive_amount_1}]
myopid = self.nodes[0].z_sendmany(sproutzaddr, recipients)
txid_2 = wait_and_assert_operationid_status(self.nodes[0], myopid)
self.sync_all()
# list unspent, allowing 0conf txs
unspent_tx = self.nodes[0].z_listunspent(0)
assert_equal(len(unspent_tx), 2)
# sort low-to-high by amount (order of returned entries is not guaranteed)
unspent_tx = sorted(unspent_tx, key=lambda k: k['amount'])
assert_equal(False, unspent_tx[0]['change'])
assert_equal(txid_2, unspent_tx[0]['txid'])
assert_equal(True, unspent_tx[0]['spendable'])
assert_equal(sproutzaddr2, unspent_tx[0]['address'])
assert_equal(receive_amount_1, unspent_tx[0]['amount'])
assert_equal(True, unspent_tx[1]['change'])
assert_equal(txid_2, unspent_tx[1]['txid'])
assert_equal(True, unspent_tx[1]['spendable'])
assert_equal(sproutzaddr, unspent_tx[1]['address'])
assert_equal(change_amount_9, unspent_tx[1]['amount'])
unspent_tx_filter = self.nodes[0].z_listunspent(0, 9999, False, [sproutzaddr2])
assert_equal(1, len(unspent_tx_filter))
assert_equal(unspent_tx[0], unspent_tx_filter[0])
unspent_tx_filter = self.nodes[0].z_listunspent(0, 9999, False, [sproutzaddr])
assert_equal(1, len(unspent_tx_filter))
assert_equal(unspent_tx[1], unspent_tx_filter[0])
# Set current height to 204 -> Sapling
self.nodes[0].generate(2)

59
qa/rpc-tests/zcjoinsplit.py

@ -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()

182
qa/rpc-tests/zcjoinsplitdoublespend.py

@ -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()

25
src/Makefile.am

@ -1,3 +1,5 @@
# Copyright 2019-2020 The Hush developers
DIST_SUBDIRS = secp256k1 univalue cryptoconditions
AM_LDFLAGS = $(PTHREAD_CFLAGS) $(LIBTOOL_LDFLAGS) $(SAN_LDFLAGS) $(HARDENED_LDFLAGS)
@ -181,14 +183,11 @@ BITCOIN_CORE_H = \
netbase.h \
notaries_staked.h \
noui.h \
paymentdisclosure.h \
paymentdisclosuredb.h \
policy/fees.h \
pow.h \
prevector.h \
primitives/block.h \
primitives/transaction.h \
primitives/nonce.h \
protocol.h \
pubkey.h \
random.h \
@ -303,8 +302,6 @@ libbitcoin_server_a_SOURCES = \
notaries_staked.cpp \
noui.cpp \
notarisationdb.cpp \
paymentdisclosure.cpp \
paymentdisclosuredb.cpp \
policy/fees.cpp \
pow.cpp \
rest.cpp \
@ -343,12 +340,10 @@ libbitcoin_proton_a_SOURCES = \
amqp/amqppublishnotifier.cpp
endif
# wallet: zcashd, but only linked when wallet enabled
# wallet: komodod, but only linked when wallet enabled
libbitcoin_wallet_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
libbitcoin_wallet_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libbitcoin_wallet_a_SOURCES = \
utiltest.cpp \
utiltest.h \
zcbenchmarks.cpp \
zcbenchmarks.h \
wallet/asyncrpcoperation_mergetoaddress.cpp \
@ -357,10 +352,8 @@ libbitcoin_wallet_a_SOURCES = \
wallet/asyncrpcoperation_shieldcoinbase.cpp \
wallet/crypter.cpp \
wallet/db.cpp \
paymentdisclosure.cpp \
paymentdisclosuredb.cpp \
zcash/Note.cpp \
transaction_builder.cpp \
wallet/rpcdisclosure.cpp \
wallet/rpcdump.cpp \
cc/CCtokens.cpp \
cc/CCassetsCore.cpp \
@ -436,7 +429,6 @@ libbitcoin_common_a_SOURCES = \
metrics.cpp \
primitives/block.cpp \
primitives/transaction.cpp \
primitives/nonce.cpp \
protocol.cpp \
pubkey.cpp \
scheduler.cpp \
@ -496,7 +488,7 @@ libbitcoin_cli_a_SOURCES = \
nodist_libbitcoin_util_a_SOURCES = $(srcdir)/obj/build.h
#
# bitcoind binary #
# komodod binary #
komodod_SOURCES = bitcoind.cpp
komodod_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
komodod_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
@ -555,7 +547,7 @@ if TARGET_DARWIN
komodod_LDFLAGS += -static-libgcc
endif
# bitcoin-cli binary #
# komodo-cli binary #
komodo_cli_SOURCES = bitcoin-cli.cpp
komodo_cli_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(EVENT_CFLAGS)
komodo_cli_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
@ -603,7 +595,7 @@ wallet_utility_LDADD = \
$(LIBCRYPTOCONDITIONS)
endif
# zcash-tx binary #
# komodo-tx binary #
komodo_tx_SOURCES = komodo-tx.cpp
komodo_tx_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
komodo_tx_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
@ -613,7 +605,6 @@ if TARGET_WINDOWS
komodo_tx_SOURCES += bitcoin-tx-res.rc
endif
# FIXME: Is libzcash needed for hush-tx ?
komodo_tx_LDADD = \
$(LIBUNIVALUE) \
$(LIBBITCOIN_COMMON) \
@ -625,7 +616,6 @@ komodo_tx_LDADD = \
$(LIBCRYPTOCONDITIONS)
komodo_tx_LDADD += $(BOOST_LIBS) $(CRYPTO_LIBS)
#
# zcash protocol primitives #
libzcash_a_SOURCES = \
@ -661,7 +651,6 @@ libzcashconsensus_la_SOURCES = \
crypto/sha512.cpp \
hash.cpp \
primitives/transaction.cpp \
primitives/nonce.cpp \
pubkey.cpp \
script/zcashconsensus.cpp \
script/interpreter.cpp \

1
src/amount.h

@ -1,5 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core 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.

5
src/arith_uint256.cpp

@ -193,9 +193,10 @@ unsigned int base_uint<BITS>::bits() const
{
for (int pos = WIDTH - 1; pos >= 0; pos--) {
if (pn[pos]) {
for (int bits = 31; bits > 0; bits--) {
if (pn[pos] & 1 << bits)
for (size_t bits = 31; bits > 0; bits--) {
if (pn[pos] & (1U << bits)) {
return 32 * pos + bits + 1;
}
}
return 32 * pos + 1;
}

1
src/cc/cclib.cpp

@ -1,3 +1,4 @@
// Copyright © 2019-2020 The Hush Developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *

5
src/cc/dapps/Makefile

@ -1,6 +1,10 @@
# Copyright 2020 The Hush Developers
# just type make to compile all dapps
all: zmigrate oraclefeed
subatomic:
$(CC) subatomic.c -o subatomic -lm
zmigrate:
$(CC) zmigrate.c -o zmigrate -lm
@ -9,3 +13,4 @@ oraclefeed:
clean:
rm zmigrate oraclefeed

1599
src/cc/dapps/dappinc.h

File diff suppressed because it is too large

1429
src/cc/dapps/subatomic.c

File diff suppressed because it is too large

27
src/cc/dapps/subatomic.json

@ -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" }
]
}

2
src/checkpoints.cpp

@ -74,7 +74,7 @@ namespace Checkpoints {
fWorkAfter = nExpensiveAfter*fSigcheckVerificationFactor;
}
return fWorkBefore / (fWorkBefore + fWorkAfter);
return std::min(fWorkBefore / (fWorkBefore + fWorkAfter), 1.0);
}
int GetTotalBlocksEstimate(const CChainParams::CCheckpointData& data)

4
src/clientversion.h

@ -33,8 +33,8 @@
//! These need to be macros, as clientversion.cpp's and bitcoin*-res.rc's voodoo requires it
// Must be kept in sync with configure.ac !
#define CLIENT_VERSION_MAJOR 3
#define CLIENT_VERSION_MINOR 3
#define CLIENT_VERSION_REVISION 2
#define CLIENT_VERSION_MINOR 4
#define CLIENT_VERSION_REVISION 0
#define CLIENT_VERSION_BUILD 50
//! Set to true for release, false for prerelease or test build

64
src/coins.cpp

@ -1,5 +1,5 @@
// Copyright (c) 2012-2014 The Bitcoin Core developers
// Copyright (c) 2019 The Hush 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.
@ -65,7 +65,6 @@ bool CCoins::Spend(uint32_t nPos)
Cleanup();
return true;
}
bool CCoinsView::GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const { return false; }
bool CCoinsView::GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const { return false; }
bool CCoinsView::GetNullifier(const uint256 &nullifier, ShieldedType type) const { return false; }
bool CCoinsView::GetCoins(const uint256 &txid, CCoins &coins) const { return false; }
@ -85,7 +84,6 @@ bool CCoinsView::GetStats(CCoinsStats &stats) const { return false; }
CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { }
bool CCoinsViewBacked::GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const { return base->GetSproutAnchorAt(rt, tree); }
bool CCoinsViewBacked::GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const { return base->GetSaplingAnchorAt(rt, tree); }
bool CCoinsViewBacked::GetNullifier(const uint256 &nullifier, ShieldedType type) const { return base->GetNullifier(nullifier, type); }
bool CCoinsViewBacked::GetCoins(const uint256 &txid, CCoins &coins) const { return base->GetCoins(txid, coins); }
@ -139,30 +137,6 @@ CCoinsMap::const_iterator CCoinsViewCache::FetchCoins(const uint256 &txid) const
return ret;
}
bool CCoinsViewCache::GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
CAnchorsSproutMap::const_iterator it = cacheSproutAnchors.find(rt);
if (it != cacheSproutAnchors.end()) {
if (it->second.entered) {
tree = it->second.tree;
return true;
} else {
return false;
}
}
if (!base->GetSproutAnchorAt(rt, tree)) {
return false;
}
CAnchorsSproutMap::iterator ret = cacheSproutAnchors.insert(std::make_pair(rt, CAnchorsSproutCacheEntry())).first;
ret->second.entered = true;
ret->second.tree = tree;
cachedCoinsUsage += ret->second.tree.DynamicMemoryUsage();
return true;
}
bool CCoinsViewCache::GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
CAnchorsSaplingMap::const_iterator it = cacheSaplingAnchors.find(rt);
if (it != cacheSaplingAnchors.end()) {
@ -271,7 +245,6 @@ void CCoinsViewCache::BringBestAnchorIntoCache(
SproutMerkleTree &tree
)
{
assert(GetSproutAnchorAt(currentRoot, tree));
}
template<>
@ -550,9 +523,9 @@ bool CCoinsViewCache::BatchWrite(CCoinsMap &mapCoins,
bool CCoinsViewCache::Flush() {
bool fOk = base->BatchWrite(cacheCoins, hashBlock, hashSproutAnchor, hashSaplingAnchor, cacheSproutAnchors, cacheSaplingAnchors, cacheSproutNullifiers, cacheSaplingNullifiers);
cacheCoins.clear();
cacheSproutAnchors.clear();
//cacheSproutAnchors.clear();
cacheSaplingAnchors.clear();
cacheSproutNullifiers.clear();
//cacheSproutNullifiers.clear();
cacheSaplingNullifiers.clear();
cachedCoinsUsage = 0;
return fOk;
@ -624,37 +597,8 @@ CAmount CCoinsViewCache::GetValueIn(int32_t nHeight,int64_t *interestp,const CTr
}
bool CCoinsViewCache::HaveJoinSplitRequirements(const CTransaction& tx) const
bool CCoinsViewCache::HaveShieldedRequirements(const CTransaction& tx) const
{
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit)
{
BOOST_FOREACH(const uint256& nullifier, joinsplit.nullifiers)
{
if (GetNullifier(nullifier, SPROUT)) {
// If the nullifier is set, this transaction
// double-spends!
return false;
}
}
SproutMerkleTree tree;
auto it = intermediates.find(joinsplit.anchor);
if (it != intermediates.end()) {
tree = it->second;
} else if (!GetSproutAnchorAt(joinsplit.anchor, tree)) {
return false;
}
BOOST_FOREACH(const uint256& commitment, joinsplit.commitments)
{
tree.append(commitment);
}
intermediates.insert(std::make_pair(tree.root(), tree));
}
for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
if (GetNullifier(spendDescription.nullifier, SAPLING)) { // Prevent double spends
LogPrintf("%s: sapling nullifier %s exists, preventing double spend\n", __FUNCTION__, spendDescription.nullifier.GetHex().c_str());

11
src/coins.h

@ -372,9 +372,6 @@ struct CCoinsStats
class CCoinsView
{
public:
//! Retrieve the tree (Sprout) at a particular anchored root in the chain
virtual bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const;
//! Retrieve the tree (Sapling) at a particular anchored root in the chain
virtual bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const;
@ -421,7 +418,6 @@ protected:
public:
CCoinsViewBacked(CCoinsView *viewIn);
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const;
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const;
bool GetNullifier(const uint256 &nullifier, ShieldedType type) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const;
@ -493,7 +489,6 @@ public:
CNullifiersMap getNullifiers();
// Standard CCoinsView methods
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const;
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const;
bool GetNullifier(const uint256 &nullifier, ShieldedType type) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const;
@ -550,7 +545,7 @@ public:
size_t DynamicMemoryUsage() const;
/**
* Amount of bitcoins coming in to a transaction
* Amount of HUSH coming in to a transaction
* Note that lightweight clients may not know anything besides the hash of previous transactions,
* so may not be able to calculate this.
*
@ -562,8 +557,8 @@ public:
//! Check whether all prevouts of the transaction are present in the UTXO set represented by this view
bool HaveInputs(const CTransaction& tx) const;
//! Check whether all joinsplit requirements (anchors/nullifiers) are satisfied
bool HaveJoinSplitRequirements(const CTransaction& tx) const;
//! Check whether all shielded requirements (anchors/nullifiers) are satisfied
bool HaveShieldedRequirements(const CTransaction& tx) const;
//! Return priority of tx at height nHeight
double GetPriority(const CTransaction &tx, int nHeight) const;

1
src/consensus/upgrades.cpp

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
// 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.

6
src/cryptoconditions/src/anon.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -53,11 +54,8 @@ static void anonToJSON(const CC *cond, cJSON *params) {
}
static unsigned char *anonFingerprint(const CC *cond) {
unsigned char *out = calloc(1, 32);
//fprintf(stderr,"anon fingerprint %p %p\n",out,cond->fingerprint);
static void anonFingerprint(const CC *cond, uint8_t *out) {
memcpy(out, cond->fingerprint, 32);
return out;
}

19
src/cryptoconditions/src/cryptoconditions.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -65,8 +66,8 @@ void appendUriSubtypes(uint32_t mask, unsigned char *buf) {
char *cc_conditionUri(const CC *cond) {
unsigned char *fp = cond->type->fingerprint(cond);
if (!fp) return NULL;
unsigned char *fp = calloc(1, 32);
cond->type->fingerprint(cond, fp);
unsigned char *encoded = base64_encode(fp, 32);
@ -118,13 +119,13 @@ uint32_t fromAsnSubtypes(const ConditionTypes_t types) {
size_t cc_conditionBinary(const CC *cond, unsigned char *buf) {
Condition_t *asn = calloc(1, sizeof(Condition_t));
asnCondition(cond, asn);
size_t out = 0;
asn_enc_rval_t rc = der_encode_to_buffer(&asn_DEF_Condition, asn, buf, 1000);
if (rc.encoded == -1) {
fprintf(stderr, "CONDITION NOT ENCODED\n");
return 0;
}
if (rc.encoded == -1) goto end;
out = rc.encoded;
end:
ASN_STRUCT_FREE(asn_DEF_Condition, asn);
return rc.encoded;
return out;
}
@ -146,10 +147,12 @@ void asnCondition(const CC *cond, Condition_t *asn) {
// This may look a little weird - we dont have a reference here to the correct
// union choice for the condition type, so we just assign everything to the threshold
// type. This works out nicely since the union choices have the same binary interface.
CompoundSha256Condition_t *choice = &asn->choice.thresholdSha256;
choice->cost = cc_getCost(cond);
choice->fingerprint.buf = cond->type->fingerprint(cond);
choice->fingerprint.size = 32;
choice->fingerprint.buf = calloc(1, 32);
cond->type->fingerprint(cond, choice->fingerprint.buf);
choice->subtypes = asnSubtypes(cond->type->getSubtypes(cond));
}

6
src/cryptoconditions/src/ed25519.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -25,11 +26,10 @@
struct CCType CC_Ed25519Type;
static unsigned char *ed25519Fingerprint(const CC *cond) {
static void ed25519Fingerprint(const CC *cond, uint8_t *out) {
Ed25519FingerprintContents_t *fp = calloc(1, sizeof(Ed25519FingerprintContents_t));
//fprintf(stderr,"ed25519 fingerprint %p %p\n",fp,cond->publicKey);
OCTET_STRING_fromBuf(&fp->publicKey, cond->publicKey, 32);
return hashFingerprintContents(&asn_DEF_Ed25519FingerprintContents, fp);
hashFingerprintContents(&asn_DEF_Ed25519FingerprintContents, fp, out);
}

10
src/cryptoconditions/src/eval.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -25,11 +26,8 @@
struct CCType CC_EvalType;
static unsigned char *evalFingerprint(const CC *cond) {
unsigned char *hash = calloc(1, 32);
//fprintf(stderr,"evalfingerprint %p %p\n",hash,cond->code);
sha256(cond->code, cond->codeLength, hash);
return hash;
static void evalFingerprint(const CC *cond, uint8_t *out) {
sha256(cond->code, cond->codeLength, out);
}
@ -105,7 +103,7 @@ static uint32_t evalSubtypes(const CC *cond) {
*/
int jsonVerifyEval(CC *cond, void *context) {
if (cond->codeLength == 5 && 0 == memcmp(cond->code, "TEST", 4)) {
return cond->code[5];
return cond->code[4];
}
fprintf(stderr, "Cannot verify eval; user function unknown\n");
return 0;

2
src/cryptoconditions/src/include/secp256k1/src/ecmult_const.h

@ -36,4 +36,4 @@ static void secp256k1_ecmult_const(secp256k1_gej *r, const secp256k1_ge *a, cons
#endif
#endif

5
src/cryptoconditions/src/internal.h

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -41,7 +42,7 @@ typedef struct CCType {
char name[100];
Condition_PR asnType;
int (*visitChildren)(CC *cond, CCVisitor visitor);
unsigned char *(*fingerprint)(const CC *cond);
void (*fingerprint)(const CC *cond, uint8_t *fp);
unsigned long (*getCost)(const CC *cond);
uint32_t (*getSubtypes)(const CC *cond);
CC *(*fromJSON)(const cJSON *params, char *err);
@ -77,7 +78,7 @@ struct CCType *getTypeByAsnEnum(Condition_PR present);
*/
unsigned char *base64_encode(const unsigned char *data, size_t input_length);
unsigned char *base64_decode(const unsigned char *data_, size_t *output_length);
unsigned char *hashFingerprintContents(asn_TYPE_descriptor_t *asnType, void *fp);
void hashFingerprintContents(asn_TYPE_descriptor_t *asnType, void *fp, uint8_t* out);
void dumpStr(unsigned char *str, size_t len);
int checkString(const cJSON *value, char *key, char *err);
int checkDecodeBase64(const cJSON *value, char *key, char *err, unsigned char **data, size_t *size);

1
src/cryptoconditions/src/json_rpc.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *

8
src/cryptoconditions/src/prefix.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -37,13 +38,12 @@ static int prefixVisitChildren(CC *cond, CCVisitor visitor) {
}
static unsigned char *prefixFingerprint(const CC *cond) {
static void prefixFingerprint(const CC *cond, uint8_t *out) {
PrefixFingerprintContents_t *fp = calloc(1, sizeof(PrefixFingerprintContents_t));
//fprintf(stderr,"prefixfinger %p %p\n",fp,cond->prefix);
asnCondition(cond->subcondition, &fp->subcondition); // TODO: check asnCondition for safety
asnCondition(cond->subcondition, &fp->subcondition);
fp->maxMessageLength = cond->maxMessageLength;
OCTET_STRING_fromBuf(&fp->prefix, cond->prefix, cond->prefixLength);
return hashFingerprintContents(&asn_DEF_PrefixFingerprintContents, fp);
hashFingerprintContents(&asn_DEF_PrefixFingerprintContents, fp, out);
}

8
src/cryptoconditions/src/preimage.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -44,11 +45,8 @@ static unsigned long preimageCost(const CC *cond) {
}
static unsigned char *preimageFingerprint(const CC *cond) {
unsigned char *hash = calloc(1, 32);
//fprintf(stderr,"preimage %p %p\n",hash,cond->preimage);
sha256(cond->preimage, cond->preimageLength, hash);
return hash;
static void preimageFingerprint(const CC *cond, uint8_t *out) {
sha256(cond->preimage, cond->preimageLength, out);
}

6
src/cryptoconditions/src/secp256k1.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -88,11 +89,10 @@ void initVerify() {
}
static unsigned char *secp256k1Fingerprint(const CC *cond) {
static void secp256k1Fingerprint(const CC *cond, uint8_t *out) {
Secp256k1FingerprintContents_t *fp = calloc(1, sizeof(Secp256k1FingerprintContents_t));
//fprintf(stderr,"secpfinger %p %p size %d vs %d\n",fp,cond->publicKey,(int32_t)sizeof(Secp256k1FingerprintContents_t),(int32_t)SECP256K1_PK_SIZE);
OCTET_STRING_fromBuf(&fp->publicKey, cond->publicKey, SECP256K1_PK_SIZE);
return hashFingerprintContents(&asn_DEF_Secp256k1FingerprintContents, fp);
hashFingerprintContents(&asn_DEF_Secp256k1FingerprintContents, fp, out);
}

7
src/cryptoconditions/src/threshold.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -94,17 +95,15 @@ static int cmpConditionBin(const void *a, const void *b) {
}
static unsigned char *thresholdFingerprint(const CC *cond) {
/* Create fingerprint */
static void thresholdFingerprint(const CC *cond, uint8_t *out) {
ThresholdFingerprintContents_t *fp = calloc(1, sizeof(ThresholdFingerprintContents_t));
//fprintf(stderr,"thresholdfinger %p\n",fp);
fp->threshold = cond->threshold;
for (int i=0; i<cond->size; i++) {
Condition_t *asnCond = asnConditionNew(cond->subconditions[i]);
asn_set_add(&fp->subconditions2, asnCond);
}
qsort(fp->subconditions2.list.array, cond->size, sizeof(Condition_t*), cmpConditionBin);
return hashFingerprintContents(&asn_DEF_ThresholdFingerprintContents, fp);
hashFingerprintContents(&asn_DEF_ThresholdFingerprintContents, fp, out);
}

11
src/cryptoconditions/src/utils.c

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -210,17 +211,15 @@ void jsonAddBase64(cJSON *params, char *key, unsigned char *bin, size_t size) {
}
unsigned char *hashFingerprintContents(asn_TYPE_descriptor_t *asnType, void *fp) {
void hashFingerprintContents(asn_TYPE_descriptor_t *asnType, void *fp, uint8_t *out) {
unsigned char buf[BUF_SIZE];
asn_enc_rval_t rc = der_encode_to_buffer(asnType, fp, buf, BUF_SIZE);
ASN_STRUCT_FREE(*asnType, fp);
if (rc.encoded < 1) {
fprintf(stderr, "Encoding fingerprint failed\n");
return 0;
return;
}
unsigned char *hash = calloc(1,32);
sha256(buf, rc.encoded, hash);
return hash;
sha256(buf, rc.encoded, out);
}
@ -301,5 +300,3 @@ int jsonGetHexOptional(const cJSON *params, char *key, char *err, unsigned char
}
return checkDecodeHex(item, key, err, data, size);
}

21
src/cryptoconditions/tests/test_failure_modes.py

@ -82,4 +82,25 @@ def test_malleability_checked():
assert not cc_rfb(b'\xa2\x13\xa0\x0f\xa0\x06\x80\x04abcd\xa0\x05\x80\x03abc\xa1\x00')
def test_large_threshold():
conds = [{
'type': "secp256k1-sha-256",
"publicKey": "02D5D969305535AC29A77079C11D4F0DD40661CF96E04E974A5E8D7E374EE225AA"
}]
for i in range(250):
conds.append({
"type": "eval-sha-256",
"code": "VEVTVAE"
})
r = jsonRPC("encodeCondition", {
"type": "threshold-sha-256",
"subfulfillments": conds,
"threshold": 251
})
assert 'error' not in r, r
so.cc_conditionUri.restype = ctypes.c_char_p

585
src/gtest/test_joinsplit.cpp

@ -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(&note, &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);
}

172
src/gtest/test_keystore.cpp

@ -95,101 +95,6 @@ TEST(keystore_tests, sapling_keys) {
}
}
TEST(keystore_tests, store_and_retrieve_spending_key) {
CBasicKeyStore keyStore;
libzcash::SproutSpendingKey skOut;
std::set<libzcash::SproutPaymentAddress> addrs;
keyStore.GetSproutPaymentAddresses(addrs);
EXPECT_EQ(0, addrs.size());
auto sk = libzcash::SproutSpendingKey::random();
auto addr = sk.address();
// Sanity-check: we can't get a key we haven't added
EXPECT_FALSE(keyStore.HaveSproutSpendingKey(addr));
EXPECT_FALSE(keyStore.GetSproutSpendingKey(addr, skOut));
keyStore.AddSproutSpendingKey(sk);
EXPECT_TRUE(keyStore.HaveSproutSpendingKey(addr));
EXPECT_TRUE(keyStore.GetSproutSpendingKey(addr, skOut));
EXPECT_EQ(sk, skOut);
keyStore.GetSproutPaymentAddresses(addrs);
EXPECT_EQ(1, addrs.size());
EXPECT_EQ(1, addrs.count(addr));
}
TEST(keystore_tests, store_and_retrieve_note_decryptor) {
CBasicKeyStore keyStore;
ZCNoteDecryption decOut;
auto sk = libzcash::SproutSpendingKey::random();
auto addr = sk.address();
EXPECT_FALSE(keyStore.GetNoteDecryptor(addr, decOut));
keyStore.AddSproutSpendingKey(sk);
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut));
EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut);
}
TEST(keystore_tests, StoreAndRetrieveViewingKey) {
CBasicKeyStore keyStore;
libzcash::SproutViewingKey vkOut;
libzcash::SproutSpendingKey skOut;
ZCNoteDecryption decOut;
auto sk = libzcash::SproutSpendingKey::random();
auto vk = sk.viewing_key();
auto addr = sk.address();
// Sanity-check: we can't get a viewing key we haven't added
EXPECT_FALSE(keyStore.HaveSproutViewingKey(addr));
EXPECT_FALSE(keyStore.GetSproutViewingKey(addr, vkOut));
// and we shouldn't have a spending key or decryptor either
EXPECT_FALSE(keyStore.HaveSproutSpendingKey(addr));
EXPECT_FALSE(keyStore.GetSproutSpendingKey(addr, skOut));
EXPECT_FALSE(keyStore.GetNoteDecryptor(addr, decOut));
// and we can't find it in our list of addresses
std::set<libzcash::SproutPaymentAddress> addresses;
keyStore.GetSproutPaymentAddresses(addresses);
EXPECT_FALSE(addresses.count(addr));
keyStore.AddSproutViewingKey(vk);
EXPECT_TRUE(keyStore.HaveSproutViewingKey(addr));
EXPECT_TRUE(keyStore.GetSproutViewingKey(addr, vkOut));
EXPECT_EQ(vk, vkOut);
// We should still not have the spending key...
EXPECT_FALSE(keyStore.HaveSproutSpendingKey(addr));
EXPECT_FALSE(keyStore.GetSproutSpendingKey(addr, skOut));
// ... but we should have a decryptor
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut));
EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut);
// ... and we should find it in our list of addresses
addresses.clear();
keyStore.GetSproutPaymentAddresses(addresses);
EXPECT_TRUE(addresses.count(addr));
keyStore.RemoveSproutViewingKey(vk);
EXPECT_FALSE(keyStore.HaveSproutViewingKey(addr));
EXPECT_FALSE(keyStore.GetSproutViewingKey(addr, vkOut));
EXPECT_FALSE(keyStore.HaveSproutSpendingKey(addr));
EXPECT_FALSE(keyStore.GetSproutSpendingKey(addr, skOut));
addresses.clear();
keyStore.GetSproutPaymentAddresses(addresses);
EXPECT_FALSE(addresses.count(addr));
// We still have a decryptor because those are cached in memory
// (and also we only remove viewing keys when adding a spending key)
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut));
EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut);
}
// Sapling
TEST(keystore_tests, StoreAndRetrieveSaplingSpendingKey) {
@ -280,9 +185,6 @@ TEST(keystore_tests, StoreAndRetrieveHDSeedInEncryptedStore) {
// 3) Test adding a new seed to an already-encrypted key store
TestCCryptoKeyStore keyStore2;
// Add a Sprout address so the wallet has something to test when decrypting
ASSERT_TRUE(keyStore2.AddSproutSpendingKey(libzcash::SproutSpendingKey::random()));
ASSERT_TRUE(keyStore2.EncryptKeys(vMasterKey));
ASSERT_TRUE(keyStore2.Unlock(vMasterKey));
@ -296,78 +198,4 @@ TEST(keystore_tests, StoreAndRetrieveHDSeedInEncryptedStore) {
EXPECT_EQ(seed3, seedOut);
}
TEST(keystore_tests, store_and_retrieve_spending_key_in_encrypted_store) {
TestCCryptoKeyStore keyStore;
uint256 r {GetRandHash()};
CKeyingMaterial vMasterKey (r.begin(), r.end());
libzcash::SproutSpendingKey keyOut;
ZCNoteDecryption decOut;
std::set<libzcash::SproutPaymentAddress> addrs;
// 1) Test adding a key to an unencrypted key store, then encrypting it
auto sk = libzcash::SproutSpendingKey::random();
auto addr = sk.address();
EXPECT_FALSE(keyStore.GetNoteDecryptor(addr, decOut));
keyStore.AddSproutSpendingKey(sk);
ASSERT_TRUE(keyStore.HaveSproutSpendingKey(addr));
ASSERT_TRUE(keyStore.GetSproutSpendingKey(addr, keyOut));
ASSERT_EQ(sk, keyOut);
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut));
EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut);
ASSERT_TRUE(keyStore.EncryptKeys(vMasterKey));
ASSERT_TRUE(keyStore.HaveSproutSpendingKey(addr));
ASSERT_FALSE(keyStore.GetSproutSpendingKey(addr, keyOut));
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr, decOut));
EXPECT_EQ(ZCNoteDecryption(sk.receiving_key()), decOut);
// Unlocking with a random key should fail
uint256 r2 {GetRandHash()};
CKeyingMaterial vRandomKey (r2.begin(), r2.end());
EXPECT_FALSE(keyStore.Unlock(vRandomKey));
// Unlocking with a slightly-modified vMasterKey should fail
CKeyingMaterial vModifiedKey (r.begin(), r.end());
vModifiedKey[0] += 1;
EXPECT_FALSE(keyStore.Unlock(vModifiedKey));
// Unlocking with vMasterKey should succeed
ASSERT_TRUE(keyStore.Unlock(vMasterKey));
ASSERT_TRUE(keyStore.GetSproutSpendingKey(addr, keyOut));
ASSERT_EQ(sk, keyOut);
keyStore.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
ASSERT_EQ(1, addrs.count(addr));
// 2) Test adding a spending key to an already-encrypted key store
auto sk2 = libzcash::SproutSpendingKey::random();
auto addr2 = sk2.address();
EXPECT_FALSE(keyStore.GetNoteDecryptor(addr2, decOut));
keyStore.AddSproutSpendingKey(sk2);
ASSERT_TRUE(keyStore.HaveSproutSpendingKey(addr2));
ASSERT_TRUE(keyStore.GetSproutSpendingKey(addr2, keyOut));
ASSERT_EQ(sk2, keyOut);
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr2, decOut));
EXPECT_EQ(ZCNoteDecryption(sk2.receiving_key()), decOut);
ASSERT_TRUE(keyStore.Lock());
ASSERT_TRUE(keyStore.HaveSproutSpendingKey(addr2));
ASSERT_FALSE(keyStore.GetSproutSpendingKey(addr2, keyOut));
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr2, decOut));
EXPECT_EQ(ZCNoteDecryption(sk2.receiving_key()), decOut);
ASSERT_TRUE(keyStore.Unlock(vMasterKey));
ASSERT_TRUE(keyStore.GetSproutSpendingKey(addr2, keyOut));
ASSERT_EQ(sk2, keyOut);
EXPECT_TRUE(keyStore.GetNoteDecryptor(addr2, decOut));
EXPECT_EQ(ZCNoteDecryption(sk2.receiving_key()), decOut);
keyStore.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(2, addrs.size());
ASSERT_EQ(1, addrs.count(addr));
ASSERT_EQ(1, addrs.count(addr2));
}
#endif

211
src/gtest/test_paymentdisclosure.cpp

@ -1,211 +0,0 @@
#include <gtest/gtest.h>
#include "main.h"
#include "utilmoneystr.h"
#include "chainparams.h"
#include "utilstrencodings.h"
#include "zcash/Address.hpp"
#include "wallet/wallet.h"
#include "amount.h"
#include <array>
#include <memory>
#include <string>
#include <set>
#include <vector>
#include <boost/filesystem.hpp>
#include <iostream>
#include "util.h"
#include "paymentdisclosure.h"
#include "paymentdisclosuredb.h"
#include "sodium.h"
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
using namespace std;
/*
To run tests:
./zcash-gtest --gtest_filter="paymentdisclosure.*"
Note: As an experimental feature, writing your own tests may require option flags to be set.
mapArgs["-experimentalfeatures"] = true;
mapArgs["-paymentdisclosure"] = true;
*/
#define NUM_TRIES 10000
#define DUMP_DATABASE_TO_STDOUT false
static boost::uuids::random_generator uuidgen;
static uint256 random_uint256()
{
uint256 ret;
randombytes_buf(ret.begin(), 32);
return ret;
}
// Subclass of PaymentDisclosureDB to add debugging methods
class PaymentDisclosureDBTest : public PaymentDisclosureDB {
public:
PaymentDisclosureDBTest(const boost::filesystem::path& dbPath) : PaymentDisclosureDB(dbPath) {}
void DebugDumpAllStdout() {
ASSERT_NE(db, nullptr);
std::lock_guard<std::mutex> guard(lock_);
// Iterate over each item in the database and print them
leveldb::Iterator* it = db->NewIterator(leveldb::ReadOptions());
for (it->SeekToFirst(); it->Valid(); it->Next()) {
cout << it->key().ToString() << " : ";
// << it->value().ToString() << endl;
try {
std::string strValue = it->value().ToString();
PaymentDisclosureInfo info;
CDataStream ssValue(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION);
ssValue >> info;
cout << info.ToString() << std::endl;
} catch (const std::exception& e) {
cout << e.what() << std::endl;
}
}
if (false == it->status().ok()) {
cerr << "An error was found iterating over the database" << endl;
cerr << it->status().ToString() << endl;
}
delete it;
}
};
// This test creates random payment disclosure blobs and checks that they can be
// 1. inserted and retrieved from a database
// 2. serialized and deserialized without corruption
// Note that the zpd: prefix is not part of the payment disclosure blob itself. It is only
// used as convention to improve the user experience when sharing payment disclosure blobs.
TEST(paymentdisclosure, mainnet) {
SelectParams(CBaseChainParams::MAIN);
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
std::cout << "Test payment disclosure database created in folder: " << pathTemp.string() << std::endl;
PaymentDisclosureDBTest mydb(pathTemp);
for (int i=0; i<NUM_TRIES; i++) {
// Generate an ephemeral keypair for joinsplit sig.
uint256 joinSplitPubKey;
unsigned char buffer[crypto_sign_SECRETKEYBYTES] = {0};
crypto_sign_keypair(joinSplitPubKey.begin(), &buffer[0]);
// First 32 bytes contain private key, second 32 bytes contain public key.
ASSERT_EQ(0, memcmp(joinSplitPubKey.begin(), &buffer[0]+32, 32));
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
uint256 joinSplitPrivKey = uint256(vch);
// Create payment disclosure key and info data to store in test database
size_t js = random_uint256().GetCheapHash() % std::numeric_limits<size_t>::max();
uint8_t n = random_uint256().GetCheapHash() % std::numeric_limits<uint8_t>::max();
PaymentDisclosureKey key { random_uint256(), js, n};
PaymentDisclosureInfo info;
info.esk = random_uint256();
info.joinSplitPrivKey = joinSplitPrivKey;
info.zaddr = libzcash::SproutSpendingKey::random().address();
ASSERT_TRUE(mydb.Put(key, info));
// Retrieve info from test database into new local variable and test it matches
PaymentDisclosureInfo info2;
ASSERT_TRUE(mydb.Get(key, info2));
ASSERT_EQ(info, info2);
// Modify this local variable and confirm it no longer matches
info2.esk = random_uint256();
info2.joinSplitPrivKey = random_uint256();
info2.zaddr = libzcash::SproutSpendingKey::random().address();
ASSERT_NE(info, info2);
// Using the payment info object, let's create a dummy payload
PaymentDisclosurePayload payload;
payload.version = PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL;
payload.esk = info.esk;
payload.txid = key.hash;
payload.js = key.js;
payload.n = key.n;
payload.message = "random-" + boost::uuids::to_string(uuidgen()); // random message
payload.zaddr = info.zaddr;
// Serialize and hash the payload to generate a signature
uint256 dataToBeSigned = SerializeHash(payload, SER_GETHASH, 0);
// Compute the payload signature
unsigned char payloadSig[64];
if (!(crypto_sign_detached(&payloadSig[0], NULL,
dataToBeSigned.begin(), 32,
&buffer[0] // buffer containing both private and public key required
) == 0))
{
throw std::runtime_error("crypto_sign_detached failed");
}
// Sanity check
if (!(crypto_sign_verify_detached(&payloadSig[0],
dataToBeSigned.begin(), 32,
joinSplitPubKey.begin()
) == 0))
{
throw std::runtime_error("crypto_sign_verify_detached failed");
}
// Convert signature buffer to boost array
std::array<unsigned char, 64> arrayPayloadSig;
memcpy(arrayPayloadSig.data(), &payloadSig[0], 64);
// Payment disclosure blob to pass around
PaymentDisclosure pd = {payload, arrayPayloadSig};
// Test payment disclosure constructors
PaymentDisclosure pd2(payload, arrayPayloadSig);
ASSERT_EQ(pd, pd2);
PaymentDisclosure pd3(joinSplitPubKey, key, info, payload.message);
ASSERT_EQ(pd, pd3);
// Verify serialization and deserialization works
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << pd;
std::string ssHexString = HexStr(ss.begin(), ss.end());
PaymentDisclosure pdTmp;
CDataStream ssTmp(ParseHex(ssHexString), SER_NETWORK, PROTOCOL_VERSION);
ssTmp >> pdTmp;
ASSERT_EQ(pd, pdTmp);
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << pdTmp;
std::string ss2HexString = HexStr(ss2.begin(), ss2.end());
ASSERT_EQ(ssHexString, ss2HexString);
// Verify marker
ASSERT_EQ(pd.payload.marker, PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES);
ASSERT_EQ(pdTmp.payload.marker, PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES);
ASSERT_EQ(0, ssHexString.find("706462ff")); // Little endian encoding of PAYMENT_DISCLOSURE_PAYLOAD_MAGIC_BYTES value
// Sanity check
PaymentDisclosure pdDummy;
ASSERT_NE(pd, pdDummy);
}
#if DUMP_DATABASE_TO_STDOUT == true
mydb.DebugDumpAllStdout();
#endif
}

90
src/gtest/test_transaction.cpp

@ -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);
}
}

11
src/httpserver.cpp

@ -11,6 +11,7 @@
#include "rpc/protocol.h" // For HTTP status codes
#include "sync.h"
#include "ui_interface.h"
#include "utilstrencodings.h"
#include <stdio.h>
#include <stdlib.h>
@ -251,21 +252,25 @@ static void http_request_cb(struct evhttp_request* req, void* arg)
{
std::unique_ptr<HTTPRequest> hreq(new HTTPRequest(req));
LogPrint("http", "Received a %s request for %s from %s\n",
RequestMethodString(hreq->GetRequestMethod()), hreq->GetURI(), hreq->GetPeer().ToString());
// Early address-based allow check
if (!ClientAllowed(hreq->GetPeer())) {
LogPrint("http", "HTTP request from %s rejected: Client network is not allowed RPC access\n",
hreq->GetPeer().ToString());
hreq->WriteReply(HTTP_FORBIDDEN);
return;
}
// Early reject unknown HTTP methods
if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) {
LogPrint("http", "HTTP request from %s rejected: Unknown HTTP request method\n",
hreq->GetPeer().ToString());
hreq->WriteReply(HTTP_BADMETHOD);
return;
}
LogPrint("http", "Received a %s request for %s from %s\n",
RequestMethodString(hreq->GetRequestMethod()), SanitizeString(hreq->GetURI(), SAFE_CHARS_URI).substr(0, 100), hreq->GetPeer().ToString());
// Find registered handler for prefix
std::string strURI = hreq->GetURI();
std::string path;

24
src/init.cpp

@ -101,7 +101,7 @@ extern int32_t KOMODO_SNAPSHOT_INTERVAL;
extern void komodo_init(int32_t height);
ZCJoinSplit* pzcashParams = NULL;
//ZCJoinSplit* pzcashParams = NULL;
#ifdef ENABLE_WALLET
CWallet* pwalletMain = NULL;
@ -305,8 +305,8 @@ void Shutdown()
delete pwalletMain;
pwalletMain = NULL;
#endif
delete pzcashParams;
pzcashParams = NULL;
//delete pzcashParams;
//pzcashParams = NULL;
globalVerifyHandle.reset();
ECC_Stop();
LogPrintf("%s: done\n", __func__);
@ -399,6 +399,7 @@ std::string HelpMessage(HelpMessageMode mode)
#ifndef _WIN32
strUsage += HelpMessageOpt("-pid=<file>", strprintf(_("Specify pid file (default: %s)"), "komodod.pid"));
#endif
strUsage += HelpMessageOpt("-txexpirynotify=<cmd>", _("Execute command when transaction expires (%s in cmd is replaced by transaction id)"));
strUsage += HelpMessageOpt("-prune=<n>", strprintf(_("Reduce storage requirements by pruning (deleting) old blocks. This mode disables wallet support and is incompatible with -txindex. "
"Warning: Reverting this setting requires re-downloading the entire blockchain. "
"(default: 0 = disable pruning blocks, >%u = target size in MiB to use for block files)"), MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024));
@ -581,7 +582,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-metricsui", _("Set to 1 for a persistent metrics screen, 0 for sequential metrics output (default: 1 if running in a console, 0 otherwise)"));
strUsage += HelpMessageOpt("-metricsrefreshtime", strprintf(_("Number of seconds between metrics refreshes (default: %u if running in a console, %u otherwise)"), 1, 600));
}
strUsage += HelpMessageGroup(_("Komodo Asset Chain options:"));
strUsage += HelpMessageGroup(_("Hush Smart Chain options:"));
strUsage += HelpMessageOpt("-ac_algo", _("Choose PoW mining algorithm, default is Equihash"));
strUsage += HelpMessageOpt("-ac_blocktime", _("Block time in seconds, default is 60"));
strUsage += HelpMessageOpt("-ac_cc", _("Cryptoconditions, default 0"));
@ -618,6 +619,14 @@ static void BlockNotifyCallback(const uint256& hashNewTip)
boost::thread t(runCommand, strCmd); // thread runs free
}
static void TxExpiryNotifyCallback(const uint256& txid)
{
std::string strCmd = GetArg("-txexpirynotify", "");
boost::replace_all(strCmd, "%s", txid.GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
}
struct CImportingNow
{
CImportingNow() {
@ -1033,10 +1042,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (mapArgs.count("-developerencryptwallet")) {
fprintf(stderr,"%s wallet encryption error\n", __FUNCTION__);
return InitError(_("Wallet encryption requires -experimentalfeatures."));
}
else if (mapArgs.count("-paymentdisclosure")) {
fprintf(stderr,"%s payment disclosure error\n", __FUNCTION__);
return InitError(_("Payment disclosure requires -experimentalfeatures."));
//TODO: make this non experimental
} else if (mapArgs.count("-zmergetoaddress")) {
fprintf(stderr,"%s zmerge error\n", __FUNCTION__);
return InitError(_("RPC method z_mergetoaddress requires -experimentalfeatures."));
@ -2114,6 +2120,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
if (mapArgs.count("-blocknotify"))
uiInterface.NotifyBlockTip.connect(BlockNotifyCallback);
if (mapArgs.count("-txexpirynotify"))
uiInterface.NotifyTxExpiration.connect(TxExpiryNotifyCallback);
if ( KOMODO_REWIND >= 0 )
{
uiInterface.InitMessage(_("Activating best chain..."));

4
src/init.h

@ -23,7 +23,7 @@
#include <string>
#include "zcash/JoinSplit.hpp"
//#include "zcash/JoinSplit.hpp"
class CScheduler;
class CWallet;
@ -34,7 +34,7 @@ class thread_group;
} // namespace boost
extern CWallet* pwalletMain;
extern ZCJoinSplit* pzcashParams;
//extern ZCJoinSplit* pzcashParams;
void StartShutdown();
bool ShutdownRequested();

1
src/key.h

@ -1,6 +1,7 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

73
src/key_io.cpp

@ -1,5 +1,6 @@
// Copyright (c) 2014-2016 The Bitcoin Core developers
// Copyright (c) 2016-2018 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.
@ -84,15 +85,6 @@ private:
public:
PaymentAddressEncoder(const CChainParams& params) : m_params(params) {}
std::string operator()(const libzcash::SproutPaymentAddress& zaddr) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zaddr;
std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::ZCPAYMENT_ADDRRESS);
data.insert(data.end(), ss.begin(), ss.end());
return EncodeBase58Check(data);
}
std::string operator()(const libzcash::SaplingPaymentAddress& zaddr) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
@ -107,6 +99,7 @@ public:
}
std::string operator()(const libzcash::InvalidEncoding& no) const { return {}; }
std::string operator()(const libzcash::SproutPaymentAddress& zaddr) const { return {}; }
};
class ViewingKeyEncoder : public boost::static_visitor<std::string>
@ -117,17 +110,6 @@ private:
public:
ViewingKeyEncoder(const CChainParams& params) : m_params(params) {}
std::string operator()(const libzcash::SproutViewingKey& vk) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << vk;
std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::ZCVIEWING_KEY);
data.insert(data.end(), ss.begin(), ss.end());
std::string ret = EncodeBase58Check(data);
memory_cleanse(data.data(), data.size());
return ret;
}
std::string operator()(const libzcash::SaplingIncomingViewingKey& vk) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
@ -152,17 +134,6 @@ private:
public:
SpendingKeyEncoder(const CChainParams& params) : m_params(params) {}
std::string operator()(const libzcash::SproutSpendingKey& zkey) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << zkey;
std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::ZCSPENDING_KEY);
data.insert(data.end(), ss.begin(), ss.end());
std::string ret = EncodeBase58Check(data);
memory_cleanse(data.data(), data.size());
return ret;
}
std::string operator()(const libzcash::SaplingExtendedSpendingKey& zkey) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
@ -324,18 +295,6 @@ std::string EncodePaymentAddress(const libzcash::PaymentAddress& zaddr)
libzcash::PaymentAddress DecodePaymentAddress(const std::string& str)
{
std::vector<unsigned char> data;
if (DecodeBase58Check(str, data)) {
const std::vector<unsigned char>& zaddr_prefix = Params().Base58Prefix(CChainParams::ZCPAYMENT_ADDRRESS);
if ((data.size() == libzcash::SerializedSproutPaymentAddressSize + zaddr_prefix.size()) &&
std::equal(zaddr_prefix.begin(), zaddr_prefix.end(), data.begin())) {
CSerializeData serialized(data.begin() + zaddr_prefix.size(), data.end());
CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION);
libzcash::SproutPaymentAddress ret;
ss >> ret;
return ret;
}
}
data.clear();
auto bech = bech32::Decode(str);
if (bech.first == Params().Bech32HRP(CChainParams::SAPLING_PAYMENT_ADDRESS) &&
bech.second.size() == ConvertedSaplingPaymentAddressSize) {
@ -363,20 +322,6 @@ std::string EncodeViewingKey(const libzcash::ViewingKey& vk)
libzcash::ViewingKey DecodeViewingKey(const std::string& str)
{
std::vector<unsigned char> data;
if (DecodeBase58Check(str, data)) {
const std::vector<unsigned char>& vk_prefix = Params().Base58Prefix(CChainParams::ZCVIEWING_KEY);
if ((data.size() == libzcash::SerializedSproutViewingKeySize + vk_prefix.size()) &&
std::equal(vk_prefix.begin(), vk_prefix.end(), data.begin())) {
CSerializeData serialized(data.begin() + vk_prefix.size(), data.end());
CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION);
libzcash::SproutViewingKey ret;
ss >> ret;
memory_cleanse(serialized.data(), serialized.size());
memory_cleanse(data.data(), data.size());
return ret;
}
}
data.clear();
auto bech = bech32::Decode(str);
if(bech.first == Params().Bech32HRP(CChainParams::SAPLING_INCOMING_VIEWING_KEY) &&
bech.second.size() == ConvertedSaplingIncomingViewingKeySize) {
@ -400,20 +345,6 @@ std::string EncodeSpendingKey(const libzcash::SpendingKey& zkey)
libzcash::SpendingKey DecodeSpendingKey(const std::string& str)
{
std::vector<unsigned char> data;
if (DecodeBase58Check(str, data)) {
const std::vector<unsigned char>& zkey_prefix = Params().Base58Prefix(CChainParams::ZCSPENDING_KEY);
if ((data.size() == libzcash::SerializedSproutSpendingKeySize + zkey_prefix.size()) &&
std::equal(zkey_prefix.begin(), zkey_prefix.end(), data.begin())) {
CSerializeData serialized(data.begin() + zkey_prefix.size(), data.end());
CDataStream ss(serialized, SER_NETWORK, PROTOCOL_VERSION);
libzcash::SproutSpendingKey ret;
ss >> ret;
memory_cleanse(serialized.data(), serialized.size());
memory_cleanse(data.data(), data.size());
return ret;
}
}
data.clear();
auto bech = bech32::Decode(str);
if (bech.first == Params().Bech32HRP(CChainParams::SAPLING_EXTENDED_SPEND_KEY) &&
bech.second.size() == ConvertedSaplingExtendedSpendingKeySize) {

1
src/key_io.h

@ -1,6 +1,7 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2015 The Bitcoin Core developers
// Copyright (c) 2016-2018 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.

42
src/keystore.cpp

@ -1,5 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core 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.
@ -128,15 +129,6 @@ bool CBasicKeyStore::HaveWatchOnly() const
return (!setWatchOnly.empty());
}
bool CBasicKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk)
{
LOCK(cs_SpendingKeyStore);
auto address = sk.address();
mapSproutSpendingKeys[address] = sk;
mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(sk.receiving_key())));
return true;
}
//! Sapling
bool CBasicKeyStore::AddSaplingSpendingKey(
const libzcash::SaplingExtendedSpendingKey &sk,
@ -155,14 +147,6 @@ bool CBasicKeyStore::AddSaplingSpendingKey(
return true;
}
bool CBasicKeyStore::AddSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
LOCK(cs_SpendingKeyStore);
auto address = vk.address();
mapSproutViewingKeys[address] = vk;
mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(vk.sk_enc)));
return true;
}
bool CBasicKeyStore::AddSaplingFullViewingKey(
const libzcash::SaplingFullViewingKey &fvk,
@ -190,18 +174,7 @@ bool CBasicKeyStore::AddSaplingIncomingViewingKey(
return true;
}
bool CBasicKeyStore::RemoveSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
LOCK(cs_SpendingKeyStore);
mapSproutViewingKeys.erase(vk.address());
return true;
}
bool CBasicKeyStore::HaveSproutViewingKey(const libzcash::SproutPaymentAddress &address) const
{
LOCK(cs_SpendingKeyStore);
return mapSproutViewingKeys.count(address) > 0;
}
bool CBasicKeyStore::HaveSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk) const
{
@ -215,19 +188,6 @@ bool CBasicKeyStore::HaveSaplingIncomingViewingKey(const libzcash::SaplingPaymen
return mapSaplingIncomingViewingKeys.count(addr) > 0;
}
bool CBasicKeyStore::GetSproutViewingKey(
const libzcash::SproutPaymentAddress &address,
libzcash::SproutViewingKey &vkOut) const
{
LOCK(cs_SpendingKeyStore);
SproutViewingKeyMap::const_iterator mi = mapSproutViewingKeys.find(address);
if (mi != mapSproutViewingKeys.end()) {
vkOut = mi->second;
return true;
}
return false;
}
bool CBasicKeyStore::GetSaplingFullViewingKey(const libzcash::SaplingIncomingViewingKey &ivk,
libzcash::SaplingFullViewingKey &fvkOut) const
{

87
src/keystore.h

@ -1,5 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core 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.
@ -70,14 +71,6 @@ public:
virtual bool HaveWatchOnly(const CScript &dest) const =0;
virtual bool HaveWatchOnly() const =0;
//! Add a spending key to the store.
virtual bool AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk) =0;
//! Check whether a spending key corresponding to a given payment address is present in the store.
virtual bool HaveSproutSpendingKey(const libzcash::SproutPaymentAddress &address) const =0;
virtual bool GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey& skOut) const =0;
virtual void GetSproutPaymentAddresses(std::set<libzcash::SproutPaymentAddress> &setAddress) const =0;
//! Add a Sapling spending key to the store.
virtual bool AddSaplingSpendingKey(
const libzcash::SaplingExtendedSpendingKey &sk,
@ -106,21 +99,11 @@ public:
libzcash::SaplingIncomingViewingKey& ivkOut) const =0;
virtual void GetSaplingPaymentAddresses(std::set<libzcash::SaplingPaymentAddress> &setAddress) const =0;
//! Support for Sprout viewing keys
virtual bool AddSproutViewingKey(const libzcash::SproutViewingKey &vk) =0;
virtual bool RemoveSproutViewingKey(const libzcash::SproutViewingKey &vk) =0;
virtual bool HaveSproutViewingKey(const libzcash::SproutPaymentAddress &address) const =0;
virtual bool GetSproutViewingKey(
const libzcash::SproutPaymentAddress &address,
libzcash::SproutViewingKey& vkOut) const =0;
};
typedef std::map<CKeyID, CKey> KeyMap;
typedef std::map<CScriptID, CScript > ScriptMap;
typedef std::set<CScript> WatchOnlySet;
typedef std::map<libzcash::SproutPaymentAddress, libzcash::SproutSpendingKey> SproutSpendingKeyMap;
typedef std::map<libzcash::SproutPaymentAddress, libzcash::SproutViewingKey> SproutViewingKeyMap;
typedef std::map<libzcash::SproutPaymentAddress, ZCNoteDecryption> NoteDecryptorMap;
// Full viewing key has equivalent functionality to a transparent address
// When encrypting wallet, encrypt SaplingSpendingKeyMap, while leaving SaplingFullViewingKeyMap unencrypted
@ -137,10 +120,7 @@ protected:
KeyMap mapKeys;
ScriptMap mapScripts;
WatchOnlySet setWatchOnly;
SproutSpendingKeyMap mapSproutSpendingKeys;
SproutViewingKeyMap mapSproutViewingKeys;
NoteDecryptorMap mapNoteDecryptors;
SaplingSpendingKeyMap mapSaplingSpendingKeys;
SaplingFullViewingKeyMap mapSaplingFullViewingKeys;
SaplingIncomingViewingKeyMap mapSaplingIncomingViewingKeys;
@ -195,62 +175,6 @@ public:
virtual bool HaveWatchOnly(const CScript &dest) const;
virtual bool HaveWatchOnly() const;
bool AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk);
bool HaveSproutSpendingKey(const libzcash::SproutPaymentAddress &address) const
{
bool result;
{
LOCK(cs_SpendingKeyStore);
result = (mapSproutSpendingKeys.count(address) > 0);
}
return result;
}
bool GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const
{
{
LOCK(cs_SpendingKeyStore);
SproutSpendingKeyMap::const_iterator mi = mapSproutSpendingKeys.find(address);
if (mi != mapSproutSpendingKeys.end())
{
skOut = mi->second;
return true;
}
}
return false;
}
bool GetNoteDecryptor(const libzcash::SproutPaymentAddress &address, ZCNoteDecryption &decOut) const
{
{
LOCK(cs_SpendingKeyStore);
NoteDecryptorMap::const_iterator mi = mapNoteDecryptors.find(address);
if (mi != mapNoteDecryptors.end())
{
decOut = mi->second;
return true;
}
}
return false;
}
void GetSproutPaymentAddresses(std::set<libzcash::SproutPaymentAddress> &setAddress) const
{
setAddress.clear();
{
LOCK(cs_SpendingKeyStore);
SproutSpendingKeyMap::const_iterator mi = mapSproutSpendingKeys.begin();
while (mi != mapSproutSpendingKeys.end())
{
setAddress.insert((*mi).first);
mi++;
}
SproutViewingKeyMap::const_iterator mvi = mapSproutViewingKeys.begin();
while (mvi != mapSproutViewingKeys.end())
{
setAddress.insert((*mvi).first);
mvi++;
}
}
}
//! Sapling
bool AddSaplingSpendingKey(
const libzcash::SaplingExtendedSpendingKey &sk,
@ -313,17 +237,10 @@ public:
}
}
virtual bool AddSproutViewingKey(const libzcash::SproutViewingKey &vk);
virtual bool RemoveSproutViewingKey(const libzcash::SproutViewingKey &vk);
virtual bool HaveSproutViewingKey(const libzcash::SproutPaymentAddress &address) const;
virtual bool GetSproutViewingKey(
const libzcash::SproutPaymentAddress &address,
libzcash::SproutViewingKey& vkOut) const;
};
typedef std::vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;
typedef std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char> > > CryptedKeyMap;
typedef std::map<libzcash::SproutPaymentAddress, std::vector<unsigned char> > CryptedSproutSpendingKeyMap;
//! Sapling
typedef std::map<libzcash::SaplingExtendedFullViewingKey, std::vector<unsigned char> > CryptedSaplingSpendingKeyMap;

2
src/komodo_bitcoind.h

@ -20,7 +20,7 @@
#include <curl/curl.h>
#include <curl/easy.h>
#include "consensus/params.h"
#include "primitives/nonce.h"
//#include "primitives/nonce.h"
#include "komodo_defs.h"
#include "script/standard.h"
#include "cc/CCinclude.h"

92
src/komodo_defs.h

@ -1,4 +1,4 @@
// Copyright (c) 2019 The Hush developers
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *
@ -34,33 +34,34 @@
#define KOMODO_FIRSTFUNGIBLEID 100
#define KOMODO_SAPLING_ACTIVATION 1544832000 // Dec 15th, 2018
#define KOMODO_SAPLING_DEADLINE 1550188800 // Feb 15th, 2019
#define ASSETCHAINS_STAKED_BLOCK_FUTURE_MAX 57
#define ASSETCHAINS_STAKED_BLOCK_FUTURE_HALF 27
#define ASSETCHAINS_STAKED_MIN_POW_DIFF 536900000 // 537000000 537300000
#define _COINBASE_MATURITY 100
// KMD Notary Seasons
// 1: May 1st 2018 1530921600
// 2: July 15th 2019 1563148800 -> estimated height 1444000
// 3: 3rd season ending isnt known, so use very far times in future.
// 3: 3rd season
// 1751328000 = dummy timestamp, 1 July 2025!
// 7113400 = 5x current KMD blockheight.
// to add 4th season, change NUM_KMD_SEASONS to 4, and add timestamp and height of activation to these arrays.
// to add seasons, change NUM_KMD_SEASONS, and add timestamp and height of activation to these arrays.
#define NUM_KMD_SEASONS 4
#define NUM_KMD_SEASONS 6
#define NUM_KMD_NOTARIES 64
// $ ./contrib/block_time.pl 166250
// Hush Block 166250 will happen at roughly:
// Wed Jan 29 08:14:12 2020 Eastern # 1580303652
// Wed Jan 29 13:14:12 2020 GMT # 1580303652
const uint32_t nHushHardforkHeight = 166250;
const uint32_t nHushHardforkHeight = 166250;
// $ ./contrib/block_time.pl 245555
// Hush Block 245555 will happen at roughly... now
const uint32_t nHushHardforkHeight2 = 245055;
// No coins/code are currently using timestamp activated fork
const uint32_t nHushHardforkTimestamp = 1580303652; // Jan 29nd 1pm GMT
const uint32_t nHushHardforkTimestamp = 1580303652; // Jan 29nd 1pm GMT
const uint32_t nHushHardforkTimestamp2 = 1594425600; // Jul 11th 12a GMT
static const uint32_t KMD_SEASON_TIMESTAMPS[NUM_KMD_SEASONS] = {1525132800, 1563148800, nHushHardforkTimestamp, 1751328000};
static const int32_t KMD_SEASON_HEIGHTS[NUM_KMD_SEASONS] = {1,2,nHushHardforkHeight, 5*nHushHardforkHeight};
static const uint32_t KMD_SEASON_TIMESTAMPS[NUM_KMD_SEASONS] = {1525132800, 1563148800, nHushHardforkTimestamp, nHushHardforkTimestamp2, nHushHardforkTimestamp2*5, nHushHardforkTimestamp2*6};
static const int32_t KMD_SEASON_HEIGHTS[NUM_KMD_SEASONS] = {1,2,nHushHardforkHeight, nHushHardforkHeight2, (int)340000, 5*nHushHardforkHeight2};
// Era array of pubkeys. Add extra seasons to bottom as requried, after adding appropriate info above.
static const char *notaries_elected[NUM_KMD_SEASONS][NUM_KMD_NOTARIES][2] =
@ -329,7 +330,74 @@ static const char *notaries_elected[NUM_KMD_SEASONS][NUM_KMD_NOTARIES][2] =
{"gt_AR", "0307c1cf89bd8ed4db1b09a0a98cf5f746fc77df3803ecc8611cf9455ec0ce6960" },
{"patchkez_SH", "03d7c187689bf829ca076a30bbf36d2e67bb74e16a3290d8a55df21d6cb15c80c1" },
{"decker_AR", "02a85540db8d41c7e60bf0d33d1364b4151cad883dd032878ea4c037f67b769635" }
}
},
{
// Season 4 https://github.com/KomodoPlatform/dPoW/blob/s4/iguana/3rd_party
{"alien_AR", "024f20c096b085308e21893383f44b4faf1cdedea9ad53cc7d7e7fbfa0c30c1e71" },
{"alien_EU", "022b85908191788f409506ebcf96a892f3274f352864c3ed566c5a16de63953236" },
{"alien_NA", "022f62b56ddfd07c9860921c701285ac39bb3ac8f6f083d1b59c8f4943be3de162" },
{"alright_DEV", "03b6f9493658bdd102503585a08ae642b49d6a68fb69ac3626f9737cd7581abdfa" },
{"artemii235_DEV", "037a20916d2e9ea575300ac9d729507c23a606b9a200c8e913d7c9832f912a1fa7" },
{"chainmakers_NA", "028803e07bcc521fde264b7191a944f9b3612e8ee4e24a99bcd903f6976240839a" },
{"chainzilla_SH", "0311dde03c2dd654ce78323b718ed3ad73a464d1bde97820f3395f54788b5420dd" },
{"chmex_AR", "030cd487e10fbf142e0e8d582e702ecb775f378569c3cb5acd0ff97b6b12803588" },
{"chmex_EU", "030bf7bd7ad0515c33b5d5d9a91e0729baf801b9002f80495ae535ea1cebb352cb" },
{"cipi_EU", "026f4f66385daaf8313ef30ffe4988e7db497132682dca185a70763d93e1417d9d" },
{"cipi_NA", "03f4e69edcb4fa3b2095cb8cb1ca010f4ec4972eac5d8822397e5c8d87aa21a739" },
{"daemonfox_NA", "023c7584b1006d4a62a4b4c9c1ede390a3789316547897d5ed49ff9385a3acb411" },
{"dappvader_SH", "025199bc04bcb8a17976d9fe8bc87763a6150c2727321aa59bf34a2b49f2f3a0ce" },
{"decker_AR", "02a85540db8d41c7e60bf0d33d1364b4151cad883dd032878ea4c037f67b769635" },
{"decker_DEV", "02fca8ee50e49f480de275745618db7b0b3680b0bdcce7dcae7d2e0fd5c3345744" },
{"decker_EU", "027777775b89ff548c3be54fb0c9455437d87f38bfce83bdef113899881b219c9e" },
{"dragonhound_NA", "029912212d370ee0fb4d38eefd8bfcd8ab04e2c3b0354020789c29ddf2a35c72d6" },
{"dudezmobi_AR", "033c121d3f8d450174674a73f3b7f140b2717a7d51ea19ee597e2e8e8f9d5ed87f" },
{"etszombi_AR", "03bfcbca83f11e622fa4eed9a1fa25dba377981ea3b22e3d0a4015f9a932af9272" },
{"etszombi_EU", "03a5c083c78ba397970f20b544a01c13e7ed36ca8a5ae26d5fe7bd38b92b6a0c94" },
{"fullmoon_AR", "03639bc56d3fecf856f17759a441c5893668e7c2d460f3d216798a413cd6766bb2" },
{"fullmoon_NA", "03e388bcc579ac2675f8fadfa921eec186dcea8d2b43de1eed6caba23d5a962b74" },
{"fullmoon_SH", "03a5cfda2b097c808834ccdd805828c811b519611feabdfe6b3644312e53f6748f" },
{"gcharang_SH", "02a654037d12cdd609f4fad48e15ec54538e03f61fdae1acb855f16ebacac6bd73" },
{"greer_NA", "0262da6aaa0b295b8e2f120035924758a4a630f899316dc63ee15ef03e9b7b2b23" },
{"indenodes_AR", "0242778789986d614f75bcf629081651b851a12ab1cc10c73995b27b90febb75a2" },
{"indenodes_EU", "03a416533cace0814455a1bb1cd7861ce825a543c6f6284a432c4c8d8875b7ace9" },
{"indenodes_NA", "02b3908eda4078f0e9b6704451cdc24d418e899c0f515fab338d7494da6f0a647b" },
{"indenodes_SH", "031d1584cf0eb4a2d314465e49e2677226b1615c3718013b8d6b4854c15676a58c" },
{"karasugoi_NA", "02f803e6f159824a181cc5d709f3d1e7ff65f19e1899920724aeb4e3d2d869f911" },
{"madmax_AR", "027afddbcf690230dd8d435ec16a7bfb0083e6b77030f763437f291dfc40a579d0" },
{"madmax_EU", "0397ec3a4ad84b3009566d260c89f1c4404e86e5d044964747c9371277e38f5995" },
{"madmax_NA", "036d3afebe1eab09f4c38c3ee6a4659ad390f3df92787c11437a58c59a29e408e6" },
{"marmarachain_AR", "028690ca1e3afdf8a38b421f6a41f5ff407afc96d5a7a6a488330aae26c8b086bb" },
{"mcrypt_SH", "027a4ca7b11d3456ff558c08bb04483a89c7f383448461fd0b6b3b07424aabe9a4" },
{"metaphilibert_AR", "0239e34ad22957bbf4c8df824401f237b2afe8d40f7a645ecd43e8f27dde1ab0da" },
{"metaphilibert_SH", "03b21ff042bf1730b28bde43f44c064578b41996117ac7634b567c3773089e3be3" },
{"mihailo_EU", "036494e7c9467c8c7ff3bf29e841907fb0fa24241866569944ea422479ec0e6252" },
{"mrlynch_AR", "03e67440141f53a08684c329ebc852b018e41f905da88e52aa4a6dc5aa4b12447a" },
{"mylo_SH", "026d5f29d09ff3f33e14db4811606249b2438c6bcf964876714f81d1f2d952acde" },
{"node9_EU", "0392e4c9400e69f28c6b9e89d586da69d5a6af7702f1045eaa6ebc1996f0496e1f" },
{"nodeone_NA", "0310a249c6c2dcc29f2135715138a9ddb8e01c0eab701cbd0b96d9cec660dbdc58" },
{"nutellalicka_SH", "0284c4d3cb97dd8a32d10fb32b1855ae18cf845dad542e3b8937ca0e998fb54ecc" },
{"oszy_EU", "03c53bd421de4a29ce68c8cc83f802e1181e77c08f8f16684490d61452ea8d023a" },
{"patchkez_SH", "028c08db6e7242681f50db6c234fe3d6e12fb1a915350311be26373bac0d457d49" },
{"pbca26_NA", "03c18431bb6bc95672f640f19998a196becd2851d5dcba4795fe8d85b7d77eab81" },
{"peer2cloud_AR", "0243958faf9ae4d43b598b859ddc595c170c4cf50f8e4517d660ae5bc72aeb821b" },
{"phba2061_EU", "03369187ce134bd7793ee34af7756fe1ab27202e09306491cdd5d8ad2c71697937" },
{"phm87_SH", "03889a10f9df2caef57220628515693cf25316fe1b0693b0241419e75d0d0e66ed" },
{"pirate_EU", "0240011b95cde819f298fe0f507b2260c9fecdab784924076d4d1e54c522103cb1" },
{"pirate_NA", "02ad7ef25d2dd461e361120cd3efe7cbce5e9512c361e9185aac33dd303d758613" },
{"pungocloud_SH", "02641c36ae6747b88150a463a1fe65cf7a9d1c00a64387c73f296f0b64e77c7d3f" },
{"smdmitry_AR", "0397b7584cb29717b721c0c587d4462477efc1f36a56921f133c9d17b0cd7f278a" },
{"starfleet_EU", "03c6e047218f34644ccba67e317b9da5d28e68bbbb6b9973aef1281d2bafa46496" },
{"strob_NA", "02285bf2f9e96068ecac14bc6f770e394927b4da9f5ba833eaa9468b5d47f203a3" },
{"strob_SH", "0213751a1c59d3489ca85b3d62a3d606dcef7f0428aa021c1978ea16fb38a2fad6" },
{"swisscertifiers_EU", "02e7722ebba9f8b5ebfb4e87d4fa58cc75aef677535b9cfc060c7d9471aacd9c9e" },
{"titomane_AR", "03958bd8d13fe6946b8d0d0fbbc3861c72542560d0276e80a4c6b5fe55bc758b81" },
{"titomane_EU", "02276090e483db1a01a802456b10831b3b6e0a6ad3ece9b2a01f4aad0e480c8edc" },
{"titomane_SH", "02abf206bafc8048dbdc042b8eb6b1e356ea5dbe149eae3532b4811d4905e5cf01" },
{"tonyl_AR", "0229e499e3f2e065ced402ceb8aaf3d5ab8bd3793aa074305e9fa30772ce604908" },
{"tonyl_DEV", "0258b77d7dcfc6c2628b0b6b438951a6e74201fb2cd180a795e4c37fcf8e78a678" },
{"webworker01_NA", "02de90c720c007229374772505a43917a84ed129d5fbcfa4949cc2e9b563351124" },
{"zatjum_SH", "0241c5660ca540780be66603b1791127a1261d56abbcb7562c297eec8e4fc078fb" }
}
};
#define SETBIT(bits,bitoffset) (((uint8_t *)bits)[(bitoffset) >> 3] |= (1 << ((bitoffset) & 7)))

2
src/komodo_notary.h

@ -1,4 +1,4 @@
// Copyright (c) 2019 The Hush developers
// Copyright (c) 2019-2020 The Hush developers
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
* *

106
src/main.cpp

@ -114,6 +114,7 @@ bool fAlerts = DEFAULT_ALERTS;
/* If the tip is older than this (in seconds), the node is considered to be in initial block download.
*/
int64_t nMaxTipAge = DEFAULT_MAX_TIP_AGE;
bool ishush3 = strncmp(ASSETCHAINS_SYMBOL, "HUSH3",5) == 0 ? true : false;
unsigned int expiryDelta = DEFAULT_TX_EXPIRY_DELTA;
extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN];
@ -1224,9 +1225,9 @@ bool ContextualCheckTransaction(int32_t slowflag,const CBlock *block, CBlockInde
if (IsExpiredTx(tx, nHeight)) {
// Don't increase banscore if the transaction only just expired
int expiredDosLevel = IsExpiredTx(tx, nHeight - 1) ? (dosLevel > 10 ? dosLevel : 10) : 0;
string strHex = EncodeHexTx(tx);
//string strHex = EncodeHexTx(tx);
//fprintf(stderr, "transaction exipred.%s\n",strHex.c_str());
return state.DoS(expiredDosLevel, error("ContextualCheckTransaction(): transaction %s is expired, expiry block %i vs current block %i\n txhex.%s",tx.GetHash().ToString(),tx.nExpiryHeight,nHeight,strHex), REJECT_INVALID, "tx-overwinter-expired");
return state.DoS(expiredDosLevel, error("ContextualCheckTransaction(): transaction %s is expired, expiry block %i vs current block %i\n",tx.GetHash().ToString(),tx.nExpiryHeight,nHeight), REJECT_INVALID, "tx-overwinter-expired");
}
}
@ -1327,7 +1328,7 @@ bool ContextualCheckTransaction(int32_t slowflag,const CBlock *block, CBlockInde
))
{
librustzcash_sapling_verification_ctx_free(ctx);
fprintf(stderr,"%s: Invalid sapling binding sig! tx=%s valueBalance=%li, bindingSig.size=%d\n", __func__, tx.GetHash().ToString().c_str(), tx.valueBalance, tx.bindingSig.size() );
fprintf(stderr,"%s: Invalid sapling binding sig! tx=%s valueBalance=%li, bindingSig.size=%li\n", __func__, tx.GetHash().ToString().c_str(), tx.valueBalance, tx.bindingSig.size() );
return state.DoS(100, error("ContextualCheckTransaction(): Sapling binding signature invalid"),
REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid");
}
@ -1367,16 +1368,8 @@ bool CheckTransaction(uint32_t tiptime,const CTransaction& tx, CValidationState
if (!CheckTransactionWithoutProofVerification(tiptime,tx, state)) {
return false;
} else {
// Ensure that zk-SNARKs v|| y
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
if (!joinsplit.Verify(*pzcashParams, verifier, tx.joinSplitPubKey)) {
return state.DoS(100, error("CheckTransaction(): joinsplit does not verify"),
REJECT_INVALID, "bad-txns-joinsplit-verification-failed");
}
}
return true;
}
return true;
}
int32_t komodo_isnotaryvout(char *coinaddr,uint32_t tiptime) // from ac_private chains only
@ -1585,17 +1578,18 @@ bool CheckTransactionWithoutProofVerification(uint32_t tiptime,const CTransactio
else if ( joinsplit.vpub_new != 0 && joinsplit.vpub_old == 0 )
z_t++;
}
if ( ASSETCHAINS_PRIVATE != 0 && invalid_private_taddr != 0 )
{
static uint32_t counter;
if ( counter++ < 10 )
fprintf(stderr,"found taddr in private chain: z_z.%d z_t.%d t_z.%d vinsize.%d\n",z_z,z_t,t_z,(int32_t)tx.vin.size());
if ( z_t == 0 || z_z != 0 || t_z != 0 || tx.vin.size() != 0 )
return state.DoS(100, error("CheckTransaction(): this is a private chain, only sprout -> taddr allowed until deadline"),REJECT_INVALID, "bad-txns-acprivacy-chain");
return state.DoS(100, error("CheckTransaction(): this is a private chain, sending to taddrs not allowed"),REJECT_INVALID, "bad-txns-acprivacy-chain");
}
if ( ASSETCHAINS_TXPOW != 0 )
{
// genesis coinbase 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
// BTC genesis coinbase 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
uint256 txid = tx.GetHash();
if ( ((ASSETCHAINS_TXPOW & 2) != 0 && iscoinbase != 0) || ((ASSETCHAINS_TXPOW & 1) != 0 && iscoinbase == 0) )
{
@ -1741,6 +1735,23 @@ CAmount GetMinRelayFee(const CTransaction& tx, unsigned int nBytes, bool fAllowF
bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransaction &tx, bool fLimitFree,bool* pfMissingInputs, bool fRejectAbsurdFee, int dosLevel)
{
AssertLockHeld(cs_main);
const uint32_t z2zTransitionWindow = 10;
const uint32_t z2zTransitionStart = 340000 - z2zTransitionWindow;
const uint32_t nHeight = chainActive.Height();
// This only applies to HUSH3, other chains can start off z2z via ac_private=1
if(ishush3) {
if((nHeight >= z2zTransitionStart) || (nHeight <= 340000)) {
// During the z2z transition window, only coinbase tx's as part of blocks are allowed
// Theory: We want an empty mempool at our fork block height, and the only way to assure that
// is to have an empty mempool for a few previous blocks, to take care of potential re-orgs
// and edge cases. This empty mempool assures there will be no transactions involving taddrs
// stuck in the mempool, when the z2z rule takes effect.
// Thanks to jl777 for helping design this
fprintf(stderr,"%s: rejecting all tx's during z2z transition window at height=%d\n", __func__,nHeight);
return false;
}
}
if (pfMissingInputs)
*pfMissingInputs = false;
uint32_t tiptime;
@ -1894,10 +1905,10 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
}
// are the joinsplit's requirements met?
if (!view.HaveJoinSplitRequirements(tx))
if (!view.HaveShieldedRequirements(tx))
{
//fprintf(stderr,"accept failure.2\n");
return state.Invalid(error("AcceptToMemoryPool: joinsplit requirements not met"),REJECT_DUPLICATE, "bad-txns-joinsplit-requirements-not-met");
return state.Invalid(error("AcceptToMemoryPool: shielded requirements not met"),REJECT_DUPLICATE, "bad-txns-joinsplit-requirements-not-met");
}
// Bring the best block into scope
@ -2718,9 +2729,9 @@ namespace Consensus {
if (!inputs.HaveInputs(tx))
return state.Invalid(error("CheckInputs(): %s inputs unavailable", tx.GetHash().ToString()));
// are the JoinSplit's requirements met?
if (!inputs.HaveJoinSplitRequirements(tx))
return state.Invalid(error("CheckInputs(): %s JoinSplit requirements not met", tx.GetHash().ToString()));
// are the shielded requirements met?
if (!inputs.HaveShieldedRequirements(tx))
return state.Invalid(error("CheckInputs(): %s shielded requirements not met", tx.GetHash().ToString()));
CAmount nValueIn = 0;
CAmount nFees = 0;
@ -3349,6 +3360,18 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
return(false);
//fprintf(stderr,"connectblock ht.%d\n",(int32_t)pindex->GetHeight());
AssertLockHeld(cs_main);
bool ishush3 = strncmp(ASSETCHAINS_SYMBOL, "HUSH3",5) == 0 ? true : false;
if(!ASSETCHAINS_PRIVATE && ishush3) {
unsigned int nHeight = pindex->GetHeight();
if(nHeight >= 340000) {
// At startup, HUSH3 doesn't know a block height yet and so we must wait until
// connecting a block
fprintf(stderr, "%s: Going full z2z at height %d!\n",__func__,nHeight);
ASSETCHAINS_PRIVATE = 1;
}
}
bool fExpensiveChecks = true;
if (fCheckpointsEnabled) {
CBlockIndex *pindexLastCheckpoint = Checkpoints::GetLastCheckpoint(chainparams.Checkpoints());
@ -3442,7 +3465,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
// Before the genesis block, there was an empty tree
SproutMerkleTree tree;
pindex->hashSproutAnchor = tree.root();
// The genesis block contained no JoinSplits
// The genesis block contained no JoinSplits, lulz
pindex->hashFinalSproutRoot = pindex->hashSproutAnchor;
}
return true;
@ -3501,13 +3524,13 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
// This should never fail: we should always be able to get the root
// that is on the tip of our chain
assert(view.GetSproutAnchorAt(old_sprout_tree_root, sprout_tree));
//assert(view.GetSproutAnchorAt(old_sprout_tree_root, sprout_tree));
{
//{
// Consistency check: the root of the tree we're given should
// match what we asked for.
assert(sprout_tree.root() == old_sprout_tree_root);
}
//assert(sprout_tree.root() == old_sprout_tree_root);
//}
SaplingMerkleTree sapling_tree;
assert(view.GetSaplingAnchorAt(view.GetBestAnchor(SAPLING), sapling_tree));
@ -3535,10 +3558,9 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
return state.DoS(100, error("ConnectBlock(): inputs missing/spent"),
REJECT_INVALID, "bad-txns-inputs-missingorspent");
}
// are the JoinSplit's requirements met?
if (!view.HaveJoinSplitRequirements(tx))
return state.DoS(100, error("ConnectBlock(): JoinSplit requirements not met"),
REJECT_INVALID, "bad-txns-joinsplit-requirements-not-met");
// are the shielded requirements met?
if (!view.HaveShieldedRequirements(tx))
return state.DoS(100, error("ConnectBlock(): shielded requirements not met"), REJECT_INVALID, "bad-txns-joinsplit-requirements-not-met");
if (fAddressIndex || fSpentIndex)
{
@ -3636,21 +3658,12 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
}
}
//if ( ASSETCHAINS_SYMBOL[0] == 0 )
// komodo_earned_interest(pindex->GetHeight(),sum);
CTxUndo undoDummy;
if (i > 0) {
blockundo.vtxundo.push_back(CTxUndo());
}
UpdateCoins(tx, view, i == 0 ? undoDummy : blockundo.vtxundo.back(), pindex->GetHeight());
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
BOOST_FOREACH(const uint256 &note_commitment, joinsplit.commitments) {
// Insert the note commitments into our temporary tree.
sprout_tree.append(note_commitment);
}
}
BOOST_FOREACH(const OutputDescription &outputDescription, tx.vShieldedOutput) {
sapling_tree.append(outputDescription.cm);
@ -3660,7 +3673,7 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin
pos.nTxOffset += ::GetSerializeSize(tx, SER_DISK, CLIENT_VERSION);
}
view.PushAnchor(sprout_tree);
//view.PushAnchor(sprout_tree);
view.PushAnchor(sapling_tree);
if (!fJustCheck) {
pindex->hashFinalSproutRoot = sprout_tree.root();
@ -4210,7 +4223,11 @@ bool static ConnectTip(CValidationState &state, CBlockIndex *pindexNew, CBlock *
mempool.removeForBlock(pblock->vtx, pindexNew->GetHeight(), txConflicted, !IsInitialBlockDownload());
// Remove transactions that expire at new block height from mempool
mempool.removeExpired(pindexNew->GetHeight());
auto ids = mempool.removeExpired(pindexNew->GetHeight());
for (auto id : ids) {
uiInterface.NotifyTxExpiration(id);
}
// Update chainActive & related variables.
UpdateTip(pindexNew);
@ -6089,7 +6106,7 @@ CBlockIndex * InsertBlockIndex(uint256 hash)
// Create new
CBlockIndex* pindexNew = new CBlockIndex();
if (!pindexNew)
throw runtime_error("LoadBlockIndex(): new CBlockIndex failed");
throw runtime_error("InsertBlockIndex(): new CBlockIndex failed");
mi = mapBlockIndex.insert(make_pair(hash, pindexNew)).first;
pindexNew->phashBlock = &((*mi).first);
//fprintf(stderr,"inserted to block index %s\n",hash.ToString().c_str());
@ -6306,6 +6323,13 @@ bool static LoadBlockIndexDB()
chainActive.SetTip(it->second);
// Try to detect if we are z2z based on height of blocks on disk
// This helps to set it correctly on startup before a new block is connected
if(ishush3 && chainActive.Height() >= 340000) {
LogPrintf("%s: enabled ac_private=1 at height=%d\n", __func__, chainActive.Height());
ASSETCHAINS_PRIVATE = 1;
}
// Set hashFinalSproutRoot for the end of best chain
it->second->hashFinalSproutRoot = pcoinsTip->GetBestAnchor(SPROUT);
@ -6368,7 +6392,7 @@ bool CVerifyDB::VerifyDB(CCoinsView *coinsview, int nCheckLevel, int nCheckDepth
CBlockIndex* pindexFailure = NULL;
int nGoodTransactions = 0;
CValidationState state;
// No need to verify JoinSplits twice
// No need to verify shielded req's twice
auto verifier = libzcash::ProofVerifier::Disabled();
//fprintf(stderr,"start VerifyDB %u\n",(uint32_t)time(NULL));
for (CBlockIndex* pindex = chainActive.Tip(); pindex && pindex->pprev; pindex = pindex->pprev)

2
src/metrics.cpp

@ -288,7 +288,7 @@ int printMiningStatus(bool mining)
} else if (IsInitialBlockDownload()) {
std::cout << _("Mining is paused while downloading blocks.") << std::endl;
} else {
std::cout << _("Mining is paused (a JoinSplit may be in progress).") << std::endl;
std::cout << _("Mining is paused, enhance your calm") << std::endl;
}
}
lines++;

2
src/miner.cpp

@ -340,7 +340,7 @@ CBlockTemplate* CreateNewBlock(CPubKey _pk,const CScript& _scriptPubKeyIn, int32
if (!mempool.mapTx.count(txin.prevout.hash))
{
LogPrintf("ERROR: mempool transaction missing input\n");
if (fDebug) assert("mempool transaction missing input" == 0);
// if (fDebug) assert("mempool transaction missing input" == 0);
fMissingInputs = true;
if (porphan)
vOrphan.pop_back();

65
src/paymentdisclosure.cpp

@ -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);
}

148
src/paymentdisclosure.h

@ -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

93
src/paymentdisclosuredb.cpp

@ -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;
}

42
src/paymentdisclosuredb.h

@ -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

4
src/primitives/block.h

@ -1,6 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2013 The Bitcoin Core developers
// Copyright (c) 2019 The Hush 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.
@ -23,7 +23,7 @@
#define BITCOIN_PRIMITIVES_BLOCK_H
#include "primitives/transaction.h"
#include "primitives/nonce.h"
//#include "primitives/nonce.h"
#include "serialize.h"
#include "uint256.h"
#include "arith_uint256.h"

24
src/primitives/nonce.cpp

@ -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>

29
src/primitives/nonce.h

@ -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

78
src/primitives/transaction.cpp

@ -1,5 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core 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.
@ -26,83 +27,6 @@
#include "librustzcash.h"
JSDescription::JSDescription(
ZCJoinSplit& params,
const uint256& joinSplitPubKey,
const uint256& anchor,
const std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS>& inputs,
const std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS>& outputs,
CAmount vpub_old,
CAmount vpub_new,
bool computeProof,
uint256 *esk // payment disclosure
) : vpub_old(vpub_old), vpub_new(vpub_new), anchor(anchor)
{
std::array<libzcash::SproutNote, ZC_NUM_JS_OUTPUTS> notes;
proof = params.prove(
inputs,
outputs,
notes,
ciphertexts,
ephemeralKey,
joinSplitPubKey,
randomSeed,
macs,
nullifiers,
commitments,
vpub_old,
vpub_new,
anchor,
computeProof,
esk // payment disclosure
);
}
JSDescription JSDescription::Randomized(
ZCJoinSplit& params,
const uint256& joinSplitPubKey,
const uint256& anchor,
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS>& inputs,
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS>& outputs,
std::array<size_t, ZC_NUM_JS_INPUTS>& inputMap,
std::array<size_t, ZC_NUM_JS_OUTPUTS>& outputMap,
CAmount vpub_old,
CAmount vpub_new,
bool computeProof,
uint256 *esk, // payment disclosure
std::function<int(int)> gen
)
{
// Randomize the order of the inputs and outputs
inputMap = {0, 1};
outputMap = {0, 1};
assert(gen);
MappedShuffle(inputs.begin(), inputMap.begin(), ZC_NUM_JS_INPUTS, gen);
MappedShuffle(outputs.begin(), outputMap.begin(), ZC_NUM_JS_OUTPUTS, gen);
return JSDescription(
params, joinSplitPubKey, anchor, inputs, outputs,
vpub_old, vpub_new, computeProof,
esk // payment disclosure
);
}
bool JSDescription::Verify(
ZCJoinSplit& params,
libzcash::ProofVerifier& verifier,
const uint256& joinSplitPubKey
) const {
return false;
}
uint256 JSDescription::h_sig(ZCJoinSplit& params, const uint256& joinSplitPubKey) const
{
return params.h_sig(randomSeed, nullifiers, joinSplitPubKey);
}
std::string COutPoint::ToString() const
{
return strprintf("COutPoint(%s, %u)", hash.ToString().substr(0,10), n);

1
src/primitives/transaction.h

@ -44,6 +44,7 @@
#include "zcash/Zcash.h"
#include "zcash/JoinSplit.hpp"
#include "zcash/Proof.hpp"
#include "zcash/Note.hpp"
extern uint32_t ASSETCHAINS_MAGIC;
extern std::string ASSETCHAINS_SELFIMPORT;

7
src/rpc/blockchain.cpp

@ -1668,9 +1668,10 @@ UniValue getblockchaininfo(const UniValue& params, bool fHelp, const CPubKey& my
obj.push_back(Pair("chainwork", chainActive.LastTip()->chainPower.chainWork.GetHex()));
obj.push_back(Pair("pruned", fPruneMode));
SproutMerkleTree tree;
pcoinsTip->GetSproutAnchorAt(pcoinsTip->GetBestAnchor(SPROUT), tree);
obj.push_back(Pair("commitments", static_cast<uint64_t>(tree.size())));
//SproutMerkleTree tree;
//pcoinsTip->GetSproutAnchorAt(pcoinsTip->GetBestAnchor(SPROUT), tree);
//obj.push_back(Pair("commitments", static_cast<uint64_t>(tree.size())));
obj.push_back(Pair("commitments", 0));
CBlockIndex* tip = chainActive.LastTip();
UniValue valuePools(UniValue::VARR);

3
src/rpc/client.cpp

@ -139,6 +139,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "z_listunspent", 2 },
{ "z_listunspent", 3 },
{ "z_getbalance", 1},
{ "z_getnotescount", 0},
{ "z_gettotalbalance", 0},
{ "z_gettotalbalance", 1},
{ "z_gettotalbalance", 2},
@ -163,8 +164,6 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "kvupdate", 4 },
{ "z_importkey", 2 },
{ "z_importviewingkey", 2 },
{ "z_getpaymentdisclosure", 1},
{ "z_getpaymentdisclosure", 2},
{ "z_listsentbyaddress", 1},
{ "z_listsentbyaddress", 2},
{ "z_listsentbyaddress", 3},

76
src/rpc/misc.cpp

@ -1,5 +1,6 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core 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.
@ -203,7 +204,7 @@ UniValue getinfo(const UniValue& params, bool fHelp, const CPubKey& mypk)
" \"version\": xxxxx, (numeric) the server version\n"
" \"protocolversion\": xxxxx, (numeric) the protocol version\n"
" \"walletversion\": xxxxx, (numeric) the wallet version\n"
" \"balance\": xxxxxxx, (numeric) the total Komodo balance of the wallet\n"
" \"balance\": xxxxxxx, (numeric) the total Hush balance of the wallet\n"
" \"blocks\": xxxxxx, (numeric) the current number of blocks processed in the server\n"
" \"timeoffset\": xxxxx, (numeric) the time offset\n"
" \"connections\": xxxxx, (numeric) the number of connections\n"
@ -536,14 +537,14 @@ UniValue validateaddress(const UniValue& params, bool fHelp, const CPubKey& mypk
{
if (fHelp || params.size() != 1)
throw runtime_error(
"validateaddress \"komodoaddress\"\n"
"\nReturn information about the given Komodo address.\n"
"validateaddress \"hushaddress\"\n"
"\nReturn information about the given Hush address.\n"
"\nArguments:\n"
"1. \"komodoaddress\" (string, required) The Komodo address to validate\n"
"1. \"hushaddress\" (string, required) The Hush address to validate\n"
"\nResult:\n"
"{\n"
" \"isvalid\" : true|false, (boolean) If the address is valid or not. If not, this is the only property returned.\n"
" \"address\" : \"komodoaddress\", (string) The Komodo address validated\n"
" \"address\" : \"hushaddress\", (string) The Hush address validated\n"
" \"scriptPubKey\" : \"hex\", (string) The hex encoded scriptPubKey generated by the address\n"
" \"ismine\" : true|false, (boolean) If the address is yours or not\n"
" \"isscript\" : true|false, (boolean) If the key is a script\n"
@ -589,43 +590,6 @@ UniValue validateaddress(const UniValue& params, bool fHelp, const CPubKey& mypk
}
class DescribePaymentAddressVisitor : public boost::static_visitor<UniValue>
{
public:
UniValue operator()(const libzcash::InvalidEncoding &zaddr) const { return UniValue(UniValue::VOBJ); }
UniValue operator()(const libzcash::SproutPaymentAddress &zaddr) const {
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("type", "sprout"));
obj.push_back(Pair("payingkey", zaddr.a_pk.GetHex()));
obj.push_back(Pair("transmissionkey", zaddr.pk_enc.GetHex()));
#ifdef ENABLE_WALLET
if (pwalletMain) {
obj.push_back(Pair("ismine", pwalletMain->HaveSproutSpendingKey(zaddr)));
}
#endif
return obj;
}
UniValue operator()(const libzcash::SaplingPaymentAddress &zaddr) const {
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("type", "sapling"));
obj.push_back(Pair("diversifier", HexStr(zaddr.d)));
obj.push_back(Pair("diversifiedtransmissionkey", zaddr.pk_d.GetHex()));
#ifdef ENABLE_WALLET
if (pwalletMain) {
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingFullViewingKey fvk;
bool isMine = pwalletMain->GetSaplingIncomingViewingKey(zaddr, ivk) &&
pwalletMain->GetSaplingFullViewingKey(ivk, fvk) &&
pwalletMain->HaveSaplingSpendingKey(fvk);
obj.push_back(Pair("ismine", isMine));
}
#endif
return obj;
}
};
UniValue z_validateaddress(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 1)
@ -662,11 +626,25 @@ UniValue z_validateaddress(const UniValue& params, bool fHelp, const CPubKey& my
UniValue ret(UniValue::VOBJ);
ret.push_back(Pair("isvalid", isValid));
if (isValid)
auto zaddr = boost::get<libzcash::SaplingPaymentAddress>(&address);
if (isValid && (zaddr != nullptr))
{
ret.push_back(Pair("address", strAddress));
UniValue detail = boost::apply_visitor(DescribePaymentAddressVisitor(), address);
ret.pushKVs(detail);
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("type", "sapling"));
obj.push_back(Pair("diversifier", HexStr(zaddr->d)));
obj.push_back(Pair("diversifiedtransmissionkey", zaddr->pk_d.GetHex()));
#ifdef ENABLE_WALLET
if (pwalletMain) {
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingFullViewingKey fvk;
bool isMine = pwalletMain->GetSaplingIncomingViewingKey(*zaddr, ivk) &&
pwalletMain->GetSaplingFullViewingKey(ivk, fvk) &&
pwalletMain->HaveSaplingSpendingKey(fvk);
obj.push_back(Pair("ismine", isMine));
}
#endif
ret.pushKVs(obj);
}
return ret;
}
@ -745,9 +723,9 @@ UniValue createmultisig(const UniValue& params, bool fHelp, const CPubKey& mypk)
"\nArguments:\n"
"1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
"2. \"keys\" (string, required) A json array of keys which are Komodo addresses or hex-encoded public keys\n"
"2. \"keys\" (string, required) A json array of keys which are Hush addresses or hex-encoded public keys\n"
" [\n"
" \"key\" (string) Komodo address or hex-encoded public key\n"
" \"key\" (string) Hush address or hex-encoded public key\n"
" ,...\n"
" ]\n"
@ -781,10 +759,10 @@ UniValue verifymessage(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (fHelp || params.size() != 3)
throw runtime_error(
"verifymessage \"komodoaddress\" \"signature\" \"message\"\n"
"verifymessage \"hushaddress\" \"signature\" \"message\"\n"
"\nVerify a signed message\n"
"\nArguments:\n"
"1. \"komodoaddress\" (string, required) The Komodo address to use for the signature.\n"
"1. \"hushaddress\" (string, required) The Hush address to use for the signature.\n"
"2. \"signature\" (string, required) The signature provided by the signer in base 64 encoding (see signmessage).\n"
"3. \"message\" (string, required) The message that was signed.\n"
"\nResult:\n"

8
src/rpc/server.cpp

@ -1,6 +1,6 @@
// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2019 The Hush 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.
@ -669,11 +669,7 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "z_importviewingkey", &z_importviewingkey, true },
{ "wallet", "z_exportwallet", &z_exportwallet, true },
{ "wallet", "z_importwallet", &z_importwallet, true },
{ "wallet", "opreturn_burn", &opreturn_burn, true },
// TODO: rearrange into another category
{ "disclosure", "z_getpaymentdisclosure", &z_getpaymentdisclosure, true },
{ "disclosure", "z_validatepaymentdisclosure", &z_validatepaymentdisclosure, true }
{ "wallet", "opreturn_burn", &opreturn_burn, true }
#endif // ENABLE_WALLET
};

1
src/rpc/server.h

@ -489,6 +489,7 @@ extern UniValue z_getoperationstatus(const UniValue& params, bool fHelp, const C
extern UniValue z_getoperationresult(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue z_listoperationids(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue opreturn_burn(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue rescan(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue z_validateaddress(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcmisc.cpp
extern UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdisclosure.cpp
extern UniValue z_validatepaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdisclosure.cpp

1625
src/rpcblockchain.old

File diff suppressed because it is too large

1
src/serialize.h

@ -1,5 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2009-2014 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

27
src/test/coins_tests.cpp

@ -1,4 +1,5 @@
// Copyright (c) 2014 The Bitcoin Core 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.
@ -39,22 +40,6 @@ public:
hashBestSaplingAnchor_ = SaplingMerkleTree::empty_root();
}
bool GetSproutAnchorAt(const uint256& rt, SproutMerkleTree &tree) const {
if (rt == SproutMerkleTree::empty_root()) {
SproutMerkleTree new_tree;
tree = new_tree;
return true;
}
std::map<uint256, SproutMerkleTree>::const_iterator it = mapSproutAnchors_.find(rt);
if (it == mapSproutAnchors_.end()) {
return false;
} else {
tree = it->second;
return true;
}
}
bool GetSaplingAnchorAt(const uint256& rt, SaplingMerkleTree &tree) const {
if (rt == SaplingMerkleTree::empty_root()) {
SaplingMerkleTree new_tree;
@ -632,7 +617,7 @@ BOOST_AUTO_TEST_CASE(chained_joinsplits)
CMutableTransaction mtx;
mtx.vjoinsplit.push_back(js2);
BOOST_CHECK(!cache.HaveJoinSplitRequirements(mtx));
BOOST_CHECK(!cache.HaveShieldedRequirements(mtx));
}
{
@ -642,7 +627,7 @@ BOOST_AUTO_TEST_CASE(chained_joinsplits)
mtx.vjoinsplit.push_back(js2);
mtx.vjoinsplit.push_back(js1);
BOOST_CHECK(!cache.HaveJoinSplitRequirements(mtx));
BOOST_CHECK(!cache.HaveShieldedRequirements(mtx));
}
{
@ -650,7 +635,7 @@ BOOST_AUTO_TEST_CASE(chained_joinsplits)
mtx.vjoinsplit.push_back(js1);
mtx.vjoinsplit.push_back(js2);
BOOST_CHECK(cache.HaveJoinSplitRequirements(mtx));
BOOST_CHECK(cache.HaveShieldedRequirements(mtx));
}
{
@ -659,7 +644,7 @@ BOOST_AUTO_TEST_CASE(chained_joinsplits)
mtx.vjoinsplit.push_back(js2);
mtx.vjoinsplit.push_back(js3);
BOOST_CHECK(cache.HaveJoinSplitRequirements(mtx));
BOOST_CHECK(cache.HaveShieldedRequirements(mtx));
}
{
@ -669,7 +654,7 @@ BOOST_AUTO_TEST_CASE(chained_joinsplits)
mtx.vjoinsplit.push_back(js2);
mtx.vjoinsplit.push_back(js3);
BOOST_CHECK(cache.HaveJoinSplitRequirements(mtx));
BOOST_CHECK(cache.HaveShieldedRequirements(mtx));
}
}

37
src/test/key_tests.cpp

@ -1,4 +1,5 @@
// Copyright (c) 2012-2013 The Bitcoin Core 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.
@ -22,6 +23,7 @@
using namespace std;
using namespace libzcash;
//TODO: convert to Hush addresses
static const std::string strSecret1 = "5HxWvvfubhXpYYpS3tJkw6fq9jE9j18THftkZjHHfmFiWtmAbrj";
static const std::string strSecret2 = "5KC4ejrDjv152FGwP386VD1i2NYc5KkfSMyv1nGy1VGDxGHqVY3";
static const std::string strSecret1C = "Kwr371tjA9u2rFSMZjTNun2PXXP3WPZu2afRHTcta6KxEUdm1vEw";
@ -186,41 +188,6 @@ BOOST_AUTO_TEST_CASE(key_test1)
BOOST_CHECK(detsigc == ParseHex("2052d8a32079c11e79db95af63bb9600c5b04f21a9ca33dc129c2bfa8ac9dc1cd561d8ae5e0f6c1a16bde3719c64c2fd70e404b6428ab9a69566962e8771b5944d"));
}
BOOST_AUTO_TEST_CASE(zc_address_test)
{
for (size_t i = 0; i < 1000; i++) {
auto sk = SproutSpendingKey::random();
{
string sk_string = EncodeSpendingKey(sk);
BOOST_CHECK(sk_string[0] == 'S');
BOOST_CHECK(sk_string[1] == 'K');
auto spendingkey2 = DecodeSpendingKey(sk_string);
BOOST_CHECK(IsValidSpendingKey(spendingkey2));
BOOST_ASSERT(boost::get<SproutSpendingKey>(&spendingkey2) != nullptr);
auto sk2 = boost::get<SproutSpendingKey>(spendingkey2);
BOOST_CHECK(sk.inner() == sk2.inner());
}
{
auto addr = sk.address();
std::string addr_string = EncodePaymentAddress(addr);
BOOST_CHECK(addr_string[0] == 'z');
BOOST_CHECK(addr_string[1] == 'c');
auto paymentaddr2 = DecodePaymentAddress(addr_string);
BOOST_ASSERT(IsValidPaymentAddress(paymentaddr2));
BOOST_ASSERT(boost::get<SproutPaymentAddress>(&paymentaddr2) != nullptr);
auto addr2 = boost::get<SproutPaymentAddress>(paymentaddr2);
BOOST_CHECK(addr.a_pk == addr2.a_pk);
BOOST_CHECK(addr.pk_enc == addr2.pk_enc);
}
}
}
BOOST_AUTO_TEST_CASE(zs_address_test)
{
SelectParams(CBaseChainParams::REGTEST);

67
src/test/rpc_wallet_tests.cpp

@ -1,6 +1,7 @@
// Copyright (c) 2013-2014 The Bitcoin Core 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.
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#include "rpc/server.h"
#include "rpc/client.h"
@ -341,38 +342,12 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_validateaddress)
BOOST_CHECK_THROW(CallRPC("z_validateaddress"), runtime_error);
BOOST_CHECK_THROW(CallRPC("z_validateaddress toomany args"), runtime_error);
// Wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==0);
// This address is not valid, it belongs to another network
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress ztaaga95QAPyp1kSQ1hD2kguCpzyMHjxWZqaYDEkzbvo7uYQYAw2S8X4Kx98AvhhofMtQL8PAXKHuZsmhRcanavKRKmdCzk"));
UniValue resultObj = retValue.get_obj();
bool b = find_value(resultObj, "isvalid").get_bool();
BOOST_CHECK_EQUAL(b, false);
// This address is valid, but the spending key is not in this wallet
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress zcfA19SDAKRYHLoRDoShcoz4nPohqWxuHcqg8WAxsiB2jFrrs6k7oSvst3UZvMYqpMNSRBkxBsnyjjngX5L55FxMzLKach8"));
resultObj = retValue.get_obj();
b = find_value(resultObj, "isvalid").get_bool();
BOOST_CHECK_EQUAL(b, true);
BOOST_CHECK_EQUAL(find_value(resultObj, "type").get_str(), "sprout");
b = find_value(resultObj, "ismine").get_bool();
BOOST_CHECK_EQUAL(b, false);
// Let's import a spending key to the wallet and validate its payment address
BOOST_CHECK_NO_THROW(CallRPC("z_importkey SKxoWv77WGwFnUJitQKNEcD636bL4X5Gd6wWmgaA4Q9x8jZBPJXT"));
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress zcWsmqT4X2V4jgxbgiCzyrAfRT1vi1F4sn7M5Pkh66izzw8Uk7LBGAH3DtcSMJeUb2pi3W4SQF8LMKkU2cUuVP68yAGcomL"));
resultObj = retValue.get_obj();
b = find_value(resultObj, "isvalid").get_bool();
BOOST_CHECK_EQUAL(b, true);
BOOST_CHECK_EQUAL(find_value(resultObj, "type").get_str(), "sprout");
b = find_value(resultObj, "ismine").get_bool();
BOOST_CHECK_EQUAL(b, true);
BOOST_CHECK_EQUAL(find_value(resultObj, "payingkey").get_str(), "f5bb3c888ccc9831e3f6ba06e7528e26a312eec3acc1823be8918b6a3a5e20ad");
BOOST_CHECK_EQUAL(find_value(resultObj, "transmissionkey").get_str(), "7a58c7132446564e6b810cf895c20537b3528357dc00150a8e201f491efa9c1a");
// This Sapling address is not valid, it belongs to another network
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_validateaddress ztestsapling1knww2nyjc62njkard0jmx7hlsj6twxmxwprn7anvrv4dc2zxanl3nemc0qx2hvplxmd2uau8gyw"));
resultObj = retValue.get_obj();
@ -398,16 +373,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_exportwallet)
{
LOCK2(cs_main, pwalletMain->cs_wallet);
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==0);
// wallet should have one key
libzcash::SproutPaymentAddress addr = pwalletMain->GenerateNewSproutZKey();
pwalletMain->GetSproutPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==1);
// Set up paths
boost::filesystem::path tmppath = boost::filesystem::temp_directory_path();
boost::filesystem::path tmpfilename = boost::filesystem::unique_path("%%%%%%%%");
@ -429,9 +394,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_exportwallet)
BOOST_CHECK_NO_THROW(CallRPC(string("z_exportwallet ") + tmpfilename.string()));
libzcash::SproutSpendingKey key;
BOOST_CHECK(pwalletMain->GetSproutSpendingKey(addr, key));
std::string s1 = EncodePaymentAddress(addr);
std::string s2 = EncodeSpendingKey(key);
@ -474,13 +436,13 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importwallet)
BOOST_CHECK_THROW(CallRPC("z_importwallet toomany args"), runtime_error);
// create a random key locally
auto testSpendingKey = libzcash::SproutSpendingKey::random();
auto testSpendingKey = libzcash::SaplingSpendingKey::random();
auto testPaymentAddress = testSpendingKey.address();
std::string testAddr = EncodePaymentAddress(testPaymentAddress);
std::string testKey = EncodeSpendingKey(testSpendingKey);
// create test data using the random key
std::string format_str = "# Wallet dump created by Komodo v0.11.2.0.z8-9155cc6-dirty (2016-08-11 11:37:00 -0700)\n"
std::string format_str = "# Wallet dump created by Hush v0.11.2.0.z8-9155cc6-dirty (2016-08-11 11:37:00 -0700)\n"
"# * Created on 2016-08-12T21:55:36Z\n"
"# * Best block at time of backup was 0 (0de0a3851fef2d433b9b4f51d4342bdd24c5ddd793eb8fba57189f07e9235d52),\n"
"# mined on 2009-01-03T18:15:05Z\n"
@ -503,28 +465,31 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importwallet)
file << std::flush;
// wallet should currently be empty
std::set<libzcash::SproutPaymentAddress> addrs;
pwalletMain->GetSproutPaymentAddresses(addrs);
std::set<libzcash::SaplingPaymentAddress> addrs;
pwalletMain->GetSaplingPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==0);
// import test data from file into wallet
BOOST_CHECK_NO_THROW(CallRPC(string("z_importwallet ") + path));
// wallet should now have one zkey
pwalletMain->GetSproutPaymentAddresses(addrs);
pwalletMain->GetSaplingPaymentAddresses(addrs);
BOOST_CHECK(addrs.size()==1);
// check that we have the spending key for the address
/*
auto address = DecodePaymentAddress(testAddr);
BOOST_CHECK(IsValidPaymentAddress(address));
BOOST_ASSERT(boost::get<libzcash::SproutPaymentAddress>(&address) != nullptr);
auto addr = boost::get<libzcash::SproutPaymentAddress>(address);
BOOST_CHECK(pwalletMain->HaveSproutSpendingKey(addr));
*/
// Verify the spending key is the same as the test data
libzcash::SproutSpendingKey k;
BOOST_CHECK(pwalletMain->GetSproutSpendingKey(addr, k));
BOOST_CHECK_EQUAL(testKey, EncodeSpendingKey(k));
//libzcash::SproutSpendingKey k;
//BOOST_CHECK(pwalletMain->GetSproutSpendingKey(addr, k));
//BOOST_CHECK_EQUAL(testKey, EncodeSpendingKey(k));
}
@ -606,9 +571,8 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
// Verify number of addresses stored in wallet is n1+n2
int numAddrs = myaddrs.size();
BOOST_CHECK(numAddrs == (2 * n1) + n2);
pwalletMain->GetSproutPaymentAddresses(addrs);
pwalletMain->GetSaplingPaymentAddresses(saplingAddrs);
BOOST_CHECK(addrs.size() + saplingAddrs.size() == numAddrs);
BOOST_CHECK(saplingAddrs.size() == numAddrs);
// Ask wallet to list addresses
BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses"));
@ -630,9 +594,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport)
std::string newaddress = retValue.get_str();
auto address = DecodePaymentAddress(newaddress);
BOOST_CHECK(IsValidPaymentAddress(address));
BOOST_ASSERT(boost::get<libzcash::SproutPaymentAddress>(&address) != nullptr);
auto newAddr = boost::get<libzcash::SproutPaymentAddress>(address);
BOOST_CHECK(pwalletMain->HaveSproutSpendingKey(newAddr));
// Check if too many args
BOOST_CHECK_THROW(CallRPC("z_getnewaddress toomanyargs"), runtime_error);

1
src/test/transaction_tests.cpp

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
// Copyright (c) 2011-2014 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

1
src/timedata.cpp

@ -1,4 +1,5 @@
// Copyright (c) 2014 The Bitcoin Core 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.

1
src/timedata.h

@ -1,4 +1,5 @@
// Copyright (c) 2014 The Bitcoin Core 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.

1
src/torcontrol.cpp

@ -1,5 +1,6 @@
// Copyright (c) 2015-2017 The Bitcoin Core developers
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

1
src/torcontrol.h

@ -1,4 +1,5 @@
// Copyright (c) 2015 The Bitcoin Core 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.

3
src/transaction_builder.cpp

@ -1,7 +1,5 @@
// Copyright (c) 2018 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Released under the GPLv3
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -14,6 +12,7 @@
#include <boost/variant.hpp>
#include <boost/optional/optional_io.hpp>
#include <librustzcash.h>
#include "zcash/Note.hpp"
SpendDescriptionInfo::SpendDescriptionInfo(
libzcash::SaplingExpandedSpendingKey expsk,

1
src/transaction_builder.h

@ -1,4 +1,5 @@
// Copyright (c) 2018 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.

12
src/txdb.cpp

@ -67,18 +67,6 @@ CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(Get
}
bool CCoinsViewDB::GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
if (rt == SproutMerkleTree::empty_root()) {
SproutMerkleTree new_tree;
tree = new_tree;
return true;
}
bool read = db.Read(make_pair(DB_SPROUT_ANCHOR, rt), tree);
return read;
}
bool CCoinsViewDB::GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const {
if (rt == SaplingMerkleTree::empty_root()) {
SaplingMerkleTree new_tree;

2
src/txdb.h

@ -63,7 +63,7 @@ protected:
public:
CCoinsViewDB(size_t nCacheSize, bool fMemory = false, bool fWipe = false);
bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const;
//bool GetSproutAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const;
bool GetSaplingAnchorAt(const uint256 &rt, SaplingMerkleTree &tree) const;
bool GetNullifier(const uint256 &nf, ShieldedType type) const;
bool GetCoins(const uint256 &txid, CCoins &coins) const;

15
src/txmempool.cpp

@ -513,7 +513,7 @@ void CTxMemPool::removeConflicts(const CTransaction &tx, std::list<CTransaction>
int32_t komodo_validate_interest(const CTransaction &tx,int32_t txheight,uint32_t nTime,int32_t dispflag);
extern char ASSETCHAINS_SYMBOL[];
void CTxMemPool::removeExpired(unsigned int nBlockHeight)
std::vector<uint256> CTxMemPool::removeExpired(unsigned int nBlockHeight)
{
CBlockIndex *tipindex;
// Remove expired txs from the mempool
@ -523,16 +523,23 @@ void CTxMemPool::removeExpired(unsigned int nBlockHeight)
{
const CTransaction& tx = it->GetTx();
tipindex = chainActive.LastTip();
if (IsExpiredTx(tx, nBlockHeight) || (ASSETCHAINS_SYMBOL[0] == 0 && tipindex != 0 && komodo_validate_interest(tx,tipindex->GetHeight()+1,tipindex->GetMedianTimePast() + 777,0)) < 0)
bool fInterestNotValidated = ASSETCHAINS_SYMBOL[0] == 0 && tipindex != 0 && komodo_validate_interest(tx,tipindex->GetHeight()+1,tipindex->GetMedianTimePast() + 777,0) < 0;
if (IsExpiredTx(tx, nBlockHeight) || fInterestNotValidated)
{
if (fInterestNotValidated && tipindex != 0)
LogPrintf("Removing interest violate txid.%s nHeight.%d nTime.%u vs locktime.%u\n",tx.GetHash().ToString(),tipindex->GetHeight()+1,tipindex->GetMedianTimePast() + 777,tx.nLockTime);
transactionsToRemove.push_back(tx);
}
}
std::vector<uint256> ids;
for (const CTransaction& tx : transactionsToRemove) {
list<CTransaction> removed;
remove(tx, removed, true);
ids.push_back(tx.GetHash());
LogPrint("mempool", "Removing expired txid: %s\n", tx.GetHash().ToString());
}
return ids;
}
/**
@ -637,8 +644,9 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
i++;
}
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
/*
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
BOOST_FOREACH(const JSDescription &joinsplit, tx.vjoinsplit) {
BOOST_FOREACH(const uint256 &nf, joinsplit.nullifiers) {
assert(!pcoins->GetNullifier(nf, SPROUT));
@ -659,6 +667,7 @@ void CTxMemPool::check(const CCoinsViewCache *pcoins) const
intermediates.insert(std::make_pair(tree.root(), tree));
}
*/
for (const SpendDescription &spendDescription : tx.vShieldedSpend) {
SaplingMerkleTree tree;

2
src/txmempool.h

@ -218,7 +218,7 @@ public:
void removeWithAnchor(const uint256 &invalidRoot, ShieldedType type);
void removeForReorg(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight, int flags);
void removeConflicts(const CTransaction &tx, std::list<CTransaction>& removed);
void removeExpired(unsigned int nBlockHeight);
std::vector<uint256> removeExpired(unsigned int nBlockHeight);
void removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
std::list<CTransaction>& conflicts, bool fCurrentEstimate = true);
void removeWithoutBranchId(uint32_t nMemPoolBranchId);

3
src/ui_interface.h

@ -98,6 +98,9 @@ public:
/** New block has been accepted */
boost::signals2::signal<void (const uint256& hash)> NotifyBlockTip;
/** Transaction expired */
boost::signals2::signal<void (const uint256& txid)> NotifyTxExpiration;
};
extern CClientUIInterface uiInterface;

1
src/util.h

@ -1,5 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core 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.

3
src/utilstrencodings.cpp

@ -20,7 +20,8 @@ static const string CHARS_ALPHA_NUM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO
static const string SAFE_CHARS[] =
{
CHARS_ALPHA_NUM + " .,;_/:?@()", // SAFE_CHARS_DEFAULT
CHARS_ALPHA_NUM + " .,;_?@" // SAFE_CHARS_UA_COMMENT
CHARS_ALPHA_NUM + " .,;_?@", // SAFE_CHARS_UA_COMMENT
CHARS_ALPHA_NUM + "!*'();:@&=+$,/?#[]-_.~%" // SAFE_CHARS_URI
};
string SanitizeString(const string& str, int rule)

3
src/utilstrencodings.h

@ -26,7 +26,8 @@
enum SafeChars
{
SAFE_CHARS_DEFAULT, //!< The full set of allowed chars
SAFE_CHARS_UA_COMMENT //!< BIP-0014 subset
SAFE_CHARS_UA_COMMENT, //!< BIP-0014 subset
SAFE_CHARS_URI //!< Chars allowed in URIs (RFC 3986)
};
std::string SanitizeFilename(const std::string& str);

154
src/utiltest.cpp

@ -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;
}

19
src/utiltest.h

@ -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);

4
src/validationinterface.cpp

@ -1,5 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
@ -138,7 +139,8 @@ void ThreadNotifyWallets(CBlockIndex *pindexLastTip)
while (pindex && pindex != pindexFork) {
// Get the Sprout commitment tree as of the start of this block.
SproutMerkleTree oldSproutTree;
assert(pcoinsTip->GetSproutAnchorAt(pindex->hashSproutAnchor, oldSproutTree));
//TODO: how important is oldSproutTree ?
//assert(pcoinsTip->GetSproutAnchorAt(pindex->hashSproutAnchor, oldSproutTree));
// Get the Sapling commitment tree as of the start of this block.
// We can get this from the `hashFinalSaplingRoot` of the last block

565
src/wallet/asyncrpcoperation_mergetoaddress.cpp

@ -46,7 +46,6 @@
#include <string>
#include <thread>
#include "paymentdisclosuredb.h"
int32_t komodo_blockheight(uint256 hash);
using namespace libzcash;
@ -75,19 +74,17 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient,
CAmount fee,
UniValue contextInfo) :
tx_(contextualTx), utxoInputs_(utxoInputs), sproutNoteInputs_(sproutNoteInputs),
saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
tx_(contextualTx), utxoInputs_(utxoInputs), saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), contextinfo_(contextInfo)
{
if (fee < 0 || fee > MAX_MONEY) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range");
}
if (utxoInputs.empty() && sproutNoteInputs.empty() && saplingNoteInputs.empty()) {
if (utxoInputs.empty() && saplingNoteInputs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "No inputs");
}
@ -95,14 +92,6 @@ saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), context
throw JSONRPCError(RPC_INVALID_PARAMETER, "Recipient parameter missing");
}
if (sproutNoteInputs.size() > 0 && saplingNoteInputs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
}
if (sproutNoteInputs.size() > 0 && builder) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Sprout notes are not supported by the TransactionBuilder");
}
isUsingBuilder_ = false;
if (builder) {
isUsingBuilder_ = true;
@ -133,9 +122,6 @@ saplingNoteInputs_(saplingNoteInputs), recipient_(recipient), fee_(fee), context
// Lock UTXOs
lock_utxos();
lock_notes();
// Enable payment disclosure if requested
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
}
AsyncRPCOperation_mergetoaddress::~AsyncRPCOperation_mergetoaddress()
@ -210,31 +196,16 @@ void AsyncRPCOperation_mergetoaddress::main()
unlock_utxos(); // clean up
unlock_notes(); // clean up
// !!! Payment disclosure START
if (success && paymentDisclosureMode && paymentDisclosureData_.size() > 0) {
uint256 txidhash = tx_.GetHash();
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
p.first.hash = txidhash;
if (!db->Put(p.first, p.second)) {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
} else {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
}
}
}
// !!! Payment disclosure END
}
// Notes:
// 1. #1359 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
// 1. #1359 Currently there is no limit set on the number of inputs+outputs, so size of tx could be invalid.
// 2. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them.
bool AsyncRPCOperation_mergetoaddress::main_impl()
{
assert(isToTaddr_ != isToZaddr_);
bool isPureTaddrOnlyTx = (sproutNoteInputs_.empty() && saplingNoteInputs_.empty() && isToTaddr_);
bool isPureTaddrOnlyTx = (saplingNoteInputs_.empty() && isToTaddr_);
CAmount minersFee = fee_;
size_t numInputs = utxoInputs_.size();
@ -259,9 +230,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
}
CAmount z_inputs_total = 0;
for (const MergeToAddressInputSproutNote& t : sproutNoteInputs_) {
z_inputs_total += std::get<2>(t);
}
for (const MergeToAddressInputSaplingNote& t : saplingNoteInputs_) {
z_inputs_total += std::get<2>(t);
@ -312,7 +280,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
/**
* SCENARIO #0
*
* Sprout not involved, so we just use the TransactionBuilder and we're done.
* Only sapling involved, so we just use the TransactionBuilder and we're done.
*
* This is based on code from AsyncRPCOperation_sendmany::main_impl() and should be refactored.
*/
@ -429,7 +397,6 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
* END SCENARIO #0
*/
/**
* SCENARIO #1
*
@ -447,337 +414,7 @@ bool AsyncRPCOperation_mergetoaddress::main_impl()
* END SCENARIO #1
*/
// Prepare raw transaction to handle JoinSplits
CMutableTransaction mtx(tx_);
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_);
mtx.joinSplitPubKey = joinSplitPubKey_;
tx_ = CTransaction(mtx);
std::string hexMemo = std::get<1>(recipient_);
/**
* SCENARIO #2
*
* taddrs -> zaddr
*
* We only need a single JoinSplit.
*/
if (sproutNoteInputs_.empty() && isToZaddr_) {
// Create JoinSplit to target z-addr.
MergeToAddressJSInfo info;
info.vpub_old = sendAmount;
info.vpub_new = 0;
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(toPaymentAddress_), sendAmount);
if (hexMemo.size() > 0) {
jso.memo = get_memo_from_hex_string(hexMemo);
}
info.vjsout.push_back(jso);
UniValue obj(UniValue::VOBJ);
obj = perform_joinsplit(info);
sign_send_raw_transaction(obj);
return true;
}
/**
* END SCENARIO #2
*/
// Copy zinputs to more flexible containers
std::deque<MergeToAddressInputSproutNote> zInputsDeque;
for (const auto& o : sproutNoteInputs_) {
zInputsDeque.push_back(o);
}
// When spending notes, take a snapshot of note witnesses and anchors as the treestate will
// change upon arrival of new blocks which contain joinsplit transactions. This is likely
// to happen as creating a chained joinsplit transaction can take longer than the block interval.
{
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto t : sproutNoteInputs_) {
JSOutPoint jso = std::get<0>(t);
std::vector<JSOutPoint> vOutPoints = {jso};
uint256 inputAnchor;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
jsopWitnessAnchorMap[jso.ToString()] = MergeToAddressWitnessAnchorData{vInputWitnesses[0], inputAnchor};
}
}
/**
* SCENARIO #3
*
* zaddrs -> zaddr
* taddrs ->
*
* zaddrs ->
* taddrs -> taddr
*
* Send to zaddr by chaining JoinSplits together and immediately consuming any change
* Send to taddr by creating dummy z outputs and accumulating value in a change note
* which is used to set vpub_new in the last chained joinsplit.
*/
UniValue obj(UniValue::VOBJ);
CAmount jsChange = 0; // this is updated after each joinsplit
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
bool vpubOldProcessed = false; // updated when vpub_old for taddr inputs is set in first joinsplit
bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit
// At this point, we are guaranteed to have at least one input note.
// Use address of first input note as the temporary change address.
SproutSpendingKey changeKey = std::get<3>(zInputsDeque.front());
SproutPaymentAddress changeAddress = changeKey.address();
CAmount vpubOldTarget = 0;
CAmount vpubNewTarget = 0;
if (isToTaddr_) {
vpubNewTarget = z_inputs_total;
} else {
if (utxoInputs_.empty()) {
vpubNewTarget = minersFee;
} else {
vpubOldTarget = t_inputs_total - minersFee;
}
}
// Keep track of treestate within this transaction
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
std::vector<uint256> previousCommitments;
while (!vpubNewProcessed) {
MergeToAddressJSInfo info;
info.vpub_old = 0;
info.vpub_new = 0;
// Set vpub_old in the first joinsplit
if (!vpubOldProcessed) {
if (t_inputs_total < vpubOldTarget) {
throw JSONRPCError(RPC_WALLET_ERROR,
strprintf("Insufficient transparent funds for vpub_old %s (miners fee %s, taddr inputs %s)",
FormatMoney(vpubOldTarget), FormatMoney(minersFee), FormatMoney(t_inputs_total)));
}
info.vpub_old += vpubOldTarget; // funds flowing from public pool
vpubOldProcessed = true;
}
CAmount jsInputValue = 0;
uint256 jsAnchor;
std::vector<boost::optional<SproutWitness>> witnesses;
JSDescription prevJoinSplit;
// Keep track of previous JoinSplit and its commitments
if (tx_.vjoinsplit.size() > 0) {
prevJoinSplit = tx_.vjoinsplit.back();
}
// If there is no change, the chain has terminated so we can reset the tracked treestate.
if (jsChange == 0 && tx_.vjoinsplit.size() > 0) {
intermediates.clear();
previousCommitments.clear();
}
//
// Consume change as the first input of the JoinSplit.
//
if (jsChange > 0) {
LOCK2(cs_main, pwalletMain->cs_wallet);
// Update tree state with previous joinsplit
SproutMerkleTree tree;
auto it = intermediates.find(prevJoinSplit.anchor);
if (it != intermediates.end()) {
tree = it->second;
} else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
}
assert(changeOutputIndex != -1);
boost::optional<SproutWitness> changeWitness;
int n = 0;
for (const uint256& commitment : prevJoinSplit.commitments) {
tree.append(commitment);
previousCommitments.push_back(commitment);
if (!changeWitness && changeOutputIndex == n++) {
changeWitness = tree.witness();
} else if (changeWitness) {
changeWitness.get().append(commitment);
}
}
if (changeWitness) {
witnesses.push_back(changeWitness);
}
jsAnchor = tree.root();
intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries)
// Decrypt the change note's ciphertext to retrieve some data we need
ZCNoteDecryption decryptor(changeKey.receiving_key());
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
try {
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
decryptor,
prevJoinSplit.ciphertexts[changeOutputIndex],
prevJoinSplit.ephemeralKey,
hSig,
(unsigned char)changeOutputIndex);
SproutNote note = plaintext.note(changeAddress);
info.notes.push_back(note);
info.zkeys.push_back(changeKey);
jsInputValue += plaintext.value();
LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n",
getId(),
FormatMoney(plaintext.value()));
} catch (const std::exception& e) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what()));
}
}
//
// Consume spendable non-change notes
//
std::vector<SproutNote> vInputNotes;
std::vector<SproutSpendingKey> vInputZKeys;
std::vector<JSOutPoint> vOutPoints;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
uint256 inputAnchor;
int numInputsNeeded = (jsChange > 0) ? 1 : 0;
while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) {
MergeToAddressInputSproutNote t = zInputsDeque.front();
JSOutPoint jso = std::get<0>(t);
SproutNote note = std::get<1>(t);
CAmount noteFunds = std::get<2>(t);
SproutSpendingKey zkey = std::get<3>(t);
zInputsDeque.pop_front();
MergeToAddressWitnessAnchorData wad = jsopWitnessAnchorMap[jso.ToString()];
vInputWitnesses.push_back(wad.witness);
if (inputAnchor.IsNull()) {
inputAnchor = wad.anchor;
} else if (inputAnchor != wad.anchor) {
throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor");
}
vOutPoints.push_back(jso);
vInputNotes.push_back(note);
vInputZKeys.push_back(zkey);
jsInputValue += noteFunds;
int wtxHeight = -1;
int wtxDepth = -1;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash];
// Zero confirmation notes belong to transactions which have not yet been mined
if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString()));
}
wtxHeight = komodo_blockheight(wtx.hashBlock);
wtxDepth = wtx.GetDepthInMainChain();
}
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
getId(),
jso.hash.ToString().substr(0, 10),
jso.js,
int(jso.n), // uint8_t
FormatMoney(noteFunds),
wtxHeight,
wtxDepth);
}
// Add history of previous commitments to witness
if (vInputNotes.size() > 0) {
if (vInputWitnesses.size() == 0) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment");
}
for (auto& optionalWitness : vInputWitnesses) {
if (!optionalWitness) {
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null");
}
SproutWitness w = *optionalWitness; // could use .get();
if (jsChange > 0) {
for (const uint256& commitment : previousCommitments) {
w.append(commitment);
}
if (jsAnchor != w.root()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input");
}
}
witnesses.push_back(w);
}
// The jsAnchor is null if this JoinSplit is at the start of a new chain
if (jsAnchor.IsNull()) {
jsAnchor = inputAnchor;
}
// Add spendable notes as inputs
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
std::copy(vInputZKeys.begin(), vInputZKeys.end(), std::back_inserter(info.zkeys));
}
// Accumulate change
jsChange = jsInputValue + info.vpub_old;
// Set vpub_new in the last joinsplit (when there are no more notes to spend)
if (zInputsDeque.empty()) {
assert(!vpubNewProcessed);
if (jsInputValue < vpubNewTarget) {
throw JSONRPCError(RPC_WALLET_ERROR,
strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr inputs %s)",
FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_inputs_total)));
}
info.vpub_new += vpubNewTarget; // funds flowing back to public pool
vpubNewProcessed = true;
jsChange -= vpubNewTarget;
// If we are merging to a t-addr, there should be no change
if (isToTaddr_) assert(jsChange == 0);
}
// create dummy output
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
// create output for any change
if (jsChange > 0) {
std::string outputType = "change";
auto jso = JSOutput(changeAddress, jsChange);
// If this is the final output, set the target and memo
if (isToZaddr_ && vpubNewProcessed) {
outputType = "target";
jso.addr = boost::get<libzcash::SproutPaymentAddress>(toPaymentAddress_);
if (!hexMemo.empty()) {
jso.memo = get_memo_from_hex_string(hexMemo);
}
}
info.vjsout.push_back(jso);
LogPrint("zrpcunsafe", "%s: generating note for %s (amount=%s)\n",
getId(),
outputType,
FormatMoney(jsChange));
}
obj = perform_joinsplit(info, witnesses, jsAnchor);
if (jsChange > 0) {
changeOutputIndex = mta_find_output(obj, 1);
}
}
// Sanity check in case changes to code block above exits loop by invoking 'break'
assert(zInputsDeque.size() == 0);
assert(vpubNewProcessed);
sign_send_raw_transaction(obj);
return true;
return false;
}
@ -849,190 +486,6 @@ void AsyncRPCOperation_mergetoaddress::sign_send_raw_transaction(UniValue obj)
tx_ = tx;
}
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(MergeToAddressJSInfo& info)
{
std::vector<boost::optional<SproutWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor
}
return perform_joinsplit(info, witnesses, anchor);
}
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(MergeToAddressJSInfo& info, std::vector<JSOutPoint>& outPoints)
{
std::vector<boost::optional<SproutWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
pwalletMain->GetSproutNoteWitnesses(outPoints, witnesses, anchor);
}
return perform_joinsplit(info, witnesses, anchor);
}
UniValue AsyncRPCOperation_mergetoaddress::perform_joinsplit(
MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor)
{
if (anchor.IsNull()) {
throw std::runtime_error("anchor is null");
}
if (witnesses.size() != info.notes.size()) {
throw runtime_error("number of notes and witnesses do not match");
}
if (info.notes.size() != info.zkeys.size()) {
throw runtime_error("number of notes and spending keys do not match");
}
for (size_t i = 0; i < witnesses.size(); i++) {
if (!witnesses[i]) {
throw runtime_error("joinsplit input could not be found in tree");
}
info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], info.zkeys[i]));
}
// Make sure there are two inputs and two outputs
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
info.vjsin.push_back(JSInput());
}
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
info.vjsout.push_back(JSOutput());
}
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
throw runtime_error("unsupported joinsplit input/output counts");
}
CMutableTransaction mtx(tx_);
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
getId(),
tx_.vjoinsplit.size(),
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value));
// Generate the proof, this can take over a minute.
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs{info.vjsin[0], info.vjsin[1]};
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs{info.vjsout[0], info.vjsout[1]};
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
uint256 esk; // payment disclosure - secret
JSDescription jsdesc = JSDescription::Randomized(
*pzcashParams,
joinSplitPubKey_,
anchor,
inputs,
outputs,
inputMap,
outputMap,
info.vpub_old,
info.vpub_new,
!this->testmode,
&esk); // parameter expects pointer to esk, so pass in address
{
auto verifier = libzcash::ProofVerifier::Strict();
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
throw std::runtime_error("error verifying joinsplit");
}
}
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_);
// Add the signature
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey_) == 0)) {
throw std::runtime_error("crypto_sign_detached failed");
}
// Sanity check
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
dataToBeSigned.begin(), 32,
mtx.joinSplitPubKey.begin()) == 0)) {
throw std::runtime_error("crypto_sign_verify_detached failed");
}
CTransaction rawTx(mtx);
tx_ = rawTx;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << rawTx;
std::string encryptedNote1;
std::string encryptedNote2;
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char)0x00);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[0];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
}
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char)0x01);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[1];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
}
UniValue arrInputMap(UniValue::VARR);
UniValue arrOutputMap(UniValue::VARR);
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
}
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
}
// !!! Payment disclosure START
unsigned char buffer[32] = {0};
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
uint256 joinSplitPrivKey = uint256(vch);
size_t js_index = tx_.vjoinsplit.size() - 1;
uint256 placeholder;
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
uint8_t mapped_index = outputMap[i];
// placeholder for txid will be filled in later when tx has been finalized and signed.
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
JSOutput output = outputs[mapped_index];
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
}
// !!! Payment disclosure END
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("encryptednote1", encryptedNote1));
obj.push_back(Pair("encryptednote2", encryptedNote2));
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
obj.push_back(Pair("inputmap", arrInputMap));
obj.push_back(Pair("outputmap", arrOutputMap));
return obj;
}
std::array<unsigned char, ZC_MEMO_SIZE> AsyncRPCOperation_mergetoaddress::get_memo_from_hex_string(std::string s)
{
std::array<unsigned char, ZC_MEMO_SIZE> memo = {{0x00}};
@ -1099,9 +552,6 @@ void AsyncRPCOperation_mergetoaddress::unlock_utxos() {
*/
void AsyncRPCOperation_mergetoaddress::lock_notes() {
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto note : sproutNoteInputs_) {
pwalletMain->LockNote(std::get<0>(note));
}
for (auto note : saplingNoteInputs_) {
pwalletMain->LockNote(std::get<0>(note));
}
@ -1112,9 +562,6 @@ void AsyncRPCOperation_mergetoaddress::lock_notes() {
*/
void AsyncRPCOperation_mergetoaddress::unlock_notes() {
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto note : sproutNoteInputs_) {
pwalletMain->UnlockNote(std::get<0>(note));
}
for (auto note : saplingNoteInputs_) {
pwalletMain->UnlockNote(std::get<0>(note));
}

60
src/wallet/asyncrpcoperation_mergetoaddress.h

@ -1,4 +1,6 @@
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -22,7 +24,6 @@
#include "amount.h"
#include "asyncrpcoperation.h"
#include "paymentdisclosure.h"
#include "primitives/transaction.h"
#include "transaction_builder.h"
#include "wallet.h"
@ -43,29 +44,11 @@ using namespace libzcash;
// Input UTXO is a tuple of txid, vout, amount, script
typedef std::tuple<COutPoint, CAmount, CScript> MergeToAddressInputUTXO;
// Input JSOP is a tuple of JSOutpoint, note, amount, spending key
typedef std::tuple<JSOutPoint, SproutNote, CAmount, SproutSpendingKey> MergeToAddressInputSproutNote;
typedef std::tuple<SaplingOutPoint, SaplingNote, CAmount, SaplingExpandedSpendingKey> MergeToAddressInputSaplingNote;
// A recipient is a tuple of address, memo (optional if zaddr)
typedef std::tuple<std::string, std::string> MergeToAddressRecipient;
// Package of info which is passed to perform_joinsplit methods.
struct MergeToAddressJSInfo {
std::vector<JSInput> vjsin;
std::vector<JSOutput> vjsout;
std::vector<SproutNote> notes;
std::vector<SproutSpendingKey> zkeys;
CAmount vpub_old = 0;
CAmount vpub_new = 0;
};
// A struct to help us track the witness and anchor for a given JSOutPoint
struct MergeToAddressWitnessAnchorData {
boost::optional<SproutWitness> witness;
uint256 anchor;
};
class AsyncRPCOperation_mergetoaddress : public AsyncRPCOperation
{
@ -74,7 +57,6 @@ public:
boost::optional<TransactionBuilder> builder,
CMutableTransaction contextualTx,
std::vector<MergeToAddressInputUTXO> utxoInputs,
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs,
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs,
MergeToAddressRecipient recipient,
CAmount fee = MERGE_TO_ADDRESS_OPERATION_DEFAULT_MINERS_FEE,
@ -93,8 +75,6 @@ public:
bool testmode = false; // Set to true to disable sending txs and generating proofs
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
private:
friend class TEST_FRIEND_AsyncRPCOperation_mergetoaddress; // class for unit testing
@ -113,11 +93,7 @@ private:
uint256 joinSplitPubKey_;
unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES];
// The key is the result string from calling JSOutPoint::ToString()
std::unordered_map<std::string, MergeToAddressWitnessAnchorData> jsopWitnessAnchorMap;
std::vector<MergeToAddressInputUTXO> utxoInputs_;
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs_;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs_;
TransactionBuilder builder_;
@ -126,18 +102,6 @@ private:
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
bool main_impl();
// JoinSplit without any input notes to spend
UniValue perform_joinsplit(MergeToAddressJSInfo&);
// JoinSplit with input notes to spend (JSOutPoints))
UniValue perform_joinsplit(MergeToAddressJSInfo&, std::vector<JSOutPoint>&);
// JoinSplit where you have the witnesses and anchor
UniValue perform_joinsplit(
MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor);
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
void lock_utxos();
@ -148,8 +112,6 @@ private:
void unlock_notes();
// payment disclosure!
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
};
@ -183,24 +145,6 @@ public:
return delegate->main_impl();
}
UniValue perform_joinsplit(MergeToAddressJSInfo& info)
{
return delegate->perform_joinsplit(info);
}
UniValue perform_joinsplit(MergeToAddressJSInfo& info, std::vector<JSOutPoint>& v)
{
return delegate->perform_joinsplit(info, v);
}
UniValue perform_joinsplit(
MergeToAddressJSInfo& info,
std::vector<boost::optional<SproutWitness>> witnesses,
uint256 anchor)
{
return delegate->perform_joinsplit(info, witnesses, anchor);
}
void sign_send_raw_transaction(UniValue obj)
{
delegate->sign_send_raw_transaction(obj);

18
src/wallet/asyncrpcoperation_saplingconsolidation.cpp

@ -1,4 +1,4 @@
// Copyright (c) 2020 The Hush developers
// Copyright (c) 2019-2020 The Hush developers
// Copyright (c) 2019 CryptoForge
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -87,24 +87,30 @@ bool AsyncRPCOperation_saplingconsolidation::main_impl() {
return status;
}
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
std::set<libzcash::SaplingPaymentAddress> addresses;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
// We set minDepth to 11 to avoid unconfirmed notes and in anticipation of specifying
// an anchor at height N-10 for each Sprout JoinSplit description
// an anchor at height N-10 for each SpendDescription
// Consider, should notes be sorted?
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, "", 11);
pwalletMain->GetFilteredNotes(saplingEntries, "", 11);
if(saplingEntries.size() == 0) {
LogPrint("zrpcunsafe", "%s: Nothing to consolidate, done.\n",opid);
return true;
}
if (fConsolidationMapUsed) {
const vector<string>& v = mapMultiArgs["-consolidatesaplingaddress"];
for(int i = 0; i < v.size(); i++) {
auto zAddress = DecodePaymentAddress(v[i]);
if (boost::get<libzcash::SaplingPaymentAddress>(&zAddress) != nullptr) {
libzcash::SaplingPaymentAddress saplingAddress = boost::get<libzcash::SaplingPaymentAddress>(zAddress);
addresses.insert(saplingAddress );
addresses.insert(saplingAddress);
} else {
//TODO: how to handle invalid zaddrs?
LogPrint("zrpcunsafe", "%s: Invalid zaddr, exiting\n", opid);
return false;
}
}
} else {

2
src/wallet/asyncrpcoperation_saplingconsolidation.h

@ -1,5 +1,5 @@
// Copyright (c) 2020 The Hush developers
// TODO: Forge should add his preferred copyright line here
// Copyright (c) 2019 CryptoForge
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

671
src/wallet/asyncrpcoperation_sendmany.cpp

@ -1,7 +1,7 @@
// 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.
// file COPYING or https://www.opensource.org/licenses/mit-license.php
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
@ -49,7 +49,6 @@
#include <thread>
#include <string>
#include "paymentdisclosuredb.h"
#include <boost/optional/optional_io.hpp>
using namespace libzcash;
@ -141,10 +140,6 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
} else {
LogPrint("zrpc", "%s: z_sendmany initialized\n", getId());
}
// Enable payment disclosure if requested
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
}
AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() {
@ -211,25 +206,10 @@ void AsyncRPCOperation_sendmany::main() {
s += strprintf(", error=%s)\n", getErrorMessage());
}
LogPrintf("%s",s);
// !!! Payment disclosure START
if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) {
uint256 txidhash = tx_.GetHash();
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
p.first.hash = txidhash;
if (!db->Put(p.first, p.second)) {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
} else {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
}
}
}
// !!! Payment disclosure END
}
// Notes:
// 1. #1159 Currently there is no limit set on the number of joinsplits, so size of tx could be invalid.
// 1. #1159 Currently there is no limit set on the number of shielded spends, so size of tx could be invalid.
// 2. #1360 Note selection is not optimal
// 3. #1277 Spendable notes are not locked, so an operation running in parallel could also try to use them
bool AsyncRPCOperation_sendmany::main_impl() {
@ -265,18 +245,12 @@ bool AsyncRPCOperation_sendmany::main_impl() {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds, no unspent notes found for zaddr from address.");
}
// At least one of z_sprout_inputs_ and z_sapling_inputs_ must be empty by design
assert(z_sprout_inputs_.empty() || z_sapling_inputs_.empty());
CAmount t_inputs_total = 0;
for (SendManyInputUTXO & t : t_inputs_) {
t_inputs_total += std::get<2>(t);
}
CAmount z_inputs_total = 0;
for (SendManyInputJSOP & t : z_sprout_inputs_) {
z_inputs_total += std::get<2>(t);
}
for (auto t : z_sapling_inputs_) {
z_inputs_total += t.note.value();
}
@ -566,430 +540,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
* END SCENARIO #0
*/
// Grab the current consensus branch ID
{
LOCK(cs_main);
consensusBranchId_ = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
}
/**
* SCENARIO #1
*
* taddr -> taddrs
*
* There are no zaddrs or joinsplits involved.
*/
if (isPureTaddrOnlyTx) {
add_taddr_outputs_to_tx();
CAmount funds = selectedUTXOAmount;
CAmount fundsSpent = t_outputs_total + minersFee;
CAmount change = funds - fundsSpent;
if (change > 0) {
add_taddr_change_output_to_tx(0,change);
LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n",
getId(),
FormatMoney(change)
);
}
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("rawtxn", EncodeHexTx(tx_)));
sign_send_raw_transaction(obj);
return true;
}
/**
* END SCENARIO #1
*/
// Prepare raw transaction to handle JoinSplits
CMutableTransaction mtx(tx_);
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_);
mtx.joinSplitPubKey = joinSplitPubKey_;
//if ((uint32_t)chainActive.LastTip()->nTime < ASSETCHAINS_STAKED_HF_TIMESTAMP)
if ( !hush_hardfork_active((uint32_t)chainActive.LastTip()->nTime) )
mtx.nLockTime = (uint32_t)time(NULL) - 60; // jl777
else
mtx.nLockTime = (uint32_t)chainActive.Tip()->GetMedianTimePast();
tx_ = CTransaction(mtx);
// Copy zinputs and zoutputs to more flexible containers
std::deque<SendManyInputJSOP> zInputsDeque; // zInputsDeque stores minimum numbers of notes for target amount
CAmount tmp = 0;
for (auto o : z_sprout_inputs_) {
zInputsDeque.push_back(o);
tmp += std::get<2>(o);
if (tmp >= targetAmount) {
break;
}
}
std::deque<SendManyRecipient> zOutputsDeque;
for (auto o : z_outputs_) {
zOutputsDeque.push_back(o);
}
// When spending notes, take a snapshot of note witnesses and anchors as the treestate will
// change upon arrival of new blocks which contain joinsplit transactions. This is likely
// to happen as creating a chained joinsplit transaction can take longer than the block interval.
if (z_sprout_inputs_.size() > 0) {
LOCK2(cs_main, pwalletMain->cs_wallet);
for (auto t : z_sprout_inputs_) {
JSOutPoint jso = std::get<0>(t);
std::vector<JSOutPoint> vOutPoints = { jso };
uint256 inputAnchor;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
pwalletMain->GetSproutNoteWitnesses(vOutPoints, vInputWitnesses, inputAnchor);
jsopWitnessAnchorMap[ jso.ToString() ] = WitnessAnchorData{ vInputWitnesses[0], inputAnchor };
}
}
/**
* SCENARIO #2
*
* taddr -> taddrs
* -> zaddrs
*
* Note: Consensus rule states that coinbase utxos can only be sent to a zaddr. TODO: Do they?
* Local wallet rule does not allow any change when sending coinbase utxos
* since there is currently no way to specify a change address and we don't
* want users accidentally sending excess funds to a recipient.
*/
if (isfromtaddr_) {
add_taddr_outputs_to_tx();
CAmount funds = selectedUTXOAmount;
CAmount fundsSpent = t_outputs_total + minersFee + z_outputs_total;
CAmount change = funds - fundsSpent;
if (change > 0) {
if (selectedUTXOCoinbase) {
assert(isSingleZaddrOutput);
throw JSONRPCError(RPC_WALLET_ERROR, strprintf(
"Change %s not allowed. When shielding coinbase funds, the wallet does not "
"allow any change as there is currently no way to specify a change address "
"in z_sendmany.", FormatMoney(change)));
} else {
CBitcoinAddress ba = CBitcoinAddress(fromtaddr_);
add_taddr_change_output_to_tx(&ba,change);
LogPrint("zrpc", "%s: transparent change in transaction output (amount=%s)\n",
getId(),
FormatMoney(change)
);
}
}
// Create joinsplits, where each output represents a zaddr recipient.
UniValue obj(UniValue::VOBJ);
while (zOutputsDeque.size() > 0) {
AsyncJoinSplitInfo info;
info.vpub_old = 0;
info.vpub_new = 0;
int n = 0;
while (n++<ZC_NUM_JS_OUTPUTS && zOutputsDeque.size() > 0) {
SendManyRecipient smr = zOutputsDeque.front();
std::string address = std::get<0>(smr);
CAmount value = std::get<1>(smr);
std::string hexMemo = std::get<2>(smr);
zOutputsDeque.pop_front();
PaymentAddress pa = DecodePaymentAddress(address);
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
if (hexMemo.size() > 0) {
jso.memo = get_memo_from_hex_string(hexMemo);
}
info.vjsout.push_back(jso);
// Funds are removed from the value pool and enter the private pool
info.vpub_old += value;
}
obj = perform_joinsplit(info);
}
sign_send_raw_transaction(obj);
return true;
}
/**
* END SCENARIO #2
*/
/**
* SCENARIO #3
*
* zaddr -> taddrs
* -> zaddrs
*
* Send to zaddrs by chaining JoinSplits together and immediately consuming any change
* Send to taddrs by creating dummy z outputs and accumulating value in a change note
* which is used to set vpub_new in the last chained joinsplit.
*/
UniValue obj(UniValue::VOBJ);
CAmount jsChange = 0; // this is updated after each joinsplit
int changeOutputIndex = -1; // this is updated after each joinsplit if jsChange > 0
bool vpubNewProcessed = false; // updated when vpub_new for miner fee and taddr outputs is set in last joinsplit
CAmount vpubNewTarget = minersFee;
if (t_outputs_total > 0) {
add_taddr_outputs_to_tx();
vpubNewTarget += t_outputs_total;
}
// Keep track of treestate within this transaction
boost::unordered_map<uint256, SproutMerkleTree, CCoinsKeyHasher> intermediates;
std::vector<uint256> previousCommitments;
while (!vpubNewProcessed) {
AsyncJoinSplitInfo info;
info.vpub_old = 0;
info.vpub_new = 0;
CAmount jsInputValue = 0;
uint256 jsAnchor;
std::vector<boost::optional<SproutWitness>> witnesses;
JSDescription prevJoinSplit;
// Keep track of previous JoinSplit and its commitments
if (tx_.vjoinsplit.size() > 0) {
prevJoinSplit = tx_.vjoinsplit.back();
}
// If there is no change, the chain has terminated so we can reset the tracked treestate.
if (jsChange==0 && tx_.vjoinsplit.size() > 0) {
intermediates.clear();
previousCommitments.clear();
}
//
// Consume change as the first input of the JoinSplit.
//
if (jsChange > 0) {
LOCK2(cs_main, pwalletMain->cs_wallet);
// Update tree state with previous joinsplit
SproutMerkleTree tree;
auto it = intermediates.find(prevJoinSplit.anchor);
if (it != intermediates.end()) {
tree = it->second;
} else if (!pcoinsTip->GetSproutAnchorAt(prevJoinSplit.anchor, tree)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find previous JoinSplit anchor");
}
assert(changeOutputIndex != -1);
boost::optional<SproutWitness> changeWitness;
int n = 0;
for (const uint256& commitment : prevJoinSplit.commitments) {
tree.append(commitment);
previousCommitments.push_back(commitment);
if (!changeWitness && changeOutputIndex == n++) {
changeWitness = tree.witness();
} else if (changeWitness) {
changeWitness.get().append(commitment);
}
}
if (changeWitness) {
witnesses.push_back(changeWitness);
}
jsAnchor = tree.root();
intermediates.insert(std::make_pair(tree.root(), tree)); // chained js are interstitial (found in between block boundaries)
// Decrypt the change note's ciphertext to retrieve some data we need
ZCNoteDecryption decryptor(boost::get<libzcash::SproutSpendingKey>(spendingkey_).receiving_key());
auto hSig = prevJoinSplit.h_sig(*pzcashParams, tx_.joinSplitPubKey);
try {
SproutNotePlaintext plaintext = SproutNotePlaintext::decrypt(
decryptor,
prevJoinSplit.ciphertexts[changeOutputIndex],
prevJoinSplit.ephemeralKey,
hSig,
(unsigned char) changeOutputIndex);
SproutNote note = plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_));
info.notes.push_back(note);
jsInputValue += plaintext.value();
LogPrint("zrpcunsafe", "%s: spending change (amount=%s)\n",
getId(),
FormatMoney(plaintext.value())
);
} catch (const std::exception& e) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error decrypting output note of previous JoinSplit: %s", e.what()));
}
}
//
// Consume spendable non-change notes
//
std::vector<SproutNote> vInputNotes;
std::vector<JSOutPoint> vOutPoints;
std::vector<boost::optional<SproutWitness>> vInputWitnesses;
uint256 inputAnchor;
int numInputsNeeded = (jsChange>0) ? 1 : 0;
while (numInputsNeeded++ < ZC_NUM_JS_INPUTS && zInputsDeque.size() > 0) {
SendManyInputJSOP t = zInputsDeque.front();
JSOutPoint jso = std::get<0>(t);
SproutNote note = std::get<1>(t);
CAmount noteFunds = std::get<2>(t);
zInputsDeque.pop_front();
WitnessAnchorData wad = jsopWitnessAnchorMap[ jso.ToString() ];
vInputWitnesses.push_back(wad.witness);
if (inputAnchor.IsNull()) {
inputAnchor = wad.anchor;
} else if (inputAnchor != wad.anchor) {
throw JSONRPCError(RPC_WALLET_ERROR, "Selected input notes do not share the same anchor");
}
vOutPoints.push_back(jso);
vInputNotes.push_back(note);
jsInputValue += noteFunds;
int wtxHeight = -1;
int wtxDepth = -1;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
const CWalletTx& wtx = pwalletMain->mapWallet[jso.hash];
// Zero-confirmation notes belong to transactions which have not yet been mined
if (mapBlockIndex.find(wtx.hashBlock) == mapBlockIndex.end()) {
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("mapBlockIndex does not contain block hash %s", wtx.hashBlock.ToString()));
}
wtxHeight = komodo_blockheight(wtx.hashBlock);
wtxDepth = wtx.GetDepthInMainChain();
}
LogPrint("zrpcunsafe", "%s: spending note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, height=%d, confirmations=%d)\n",
getId(),
jso.hash.ToString().substr(0, 10),
jso.js,
int(jso.n), // uint8_t
FormatMoney(noteFunds),
wtxHeight,
wtxDepth
);
}
// Add history of previous commitments to witness
if (vInputNotes.size() > 0) {
if (vInputWitnesses.size()==0) {
throw JSONRPCError(RPC_WALLET_ERROR, "Could not find witness for note commitment");
}
for (auto & optionalWitness : vInputWitnesses) {
if (!optionalWitness) {
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for note commitment is null");
}
SproutWitness w = *optionalWitness; // could use .get();
if (jsChange > 0) {
for (const uint256& commitment : previousCommitments) {
w.append(commitment);
}
if (jsAnchor != w.root()) {
throw JSONRPCError(RPC_WALLET_ERROR, "Witness for spendable note does not have same anchor as change input");
}
}
witnesses.push_back(w);
}
// The jsAnchor is null if this JoinSplit is at the start of a new chain
if (jsAnchor.IsNull()) {
jsAnchor = inputAnchor;
}
// Add spendable notes as inputs
std::copy(vInputNotes.begin(), vInputNotes.end(), std::back_inserter(info.notes));
}
// Find recipient to transfer funds to
std::string address, hexMemo;
CAmount value = 0;
if (zOutputsDeque.size() > 0) {
SendManyRecipient smr = zOutputsDeque.front();
address = std::get<0>(smr);
value = std::get<1>(smr);
hexMemo = std::get<2>(smr);
zOutputsDeque.pop_front();
}
// Reset change
jsChange = 0;
CAmount outAmount = value;
// Set vpub_new in the last joinsplit (when there are no more notes to spend or zaddr outputs to satisfy)
if (zOutputsDeque.size() == 0 && zInputsDeque.size() == 0) {
assert(!vpubNewProcessed);
if (jsInputValue < vpubNewTarget) {
throw JSONRPCError(RPC_WALLET_ERROR,
strprintf("Insufficient funds for vpub_new %s (miners fee %s, taddr outputs %s)",
FormatMoney(vpubNewTarget), FormatMoney(minersFee), FormatMoney(t_outputs_total)));
}
outAmount += vpubNewTarget;
info.vpub_new += vpubNewTarget; // funds flowing back to public pool
vpubNewProcessed = true;
jsChange = jsInputValue - outAmount;
assert(jsChange >= 0);
}
else {
// This is not the last joinsplit, so compute change and any amount still due to the recipient
if (jsInputValue > outAmount) {
jsChange = jsInputValue - outAmount;
} else if (outAmount > jsInputValue) {
// Any amount due is owed to the recipient. Let the miners fee get paid first.
CAmount due = outAmount - jsInputValue;
SendManyRecipient r = SendManyRecipient(address, due, hexMemo);
zOutputsDeque.push_front(r);
// reduce the amount being sent right now to the value of all inputs
value = jsInputValue;
}
}
// create output for recipient
if (address.empty()) {
assert(value==0);
info.vjsout.push_back(JSOutput()); // dummy output while we accumulate funds into a change note for vpub_new
} else {
PaymentAddress pa = DecodePaymentAddress(address);
// If we are here, we know we have no Sapling outputs.
JSOutput jso = JSOutput(boost::get<libzcash::SproutPaymentAddress>(pa), value);
if (hexMemo.size() > 0) {
jso.memo = get_memo_from_hex_string(hexMemo);
}
info.vjsout.push_back(jso);
}
// create output for any change
if (jsChange>0) {
info.vjsout.push_back(JSOutput(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_), jsChange));
LogPrint("zrpcunsafe", "%s: generating note for change (amount=%s)\n",
getId(),
FormatMoney(jsChange)
);
}
obj = perform_joinsplit(info, witnesses, jsAnchor);
if (jsChange > 0) {
changeOutputIndex = find_output(obj, 1);
}
}
// Sanity check in case changes to code block above exits loop by invoking 'break'
assert(zInputsDeque.size() == 0);
assert(zOutputsDeque.size() == 0);
assert(vpubNewProcessed);
sign_send_raw_transaction(obj);
return true;
return false;
}
@ -1127,33 +678,10 @@ bool AsyncRPCOperation_sendmany::find_utxos(bool fAcceptCoinbase=false) {
bool AsyncRPCOperation_sendmany::find_unspent_notes() {
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
{
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress_, mindepth_);
}
// If using the TransactionBuilder, we only want Sapling notes.
// If not using it, we only want Sprout notes.
// TODO: Refactor `GetFilteredNotes()` so we only fetch what we need.
if (isUsingBuilder_) {
sproutEntries.clear();
} else {
saplingEntries.clear();
}
for (CSproutNotePlaintextEntry & entry : sproutEntries) {
z_sprout_inputs_.push_back(SendManyInputJSOP(entry.jsop, entry.plaintext.note(boost::get<libzcash::SproutPaymentAddress>(frompaymentaddress_)), CAmount(entry.plaintext.value())));
std::string data(entry.plaintext.memo().begin(), entry.plaintext.memo().end());
LogPrint("zrpcunsafe", "%s: found unspent Sprout note (txid=%s, vjoinsplit=%d, ciphertext=%d, amount=%s, memo=%s)\n",
getId(),
entry.jsop.hash.ToString().substr(0, 10),
entry.jsop.js,
int(entry.jsop.n), // uint8_t
FormatMoney(entry.plaintext.value()),
HexStr(data).substr(0, 10)
);
pwalletMain->GetFilteredNotes(saplingEntries, fromaddress_, mindepth_);
}
for (auto entry : saplingEntries) {
@ -1167,15 +695,7 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
HexStr(data).substr(0, 10));
}
if (z_sprout_inputs_.empty() && z_sapling_inputs_.empty()) {
return false;
}
// sort in descending order, so big notes appear first
std::sort(z_sprout_inputs_.begin(), z_sprout_inputs_.end(),
[](SendManyInputJSOP i, SendManyInputJSOP j) -> bool {
return std::get<2>(i) > std::get<2>(j);
});
std::sort(z_sapling_inputs_.begin(), z_sapling_inputs_.end(),
[](SaplingNoteEntry i, SaplingNoteEntry j) -> bool {
return i.note.value() > j.note.value();
@ -1184,189 +704,6 @@ bool AsyncRPCOperation_sendmany::find_unspent_notes() {
return true;
}
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info) {
std::vector<boost::optional < SproutWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
anchor = pcoinsTip->GetBestAnchor(SPROUT); // As there are no inputs, ask the wallet for the best anchor
}
return perform_joinsplit(info, witnesses, anchor);
}
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(AsyncJoinSplitInfo & info, std::vector<JSOutPoint> & outPoints) {
std::vector<boost::optional < SproutWitness>> witnesses;
uint256 anchor;
{
LOCK(cs_main);
pwalletMain->GetSproutNoteWitnesses(outPoints, witnesses, anchor);
}
return perform_joinsplit(info, witnesses, anchor);
}
UniValue AsyncRPCOperation_sendmany::perform_joinsplit(
AsyncJoinSplitInfo & info,
std::vector<boost::optional < SproutWitness>> witnesses,
uint256 anchor)
{
if (anchor.IsNull()) {
throw std::runtime_error("anchor is null");
}
if (!(witnesses.size() == info.notes.size())) {
throw runtime_error("number of notes and witnesses do not match");
}
for (size_t i = 0; i < witnesses.size(); i++) {
if (!witnesses[i]) {
throw runtime_error("joinsplit input could not be found in tree");
}
info.vjsin.push_back(JSInput(*witnesses[i], info.notes[i], boost::get<libzcash::SproutSpendingKey>(spendingkey_)));
}
// Make sure there are two inputs and two outputs
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
info.vjsin.push_back(JSInput());
}
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
info.vjsout.push_back(JSOutput());
}
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
throw runtime_error("unsupported joinsplit input/output counts");
}
CMutableTransaction mtx(tx_);
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
getId(),
tx_.vjoinsplit.size(),
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value)
);
// Generate the proof, this can take over a minute.
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs
{info.vjsin[0], info.vjsin[1]};
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs
{info.vjsout[0], info.vjsout[1]};
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
uint256 esk; // payment disclosure - secret
JSDescription jsdesc = JSDescription::Randomized(
*pzcashParams,
joinSplitPubKey_,
anchor,
inputs,
outputs,
inputMap,
outputMap,
info.vpub_old,
info.vpub_new,
!this->testmode,
&esk); // parameter expects pointer to esk, so pass in address
{
auto verifier = libzcash::ProofVerifier::Strict();
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
throw std::runtime_error("error verifying joinsplit");
}
}
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId_);
// Add the signature
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey_
) == 0))
{
throw std::runtime_error("crypto_sign_detached failed");
}
// Sanity check
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
dataToBeSigned.begin(), 32,
mtx.joinSplitPubKey.begin()
) == 0))
{
throw std::runtime_error("crypto_sign_verify_detached failed");
}
CTransaction rawTx(mtx);
tx_ = rawTx;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << rawTx;
std::string encryptedNote1;
std::string encryptedNote2;
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char) 0x00);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[0];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
}
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char) 0x01);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[1];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
}
UniValue arrInputMap(UniValue::VARR);
UniValue arrOutputMap(UniValue::VARR);
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
}
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
}
// !!! Payment disclosure START
unsigned char buffer[32] = {0};
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
uint256 joinSplitPrivKey = uint256(vch);
size_t js_index = tx_.vjoinsplit.size() - 1;
uint256 placeholder;
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
uint8_t mapped_index = outputMap[i];
// placeholder for txid will be filled in later when tx has been finalized and signed.
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
JSOutput output = outputs[mapped_index];
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
}
// !!! Payment disclosure END
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("encryptednote1", encryptedNote1));
obj.push_back(Pair("encryptednote2", encryptedNote2));
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
obj.push_back(Pair("inputmap", arrInputMap));
obj.push_back(Pair("outputmap", arrOutputMap));
return obj;
}
void AsyncRPCOperation_sendmany::add_taddr_outputs_to_tx() {
CMutableTransaction rawTx(tx_);

50
src/wallet/asyncrpcoperation_sendmany.h

@ -1,4 +1,5 @@
// 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.
@ -27,7 +28,6 @@
#include "zcash/JoinSplit.hpp"
#include "zcash/Address.hpp"
#include "wallet.h"
#include "paymentdisclosure.h"
#include <array>
#include <unordered_map>
@ -46,19 +46,6 @@ typedef std::tuple<std::string, CAmount, std::string> SendManyRecipient;
// Input UTXO is a tuple (quadruple) of txid, vout, amount, coinbase)
typedef std::tuple<uint256, int, CAmount, bool, CTxDestination> SendManyInputUTXO;
// Input JSOP is a tuple of JSOutpoint, note and amount
typedef std::tuple<JSOutPoint, SproutNote, CAmount> SendManyInputJSOP;
// Package of info which is passed to perform_joinsplit methods.
struct AsyncJoinSplitInfo
{
std::vector<JSInput> vjsin;
std::vector<JSOutput> vjsout;
std::vector<SproutNote> notes;
CAmount vpub_old = 0;
CAmount vpub_new = 0;
};
// A struct to help us track the witness and anchor for a given JSOutPoint
struct WitnessAnchorData {
boost::optional<SproutWitness> witness;
@ -90,8 +77,6 @@ public:
bool testmode = false; // Set to true to disable sending txs and generating proofs
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
private:
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
@ -117,7 +102,7 @@ private:
std::vector<SendManyRecipient> t_outputs_;
std::vector<SendManyRecipient> z_outputs_;
std::vector<SendManyInputUTXO> t_inputs_;
std::vector<SendManyInputJSOP> z_sprout_inputs_;
//std::vector<SendManyInputJSOP> z_sprout_inputs_;
std::vector<SaplingNoteEntry> z_sapling_inputs_;
TransactionBuilder builder_;
@ -130,22 +115,7 @@ private:
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
bool main_impl();
// JoinSplit without any input notes to spend
UniValue perform_joinsplit(AsyncJoinSplitInfo &);
// JoinSplit with input notes to spend (JSOutPoints))
UniValue perform_joinsplit(AsyncJoinSplitInfo &, std::vector<JSOutPoint> & );
// JoinSplit where you have the witnesses and anchor
UniValue perform_joinsplit(
AsyncJoinSplitInfo & info,
std::vector<boost::optional < SproutWitness>> witnesses,
uint256 anchor);
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
// payment disclosure!
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
};
@ -190,22 +160,6 @@ public:
return delegate->main_impl();
}
UniValue perform_joinsplit(AsyncJoinSplitInfo &info) {
return delegate->perform_joinsplit(info);
}
UniValue perform_joinsplit(AsyncJoinSplitInfo &info, std::vector<JSOutPoint> &v ) {
return delegate->perform_joinsplit(info, v);
}
UniValue perform_joinsplit(
AsyncJoinSplitInfo & info,
std::vector<boost::optional < SproutWitness>> witnesses,
uint256 anchor)
{
return delegate->perform_joinsplit(info, witnesses, anchor);
}
void sign_send_raw_transaction(UniValue obj) {
delegate->sign_send_raw_transaction(obj);
}

208
src/wallet/asyncrpcoperation_shieldcoinbase.cpp

@ -1,4 +1,5 @@
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -47,9 +48,6 @@
#include "asyncrpcoperation_shieldcoinbase.h"
#include "paymentdisclosure.h"
#include "paymentdisclosuredb.h"
using namespace libzcash;
extern uint64_t ASSETCHAINS_TIMELOCKGTE;
@ -107,8 +105,6 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
// Lock UTXOs
lock_utxos();
// Enable payment disclosure if requested
paymentDisclosureMode = fExperimentalMode && GetBoolArg("-paymentdisclosure", true);
}
AsyncRPCOperation_shieldcoinbase::~AsyncRPCOperation_shieldcoinbase() {
@ -180,20 +176,6 @@ void AsyncRPCOperation_shieldcoinbase::main() {
unlock_utxos(); // clean up
// !!! Payment disclosure START
if (success && paymentDisclosureMode && paymentDisclosureData_.size()>0) {
uint256 txidhash = tx_.GetHash();
std::shared_ptr<PaymentDisclosureDB> db = PaymentDisclosureDB::sharedInstance();
for (PaymentDisclosureKeyInfo p : paymentDisclosureData_) {
p.first.hash = txidhash;
if (!db->Put(p.first, p.second)) {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Error writing entry to database for key %s\n", getId(), p.first.ToString());
} else {
LogPrint("paymentdisclosure", "%s: Payment Disclosure: Successfully added entry to database for key %s\n", getId(), p.first.ToString());
}
}
}
// !!! Payment disclosure END
}
bool AsyncRPCOperation_shieldcoinbase::main_impl() {
@ -234,37 +216,6 @@ bool AsyncRPCOperation_shieldcoinbase::main_impl() {
return boost::apply_visitor(ShieldToAddress(this, sendAmount), tozaddr_);
}
bool ShieldToAddress::operator()(const libzcash::SproutPaymentAddress &zaddr) const {
// update the transaction with these inputs
CMutableTransaction rawTx(m_op->tx_);
for (ShieldCoinbaseUTXO & t : m_op->inputs_) {
CTxIn in(COutPoint(t.txid, t.vout));
if (t.amount >= ASSETCHAINS_TIMELOCKGTE)
in.nSequence = 0xfffffffe;
rawTx.vin.push_back(in);
}
m_op->tx_ = CTransaction(rawTx);
// Prepare raw transaction to handle JoinSplits
CMutableTransaction mtx(m_op->tx_);
crypto_sign_keypair(m_op->joinSplitPubKey_.begin(), m_op->joinSplitPrivKey_);
mtx.joinSplitPubKey = m_op->joinSplitPubKey_;
m_op->tx_ = CTransaction(mtx);
// Create joinsplit
UniValue obj(UniValue::VOBJ);
ShieldCoinbaseJSInfo info;
info.vpub_old = sendAmount;
info.vpub_new = 0;
JSOutput jso = JSOutput(zaddr, sendAmount);
info.vjsout.push_back(jso);
obj = m_op->perform_joinsplit(info);
m_op->sign_send_raw_transaction(obj);
return true;
}
extern UniValue signrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue sendrawtransaction(const UniValue& params, bool fHelp, const CPubKey& mypk);
@ -406,163 +357,6 @@ void AsyncRPCOperation_shieldcoinbase::sign_send_raw_transaction(UniValue obj)
}
UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInfo & info) {
uint32_t consensusBranchId;
uint256 anchor;
{
LOCK(cs_main);
consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
anchor = pcoinsTip->GetBestAnchor(SPROUT);
}
if (anchor.IsNull()) {
throw std::runtime_error("anchor is null");
}
// Make sure there are two inputs and two outputs
while (info.vjsin.size() < ZC_NUM_JS_INPUTS) {
info.vjsin.push_back(JSInput());
}
while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) {
info.vjsout.push_back(JSOutput());
}
if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) {
throw runtime_error("unsupported joinsplit input/output counts");
}
CMutableTransaction mtx(tx_);
LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n",
getId(),
tx_.vjoinsplit.size(),
FormatMoney(info.vpub_old), FormatMoney(info.vpub_new),
FormatMoney(info.vjsin[0].note.value()), FormatMoney(info.vjsin[1].note.value()),
FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value)
);
// Generate the proof, this can take over a minute.
std::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs
{info.vjsin[0], info.vjsin[1]};
std::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs
{info.vjsout[0], info.vjsout[1]};
std::array<size_t, ZC_NUM_JS_INPUTS> inputMap;
std::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap;
uint256 esk; // payment disclosure - secret
JSDescription jsdesc = JSDescription::Randomized(
*pzcashParams,
joinSplitPubKey_,
anchor,
inputs,
outputs,
inputMap,
outputMap,
info.vpub_old,
info.vpub_new,
!this->testmode,
&esk); // parameter expects pointer to esk, so pass in address
{
auto verifier = libzcash::ProofVerifier::Strict();
if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) {
throw std::runtime_error("error verifying joinsplit");
}
}
mtx.vjoinsplit.push_back(jsdesc);
// Empty output script.
CScript scriptCode;
CTransaction signTx(mtx);
uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
// Add the signature
if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL,
dataToBeSigned.begin(), 32,
joinSplitPrivKey_
) == 0))
{
throw std::runtime_error("crypto_sign_detached failed");
}
// Sanity check
if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0],
dataToBeSigned.begin(), 32,
mtx.joinSplitPubKey.begin()
) == 0))
{
throw std::runtime_error("crypto_sign_verify_detached failed");
}
CTransaction rawTx(mtx);
tx_ = rawTx;
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << rawTx;
std::string encryptedNote1;
std::string encryptedNote2;
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char) 0x00);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[0];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote1 = HexStr(ss2.begin(), ss2.end());
}
{
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << ((unsigned char) 0x01);
ss2 << jsdesc.ephemeralKey;
ss2 << jsdesc.ciphertexts[1];
ss2 << jsdesc.h_sig(*pzcashParams, joinSplitPubKey_);
encryptedNote2 = HexStr(ss2.begin(), ss2.end());
}
UniValue arrInputMap(UniValue::VARR);
UniValue arrOutputMap(UniValue::VARR);
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) {
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i]));
}
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i]));
}
// !!! Payment disclosure START
unsigned char buffer[32] = {0};
memcpy(&buffer[0], &joinSplitPrivKey_[0], 32); // private key in first half of 64 byte buffer
std::vector<unsigned char> vch(&buffer[0], &buffer[0] + 32);
uint256 joinSplitPrivKey = uint256(vch);
size_t js_index = tx_.vjoinsplit.size() - 1;
uint256 placeholder;
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) {
uint8_t mapped_index = outputMap[i];
// placeholder for txid will be filled in later when tx has been finalized and signed.
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index};
JSOutput output = outputs[mapped_index];
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey, zaddr};
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo));
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), EncodePaymentAddress(zaddr));
}
// !!! Payment disclosure END
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("encryptednote1", encryptedNote1));
obj.push_back(Pair("encryptednote2", encryptedNote2));
obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end())));
obj.push_back(Pair("inputmap", arrInputMap));
obj.push_back(Pair("outputmap", arrOutputMap));
return obj;
}
/**
* Override getStatus() to append the operation's context object to the default status object.
*/

9
src/wallet/asyncrpcoperation_shieldcoinbase.h

@ -1,4 +1,5 @@
// Copyright (c) 2017 The Zcash developers
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -33,8 +34,6 @@
#include <univalue.h>
#include "paymentdisclosure.h"
// Default transaction fee if caller does not specify one.
#define SHIELD_COINBASE_DEFAULT_MINERS_FEE 10000
@ -80,8 +79,6 @@ public:
bool testmode = false; // Set to true to disable sending txs and generating proofs
bool cheatSpend = false; // set when this is shielding a cheating coinbase
bool paymentDisclosureMode = true; // Set to true to save esk for encrypted notes in payment disclosure database.
private:
friend class ShieldToAddress;
friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing
@ -109,9 +106,6 @@ private:
void lock_utxos();
void unlock_utxos();
// payment disclosure!
std::vector<PaymentDisclosureKeyInfo> paymentDisclosureData_;
};
class ShieldToAddress : public boost::static_visitor<bool>
@ -123,7 +117,6 @@ public:
ShieldToAddress(AsyncRPCOperation_shieldcoinbase *op, CAmount sendAmount) :
m_op(op), sendAmount(sendAmount) {}
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};

111
src/wallet/crypter.cpp

@ -1,6 +1,7 @@
// Copyright (c) 2009-2013 The Bitcoin Core 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.
// file COPYING or https://www.opensource.org/licenses/mit-license.php
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
@ -168,22 +169,6 @@ static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsi
return key.VerifyPubKey(vchPubKey);
}
static bool DecryptSproutSpendingKey(const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret,
const libzcash::SproutPaymentAddress& address,
libzcash::SproutSpendingKey& sk)
{
CKeyingMaterial vchSecret;
if (!DecryptSecret(vMasterKey, vchCryptedSecret, address.GetHash(), vchSecret))
return false;
if (vchSecret.size() != libzcash::SerializedSproutSpendingKeySize)
return false;
CSecureDataStream ss(vchSecret, SER_NETWORK, PROTOCOL_VERSION);
ss >> sk;
return sk.address() == address;
}
static bool DecryptSaplingSpendingKey(const CKeyingMaterial& vMasterKey,
const std::vector<unsigned char>& vchCryptedSecret,
@ -207,7 +192,7 @@ bool CCryptoKeyStore::SetCrypted()
LOCK2(cs_KeyStore, cs_SpendingKeyStore);
if (fUseCrypto)
return true;
if (!(mapKeys.empty() && mapSproutSpendingKeys.empty() && mapSaplingSpendingKeys.empty()))
if (!(mapKeys.empty() && mapSaplingSpendingKeys.empty()))
return false;
fUseCrypto = true;
return true;
@ -260,21 +245,6 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
if (fDecryptionThoroughlyChecked)
break;
}
CryptedSproutSpendingKeyMap::const_iterator miSprout = mapCryptedSproutSpendingKeys.begin();
for (; miSprout != mapCryptedSproutSpendingKeys.end(); ++miSprout)
{
const libzcash::SproutPaymentAddress &address = (*miSprout).first;
const std::vector<unsigned char> &vchCryptedSecret = (*miSprout).second;
libzcash::SproutSpendingKey sk;
if (!DecryptSproutSpendingKey(vMasterKeyIn, vchCryptedSecret, address, sk))
{
keyFail = true;
break;
}
keyPass = true;
if (fDecryptionThoroughlyChecked)
break;
}
CryptedSaplingSpendingKeyMap::const_iterator miSapling = mapCryptedSaplingSpendingKeys.begin();
for (; miSapling != mapCryptedSaplingSpendingKeys.end(); ++miSapling)
{
@ -292,7 +262,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
}
if (keyPass && keyFail)
{
LogPrintf("The wallet is probably corrupted: Some keys decrypt but not all.\n");
LogPrintf("Oh shit! The wallet is probably corrupted: Some keys decrypt but not all.\n");
assert(false);
}
if (keyFail || !keyPass)
@ -440,30 +410,6 @@ bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) co
return false;
}
bool CCryptoKeyStore::AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk)
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::AddSproutSpendingKey(sk);
if (IsLocked())
return false;
std::vector<unsigned char> vchCryptedSecret;
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << sk;
CKeyingMaterial vchSecret(ss.begin(), ss.end());
auto address = sk.address();
if (!EncryptSecret(vMasterKey, vchSecret, address.GetHash(), vchCryptedSecret))
return false;
if (!AddCryptedSproutSpendingKey(address, sk.receiving_key(), vchCryptedSecret))
return false;
}
return true;
}
bool CCryptoKeyStore::AddSaplingSpendingKey(
const libzcash::SaplingExtendedSpendingKey &sk,
const libzcash::SaplingPaymentAddress &defaultAddr)
@ -494,22 +440,6 @@ bool CCryptoKeyStore::AddSaplingSpendingKey(
return true;
}
bool CCryptoKeyStore::AddCryptedSproutSpendingKey(
const libzcash::SproutPaymentAddress &address,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char> &vchCryptedSecret)
{
{
LOCK(cs_SpendingKeyStore);
if (!SetCrypted())
return false;
mapCryptedSproutSpendingKeys[address] = vchCryptedSecret;
mapNoteDecryptors.insert(std::make_pair(address, ZCNoteDecryption(rk)));
}
return true;
}
bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char> &vchCryptedSecret,
@ -531,23 +461,6 @@ bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(
return true;
}
bool CCryptoKeyStore::GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::GetSproutSpendingKey(address, skOut);
CryptedSproutSpendingKeyMap::const_iterator mi = mapCryptedSproutSpendingKeys.find(address);
if (mi != mapCryptedSproutSpendingKeys.end())
{
const std::vector<unsigned char> &vchCryptedSecret = (*mi).second;
return DecryptSproutSpendingKey(vMasterKey, vchCryptedSecret, address, skOut);
}
}
return false;
}
bool CCryptoKeyStore::GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey &skOut) const
{
{
@ -603,22 +516,6 @@ bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
}
}
mapKeys.clear();
BOOST_FOREACH(SproutSpendingKeyMap::value_type& mSproutSpendingKey, mapSproutSpendingKeys)
{
const libzcash::SproutSpendingKey &sk = mSproutSpendingKey.second;
CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << sk;
CKeyingMaterial vchSecret(ss.begin(), ss.end());
libzcash::SproutPaymentAddress address = sk.address();
std::vector<unsigned char> vchCryptedSecret;
if (!EncryptSecret(vMasterKeyIn, vchSecret, address.GetHash(), vchCryptedSecret)) {
return false;
}
if (!AddCryptedSproutSpendingKey(address, sk.receiving_key(), vchCryptedSecret)) {
return false;
}
}
mapSproutSpendingKeys.clear();
//! Sapling key support
BOOST_FOREACH(SaplingSpendingKeyMap::value_type& mSaplingSpendingKey, mapSaplingSpendingKeys)
{

34
src/wallet/crypter.h

@ -1,4 +1,5 @@
// Copyright (c) 2009-2014 The Bitcoin Core 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.
@ -145,7 +146,7 @@ class CCryptoKeyStore : public CBasicKeyStore
private:
std::pair<uint256, std::vector<unsigned char>> cryptedHDSeed;
CryptedKeyMap mapCryptedKeys;
CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
//CryptedSproutSpendingKeyMap mapCryptedSproutSpendingKeys;
CryptedSaplingSpendingKeyMap mapCryptedSaplingSpendingKeys;
CKeyingMaterial vMasterKey;
@ -223,37 +224,6 @@ public:
mi++;
}
}
virtual bool AddCryptedSproutSpendingKey(
const libzcash::SproutPaymentAddress &address,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char> &vchCryptedSecret);
bool AddSproutSpendingKey(const libzcash::SproutSpendingKey &sk);
bool HaveSproutSpendingKey(const libzcash::SproutPaymentAddress &address) const
{
{
LOCK(cs_SpendingKeyStore);
if (!IsCrypted())
return CBasicKeyStore::HaveSproutSpendingKey(address);
return mapCryptedSproutSpendingKeys.count(address) > 0;
}
return false;
}
bool GetSproutSpendingKey(const libzcash::SproutPaymentAddress &address, libzcash::SproutSpendingKey &skOut) const;
void GetSproutPaymentAddresses(std::set<libzcash::SproutPaymentAddress> &setAddress) const
{
if (!IsCrypted())
{
CBasicKeyStore::GetSproutPaymentAddresses(setAddress);
return;
}
setAddress.clear();
CryptedSproutSpendingKeyMap::const_iterator mi = mapCryptedSproutSpendingKeys.begin();
while (mi != mapCryptedSproutSpendingKeys.end())
{
setAddress.insert((*mi).first);
mi++;
}
}
//! Sapling
virtual bool AddCryptedSaplingSpendingKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,

575
src/wallet/gtest/test_wallet.cpp

@ -75,11 +75,6 @@ CWalletTx GetValidReceive(const libzcash::SproutSpendingKey& sk, CAmount value,
return GetValidReceive(*params, sk, value, randomInputs, version);
}
libzcash::SproutNote GetNote(const libzcash::SproutSpendingKey& sk,
const CTransaction& tx, size_t js, size_t n) {
return GetNote(*params, sk, tx, js, n);
}
CWalletTx GetValidSpend(const libzcash::SproutSpendingKey& sk,
const libzcash::SproutNote& note, CAmount value) {
return GetValidSpend(*params, sk, note, value);
@ -139,341 +134,8 @@ TEST(WalletTests, SetupDatadirLocationRunAsFirstTest) {
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
}
/*
TEST(WalletTests, SproutNoteDataSerialisation) {
auto sk = libzcash::SproutSpendingKey::random();
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
SproutMerkleTree tree;
nd.witnesses.push_front(tree.witness());
noteData[jsoutpt] = nd;
CDataStream ss(SER_DISK, CLIENT_VERSION);
ss << noteData;
mapSproutNoteData_t noteData2;
ss >> noteData2;
EXPECT_EQ(noteData, noteData2);
EXPECT_EQ(noteData[jsoutpt].witnesses, noteData2[jsoutpt].witnesses);
}
TEST(WalletTests, FindUnspentSproutNotes) {
SelectParams(CBaseChainParams::TESTNET);
CWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
// We currently have an unspent and unconfirmed note in the wallet (depth of -1)
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", -1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Fake-mine the transaction
EXPECT_EQ(-1, chainActive.Height());
CBlock block;
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
chainActive.SetTip(&fakeIndex);
EXPECT_TRUE(chainActive.Contains(&fakeIndex));
EXPECT_EQ(0, chainActive.Height());
wtx.SetMerkleBranch(block);
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
// We now have an unspent and confirmed note in the wallet (depth of 1)
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's spend the note.
auto wtx2 = GetValidSpend(sk, note, 5);
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
// Fake-mine a spend transaction
EXPECT_EQ(0, chainActive.Height());
CBlock block2;
block2.vtx.push_back(wtx2);
block2.hashMerkleRoot = block2.BuildMerkleTree();
block2.hashPrevBlock = blockHash;
auto blockHash2 = block2.GetHash();
CBlockIndex fakeIndex2 {block2};
mapBlockIndex.insert(std::make_pair(blockHash2, &fakeIndex2));
fakeIndex2.SetHeight(1);
chainActive.SetTip(&fakeIndex2);
EXPECT_TRUE(chainActive.Contains(&fakeIndex2));
EXPECT_EQ(1, chainActive.Height());
wtx2.SetMerkleBranch(block2);
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_TRUE(wallet.IsSproutSpent(nullifier));
// The note has been spent. By default, GetFilteredNotes() ignores spent notes.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's include spent notes to retrieve it.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 0, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// The spent note has two confirmations.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// It does not have 3 confirmations.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 3, false);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's receive a new note
CWalletTx wtx3;
{
auto wtx = GetValidReceive(sk, 20, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
EXPECT_FALSE(wallet.IsSproutSpent(nullifier));
wtx3 = wtx;
}
// Fake-mine the new transaction
EXPECT_EQ(1, chainActive.Height());
CBlock block3;
block3.vtx.push_back(wtx3);
block3.hashMerkleRoot = block3.BuildMerkleTree();
block3.hashPrevBlock = blockHash2;
auto blockHash3 = block3.GetHash();
CBlockIndex fakeIndex3 {block3};
mapBlockIndex.insert(std::make_pair(blockHash3, &fakeIndex3));
fakeIndex3.SetHeight(2);
chainActive.SetTip(&fakeIndex3);
EXPECT_TRUE(chainActive.Contains(&fakeIndex3));
EXPECT_EQ(2, chainActive.Height());
wtx3.SetMerkleBranch(block3);
wallet.AddToWallet(wtx3, true, NULL);
// We now have an unspent note which has one confirmation, in addition to our spent note.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Let's return the spent note too.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 1, false);
EXPECT_EQ(2, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Increasing number of confirmations will exclude our new unspent note.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, false);
EXPECT_EQ(1, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// If we also ignore spent notes at this depth, we won't find any notes.
wallet.GetFilteredNotes(sproutEntries, saplingEntries, "", 2, true);
EXPECT_EQ(0, sproutEntries.size());
sproutEntries.clear();
saplingEntries.clear();
// Tear down
chainActive.SetTip(NULL);
mapBlockIndex.erase(blockHash);
mapBlockIndex.erase(blockHash2);
mapBlockIndex.erase(blockHash3);
}
TEST(WalletTests, SetSproutNoteAddrsInCWalletTx) {
auto sk = libzcash::SproutSpendingKey::random();
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
EXPECT_EQ(0, wtx.mapSproutNoteData.size());
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
EXPECT_EQ(noteData, wtx.mapSproutNoteData);
}
TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
auto consensusParams = Params().GetConsensus();
TestWallet wallet;
std::vector<unsigned char, secure_allocator<unsigned char>> rawSeed(32);
HDSeed seed(rawSeed);
auto sk = libzcash::SaplingExtendedSpendingKey::Master(seed);
auto expsk = sk.expsk;
auto fvk = expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
auto pk = sk.DefaultAddress();
libzcash::SaplingNote note(pk, 50000);
auto cm = note.cm().get();
SaplingMerkleTree tree;
tree.append(cm);
auto anchor = tree.root();
auto witness = tree.witness();
auto nf = note.nullifier(fvk, witness.position());
ASSERT_TRUE(nf);
uint256 nullifier = nf.get();
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
builder.SetFee(0);
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
CWalletTx wtx {&wallet, tx};
EXPECT_EQ(0, wtx.mapSaplingNoteData.size());
mapSaplingNoteData_t noteData;
SaplingOutPoint op {wtx.GetHash(), 0};
SaplingNoteData nd;
nd.nullifier = nullifier;
nd.ivk = ivk;
nd.witnesses.push_front(witness);
nd.witnessHeight = 123;
noteData.insert(std::make_pair(op, nd));
wtx.SetSaplingNoteData(noteData);
EXPECT_EQ(noteData, wtx.mapSaplingNoteData);
// Test individual fields in case equality operator is defined/changed.
EXPECT_EQ(ivk, wtx.mapSaplingNoteData[op].ivk);
EXPECT_EQ(nullifier, wtx.mapSaplingNoteData[op].nullifier);
EXPECT_EQ(nd.witnessHeight, wtx.mapSaplingNoteData[op].witnessHeight);
EXPECT_TRUE(witness == wtx.mapSaplingNoteData[op].witnesses.front());
// Revert to default
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
TEST(WalletTests, SetSproutInvalidNoteAddrsInCWalletTx) {
CWalletTx wtx;
EXPECT_EQ(0, wtx.mapSproutNoteData.size());
mapSproutNoteData_t noteData;
auto sk = libzcash::SproutSpendingKey::random();
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), uint256()};
noteData[jsoutpt] = nd;
EXPECT_THROW(wtx.SetSproutNoteData(noteData), std::logic_error);
}
// The following test is the same as SetInvalidSaplingNoteDataInCWalletTx
// TEST(WalletTests, SetSaplingInvalidNoteAddrsInCWalletTx)
// Cannot add note data for an index which does not exist in tx.vShieldedOutput
TEST(WalletTests, SetInvalidSaplingNoteDataInCWalletTx) {
CWalletTx wtx;
EXPECT_EQ(0, wtx.mapSaplingNoteData.size());
mapSaplingNoteData_t noteData;
SaplingOutPoint op {uint256(), 1};
SaplingNoteData nd;
noteData.insert(std::make_pair(op, nd));
EXPECT_THROW(wtx.SetSaplingNoteData(noteData), std::logic_error);
}
TEST(WalletTests, GetSproutNoteNullifier) {
CWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
auto address = sk.address();
auto dec = ZCNoteDecryption(sk.receiving_key());
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto hSig = wtx.vjoinsplit[0].h_sig(
*params, wtx.joinSplitPubKey);
auto ret = wallet.GetSproutNoteNullifier(
wtx.vjoinsplit[0],
address,
dec,
hSig, 1);
EXPECT_NE(nullifier, ret);
wallet.AddSproutSpendingKey(sk);
ret = wallet.GetSproutNoteNullifier(
wtx.vjoinsplit[0],
address,
dec,
hSig, 1);
EXPECT_EQ(nullifier, ret);
}
TEST(WalletTests, FindMySaplingNotes) {
SelectParams(CBaseChainParams::REGTEST);
@ -577,92 +239,6 @@ TEST(WalletTests, FindMySaplingNotesWithIvkOnly) {
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
TEST(WalletTests, FindMySproutNotes) {
CWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
auto sk2 = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk2);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto noteMap = wallet.FindMySproutNotes(wtx);
EXPECT_EQ(0, noteMap.size());
wallet.AddSproutSpendingKey(sk);
noteMap = wallet.FindMySproutNotes(wtx);
EXPECT_EQ(2, noteMap.size());
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_EQ(nd, noteMap[jsoutpt]);
}
TEST(WalletTests, FindMySproutNotesInEncryptedWallet) {
TestWallet wallet;
uint256 r {GetRandHash()};
CKeyingMaterial vMasterKey (r.begin(), r.end());
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
ASSERT_TRUE(wallet.EncryptKeys(vMasterKey));
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto noteMap = wallet.FindMySproutNotes(wtx);
EXPECT_EQ(2, noteMap.size());
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_NE(nd, noteMap[jsoutpt]);
ASSERT_TRUE(wallet.Unlock(vMasterKey));
noteMap = wallet.FindMySproutNotes(wtx);
EXPECT_EQ(2, noteMap.size());
EXPECT_EQ(1, noteMap.count(jsoutpt));
EXPECT_EQ(nd, noteMap[jsoutpt]);
}
TEST(WalletTests, GetConflictedSproutNotes) {
CWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto wtx2 = GetValidSpend(sk, note, 5);
auto wtx3 = GetValidSpend(sk, note, 10);
auto hash2 = wtx2.GetHash();
auto hash3 = wtx3.GetHash();
// No conflicts for no spends
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
wallet.AddToWallet(wtx, true, NULL);
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
// No conflicts for one spend
wallet.AddToWallet(wtx2, true, NULL);
EXPECT_EQ(0, wallet.GetConflicts(hash2).size());
// Conflicts for two spends
wallet.AddToWallet(wtx3, true, NULL);
auto c3 = wallet.GetConflicts(hash2);
EXPECT_EQ(2, c3.size());
EXPECT_EQ(std::set<uint256>({hash2, hash3}), c3);
}
// Generate note A and spend to create note B, from which we spend to create two conflicting transactions
TEST(WalletTests, GetConflictedSaplingNotes) {
SelectParams(CBaseChainParams::REGTEST);
@ -1735,88 +1311,6 @@ TEST(WalletTests, SetBestChainIgnoresTxsWithoutShieldedData) {
wallet.SetBestChain(walletdb, loc);
}
TEST(WalletTests, UpdateSproutNullifierNoteMap) {
TestWallet wallet;
uint256 r {GetRandHash()};
CKeyingMaterial vMasterKey (r.begin(), r.end());
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
ASSERT_TRUE(wallet.EncryptKeys(vMasterKey));
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
// Pretend that we called FindMySproutNotes while the wallet was locked
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address()};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
EXPECT_EQ(0, wallet.mapSproutNullifiersToNotes.count(nullifier));
EXPECT_FALSE(wallet.UpdateNullifierNoteMap());
ASSERT_TRUE(wallet.Unlock(vMasterKey));
EXPECT_TRUE(wallet.UpdateNullifierNoteMap());
EXPECT_EQ(1, wallet.mapSproutNullifiersToNotes.count(nullifier));
EXPECT_EQ(wtx.GetHash(), wallet.mapSproutNullifiersToNotes[nullifier].hash);
EXPECT_EQ(0, wallet.mapSproutNullifiersToNotes[nullifier].js);
EXPECT_EQ(1, wallet.mapSproutNullifiersToNotes[nullifier].n);
}
TEST(WalletTests, UpdatedSproutNoteData) {
TestWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto note = GetNote(sk, wtx, 0, 0);
auto note2 = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto nullifier2 = note2.nullifier(sk);
auto wtx2 = wtx;
// First pretend we added the tx to the wallet and
// we don't have the key for the second note
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 0};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
// Pretend we mined the tx by adding a fake witness
SproutMerkleTree tree;
wtx.mapSproutNoteData[jsoutpt].witnesses.push_front(tree.witness());
wtx.mapSproutNoteData[jsoutpt].witnessHeight = 100;
// Now pretend we added the key for the second note, and
// the tx was "added" to the wallet again to update it.
// This happens via the 'z_importkey' RPC method.
JSOutPoint jsoutpt2 {wtx2.GetHash(), 0, 1};
SproutNoteData nd2 {sk.address(), nullifier2};
noteData[jsoutpt2] = nd2;
wtx2.SetSproutNoteData(noteData);
// The txs should initially be different
EXPECT_NE(wtx.mapSproutNoteData, wtx2.mapSproutNoteData);
EXPECT_EQ(1, wtx.mapSproutNoteData[jsoutpt].witnesses.size());
EXPECT_EQ(100, wtx.mapSproutNoteData[jsoutpt].witnessHeight);
// After updating, they should be the same
EXPECT_TRUE(wallet.UpdatedNoteData(wtx2, wtx));
EXPECT_EQ(wtx.mapSproutNoteData, wtx2.mapSproutNoteData);
EXPECT_EQ(1, wtx.mapSproutNoteData[jsoutpt].witnesses.size());
EXPECT_EQ(100, wtx.mapSproutNoteData[jsoutpt].witnessHeight);
// TODO: The new note should get witnessed (but maybe not here) (#1350)
}
TEST(WalletTests, UpdatedSaplingNoteData) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
@ -1941,37 +1435,6 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
TEST(WalletTests, MarkAffectedSproutTransactionsDirty) {
TestWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto hash = wtx.GetHash();
auto note = GetNote(sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
auto wtx2 = GetValidSpend(sk, note, 5);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {hash, 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
wallet.MarkAffectedTransactionsDirty(wtx);
// After getting a cached value, the first tx should be clean
wallet.mapWallet[hash].GetDebit(ISMINE_ALL);
EXPECT_TRUE(wallet.mapWallet[hash].fDebitCached);
// After adding the note spend, the first tx should be dirty
wallet.AddToWallet(wtx2, true, NULL);
wallet.MarkAffectedTransactionsDirty(wtx2);
EXPECT_FALSE(wallet.mapWallet[hash].fDebitCached);
}
TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
@ -1999,7 +1462,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
// Generate shielding tx from transparent to Sapling
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
// 0.0005 t-HUSH in, 0.0004 z-HUSH out, 0.0001 t-HUSH fee
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk.ovk, pk, 40000, {});
@ -2093,39 +1556,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
TEST(WalletTests, SproutNoteLocking) {
TestWallet wallet;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
auto wtx = GetValidReceive(sk, 10, true);
auto wtx2 = GetValidReceive(sk, 10, true);
JSOutPoint jsoutpt {wtx.GetHash(), 0, 0};
JSOutPoint jsoutpt2 {wtx2.GetHash(),0, 0};
// Test selective locking
wallet.LockNote(jsoutpt);
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt));
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt2));
// Test selective unlocking
wallet.UnlockNote(jsoutpt);
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt));
// Test multiple locking
wallet.LockNote(jsoutpt);
wallet.LockNote(jsoutpt2);
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt));
EXPECT_TRUE(wallet.IsLockedNote(jsoutpt2));
// Test unlock all
wallet.UnlockAllSproutNotes();
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt));
EXPECT_FALSE(wallet.IsLockedNote(jsoutpt2));
}
*/
TEST(WalletTests, SaplingNoteLocking) {
TestWallet wallet;

297
src/wallet/gtest/test_wallet_zkeys.cpp

@ -106,303 +106,6 @@ TEST(wallet_zkeys_tests, StoreAndLoadSaplingZkeys) {
EXPECT_TRUE(wallet.HaveSaplingIncomingViewingKey(dpa2));
}
/**
* This test covers methods on CWallet
* GenerateNewSproutZKey()
* AddSproutZKey()
* LoadZKey()
* LoadZKeyMetadata()
*/
TEST(wallet_zkeys_tests, store_and_load_zkeys) {
SelectParams(CBaseChainParams::MAIN);
CWallet wallet;
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size());
// wallet should have one key
auto addr = wallet.GenerateNewSproutZKey();
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
// verify wallet has spending key for the address
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
// manually add new spending key to wallet
auto sk = libzcash::SproutSpendingKey::random();
ASSERT_TRUE(wallet.AddSproutZKey(sk));
// verify wallet did add it
addr = sk.address();
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
// verify spending key stored correctly
libzcash::SproutSpendingKey keyOut;
wallet.GetSproutSpendingKey(addr, keyOut);
ASSERT_EQ(sk, keyOut);
// verify there are two keys
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(2, addrs.size());
ASSERT_EQ(1, addrs.count(addr));
// Load a third key into the wallet
sk = libzcash::SproutSpendingKey::random();
ASSERT_TRUE(wallet.LoadZKey(sk));
// attach metadata to this third key
addr = sk.address();
int64_t now = GetTime();
CKeyMetadata meta(now);
ASSERT_TRUE(wallet.LoadZKeyMetadata(addr, meta));
// check metadata is the same
CKeyMetadata m= wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, now);
}
/**
* This test covers methods on CWallet
* AddSproutViewingKey()
* RemoveSproutViewingKey()
* LoadSproutViewingKey()
*/
TEST(wallet_zkeys_tests, StoreAndLoadViewingKeys) {
SelectParams(CBaseChainParams::MAIN);
CWallet wallet;
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size());
// manually add new viewing key to wallet
auto sk = libzcash::SproutSpendingKey::random();
auto vk = sk.viewing_key();
ASSERT_TRUE(wallet.AddSproutViewingKey(vk));
// verify wallet did add it
auto addr = sk.address();
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr));
// and that we don't have the corresponding spending key
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr));
// verify viewing key stored correctly
libzcash::SproutViewingKey vkOut;
wallet.GetSproutViewingKey(addr, vkOut);
ASSERT_EQ(vk, vkOut);
// Load a second viewing key into the wallet
auto sk2 = libzcash::SproutSpendingKey::random();
ASSERT_TRUE(wallet.LoadSproutViewingKey(sk2.viewing_key()));
// verify wallet did add it
auto addr2 = sk2.address();
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr2));
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr2));
// Remove the first viewing key
ASSERT_TRUE(wallet.RemoveSproutViewingKey(vk));
ASSERT_FALSE(wallet.HaveSproutViewingKey(addr));
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr2));
}
/**
* This test covers methods on CWalletDB
* WriteZKey()
*/
TEST(wallet_zkeys_tests, write_zkey_direct_to_db) {
SelectParams(CBaseChainParams::TESTNET);
// Get temporary and unique path for file.
// Note: / operator to append paths
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
bool fFirstRun;
CWallet wallet("wallet.dat");
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// No default CPubKey set
ASSERT_TRUE(fFirstRun);
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size());
// Add random key to the wallet
auto paymentAddress = wallet.GenerateNewSproutZKey();
// wallet should have one key
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
// create random key and add it to database directly, bypassing wallet
auto sk = libzcash::SproutSpendingKey::random();
auto addr = sk.address();
int64_t now = GetTime();
CKeyMetadata meta(now);
CWalletDB db("wallet.dat");
db.WriteZKey(addr, sk, meta);
// wallet should not be aware of key
ASSERT_FALSE(wallet.HaveSproutSpendingKey(addr));
// wallet sees one key
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
// wallet should have default metadata for addr with null createtime
CKeyMetadata m = wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, 0);
ASSERT_NE(m.nCreateTime, now);
// load the wallet again
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// wallet can now see the spending key
ASSERT_TRUE(wallet.HaveSproutSpendingKey(addr));
// check key is the same
libzcash::SproutSpendingKey keyOut;
wallet.GetSproutSpendingKey(addr, keyOut);
ASSERT_EQ(sk, keyOut);
// wallet should have two keys
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(2, addrs.size());
// check metadata is now the same
m = wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, now);
}
/**
* This test covers methods on CWalletDB
* WriteSproutViewingKey()
*/
TEST(wallet_zkeys_tests, WriteViewingKeyDirectToDB) {
SelectParams(CBaseChainParams::TESTNET);
// Get temporary and unique path for file.
// Note: / operator to append paths
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
bool fFirstRun;
CWallet wallet("wallet-vkey.dat");
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// No default CPubKey set
ASSERT_TRUE(fFirstRun);
// create random viewing key and add it to database directly, bypassing wallet
auto sk = libzcash::SproutSpendingKey::random();
auto vk = sk.viewing_key();
auto addr = sk.address();
int64_t now = GetTime();
CKeyMetadata meta(now);
CWalletDB db("wallet-vkey.dat");
db.WriteSproutViewingKey(vk);
// wallet should not be aware of viewing key
ASSERT_FALSE(wallet.HaveSproutViewingKey(addr));
// load the wallet again
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// wallet can now see the viewing key
ASSERT_TRUE(wallet.HaveSproutViewingKey(addr));
// check key is the same
libzcash::SproutViewingKey vkOut;
wallet.GetSproutViewingKey(addr, vkOut);
ASSERT_EQ(vk, vkOut);
}
/**
* This test covers methods on CWalletDB to load/save crypted z keys.
*/
TEST(wallet_zkeys_tests, write_cryptedzkey_direct_to_db) {
SelectParams(CBaseChainParams::TESTNET);
// Get temporary and unique path for file.
// Note: / operator to append paths
boost::filesystem::path pathTemp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
boost::filesystem::create_directories(pathTemp);
mapArgs["-datadir"] = pathTemp.string();
bool fFirstRun;
CWallet wallet("wallet_crypted.dat");
ASSERT_EQ(DB_LOAD_OK, wallet.LoadWallet(fFirstRun));
// No default CPubKey set
ASSERT_TRUE(fFirstRun);
// wallet should be empty
std::set<libzcash::SproutPaymentAddress> addrs;
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(0, addrs.size());
// Add random key to the wallet
auto paymentAddress = wallet.GenerateNewSproutZKey();
// wallet should have one key
wallet.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(1, addrs.size());
// encrypt wallet
SecureString strWalletPass;
strWalletPass.reserve(100);
strWalletPass = "hello";
ASSERT_TRUE(wallet.EncryptWallet(strWalletPass));
// adding a new key will fail as the wallet is locked
EXPECT_ANY_THROW(wallet.GenerateNewSproutZKey());
// unlock wallet and then add
wallet.Unlock(strWalletPass);
auto paymentAddress2 = wallet.GenerateNewSproutZKey();
// Create a new wallet from the existing wallet path
CWallet wallet2("wallet_crypted.dat");
ASSERT_EQ(DB_LOAD_OK, wallet2.LoadWallet(fFirstRun));
// Confirm it's not the same as the other wallet
ASSERT_TRUE(&wallet != &wallet2);
// wallet should have two keys
wallet2.GetSproutPaymentAddresses(addrs);
ASSERT_EQ(2, addrs.size());
// check we have entries for our payment addresses
ASSERT_TRUE(addrs.count(paymentAddress));
ASSERT_TRUE(addrs.count(paymentAddress2));
// spending key is crypted, so we can't extract valid payment address
libzcash::SproutSpendingKey keyOut;
wallet2.GetSproutSpendingKey(paymentAddress, keyOut);
ASSERT_FALSE(paymentAddress == keyOut.address());
// unlock wallet to get spending keys and verify payment addresses
wallet2.Unlock(strWalletPass);
wallet2.GetSproutSpendingKey(paymentAddress, keyOut);
ASSERT_EQ(paymentAddress, keyOut.address());
wallet2.GetSproutSpendingKey(paymentAddress2, keyOut);
ASSERT_EQ(paymentAddress2, keyOut.address());
}
/**
* This test covers methods on CWalletDB to load/save crypted sapling z keys.
*/

319
src/wallet/rpcdisclosure.cpp

@ -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;
}

114
src/wallet/rpcdump.cpp

@ -149,6 +149,43 @@ UniValue convertpassphrase(const UniValue& params, bool fHelp, const CPubKey& my
return ret;
}
UniValue rescan(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
//LOCK2(cs_main, pwalletMain->cs_wallet);
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 1)
throw runtime_error(
"rescan \"height\"\n"
"\nRescan all transactions from genesis or given block height.\n"
"\nArguments:\n"
"1. \"height\" (integer, optional) The block height to rescan from\n"
"\nExamples:\n"
"\nRescan from block height 555\n"
+ HelpExampleCli("rescan", "\"555\"") +
"\nRescan from genesis block\n"
+ HelpExampleCli("rescan","")
);
// Height to rescan from
int nRescanHeight = 0;
if (params.size() > 0)
nRescanHeight = params[0].get_int();
if (nRescanHeight < 0 || nRescanHeight > chainActive.Height()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Block height out of range");
}
LogPrintf("Rescanning from height=%d\n", nRescanHeight);
//pwalletMain->ScanForWalletTransactions(chainActive[nRescanHeight],true);
bool update = false;
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(),update);
//TODO: can we return something more useful?
return NullUniValue;
}
UniValue importprivkey(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
@ -856,48 +893,27 @@ UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& m
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid viewing key");
}
if (boost::get<libzcash::SproutViewingKey>(&viewingkey) == nullptr) {
if (params.size() < 4) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key.");
}
string strAddress = params[3].get_str();
auto address = DecodePaymentAddress(strAddress);
if (!IsValidPaymentAddress(address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
}
if (params.size() < 4) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Missing zaddr for Sapling viewing key.");
}
string strAddress = params[3].get_str();
auto address = DecodePaymentAddress(strAddress);
if (!IsValidPaymentAddress(address)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
}
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
auto ivk = boost::get<libzcash::SaplingIncomingViewingKey>(viewingkey);
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
auto ivk = boost::get<libzcash::SaplingIncomingViewingKey>(viewingkey);
if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) {
if (fIgnoreExistingKey) {
return NullUniValue;
}
} else {
pwalletMain->MarkDirty();
if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
}
if (pwalletMain->HaveSaplingIncomingViewingKey(addr)) {
if (fIgnoreExistingKey) {
return NullUniValue;
}
} else {
auto vkey = boost::get<libzcash::SproutViewingKey>(viewingkey);
auto addr = vkey.address();
if (pwalletMain->HaveSproutSpendingKey(addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this viewing key");
}
// Don't throw error in case a viewing key is already there
if (pwalletMain->HaveSproutViewingKey(addr)) {
if (fIgnoreExistingKey) {
return NullUniValue;
}
} else {
pwalletMain->MarkDirty();
pwalletMain->MarkDirty();
if (!pwalletMain->AddSproutViewingKey(vkey)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
}
if (!pwalletMain->AddSaplingIncomingViewingKey(ivk, addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding viewing key to wallet");
}
}
@ -977,26 +993,12 @@ UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPubKey& m
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr");
}
if (boost::get<libzcash::SproutPaymentAddress>(&address) == nullptr) {
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
libzcash::SaplingIncomingViewingKey ivk;
if(!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr");
}
return EncodeViewingKey(ivk);
auto addr = boost::get<libzcash::SaplingPaymentAddress>(address);
libzcash::SaplingIncomingViewingKey ivk;
if(!pwalletMain->GetSaplingIncomingViewingKey(addr, ivk)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold viewing key for this zaddr");
}
auto addr = boost::get<libzcash::SproutPaymentAddress>(address);
libzcash::SproutViewingKey vk;
if (!pwalletMain->GetSproutViewingKey(addr, vk)) {
libzcash::SproutSpendingKey k;
if (!pwalletMain->GetSproutSpendingKey(addr, k)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Wallet does not hold private key or viewing key for this zaddr");
}
vk = k.viewing_key();
}
return EncodeViewingKey(vk);
return EncodeViewingKey(ivk);
}
extern int32_t KOMODO_NSPV;

2
src/wallet/rpchushwallet.cpp

@ -1,4 +1,4 @@
// Copyright (c) 2020 The Hush developers
// Copyright (c) 2019-2020 The Hush developers
// Copyright (c) 2019 Cryptoforge
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

220
src/wallet/rpcwallet.cpp

@ -38,6 +38,7 @@
#include "zcbenchmarks.h"
#include "script/interpreter.h"
#include "zcash/zip32.h"
#include "zcash/Note.hpp"
#include "notaries_staked.h"
#include "utiltime.h"
@ -70,7 +71,6 @@ using namespace libzcash;
extern char ASSETCHAINS_SYMBOL[KOMODO_ASSETCHAIN_MAXLEN];
extern std::string ASSETCHAINS_OVERRIDE_PUBKEY;
const std::string ADDR_TYPE_SPROUT = "sprout";
const std::string ADDR_TYPE_SAPLING = "sapling";
extern UniValue TxJoinSplitToJSON(const CTransaction& tx);
extern int32_t KOMODO_INSYNC;
@ -3123,7 +3123,7 @@ UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&)
if (wtx.GetDepthInMainChain() < 0)
continue;
if (wtx.mapSaplingNoteData.size() == 0 && wtx.mapSproutNoteData.size() == 0 && !wtx.IsTrusted())
if (wtx.mapSaplingNoteData.size() == 0 && !wtx.IsTrusted())
continue;
//Assign Immature
@ -3309,7 +3309,7 @@ UniValue getalldata(const UniValue& params, bool fHelp,const CPubKey&)
if (!CheckFinalTx(wtx))
continue;
if (wtx.mapSaplingNoteData.size() == 0 && wtx.mapSproutNoteData.size() == 0 && !wtx.IsTrusted())
if (wtx.mapSaplingNoteData.size() == 0 && !wtx.IsTrusted())
continue;
//Excude transactions with less confirmations than required
@ -3787,9 +3787,8 @@ UniValue z_listunspent(const UniValue& params, bool fHelp, const CPubKey& mypk)
UniValue results(UniValue::VARR);
if (zaddrs.size() > 0) {
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs, nMinDepth, nMaxDepth, true, !fIncludeWatchonly, false);
std::set<std::pair<PaymentAddress, uint256>> nullifierSet = pwalletMain->GetNullifiersForAddresses(zaddrs);
for (auto & entry : saplingEntries) {
@ -4047,10 +4046,9 @@ CAmount getBalanceTaddr(std::string transparentAddress, int minDepth=1, bool ign
CAmount getBalanceZaddr(std::string address, int minDepth = 1, bool ignoreUnspendable=true) {
CAmount balance = 0;
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
LOCK2(cs_main, pwalletMain->cs_wallet);
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, address, minDepth, true, ignoreUnspendable);
pwalletMain->GetFilteredNotes(saplingEntries, address, minDepth, true, ignoreUnspendable);
for (auto & entry : saplingEntries) {
balance += CAmount(entry.note.value());
}
@ -4102,16 +4100,14 @@ UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp, const CPubK
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid zaddr.");
}
// Visitor to support Sprout and Sapling addrs
if (!boost::apply_visitor(PaymentAddressBelongsToWallet(pwalletMain), zaddr) &&
!boost::apply_visitor(IncomingViewingKeyBelongsToWallet(pwalletMain), zaddr)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key or viewing key not found.");
}
UniValue result(UniValue::VARR);
std::vector<CSproutNotePlaintextEntry> sproutEntries;
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, fromaddress, nMinDepth, false, false);
pwalletMain->GetFilteredNotes(saplingEntries, fromaddress, nMinDepth, false, false);
std::set<std::pair<PaymentAddress, uint256>> nullifierSet;
auto hasSpendingKey = boost::apply_visitor(HaveSpendingKeyForPaymentAddress(pwalletMain), zaddr);
@ -4213,6 +4209,42 @@ UniValue z_getbalance(const UniValue& params, bool fHelp, const CPubKey& mypk)
return ValueFromAmount(nBalance);
}
UniValue z_getnotescount(const UniValue& params, bool fHelp,const CPubKey& mypk)
{
if (!EnsureWalletIsAvailable(fHelp))
return NullUniValue;
if (fHelp || params.size() > 1)
throw runtime_error(
"z_getnotescount\n"
"\nArguments:\n"
"1. minconf (numeric, optional, default=1) Only include notes in transactions confirmed at least this many times.\n"
"\nReturns the number of sapling notes available in the wallet.\n"
"\nResult:\n"
"{\n"
" \"sapling\" (numeric) the number of sapling notes in the wallet\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("z_getnotescount", "0")
);
LOCK2(cs_main, pwalletMain->cs_wallet);
int nMinDepth = 1;
if (params.size() > 0)
nMinDepth = params[0].get_int();
int sapling = 0;
for (auto& wtx : pwalletMain->mapWallet) {
if (wtx.second.GetDepthInMainChain() >= nMinDepth) {
sapling += wtx.second.mapSaplingNoteData.size();
}
}
UniValue ret(UniValue::VOBJ);
ret.push_back(Pair("sapling", sapling));
return ret;
}
UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPubKey& mypk)
{
@ -4232,7 +4264,7 @@ UniValue z_gettotalbalance(const UniValue& params, bool fHelp, const CPubKey& my
"\nResult:\n"
"{\n"
" \"transparent\": xxxxx, (numeric) the total balance of transparent funds\n"
" \"private\": xxxxx, (numeric) the total balance of private funds (in both Sprout and Sapling addresses)\n"
" \"private\": xxxxx, (numeric) the total balance of private funds\n"
" \"total\": xxxxx, (numeric) the total balance of both transparent and private funds\n"
"}\n"
"\nExamples:\n"
@ -4352,7 +4384,9 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp, const CPubKey& my
// Fetch the note that is being spent
auto res = pwalletMain->mapSaplingNullifiersToNotes.find(spend.nullifier);
if (res == pwalletMain->mapSaplingNullifiersToNotes.end()) {
fprintf(stderr,"Could not find spending note %s", uint256_str(str, spend.nullifier));
if(fZdebug) {
fprintf(stderr,"Could not find spending note %s\n", uint256_str(str, spend.nullifier));
}
continue;
}
auto op = res->second;
@ -4406,8 +4440,10 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp, const CPubKey& my
pa = recovered->second;
isOutgoing = true;
} else {
// Unreadable
fprintf(stderr,"Could not recover Sapling note!");
// Unreadable or unconfirmed?
if(fZdebug) {
fprintf(stderr,"Could not recover Sapling note!\n");
}
continue;
}
}
@ -4610,14 +4646,9 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "From address does not belong to this node, zaddr spending key not found.");
}
// Remember whether this is a Sprout or Sapling address
// Remember whether this is a Sapling address
fromSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
}
// This logic will need to be updated if we add a new shielded pool
bool fromSprout = !(fromTaddr || fromSapling);
if (fromSprout)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot send from a Sprout zaddr, only Sapling zaddrs supported.");
UniValue outputs = params[1].get_array();
@ -4627,15 +4658,11 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
// Keep track of addresses to spot duplicates
set<std::string> setAddress;
// Track whether we see any Sprout addresses
bool noSproutAddrs = !fromSprout;
// Recipients
std::vector<SendManyRecipient> taddrRecipients;
std::vector<SendManyRecipient> zaddrRecipients;
CAmount nTotalOut = 0;
bool containsSproutOutput = false;
bool containsSaplingOutput = false;
for (const UniValue& o : outputs.getValues()) {
@ -4656,35 +4683,6 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
auto res = DecodePaymentAddress(address);
if (IsValidPaymentAddress(res, branchId)) {
isZaddr = true;
bool toSapling = boost::get<libzcash::SaplingPaymentAddress>(&res) != nullptr;
bool toSprout = !toSapling;
noSproutAddrs = noSproutAddrs && toSapling;
containsSproutOutput |= toSprout;
containsSaplingOutput |= toSapling;
// Sending to both Sprout and Sapling is currently unsupported using z_sendmany
if (containsSproutOutput && containsSaplingOutput) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send to both Sprout and Sapling addresses using z_sendmany");
}
if ( GetTime() > KOMODO_SAPLING_DEADLINE )
{
if ( fromSprout || toSprout )
throw JSONRPCError(RPC_INVALID_PARAMETER,"Sprout usage has expired");
}
if ( toSapling && ASSETCHAINS_SYMBOL[0] == 0 )
throw JSONRPCError(RPC_INVALID_PARAMETER,"Sprout usage will expire soon");
// If we are sending from a shielded address, all recipient
// shielded addresses must be of the same type.
if ((fromSprout && toSapling) || (fromSapling && toSprout)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send between Sprout and Sapling addresses using z_sendmany");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ")+address );
}
@ -4875,9 +4873,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp, const CPubKey& mypk)
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (noSproutAddrs) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
}
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
// Contextual transaction we will build on
// (used if no Sapling addresses are involved)
@ -5140,7 +5136,6 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp, const CPubKey& myp
#define MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT 10
#define MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT 90
#define JOINSPLIT_SIZE GetSerializeSize(JSDescription(), SER_NETWORK, PROTOCOL_VERSION)
#define OUTPUTDESCRIPTION_SIZE GetSerializeSize(OutputDescription(), SER_NETWORK, PROTOCOL_VERSION)
#define SPENDDESCRIPTION_SIZE GetSerializeSize(SpendDescription(), SER_NETWORK, PROTOCOL_VERSION)
@ -5150,16 +5145,15 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
return NullUniValue;
string enableArg = "zmergetoaddress";
auto fEnableMergeToAddress = fExperimentalMode && GetBoolArg("-" + enableArg, true);
std::string strDisabledMsg = "";
if (!fEnableMergeToAddress) {
strDisabledMsg = experimentalDisabledHelpMsg("z_mergetoaddress", enableArg);
}
//auto fEnableMergeToAddress = fExperimentalMode && GetBoolArg("-" + enableArg, true);
//std::string strDisabledMsg = "";
//if (!fEnableMergeToAddress) {
// strDisabledMsg = experimentalDisabledHelpMsg("z_mergetoaddress", enableArg);
//}
if (fHelp || params.size() < 2 || params.size() > 7)
throw runtime_error(
"z_mergetoaddress [\"fromaddress\", ... ] \"toaddress\" ( fee ) ( transparent_limit ) ( shielded_limit ) ( memo )\n"
+ strDisabledMsg +
"\nMerge multiple UTXOs and notes into a single UTXO or note. Coinbase UTXOs are ignored; use `z_shieldcoinbase`"
"\nto combine those into a single note."
"\n\nThis is an asynchronous operation, and UTXOs selected for merging will be locked. If there is an error, they"
@ -5203,20 +5197,15 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
" \"opid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("z_mergetoaddress", "'[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"]' ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf")
+ HelpExampleRpc("z_mergetoaddress", "[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"], \"ztfaW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"")
+ HelpExampleCli("z_mergetoaddress", "'[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"]' zs1aW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf")
+ HelpExampleRpc("z_mergetoaddress", "[\"RD6GgnrMpPaTSMn8vai6yiGA7mN4QGPV\"], \"zs1aW34Gj9FrnGUEf833ywDVL62NWXBM81u6EQnM6VR45eYnXhwztecW1SjxA7JrmAXKJhxhj3vDNEpVCQoSvVoSpmbhtjf\"")
);
if (!fEnableMergeToAddress) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error: z_mergetoaddress is disabled.");
}
LOCK2(cs_main, pwalletMain->cs_wallet);
THROW_IF_SYNCING(KOMODO_INSYNC);
bool useAnyUTXO = false;
bool useAnySprout = false;
bool useAnySapling = false;
std::set<CTxDestination> taddrs = {};
std::set<libzcash::PaymentAddress> zaddrs = {};
@ -5237,8 +5226,6 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
if (address == "ANY_TADDR") {
useAnyUTXO = true;
} else if (address == "ANY_SPROUT") {
useAnySprout = true;
} else if (address == "ANY_SAPLING") {
useAnySapling = true;
} else {
@ -5263,8 +5250,8 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
if (useAnyUTXO && taddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific t-addrs when using \"ANY_TADDR\"");
}
if ((useAnySprout || useAnySapling) && zaddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific z-addrs when using \"ANY_SPROUT\" or \"ANY_SAPLING\"");
if ((useAnySapling) && zaddrs.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify specific z-addrs when using \"ANY_SAPLING\"");
}
const int nextBlockHeight = chainActive.Height() + 1;
@ -5273,7 +5260,6 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
// Validate the destination address
auto destaddress = params[1].get_str();
bool isToSproutZaddr = false;
bool isToSaplingZaddr = false;
CTxDestination taddr = DecodeDestination(destaddress);
if (!IsValidDestination(taddr)) {
@ -5286,7 +5272,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
} else {
isToSproutZaddr = true;
throw JSONRPCError(RPC_INVALID_PARAMETER, "Only Sapling zaddrs allowed!");
}
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress );
@ -5311,14 +5297,12 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
}
}
int sproutNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SPROUT_LIMIT;
int saplingNoteLimit = MERGE_TO_ADDRESS_DEFAULT_SAPLING_LIMIT;
if (params.size() > 4) {
int nNoteLimit = params[4].get_int();
if (nNoteLimit < 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Limit on maximum number of notes cannot be negative");
}
sproutNoteLimit = nNoteLimit;
saplingNoteLimit = nNoteLimit;
}
@ -5335,7 +5319,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
std::string memo;
if (params.size() > 6) {
memo = params[6].get_str();
if (!(isToSproutZaddr || isToSaplingZaddr)) {
if (!isToSaplingZaddr) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo can not be used with a taddr. It can only be used with a zaddr.");
} else if (!IsHex(memo)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format.");
@ -5349,7 +5333,6 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
// Prepare to get UTXOs and notes
std::vector<MergeToAddressInputUTXO> utxoInputs;
std::vector<MergeToAddressInputSproutNote> sproutNoteInputs;
std::vector<MergeToAddressInputSaplingNote> saplingNoteInputs;
CAmount mergedUTXOValue = 0;
CAmount mergedNoteValue = 0;
@ -5363,9 +5346,8 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
unsigned int max_tx_size = saplingActive ? MAX_TX_SIZE_AFTER_SAPLING : MAX_TX_SIZE_BEFORE_SAPLING;
size_t estimatedTxSize = 200; // tx overhead + wiggle room
if (isToSproutZaddr) {
estimatedTxSize += JOINSPLIT_SIZE;
} else if (isToSaplingZaddr) {
if (isToSaplingZaddr) {
estimatedTxSize += OUTPUTDESCRIPTION_SIZE;
}
@ -5423,57 +5405,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
}
}
if (useAnySprout || useAnySapling || zaddrs.size() > 0) {
if (useAnySapling || zaddrs.size() > 0) {
// Get available notes
std::vector<CSproutNotePlaintextEntry> sproutEntries;
//std::vector<SaplingNoteEntry> saplingEntries;
//pwalletMain->GetFilteredNotes(sproutEntries, saplingEntries, zaddrs);
std::vector<SaplingNoteEntry> saplingEntries,skipsapling;
pwalletMain->GetFilteredNotes(sproutEntries, useAnySprout == 0 ? saplingEntries : skipsapling, zaddrs);
// If Sapling is not active, do not allow sending from a sapling addresses.
if (!saplingActive && saplingEntries.size() > 0) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling has not activated");
}
// Sending from both Sprout and Sapling is currently unsupported using z_mergetoaddress
if (sproutEntries.size() > 0 && saplingEntries.size() > 0) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send from both Sprout and Sapling addresses using z_mergetoaddress");
}
// If sending between shielded addresses, they must be the same type
if ((saplingEntries.size() > 0 && isToSproutZaddr) || (sproutEntries.size() > 0 && isToSaplingZaddr)) {
throw JSONRPCError(
RPC_INVALID_PARAMETER,
"Cannot send between Sprout and Sapling addresses using z_mergetoaddress");
}
// Find unspent notes and update estimated size
for (const CSproutNotePlaintextEntry& entry : sproutEntries) {
noteCounter++;
CAmount nValue = entry.plaintext.value();
if (!maxedOutNotesFlag) {
// If we haven't added any notes yet and the merge is to a
// z-address, we have already accounted for the first JoinSplit.
size_t increase = (sproutNoteInputs.empty() && !isToSproutZaddr) || (sproutNoteInputs.size() % 2 == 0) ? JOINSPLIT_SIZE : 0;
if (estimatedTxSize + increase >= max_tx_size ||
(sproutNoteLimit > 0 && noteCounter > sproutNoteLimit))
{
maxedOutNotesFlag = true;
} else {
estimatedTxSize += increase;
auto zaddr = entry.address;
SproutSpendingKey zkey;
pwalletMain->GetSproutSpendingKey(zaddr, zkey);
sproutNoteInputs.emplace_back(entry.jsop, entry.plaintext.note(zaddr), nValue, zkey);
mergedNoteValue += nValue;
}
}
if (maxedOutNotesFlag) {
remainingNoteValue += nValue;
}
}
std::vector<SaplingNoteEntry> saplingEntries;
pwalletMain->GetFilteredNotes(saplingEntries, zaddrs);
for (const SaplingNoteEntry& entry : saplingEntries) {
noteCounter++;
@ -5502,7 +5437,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
}
size_t numUtxos = utxoInputs.size();
size_t numNotes = sproutNoteInputs.size() + saplingNoteInputs.size();
size_t numNotes = saplingNoteInputs.size();
//fprintf(stderr, "num utxos.%li\n", numUtxos);
if (numUtxos < 2 && numNotes == 0) {
@ -5540,22 +5475,19 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp, const CPubKey& myp
CMutableTransaction contextualTx = CreateNewContextualCMutableTransaction(
Params().GetConsensus(),
nextBlockHeight);
bool isSproutShielded = sproutNoteInputs.size() > 0 || isToSproutZaddr;
if (contextualTx.nVersion == 1 && isSproutShielded) {
contextualTx.nVersion = 2; // Tx format should support vjoinsplit
}
// Builder (used if Sapling addresses are involved)
boost::optional<TransactionBuilder> builder;
if (isToSaplingZaddr || saplingNoteInputs.size() > 0) {
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
} else
} else {
contextualTx.nExpiryHeight = 0; // set non z-tx to have no expiry height.
}
// Create operation and add to global queue
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
std::shared_ptr<AsyncRPCOperation> operation(
new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
q->addOperation(operation);
AsyncRPCOperationId operationId = operation->getId();
@ -8296,9 +8228,8 @@ extern UniValue z_exportviewingkey(const UniValue& params, bool fHelp, const CPu
extern UniValue z_importviewingkey(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue z_exportwallet(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue z_importwallet(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue rescan(const UniValue& params, bool fHelp, const CPubKey& mypk);
extern UniValue z_getpaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcdisclosure.cpp
extern UniValue z_validatepaymentdisclosure(const UniValue& params, bool fHelp, const CPubKey& mypk);
static const CRPCCommand commands[] =
{ // category name actor (function) okSafeMode
@ -8337,6 +8268,7 @@ static const CRPCCommand commands[] =
{ "wallet", "listunspent", &listunspent, false },
{ "wallet", "lockunspent", &lockunspent, true },
{ "wallet", "move", &movecmd, false },
{ "wallet", "rescan", &rescan, false },
{ "wallet", "sendfrom", &sendfrom, false },
{ "wallet", "sendmany", &sendmany, false },
{ "wallet", "sendtoaddress", &sendtoaddress, false },
@ -8368,9 +8300,7 @@ static const CRPCCommand commands[] =
{ "wallet", "z_getinfo", &z_getinfo, true },
{ "wallet", "z_listsentbyaddress", &z_listsentbyaddress, true },
{ "wallet", "z_listreceivedbyaddress", &z_listreceivedbyaddress, true },
// TODO: rearrange into another category
{ "disclosure", "z_getpaymentdisclosure", &z_getpaymentdisclosure, true },
{ "disclosure", "z_validatepaymentdisclosure", &z_validatepaymentdisclosure, true }
{ "wallet", "z_getnotescount", &z_getnotescount, false }
};

670
src/wallet/wallet.cpp

@ -1,6 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2019 The Hush 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.
@ -114,26 +114,6 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
return &(it->second);
}
// Generate a new spending key and return its public payment address
libzcash::SproutPaymentAddress CWallet::GenerateNewSproutZKey()
{
AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata
auto k = SproutSpendingKey::random();
auto addr = k.address();
// Check for collision, even though it is unlikely to ever occur
if (CCryptoKeyStore::HaveSproutSpendingKey(addr))
throw std::runtime_error("CWallet::GenerateNewSproutZKey(): Collision detected");
// Create new metadata
int64_t nCreationTime = GetTime();
mapSproutZKeyMetadata[addr] = CKeyMetadata(nCreationTime);
if (!AddSproutZKey(k))
throw std::runtime_error("CWallet::GenerateNewSproutZKey(): AddSproutZKey failed");
return addr;
}
// Generate a new Sapling spending key and return its public payment address
SaplingPaymentAddress CWallet::GenerateNewSaplingZKey()
@ -231,30 +211,6 @@ bool CWallet::AddSaplingIncomingViewingKey(
}
// Add spending key to keystore and persist to disk
bool CWallet::AddSproutZKey(const libzcash::SproutSpendingKey &key)
{
AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata
auto addr = key.address();
if (!CCryptoKeyStore::AddSproutSpendingKey(key))
return false;
// check if we need to remove from viewing keys
if (HaveSproutViewingKey(addr))
RemoveSproutViewingKey(key.viewing_key());
if (!fFileBacked)
return true;
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteZKey(addr,
key,
mapSproutZKeyMetadata[addr]);
}
return true;
}
CPubKey CWallet::GenerateNewKey()
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
@ -325,33 +281,6 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
return false;
}
bool CWallet::AddCryptedSproutSpendingKey(
const libzcash::SproutPaymentAddress &address,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::AddCryptedSproutSpendingKey(address, rk, vchCryptedSecret))
return false;
if (!fFileBacked)
return true;
{
LOCK(cs_wallet);
if (pwalletdbEncryption) {
return pwalletdbEncryption->WriteCryptedZKey(address,
rk,
vchCryptedSecret,
mapSproutZKeyMetadata[address]);
} else {
return CWalletDB(strWalletFile).WriteCryptedZKey(address,
rk,
vchCryptedSecret,
mapSproutZKeyMetadata[address]);
}
}
return false;
}
bool CWallet::AddCryptedSaplingSpendingKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char> &vchCryptedSecret,
const libzcash::SaplingPaymentAddress &defaultAddr)
@ -385,22 +314,11 @@ bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
return true;
}
bool CWallet::LoadZKeyMetadata(const SproutPaymentAddress &addr, const CKeyMetadata &meta)
{
AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata
mapSproutZKeyMetadata[addr] = meta;
return true;
}
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
}
bool CWallet::LoadCryptedZKey(const libzcash::SproutPaymentAddress &addr, const libzcash::ReceivingKey &rk, const std::vector<unsigned char> &vchCryptedSecret)
{
return CCryptoKeyStore::AddCryptedSproutSpendingKey(addr, rk, vchCryptedSecret);
}
bool CWallet::LoadCryptedSaplingZKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,
@ -428,43 +346,6 @@ bool CWallet::LoadSaplingPaymentAddress(
return CCryptoKeyStore::AddSaplingIncomingViewingKey(ivk, addr);
}
bool CWallet::LoadZKey(const libzcash::SproutSpendingKey &key)
{
return CCryptoKeyStore::AddSproutSpendingKey(key);
}
bool CWallet::AddSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
if (!CCryptoKeyStore::AddSproutViewingKey(vk)) {
return false;
}
nTimeFirstKey = 1; // No birthday information for viewing keys.
if (!fFileBacked) {
return true;
}
return CWalletDB(strWalletFile).WriteSproutViewingKey(vk);
}
bool CWallet::RemoveSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
AssertLockHeld(cs_wallet);
if (!CCryptoKeyStore::RemoveSproutViewingKey(vk)) {
return false;
}
if (fFileBacked) {
if (!CWalletDB(strWalletFile).EraseSproutViewingKey(vk)) {
return false;
}
}
return true;
}
bool CWallet::LoadSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
return CCryptoKeyStore::AddSproutViewingKey(vk);
}
bool CWallet::AddCScript(const CScript& redeemScript)
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
@ -681,29 +562,6 @@ std::set<std::pair<libzcash::PaymentAddress, uint256>> CWallet::GetNullifiersFor
return nullifierSet;
}
bool CWallet::IsNoteSproutChange(
const std::set<std::pair<libzcash::PaymentAddress, uint256>> & nullifierSet,
const PaymentAddress & address,
const JSOutPoint & jsop)
{
// A Note is marked as "change" if the address that received it
// also spent Notes in the same transaction. This will catch,
// for instance:
// - Change created by spending fractions of Notes (because
// z_sendmany sends change to the originating z-address).
// - "Chaining Notes" used to connect JoinSplits together.
// - Notes created by consolidation transactions (e.g. using
// z_mergetoaddress).
// - Notes sent from one address to itself.
for (const JSDescription & jsd : mapWallet[jsop.hash].vjoinsplit) {
for (const uint256 & nullifier : jsd.nullifiers) {
if (nullifierSet.count(std::make_pair(address, nullifier))) {
return true;
}
}
}
return false;
}
bool CWallet::IsNoteSaplingChange(const std::set<std::pair<libzcash::PaymentAddress, uint256>> & nullifierSet,
const libzcash::PaymentAddress & address,
@ -785,20 +643,6 @@ set<uint256> CWallet::GetConflicts(const uint256& txid) const
result.insert(it->second);
}
std::pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range_n;
for (const JSDescription& jsdesc : wtx.vjoinsplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
if (mapTxSproutNullifiers.count(nullifier) <= 1) {
continue; // No conflict if zero or one spends
}
range_n = mapTxSproutNullifiers.equal_range(nullifier);
for (TxNullifiers::const_iterator it = range_n.first; it != range_n.second; ++it) {
result.insert(it->second);
}
}
}
std::pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range_o;
for (const SpendDescription &spend : wtx.vShieldedSpend) {
@ -941,38 +785,6 @@ unsigned int CWallet::GetSpendDepth(const uint256& hash, unsigned int n) const
return 0;
}
/**
* Note is spent if any non-conflicted transaction
* spends it:
*/
bool CWallet::IsSproutSpent(const uint256& nullifier) const {
pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range;
range = mapTxSproutNullifiers.equal_range(nullifier);
for (TxNullifiers::const_iterator it = range.first; it != range.second; ++it) {
const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) {
return true; // Spent
}
}
return false;
}
unsigned int CWallet::GetSproutSpendDepth(const uint256& nullifier) const {
pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range;
range = mapTxSproutNullifiers.equal_range(nullifier);
for (TxNullifiers::const_iterator it = range.first; it != range.second; ++it) {
const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0) {
return mit->second.GetDepthInMainChain(); // Spent
}
}
return 0;
}
bool CWallet::IsSaplingSpent(const uint256& nullifier) const {
pair<TxNullifiers::const_iterator, TxNullifiers::const_iterator> range;
range = mapTxSaplingNullifiers.equal_range(nullifier);
@ -1010,15 +822,6 @@ void CWallet::AddToTransparentSpends(const COutPoint& outpoint, const uint256& w
SyncMetaData<COutPoint>(range);
}
void CWallet::AddToSproutSpends(const uint256& nullifier, const uint256& wtxid)
{
mapTxSproutNullifiers.insert(make_pair(nullifier, wtxid));
pair<TxNullifiers::iterator, TxNullifiers::iterator> range;
range = mapTxSproutNullifiers.equal_range(nullifier);
SyncMetaData<uint256>(range);
}
void CWallet::AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid)
{
mapTxSaplingNullifiers.insert(make_pair(nullifier, wtxid));
@ -1038,11 +841,6 @@ void CWallet::AddToSpends(const uint256& wtxid)
for (const CTxIn& txin : thisTx.vin) {
AddToTransparentSpends(txin.prevout, wtxid);
}
for (const JSDescription& jsdesc : thisTx.vjoinsplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
AddToSproutSpends(nullifier, wtxid);
}
}
for (const SpendDescription &spend : thisTx.vShieldedSpend) {
AddToSaplingSpends(spend.nullifier, wtxid);
}
@ -1098,21 +896,6 @@ void CWallet::DecrementNoteWitnesses(const CBlockIndex* pindex)
extern int32_t KOMODO_REWIND;
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
//Sprout
for (auto& item : wtxItem.second.mapSproutNoteData) {
auto* nd = &(item.second);
if (nd->nullifier && pwalletMain->GetSproutSpendDepth(*item.second.nullifier) <= WITNESS_CACHE_SIZE) {
// Only decrement witnesses that are not above the current height
if (nd->witnessHeight <= pindex->GetHeight()) {
if (nd->witnesses.size() > 1) {
// indexHeight is the height of the block being removed, so
// the new witness cache height is one below it.
nd->witnesses.pop_front();
nd->witnessHeight = pindex->GetHeight() - 1;
}
}
}
}
//Sapling
for (auto& item : wtxItem.second.mapSaplingNoteData) {
auto* nd = &(item.second);
@ -1140,14 +923,6 @@ void ClearSingleNoteWitnessCache(NoteData* nd)
nd->witnessRootValidated = false;
}
int CWallet::SproutWitnessMinimumHeight(const uint256& nullifier, int nWitnessHeight, int nMinimumHeight)
{
if (GetSproutSpendDepth(nullifier) <= WITNESS_CACHE_SIZE) {
nMinimumHeight = min(nWitnessHeight, nMinimumHeight);
}
return nMinimumHeight;
}
int CWallet::SaplingWitnessMinimumHeight(const uint256& nullifier, int nWitnessHeight, int nMinimumHeight)
{
if (GetSaplingSpendDepth(nullifier) <= WITNESS_CACHE_SIZE) {
@ -1167,7 +942,7 @@ int CWallet::VerifyAndSetInitialWitness(const CBlockIndex* pindex, bool witnessO
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
nWitnessTxIncrement += 1;
if (wtxItem.second.mapSproutNoteData.empty() && wtxItem.second.mapSaplingNoteData.empty())
if (wtxItem.second.mapSaplingNoteData.empty())
continue;
if (wtxItem.second.GetDepthInMainChain() > 0) {
@ -1288,10 +1063,6 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly)
LogPrintf("Building Witnesses for block %i %.4f complete\n", pblockindex->GetHeight(), pblockindex->GetHeight() / double(height));
}
SproutMerkleTree sproutTree;
sproutRoot = pblockindex->pprev->hashFinalSproutRoot;
pcoinsTip->GetSproutAnchorAt(sproutRoot, sproutTree);
SaplingMerkleTree saplingTree;
saplingRoot = pblockindex->pprev->hashFinalSaplingRoot;
pcoinsTip->GetSaplingAnchorAt(saplingRoot, saplingTree);
@ -1302,7 +1073,7 @@ void CWallet::BuildWitnessCache(const CBlockIndex* pindex, bool witnessOnly)
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
if (wtxItem.second.mapSproutNoteData.empty() && wtxItem.second.mapSaplingNoteData.empty())
if (wtxItem.second.mapSaplingNoteData.empty())
continue;
if (wtxItem.second.GetDepthInMainChain() > 0) {
@ -1492,22 +1263,6 @@ bool CWallet::UpdateNullifierNoteMap()
ZCNoteDecryption dec;
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
for (mapSproutNoteData_t::value_type& item : wtxItem.second.mapSproutNoteData) {
if (!item.second.nullifier) {
if (GetNoteDecryptor(item.second.address, dec)) {
auto i = item.first.js;
auto hSig = wtxItem.second.vjoinsplit[i].h_sig(
*pzcashParams, wtxItem.second.joinSplitPubKey);
item.second.nullifier = GetSproutNoteNullifier(
wtxItem.second.vjoinsplit[i],
item.second.address,
dec,
hSig,
item.first.n);
}
}
}
// TODO: Sapling. This method is only called from RPC walletpassphrase, which is currently unsupported
// as RPC encryptwallet is hidden behind two flags: -developerencryptwallet -experimentalfeatures
@ -1518,7 +1273,7 @@ bool CWallet::UpdateNullifierNoteMap()
}
/**
* Update mapSproutNullifiersToNotes and mapSaplingNullifiersToNotes
* Update mapSaplingNullifiersToNotes
* with the cached nullifiers in this tx.
*/
void CWallet::UpdateNullifierNoteMapWithTx(const CWalletTx& wtx)
@ -1534,48 +1289,6 @@ void CWallet::UpdateNullifierNoteMapWithTx(const CWalletTx& wtx)
}
}
/**
* Update mapSproutNullifiersToNotes, computing the nullifier from a cached witness if necessary.
*/
void CWallet::UpdateSproutNullifierNoteMapWithTx(CWalletTx& wtx) {
LOCK(cs_wallet);
ZCNoteDecryption dec;
for (mapSproutNoteData_t::value_type& item : wtx.mapSproutNoteData) {
SproutNoteData nd = item.second;
if (nd.witnesses.empty()) {
// If there are no witnesses, erase the nullifier and associated mapping.
if (nd.nullifier) {
mapSproutNullifiersToNotes.erase(nd.nullifier.get());
}
nd.nullifier = boost::none;
}
else {
if (GetNoteDecryptor(nd.address, dec)) {
auto i = item.first.js;
auto hSig = wtx.vjoinsplit[i].h_sig(
*pzcashParams, wtx.joinSplitPubKey);
auto optNullifier = GetSproutNoteNullifier(
wtx.vjoinsplit[i],
item.second.address,
dec,
hSig,
item.first.n);
if (!optNullifier) {
// This should not happen. If it does, maybe the position has been corrupted or miscalculated?
assert(false);
}
uint256 nullifier = optNullifier.get();
mapSproutNullifiersToNotes[nullifier] = item.first;
item.second.nullifier = nullifier;
}
}
}
}
/**
* Update mapSaplingNullifiersToNotes, computing the nullifier from a cached witness if necessary.
*/
@ -1633,7 +1346,6 @@ void CWallet::UpdateNullifierNoteMapForBlock(const CBlock *pblock) {
auto hash = tx.GetHash();
bool txIsOurs = mapWallet.count(hash);
if (txIsOurs) {
UpdateSproutNullifierNoteMapWithTx(mapWallet[hash]);
UpdateSaplingNullifierNoteMapWithTx(mapWallet[hash]);
}
}
@ -1673,6 +1385,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
int64_t latestEntry = 0;
{
// Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
// TODO: this is 2 blocktimes, which will become 150?
int64_t latestTolerated = latestNow + 300;
std::list<CAccountingEntry> acentries;
TxItems txOrdered = OrderedTxItems(acentries);
@ -1766,21 +1479,6 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
bool CWallet::UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx)
{
bool unchangedSproutFlag = (wtxIn.mapSproutNoteData.empty() || wtxIn.mapSproutNoteData == wtx.mapSproutNoteData);
if (!unchangedSproutFlag) {
auto tmp = wtxIn.mapSproutNoteData;
// Ensure we keep any cached witnesses we may already have
for (const std::pair <JSOutPoint, SproutNoteData> nd : wtx.mapSproutNoteData) {
if (tmp.count(nd.first) && nd.second.witnesses.size() > 0) {
tmp.at(nd.first).witnesses.assign(
nd.second.witnesses.cbegin(), nd.second.witnesses.cend());
}
tmp.at(nd.first).witnessHeight = nd.second.witnessHeight;
}
// Now copy over the updated note data
wtx.mapSproutNoteData = tmp;
}
bool unchangedSaplingFlag = (wtxIn.mapSaplingNoteData.empty() || wtxIn.mapSaplingNoteData == wtx.mapSaplingNoteData);
if (!unchangedSaplingFlag) {
auto tmp = wtxIn.mapSaplingNoteData;
@ -1798,7 +1496,7 @@ bool CWallet::UpdatedNoteData(const CWalletTx& wtxIn, CWalletTx& wtx)
wtx.mapSaplingNoteData = tmp;
}
return !unchangedSproutFlag || !unchangedSaplingFlag;
return !unchangedSaplingFlag;
}
/**
@ -1815,7 +1513,6 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
return false;
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false;
auto sproutNoteData = FindMySproutNotes(tx);
auto saplingNoteDataAndAddressesToAdd = FindMySaplingNotes(tx);
auto saplingNoteData = saplingNoteDataAndAddressesToAdd.first;
auto addressesToAdd = saplingNoteDataAndAddressesToAdd.second;
@ -1839,7 +1536,7 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
fprintf(stderr, " %s\n", wladdr.c_str());
}
}
if (fExisted || IsMine(tx) || IsFromMe(tx) || sproutNoteData.size() > 0 || saplingNoteData.size() > 0)
if (fExisted || IsMine(tx) || IsFromMe(tx) || saplingNoteData.size() > 0)
{
// wallet filter for notary nodes. Enables by setting -whitelistaddress= as startup param or in conf file (works same as -addnode byut with R-address's)
if ( !tx.IsCoinBase() && !vWhiteListAddress.empty() && !NotaryAddress.empty() )
@ -1871,10 +1568,6 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
CWalletTx wtx(this,tx);
if (sproutNoteData.size() > 0) {
wtx.SetSproutNoteData(sproutNoteData);
}
if (saplingNoteData.size() > 0) {
wtx.SetSaplingNoteData(saplingNoteData);
}
@ -1946,82 +1639,6 @@ void CWallet::RescanWallet()
}
/**
* Returns a nullifier if the SpendingKey is available
* Throws std::runtime_error if the decryptor doesn't match this note
*/
boost::optional<uint256> CWallet::GetSproutNoteNullifier(const JSDescription &jsdesc,
const libzcash::SproutPaymentAddress &address,
const ZCNoteDecryption &dec,
const uint256 &hSig,
uint8_t n) const
{
boost::optional<uint256> ret;
auto note_pt = libzcash::SproutNotePlaintext::decrypt(
dec,
jsdesc.ciphertexts[n],
jsdesc.ephemeralKey,
hSig,
(unsigned char) n);
auto note = note_pt.note(address);
// SpendingKeys are only available if:
// - We have them (this isn't a viewing key)
// - The wallet is unlocked
libzcash::SproutSpendingKey key;
if (GetSproutSpendingKey(address, key)) {
ret = note.nullifier(key);
}
return ret;
}
/**
* Finds all output notes in the given transaction that have been sent to
* PaymentAddresses in this wallet.
*
* It should never be necessary to call this method with a CWalletTx, because
* the result of FindMySproutNotes (for the addresses available at the time) will
* already have been cached in CWalletTx.mapSproutNoteData.
*/
mapSproutNoteData_t CWallet::FindMySproutNotes(const CTransaction &tx) const
{
LOCK(cs_SpendingKeyStore);
uint256 hash = tx.GetHash();
mapSproutNoteData_t noteData;
for (size_t i = 0; i < tx.vjoinsplit.size(); i++) {
auto hSig = tx.vjoinsplit[i].h_sig(*pzcashParams, tx.joinSplitPubKey);
for (uint8_t j = 0; j < tx.vjoinsplit[i].ciphertexts.size(); j++) {
for (const NoteDecryptorMap::value_type& item : mapNoteDecryptors) {
try {
auto address = item.first;
JSOutPoint jsoutpt {hash, i, j};
auto nullifier = GetSproutNoteNullifier(
tx.vjoinsplit[i],
address,
item.second,
hSig, j);
if (nullifier) {
SproutNoteData nd {address, *nullifier};
noteData.insert(std::make_pair(jsoutpt, nd));
} else {
SproutNoteData nd {address};
noteData.insert(std::make_pair(jsoutpt, nd));
}
break;
} catch (const note_decryption_failed &err) {
// Couldn't decrypt with this decryptor
} catch (const std::exception &exc) {
// Unexpected failure
LogPrintf("FindMySproutNotes(): Unexpected error while testing decrypt:\n");
LogPrintf("%s\n", exc.what());
}
}
}
}
return noteData;
}
/**
* Finds all output notes in the given transaction that have been sent to
* SaplingPaymentAddresses in this wallet.
@ -2081,18 +1698,6 @@ std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> CWallet::FindMySap
return std::make_pair(noteData, viewingKeysToAdd);
}
bool CWallet::IsSproutNullifierFromMe(const uint256& nullifier) const
{
{
LOCK(cs_wallet);
if (mapSproutNullifiersToNotes.count(nullifier) &&
mapWallet.count(mapSproutNullifiersToNotes.at(nullifier).hash)) {
return true;
}
}
return false;
}
bool CWallet::IsSaplingNullifierFromMe(const uint256& nullifier) const
{
{
@ -2105,33 +1710,6 @@ bool CWallet::IsSaplingNullifierFromMe(const uint256& nullifier) const
return false;
}
void CWallet::GetSproutNoteWitnesses(std::vector<JSOutPoint> notes,
std::vector<boost::optional<SproutWitness>>& witnesses,
uint256 &final_anchor)
{
LOCK(cs_wallet);
witnesses.resize(notes.size());
boost::optional<uint256> rt;
int i = 0;
for (JSOutPoint note : notes) {
if (mapWallet.count(note.hash) &&
mapWallet[note.hash].mapSproutNoteData.count(note) &&
mapWallet[note.hash].mapSproutNoteData[note].witnesses.size() > 0) {
witnesses[i] = mapWallet[note.hash].mapSproutNoteData[note].witnesses.front();
if (!rt) {
rt = witnesses[i]->root();
} else {
assert(*rt == witnesses[i]->root());
}
}
i++;
}
// All returned witnesses have the same anchor
if (rt) {
final_anchor = *rt;
}
}
void CWallet::GetSaplingNoteWitnesses(std::vector<SaplingOutPoint> notes,
std::vector<boost::optional<SaplingWitness>>& witnesses,
uint256 &final_anchor)
@ -2141,14 +1719,26 @@ void CWallet::GetSaplingNoteWitnesses(std::vector<SaplingOutPoint> notes,
boost::optional<uint256> rt;
int i = 0;
for (SaplingOutPoint note : notes) {
if (mapWallet.count(note.hash) &&
mapWallet[note.hash].mapSaplingNoteData.count(note) &&
mapWallet[note.hash].mapSaplingNoteData[note].witnesses.size() > 0) {
witnesses[i] = mapWallet[note.hash].mapSaplingNoteData[note].witnesses.front();
//fprintf(stderr,"%s: i=%d\n", __func__,i);
auto noteData = mapWallet[note.hash].mapSaplingNoteData;
auto nWitnesses = noteData[note].witnesses.size();
if (mapWallet.count(note.hash) && noteData.count(note) && nWitnesses > 0) {
fprintf(stderr,"%s: Found %lu witnesses for note %s\n", __func__, nWitnesses, note.hash.ToString().c_str() );
witnesses[i] = noteData[note].witnesses.front();
if (!rt) {
//fprintf(stderr,"%s: Setting witness root\n",__func__);
rt = witnesses[i]->root();
} else {
assert(*rt == witnesses[i]->root());
if(*rt == witnesses[i]->root()) {
//fprintf(stderr,"%s: rt=%s\n",__func__,rt.GetHash().ToString().c_str());
//fprintf(stderr,"%s: witnesses[%d]->root()=%s\n",__func__,i,witnesses[i]->root().GetHash().ToString().c_str());
// Something is fucky
std::string err = string("CWallet::GetSaplingNoteWitnesses: Invalid witness root! rt=") + rt.get().ToString();
err += string("\n!= witness[i]->root()=") + witnesses[i]->root().ToString();
//throw std::logic_error(err);
fprintf(stderr,"%s: IGNORING %s\n", __func__,err.c_str());
}
}
}
i++;
@ -2156,6 +1746,7 @@ void CWallet::GetSaplingNoteWitnesses(std::vector<SaplingOutPoint> notes,
// All returned witnesses have the same anchor
if (rt) {
final_anchor = *rt;
//fprintf(stderr,"%s: final_anchor=%s\n", __func__, rt.get().ToString().c_str() );
}
}
@ -2364,13 +1955,6 @@ bool CWallet::IsFromMe(const CTransaction& tx) const
if (GetDebit(tx, ISMINE_ALL) > 0) {
return true;
}
for (const JSDescription& jsdesc : tx.vjoinsplit) {
for (const uint256& nullifier : jsdesc.nullifiers) {
if (IsSproutNullifierFromMe(nullifier)) {
return true;
}
}
}
for (const SpendDescription &spend : tx.vShieldedSpend) {
if (IsSaplingNullifierFromMe(spend.nullifier)) {
return true;
@ -2506,22 +2090,6 @@ bool CWallet::LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigne
return CCryptoKeyStore::SetCryptedHDSeed(seedFp, seed);
}
void CWalletTx::SetSproutNoteData(mapSproutNoteData_t &noteData)
{
mapSproutNoteData.clear();
for (const std::pair<JSOutPoint, SproutNoteData> nd : noteData) {
if (nd.first.js < vjoinsplit.size() &&
nd.first.n < vjoinsplit[nd.first.js].ciphertexts.size()) {
// Store the address and nullifier for the Note
mapSproutNoteData[nd.first] = nd.second;
} else {
// If FindMySproutNotes() was used to obtain noteData,
// this should never happen
throw std::logic_error("CWalletTx::SetSproutNoteData(): Invalid note");
}
}
}
void CWalletTx::SetSaplingNoteData(mapSaplingNoteData_t &noteData)
{
mapSaplingNoteData.clear();
@ -2665,36 +2233,6 @@ void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
if (isFromMyTaddr) {
CAmount myVpubOld = 0;
CAmount myVpubNew = 0;
for (const JSDescription& js : vjoinsplit) {
bool fMyJSDesc = false;
// Check input side
for (const uint256& nullifier : js.nullifiers) {
if (pwallet->IsSproutNullifierFromMe(nullifier)) {
fMyJSDesc = true;
break;
}
}
// Check output side
if (!fMyJSDesc) {
for (const std::pair<JSOutPoint, SproutNoteData> nd : this->mapSproutNoteData) {
if (nd.first.js < vjoinsplit.size() && nd.first.n < vjoinsplit[nd.first.js].ciphertexts.size()) {
fMyJSDesc = true;
break;
}
}
}
if (fMyJSDesc) {
myVpubOld += js.vpub_old;
myVpubNew += js.vpub_new;
}
if (!MoneyRange(js.vpub_old) || !MoneyRange(js.vpub_new) || !MoneyRange(myVpubOld) || !MoneyRange(myVpubNew)) {
throw std::runtime_error("CWalletTx::GetAmounts: value out of range");
}
}
// Create an output for the value taken from or added to the transparent value pool by JoinSplits
if (myVpubOld > myVpubNew) {
@ -2812,57 +2350,6 @@ void CWallet::WitnessNoteCommitment(std::vector<uint256> commitments,
std::vector<boost::optional<SproutWitness>>& witnesses,
uint256 &final_anchor)
{
witnesses.resize(commitments.size());
CBlockIndex* pindex = chainActive.Genesis();
SproutMerkleTree tree;
while (pindex) {
CBlock block;
ReadBlockFromDisk(block, pindex,1);
BOOST_FOREACH(const CTransaction& tx, block.vtx)
{
BOOST_FOREACH(const JSDescription& jsdesc, tx.vjoinsplit)
{
BOOST_FOREACH(const uint256 &note_commitment, jsdesc.commitments)
{
tree.append(note_commitment);
BOOST_FOREACH(boost::optional<SproutWitness>& wit, witnesses) {
if (wit) {
wit->append(note_commitment);
}
}
size_t i = 0;
BOOST_FOREACH(uint256& commitment, commitments) {
if (note_commitment == commitment) {
witnesses.at(i) = tree.witness();
}
i++;
}
}
}
}
uint256 current_anchor = tree.root();
// Consistency check: we should be able to find the current tree
// in our CCoins view.
SproutMerkleTree dummy_tree;
assert(pcoinsTip->GetSproutAnchorAt(current_anchor, dummy_tree));
pindex = chainActive.Next(pindex);
}
// TODO: #93; Select a root via some heuristic.
final_anchor = tree.root();
BOOST_FOREACH(boost::optional<SproutWitness>& wit, witnesses) {
if (wit) {
assert(final_anchor == wit->root());
}
}
}
/**
@ -3066,38 +2553,11 @@ void CWallet::DeleteWalletTransactions(const CBlockIndex* pindex) {
continue;
}
//Check for unspent inputs or spend less than N Blocks ago. (Sprout)
for (auto & pair : pwtx->mapSproutNoteData) {
SproutNoteData nd = pair.second;
if (!nd.nullifier || pwalletMain->GetSproutSpendDepth(*nd.nullifier) <= fDeleteTransactionsAfterNBlocks) {
LogPrint("deletetx","DeleteTx - Unspent sprout input tx %s\n", pwtx->GetHash().ToString());
deleteTx = false;
continue;
}
}
if (!deleteTx) {
txSaveCount++;
continue;
}
//Check for outputs that no longer have parents in the wallet. Exclude parents that are in the same transaction. (Sprout)
for (int i = 0; i < pwtx->vjoinsplit.size(); i++) {
const JSDescription& jsdesc = pwtx->vjoinsplit[i];
for (const uint256 &nullifier : jsdesc.nullifiers) {
// JSOutPoint op = pwalletMain->mapSproutNullifiersToNotes[nullifier];
if (pwalletMain->IsSproutNullifierFromMe(nullifier)) {
const uint256& parentHash = pwalletMain->mapSproutNullifiersToNotes[nullifier].hash;
const CWalletTx* parent = pwalletMain->GetWalletTx(parentHash);
if (parent != NULL && parentHash != wtxid) {
LogPrint("deletetx","DeleteTx - Parent of sprout tx %s found\n", pwtx->GetHash().ToString());
deleteTx = false;
continue;
}
}
}
}
if (!deleteTx) {
txSaveCount++;
continue;
@ -3202,11 +2662,11 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
}
}
SproutMerkleTree sproutTree;
//SproutMerkleTree sproutTree;
SaplingMerkleTree saplingTree;
// This should never fail: we should always be able to get the tree
// state on the path to the tip of our chain
assert(pcoinsTip->GetSproutAnchorAt(pindex->hashSproutAnchor, sproutTree));
//assert(pcoinsTip->GetSproutAnchorAt(pindex->hashSproutAnchor, sproutTree));
if (pindex->pprev) {
if (NetworkUpgradeActive(pindex->pprev->GetHeight(), Params().GetConsensus(), Consensus::UPGRADE_SAPLING)) {
assert(pcoinsTip->GetSaplingAnchorAt(pindex->pprev->hashFinalSaplingRoot, saplingTree));
@ -5021,38 +4481,6 @@ void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts)
// Note Locking Operations
void CWallet::LockNote(const JSOutPoint& output)
{
AssertLockHeld(cs_wallet); // setLockedSproutNotes
setLockedSproutNotes.insert(output);
}
void CWallet::UnlockNote(const JSOutPoint& output)
{
AssertLockHeld(cs_wallet); // setLockedSproutNotes
setLockedSproutNotes.erase(output);
}
void CWallet::UnlockAllSproutNotes()
{
AssertLockHeld(cs_wallet); // setLockedSproutNotes
setLockedSproutNotes.clear();
}
bool CWallet::IsLockedNote(const JSOutPoint& outpt) const
{
AssertLockHeld(cs_wallet); // setLockedSproutNotes
return (setLockedSproutNotes.count(outpt) > 0);
}
std::vector<JSOutPoint> CWallet::ListLockedSproutNotes()
{
AssertLockHeld(cs_wallet); // setLockedSproutNotes
std::vector<JSOutPoint> vOutpts(setLockedSproutNotes.begin(), setLockedSproutNotes.end());
return vOutpts;
}
void CWallet::LockNote(const SaplingOutPoint& output)
{
AssertLockHeld(cs_wallet);
@ -5318,7 +4746,6 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
* These notes are decrypted and added to the output parameter vector, outEntries.
*/
void CWallet::GetFilteredNotes(
std::vector<CSproutNotePlaintextEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
std::string address,
int minDepth,
@ -5331,7 +4758,7 @@ void CWallet::GetFilteredNotes(
filterAddresses.insert(DecodePaymentAddress(address));
}
GetFilteredNotes(sproutEntries, saplingEntries, filterAddresses, minDepth, INT_MAX, ignoreSpent, requireSpendingKey);
GetFilteredNotes(saplingEntries, filterAddresses, minDepth, INT_MAX, ignoreSpent, requireSpendingKey);
}
/**
@ -5340,7 +4767,6 @@ void CWallet::GetFilteredNotes(
* These notes are decrypted and added to the output parameter vector, outEntries.
*/
void CWallet::GetFilteredNotes(
std::vector<CSproutNotePlaintextEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
std::set<PaymentAddress>& filterAddresses,
int minDepth,
@ -5429,11 +4855,6 @@ void CWallet::GetFilteredNotes(
// Shielded key and address generalizations
//
bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SproutPaymentAddress &zaddr) const
{
return m_wallet->HaveSproutViewingKey(zaddr);
}
bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingIncomingViewingKey ivk;
@ -5445,11 +4866,6 @@ bool IncomingViewingKeyBelongsToWallet::operator()(const libzcash::InvalidEncodi
return false;
}
bool PaymentAddressBelongsToWallet::operator()(const libzcash::SproutPaymentAddress &zaddr) const
{
return m_wallet->HaveSproutSpendingKey(zaddr) || m_wallet->HaveSproutViewingKey(zaddr);
}
bool PaymentAddressBelongsToWallet::operator()(const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingIncomingViewingKey ivk;
@ -5465,11 +4881,6 @@ bool PaymentAddressBelongsToWallet::operator()(const libzcash::InvalidEncoding&
return false;
}
bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::SproutPaymentAddress &zaddr) const
{
return m_wallet->HaveSproutSpendingKey(zaddr);
}
bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingIncomingViewingKey ivk;
@ -5485,17 +4896,6 @@ bool HaveSpendingKeyForPaymentAddress::operator()(const libzcash::InvalidEncodin
return false;
}
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
const libzcash::SproutPaymentAddress &zaddr) const
{
libzcash::SproutSpendingKey k;
if (m_wallet->GetSproutSpendingKey(zaddr, k)) {
return libzcash::SpendingKey(k);
} else {
return boost::none;
}
}
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
const libzcash::SaplingPaymentAddress &zaddr) const
{
@ -5514,20 +4914,6 @@ boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator
return libzcash::SpendingKey();
}
SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKey &sk) const {
auto addr = sk.address();
if (log){
LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr));
}
if (m_wallet->HaveSproutSpendingKey(addr)) {
return KeyAlreadyExists;
} else if (m_wallet-> AddSproutZKey(sk)) {
m_wallet->mapSproutZKeyMetadata[addr].nCreateTime = nTime;
return KeyAdded;
} else {
return KeyNotAdded;
}
}
SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const {
auto fvk = sk.expsk.full_viewing_key();

116
src/wallet/wallet.h

@ -236,9 +236,10 @@ public:
std::string ToString() const;
};
// NOTE: wallet.dat format depends on this data structure :(
class SproutNoteData
{
public:
{
public:
libzcash::SproutPaymentAddress address;
/**
@ -264,7 +265,7 @@ public:
/**
* Block height corresponding to the most current witness.
*
* When we first create a SproutNoteData in CWallet::FindMySproutNotes, this is set to
* When we first create a SaplingNoteData in CWallet::FindMySaplingNotes, this is set to
* -1 as a placeholder. The next time CWallet::ChainTip is called, we can
* determine what height the witness cache for this note is valid for (even
* if no witnesses were cached), and so can set the correct value in
@ -272,15 +273,6 @@ public:
*/
int witnessHeight;
//In Memory Only
bool witnessRootValidated;
SproutNoteData() : address(), nullifier(), witnessHeight {-1}, witnessRootValidated {false} { }
SproutNoteData(libzcash::SproutPaymentAddress a) :
address {a}, nullifier(), witnessHeight {-1}, witnessRootValidated {false} { }
SproutNoteData(libzcash::SproutPaymentAddress a, uint256 n) :
address {a}, nullifier {n}, witnessHeight {-1}, witnessRootValidated {false} { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
@ -291,27 +283,22 @@ public:
READWRITE(witnessHeight);
}
friend bool operator<(const SproutNoteData& a, const SproutNoteData& b) {
return (a.address < b.address ||
(a.address == b.address && a.nullifier < b.nullifier));
}
friend bool operator==(const SproutNoteData& a, const SproutNoteData& b) {
return (a.address == b.address && a.nullifier == b.nullifier);
}
friend bool operator!=(const SproutNoteData& a, const SproutNoteData& b) {
return !(a == b);
}
};
class SaplingNoteData
{
public:
/**
* We initialize the height to -1 for the same reason as we do in SproutNoteData.
* See the comment in that class for a full description.
* Block height corresponding to the most current witness.
*
* When we first create a SaplingNoteData in CWallet::FindMySaplingNotes, this is set to
* -1 as a placeholder. The next time CWallet::ChainTip is called, we can
* determine what height the witness cache for this note is valid for (even
* if no witnesses were cached), and so can set the correct value in
* CWallet::BuildWitnessCache and CWallet::DecrementNoteWitnesses.
*/
SaplingNoteData() : witnessHeight {-1}, nullifier() { }
SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk) : ivk {ivk}, witnessHeight {-1}, nullifier() { }
SaplingNoteData(libzcash::SaplingIncomingViewingKey ivk, uint256 n) : ivk {ivk}, witnessHeight {-1}, nullifier(n) { }
@ -347,18 +334,11 @@ public:
}
};
// NOTE: this sprout structure is serialized into wallet.dat, removing it would change wallet.dat format on disk :(
typedef std::map<JSOutPoint, SproutNoteData> mapSproutNoteData_t;
typedef std::map<SaplingOutPoint, SaplingNoteData> mapSaplingNoteData_t;
/** Decrypted note, its location in a transaction, and number of confirmations. */
struct CSproutNotePlaintextEntry
{
JSOutPoint jsop;
libzcash::SproutPaymentAddress address;
libzcash::SproutNotePlaintext plaintext;
int confirmations;
};
/** Sapling note, its location in a transaction, and number of confirmations. */
struct SaplingNoteEntry
{
@ -589,11 +569,8 @@ public:
MarkDirty();
}
void SetSproutNoteData(mapSproutNoteData_t &noteData);
void SetSaplingNoteData(mapSaplingNoteData_t &noteData);
std::pair<libzcash::SproutNotePlaintext, libzcash::SproutPaymentAddress> DecryptSproutNote(
JSOutPoint jsop) const;
boost::optional<std::pair<
libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> DecryptSaplingNote(SaplingOutPoint op) const;
@ -801,14 +778,12 @@ private:
* detect and report conflicts (double-spends).
*/
typedef TxSpendMap<uint256> TxNullifiers;
TxNullifiers mapTxSproutNullifiers;
TxNullifiers mapTxSaplingNullifiers;
std::vector<CTransaction> pendingSaplingConsolidationTxs;
AsyncRPCOperationId saplingConsolidationOperationId;
void AddToTransparentSpends(const COutPoint& outpoint, const uint256& wtxid);
void AddToSproutSpends(const uint256& nullifier, const uint256& wtxid);
void AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid);
void AddToSpends(const uint256& wtxid);
@ -829,7 +804,6 @@ public:
protected:
int SproutWitnessMinimumHeight(const uint256& nullifier, int nWitnessHeight, int nMinimumHeight);
int SaplingWitnessMinimumHeight(const uint256& nullifier, int nWitnessHeight, int nMinimumHeight);
/**
@ -853,11 +827,11 @@ protected:
try {
for (std::pair<const uint256, CWalletTx>& wtxItem : mapWallet) {
auto wtx = wtxItem.second;
// We skip transactions for which mapSproutNoteData and mapSaplingNoteData
// are empty. This covers transactions that have no Sprout or Sapling data
// We skip transactions for which mapSaplingNoteData
// is empty. This covers transactions that have no Sapling data
// (i.e. are purely transparent), as well as shielding and unshielding
// transactions in which we only have transparent addresses involved.
if (!(wtx.mapSproutNoteData.empty() && wtx.mapSaplingNoteData.empty())) {
if (!(wtx.mapSaplingNoteData.empty())) {
if (!walletdb.WriteTx(wtxItem.first, wtx)) {
LogPrintf("SetBestChain(): Failed to write CWalletTx, aborting atomic write\n");
walletdb.TxnAbort();
@ -915,7 +889,6 @@ public:
std::set<int64_t> setKeyPool;
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
@ -1033,8 +1006,6 @@ public:
bool IsSpent(const uint256& hash, unsigned int n) const;
unsigned int GetSpendDepth(const uint256& hash, unsigned int n) const;
bool IsSproutSpent(const uint256& nullifier) const;
unsigned int GetSproutSpendDepth(const uint256& nullifier) const;
bool IsSaplingSpent(const uint256& nullifier) const;
unsigned int GetSaplingSpendDepth(const uint256& nullifier) const;
@ -1047,8 +1018,6 @@ public:
bool IsLockedNote(const JSOutPoint& outpt) const;
void LockNote(const JSOutPoint& output);
void UnlockNote(const JSOutPoint& output);
void UnlockAllSproutNotes();
std::vector<JSOutPoint> ListLockedSproutNotes();
bool IsLockedNote(const SaplingOutPoint& output) const;
void LockNote(const SaplingOutPoint& output);
@ -1098,31 +1067,6 @@ public:
void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const;
/**
* Sprout ZKeys
*/
//! Generates a new Sprout zaddr
libzcash::SproutPaymentAddress GenerateNewSproutZKey();
//! Adds spending key to the store, and saves it to disk
bool AddSproutZKey(const libzcash::SproutSpendingKey &key);
//! Adds spending key to the store, without saving it to disk (used by LoadWallet)
bool LoadZKey(const libzcash::SproutSpendingKey &key);
//! Load spending key metadata (used by LoadWallet)
bool LoadZKeyMetadata(const libzcash::SproutPaymentAddress &addr, const CKeyMetadata &meta);
//! Adds an encrypted spending key to the store, without saving it to disk (used by LoadWallet)
bool LoadCryptedZKey(const libzcash::SproutPaymentAddress &addr, const libzcash::ReceivingKey &rk, const std::vector<unsigned char> &vchCryptedSecret);
//! Adds an encrypted spending key to the store, and saves it to disk (virtual method, declared in crypter.h)
bool AddCryptedSproutSpendingKey(
const libzcash::SproutPaymentAddress &address,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char> &vchCryptedSecret);
//! Adds a Sprout viewing key to the store, and saves it to disk.
bool AddSproutViewingKey(const libzcash::SproutViewingKey &vk);
bool RemoveSproutViewingKey(const libzcash::SproutViewingKey &vk);
//! Adds a Sprout viewing key to the store, without saving it to disk (used by LoadWallet)
bool LoadSproutViewingKey(const libzcash::SproutViewingKey &dest);
/**
* Sapling ZKeys
*/
@ -1171,7 +1115,6 @@ public:
void MarkDirty();
bool UpdateNullifierNoteMap();
void UpdateNullifierNoteMapWithTx(const CWalletTx& wtx);
void UpdateSproutNullifierNoteMapWithTx(CWalletTx& wtx);
void UpdateSaplingNullifierNoteMapWithTx(CWalletTx& wtx);
void UpdateNullifierNoteMapForBlock(const CBlock* pblock);
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb);
@ -1219,21 +1162,9 @@ public:
std::set<CTxDestination> GetAccountAddresses(const std::string& strAccount) const;
boost::optional<uint256> GetSproutNoteNullifier(
const JSDescription& jsdesc,
const libzcash::SproutPaymentAddress& address,
const ZCNoteDecryption& dec,
const uint256& hSig,
uint8_t n) const;
mapSproutNoteData_t FindMySproutNotes(const CTransaction& tx) const;
std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> FindMySaplingNotes(const CTransaction& tx) const;
bool IsSproutNullifierFromMe(const uint256& nullifier) const;
bool IsSaplingNullifierFromMe(const uint256& nullifier) const;
void GetSproutNoteWitnesses(
std::vector<JSOutPoint> notes,
std::vector<boost::optional<SproutWitness>>& witnesses,
uint256 &final_anchor);
void GetSaplingNoteWitnesses(
std::vector<SaplingOutPoint> notes,
std::vector<boost::optional<SaplingWitness>>& witnesses,
@ -1367,8 +1298,7 @@ public:
bool LoadCryptedHDSeed(const uint256& seedFp, const std::vector<unsigned char>& seed);
/* Find notes filtered by payment address, min depth, ability to spend */
void GetFilteredNotes(std::vector<CSproutNotePlaintextEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
void GetFilteredNotes(std::vector<SaplingNoteEntry>& saplingEntries,
std::string address,
int minDepth=1,
bool ignoreSpent=true,
@ -1376,8 +1306,7 @@ public:
/* Find notes filtered by payment addresses, min depth, max depth, if they are spent,
if a spending key is required, and if they are locked */
void GetFilteredNotes(std::vector<CSproutNotePlaintextEntry>& sproutEntries,
std::vector<SaplingNoteEntry>& saplingEntries,
void GetFilteredNotes(std::vector<SaplingNoteEntry>& saplingEntries,
std::set<libzcash::PaymentAddress>& filterAddresses,
int minDepth=1,
int maxDepth=INT_MAX,
@ -1456,7 +1385,6 @@ private:
public:
PaymentAddressBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {}
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};
@ -1469,7 +1397,6 @@ private:
public:
IncomingViewingKeyBelongsToWallet(CWallet *wallet) : m_wallet(wallet) {}
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};
@ -1481,7 +1408,6 @@ private:
public:
HaveSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
bool operator()(const libzcash::SproutPaymentAddress &zaddr) const;
bool operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
bool operator()(const libzcash::InvalidEncoding& no) const;
};
@ -1493,7 +1419,6 @@ private:
public:
GetSpendingKeyForPaymentAddress(CWallet *wallet) : m_wallet(wallet) {}
boost::optional<libzcash::SpendingKey> operator()(const libzcash::SproutPaymentAddress &zaddr) const;
boost::optional<libzcash::SpendingKey> operator()(const libzcash::SaplingPaymentAddress &zaddr) const;
boost::optional<libzcash::SpendingKey> operator()(const libzcash::InvalidEncoding& no) const;
};
@ -1565,7 +1490,6 @@ public:
) : m_wallet(wallet), params(params), nTime(_nTime), hdKeypath(_hdKeypath), seedFpStr(_seedFp), log(_log) {}
SpendingKeyAddResult operator()(const libzcash::SproutSpendingKey &sk) const;
SpendingKeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const;
SpendingKeyAddResult operator()(const libzcash::InvalidEncoding& no) const;
};

68
src/wallet/walletdb.cpp

@ -1,6 +1,6 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2019 The Hush 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.
@ -124,26 +124,6 @@ bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
return true;
}
bool CWalletDB::WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr,
const libzcash::ReceivingKey &rk,
const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta)
{
const bool fEraseUnencryptedKey = true;
nWalletDBUpdated++;
if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta))
return false;
if (!Write(std::make_pair(std::string("czkey"), addr), std::make_pair(rk, vchCryptedSecret), false))
return false;
if (fEraseUnencryptedKey)
{
Erase(std::make_pair(std::string("zkey"), addr));
}
return true;
}
bool CWalletDB::WriteCryptedSaplingZKey(
const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char>& vchCryptedSecret,
@ -172,16 +152,6 @@ bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
}
bool CWalletDB::WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta)
{
nWalletDBUpdated++;
if (!Write(std::make_pair(std::string("zkeymeta"), addr), keyMeta))
return false;
// pair is: tuple_key("zkey", paymentaddress) --> secretkey
return Write(std::make_pair(std::string("zkey"), addr), key, false);
}
bool CWalletDB::WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingExtendedSpendingKey &key,
const CKeyMetadata &keyMeta)
@ -203,18 +173,6 @@ bool CWalletDB::WriteSaplingPaymentAddress(
return Write(std::make_pair(std::string("sapzaddr"), addr), ivk, false);
}
bool CWalletDB::WriteSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
nWalletDBUpdated++;
return Write(std::make_pair(std::string("vkey"), vk), '1');
}
bool CWalletDB::EraseSproutViewingKey(const libzcash::SproutViewingKey &vk)
{
nWalletDBUpdated++;
return Erase(std::make_pair(std::string("vkey"), vk));
}
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
{
nWalletDBUpdated++;
@ -552,6 +510,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "vkey")
{
/*
libzcash::SproutViewingKey vk;
ssKey >> vk;
char fYes;
@ -559,24 +518,27 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
if (fYes == '1')
pwallet->LoadSproutViewingKey(vk);
// Viewing keys have no birthday information for now,
// so set the wallet birthday to the beginning of time.
Viewing keys have no birthday information for now,
so set the wallet birthday to the beginning of time.
pwallet->nTimeFirstKey = 1;
*/
}
else if (strType == "zkey")
{
/*
libzcash::SproutPaymentAddress addr;
ssKey >> addr;
libzcash::SproutSpendingKey key;
ssValue >> key;
if (!pwallet->LoadZKey(key))
{
strErr = "Error reading wallet database: LoadZKey failed";
return false;
}
//if (!pwallet->LoadZKey(key))
//{
// strErr = "Error reading wallet database: LoadZKey failed";
// return false;
//}
wss.nZKeys++;
*/
}
else if (strType == "sapzkey")
{
@ -691,6 +653,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "czkey")
{
/*
libzcash::SproutPaymentAddress addr;
ssKey >> addr;
// Deserialization of a pair is just one item after another
@ -707,6 +670,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
return false;
}
wss.fIsEncrypted = true;
*/
}
else if (strType == "csapzkey")
{
@ -742,13 +706,15 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "zkeymeta")
{
/*
libzcash::SproutPaymentAddress addr;
ssKey >> addr;
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nZKeyMeta++;
*/
pwallet->LoadZKeyMetadata(addr, keyMeta);
// pwallet->LoadZKeyMetadata(addr, keyMeta);
// ignore earliest key creation time as taddr will exist before any zaddr
}

11
src/wallet/walletdb.h

@ -1,7 +1,8 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2013 The Bitcoin Core developers
// Copyright (c) 2009-2013 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// file COPYING or https://www.opensource.org/licenses/mit-license.php
/******************************************************************************
* Copyright © 2014-2019 The SuperNET Developers. *
@ -199,23 +200,15 @@ public:
bool WriteHDChain(const CHDChain& chain);
/// Write spending key to wallet database, where key is payment address and value is spending key.
bool WriteZKey(const libzcash::SproutPaymentAddress& addr, const libzcash::SproutSpendingKey& key, const CKeyMetadata &keyMeta);
bool WriteSaplingZKey(const libzcash::SaplingIncomingViewingKey &ivk,
const libzcash::SaplingExtendedSpendingKey &key,
const CKeyMetadata &keyMeta);
bool WriteSaplingPaymentAddress(const libzcash::SaplingPaymentAddress &addr,
const libzcash::SaplingIncomingViewingKey &ivk);
bool WriteCryptedZKey(const libzcash::SproutPaymentAddress & addr,
const libzcash::ReceivingKey & rk,
const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta);
bool WriteCryptedSaplingZKey(const libzcash::SaplingExtendedFullViewingKey &extfvk,
const std::vector<unsigned char>& vchCryptedSecret,
const CKeyMetadata &keyMeta);
bool WriteSproutViewingKey(const libzcash::SproutViewingKey &vk);
bool EraseSproutViewingKey(const libzcash::SproutViewingKey &vk);
private:
CWalletDB(const CWalletDB&);
void operator=(const CWalletDB&);

38
src/zcash/Address.cpp

@ -1,3 +1,7 @@
// Copyright (c) 2019-2020 The Hush developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php
#include "Address.hpp"
#include "NoteEncryption.hpp"
#include "hash.h"
@ -13,36 +17,6 @@ const uint32_t SAPLING_BRANCH_ID = 0x76b809bb;
namespace libzcash {
uint256 SproutPaymentAddress::GetHash() const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << *this;
return Hash(ss.begin(), ss.end());
}
uint256 ReceivingKey::pk_enc() const {
return ZCNoteEncryption::generate_pubkey(*this);
}
SproutPaymentAddress SproutViewingKey::address() const {
return SproutPaymentAddress(a_pk, sk_enc.pk_enc());
}
ReceivingKey SproutSpendingKey::receiving_key() const {
return ReceivingKey(ZCNoteEncryption::generate_privkey(*this));
}
SproutViewingKey SproutSpendingKey::viewing_key() const {
return SproutViewingKey(PRF_addr_a_pk(*this), receiving_key());
}
SproutSpendingKey SproutSpendingKey::random() {
return SproutSpendingKey(random_uint252());
}
SproutPaymentAddress SproutSpendingKey::address() const {
return viewing_key().address();
}
//! Sapling
uint256 SaplingPaymentAddress::GetHash() const {
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
@ -119,10 +93,6 @@ class IsValidAddressForNetwork : public boost::static_visitor<bool> {
public:
IsValidAddressForNetwork(uint32_t consensusBranchId) : branchId(consensusBranchId) {}
bool operator()(const libzcash::SproutPaymentAddress &addr) const {
return true;
}
bool operator()(const libzcash::InvalidEncoding &addr) const {
return false;
}

62
src/zcash/Address.hpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#ifndef ZC_ADDRESS_H_
#define ZC_ADDRESS_H_
@ -26,6 +28,16 @@ const size_t SerializedSaplingSpendingKeySize = 32;
typedef std::array<unsigned char, ZC_DIVERSIFIER_SIZE> diversifier_t;
class ReceivingKey : public uint256 {
public:
ReceivingKey() { }
ReceivingKey(uint256 sk_enc) : uint256(sk_enc) { }
uint256 pk_enc() const;
};
// NOTE: wallet.dat format depends on this
class SproutPaymentAddress {
public:
uint256 a_pk;
@ -54,52 +66,6 @@ public:
}
};
class ReceivingKey : public uint256 {
public:
ReceivingKey() { }
ReceivingKey(uint256 sk_enc) : uint256(sk_enc) { }
uint256 pk_enc() const;
};
class SproutViewingKey {
public:
uint256 a_pk;
ReceivingKey sk_enc;
SproutViewingKey() : a_pk(), sk_enc() { }
SproutViewingKey(uint256 a_pk, ReceivingKey sk_enc) : a_pk(a_pk), sk_enc(sk_enc) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(a_pk);
READWRITE(sk_enc);
}
SproutPaymentAddress address() const;
friend inline bool operator==(const SproutViewingKey& a, const SproutViewingKey& b) {
return a.a_pk == b.a_pk && a.sk_enc == b.sk_enc;
}
friend inline bool operator<(const SproutViewingKey& a, const SproutViewingKey& b) {
return (a.a_pk < b.a_pk ||
(a.a_pk == b.a_pk && a.sk_enc < b.sk_enc));
}
};
class SproutSpendingKey : public uint252 {
public:
SproutSpendingKey() : uint252() { }
SproutSpendingKey(uint252 a_sk) : uint252(a_sk) { }
static SproutSpendingKey random();
ReceivingKey receiving_key() const;
SproutViewingKey viewing_key() const;
SproutPaymentAddress address() const;
};
//! Sapling functions.
class SaplingPaymentAddress {
@ -218,8 +184,8 @@ public:
SaplingPaymentAddress default_address() const;
};
typedef boost::variant<InvalidEncoding, SproutPaymentAddress, SaplingPaymentAddress> PaymentAddress;
typedef boost::variant<InvalidEncoding, SproutViewingKey, SaplingIncomingViewingKey> ViewingKey;
typedef boost::variant<InvalidEncoding, SaplingPaymentAddress> PaymentAddress;
typedef boost::variant<InvalidEncoding, SaplingIncomingViewingKey> ViewingKey;
}

3
src/zcash/IncrementalMerkleTree.cpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#include <stdexcept>
#include <boost/foreach.hpp>
@ -1016,6 +1018,7 @@ size_t IncrementalMerkleTree<Depth, Hash>::next_depth(size_t skip) const {
template<size_t Depth, typename Hash>
Hash IncrementalMerkleTree<Depth, Hash>::root(size_t depth,
std::deque<Hash> filler_hashes) const {
//fprintf(stderr,"%s: depth=%d\n",__func__,depth);
PathFiller<Depth, Hash> filler(filler_hashes);
Hash combine_left = left ? *left : filler.next(0);

2
src/zcash/IncrementalMerkleTree.hpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#ifndef ZC_INCREMENTALMERKLETREE_H_
#define ZC_INCREMENTALMERKLETREE_H_

241
src/zcash/JoinSplit.cpp

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
#include "JoinSplit.hpp"
#include "prf.h"
#include "sodium.h"
@ -27,245 +28,5 @@ class JoinSplitCircuit : public JoinSplit<NumInputs, NumOutputs> {
public:
JoinSplitCircuit() {}
~JoinSplitCircuit() {}
SproutProof prove(
const std::array<JSInput, NumInputs>& inputs,
const std::array<JSOutput, NumOutputs>& outputs,
std::array<SproutNote, NumOutputs>& out_notes,
std::array<ZCNoteEncryption::Ciphertext, NumOutputs>& out_ciphertexts,
uint256& out_ephemeralKey,
const uint256& joinSplitPubKey,
uint256& out_randomSeed,
std::array<uint256, NumInputs>& out_macs,
std::array<uint256, NumInputs>& out_nullifiers,
std::array<uint256, NumOutputs>& out_commitments,
uint64_t vpub_old,
uint64_t vpub_new,
const uint256& rt,
bool computeProof,
uint256 *out_esk // Payment disclosure
) {
if (vpub_old > MAX_MONEY) {
throw std::invalid_argument("nonsensical vpub_old value");
}
if (vpub_new > MAX_MONEY) {
throw std::invalid_argument("nonsensical vpub_new value");
}
uint64_t lhs_value = vpub_old;
uint64_t rhs_value = vpub_new;
for (size_t i = 0; i < NumInputs; i++) {
// Sanity checks of input
{
// If note has nonzero value
if (inputs[i].note.value() != 0) {
// The witness root must equal the input root.
if (inputs[i].witness.root() != rt) {
throw std::invalid_argument("joinsplit not anchored to the correct root");
}
// The tree must witness the correct element
if (inputs[i].note.cm() != inputs[i].witness.element()) {
throw std::invalid_argument("witness of wrong element for joinsplit input");
}
}
// Ensure we have the key to this note.
if (inputs[i].note.a_pk != inputs[i].key.address().a_pk) {
throw std::invalid_argument("input note not authorized to spend with given key");
}
// Balance must be sensical
if (inputs[i].note.value() > MAX_MONEY) {
throw std::invalid_argument("nonsensical input note value");
}
lhs_value += inputs[i].note.value();
if (lhs_value > MAX_MONEY) {
throw std::invalid_argument("nonsensical left hand size of joinsplit balance");
}
}
// Compute nullifier of input
out_nullifiers[i] = inputs[i].nullifier();
}
// Sample randomSeed
out_randomSeed = random_uint256();
// Compute h_sig
uint256 h_sig = this->h_sig(out_randomSeed, out_nullifiers, joinSplitPubKey);
// Sample phi
uint252 phi = random_uint252();
// Compute notes for outputs
for (size_t i = 0; i < NumOutputs; i++) {
// Sanity checks of output
{
if (outputs[i].value > MAX_MONEY) {
throw std::invalid_argument("nonsensical output value");
}
rhs_value += outputs[i].value;
if (rhs_value > MAX_MONEY) {
throw std::invalid_argument("nonsensical right hand side of joinsplit balance");
}
}
// Sample r
uint256 r = random_uint256();
out_notes[i] = outputs[i].note(phi, r, i, h_sig);
}
if (lhs_value != rhs_value) {
throw std::invalid_argument("invalid joinsplit balance");
}
// Compute the output commitments
for (size_t i = 0; i < NumOutputs; i++) {
out_commitments[i] = out_notes[i].cm();
}
// Encrypt the ciphertexts containing the note
// plaintexts to the recipients of the value.
{
ZCNoteEncryption encryptor(h_sig);
for (size_t i = 0; i < NumOutputs; i++) {
SproutNotePlaintext pt(out_notes[i], outputs[i].memo);
out_ciphertexts[i] = pt.encrypt(encryptor, outputs[i].addr.pk_enc);
}
out_ephemeralKey = encryptor.get_epk();
// !!! Payment disclosure START
if (out_esk != nullptr) {
*out_esk = encryptor.get_esk();
}
// !!! Payment disclosure END
}
// Authenticate h_sig with each of the input
// spending keys, producing macs which protect
// against malleability.
for (size_t i = 0; i < NumInputs; i++) {
out_macs[i] = PRF_pk(inputs[i].key, i, h_sig);
}
if (!computeProof) {
return GrothProof();
}
GrothProof proof;
CDataStream ss1(SER_NETWORK, PROTOCOL_VERSION);
ss1 << inputs[0].witness.path();
std::vector<unsigned char> auth1(ss1.begin(), ss1.end());
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION);
ss2 << inputs[1].witness.path();
std::vector<unsigned char> auth2(ss2.begin(), ss2.end());
librustzcash_sprout_prove(
proof.begin(),
phi.begin(),
rt.begin(),
h_sig.begin(),
inputs[0].key.begin(),
inputs[0].note.value(),
inputs[0].note.rho.begin(),
inputs[0].note.r.begin(),
auth1.data(),
inputs[1].key.begin(),
inputs[1].note.value(),
inputs[1].note.rho.begin(),
inputs[1].note.r.begin(),
auth2.data(),
out_notes[0].a_pk.begin(),
out_notes[0].value(),
out_notes[0].r.begin(),
out_notes[1].a_pk.begin(),
out_notes[1].value(),
out_notes[1].r.begin(),
vpub_old,
vpub_new
);
return proof;
}
};
template<size_t NumInputs, size_t NumOutputs>
JoinSplit<NumInputs, NumOutputs>* JoinSplit<NumInputs, NumOutputs>::Prepared()
{
return new JoinSplitCircuit<NumInputs, NumOutputs>();
}
template<size_t NumInputs, size_t NumOutputs>
uint256 JoinSplit<NumInputs, NumOutputs>::h_sig(
const uint256& randomSeed,
const std::array<uint256, NumInputs>& nullifiers,
const uint256& joinSplitPubKey
) {
const unsigned char personalization[crypto_generichash_blake2b_PERSONALBYTES]
= {'Z','c','a','s','h','C','o','m','p','u','t','e','h','S','i','g'};
std::vector<unsigned char> block(randomSeed.begin(), randomSeed.end());
for (size_t i = 0; i < NumInputs; i++) {
block.insert(block.end(), nullifiers[i].begin(), nullifiers[i].end());
}
block.insert(block.end(), joinSplitPubKey.begin(), joinSplitPubKey.end());
uint256 output;
if (crypto_generichash_blake2b_salt_personal(output.begin(), 32,
&block[0], block.size(),
NULL, 0, // No key.
NULL, // No salt.
personalization
) != 0)
{
throw std::logic_error("hash function failure");
}
return output;
}
SproutNote JSOutput::note(const uint252& phi, const uint256& r, size_t i, const uint256& h_sig) const {
uint256 rho = PRF_rho(phi, i, h_sig);
return SproutNote(addr.a_pk, value, rho, r);
}
JSOutput::JSOutput() : addr(uint256(), uint256()), value(0) {
SproutSpendingKey a_sk = SproutSpendingKey::random();
addr = a_sk.address();
}
JSInput::JSInput() : witness(SproutMerkleTree().witness()),
key(SproutSpendingKey::random()) {
note = SproutNote(key.address().a_pk, 0, random_uint256(), random_uint256());
SproutMerkleTree dummy_tree;
dummy_tree.append(note.cm());
witness = dummy_tree.witness();
}
template class JoinSplit<ZC_NUM_JS_INPUTS,
ZC_NUM_JS_OUTPUTS>;
}

58
src/zcash/JoinSplit.hpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#ifndef ZC_JOINSPLIT_H_
#define ZC_JOINSPLIT_H_
@ -24,69 +26,13 @@ typedef std::array<unsigned char, GROTH_PROOF_SIZE> GrothProof;
typedef boost::variant<PHGRProof, GrothProof> SproutProof;
class JSInput {
public:
SproutWitness witness;
SproutNote note;
SproutSpendingKey key;
JSInput();
JSInput(SproutWitness witness,
SproutNote note,
SproutSpendingKey key) : witness(witness), note(note), key(key) { }
uint256 nullifier() const {
return note.nullifier(key);
}
};
class JSOutput {
public:
SproutPaymentAddress addr;
uint64_t value;
std::array<unsigned char, ZC_MEMO_SIZE> memo = {{0xF6}}; // 0xF6 is invalid UTF8 as per spec, rest of array is 0x00
JSOutput();
JSOutput(SproutPaymentAddress addr, uint64_t value) : addr(addr), value(value) { }
SproutNote note(const uint252& phi, const uint256& r, size_t i, const uint256& h_sig) const;
};
template<size_t NumInputs, size_t NumOutputs>
class JoinSplit {
public:
virtual ~JoinSplit() {}
static JoinSplit<NumInputs, NumOutputs>* Prepared();
static uint256 h_sig(const uint256& randomSeed,
const std::array<uint256, NumInputs>& nullifiers,
const uint256& joinSplitPubKey
);
// Compute nullifiers, macs, note commitments & encryptions, and SNARK proof
virtual SproutProof prove(
const std::array<JSInput, NumInputs>& inputs,
const std::array<JSOutput, NumOutputs>& outputs,
std::array<SproutNote, NumOutputs>& out_notes,
std::array<ZCNoteEncryption::Ciphertext, NumOutputs>& out_ciphertexts,
uint256& out_ephemeralKey,
const uint256& joinSplitPubKey,
uint256& out_randomSeed,
std::array<uint256, NumInputs>& out_hmacs,
std::array<uint256, NumInputs>& out_nullifiers,
std::array<uint256, NumOutputs>& out_commitments,
uint64_t vpub_old,
uint64_t vpub_new,
const uint256& rt,
bool computeProof = true,
// For paymentdisclosure, we need to retrieve the esk.
// Reference as non-const parameter with default value leads to compile error.
// So use pointer for simplicity.
uint256 *out_esk = nullptr
) = 0;
protected:
JoinSplit() {}
};
}

82
src/zcash/Note.cpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#include "Note.hpp"
#include "prf.h"
#include "crypto/sha256.h"
@ -11,35 +13,6 @@
using namespace libzcash;
SproutNote::SproutNote() {
a_pk = random_uint256();
rho = random_uint256();
r = random_uint256();
}
uint256 SproutNote::cm() const {
unsigned char discriminant = 0xb0;
CSHA256 hasher;
hasher.Write(&discriminant, 1);
hasher.Write(a_pk.begin(), 32);
auto value_vec = convertIntToVectorLE(value_);
hasher.Write(&value_vec[0], value_vec.size());
hasher.Write(rho.begin(), 32);
hasher.Write(r.begin(), 32);
uint256 result;
hasher.Finalize(result.begin());
return result;
}
uint256 SproutNote::nullifier(const SproutSpendingKey& a_sk) const {
return PRF_nf(a_sk, rho);
}
// Construct and populate Sapling note for a given payment address and value.
SaplingNote::SaplingNote(const SaplingPaymentAddress& address, const uint64_t value) : BaseNote(value) {
d = address.d;
@ -88,57 +61,6 @@ boost::optional<uint256> SaplingNote::nullifier(const SaplingFullViewingKey& vk,
return result;
}
SproutNotePlaintext::SproutNotePlaintext(
const SproutNote& note,
std::array<unsigned char, ZC_MEMO_SIZE> memo) : BaseNotePlaintext(note, memo)
{
rho = note.rho;
r = note.r;
}
SproutNote SproutNotePlaintext::note(const SproutPaymentAddress& addr) const
{
return SproutNote(addr.a_pk, value_, rho, r);
}
SproutNotePlaintext SproutNotePlaintext::decrypt(const ZCNoteDecryption& decryptor,
const ZCNoteDecryption::Ciphertext& ciphertext,
const uint256& ephemeralKey,
const uint256& h_sig,
unsigned char nonce
)
{
auto plaintext = decryptor.decrypt(ciphertext, ephemeralKey, h_sig, nonce);
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << plaintext;
SproutNotePlaintext ret;
ss >> ret;
assert(ss.size() == 0);
return ret;
}
ZCNoteEncryption::Ciphertext SproutNotePlaintext::encrypt(ZCNoteEncryption& encryptor,
const uint256& pk_enc
) const
{
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
ss << (*this);
ZCNoteEncryption::Plaintext pt;
assert(pt.size() == ss.size());
memcpy(&pt[0], &ss[0], pt.size());
return encryptor.encrypt(pk_enc, pt);
}
// Construct and populate SaplingNotePlaintext for a given note and memo.
SaplingNotePlaintext::SaplingNotePlaintext(
const SaplingNote& note,

63
src/zcash/Note.hpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#ifndef ZC_NOTE_H_
#define ZC_NOTE_H_
@ -22,25 +24,6 @@ public:
inline uint64_t value() const { return value_; };
};
class SproutNote : public BaseNote {
public:
uint256 a_pk;
uint256 rho;
uint256 r;
SproutNote(uint256 a_pk, uint64_t value, uint256 rho, uint256 r)
: BaseNote(value), a_pk(a_pk), rho(rho), r(r) {}
SproutNote();
virtual ~SproutNote() {};
uint256 cm() const;
uint256 nullifier(const SproutSpendingKey& a_sk) const;
};
class SaplingNote : public BaseNote {
public:
diversifier_t d;
@ -74,48 +57,6 @@ public:
inline const std::array<unsigned char, ZC_MEMO_SIZE> & memo() const { return memo_; }
};
class SproutNotePlaintext : public BaseNotePlaintext {
public:
uint256 rho;
uint256 r;
SproutNotePlaintext() {}
SproutNotePlaintext(const SproutNote& note, std::array<unsigned char, ZC_MEMO_SIZE> memo);
SproutNote note(const SproutPaymentAddress& addr) const;
virtual ~SproutNotePlaintext() {}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
unsigned char leadingByte = 0x00;
READWRITE(leadingByte);
if (leadingByte != 0x00) {
throw std::ios_base::failure("lead byte of SproutNotePlaintext is not recognized");
}
READWRITE(value_);
READWRITE(rho);
READWRITE(r);
READWRITE(memo_);
}
static SproutNotePlaintext decrypt(const ZCNoteDecryption& decryptor,
const ZCNoteDecryption::Ciphertext& ciphertext,
const uint256& ephemeralKey,
const uint256& h_sig,
unsigned char nonce
);
ZCNoteEncryption::Ciphertext encrypt(ZCNoteEncryption& encryptor,
const uint256& pk_enc
) const;
};
typedef std::pair<SaplingEncCiphertext, SaplingNoteEncryption> SaplingNotePlaintextEncryptionResult;
class SaplingNotePlaintext : public BaseNotePlaintext {

50
src/zcash/NoteEncryption.cpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#include "NoteEncryption.hpp"
#include <stdexcept>
#include "sodium.h"
@ -374,52 +376,6 @@ typename NoteDecryption<MLEN>::Plaintext NoteDecryption<MLEN>::decrypt
return plaintext;
}
//
// Payment disclosure - decrypt with esk
//
template<size_t MLEN>
typename PaymentDisclosureNoteDecryption<MLEN>::Plaintext PaymentDisclosureNoteDecryption<MLEN>::decryptWithEsk
(const PaymentDisclosureNoteDecryption<MLEN>::Ciphertext &ciphertext,
const uint256 &pk_enc,
const uint256 &esk,
const uint256 &hSig,
unsigned char nonce
) const
{
uint256 dhsecret;
if (crypto_scalarmult(dhsecret.begin(), esk.begin(), pk_enc.begin()) != 0) {
throw std::logic_error("Could not create DH secret");
}
// Regenerate keypair
uint256 epk = NoteEncryption<MLEN>::generate_pubkey(esk);
unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE];
KDF(K, dhsecret, epk, pk_enc, hSig, nonce);
// The nonce is zero because we never reuse keys
unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {};
PaymentDisclosureNoteDecryption<MLEN>::Plaintext plaintext;
// Message length is always NOTEENCRYPTION_AUTH_BYTES less than
// the ciphertext length.
if (crypto_aead_chacha20poly1305_ietf_decrypt(plaintext.begin(), NULL,
NULL,
ciphertext.begin(), PaymentDisclosureNoteDecryption<MLEN>::CLEN,
NULL,
0,
cipher_nonce, K) != 0) {
throw note_decryption_failed();
}
return plaintext;
}
template<size_t MLEN>
uint256 NoteEncryption<MLEN>::generate_privkey(const uint252 &a_sk)
{
@ -461,6 +417,4 @@ uint252 random_uint252()
template class NoteEncryption<ZC_NOTEPLAINTEXT_SIZE>;
template class NoteDecryption<ZC_NOTEPLAINTEXT_SIZE>;
template class PaymentDisclosureNoteDecryption<ZC_NOTEPLAINTEXT_SIZE>;
}

29
src/zcash/NoteEncryption.hpp

@ -1,7 +1,4 @@
/*
See the Zcash protocol specification for more information.
https://github.com/zcash/zips/blob/master/protocol/protocol.pdf
*/
// Copyright (c) 2019-2020 The Hush developers
#ifndef ZC_NOTE_ENCRYPTION_H_
#define ZC_NOTE_ENCRYPTION_H_
@ -169,33 +166,9 @@ public:
};
// Subclass PaymentDisclosureNoteDecryption provides a method to decrypt a note with esk.
template<size_t MLEN>
class PaymentDisclosureNoteDecryption : public NoteDecryption<MLEN> {
protected:
public:
enum { CLEN=MLEN+NOTEENCRYPTION_AUTH_BYTES };
typedef std::array<unsigned char, CLEN> Ciphertext;
typedef std::array<unsigned char, MLEN> Plaintext;
PaymentDisclosureNoteDecryption() : NoteDecryption<MLEN>() {}
PaymentDisclosureNoteDecryption(uint256 sk_enc) : NoteDecryption<MLEN>(sk_enc) {}
Plaintext decryptWithEsk(
const Ciphertext &ciphertext,
const uint256 &pk_enc,
const uint256 &esk,
const uint256 &hSig,
unsigned char nonce
) const;
};
}
typedef libzcash::NoteEncryption<ZC_NOTEPLAINTEXT_SIZE> ZCNoteEncryption;
typedef libzcash::NoteDecryption<ZC_NOTEPLAINTEXT_SIZE> ZCNoteDecryption;
typedef libzcash::PaymentDisclosureNoteDecryption<ZC_NOTEPLAINTEXT_SIZE> ZCPaymentDisclosureNoteDecryption;
#endif /* ZC_NOTE_ENCRYPTION_H_ */

2
src/zcash/Proof.cpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#include "Proof.hpp"
#include "crypto/common.h"

2
src/zcash/Proof.hpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#ifndef ZC_PROOF_H_
#define ZC_PROOF_H_

1
src/zcash/Zcash.h

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
#ifndef ZC_ZCASH_H_
#define ZC_ZCASH_H_

2
src/zcash/prf.cpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#include "prf.h"
#include "crypto/sha256.h"
#include "hash.h"

2
src/zcash/util.cpp

@ -1,3 +1,5 @@
// Copyright (c) 2019-2020 The Hush developers
#include "zcash/util.h"
#include <algorithm>
#include <stdexcept>

1
src/zcash/util.h

@ -1,3 +1,4 @@
// Copyright (c) 2019-2020 The Hush developers
#ifndef ZC_UTIL_H_
#define ZC_UTIL_H_

1
src/zcash/zip32.cpp

@ -1,4 +1,5 @@
// Copyright (c) 2018 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.

3
src/zcash/zip32.h

@ -1,4 +1,5 @@
// Copyright (c) 2018 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.
@ -134,7 +135,7 @@ struct SaplingExtendedSpendingKey {
}
};
typedef boost::variant<InvalidEncoding, SproutSpendingKey, SaplingExtendedSpendingKey> SpendingKey;
typedef boost::variant<InvalidEncoding, SaplingExtendedSpendingKey> SpendingKey;
}

211
src/zcbenchmarks.cpp

@ -25,7 +25,6 @@
#include "sodium.h"
#include "streams.h"
#include "txdb.h"
#include "utiltest.h"
#include "wallet/wallet.h"
#include "zcbenchmarks.h"
@ -41,7 +40,7 @@ void pre_wallet_load()
{
LogPrintf("%s: In progress...\n", __func__);
if (ShutdownRequested())
throw new std::runtime_error("The node is shutting down");
throw new std::runtime_error("The Hush node is shutting down");
if (pwalletMain)
pwalletMain->Flush(false);
@ -93,60 +92,6 @@ double benchmark_sleep()
return timer_stop(tv_start);
}
double benchmark_create_joinsplit()
{
uint256 joinSplitPubKey;
/* Get the anchor of an empty commitment tree. */
uint256 anchor = SproutMerkleTree().root();
struct timeval tv_start;
timer_start(tv_start);
JSDescription jsdesc(*pzcashParams,
joinSplitPubKey,
anchor,
{JSInput(), JSInput()},
{JSOutput(), JSOutput()},
0,
0);
double ret = timer_stop(tv_start);
auto verifier = libzcash::ProofVerifier::Strict();
assert(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey));
return ret;
}
std::vector<double> benchmark_create_joinsplit_threaded(int nThreads)
{
std::vector<double> ret;
std::vector<std::future<double>> tasks;
std::vector<std::thread> threads;
for (int i = 0; i < nThreads; i++) {
std::packaged_task<double(void)> task(&benchmark_create_joinsplit);
tasks.emplace_back(task.get_future());
threads.emplace_back(std::move(task));
}
std::future_status status;
for (auto it = tasks.begin(); it != tasks.end(); it++) {
it->wait();
ret.push_back(it->get());
}
for (auto it = threads.begin(); it != threads.end(); it++) {
it->join();
}
return ret;
}
double benchmark_verify_joinsplit(const JSDescription &joinsplit)
{
struct timeval tv_start;
timer_start(tv_start);
uint256 joinSplitPubKey;
auto verifier = libzcash::ProofVerifier::Strict();
joinsplit.Verify(*pzcashParams, verifier, joinSplitPubKey);
return timer_stop(tv_start);
}
#ifdef ENABLE_MINING
double benchmark_solve_equihash()
{
@ -263,160 +208,6 @@ double benchmark_large_tx(size_t nInputs)
return timer_stop(tv_start);
}
double benchmark_try_decrypt_notes(size_t nAddrs)
{
CWallet wallet;
for (int i = 0; i < nAddrs; i++) {
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
}
auto sk = libzcash::SproutSpendingKey::random();
auto tx = GetValidReceive(*pzcashParams, sk, 10, true);
struct timeval tv_start;
timer_start(tv_start);
auto nd = wallet.FindMySproutNotes(tx);
return timer_stop(tv_start);
}
double benchmark_increment_note_witnesses(size_t nTxs)
{
CWallet wallet;
SproutMerkleTree sproutTree;
SaplingMerkleTree saplingTree;
auto sk = libzcash::SproutSpendingKey::random();
wallet.AddSproutSpendingKey(sk);
// First block
CBlock block1;
for (int i = 0; i < nTxs; i++) {
auto wtx = GetValidReceive(*pzcashParams, sk, 10, true);
auto note = GetNote(*pzcashParams, sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
block1.vtx.push_back(wtx);
}
CBlockIndex index1(block1);
index1.SetHeight(1);
// Increment to get transactions witnessed
wallet.ChainTip(&index1, &block1, std::make_pair(sproutTree, saplingTree));
// Second block
CBlock block2;
block2.hashPrevBlock = block1.GetHash();
{
auto wtx = GetValidReceive(*pzcashParams, sk, 10, true);
auto note = GetNote(*pzcashParams, sk, wtx, 0, 1);
auto nullifier = note.nullifier(sk);
mapSproutNoteData_t noteData;
JSOutPoint jsoutpt {wtx.GetHash(), 0, 1};
SproutNoteData nd {sk.address(), nullifier};
noteData[jsoutpt] = nd;
wtx.SetSproutNoteData(noteData);
wallet.AddToWallet(wtx, true, NULL);
block2.vtx.push_back(wtx);
}
CBlockIndex index2(block2);
index2.SetHeight(2);
struct timeval tv_start;
timer_start(tv_start);
wallet.ChainTip(&index2, &block2, std::make_pair(sproutTree, saplingTree));
return timer_stop(tv_start);
}
// Fake the input of a given block
class FakeCoinsViewDB : public CCoinsViewDB {
uint256 hash;
SproutMerkleTree t;
public:
FakeCoinsViewDB(std::string dbName, uint256& hash) : CCoinsViewDB(dbName, 100, false, false), hash(hash) {}
bool GetAnchorAt(const uint256 &rt, SproutMerkleTree &tree) const {
if (rt == t.root()) {
tree = t;
return true;
}
return false;
}
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
return false;
}
uint256 GetBestBlock() const {
return hash;
}
uint256 GetBestAnchor() const {
return t.root();
}
bool BatchWrite(CCoinsMap &mapCoins,
const uint256 &hashBlock,
const uint256 &hashAnchor,
CAnchorsSproutMap &mapSproutAnchors,
CNullifiersMap &mapSproutNullifiers,
CNullifiersMap& mapSaplingNullifiers) {
return false;
}
bool GetStats(CCoinsStats &stats) const {
return false;
}
};
double benchmark_connectblock_slow()
{
// Test for issue 2017-05-01.a
SelectParams(CBaseChainParams::MAIN);
CBlock block;
FILE* fp = fopen((GetDataDir() / "benchmark/block-107134.dat").string().c_str(), "rb");
if (!fp) throw new std::runtime_error("Failed to open block data file");
CAutoFile blkFile(fp, SER_DISK, CLIENT_VERSION);
blkFile >> block;
blkFile.fclose();
// Fake its inputs
auto hashPrev = uint256S("00000000159a41f468e22135942a567781c3f3dc7ad62257993eb3c69c3f95ef");
FakeCoinsViewDB fakeDB("benchmark/block-107134-inputs", hashPrev);
CCoinsViewCache view(&fakeDB);
// Fake the chain
CBlockIndex index(block);
index.SetHeight(107134);
CBlockIndex indexPrev;
indexPrev.phashBlock = &hashPrev;
indexPrev.SetHeight(index.GetHeight() - 1);
index.pprev = &indexPrev;
mapBlockIndex.insert(std::make_pair(hashPrev, &indexPrev));
CValidationState state;
struct timeval tv_start;
timer_start(tv_start);
assert(ConnectBlock(block, state, &index, view, true));
auto duration = timer_stop(tv_start);
// Undo alterations to global state
mapBlockIndex.erase(hashPrev);
SelectParamsFromCommandLine();
return duration;
}
extern UniValue getnewaddress(const UniValue& params, bool fHelp, const CPubKey& mypk); // in rpcwallet.cpp
extern UniValue sendtoaddress(const UniValue& params, bool fHelp, const CPubKey& mypk);

Loading…
Cancel
Save