forked from hush/hush3
![simon@bitcartel.com](/assets/img/avatar_default.png)
12 changed files with 1161 additions and 90 deletions
@ -0,0 +1,190 @@ |
|||
#!/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, sync_blocks |
|||
|
|||
import sys |
|||
import time |
|||
from decimal import Decimal |
|||
|
|||
class WalletShieldCoinbaseTest (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 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe'] |
|||
self.nodes = [] |
|||
self.nodes.append(start_node(0, self.options.tmpdir, args)) |
|||
self.nodes.append(start_node(1, self.options.tmpdir, args)) |
|||
args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"] |
|||
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() |
|||
|
|||
# Returns txid if operation was a success or None |
|||
def wait_and_assert_operationid_status(self, nodeid, myopid, in_status='success', in_errormsg=None): |
|||
print('waiting for async operation {}'.format(myopid)) |
|||
opids = [] |
|||
opids.append(myopid) |
|||
timeout = 300 |
|||
status = None |
|||
errormsg = None |
|||
txid = None |
|||
for x in xrange(1, timeout): |
|||
results = self.nodes[nodeid].z_getoperationresult(opids) |
|||
if len(results)==0: |
|||
time.sleep(1) |
|||
else: |
|||
status = results[0]["status"] |
|||
if status == "failed": |
|||
errormsg = results[0]['error']['message'] |
|||
elif status == "success": |
|||
txid = results[0]['result']['txid'] |
|||
break |
|||
print('...returned status: {}'.format(status)) |
|||
assert_equal(in_status, status) |
|||
if errormsg is not None: |
|||
assert(in_errormsg is not None) |
|||
assert_equal(in_errormsg in errormsg, True) |
|||
print('...returned error: {}'.format(errormsg)) |
|||
return txid |
|||
|
|||
def run_test (self): |
|||
print "Mining blocks..." |
|||
|
|||
self.nodes[0].generate(1) |
|||
do_not_shield_taddr = self.nodes[0].getnewaddress() |
|||
|
|||
self.nodes[0].generate(4) |
|||
walletinfo = self.nodes[0].getwalletinfo() |
|||
assert_equal(walletinfo['immature_balance'], 50) |
|||
assert_equal(walletinfo['balance'], 0) |
|||
self.sync_all() |
|||
self.nodes[2].generate(1) |
|||
self.nodes[2].getnewaddress() |
|||
self.nodes[2].generate(1) |
|||
self.nodes[2].getnewaddress() |
|||
self.nodes[2].generate(1) |
|||
self.sync_all() |
|||
self.nodes[1].generate(101) |
|||
self.sync_all() |
|||
assert_equal(self.nodes[0].getbalance(), 50) |
|||
assert_equal(self.nodes[1].getbalance(), 10) |
|||
assert_equal(self.nodes[2].getbalance(), 30) |
|||
|
|||
# Prepare to send taddr->zaddr |
|||
mytaddr = self.nodes[0].getnewaddress() |
|||
myzaddr = self.nodes[0].z_getnewaddress() |
|||
|
|||
# Shielding will fail when trying to spend from watch-only address |
|||
self.nodes[2].importaddress(mytaddr) |
|||
try: |
|||
self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr) |
|||
except JSONRPCException,e: |
|||
errorString = e.error['message'] |
|||
assert_equal("Could not find any coinbase funds to shield" in errorString, True) |
|||
|
|||
# Shielding will fail because fee is negative |
|||
try: |
|||
self.nodes[0].z_shieldcoinbase("*", myzaddr, -1) |
|||
except JSONRPCException,e: |
|||
errorString = e.error['message'] |
|||
assert_equal("Amount out of range" in errorString, True) |
|||
|
|||
# Shielding will fail because fee is larger than MAX_MONEY |
|||
try: |
|||
self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('21000000.00000001')) |
|||
except JSONRPCException,e: |
|||
errorString = e.error['message'] |
|||
assert_equal("Amount out of range" in errorString, True) |
|||
|
|||
# Shielding will fail because fee is larger than sum of utxos |
|||
try: |
|||
self.nodes[0].z_shieldcoinbase("*", myzaddr, 999) |
|||
except JSONRPCException,e: |
|||
errorString = e.error['message'] |
|||
assert_equal("Insufficient coinbase funds" in errorString, True) |
|||
|
|||
# Shield coinbase utxos from node 0 of value 40, standard fee of 0.00010000 |
|||
result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr) |
|||
mytxid = self.wait_and_assert_operationid_status(0, result['opid']) |
|||
self.sync_all() |
|||
self.nodes[1].generate(1) |
|||
self.sync_all() |
|||
|
|||
# Confirm balances and that do_not_shield_taddr containing funds of 10 was left alone |
|||
assert_equal(self.nodes[0].getbalance(), 10) |
|||
assert_equal(self.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('10.0')) |
|||
assert_equal(self.nodes[0].z_getbalance(myzaddr), Decimal('39.99990000')) |
|||
assert_equal(self.nodes[1].getbalance(), 20) |
|||
assert_equal(self.nodes[2].getbalance(), 30) |
|||
|
|||
# Shield coinbase utxos from any node 2 taddr, and set fee to 0 |
|||
result = self.nodes[2].z_shieldcoinbase("*", myzaddr, 0) |
|||
mytxid = self.wait_and_assert_operationid_status(2, result['opid']) |
|||
self.sync_all() |
|||
self.nodes[1].generate(1) |
|||
self.sync_all() |
|||
|
|||
assert_equal(self.nodes[0].getbalance(), 10) |
|||
assert_equal(self.nodes[0].z_getbalance(myzaddr), Decimal('69.99990000')) |
|||
assert_equal(self.nodes[1].getbalance(), 30) |
|||
assert_equal(self.nodes[2].getbalance(), 0) |
|||
|
|||
# Generate 800 coinbase utxos on node 0, and 20 coinbase utxos on node 2 |
|||
self.nodes[0].generate(800) |
|||
self.sync_all() |
|||
self.nodes[2].generate(20) |
|||
self.sync_all() |
|||
self.nodes[1].generate(100) |
|||
self.sync_all() |
|||
mytaddr = self.nodes[0].getnewaddress() |
|||
|
|||
# Shielding the 800 utxos will occur over two transactions, since max tx size is 100,000 bytes. |
|||
# We don't verify shieldingValue as utxos are not selected in any specific order, so value can change on each test run. |
|||
result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr, 0) |
|||
assert_equal(result["shieldingUTXOs"], Decimal('662')) |
|||
assert_equal(result["remainingUTXOs"], Decimal('138')) |
|||
remainingValue = result["remainingValue"] |
|||
opid1 = result['opid'] |
|||
|
|||
# Verify that utxos are locked (not available for selection) by queuing up another shielding operation |
|||
result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr) |
|||
assert_equal(result["shieldingValue"], Decimal(remainingValue)) |
|||
assert_equal(result["shieldingUTXOs"], Decimal('138')) |
|||
assert_equal(result["remainingValue"], Decimal('0')) |
|||
assert_equal(result["remainingUTXOs"], Decimal('0')) |
|||
opid2 = result['opid'] |
|||
|
|||
# wait for both aysnc operations to complete |
|||
self.wait_and_assert_operationid_status(0, opid1) |
|||
self.wait_and_assert_operationid_status(0, opid2) |
|||
|
|||
# sync_all() invokes sync_mempool() but node 2's mempool limit will cause tx1 and tx2 to be rejected. |
|||
# So instead, we sync on blocks, and after a new block is generated, all nodes will have an empty mempool. |
|||
sync_blocks(self.nodes) |
|||
self.nodes[1].generate(1) |
|||
self.sync_all() |
|||
|
|||
# Verify maximum number of utxos which node 2 can shield is limited by option -mempooltxinputlimit |
|||
mytaddr = self.nodes[2].getnewaddress() |
|||
result = self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr, 0) |
|||
assert_equal(result["shieldingUTXOs"], Decimal('7')) |
|||
assert_equal(result["remainingUTXOs"], Decimal('13')) |
|||
mytxid = self.wait_and_assert_operationid_status(2, result['opid']) |
|||
self.sync_all() |
|||
self.nodes[1].generate(1) |
|||
self.sync_all() |
|||
|
|||
if __name__ == '__main__': |
|||
WalletShieldCoinbaseTest().main() |
@ -0,0 +1,441 @@ |
|||
// 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 "asyncrpcqueue.h" |
|||
#include "amount.h" |
|||
#include "core_io.h" |
|||
#include "init.h" |
|||
#include "main.h" |
|||
#include "net.h" |
|||
#include "netbase.h" |
|||
#include "rpcserver.h" |
|||
#include "timedata.h" |
|||
#include "util.h" |
|||
#include "utilmoneystr.h" |
|||
#include "wallet.h" |
|||
#include "walletdb.h" |
|||
#include "script/interpreter.h" |
|||
#include "utiltime.h" |
|||
#include "rpcprotocol.h" |
|||
#include "zcash/IncrementalMerkleTree.hpp" |
|||
#include "sodium.h" |
|||
#include "miner.h" |
|||
|
|||
#include <iostream> |
|||
#include <chrono> |
|||
#include <thread> |
|||
#include <string> |
|||
|
|||
#include "asyncrpcoperation_shieldcoinbase.h" |
|||
|
|||
using namespace libzcash; |
|||
|
|||
static int find_output(UniValue obj, int n) { |
|||
UniValue outputMapValue = find_value(obj, "outputmap"); |
|||
if (!outputMapValue.isArray()) { |
|||
throw JSONRPCError(RPC_WALLET_ERROR, "Missing outputmap for JoinSplit operation"); |
|||
} |
|||
|
|||
UniValue outputMap = outputMapValue.get_array(); |
|||
assert(outputMap.size() == ZC_NUM_JS_OUTPUTS); |
|||
for (size_t i = 0; i < outputMap.size(); i++) { |
|||
if (outputMap[i].get_int() == n) { |
|||
return i; |
|||
} |
|||
} |
|||
|
|||
throw std::logic_error("n is not present in outputmap"); |
|||
} |
|||
|
|||
AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase( |
|||
std::vector<ShieldCoinbaseUTXO> inputs, |
|||
std::string toAddress, |
|||
CAmount fee, |
|||
UniValue contextInfo) : |
|||
inputs_(inputs), fee_(fee), contextinfo_(contextInfo) |
|||
{ |
|||
if (fee < 0 || fee > MAX_MONEY) { |
|||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range"); |
|||
} |
|||
|
|||
if (inputs.size() == 0) { |
|||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Empty inputs"); |
|||
} |
|||
|
|||
// Check the destination address is valid for this network i.e. not testnet being used on mainnet
|
|||
CZCPaymentAddress address(toAddress); |
|||
try { |
|||
tozaddr_ = address.Get(); |
|||
} catch (const std::runtime_error& e) { |
|||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("runtime error: ") + e.what()); |
|||
} |
|||
|
|||
// Log the context info
|
|||
if (LogAcceptCategory("zrpcunsafe")) { |
|||
LogPrint("zrpcunsafe", "%s: z_shieldcoinbase initialized (context=%s)\n", getId(), contextInfo.write()); |
|||
} else { |
|||
LogPrint("zrpc", "%s: z_shieldcoinbase initialized\n", getId()); |
|||
} |
|||
|
|||
// Lock UTXOs
|
|||
lock_utxos(); |
|||
} |
|||
|
|||
AsyncRPCOperation_shieldcoinbase::~AsyncRPCOperation_shieldcoinbase() { |
|||
} |
|||
|
|||
void AsyncRPCOperation_shieldcoinbase::main() { |
|||
if (isCancelled()) { |
|||
unlock_utxos(); // clean up
|
|||
return; |
|||
} |
|||
|
|||
set_state(OperationStatus::EXECUTING); |
|||
start_execution_clock(); |
|||
|
|||
bool success = false; |
|||
|
|||
#ifdef ENABLE_MINING |
|||
#ifdef ENABLE_WALLET |
|||
GenerateBitcoins(false, NULL, 0); |
|||
#else |
|||
GenerateBitcoins(false, 0); |
|||
#endif |
|||
#endif |
|||
|
|||
try { |
|||
success = main_impl(); |
|||
} catch (const UniValue& objError) { |
|||
int code = find_value(objError, "code").get_int(); |
|||
std::string message = find_value(objError, "message").get_str(); |
|||
set_error_code(code); |
|||
set_error_message(message); |
|||
} catch (const runtime_error& e) { |
|||
set_error_code(-1); |
|||
set_error_message("runtime error: " + string(e.what())); |
|||
} catch (const logic_error& e) { |
|||
set_error_code(-1); |
|||
set_error_message("logic error: " + string(e.what())); |
|||
} catch (const exception& e) { |
|||
set_error_code(-1); |
|||
set_error_message("general exception: " + string(e.what())); |
|||
} catch (...) { |
|||
set_error_code(-2); |
|||
set_error_message("unknown error"); |
|||
} |
|||
|
|||
#ifdef ENABLE_MINING |
|||
#ifdef ENABLE_WALLET |
|||
GenerateBitcoins(GetBoolArg("-gen",false), pwalletMain, GetArg("-genproclimit", 1)); |
|||
#else |
|||
GenerateBitcoins(GetBoolArg("-gen",false), GetArg("-genproclimit", 1)); |
|||
#endif |
|||
#endif |
|||
|
|||
stop_execution_clock(); |
|||
|
|||
if (success) { |
|||
set_state(OperationStatus::SUCCESS); |
|||
} else { |
|||
set_state(OperationStatus::FAILED); |
|||
} |
|||
|
|||
std::string s = strprintf("%s: z_shieldcoinbase finished (status=%s", getId(), getStateAsString()); |
|||
if (success) { |
|||
s += strprintf(", txid=%s)\n", tx_.GetHash().ToString()); |
|||
} else { |
|||
s += strprintf(", error=%s)\n", getErrorMessage()); |
|||
} |
|||
LogPrintf("%s",s); |
|||
|
|||
unlock_utxos(); // clean up
|
|||
} |
|||
|
|||
|
|||
bool AsyncRPCOperation_shieldcoinbase::main_impl() { |
|||
|
|||
CAmount minersFee = fee_; |
|||
|
|||
size_t numInputs = inputs_.size(); |
|||
|
|||
// Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects
|
|||
size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0); |
|||
if (limit>0 && numInputs > limit) { |
|||
throw JSONRPCError(RPC_WALLET_ERROR, |
|||
strprintf("Number of inputs %d is greater than mempooltxinputlimit of %d", |
|||
numInputs, limit)); |
|||
} |
|||
|
|||
CAmount targetAmount = 0; |
|||
for (ShieldCoinbaseUTXO & utxo : inputs_) { |
|||
targetAmount += utxo.amount; |
|||
} |
|||
|
|||
if (targetAmount <= minersFee) { |
|||
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, |
|||
strprintf("Insufficient coinbase funds, have %s and miners fee is %s", |
|||
FormatMoney(targetAmount), FormatMoney(minersFee))); |
|||
} |
|||
|
|||
CAmount sendAmount = targetAmount - minersFee; |
|||
LogPrint("zrpc", "%s: spending %s to shield %s with fee %s\n", |
|||
getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee)); |
|||
|
|||
// update the transaction with these inputs
|
|||
CMutableTransaction rawTx(tx_); |
|||
for (ShieldCoinbaseUTXO & t : inputs_) { |
|||
CTxIn in(COutPoint(t.txid, t.vout)); |
|||
rawTx.vin.push_back(in); |
|||
} |
|||
tx_ = CTransaction(rawTx); |
|||
|
|||
// Prepare raw transaction to handle JoinSplits
|
|||
CMutableTransaction mtx(tx_); |
|||
mtx.nVersion = 2; |
|||
crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_); |
|||
mtx.joinSplitPubKey = joinSplitPubKey_; |
|||
tx_ = CTransaction(mtx); |
|||
|
|||
// Create joinsplit
|
|||
UniValue obj(UniValue::VOBJ); |
|||
ShieldCoinbaseJSInfo info; |
|||
info.vpub_old = sendAmount; |
|||
info.vpub_new = 0; |
|||
JSOutput jso = JSOutput(tozaddr_, sendAmount); |
|||
info.vjsout.push_back(jso); |
|||
obj = perform_joinsplit(info); |
|||
|
|||
sign_send_raw_transaction(obj); |
|||
return true; |
|||
} |
|||
|
|||
|
|||
/**
|
|||
* Sign and send a raw transaction. |
|||
* Raw transaction as hex string should be in object field "rawtxn" |
|||
*/ |
|||
void AsyncRPCOperation_shieldcoinbase::sign_send_raw_transaction(UniValue obj) |
|||
{ |
|||
// Sign the raw transaction
|
|||
UniValue rawtxnValue = find_value(obj, "rawtxn"); |
|||
if (rawtxnValue.isNull()) { |
|||
throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for raw transaction"); |
|||
} |
|||
std::string rawtxn = rawtxnValue.get_str(); |
|||
|
|||
UniValue params = UniValue(UniValue::VARR); |
|||
params.push_back(rawtxn); |
|||
UniValue signResultValue = signrawtransaction(params, false); |
|||
UniValue signResultObject = signResultValue.get_obj(); |
|||
UniValue completeValue = find_value(signResultObject, "complete"); |
|||
bool complete = completeValue.get_bool(); |
|||
if (!complete) { |
|||
// TODO: #1366 Maybe get "errors" and print array vErrors into a string
|
|||
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to sign transaction"); |
|||
} |
|||
|
|||
UniValue hexValue = find_value(signResultObject, "hex"); |
|||
if (hexValue.isNull()) { |
|||
throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for signed transaction"); |
|||
} |
|||
std::string signedtxn = hexValue.get_str(); |
|||
|
|||
// Send the signed transaction
|
|||
if (!testmode) { |
|||
params.clear(); |
|||
params.setArray(); |
|||
params.push_back(signedtxn); |
|||
UniValue sendResultValue = sendrawtransaction(params, false); |
|||
if (sendResultValue.isNull()) { |
|||
throw JSONRPCError(RPC_WALLET_ERROR, "Send raw transaction did not return an error or a txid."); |
|||
} |
|||
|
|||
std::string txid = sendResultValue.get_str(); |
|||
|
|||
UniValue o(UniValue::VOBJ); |
|||
o.push_back(Pair("txid", txid)); |
|||
set_result(o); |
|||
} else { |
|||
// Test mode does not send the transaction to the network.
|
|||
|
|||
CDataStream stream(ParseHex(signedtxn), SER_NETWORK, PROTOCOL_VERSION); |
|||
CTransaction tx; |
|||
stream >> tx; |
|||
|
|||
UniValue o(UniValue::VOBJ); |
|||
o.push_back(Pair("test", 1)); |
|||
o.push_back(Pair("txid", tx.GetHash().ToString())); |
|||
o.push_back(Pair("hex", signedtxn)); |
|||
set_result(o); |
|||
} |
|||
|
|||
// Keep the signed transaction so we can hash to the same txid
|
|||
CDataStream stream(ParseHex(signedtxn), SER_NETWORK, PROTOCOL_VERSION); |
|||
CTransaction tx; |
|||
stream >> tx; |
|||
tx_ = tx; |
|||
} |
|||
|
|||
|
|||
UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInfo & info) { |
|||
uint256 anchor = pcoinsTip->GetBestAnchor(); |
|||
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.
|
|||
boost::array<libzcash::JSInput, ZC_NUM_JS_INPUTS> inputs |
|||
{info.vjsin[0], info.vjsin[1]}; |
|||
boost::array<libzcash::JSOutput, ZC_NUM_JS_OUTPUTS> outputs |
|||
{info.vjsout[0], info.vjsout[1]}; |
|||
boost::array<size_t, ZC_NUM_JS_INPUTS> inputMap; |
|||
boost::array<size_t, ZC_NUM_JS_OUTPUTS> outputMap; |
|||
JSDescription jsdesc = JSDescription::Randomized( |
|||
*pzcashParams, |
|||
joinSplitPubKey_, |
|||
anchor, |
|||
inputs, |
|||
outputs, |
|||
inputMap, |
|||
outputMap, |
|||
info.vpub_old, |
|||
info.vpub_new, |
|||
!this->testmode); |
|||
|
|||
{ |
|||
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); |
|||
|
|||
// 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(inputMap[i]); |
|||
} |
|||
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { |
|||
arrOutputMap.push_back(outputMap[i]); |
|||
} |
|||
|
|||
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. |
|||
*/ |
|||
UniValue AsyncRPCOperation_shieldcoinbase::getStatus() const { |
|||
UniValue v = AsyncRPCOperation::getStatus(); |
|||
if (contextinfo_.isNull()) { |
|||
return v; |
|||
} |
|||
|
|||
UniValue obj = v.get_obj(); |
|||
obj.push_back(Pair("method", "z_shieldcoinbase")); |
|||
obj.push_back(Pair("params", contextinfo_ )); |
|||
return obj; |
|||
} |
|||
|
|||
/**
|
|||
* Lock input utxos |
|||
*/ |
|||
void AsyncRPCOperation_shieldcoinbase::lock_utxos() { |
|||
LOCK2(cs_main, pwalletMain->cs_wallet); |
|||
for (auto utxo : inputs_) { |
|||
COutPoint outpt(utxo.txid, utxo.vout); |
|||
pwalletMain->LockCoin(outpt); |
|||
} |
|||
} |
|||
|
|||
/**
|
|||
* Unlock input utxos |
|||
*/ |
|||
void AsyncRPCOperation_shieldcoinbase::unlock_utxos() { |
|||
LOCK2(cs_main, pwalletMain->cs_wallet); |
|||
for (auto utxo : inputs_) { |
|||
COutPoint outpt(utxo.txid, utxo.vout); |
|||
pwalletMain->UnlockCoin(outpt); |
|||
} |
|||
} |
@ -0,0 +1,122 @@ |
|||
// 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 ASYNCRPCOPERATION_SHIELDCOINBASE_H |
|||
#define ASYNCRPCOPERATION_SHIELDCOINBASE_H |
|||
|
|||
#include "asyncrpcoperation.h" |
|||
#include "amount.h" |
|||
#include "base58.h" |
|||
#include "primitives/transaction.h" |
|||
#include "zcash/JoinSplit.hpp" |
|||
#include "zcash/Address.hpp" |
|||
#include "wallet.h" |
|||
|
|||
#include <unordered_map> |
|||
#include <tuple> |
|||
|
|||
#include <univalue.h> |
|||
|
|||
// Default transaction fee if caller does not specify one.
|
|||
#define SHIELD_COINBASE_DEFAULT_MINERS_FEE 10000 |
|||
|
|||
using namespace libzcash; |
|||
|
|||
struct ShieldCoinbaseUTXO { |
|||
uint256 txid; |
|||
int vout; |
|||
CAmount amount; |
|||
}; |
|||
|
|||
// Package of info which is passed to perform_joinsplit methods.
|
|||
struct ShieldCoinbaseJSInfo |
|||
{ |
|||
std::vector<JSInput> vjsin; |
|||
std::vector<JSOutput> vjsout; |
|||
CAmount vpub_old = 0; |
|||
CAmount vpub_new = 0; |
|||
}; |
|||
|
|||
class AsyncRPCOperation_shieldcoinbase : public AsyncRPCOperation { |
|||
public: |
|||
AsyncRPCOperation_shieldcoinbase(std::vector<ShieldCoinbaseUTXO> inputs, std::string toAddress, CAmount fee = SHIELD_COINBASE_DEFAULT_MINERS_FEE, UniValue contextInfo = NullUniValue); |
|||
virtual ~AsyncRPCOperation_shieldcoinbase(); |
|||
|
|||
// We don't want to be copied or moved around
|
|||
AsyncRPCOperation_shieldcoinbase(AsyncRPCOperation_shieldcoinbase const&) = delete; // Copy construct
|
|||
AsyncRPCOperation_shieldcoinbase(AsyncRPCOperation_shieldcoinbase&&) = delete; // Move construct
|
|||
AsyncRPCOperation_shieldcoinbase& operator=(AsyncRPCOperation_shieldcoinbase const&) = delete; // Copy assign
|
|||
AsyncRPCOperation_shieldcoinbase& operator=(AsyncRPCOperation_shieldcoinbase &&) = delete; // Move assign
|
|||
|
|||
virtual void main(); |
|||
|
|||
virtual UniValue getStatus() const; |
|||
|
|||
bool testmode = false; // Set to true to disable sending txs and generating proofs
|
|||
|
|||
private: |
|||
friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing
|
|||
|
|||
UniValue contextinfo_; // optional data to include in return value from getStatus()
|
|||
|
|||
CAmount fee_; |
|||
PaymentAddress tozaddr_; |
|||
|
|||
uint256 joinSplitPubKey_; |
|||
unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES]; |
|||
|
|||
std::vector<ShieldCoinbaseUTXO> inputs_; |
|||
|
|||
CTransaction tx_; |
|||
|
|||
bool main_impl(); |
|||
|
|||
// JoinSplit without any input notes to spend
|
|||
UniValue perform_joinsplit(ShieldCoinbaseJSInfo &); |
|||
|
|||
void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error
|
|||
|
|||
void lock_utxos(); |
|||
|
|||
void unlock_utxos(); |
|||
}; |
|||
|
|||
|
|||
// To test private methods, a friend class can act as a proxy
|
|||
class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase { |
|||
public: |
|||
std::shared_ptr<AsyncRPCOperation_shieldcoinbase> delegate; |
|||
|
|||
TEST_FRIEND_AsyncRPCOperation_shieldcoinbase(std::shared_ptr<AsyncRPCOperation_shieldcoinbase> ptr) : delegate(ptr) {} |
|||
|
|||
CTransaction getTx() { |
|||
return delegate->tx_; |
|||
} |
|||
|
|||
void setTx(CTransaction tx) { |
|||
delegate->tx_ = tx; |
|||
} |
|||
|
|||
// Delegated methods
|
|||
|
|||
bool main_impl() { |
|||
return delegate->main_impl(); |
|||
} |
|||
|
|||
UniValue perform_joinsplit(ShieldCoinbaseJSInfo &info) { |
|||
return delegate->perform_joinsplit(info); |
|||
} |
|||
|
|||
void sign_send_raw_transaction(UniValue obj) { |
|||
delegate->sign_send_raw_transaction(obj); |
|||
} |
|||
|
|||
void set_state(OperationStatus state) { |
|||
delegate->state_.store(state); |
|||
} |
|||
}; |
|||
|
|||
|
|||
#endif /* ASYNCRPCOPERATION_SHIELDCOINBASE_H */ |
|||
|
Loading…
Reference in new issue