交易是比特币最重要的一块,比特币系统的其他部分都是为交易服务的。前面的章节中已经学习了各种共识算法以及比特币PoW共识的实现,本文来分析比特币中的交易相关的源码。
1 初识比特币交易
通过比特币核心客户端的命令getrawtransaction和decoderawtransaction可以检索到比特币区块链上任意一笔交易的详细信息,以下是运行这两个命令后得到的某笔交易的详细信息,该示例摘自《精通比特币》一书:
{
"version": 1,
"locktime": 0,
"vin": [
{
"txid":"7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18",
"vout": 0,
"scriptSig": "3045022100884d142d86652a3f47ba4746ec719bbfbd040a570b1deccbb6498c75c4ae24cb02204b9f039ff08df09cbe9f6addac960298cad530a863ea8f53982c09db8f6e3813[ALL] 0484ecc0d46f1918b30928fa0e4ed99f16a0fb4fde0735e7ade8416ab9fe423cc5412336376789d172787ec3457eee41c04f4938de5cc17b4a10fa336a8d752adf",
"sequence": 4294967295
}
],
"vout": [
{
"value": 0.01500000,
"scriptPubKey": "OP_DUP OP_HASH160 ab68025513c3dbd2f7b92a94e0581f5d50f654e7 OP_EQUALVERIFY OP_CHECKSIG"
},
{
"value": 0.08450000,
"scriptPubKey": "OP_DUP OP_HASH160 7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8 OP_EQUALVERIFY OP_CHECKSIG",
}
]
}
我们仔细分析一下上面这个输出,来看看一笔比特币的交易到底包含了哪些东西。
首先是vin字段,这是一个json数组,数组中的每个元素代表一笔交易的输入,在这个例子中的交易,只有一笔输入;
其次是vout字段,这也是一个json数组,数组中的每个元素代表一笔未花费的输出(UTXO),在这个例子中的交易产生了两笔新的UTXO。
OK,我们已经看到一笔比特币交易包含了输入和输出两个部分,其中输入表示要花费的比特币来自哪里,而输出则表示输入所指向的比特币去了哪里,换句话说,比特币的交易实际上隐含着价值的转移。以示例中的交易为例,该交易所花费的比特币来自于另外一笔交易7957a35fe64f80d234d76d83a2a8f1a0d8149a41d81de548f0a65a8a999f6f18的索引为0的UTXO,该UTXO的去向在vout中指定,0.015个比特币去了公钥为ab68025513c3dbd2f7b92a94e0581f5d50f654e7对应的钱包,而0.0845个比特币则流向了公钥为7f9b1a7fb68d60c536c2fd8aeaa53a8f3cc025a8对应的钱包。
1.1 交易输出
交易的输出通常也称为UTXO,即未花费交易输出,从例子中可以看到一笔交易可能产生多个UTXO,这些UTXO在后续交易中会被花费。
交易输出包含下面一些内容:
value:该UTXO的比特币数量;
scriptPubKey:通常称为锁定脚本,决定了谁可以花费这笔UTXO,只有提供了正确的解锁脚本才能解锁并花费该UTXO;
1.2 交易输入
交易的输入可以理解为一个指向一笔UTXO的指针,表示该交易要花费的UTXO在哪里。交易输出包含如下内容:
txid:该交易要花费的UTXO所在的交易的hash;
vout:索引。一笔交易可能产生多个UTXO存放在数组中,该索引即为UTXO在数组中的下标。通过(txid, vout)就能检索到交易中的UTXO;
scriptSig:解锁脚本,用于解锁(txid, vout)所指向的UTXO。前文提到交易生成的每一笔UTXO都会设定一个锁定脚本即scriptPubKey,解锁脚本scriptSig用来解锁。如果把UTXO比作一个包含了比特币的宝箱,那么scriptPubKey就是给该宝箱上了一把锁,而scriptSig则是钥匙,只有提供真确的钥匙才能解开锁并花费宝箱里的比特币。
1.3 交易链
比特币的交易实际上是以链的形式串联在一起的,一笔交易与其前驱的交易通过交易输入串联起来。假设张三的钱包里有一笔2比特币的UTXO,然后张三给自己的好友李四转了0.5个比特币,于是生成一笔类似下面这样的交易:
交易T1的输入指向了交易T0的UTXO,该UTXO被分成了两部分,形成两笔新的UTXO:0.5BTC归李四所有,剩下的1.5BTC作为找零又回到了张三的钱包。假设之后李四在咖啡馆将收到的0.5BTC消费掉了0.1BTC,则交易链条如下:
应该注意到这样一个重要事实:每一笔新生成的交易,其交易的输入一定指向另外一笔交易的输出。比特币的交易通过这种链条的形式串联在一起,通过交易的输入就能找到其依赖的另外一笔交易。
2 交易相关的数据结构
现在我们已经从直观上知道了比特币的交易长什么样子,本节我们看看在代码中,交易是如何表示的。
2.1 交易输入的数据结构
交易的输入用如下的数据结构来表示:
/** An input of a transaction. It contains the location of the previous
* transaction's output that it claims and a signature that matches the
* output's public key.
*/
class CTxIn
{
public:
//该输入引用的UTXO
COutPoint prevout;
//解锁脚本,用于解锁输入指向的UTXO
CScript scriptSig;
//相对时间锁
uint32_t nSequence;
//见证脚本
CScriptWitness scriptWitness; //! Only serialized through CTransaction
/* Setting nSequence to this value for every input in a transaction
* disables nLockTime. */
static const uint32_t SEQUENCE_FINAL = 0xffffffff;
/* Below flags apply in the context of BIP 68*/
/* If this flag set, CTxIn::nSequence is NOT interpreted as a
* relative lock-time. */
static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31);
/* If CTxIn::nSequence encodes a relative lock-time and this flag
* is set, the relative lock-time has units of 512 seconds,
* otherwise it specifies blocks with a granularity of 1. */
static const uint32_t SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22);
/* If CTxIn::nSequence encodes a relative lock-time, this mask is
* applied to extract that lock-time from the sequence field. */
static const uint32_t SEQUENCE_LOCKTIME_MASK = 0x0000ffff;
/* In order to use the same number of bits to encode roughly the
* same wall-clock duration, and because blocks are naturally
* limited to occur every 600s on average, the minimum granularity
* for time-based relative lock-time is fixed at 512 seconds.
* Converting from CTxIn::nSequence to seconds is performed by
* multiplying by 512 = 2^9, or equivalently shifting up by
* 9 bits. */
static const int SEQUENCE_LOCKTIME_GRANULARITY = 9;
CTxIn()
{
nSequence = SEQUENCE_FINAL;
}
explicit CTxIn(COutPoint prevoutIn, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL);
CTxIn(uint256 hashPrevTx, uint32_t nOut, CScript scriptSigIn=CScript(), uint32_t nSequenceIn=SEQUENCE_FINAL);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(prevout);
READWRITE(scriptSig);
READWRITE(nSequence);
}
friend bool operator==(const CTxIn& a, const CTxIn& b)
{
return (a.prevout == b.prevout &&
a.scriptSig == b.scriptSig &&
a.nSequence == b.nSequence);
}
friend bool operator!=(const CTxIn& a, const CTxIn& b)
{
return !(a == b);
}
std::string ToString() const;
};
代码中的COutPoint是该输入所指向的UTXO,通过COutPoint定位到输入指向的UTXO:
/** An outpoint - a combination of a transaction hash and an index n into its vout */
class COutPoint
{
public:
//UTXO所在的交易hash
uint256 hash;
//UTXO的索引
uint32_t n;
COutPoint(): n((uint32_t) -1) { }
COutPoint(const uint256& hashIn, uint32_t nIn): hash(hashIn), n(nIn) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(hash);
READWRITE(n);
}
void SetNull() { hash.SetNull(); n = (uint32_t) -1; }
bool IsNull() const { return (hash.IsNull() && n == (uint32_t) -1); }
friend bool operator<(const COutPoint& a, const COutPoint& b)
{
int cmp = a.hash.Compare(b.hash);
return cmp < 0 || (cmp == 0 && a.n < b.n);
}
friend bool operator==(const COutPoint& a, const COutPoint& b)
{
return (a.hash == b.hash && a.n == b.n);
}
friend bool operator!=(const COutPoint& a, const COutPoint& b)
{
return !(a == b);
}
std::string ToString() const;
};
2.2 交易输出的数据结构
交易输出的数据结构如下:
/** An output of a transaction. It contains the public key that the next input
* must be able to sign with to claim it.
*/
class CTxOut
{
public:
CAmount nValue;
CScript scriptPubKey;
CTxOut()
{
SetNull();
}
CTxOut(const CAmount& nValueIn, CScript scriptPubKeyIn);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(nValue);
READWRITE(scriptPubKey);
}
void SetNull()
{
nValue = -1;
scriptPubKey.clear();
}
bool IsNull() const
{
return (nValue == -1);
}
friend bool operator==(const CTxOut& a, const CTxOut& b)
{
return (a.nValue == b.nValue &&
a.scriptPubKey == b.scriptPubKey);
}
friend bool operator!=(const CTxOut& a, const CTxOut& b)
{
return !(a == b);
}
std::string ToString() const;
};
可以看到定义非常简单,只有两个字段:CAmount表示该UTXO的比特币数量,scriptPubKey表示该UTXO的锁定脚本。
2.3 UTXO
UTXO的概念在比特币中非常重要,专门用一个类Coin来封装:
/**
* A UTXO entry.
*
* Serialized format:
* - VARINT((coinbase ? 1 : 0) | (height << 1))
* - the non-spent CTxOut (via CTxOutCompressor)
*/
class Coin
{
public:
//! unspent transaction output
//UTXO对应的急交易输出
CTxOut out;
//! whether containing transaction was a coinbase
//该UTXO是否是coinbase交易
unsigned int fCoinBase : 1;
//! at which height this containing transaction was included in the active block chain
//包含该UTXO的交易所在区块在区块链上的高度
uint32_t nHeight : 31;
//! construct a Coin from a CTxOut and height/coinbase information.
Coin(CTxOut&& outIn, int nHeightIn, bool fCoinBaseIn) : out(std::move(outIn)), fCoinBase(fCoinBaseIn), nHeight(nHeightIn) {}
Coin(const CTxOut& outIn, int nHeightIn, bool fCoinBaseIn) : out(outIn), fCoinBase(fCoinBaseIn),nHeight(nHeightIn) {}
void Clear() {
out.SetNull();
fCoinBase = false;
nHeight = 0;
}
//! empty constructor
Coin() : fCoinBase(false), nHeight(0) { }
bool IsCoinBase() const {
return fCoinBase;
}
template<typename Stream>
void Serialize(Stream &s) const {
assert(!IsSpent());
uint32_t code = nHeight * 2 + fCoinBase;
::Serialize(s, VARINT(code));
::Serialize(s, CTxOutCompressor(REF(out)));
}
template<typename Stream>
void Unserialize(Stream &s) {
uint32_t code = 0;
::Unserialize(s, VARINT(code));
nHeight = code >> 1;
fCoinBase = code & 1;
::Unserialize(s, CTxOutCompressor(out));
}
bool IsSpent() const {
return out.IsNull();
}
size_t DynamicMemoryUsage() const {
return memusage::DynamicUsage(out.scriptPubKey);
}
};
比特币钱包实际上就是一个由Coin构成的DB。bitcoind在启动的时候会从DB中加载Coin并存放至内存中。
2.4 交易脚本
交易输入的解锁脚本scriptSig和交易输出的锁定脚本scriptPubKey都是CScript类型,CScript用来表示交易脚本。交易脚本是比特币中一个非常重要的内容,用比特币提供的脚本语言可以完成非常复杂的功能,本文稍后还会有更详细介绍。
/** Serialized script, used inside transaction inputs and outputs */
class CScript : public CScriptBase
{
protected:
CScript& push_int64(int64_t n)
{
if (n == -1 || (n >= 1 && n <= 16))
{
push_back(n + (OP_1 - 1));
}
else if (n == 0)
{
push_back(OP_0);
}
else
{
*this << CScriptNum::serialize(n);
}
return *this;
}
public:
CScript() { }
CScript(const_iterator pbegin, const_iterator pend) : CScriptBase(pbegin, pend) { }
CScript(std::vector<unsigned char>::const_iterator pbegin, std::vector<unsigned char>::const_iterator pend) : CScriptBase(pbegin, pend) { }
CScript(const unsigned char* pbegin, const unsigned char* pend) : CScriptBase(pbegin, pend) { }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITEAS(CScriptBase, *this);
}
CScript& operator+=(const CScript& b)
{
reserve(size() + b.size());
insert(end(), b.begin(), b.end());
return *this;
}
friend CScript operator+(const CScript& a, const CScript& b)
{
CScript ret = a;
ret += b;
return ret;
}
CScript(int64_t b) { operator<<(b); }
explicit CScript(opcodetype b) { operator<<(b); }
explicit CScript(const CScriptNum& b) { operator<<(b); }
explicit CScript(const std::vector<unsigned char>& b) { operator<<(b); }
CScript& operator<<(int64_t b) { return push_int64(b); }
CScript& operator<<(opcodetype opcode)
{
if (opcode < 0 || opcode > 0xff)
throw std::runtime_error("CScript::operator<<(): invalid opcode");
insert(end(), (unsigned char)opcode);
return *this;
}
CScript& operator<<(const CScriptNum& b)
{
*this << b.getvch();
return *this;
}
CScript& operator<<(const std::vector<unsigned char>& b)
{
if (b.size() < OP_PUSHDATA1)
{
insert(end(), (unsigned char)b.size());
}
else if (b.size() <= 0xff)
{
insert(end(), OP_PUSHDATA1);
insert(end(), (unsigned char)b.size());
}
else if (b.size() <= 0xffff)
{
insert(end(), OP_PUSHDATA2);
uint8_t _data[2];
WriteLE16(_data, b.size());
insert(end(), _data, _data + sizeof(_data));
}
else
{
insert(end(), OP_PUSHDATA4);
uint8_t _data[4];
WriteLE32(_data, b.size());
insert(end(), _data, _data + sizeof(_data));
}
insert(end(), b.begin(), b.end());
return *this;
}
CScript& operator<<(const CScript& b)
{
// I'm not sure if this should push the script or concatenate scripts.
// If there's ever a use for pushing a script onto a script, delete this member fn
assert(!"Warning: Pushing a CScript onto a CScript with << is probably not intended, use + to concatenate!");
return *this;
}
bool GetOp(const_iterator& pc, opcodetype& opcodeRet, std::vector<unsigned char>& vchRet) const
{
return GetScriptOp(pc, end(), opcodeRet, &vchRet);
}
bool GetOp(const_iterator& pc, opcodetype& opcodeRet) const
{
return GetScriptOp(pc, end(), opcodeRet, nullptr);
}
/** Encode/decode small integers: */
static int DecodeOP_N(opcodetype opcode)
{
if (opcode == OP_0)
return 0;
assert(opcode >= OP_1 && opcode <= OP_16);
return (int)opcode - (int)(OP_1 - 1);
}
static opcodetype EncodeOP_N(int n)
{
assert(n >= 0 && n <= 16);
if (n == 0)
return OP_0;
return (opcodetype)(OP_1+n-1);
}
/**
* Pre-version-0.6, Bitcoin always counted CHECKMULTISIGs
* as 20 sigops. With pay-to-script-hash, that changed:
* CHECKMULTISIGs serialized in scriptSigs are
* counted more accurately, assuming they are of the form
* ... OP_N CHECKMULTISIG ...
*/
unsigned int GetSigOpCount(bool fAccurate) const;
/**
* Accurately count sigOps, including sigOps in
* pay-to-script-hash transactions:
*/
unsigned int GetSigOpCount(const CScript& scriptSig) const;
bool IsPayToScriptHash() const;
bool IsPayToWitnessScriptHash() const;
bool IsWitnessProgram(int& version, std::vector<unsigned char>& program) const;
/** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */
bool IsPushOnly(const_iterator pc) const;
bool IsPushOnly() const;
/** Check if the script contains valid OP_CODES */
bool HasValidOps() const;
/**
* Returns whether the script is guaranteed to fail at execution,
* regardless of the initial stack. This allows outputs to be pruned
* instantly when entering the UTXO set.
*/
bool IsUnspendable() const
{
return (size() > 0 && *begin() == OP_RETURN) || (size() > MAX_SCRIPT_SIZE);
}
void clear()
{
// The default prevector::clear() does not release memory
CScriptBase::clear();
shrink_to_fit();
}
};
CScript继承自ScriptBase:
/**
* We use a prevector for the script to reduce the considerable memory overhead
* of vectors in cases where they normally contain a small number of small elements.
* Tests in October 2015 showed use of this reduced dbcache memory usage by 23%
* and made an initial sync 13% faster.
*/
typedef prevector<28, unsigned char> CScriptBase;
CScriptBase实际上一个自定义的vector。CScript重写了<<操作符,可以很方便的向向量中添加数据。
2.5 交易
比特币的交易和我们已经看到的那样,由一组输入和一组输出组成:
/** The basic transaction that is broadcasted on the network and contained in
* blocks. A transaction can contain multiple inputs and outputs.
*/
class CTransaction
{
public:
// Default transaction version.
static const int32_t CURRENT_VERSION=2;
// Changing the default transaction version requires a two step process: first
// adapting relay policy by bumping MAX_STANDARD_VERSION, and then later date
// bumping the default CURRENT_VERSION at which point both CURRENT_VERSION and
// MAX_STANDARD_VERSION will be equal.
static const int32_t MAX_STANDARD_VERSION=2;
// The local variables are made const to prevent unintended modification
// without updating the cached hash value. However, CTransaction is not
// actually immutable; deserialization and assignment are implemented,
// and bypass the constness. This is safe, as they update the entire
// structure, including the hash.
//交易的全部输入
const std::vector<CTxIn> vin;
//交易的全部输出
const std::vector<CTxOut> vout;
//交易版本
const int32_t nVersion;
//交易锁定时间,用来控制在一定的时间之后交易的输出才能被花费
const uint32_t nLockTime;
private:
/** Memory only. */
const uint256 hash;
uint256 ComputeHash() const;
public:
/** Construct a CTransaction that qualifies as IsNull() */
CTransaction();
/** Convert a CMutableTransaction into a CTransaction. */
CTransaction(const CMutableTransaction &tx);
CTransaction(CMutableTransaction &&tx);
template <typename Stream>
inline void Serialize(Stream& s) const {
SerializeTransaction(*this, s);
}
/** This deserializing constructor is provided instead of an Unserialize method.
* Unserialize is not possible, since it would require overwriting const fields. */
template <typename Stream>
CTransaction(deserialize_type, Stream& s) : CTransaction(CMutableTransaction(deserialize, s)) {}
bool IsNull() const {
return vin.empty() && vout.empty();
}
const uint256& GetHash() const {
return hash;
}
// Compute a hash that includes both transaction and witness data
uint256 GetWitnessHash() const;
// Return sum of txouts.
CAmount GetValueOut() const;
// GetValueIn() is a method on CCoinsViewCache, because
// inputs must be known to compute value in.
/**
* Get the total transaction size in bytes, including witness data.
* "Total Size" defined in BIP141 and BIP144.
* @return Total transaction size in bytes
*/
unsigned int GetTotalSize() const;
bool IsCoinBase() const
{
return (vin.size() == 1 && vin[0].prevout.IsNull());
}
friend bool operator==(const CTransaction& a, const CTransaction& b)
{
return a.hash == b.hash;
}
friend bool operator!=(const CTransaction& a, const CTransaction& b)
{
return a.hash != b.hash;
}
std::string ToString() const;
bool HasWitness() const
{
for (size_t i = 0; i < vin.size(); i++) {
if (!vin[i].scriptWitness.IsNull()) {
return true;
}
}
return false;
}
};
除了交易输入和输出外,还有交易的版本和交易时间锁nLockTime,交易时间锁用来控制交易的输出只有在一段时间后才能被花费,关于该字段在《精通比特币》第2版有详细说明。
另外需要注意的是CTransaction中所有的字段全部用const修饰符来修饰,说明一旦创建出CTransaction对象以后,其中的内容就不能在更改了,因此CTransaction是一个不可变的对象,与之相对应的,还有一个交易的可变版本:
/** A mutable version of CTransaction. */
struct CMutableTransaction
{
std::vector<CTxIn> vin;
std::vector<CTxOut> vout;
int32_t nVersion;
uint32_t nLockTime;
CMutableTransaction();
CMutableTransaction(const CTransaction& tx);
template <typename Stream>
inline void Serialize(Stream& s) const {
SerializeTransaction(*this, s);
}
template <typename Stream>
inline void Unserialize(Stream& s) {
UnserializeTransaction(*this, s);
}
template <typename Stream>
CMutableTransaction(deserialize_type, Stream& s) {
Unserialize(s);
}
/** Compute the hash of this CMutableTransaction. This is computed on the
* fly, as opposed to GetHash() in CTransaction, which uses a cached result.
*/
uint256 GetHash() const;
friend bool operator==(const CMutableTransaction& a, const CMutableTransaction& b)
{
return a.GetHash() == b.GetHash();
}
bool HasWitness() const
{
for (size_t i = 0; i < vin.size(); i++) {
if (!vin[i].scriptWitness.IsNull()) {
return true;
}
}
return false;
}
};
CMutableTransaction与CTransaction的字段完全相同,所不同的是字段前面少了const修饰符,因此一个CMutableTransaction对象生成以后,它的字段还可以重新赋值。
3 交易的创建
了解了和交易相关的数据结构以后,本节我们来分析一下比特币交易是如何创建的。
通过比特币的JSONAP命令createrawtransaction可以创建一笔交易,这个命令需要传入以下形式的json参数:
"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"
需要在参数中指定每一笔输入和输出。实际中使用比特币钱包时,这些脏活都由钱包帮我们做了。
我们看看createrawtransaction是如何创建出一笔比特币交易的,该命令的实现位于rawtransaction.cpp中:
static UniValue createrawtransaction(const JSONRPCRequest& request)
{
//输入参数不合法,抛出异常,提示参数格式
if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) {
throw std::runtime_error(
// clang-format off
"createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n"
"\nCreate a transaction spending the given inputs and creating new outputs.\n"
"Outputs can be addresses or data.\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. \"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"
"\nResult:\n"
"\"transaction\" (string) hex string of the transaction\n"
"\nExamples:\n"
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"")
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")
+ HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"")
+ HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"")
// clang-format on
);
}
//检查参数
RPCTypeCheck(request.params, {
UniValue::VARR,
UniValueType(), // ARR or OBJ, checked later
UniValue::VNUM,
UniValue::VBOOL
}, true
);
if (request.params[0].isNull() || request.params[1].isNull())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null");
UniValue inputs = request.params[0].get_array();
const bool outputs_is_obj = request.params[1].isObject();
UniValue outputs = outputs_is_obj ?
request.params[1].get_obj() :
request.params[1].get_array();
//生成交易对象
CMutableTransaction rawTx;
//从参数提取交易的锁定时间(如果提供的话)
if (!request.params[2].isNull()) {
int64_t nLockTime = request.params[2].get_int64();
if (nLockTime < 0 || nLockTime > std::numeric_limits<uint32_t>::max())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range");
rawTx.nLockTime = nLockTime;
}
bool rbfOptIn = request.params[3].isTrue();
//解析参数,生成交易的输入
for (unsigned int idx = 0; idx < inputs.size(); idx++) {
const UniValue& input = inputs[idx];
const UniValue& o = input.get_obj();
//该输入指向的交易
uint256 txid = ParseHashO(o, "txid");
//该输入指向的UTXO在其交易中的索引
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");
uint32_t nSequence;
if (rbfOptIn) {
nSequence = MAX_BIP125_RBF_SEQUENCE;
} else if (rawTx.nLockTime) {
nSequence = std::numeric_limits<uint32_t>::max() - 1;
} else {
nSequence = std::numeric_limits<uint32_t>::max();
}
// set the sequence number if passed in the parameters object
const UniValue& sequenceObj = find_value(o, "sequence");
if (sequenceObj.isNum()) {
int64_t seqNr64 = sequenceObj.get_int64();
if (seqNr64 < 0 || seqNr64 > std::numeric_limits<uint32_t>::max()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, sequence number is out of range");
} else {
nSequence = (uint32_t)seqNr64;
}
}
CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);
rawTx.vin.push_back(in);
}
std::set<CTxDestination> destinations;
if (!outputs_is_obj) {
// Translate array of key-value pairs into dict
UniValue outputs_dict = UniValue(UniValue::VOBJ);
for (size_t i = 0; i < outputs.size(); ++i) {
const UniValue& output = outputs[i];
if (!output.isObject()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair not an object as expected");
}
if (output.size() != 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, key-value pair must contain exactly one key");
}
outputs_dict.pushKVs(output);
}
outputs = std::move(outputs_dict);
}
//根据参数生成交易的输出
for (const std::string& name_ : outputs.getKeys()) {
if (name_ == "data") {
std::vector<unsigned char> data = ParseHexV(outputs[name_].getValStr(), "Data");
CTxOut out(0, CScript() << OP_rawTx.vout.push_back(out)RETURN << data);
} else {
//解析出目标地址(比特币最终流向的地方)
CTxDestination destination = DecodeDestination(name_);
if (!IsValidDestination(destination)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_);
}
if (!destinations.insert(destination).second) {
throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_);
}
//根据地址生成交易输出的锁定脚本
CScript scriptPubKey = GetScriptForDestination(destination);
CAmount nAmount = AmountFromValue(outputs[name_]);
CTxOut out(nAmount, scriptPubKey);
rawTx.vout.push_back(out);
}
}
if (!request.params[3].isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option");
}
//对交易进行编码并返回
return EncodeHexTx(rawTx);
}
整体的过程并不复杂:从参数中解析出每一笔输入和输出,并填写到CMutableTransaction对象中,最后将对象编码后返回。但是这里有两个问题值得注意:
(1) 从代码中没有看到交易输入中的解锁脚本scriptSig;
(2) 交易输出的锁定脚本如何生成的需要了解;
关于第一个问题,随后在分析交易签名时解答,下面我们先来看看第二个问题:交易输出的锁定脚本如何生成。生成锁定脚本的代码如下:
CScript scriptPubKey = GetScriptForDestination(destination);
我们来看看这个函数的实现:
CScript GetScriptForDestination(const CTxDestination& dest)
{
CScript script;
boost::apply_visitor(CScriptVisitor(&script), dest);
return script;
}
首先,该方法接受CTxDestination类型的参数,该类型定义如下:
/**
* A txout script template with a specific destination. It is either:
* * CNoDestination: no destination set
* * CKeyID: TX_PUBKEYHASH destination (P2PKH)
* * CScriptID: TX_SCRIPTHASH destination (P2SH)
* * WitnessV0ScriptHash: TX_WITNESS_V0_SCRIPTHASH destination (P2WSH)
* * WitnessV0KeyHash: TX_WITNESS_V0_KEYHASH destination (P2WPKH)
* * WitnessUnknown: TX_WITNESS_UNKNOWN destination (P2W???)
* A CTxDestination is the internal data type encoded in a bitcoin address
*/
typedef boost::variant<CNoDestination, CKeyID, CScriptID, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown> CTxDestination;
CTxDestination是boost::variant类型,表示一个特定的比特币地址。boost::variant可以理解为一种增强的union类型,从该类型的定义我们也可以看出目前比特币支持如下几种类型的地址:
CKeyID:公钥,适用于P2PKH标准交易,锁定脚本中指定比特币接受者的公钥;
CScriptID:适用于P2SH标准交易的地址;
WitnessV0ScriptHash:适用于P2WSH交易的地址;
WitnessV0KeyHash:适用于P2WPKH交易的地址;
可见,针对不同类型的交易,有不同类型的地址,因此生成交易输出的锁定脚本时也要根据交易类型来具体处理。为了避免出现很多if-else分支,比特币使用boost提供的visitor设计模式的实现来进行处理,提供了CScriptVisitor针对不同类型的地址生成对应的锁定脚本:
class CScriptVisitor : public boost::static_visitor<bool>
{
private:
CScript *script;
public:
explicit CScriptVisitor(CScript *scriptin) { script = scriptin; }
bool operator()(const CNoDestination &dest) const {
script->clear();
return false;
}
//P2PKH标准交易
bool operator()(const CKeyID &keyID) const {
script->clear();
*script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
return true;
}
//P2SH标准交易
bool operator()(const CScriptID &scriptID) const {
script->clear();
*script << OP_HASH160 << ToByteVector(scriptID) << OP_EQUAL;
return true;
}
//P2WSH交易
bool operator()(const WitnessV0KeyHash& id) const
{
script->clear();
*script << OP_0 << ToByteVector(id);
return true;
}
//P2WKH交易
bool operator()(const WitnessV0ScriptHash& id) const
{
script->clear();
*script << OP_0 << ToByteVector(id);
return true;
}
bool operator()(const WitnessUnknown& id) const
{
script->clear();
*script << CScript::EncodeOP_N(id.version) << std::vector<unsigned char>(id.program, id.program + id.length);
return true;
}
};
现在,我们已经了解到交易输出的锁定脚本的生成过程了,暂时先不用管脚本是如何执行的,本文稍后还会详细说明交易脚本的运行原理。
4 交易签名
本节来回答上一节提到的第一个问题:交易输入的解锁脚本scriptSig是如何生成的。我们先来搞清楚一个问题:为什么需要对交易签名,签名的原理又是怎样?
4.1 为什么交易需要签名
在比特币中对交易进行签名的主要作用是证明某人对某一笔UTXO的所有权。假设张三给李四转账1BTC,交易中就会生成一个1BTC的UTXO,为了确保这笔UTXO随后只能被李四花费,必须要对交易进行数字签名。
4.2 交易签名的原理
交易签名实际上就是对交易进行数字签名。数字签名之前在加密算法中已经有说明,这里我们再次回顾一下:假设张三在一条不可靠的通信信道上给李四发送了一条消息msg,李四如何确认发送消息的人就是张三而不是别人呢?
(1) 张三用hash对msg生成摘要D:
(2) 张三用某种签名算法F,加上自己的私钥key对摘要D生成签名S:
(3) 张三将签名S和消息msg一并发送给李四;
(4) 李四用张三的公钥pubkey从收到的签名S中解出消息摘要D:
(5) 李四对收到的消息msg进行hash得到摘要D1,然后和解出的D对比是否相同,相同就能证明该消息确实来自于张三;
比特币交易签名的是相同的道理,其中msg就是交易,F是比特币采用的ECDSA椭圆曲线签名算法,我们以最常见的P2PKH交易为例来说明。
假设张三给李四转账1BTC,于是张三的钱包生成了交易,交易T中有一笔指向李四的UTXO,价值1BTC。张三为了确保这笔UTXO以后只能由李四消费,会在锁定脚本scriptPubKey中设置两个条件:
(C1) 消费者必须提供自己的公钥,并且对公钥进行hash后的值需要与李四的公钥的hash值相等,假设李四的公钥为P,消费者提供的公钥为pubkey,则必须满足:
张三会将李四的公钥hash即Hash(P)写入到scriptPubKey脚本中;
(C2) 消费者提供的签名必须正确。
随后,李四的钱包生成交易T,想花费这笔UTXO,则李四需要提供两样东西:李四的公钥pubkey,和李四对交易T的签名。
(1) 李四对交易T采用hash生成摘要D:
10-03 10:25