Changeset View
Changeset View
Standalone View
Standalone View
src/wallet/rpcwallet.cpp
Show First 20 Lines • Show All 4,734 Lines • ▼ Show 20 Lines | if (request.fHelp || request.params.size() != 1) { | ||||
"yours or not\n" | "yours or not\n" | ||||
" \"iswatchonly\" : true|false, (boolean) If the address is " | " \"iswatchonly\" : true|false, (boolean) If the address is " | ||||
"watchonly\n" | "watchonly\n" | ||||
" \"isscript\" : true|false, (boolean) If the key is a " | " \"isscript\" : true|false, (boolean) If the key is a " | ||||
"script\n" | "script\n" | ||||
" \"script\" : \"type\" (string, optional) The output " | " \"script\" : \"type\" (string, optional) The output " | ||||
"script type. Only if \"isscript\" is true and the redeemscript is " | "script type. Only if \"isscript\" is true and the redeemscript is " | ||||
"known. Possible types: nonstandard, pubkey, pubkeyhash, " | "known. Possible types: nonstandard, pubkey, pubkeyhash, " | ||||
"scripthash, multisig, nulldata, witness_v0_keyhash, " | "scripthash, multisig, nulldata\n" | ||||
"witness_v0_scripthash, witness_unknown\n" | |||||
" \"hex\" : \"hex\", (string, optional) The " | " \"hex\" : \"hex\", (string, optional) The " | ||||
"redeemscript for the p2sh address\n" | "redeemscript for the p2sh address\n" | ||||
" \"pubkeys\" (string, optional) Array of " | " \"pubkeys\" (string, optional) Array of " | ||||
"pubkeys associated with the known redeemscript (only if " | "pubkeys associated with the known redeemscript (only if " | ||||
"\"script\" is \"multisig\")\n" | "\"script\" is \"multisig\")\n" | ||||
" [\n" | " [\n" | ||||
" \"pubkey\"\n" | " \"pubkey\"\n" | ||||
" ,...\n" | " ,...\n" | ||||
▲ Show 20 Lines • Show All 302 Lines • ▼ Show 20 Lines | static UniValue sethdseed(const Config &config, const JSONRPCRequest &request) { | ||||
pwallet->SetHDSeed(master_pub_key); | pwallet->SetHDSeed(master_pub_key); | ||||
if (flush_key_pool) { | if (flush_key_pool) { | ||||
pwallet->NewKeyPool(); | pwallet->NewKeyPool(); | ||||
} | } | ||||
return NullUniValue; | return NullUniValue; | ||||
} | } | ||||
static 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; | |||||
// Drop the last character which is the hardened tick | |||||
item = item.substr(0, item.size() - 1); | |||||
} | |||||
// 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; | |||||
} | |||||
static 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, SigHashType sighash_type, bool sign, | |||||
bool bip32derivs) { | |||||
LOCK(pwallet->cs_wallet); | |||||
// Get all of the previous transactions | |||||
bool complete = true; | |||||
for (size_t 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 TxId &txid = txin.prevout.GetTxId(); | |||||
const auto &it = pwallet->mapWallet.find(txid); | |||||
if (it != pwallet->mapWallet.end()) { | |||||
const CWalletTx &wtx = it->second; | |||||
CTxOut utxo = wtx.tx->vout[txin.prevout.GetN()]; | |||||
input.utxo = utxo; | |||||
} | |||||
// Get the Sighash type | |||||
if (sign && input.sighash_type.getRawSigHashType() > 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); | |||||
} | |||||
// 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, SigHashType().withForkId()); | |||||
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; | |||||
} | |||||
static UniValue walletprocesspsbt(const Config &config, | |||||
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|FORKID) The signature hash type to sign with if not " | |||||
"specified by the PSBT. Must be one of\n" | |||||
" \"ALL|FORKID\"\n" | |||||
" \"NONE|FORKID\"\n" | |||||
" \"SINGLE|FORKID\"\n" | |||||
" \"ALL|FORKID|ANYONECANPAY\"\n" | |||||
" \"NONE|FORKID|ANYONECANPAY\"\n" | |||||
" \"SINGLE|FORKID|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 | |||||
SigHashType nHashType = ParseSighashString(request.params[2]); | |||||
if (!nHashType.hasForkId()) { | |||||
throw JSONRPCError(RPC_INVALID_PARAMETER, | |||||
"Signature must use SIGHASH_FORKID"); | |||||
} | |||||
// 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.pushKV("psbt", EncodeBase64((uint8_t *)ssTx.data(), ssTx.size())); | |||||
result.pushKV("complete", complete); | |||||
return result; | |||||
} | |||||
static UniValue walletcreatefundedpsbt(const Config &config, | |||||
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() > 5) { | |||||
throw std::runtime_error( | |||||
"walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] " | |||||
"[{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( " | |||||
"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. 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" | |||||
" }\n" | |||||
"5. 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::VOBJ}, | |||||
true); | |||||
Amount fee; | |||||
int change_position; | |||||
CMutableTransaction rawTx = | |||||
ConstructTransaction(config.GetChainParams(), request.params[0], | |||||
request.params[1], request.params[2]); | |||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]); | |||||
// Make a blank psbt | |||||
PartiallySignedTransaction psbtx; | |||||
psbtx.tx = rawTx; | |||||
for (size_t i = 0; i < rawTx.vin.size(); ++i) { | |||||
psbtx.inputs.push_back(PSBTInput()); | |||||
} | |||||
for (size_t 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[4].isNull() ? false : request.params[4].get_bool(); | |||||
FillPSBT(pwallet, psbtx, &txConst, SigHashType().withForkId(), false, | |||||
bip32derivs); | |||||
// Serialize the PSBT | |||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | |||||
ssTx << psbtx; | |||||
UniValue result(UniValue::VOBJ); | |||||
result.pushKV("psbt", EncodeBase64((uint8_t *)ssTx.data(), ssTx.size())); | |||||
result.pushKV("fee", ValueFromAmount(fee)); | |||||
result.pushKV("changepos", change_position); | |||||
return result; | |||||
} | |||||
// clang-format off | // clang-format off | ||||
static const ContextFreeRPCCommand commands[] = { | static const ContextFreeRPCCommand commands[] = { | ||||
// category name actor (function) argNames | // category name actor (function) argNames | ||||
// ------------------- ------------------------ ---------------------- ---------- | // ------------------- ------------------------ ---------------------- ---------- | ||||
{ "rawtransactions", "fundrawtransaction", fundrawtransaction, {"hexstring","options"} }, | { "rawtransactions", "fundrawtransaction", fundrawtransaction, {"hexstring","options"} }, | ||||
{ "wallet", "walletprocesspsbt", walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} }, | |||||
{ "wallet", "walletcreatefundedpsbt", walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} }, | |||||
{ "hidden", "resendwallettransactions", resendwallettransactions, {} }, | { "hidden", "resendwallettransactions", resendwallettransactions, {} }, | ||||
{ "wallet", "abandontransaction", abandontransaction, {"txid"} }, | { "wallet", "abandontransaction", abandontransaction, {"txid"} }, | ||||
{ "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} }, | { "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} }, | ||||
{ "wallet", "backupwallet", backupwallet, {"destination"} }, | { "wallet", "backupwallet", backupwallet, {"destination"} }, | ||||
{ "wallet", "createwallet", createwallet, {"wallet_name"} }, | { "wallet", "createwallet", createwallet, {"wallet_name"} }, | ||||
{ "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, | { "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, | ||||
{ "wallet", "getaddressinfo", getaddressinfo, {"address"} }, | { "wallet", "getaddressinfo", getaddressinfo, {"address"} }, | ||||
{ "wallet", "getbalance", getbalance, {"account","minconf","include_watchonly"} }, | { "wallet", "getbalance", getbalance, {"account","minconf","include_watchonly"} }, | ||||
▲ Show 20 Lines • Show All 56 Lines • Show Last 20 Lines |