From 342611f9325e6074ddbe00f53adc0b6e3d40d4e7 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Sat, 21 Oct 2017 23:23:57 -0700 Subject: [PATCH] add rpccryptoconditions --- src/Makefile.am | 1 + src/rpcclient.cpp | 6 +- src/rpccryptoconditions.cpp | 925 ++++++++++++++++++++++++++++++++++++ src/rpcserver.cpp | 2 + src/rpcserver.h | 3 + 5 files changed, 936 insertions(+), 1 deletion(-) create mode 100644 src/rpccryptoconditions.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 815d75d4c..919c6fc89 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -238,6 +238,7 @@ libbitcoin_server_a_SOURCES = \ rpcmisc.cpp \ rpcnet.cpp \ rpcrawtransaction.cpp \ + rpccryptoconditions.cpp \ rpcserver.cpp \ script/sigcache.cpp \ timedata.cpp \ diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index da8fdc4b6..2378e3ca0 100644 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -75,8 +75,12 @@ static const CRPCConvertParam vRPCConvertParams[] = { "getrawtransaction", 1 }, { "createrawtransaction", 0 }, { "createrawtransaction", 1 }, + { "createrawtransactioncc", 0 }, + { "createrawtransactioncc", 1 }, { "signrawtransaction", 1 }, { "signrawtransaction", 2 }, + { "signrawtransactioncc", 1 }, + { "signrawtransactioncc", 2 }, { "sendrawtransaction", 1 }, { "fundrawtransaction", 1 }, { "gettxout", 1 }, @@ -156,7 +160,7 @@ UniValue ParseNonRFCJSONValue(const std::string& strVal) UniValue jVal; if (!jVal.read(std::string("[")+strVal+std::string("]")) || !jVal.isArray() || jVal.size()!=1) - throw runtime_error(string("Error parsing JSON:")+strVal); + throw runtime_error(string("Error JSON:")+strVal); return jVal[0]; } diff --git a/src/rpccryptoconditions.cpp b/src/rpccryptoconditions.cpp new file mode 100644 index 000000000..946667aae --- /dev/null +++ b/src/rpccryptoconditions.cpp @@ -0,0 +1,925 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "base58.h" +#include "consensus/validation.h" +#include "core_io.h" +#include "init.h" +#include "keystore.h" +#include "main.h" +#include "merkleblock.h" +#include "net.h" +#include "primitives/transaction.h" +#include "rpcserver.h" +#include "script/script.h" +#include "script/script_error.h" +#include "script/sign.h" +#include "script/standard.h" +#include "uint256.h" +#include "cryptoconditions/include/cryptoconditions.h" +#ifdef ENABLE_WALLET +#include "wallet/wallet.h" +#endif + +#include + +#include + +#include + +using namespace std; + +void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); + + +UniValue TxJoinSplitToJSONcc(const CTransaction& tx) { + UniValue vjoinsplit(UniValue::VARR); + for (unsigned int i = 0; i < tx.vjoinsplit.size(); i++) { + const JSDescription& jsdescription = tx.vjoinsplit[i]; + UniValue joinsplit(UniValue::VOBJ); + + joinsplit.push_back(Pair("vpub_old", ValueFromAmount(jsdescription.vpub_old))); + joinsplit.push_back(Pair("vpub_new", ValueFromAmount(jsdescription.vpub_new))); + + joinsplit.push_back(Pair("anchor", jsdescription.anchor.GetHex())); + + { + UniValue nullifiers(UniValue::VARR); + BOOST_FOREACH(const uint256 nf, jsdescription.nullifiers) { + nullifiers.push_back(nf.GetHex()); + } + joinsplit.push_back(Pair("nullifiers", nullifiers)); + } + + { + UniValue commitments(UniValue::VARR); + BOOST_FOREACH(const uint256 commitment, jsdescription.commitments) { + commitments.push_back(commitment.GetHex()); + } + joinsplit.push_back(Pair("commitments", commitments)); + } + + joinsplit.push_back(Pair("onetimePubKey", jsdescription.ephemeralKey.GetHex())); + joinsplit.push_back(Pair("randomSeed", jsdescription.randomSeed.GetHex())); + + { + UniValue macs(UniValue::VARR); + BOOST_FOREACH(const uint256 mac, jsdescription.macs) { + macs.push_back(mac.GetHex()); + } + joinsplit.push_back(Pair("macs", macs)); + } + + CDataStream ssProof(SER_NETWORK, PROTOCOL_VERSION); + ssProof << jsdescription.proof; + joinsplit.push_back(Pair("proof", HexStr(ssProof.begin(), ssProof.end()))); + + { + UniValue ciphertexts(UniValue::VARR); + for (const ZCNoteEncryption::Ciphertext ct : jsdescription.ciphertexts) { + ciphertexts.push_back(HexStr(ct.begin(), ct.end())); + } + joinsplit.push_back(Pair("ciphertexts", ciphertexts)); + } + + vjoinsplit.push_back(joinsplit); + } + return vjoinsplit; +} + +uint64_t komodo_interest(int32_t txheight,uint64_t nValue,uint32_t nLockTime,uint32_t tiptime); + +void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); + + +UniValue getrawtransactioncc(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "getrawtransaction \"txid\" ( verbose )\n" + "\nNOTE: By default this function only works sometimes. This is when the tx is in the mempool\n" + "or there is an unspent output in the utxo for this transaction. To make it always work,\n" + "you need to maintain a transaction index, using the -txindex command line option.\n" + "\nReturn the raw transaction data.\n" + "\nIf verbose=0, returns a string that is serialized, hex-encoded data for 'txid'.\n" + "If verbose is non-zero, returns an Object with information about 'txid'.\n" + + "\nArguments:\n" + "1. \"txid\" (string, required) The transaction id\n" + "2. verbose (numeric, optional, default=0) If 0, return a string, other return a json object\n" + + "\nResult (if verbose is not set or set to 0):\n" + "\"data\" (string) The serialized, hex-encoded data for 'txid'\n" + + "\nResult (if verbose > 0):\n" + "{\n" + " \"hex\" : \"data\", (string) The serialized, hex-encoded data for 'txid'\n" + " \"txid\" : \"id\", (string) The transaction id (same as provided)\n" + " \"version\" : n, (numeric) The version\n" + " \"locktime\" : ttt, (numeric) The lock time\n" + " \"vin\" : [ (array of json objects)\n" + " {\n" + " \"txid\": \"id\", (string) The transaction id\n" + " \"vout\": n, (numeric) \n" + " \"scriptSig\": { (json object) The script\n" + " \"asm\": \"asm\", (string) asm\n" + " \"hex\": \"hex\" (string) hex\n" + " },\n" + " \"sequence\": n (numeric) The script sequence number\n" + " }\n" + " ,...\n" + " ],\n" + " \"vout\" : [ (array of json objects)\n" + " {\n" + " \"value\" : x.xxx, (numeric) The value in btc\n" + " \"n\" : n, (numeric) index\n" + " \"scriptPubKey\" : { (json object)\n" + " \"asm\" : \"asm\", (string) the asm\n" + " \"hex\" : \"hex\", (string) the hex\n" + " \"reqSigs\" : n, (numeric) The required sigs\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " \"addresses\" : [ (json array of string)\n" + " \"bitcoinaddress\" (string) bitcoin address\n" + " ,...\n" + " ]\n" + " }\n" + " }\n" + " ,...\n" + " ],\n" + " \"vjoinsplit\" : [ (array of json objects, only for version >= 2)\n" + " {\n" + " \"vpub_old\" : x.xxx, (numeric) public input value in ZEC\n" + " \"vpub_new\" : x.xxx, (numeric) public output value in ZEC\n" + " \"anchor\" : \"hex\", (string) the anchor\n" + " \"nullifiers\" : [ (json array of string)\n" + " \"hex\" (string) input note nullifier\n" + " ,...\n" + " ],\n" + " \"commitments\" : [ (json array of string)\n" + " \"hex\" (string) output note commitment\n" + " ,...\n" + " ],\n" + " \"onetimePubKey\" : \"hex\", (string) the onetime public key used to encrypt the ciphertexts\n" + " \"randomSeed\" : \"hex\", (string) the random seed\n" + " \"macs\" : [ (json array of string)\n" + " \"hex\" (string) input note MAC\n" + " ,...\n" + " ],\n" + " \"proof\" : \"hex\", (string) the zero-knowledge proof\n" + " \"ciphertexts\" : [ (json array of string)\n" + " \"hex\" (string) output note ciphertext\n" + " ,...\n" + " ]\n" + " }\n" + " ,...\n" + " ],\n" + " \"blockhash\" : \"hash\", (string) the block hash\n" + " \"confirmations\" : n, (numeric) The confirmations\n" + " \"time\" : ttt, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT)\n" + " \"blocktime\" : ttt (numeric) The block time in seconds since epoch (Jan 1 1970 GMT)\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("getrawtransaction", "\"mytxid\"") + + HelpExampleCli("getrawtransaction", "\"mytxid\" 1") + + HelpExampleRpc("getrawtransaction", "\"mytxid\", 1") + ); + + LOCK(cs_main); + + uint256 hash = ParseHashV(params[0], "parameter 1"); + + bool fVerbose = false; + if (params.size() > 1) + fVerbose = (params[1].get_int() != 0); + + CTransaction tx; + uint256 hashBlock; + if (!GetTransaction(hash, tx, hashBlock, true)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); + + string strHex = EncodeHexTx(tx); + + if (!fVerbose) + return strHex; + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("hex", strHex)); + TxToJSON(tx, hashBlock, result); + return result; +} + +int32_t gettxout_scriptPubKeycc(uint8_t *scriptPubKey,int32_t maxsize,uint256 txid,int32_t n) +{ + int32_t i,m; uint8_t *ptr; + LOCK(cs_main); + /*CCoins coins; + for (iter=0; iter<2; iter++) + { + if ( iter == 0 ) + { + LOCK(mempool.cs); + CCoinsViewMemPool view(pcoinsTip,mempool); + if ( view.GetCoins(txid,coins) == 0 ) + { + //fprintf(stderr,"cant get view\n"); + continue; + } + mempool.pruneSpent(txid, coins); // TODO: this should be done by the CCoinsViewMemPool + } + else if ( pcoinsTip->GetCoins(txid,coins) == 0 ) + { + //fprintf(stderr,"cant get pcoinsTip->GetCoins\n"); + continue; + } + if ( n < 0 || (unsigned int)n >= coins.vout.size() || coins.vout[n].IsNull() ) + { + fprintf(stderr,"iter.%d n.%d vs voutsize.%d\n",iter,n,(int32_t)coins.vout.size()); + continue; + } + ptr = (uint8_t *)coins.vout[n].scriptPubKey.data(); + m = coins.vout[n].scriptPubKey.size(); + for (i=0; i setTxids; + uint256 oneTxid; + UniValue txids = params[0].get_array(); + for (size_t idx = 0; idx < txids.size(); idx++) { + const UniValue& txid = txids[idx]; + if (txid.get_str().length() != 64 || !IsHex(txid.get_str())) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid txid ")+txid.get_str()); + uint256 hash(uint256S(txid.get_str())); + if (setTxids.count(hash)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated txid: ")+txid.get_str()); + setTxids.insert(hash); + oneTxid = hash; + } + + LOCK(cs_main); + + CBlockIndex* pblockindex = NULL; + + uint256 hashBlock; + if (params.size() > 1) + { + hashBlock = uint256S(params[1].get_str()); + if (!mapBlockIndex.count(hashBlock)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + pblockindex = mapBlockIndex[hashBlock]; + } else { + CCoins coins; + if (pcoinsTip->GetCoins(oneTxid, coins) && coins.nHeight > 0 && coins.nHeight <= chainActive.Height()) + pblockindex = chainActive[coins.nHeight]; + } + + if (pblockindex == NULL) + { + CTransaction tx; + if (!GetTransaction(oneTxid, tx, hashBlock, false) || hashBlock.IsNull()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not yet in block"); + if (!mapBlockIndex.count(hashBlock)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Transaction index corrupt"); + pblockindex = mapBlockIndex[hashBlock]; + } + + CBlock block; + if(!ReadBlockFromDisk(block, pblockindex)) + throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); + + unsigned int ntxFound = 0; + BOOST_FOREACH(const CTransaction&tx, block.vtx) + if (setTxids.count(tx.GetHash())) + ntxFound++; + if (ntxFound != setTxids.size()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "(Not all) transactions not found in specified block"); + + CDataStream ssMB(SER_NETWORK, PROTOCOL_VERSION); + CMerkleBlock mb(block, setTxids); + ssMB << mb; + std::string strHex = HexStr(ssMB.begin(), ssMB.end()); + return strHex; +} + +UniValue verifytxoutproofcc(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "verifytxoutproof \"proof\"\n" + "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n" + "and throwing an RPC error if the block is not in our best chain\n" + "\nArguments:\n" + "1. \"proof\" (string, required) The hex-encoded proof generated by gettxoutproof\n" + "\nResult:\n" + "[\"txid\"] (array, strings) The txid(s) which the proof commits to, or empty array if the proof is invalid\n" + ); + + CDataStream ssMB(ParseHexV(params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION); + CMerkleBlock merkleBlock; + ssMB >> merkleBlock; + + UniValue res(UniValue::VARR); + + vector vMatch; + if (merkleBlock.txn.ExtractMatches(vMatch) != merkleBlock.header.hashMerkleRoot) + return res; + + LOCK(cs_main); + + if (!mapBlockIndex.count(merkleBlock.header.GetHash()) || !chainActive.Contains(mapBlockIndex[merkleBlock.header.GetHash()])) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); + + BOOST_FOREACH(const uint256& hash, vMatch) + res.push_back(hash.GetHex()); + return res; +} + +UniValue createrawtransactioncc(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 2) + throw runtime_error( + "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] {\"address\":amount,...}\n" + "\nCreate a transaction spending the given inputs and sending to the given addresses.\n" + "Returns hex-encoded raw transaction.\n" + "Note that the transaction's inputs are not signed, and\n" + "it is not stored in the wallet or transmitted to the network.\n" + + "\nArguments:\n" + "1. \"transactions\" (string, required) A json array of json objects\n" + " [\n" + " {\n" + " \"txid\":\"id\", (string, required) The transaction id\n" + " \"vout\":n (numeric, required) The output number\n" + " }\n" + " ,...\n" + " ]\n" + "2. \"addresses\" (string, required) a json object with addresses as keys and amounts as values\n" + " {\n" + " \"address\": x.xxx (numeric, required) The key is the bitcoin address, the value is the btc amount\n" + " ,...\n" + " }\n" + + "\nResult:\n" + "\"transaction\" (string) hex string of the transaction\n" + + "\nExamples\n" + + HelpExampleCli("createrawtransactioncc", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"{\\\"address\\\":0.01}\"") + + HelpExampleRpc("createrawtransactioncc", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"{\\\"address\\\":0.01}\"") + ); + + LOCK(cs_main); + RPCTypeCheck(params, boost::assign::list_of(UniValue::VARR)(UniValue::VARR)); + + UniValue inputs = params[0].get_array(); + UniValue outputs = params[1].get_array(); + + CMutableTransaction rawTx; + + for (size_t idx = 0; idx < inputs.size(); idx++) { + const UniValue& input = inputs[idx]; + const UniValue& o = input.get_obj(); + + uint256 txid = ParseHashO(o, "txid"); + + const UniValue& vout_v = find_value(o, "vout"); + if (!vout_v.isNum()) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key"); + int nOutput = vout_v.get_int(); + if (nOutput < 0) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); + + CTxIn in(COutPoint(txid, nOutput)); + rawTx.vin.push_back(in); + } + + for (size_t idx = 0; idx < outputs.size(); idx++) { + const UniValue& output = outputs[idx].get_obj(); + + const UniValue& condition = find_value(output, "condition"); + std::string encoded = condition.write(); + fprintf(stderr, "%s\n", encoded.c_str()); + char *err = new char[1000]; + CC *cond = cc_conditionFromJSONString(encoded.c_str(), err); + if (NULL == cond) + throw JSONRPCError(RPC_INVALID_PARAMETER, err); + char *condBin = new char[1000]; + size_t binLength = cc_conditionBinary(cond, condBin); + std::vector condVec(condBin, condBin + binLength); + + CScript redeemScript; + redeemScript << ToByteVector(condVec) << OP_CHECKCRYPTOCONDITIONVERIFY; + CAmount nAmount = AmountFromValue(find_value(output, "amount")); + + CTxOut out(nAmount, redeemScript); + rawTx.vout.push_back(out); + } + + /* + set setAddress; + vector addrList = sendTo.getKeys(); + BOOST_FOREACH(const string& name_, addrList) { + CBitcoinAddress address(name_); + if (!address.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_); + + if (setAddress.count(address)) + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_); + setAddress.insert(address); + + CScript scriptPubKey = GetScriptForDestination(address.Get()); + CAmount nAmount = AmountFromValue(sendTo[name_]); + + CTxOut out(nAmount, scriptPubKey); + rawTx.vout.push_back(out); + } + */ + + return EncodeHexTx(rawTx); +} + +UniValue decoderawtransactioncc(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "decoderawtransaction \"hexstring\"\n" + "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n" + + "\nArguments:\n" + "1. \"hex\" (string, required) The transaction hex string\n" + + "\nResult:\n" + "{\n" + " \"txid\" : \"id\", (string) The transaction id\n" + " \"version\" : n, (numeric) The version\n" + " \"locktime\" : ttt, (numeric) The lock time\n" + " \"vin\" : [ (array of json objects)\n" + " {\n" + " \"txid\": \"id\", (string) The transaction id\n" + " \"vout\": n, (numeric) The output number\n" + " \"scriptSig\": { (json object) The script\n" + " \"asm\": \"asm\", (string) asm\n" + " \"hex\": \"hex\" (string) hex\n" + " },\n" + " \"sequence\": n (numeric) The script sequence number\n" + " }\n" + " ,...\n" + " ],\n" + " \"vout\" : [ (array of json objects)\n" + " {\n" + " \"value\" : x.xxx, (numeric) The value in btc\n" + " \"n\" : n, (numeric) index\n" + " \"scriptPubKey\" : { (json object)\n" + " \"asm\" : \"asm\", (string) the asm\n" + " \"hex\" : \"hex\", (string) the hex\n" + " \"reqSigs\" : n, (numeric) The required sigs\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " \"addresses\" : [ (json array of string)\n" + " \"12tvKAXCxZjSmdNbao16dKXC8tRWfcF5oc\" (string) bitcoin address\n" + " ,...\n" + " ]\n" + " }\n" + " }\n" + " ,...\n" + " ],\n" + " \"vjoinsplit\" : [ (array of json objects, only for version >= 2)\n" + " {\n" + " \"vpub_old\" : x.xxx, (numeric) public input value in ZEC\n" + " \"vpub_new\" : x.xxx, (numeric) public output value in ZEC\n" + " \"anchor\" : \"hex\", (string) the anchor\n" + " \"nullifiers\" : [ (json array of string)\n" + " \"hex\" (string) input note nullifier\n" + " ,...\n" + " ],\n" + " \"commitments\" : [ (json array of string)\n" + " \"hex\" (string) output note commitment\n" + " ,...\n" + " ],\n" + " \"onetimePubKey\" : \"hex\", (string) the onetime public key used to encrypt the ciphertexts\n" + " \"randomSeed\" : \"hex\", (string) the random seed\n" + " \"macs\" : [ (json array of string)\n" + " \"hex\" (string) input note MAC\n" + " ,...\n" + " ],\n" + " \"proof\" : \"hex\", (string) the zero-knowledge proof\n" + " \"ciphertexts\" : [ (json array of string)\n" + " \"hex\" (string) output note ciphertext\n" + " ,...\n" + " ]\n" + " }\n" + " ,...\n" + " ],\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("decoderawtransaction", "\"hexstring\"") + + HelpExampleRpc("decoderawtransaction", "\"hexstring\"") + ); + + LOCK(cs_main); + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); + + CTransaction tx; + + if (!DecodeHexTx(tx, params[0].get_str())) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + + UniValue result(UniValue::VOBJ); + TxToJSON(tx, uint256(), result); + + return result; +} + +UniValue decodescriptcc(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() != 1) + throw runtime_error( + "decodescript \"hex\"\n" + "\nDecode a hex-encoded script.\n" + "\nArguments:\n" + "1. \"hex\" (string) the hex encoded script\n" + "\nResult:\n" + "{\n" + " \"asm\":\"asm\", (string) Script public key\n" + " \"hex\":\"hex\", (string) hex encoded public key\n" + " \"type\":\"type\", (string) The output type\n" + " \"reqSigs\": n, (numeric) The required signatures\n" + " \"addresses\": [ (json array of string)\n" + " \"address\" (string) bitcoin address\n" + " ,...\n" + " ],\n" + " \"p2sh\",\"address\" (string) script address\n" + "}\n" + "\nExamples:\n" + + HelpExampleCli("decodescript", "\"hexstring\"") + + HelpExampleRpc("decodescript", "\"hexstring\"") + ); + + LOCK(cs_main); + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)); + + UniValue r(UniValue::VOBJ); + CScript script; + if (params[0].get_str().size() > 0){ + vector scriptData(ParseHexV(params[0], "argument")); + script = CScript(scriptData.begin(), scriptData.end()); + } else { + // Empty scripts are valid + } + ScriptPubKeyToJSON(script, r, false); + + r.push_back(Pair("p2sh", CBitcoinAddress(CScriptID(script)).ToString())); + return r; +} + +/** Pushes a JSON object for script verification or signing errors to vErrorsRet. */ +static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::string& strMessage) {} + + +UniValue signrawtransactioncc(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 4) + throw runtime_error( + "signrawtransactioncc \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] [\"privatekey1\",...] sighashtype )\n" + "\nSign cryptocondition inputs for raw transaction (serialized, hex-encoded).\n" + "The second optional argument (may be null) is an array of previous transaction outputs that\n" + "this transaction depends on but may not yet be in the block chain.\n" + "The third optional argument (may be null) is an array of base58-encoded private\n" + "keys that, if given, will be the only keys used to sign the transaction.\n" +#ifdef ENABLE_WALLET + + HelpRequiringPassphrase() + "\n" +#endif + + "\nArguments:\n" + "1. \"hexstring\" (string, required) The transaction hex string\n" + /* "2. \"prevtxs\" (string, optional) An json array of previous dependent transaction outputs\n" + " [ (json array of json objects, or 'null' if none provided)\n" + " {\n" + " \"txid\":\"id\", (string, required) The transaction id\n" + " \"vout\":n, (numeric, required) The output number\n" + " \"scriptPubKey\": \"hex\", (string, required) script key\n" + " \"redeemScript\": \"hex\" (string, required for P2SH) redeem script\n" + " }\n" + " ,...\n" + " ]\n" */ + "3. \"privatekeys\" (string, optional) A json array of base58-encoded private keys for signing\n" + " [ (json array of strings, or 'null' if none provided)\n" + " \"privatekey\" (string) private key in base58-encoding\n" + " ,...\n" + " ]\n" + /* "4. \"sighashtype\" (string, optional, default=ALL) The signature hash type. Must be one of\n" + " \"ALL\"\n" + " \"NONE\"\n" + " \"SINGLE\"\n" + " \"ALL|ANYONECANPAY\"\n" + " \"NONE|ANYONECANPAY\"\n" + " \"SINGLE|ANYONECANPAY\"\n" */ + + "\nResult:\n" + "{\n" + " \"hex\" : \"value\", (string) The hex-encoded raw transaction with signature(s)\n" + " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" + " \"errors\" : [ (json array of objects) Script verification errors (if there are any)\n" + " {\n" + " \"txid\" : \"hash\", (string) The hash of the referenced, previous transaction\n" + " \"vout\" : n, (numeric) The index of the output to spent and used as input\n" + " \"scriptSig\" : \"hex\", (string) The hex-encoded signature script\n" + " \"sequence\" : n, (numeric) Script sequence number\n" + " \"error\" : \"text\" (string) Verification or signing error related to the input\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("signrawtransactioncc", "\"myhex\"") + + HelpExampleRpc("signrawtransactioncc", "\"myhex\"") + ); + +#ifdef ENABLE_WALLET + LOCK2(cs_main, pwalletMain ? &pwalletMain->cs_wallet : NULL); +#else + LOCK(cs_main); +#endif + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VSTR), true); + + vector txData(ParseHexV(params[0], "argument 1")); + CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); + vector txVariants; + while (!ssData.empty()) { + //try { + CMutableTransaction tx; + ssData >> tx; + txVariants.push_back(tx); + //} + //catch (const std::exception&) { + //throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + //} + } + + if (txVariants.empty()) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transaction"); + + // mergedTx will end up with all the signatures; it + // starts as a clone of the rawtx: + CMutableTransaction mergedTx(txVariants[0]); + + // Fetch previous transactions (inputs): + CCoinsView viewDummy; + CCoinsViewCache view(&viewDummy); + { + LOCK(mempool.cs); + CCoinsViewCache &viewChain = *pcoinsTip; + CCoinsViewMemPool viewMempool(&viewChain, mempool); + view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view + + BOOST_FOREACH(const CTxIn& txin, mergedTx.vin) { + const uint256& prevHash = txin.prevout.hash; + CCoins coins; + view.AccessCoins(prevHash); // this is certainly allowed to fail + } + + view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long + } + + bool fGivenKeys = false; + CBasicKeyStore tempKeystore; + if (params.size() > 2 && !params[2].isNull()) { + fGivenKeys = true; + UniValue keys = params[2].get_array(); + for (size_t idx = 0; idx < keys.size(); idx++) { + UniValue k = keys[idx]; + CBitcoinSecret vchSecret; + bool fGood = vchSecret.SetString(k.get_str()); + if (!fGood) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); + CKey key = vchSecret.GetKey(); + if (!key.IsValid()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + tempKeystore.AddKey(key); + } + } +#ifdef ENABLE_WALLET + else if (pwalletMain) + EnsureWalletIsUnlocked(); +#endif + + // Add previous txouts given in the RPC call: + /* + if (params.size() > 1 && !params[1].isNull()) { + UniValue prevTxs = params[1].get_array(); + for (size_t idx = 0; idx < prevTxs.size(); idx++) { + const UniValue& p = prevTxs[idx]; + if (!p.isObject()) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "expected object with {\"txid'\",\"vout\",\"scriptPubKey\"}"); + + UniValue prevOut = p.get_obj(); + + RPCTypeCheckObj(prevOut, boost::assign::map_list_of("txid", UniValue::VSTR)("vout", UniValue::VNUM)("scriptPubKey", UniValue::VSTR)); + + uint256 txid = ParseHashO(prevOut, "txid"); + + int nOut = find_value(prevOut, "vout").get_int(); + if (nOut < 0) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "vout must be positive"); + + vector pkData(ParseHexO(prevOut, "scriptPubKey")); + CScript scriptPubKey(pkData.begin(), pkData.end()); + + { + CCoinsModifier coins = view.ModifyCoins(txid); + if (coins->IsAvailable(nOut) && coins->vout[nOut].scriptPubKey != scriptPubKey) { + string err("Previous output scriptPubKey mismatch:\n"); + err = err + coins->vout[nOut].scriptPubKey.ToString() + "\nvs:\n"+ + scriptPubKey.ToString(); + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err); + } + if ((unsigned int)nOut >= coins->vout.size()) + coins->vout.resize(nOut+1); + coins->vout[nOut].scriptPubKey = scriptPubKey; + coins->vout[nOut].nValue = 0; // we don't know the actual output value + } + + // if redeemScript given and not using the local wallet (private keys + // given), add redeemScript to the tempKeystore so it can be signed: + if (fGivenKeys && scriptPubKey.IsPayToScriptHash()) { + RPCTypeCheckObj(prevOut, boost::assign::map_list_of("txid", UniValue::VSTR)("vout", UniValue::VNUM)("scriptPubKey", UniValue::VSTR)("redeemScript",UniValue::VSTR)); + UniValue v = find_value(prevOut, "redeemScript"); + if (!v.isNull()) { + vector rsData(ParseHexV(v, "redeemScript")); + CScript redeemScript(rsData.begin(), rsData.end()); + tempKeystore.AddCScript(redeemScript); + } + } + } + } + */ + +#ifdef ENABLE_WALLET + const CKeyStore& keystore = ((fGivenKeys || !pwalletMain) ? tempKeystore : *pwalletMain); +#else + const CKeyStore& keystore = tempKeystore; +#endif + + int nHashType = SIGHASH_ALL; + /* + if (params.size() > 3 && !params[3].isNull()) { + static map mapSigHashValues = + boost::assign::map_list_of + (string("ALL"), int(SIGHASH_ALL)) + (string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)) + (string("NONE"), int(SIGHASH_NONE)) + (string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)) + (string("SINGLE"), int(SIGHASH_SINGLE)) + (string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)) + ; + string strHashType = params[3].get_str(); + if (mapSigHashValues.count(strHashType)) + nHashType = mapSigHashValues[strHashType]; + else + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param"); + } + */ + + bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); + + // Script verification errors + UniValue vErrors(UniValue::VARR); + + // Sign what we can: + for (unsigned int i = 0; i < mergedTx.vin.size(); i++) { + CTxIn& txin = mergedTx.vin[i]; + const CCoins* coins = view.AccessCoins(txin.prevout.hash); + if (coins == NULL || !coins->IsAvailable(txin.prevout.n)) { + TxInErrorToJSON(txin, vErrors, "Input not found or already spent"); + continue; + } + const CScript& prevPubKey = coins->vout[txin.prevout.n].scriptPubKey; + + txin.scriptSig.clear(); + // Only sign SIGHASH_SINGLE if there's a corresponding output: + if (!fHashSingle || (i < mergedTx.vout.size())) + SignSignature(keystore, prevPubKey, mergedTx, i, nHashType); + + // ... and merge in other signatures: + BOOST_FOREACH(const CMutableTransaction& txv, txVariants) { + txin.scriptSig = CombineSignatures(prevPubKey, mergedTx, i, txin.scriptSig, txv.vin[i].scriptSig); + } + ScriptError serror = SCRIPT_ERR_OK; + if (!VerifyScript(txin.scriptSig, prevPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&mergedTx, i), &serror)) { + TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror)); + } + } + bool fComplete = vErrors.empty(); + + UniValue result(UniValue::VOBJ); + result.push_back(Pair("hex", EncodeHexTx(mergedTx))); + result.push_back(Pair("complete", fComplete)); + if (!vErrors.empty()) { + result.push_back(Pair("errors", vErrors)); + } + + return result; +} + +UniValue sendrawtransactioncc(const UniValue& params, bool fHelp) +{ + if (fHelp || params.size() < 1 || params.size() > 2) + throw runtime_error( + "sendrawtransaction \"hexstring\" ( allowhighfees )\n" + "\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n" + "\nAlso see createrawtransaction and signrawtransaction calls.\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of the raw transaction)\n" + "2. allowhighfees (boolean, optional, default=false) Allow high fees\n" + "\nResult:\n" + "\"hex\" (string) The transaction hash in hex\n" + "\nExamples:\n" + "\nCreate a transaction\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") + + "Sign the transaction, and get back the hex\n" + + HelpExampleCli("signrawtransaction", "\"myhex\"") + + "\nSend the transaction (signed hex)\n" + + HelpExampleCli("sendrawtransaction", "\"signedhex\"") + + "\nAs a json rpc call\n" + + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") + ); + + LOCK(cs_main); + RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VBOOL)); + + // parse hex string from parameter + CTransaction tx; + if (!DecodeHexTx(tx, params[0].get_str())) + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + uint256 hashTx = tx.GetHash(); + + bool fOverrideFees = false; + if (params.size() > 1) + fOverrideFees = params[1].get_bool(); + + CCoinsViewCache &view = *pcoinsTip; + const CCoins* existingCoins = view.AccessCoins(hashTx); + bool fHaveMempool = mempool.exists(hashTx); + bool fHaveChain = existingCoins && existingCoins->nHeight < 1000000000; + if (!fHaveMempool && !fHaveChain) { + // push to local node and sync with wallets + CValidationState state; + bool fMissingInputs; + if (!AcceptToMemoryPool(mempool, state, tx, false, &fMissingInputs, !fOverrideFees)) { + if (state.IsInvalid()) { + throw JSONRPCError(RPC_TRANSACTION_REJECTED, strprintf("%i: %s", state.GetRejectCode(), state.GetRejectReason())); + } else { + if (fMissingInputs) { + throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs"); + } + throw JSONRPCError(RPC_TRANSACTION_ERROR, state.GetRejectReason()); + } + } + } else if (fHaveChain) { + throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain"); + } + RelayTransaction(tx); + + return hashTx.GetHex(); +} diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 518ba4693..06c08a3be 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -325,6 +325,8 @@ static const CRPCCommand vRPCCommands[] = { "rawtransactions", "getrawtransaction", &getrawtransaction, true }, { "rawtransactions", "sendrawtransaction", &sendrawtransaction, false }, { "rawtransactions", "signrawtransaction", &signrawtransaction, false }, /* uses wallet if enabled */ + { "rawtransactions", "createrawtransactioncc", &createrawtransactioncc, true }, + { "rawtransactions", "signrawtransactioncc", &signrawtransactioncc, false }, /* uses wallet if enabled */ #ifdef ENABLE_WALLET { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false }, #endif diff --git a/src/rpcserver.h b/src/rpcserver.h index ecf7b0573..611abd05f 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -262,6 +262,9 @@ extern UniValue sendrawtransaction(const UniValue& params, bool fHelp); extern UniValue gettxoutproof(const UniValue& params, bool fHelp); extern UniValue verifytxoutproof(const UniValue& params, bool fHelp); +extern UniValue createrawtransactioncc(const UniValue& params, bool fHelp); // in rpccryptocondition.cpp +extern UniValue signrawtransactioncc(const UniValue& params, bool fHelp); + extern UniValue getblockcount(const UniValue& params, bool fHelp); // in rpcblockchain.cpp extern UniValue getbestblockhash(const UniValue& params, bool fHelp); extern UniValue getdifficulty(const UniValue& params, bool fHelp);