比特幣原始碼分析--深入理解比特幣交易
交易是比特幣最重要的一塊,比特幣系統的其他部分都是為交易服務的。前面的章節中已經學習了各種共識演算法以及比特幣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:
(2) 李四用ECDSA簽名演算法,用自己的私鑰key對摘要D生成數字簽名S:
(3) 李四將自己的公鑰pubkey和簽名S寫入到交易T的解鎖指令碼scriptSig中,然後將交易T廣播到網路中;
(4) 網路中的節點收到交易T,對交易進行驗證,確認李四確實可以花費這筆UTXO。首先對收到的交易T的鎖定指令碼中的公鑰pubkey進行hash,看是否和UTXO的鎖定指令碼中的公鑰hash相同(滿足條件C1);然後檢查簽名:首先節點對收到的交易進行hash生成交易的摘要D':
然後用公鑰pubkey從簽名S中解出交易摘要D:
如果則可以證明這筆交易T確實是李四生成,他有權花費這筆UTXO。
4.3 交易簽名的生成
我們再次回顧了比特幣交易簽名的原理。接下來我們來看看交易輸入的解鎖指令碼(公鑰+簽名)是如何生成的。第3節介紹了通過createrawtransaction生成交易的過程,但是createrawtransaction生成的交易的輸入中還缺少解鎖指令碼scriptSig,解鎖指令碼需要通過另一個jsonapi:signra