比特币 RPC 命令「signrawtransaction」
$ bitcoin-cli help signrawtransaction signrawtransaction "hexstring" ( [{"txid":"id","vout":n,"scriptPubKey":"hex","redeemScript":"hex"},...] ["privatekey1",...] sighashtype ) 对原始交易(序列化的,16 进制编码的)的输入签名。 第二个可选参数(可能为空)是该交易依赖但可能尚未在区块亮中的前一笔交易的输出。 第三个可选参数(可能为空)是一个 base58 编码的私钥数组,如果给定,它将是唯一用于签名该交易的私钥。 参数: 1. "hexstring"(字符串,必备)交易的 16 进制字符串 2. "prevtxs" (字符串,可选)依赖上一笔的交易输出的 json 数组 [ (json 对象的 json 数组,若未提供则为空) { "txid":"id", (字符串,必备)交易索引 "vout":n, (数字,必备)输出序号 "scriptPubKey": "hex",(字符串,必备)脚本公钥 "redeemScript": "hex" (字符串,对于 P2SH 必备)赎回脚本 } ,... ] 3. "privatekeys" (字符串,可选)用于签名的 base58 编码的私钥的 json 数组 [ (字符串的 json 数组,若未提供则为空) "privatekey"(字符串)base58 编码的私钥 ,... ] 4. "sighashtype" (字符串,可选,默认为 ALL)签名哈希类型。必须是其中之一 "ALL" "NONE" "SINGLE" "ALL|ANYONECANPAY" "NONE|ANYONECANPAY" "SINGLE|ANYONECANPAY" 结果: { "hex" : "value", (字符串)16 进制编码的带签名的原始交易 "complete" : true|false,(布尔型)交易是否有一个完整的签名集 "errors" : [ (json 对象数组)脚本验证错误(如果有) { "txid" : "hash", (字符串)引用上一笔交易的哈希 "vout" : n, (数字)用于花费并作为输入的输出索引 "scriptSig" : "hex",(字符串)16 进制编码的签名脚本 "sequence" : n, (数字)脚本序列号 "error" : "text" (字符串)与输入相关的验证或签名错误 } ,... ] } 例子: > bitcoin-cli signrawtransaction "myhex" > curl --user myusername:mypassword --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "signrawtransaction", "params": ["myhex"] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/
源码剖析
signrawtransaction
对应的函数在文件 rpcserver.h
中被引用。
extern UniValue signrawtransaction(const UniValue& params, bool fHelp);
实现在文件 rpcrawtransaction.cpp
中。
UniValue signrawtransaction(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 4)
throw runtime_error(
"signrawtransaction \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] [\"privatekey1\",...] sighashtype )\n"
"\nSign 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("signrawtransaction", "\"myhex\"")
+ HelpExampleRpc("signrawtransaction", "\"myhex\"")
); // 1. 帮助内容
#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); // 2. RPC 类型检测
vector<unsigned char> txData(ParseHexV(params[0], "argument 1")); // 3. 交易解码
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
vector<CMutableTransaction> 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]); // 4. mergeTx 将以所有签名结束;它以 rawtx 的克隆开始
// Fetch previous transactions (inputs):
CCoinsView viewDummy; // 5. 获取上一笔交易(输入)
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 (unsigned int 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(); // 6. 确保钱包未上锁
#endif
// Add previous txouts given in the RPC call:
if (params.size() > 1 && !params[1].isNull()) { // 7. 添加在 RPC 调用中给定的上一笔交易输出
UniValue prevTxs = params[1].get_array();
for (unsigned int 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<unsigned char> 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 + ScriptToAsmStr(coins->vout[nOut].scriptPubKey) + "\nvs:\n"+
ScriptToAsmStr(scriptPubKey);
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()) { // 8. 如果提供了赎回脚本且不使用本地钱包(提供了私钥),则添加赎回脚本到临时密钥库以以便对其签名
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<unsigned char> 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; // 9. 设置脚本哈希类型
if (params.size() > 3 && !params[3].isNull()) {
static map<string, int> 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++) { // 10. 尽我们所能签名
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())) // 如果有相应的输出,则只签名 SIGHASH_SINGLE
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;
}
1. 帮助内容
参考比特币 RPC 命令「getbestblockhash」1. 帮助内容。