diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/core_io.h | 1 | ||||
-rw-r--r-- | src/core_read.cpp | 23 | ||||
-rw-r--r-- | src/rpc/client.cpp | 8 | ||||
-rw-r--r-- | src/rpc/rawtransaction.cpp | 18 | ||||
-rw-r--r-- | src/script/sign.cpp | 10 | ||||
-rw-r--r-- | src/script/sign.h | 11 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 328 | ||||
-rw-r--r-- | src/wallet/rpcwallet.h | 3 |
8 files changed, 385 insertions, 17 deletions
diff --git a/src/core_io.h b/src/core_io.h index d364bca8b1..ebddcc2a04 100644 --- a/src/core_io.h +++ b/src/core_io.h @@ -26,6 +26,7 @@ bool DecodeHexBlk(CBlock&, const std::string& strHexBlk); uint256 ParseHashStr(const std::string&, const std::string& strName); std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName); bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error); +int ParseSighashString(const UniValue& sighash); // core_write.cpp UniValue ValueFromAmount(const CAmount& amount); diff --git a/src/core_read.cpp b/src/core_read.cpp index 7ee3edc1f3..067e1b91bd 100644 --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -197,3 +197,26 @@ std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strN throw std::runtime_error(strName + " must be hexadecimal string (not '" + strHex + "')"); return ParseHex(strHex); } + +int ParseSighashString(const UniValue& sighash) +{ + int hash_type = SIGHASH_ALL; + if (!sighash.isNull()) { + static std::map<std::string, int> map_sighash_values = { + {std::string("ALL"), int(SIGHASH_ALL)}, + {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, + {std::string("NONE"), int(SIGHASH_NONE)}, + {std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)}, + {std::string("SINGLE"), int(SIGHASH_SINGLE)}, + {std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)}, + }; + std::string strHashType = sighash.get_str(); + const auto& it = map_sighash_values.find(strHashType); + if (it != map_sighash_values.end()) { + hash_type = it->second; + } else { + throw std::runtime_error(strHashType + " is not a valid sighash parameter."); + } + } + return hash_type; +} diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index d97165b301..96e0a96ab5 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -109,6 +109,14 @@ static const CRPCConvertParam vRPCConvertParams[] = { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 2, "iswitness" }, + { "walletcreatefundedpsbt", 0, "inputs" }, + { "walletcreatefundedpsbt", 1, "outputs" }, + { "walletcreatefundedpsbt", 2, "locktime" }, + { "walletcreatefundedpsbt", 3, "replaceable" }, + { "walletcreatefundedpsbt", 4, "options" }, + { "walletcreatefundedpsbt", 5, "bip32derivs" }, + { "walletprocesspsbt", 1, "sign" }, + { "walletprocesspsbt", 3, "bip32derivs" }, { "createpsbt", 0, "inputs" }, { "createpsbt", 1, "outputs" }, { "createpsbt", 2, "locktime" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 797c8b6973..82dfe4d561 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -840,23 +840,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } } - int nHashType = SIGHASH_ALL; - if (!hashType.isNull()) { - static std::map<std::string, int> mapSigHashValues = { - {std::string("ALL"), int(SIGHASH_ALL)}, - {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, - {std::string("NONE"), int(SIGHASH_NONE)}, - {std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)}, - {std::string("SINGLE"), int(SIGHASH_SINGLE)}, - {std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)}, - }; - std::string strHashType = hashType.get_str(); - if (mapSigHashValues.count(strHashType)) { - nHashType = mapSigHashValues[strHashType]; - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param"); - } - } + int nHashType = ParseSighashString(hashType); bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); diff --git a/src/script/sign.cpp b/src/script/sign.cpp index f9907f5e00..f1ac1f411a 100644 --- a/src/script/sign.cpp +++ b/src/script/sign.cpp @@ -614,3 +614,13 @@ void PSBTOutput::Merge(const PSBTOutput& output) if (redeem_script.empty() && !output.redeem_script.empty()) redeem_script = output.redeem_script; if (witness_script.empty() && !output.witness_script.empty()) witness_script = output.witness_script; } + +bool PublicOnlySigningProvider::GetCScript(const CScriptID &scriptid, CScript& script) const +{ + return m_provider->GetCScript(scriptid, script); +} + +bool PublicOnlySigningProvider::GetPubKey(const CKeyID &address, CPubKey& pubkey) const +{ + return m_provider->GetPubKey(address, pubkey); +} diff --git a/src/script/sign.h b/src/script/sign.h index 9f0382e257..e3a6196b28 100644 --- a/src/script/sign.h +++ b/src/script/sign.h @@ -32,6 +32,17 @@ public: extern const SigningProvider& DUMMY_SIGNING_PROVIDER; +class PublicOnlySigningProvider : public SigningProvider +{ +private: + const SigningProvider* m_provider; + +public: + PublicOnlySigningProvider(const SigningProvider* provider) : m_provider(provider) {} + bool GetCScript(const CScriptID &scriptid, CScript& script) const; + bool GetPubKey(const CKeyID &address, CPubKey& pubkey) const; +}; + /** Interface for signature creators. */ class BaseSignatureCreator { public: diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7cd9f4cfd3..6d2499ee8a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4390,6 +4390,332 @@ UniValue sethdseed(const JSONRPCRequest& request) return NullUniValue; } +bool ParseHDKeypath(std::string keypath_str, std::vector<uint32_t>& keypath) +{ + std::stringstream ss(keypath_str); + std::string item; + bool first = true; + while (std::getline(ss, item, '/')) { + if (item.compare("m") == 0) { + if (first) { + first = false; + continue; + } + return false; + } + // Finds whether it is hardened + uint32_t path = 0; + size_t pos = item.find("'"); + if (pos != std::string::npos) { + // The hardened tick can only be in the last index of the string + if (pos != item.size() - 1) { + return false; + } + path |= 0x80000000; + item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick + } + + // Ensure this is only numbers + if (item.find_first_not_of( "0123456789" ) != std::string::npos) { + return false; + } + uint32_t number; + ParseUInt32(item, &number); + path |= number; + + keypath.push_back(path); + first = false; + } + return true; +} + +void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths) +{ + CPubKey vchPubKey; + if (!pwallet->GetPubKey(keyID, vchPubKey)) { + return; + } + CKeyMetadata meta; + auto it = pwallet->mapKeyMetadata.find(keyID); + if (it != pwallet->mapKeyMetadata.end()) { + meta = it->second; + } + std::vector<uint32_t> keypath; + if (!meta.hdKeypath.empty()) { + if (!ParseHDKeypath(meta.hdKeypath, keypath)) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Internal keypath is broken"); + } + // Get the proper master key id + CKey key; + pwallet->GetKey(meta.hd_seed_id, key); + CExtKey masterKey; + masterKey.SetSeed(key.begin(), key.size()); + // Add to map + keypath.insert(keypath.begin(), ReadLE32(masterKey.key.GetPubKey().GetID().begin())); + } else { // Single pubkeys get the master fingerprint of themselves + keypath.insert(keypath.begin(), ReadLE32(vchPubKey.GetID().begin())); + } + hd_keypaths.emplace(vchPubKey, keypath); +} + +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs) +{ + LOCK(pwallet->cs_wallet); + // Get all of the previous transactions + bool complete = true; + for (unsigned int i = 0; i < txConst->vin.size(); ++i) { + const CTxIn& txin = txConst->vin[i]; + PSBTInput& input = psbtx.inputs.at(i); + + // If we don't know about this input, skip it and let someone else deal with it + const uint256& txhash = txin.prevout.hash; + const auto& it = pwallet->mapWallet.find(txhash); + if (it != pwallet->mapWallet.end()) { + const CWalletTx& wtx = it->second; + CTxOut utxo = wtx.tx->vout[txin.prevout.n]; + input.non_witness_utxo = wtx.tx; + input.witness_utxo = utxo; + } + + // Get the Sighash type + if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match."); + } + + SignatureData sigdata; + if (sign) { + complete &= SignPSBTInput(*pwallet, *psbtx.tx, input, sigdata, i, sighash_type); + } else { + complete &= SignPSBTInput(PublicOnlySigningProvider(pwallet), *psbtx.tx, input, sigdata, i, sighash_type); + } + + // Drop the unnecessary UTXO + if (sigdata.witness) { + input.non_witness_utxo = nullptr; + } else { + input.witness_utxo.SetNull(); + } + + // Get public key paths + if (bip32derivs) { + for (const auto& pubkey_it : sigdata.misc_pubkeys) { + AddKeypathToMap(pwallet, pubkey_it.first, input.hd_keypaths); + } + } + } + + // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change + for (unsigned int i = 0; i < txConst->vout.size(); ++i) { + const CTxOut& out = txConst->vout.at(i); + PSBTOutput& psbt_out = psbtx.outputs.at(i); + + // Dummy tx so we can use ProduceSignature to get stuff out + CMutableTransaction dummy_tx; + dummy_tx.vin.push_back(CTxIn()); + dummy_tx.vout.push_back(CTxOut()); + + // Fill a SignatureData with output info + SignatureData sigdata; + psbt_out.FillSignatureData(sigdata); + + MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1); + ProduceSignature(*pwallet, creator, out.scriptPubKey, sigdata); + psbt_out.FromSignatureData(sigdata); + + // Get public key paths + if (bip32derivs) { + for (const auto& pubkey_it : sigdata.misc_pubkeys) { + AddKeypathToMap(pwallet, pubkey_it.first, psbt_out.hd_keypaths); + } + } + } + return complete; +} + +UniValue walletprocesspsbt(const JSONRPCRequest& request) +{ + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) + throw std::runtime_error( + "walletprocesspsbt \"psbt\" ( sign \"sighashtype\" bip32derivs )\n" + "\nUpdate a PSBT with input information from our wallet and then sign inputs\n" + "that we can sign for.\n" + + HelpRequiringPassphrase(pwallet) + "\n" + + "\nArguments:\n" + "1. \"psbt\" (string, required) The transaction base64 string\n" + "2. sign (boolean, optional, default=true) Also sign the transaction when updating\n" + "3. \"sighashtype\" (string, optional, default=ALL) The signature hash type to sign with if not specified by the PSBT. Must be one of\n" + " \"ALL\"\n" + " \"NONE\"\n" + " \"SINGLE\"\n" + " \"ALL|ANYONECANPAY\"\n" + " \"NONE|ANYONECANPAY\"\n" + " \"SINGLE|ANYONECANPAY\"\n" + "4. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n" + + "\nResult:\n" + "{\n" + " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n" + " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("walletprocesspsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); + + // Unserialize the transaction + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + + // Get the sighash type + int nHashType = ParseSighashString(request.params[2]); + + // Use CTransaction for the constant parts of the + // transaction to avoid rehashing. + const CTransaction txConst(*psbtx.tx); + + // Fill transaction with our data and also sign + bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); + bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool(); + bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs); + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + result.push_back(Pair("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()))); + result.push_back(Pair("complete", complete)); + + return result; +} + +UniValue walletcreatefundedpsbt(const JSONRPCRequest& request) +{ + std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); + CWallet* const pwallet = wallet.get(); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + if (request.fHelp || request.params.size() < 2 || request.params.size() > 6) + throw std::runtime_error( + "walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n" + "\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n" + "Implements the Creator and Updater roles.\n" + "\nArguments:\n" + "1. \"inputs\" (array, 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" + " \"sequence\":n (numeric, optional) The sequence number\n" + " } \n" + " ,...\n" + " ]\n" + "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n" + " [\n" + " {\n" + " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n" + " },\n" + " {\n" + " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n" + " }\n" + " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n" + " accepted as second parameter.\n" + " ]\n" + "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n" + "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n" + "5. options (object, optional)\n" + " {\n" + " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n" + " \"changePosition\" (numeric, optional, default random) The index of the change output\n" + " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n" + " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n" + " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n" + " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n" + " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n" + " The fee will be equally deducted from the amount of each specified output.\n" + " The outputs are specified by their zero-based index, before any change output is added.\n" + " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n" + " If no outputs are specified here, the sender pays the fee.\n" + " [vout_index,...]\n" + " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees\n" + " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" + " }\n" + "6. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n" + "\nResult:\n" + "{\n" + " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n" + " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" + " \"changepos\": n (numeric) The position of the added change output, or -1\n" + "}\n" + "\nExamples:\n" + "\nCreate a transaction with no inputs\n" + + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + ); + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL, + UniValue::VOBJ + }, true + ); + + CAmount fee; + int change_position; + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + FundTransaction(pwallet, rawTx, fee, change_position, request.params[4]); + + // Make a blank psbt + PartiallySignedTransaction psbtx; + psbtx.tx = rawTx; + for (unsigned int i = 0; i < rawTx.vin.size(); ++i) { + psbtx.inputs.push_back(PSBTInput()); + } + for (unsigned int i = 0; i < rawTx.vout.size(); ++i) { + psbtx.outputs.push_back(PSBTOutput()); + } + + // Use CTransaction for the constant parts of the + // transaction to avoid rehashing. + const CTransaction txConst(*psbtx.tx); + + // Fill transaction with out data but don't sign + bool bip32derivs = request.params[5].isNull() ? false : request.params[5].get_bool(); + FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs); + + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + + UniValue result(UniValue::VOBJ); + result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())); + result.pushKV("fee", ValueFromAmount(fee)); + result.pushKV("changepos", change_position); + return result; +} + extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue importprivkey(const JSONRPCRequest& request); @@ -4406,6 +4732,8 @@ static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- { "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} }, + { "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} }, + { "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","replaceable","options","bip32derivs"} }, { "hidden", "resendwallettransactions", &resendwallettransactions, {} }, { "wallet", "abandontransaction", &abandontransaction, {"txid"} }, { "wallet", "abortrescan", &abortrescan, {} }, diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h index b841f3e424..64556b5824 100644 --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -11,6 +11,8 @@ class CRPCTable; class CWallet; class JSONRPCRequest; class UniValue; +struct PartiallySignedTransaction; +class CTransaction; void RegisterWalletRPCCommands(CRPCTable &t); @@ -28,4 +30,5 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException); UniValue getaddressinfo(const JSONRPCRequest& request); UniValue signrawtransactionwithwallet(const JSONRPCRequest& request); +bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type = 1, bool sign = true, bool bip32derivs = false); #endif //BITCOIN_WALLET_RPCWALLET_H |