比特币源码分析--深入理解比特币交易
交易是比特币最重要的一块,比特币系统的其他部分都是为交易服务的。前面的章节中已经学习了各种共识算法以及比特币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:signrawtransaction,这条命令需要的参数如下:
-
"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 or P2WSH) redeem script\n"
-
" \"amount\": value (numeric, required) The amount spent\n"
-
" }\n"
-
" ,...\n"
-
" ]\n"
-
"3. \"privkeys\" (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"
prevtxs提供该交易的输入所指向的UTXO,privkeys则是解锁这些UTXO需要的私钥,sighashtype则指定了只签名交易中的一部分交易还是对全部交易输入都进行签名。
签名的过程如下:
-
UniValue signrawtransaction(const JSONRPCRequest& request) {
-
#ifdef ENABLE_WALLET
-
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
-
#endif
-
//检查参数格式,如果格式不正确,抛出异常提示正确用法
-
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
-
throw std::runtime_error(
-
"signrawtransaction \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] [\"privatekey1\",...] sighashtype )\n"
-
"\nDEPRECATED. Sign 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(pwallet) + "\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 or P2WSH) redeem script\n"
-
" \"amount\": value (numeric, required) The amount spent\n"
-
" }\n"
-
" ,...\n"
-
" ]\n"
-
"3. \"privkeys\" (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\"")
-
);
-
if (!IsDeprecatedRPCEnabled("signrawtransaction")) {
-
throw JSONRPCError(RPC_METHOD_DEPRECATED,
-
"signrawtransaction is deprecated and will be fully removed in v0.18. "
-
"To use signrawtransaction in v0.17, restart bitcoind with -deprecatedrpc=signrawtransaction.\n"
-
"Projects should transition to using signrawtransactionwithkey and signrawtransactionwithwallet before upgrading to v0.18");
-
}
-
//检查参数
-
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR},
-
true);
-
// Make a JSONRPCRequest to pass on to the right signrawtransaction* command
-
JSONRPCRequest new_request;
-
new_request.id = request.id;
-
new_request.params.setArray();
-
// For signing with private keys
-
if (!request.params[2].isNull()) {
-
//通过参数提供的私钥进行签名
-
new_request.params.push_back(request.params[0]);
-
// Note: the prevtxs and privkeys are reversed for signrawtransactionwithkey
-
new_request.params.push_back(request.params[2]);
-
new_request.params.push_back(request.params[1]);
-
new_request.params.push_back(request.params[3]);
-
return signrawtransactionwithkey(new_request);
-
} else {
-
#ifdef ENABLE_WALLET
-
// Otherwise sign with the wallet which does not take a privkeys parameter
-
//通过钱包进行签名
-
new_request.params.push_back(request.params[0]);
-
new_request.params.push_back(request.params[1]);
-
new_request.params.push_back(request.params[3]);
-
return signrawtransactionwithwallet(new_request);
-
#else
-
// If we have made it this far, then wallet is disabled and no private keys were given, so fail here.
-
throw JSONRPCError(RPC_INVALID_PARAMETER, "No private keys available.");
-
#endif
-
}
我们这里只分析通过提供的私钥进行签名的过程,这个是通过signrawtransactionwithkey方法实现:
-
static UniValue signrawtransactionwithkey(const JSONRPCRequest& request)
-
{
-
//参数检查
-
if (request.fHelp || request.params.size() < 2 || request.params.size() > 4)
-
throw std::runtime_error(
-
"signrawtransactionwithkey \"hexstring\" [\"privatekey1\",...] ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] sighashtype )\n"
-
"\nSign inputs for raw transaction (serialized, hex-encoded).\n"
-
"The second argument is an array of base58-encoded private\n"
-
"keys that will be the only keys used to sign the transaction.\n"
-
"The third 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"
-
"\nArguments:\n"
-
"1. \"hexstring\" (string, required) The transaction hex string\n"
-
"2. \"privkeys\" (string, required) A json array of base58-encoded private keys for signing\n"
-
" [ (json array of strings)\n"
-
" \"privatekey\" (string) private key in base58-encoding\n"
-
" ,...\n"
-
" ]\n"
-
"3. \"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 or P2WSH) redeem script\n"
-
" \"amount\": value (numeric, required) The amount spent\n"
-
" }\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("signrawtransactionwithkey", "\"myhex\"")
-
+ HelpExampleRpc("signrawtransactionwithkey", "\"myhex\"")
-
);
-
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true);
-
//解码出原始交易
-
CMutableTransaction mtx;
-
if (!DecodeHexTx(mtx, request.params[0].get_str(), true)) {
-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
-
}
-
//从提供的私钥得到对应的公钥,并且将私钥-公钥对保存在keystore中
-
CBasicKeyStore keystore;
-
const UniValue& keys = request.params[1].get_array();
-
for (unsigned int idx = 0; idx < keys.size(); ++idx) {
-
UniValue k = keys[idx];
-
CKey key = DecodeSecret(k.get_str());
-
if (!key.IsValid()) {
-
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
-
}
-
keystore.AddKey(key);
-
}
-
//对交易输入进行签名,生成解锁脚本scriptSig
-
return SignTransaction(mtx, request.params[2], &keystore, true, request.params[3]);
-
}
这个函数中从提供的私钥得到对应的公钥,然后将私钥-公钥的配对存放在keystore中,方便后续进行检索。真正的交易签名是在SignTransaction中:
-
UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival, CBasicKeyStore *keystore, bool is_temp_keystore, const UniValue& hashType)
-
{
-
// Fetch previous transactions (inputs):
-
CCoinsView viewDummy;
-
CCoinsViewCache view(&viewDummy);
-
{
-
LOCK2(cs_main, mempool.cs);
-
CCoinsViewCache &viewChain = *pcoinsTip;
-
CCoinsViewMemPool viewMempool(&viewChain, mempool);
-
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
-
for (const CTxIn& txin : mtx.vin) {
-
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
-
}
-
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
-
}
-
// Add previous txouts given in the RPC call:
-
//查找交易输入指向的UTXO并加入到内存中
-
if (!prevTxsUnival.isNull()) {
-
UniValue prevTxs = prevTxsUnival.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,
-
{
-
{"txid", UniValueType(UniValue::VSTR)},
-
{"vout", UniValueType(UniValue::VNUM)},
-
{"scriptPubKey", UniValueType(UniValue::VSTR)},
-
});
-
//从参数中得到txid和vout,生成COutPoint指向交易输入引用的UTXO
-
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");
-
}
-
COutPoint out(txid, nOut);
-
//解析参数,得到交易输入指向的UTXO的锁定脚本
-
std::vector<unsigned char> pkData(ParseHexO(prevOut, "scriptPubKey"));
-
CScript scriptPubKey(pkData.begin(), pkData.end());
-
//查找交易输入指向的UTXO
-
{
-
const Coin& coin = view.AccessCoin(out);
-
if (!coin.IsSpent() && coin.out.scriptPubKey != scriptPubKey) {
-
std::string err("Previous output scriptPubKey mismatch:\n");
-
err = err + ScriptToAsmStr(coin.out.scriptPubKey) + "\nvs:\n"+
-
ScriptToAsmStr(scriptPubKey);
-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, err);
-
}
-
Coin newcoin;
-
newcoin.out.scriptPubKey = scriptPubKey;
-
newcoin.out.nValue = 0;
-
if (prevOut.exists("amount")) {
-
newcoin.out.nValue = AmountFromValue(find_value(prevOut, "amount"));
-
}
-
newcoin.nHeight = 1;
-
view.AddCoin(out, std::move(newcoin), true);
-
}
-
// if redeemScript given and not using the local wallet (private keys
-
// given), add redeemScript to the keystore so it can be signed:
-
//如果是P2SH或者P2WSH交易,如果指定了赎回脚本也需要加入到keystore中
-
if (is_temp_keystore && (scriptPubKey.IsPayToScriptHash() || scriptPubKey.IsPayToWitnessScriptHash())) {
-
RPCTypeCheckObj(prevOut,
-
{
-
{"redeemScript", UniValueType(UniValue::VSTR)},
-
});
-
UniValue v = find_value(prevOut, "redeemScript");
-
if (!v.isNull()) {
-
std::vector<unsigned char> rsData(ParseHexV(v, "redeemScript"));
-
CScript redeemScript(rsData.begin(), rsData.end());
-
keystore->AddCScript(redeemScript);
-
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
-
keystore->AddCScript(GetScriptForWitness(redeemScript));
-
}
-
}
-
}
-
}
-
//解析hashtype
-
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");
-
}
-
}
-
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
-
// Script verification errors
-
UniValue vErrors(UniValue::VARR);
-
// Use CTransaction for the constant parts of the
-
// transaction to avoid rehashing.
-
const CTransaction txConst(mtx);
-
// Sign what we can:
-
//对交易输入生成签名
-
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
-
//找到交易输入指向的UTXO
-
CTxIn& txin = mtx.vin[i];
-
const Coin& coin = view.AccessCoin(txin.prevout);
-
if (coin.IsSpent()) {
-
TxInErrorToJSON(txin, vErrors, "Input not found or already spent");
-
continue;
-
}
-
//拿到输入指向的UTXO的锁定脚本
-
const CScript& prevPubKey = coin.out.scriptPubKey;
-
const CAmount& amount = coin.out.nValue;
-
SignatureData sigdata;
-
// Only sign SIGHASH_SINGLE if there's a corresponding output:
-
//生成交易的解锁脚本,存放在sigdata中
-
if (!fHashSingle || (i < mtx.vout.size())) {
-
ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
-
}
-
sigdata = CombineSignatures(prevPubKey, TransactionSignatureChecker(&txConst, i, amount), sigdata, DataFromTransaction(mtx, i));
-
//将生成的解锁脚本填充到交易输入中
-
UpdateTransaction(mtx, i, sigdata);
-
ScriptError serror = SCRIPT_ERR_OK;
-
//脚本校验
-
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
-
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
-
// Unable to sign input and verification failed (possible attempt to partially sign).
-
TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)");
-
} else {
-
TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
-
}
-
}
-
}
-
bool fComplete = vErrors.empty();
-
UniValue result(UniValue::VOBJ);
-
result.pushKV("hex", EncodeHexTx(mtx));
-
result.pushKV("complete", fComplete);
-
if (!vErrors.empty()) {
-
result.pushKV("errors", vErrors);
-
}
-
return result;
-
}
如果抛开细节问题,这个函数主要做的其实就是两件事:为交易输入生成解锁脚本以及校验脚本。
4.3.1 为交易输入生成解锁脚本
给交易输入生成解锁脚本是在ProduceSignature方法中进行:
-
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata)
-
{
-
std::vector<valtype> result;
-
txnouttype whichType;
-
//进行签名
-
bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE);
-
bool P2SH = false;
-
CScript subscript;
-
sigdata.scriptWitness.stack.clear();
-
//P2SH交易,需要对子脚本进行签名
-
if (solved && whichType == TX_SCRIPTHASH)
-
{
-
// Solver returns the subscript that needs to be evaluated;
-
// the final scriptSig is the signatures from that
-
// and then the serialized subscript:
-
subscript = CScript(result[0].begin(), result[0].end());
-
solved = solved && SignStep(provider, creator, subscript, result, whichType, SigVersion::BASE) && whichType != TX_SCRIPTHASH;
-
P2SH = true;
-
}
-
//P2WKH交易,需要对见证脚本签名
-
if (solved && whichType == TX_WITNESS_V0_KEYHASH)
-
{
-
CScript witnessscript;
-
witnessscript << OP_DUP << OP_HASH160 << ToByteVector(result[0]) << OP_EQUALVERIFY << OP_CHECKSIG;
-
txnouttype subType;
-
solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0);
-
sigdata.scriptWitness.stack = result;
-
result.clear();
-
}
-
//P2WSH交易
-
else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH)
-
{
-
CScript witnessscript(result[0].begin(), result[0].end());
-
txnouttype subType;
-
solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0) && subType != TX_SCRIPTHASH && subType != TX_WITNESS_V0_SCRIPTHASH && subType != TX_WITNESS_V0_KEYHASH;
-
result.push_back(std::vector<unsigned char>(witnessscript.begin(), witnessscript.end()));
-
sigdata.scriptWitness.stack = result;
-
result.clear();
-
}
-
if (P2SH) {
-
result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end()));
-
}
-
//将生成的解锁脚本写入到sigdata中
-
sigdata.scriptSig = PushAll(result);
-
// 校验脚本
-
return solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker());
-
}
先来看看函数的参数:provider:keystore,存放了公钥-私钥配对,之前的代码中已经提到过;
creator:BaseSignatureCreator类型的实例,用于最后对交易生成签名;
fromPubKey:CScript类型,交易输入引用的UTXO的锁定脚本;
sigData:SignatureData类型,是输出参数,用于存放生成的解锁脚本;
像洋葱一样一层又一层后,最终对交易输入完成签名的是SignStep这个方法:
-
/**
-
* Sign scriptPubKey using signature made with creator.
-
* Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed),
-
* unless whichTypeRet is TX_SCRIPTHASH, in which case scriptSigRet is the redemption script.
-
* Returns false if scriptPubKey could not be completely satisfied.
-
*/
-
static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey,
-
std::vector<valtype>& ret, txnouttype& whichTypeRet, SigVersion sigversion)
-
{
-
CScript scriptRet;
-
uint160 h160;
-
ret.clear();
-
std::vector<valtype> vSolutions;
-
//解析交易输入引用的UTXO的锁定脚本,锁定脚本的类型存在输出参数whichTypeRet中,锁定脚本的数据存放在向量vSolutions中
-
if (!Solver(scriptPubKey, whichTypeRet, vSolutions))
-
return false;
-
CKeyID keyID;
-
//根据不同的锁定脚本的类型执行签名
-
switch (whichTypeRet)
-
{
-
case TX_NONSTANDARD:
-
case TX_NULL_DATA:
-
case TX_WITNESS_UNKNOWN:
-
return false;
-
case TX_PUBKEY: //锁定脚本是P2PK类型
-
keyID = CPubKey(vSolutions[0]).GetID();
-
return Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion);
-
case TX_PUBKEYHASH: //锁定脚本是P2PKH类型
-
keyID = CKeyID(uint160(vSolutions[0]));
-
if (!Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion))
-
return false;
-
else
-
{
-
CPubKey vch;
-
provider.GetPubKey(keyID, vch);
-
ret.push_back(ToByteVector(vch));
-
}
-
return true;
-
case TX_SCRIPTHASH: //锁定脚本是P2SH类型
-
if (provider.GetCScript(uint160(vSolutions[0]), scriptRet)) {
-
ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
-
return true;
-
}
-
return false;
-
case TX_MULTISIG: //锁定脚本是MultiSig(多重签名)
-
ret.push_back(valtype()); // workaround CHECKMULTISIG bug
-
return (SignN(provider, vSolutions, creator, scriptPubKey, ret, sigversion));
-
case TX_WITNESS_V0_KEYHASH: //锁定脚本是P2WKH类型
-
ret.push_back(vSolutions[0]);
-
return true;
-
case TX_WITNESS_V0_SCRIPTHASH: //锁定脚本是P2WSH类型
-
CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160.begin());
-
if (provider.GetCScript(h160, scriptRet)) {
-
ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
-
return true;
-
}
-
return false;
-
default:
-
return false;
-
}
-
}
首先是解析交易输入引用的UTXO的锁定脚本,然后根据不同的锁定脚本类型进行签名。此处我们以最常见的P2PKH交易来作为例子,其他的交易类型原理差不多。
(1) 解析锁定脚本
先来过下源码,看看锁定脚本是如何解析的:
-
bool Solver(const CScript& scriptPubKey, txnouttype& typeRet, std::vector<std::vector<unsigned char> >& vSolutionsRet)
-
{
-
// Templates
-
//P2PK/P2PKH/MULTISIG交易的锁定脚本模板
-
static std::multimap<txnouttype, CScript> mTemplates;
-
if (mTemplates.empty())
-
{
-
// Standard tx, sender provides pubkey, receiver adds signature
-
mTemplates.insert(std::make_pair(TX_PUBKEY, CScript() << OP_PUBKEY << OP_CHECKSIG));
-
// Bitcoin address tx, sender provides hash of pubkey, receiver provides signature and pubkey
-
mTemplates.insert(std::make_pair(TX_PUBKEYHASH, CScript() << OP_DUP << OP_HASH160 << OP_PUBKEYHASH << OP_EQUALVERIFY << OP_CHECKSIG));
-
// Sender provides N pubkeys, receivers provides M signatures
-
mTemplates.insert(std::make_pair(TX_MULTISIG, CScript() << OP_SMALLINTEGER << OP_PUBKEYS << OP_SMALLINTEGER << OP_CHECKMULTISIG));
-
}
-
vSolutionsRet.clear();
-
// Shortcut for pay-to-script-hash, which are more constrained than the other types:
-
// it is always OP_HASH160 20 [20 byte hash] OP_EQUAL
-
//锁定脚本是P2SH类型,这种类型的锁定脚本的格式:OP_HASH160 20 [20 byte hash] OP_EQUAL,脚本中2-22为20字节的数据,放到vSolutionsRet中
-
if (scriptPubKey.IsPayToScriptHash())
-
{
-
typeRet = TX_SCRIPTHASH;
-
std::vector<unsigned char> hashBytes(scriptPubKey.begin()+2, scriptPubKey.begin()+22);
-
vSolutionsRet.push_back(hashBytes);
-
return true;
-
}
-
//P2WKH/P2WSH类型的处理
-
int witnessversion;
-
std::vector<unsigned char> witnessprogram;
-
if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
-
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_KEYHASH_SIZE) {
-
typeRet = TX_WITNESS_V0_KEYHASH;
-
vSolutionsRet.push_back(witnessprogram);
-
return true;
-
}
-
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
-
typeRet = TX_WITNESS_V0_SCRIPTHASH;
-
vSolutionsRet.push_back(witnessprogram);
-
return true;
-
}
-
if (witnessversion != 0) {
-
typeRet = TX_WITNESS_UNKNOWN;
-
vSolutionsRet.push_back(std::vector<unsigned char>{(unsigned char)witnessversion});
-
vSolutionsRet.push_back(std::move(witnessprogram));
-
return true;
-
}
-
return false;
-
}
-
// Provably prunable, data-carrying output
-
//
-
// So long as script passes the IsUnspendable() test and all but the first
-
// byte passes the IsPushOnly() test we don't care what exactly is in the
-
// script.
-
if (scriptPubKey.size() >= 1 && scriptPubKey[0] == OP_RETURN && scriptPubKey.IsPushOnly(scriptPubKey.begin()+1)) {
-
typeRet = TX_NULL_DATA;
-
return true;
-
}
-
// Scan templates
-
//P2PKH/P2PK/MULTISIG类型,扫描模板进行解析
-
const CScript& script1 = scriptPubKey;
-
for (const std::pair<txnouttype, CScript>& tplate : mTemplates)
-
{
-
const CScript& script2 = tplate.second;
-
vSolutionsRet.clear();
-
opcodetype opcode1, opcode2;
-
std::vector<unsigned char> vch1, vch2;
-
// Compare
-
//pc1指向UTXO的锁定脚本,pc2指向脚本模板
-
CScript::const_iterator pc1 = script1.begin();
-
CScript::const_iterator pc2 = script2.begin();
-
while (true)
-
{
-
//两个指针同时到了结尾,则找到和模板匹配的交易类型,返回
-
if (pc1 == script1.end() && pc2 == script2.end())
-
{
-
// Found a match
-
typeRet = tplate.first;
-
if (typeRet == TX_MULTISIG)
-
{
-
// Additional checks for TX_MULTISIG:
-
unsigned char m = vSolutionsRet.front()[0];
-
unsigned char n = vSolutionsRet.back()[0];
-
if (m < 1 || n < 1 || m > n || vSolutionsRet.size()-2 != n)
-
return false;
-
}
-
return true;
-
}
-
//得到操作符和操作数
-
if (!script1.GetOp(pc1, opcode1, vch1))
-
break;
-
if (!script2.GetOp(pc2, opcode2, vch2))
-
break;
-
// Template matching opcodes:
-
//几种操作符的处理
-
if (opcode2 == OP_PUBKEYS)
-
{
-
while (CPubKey::ValidSize(vch1))
-
{
-
vSolutionsRet.push_back(vch1);
-
if (!script1.GetOp(pc1, opcode1, vch1))
-
break;
-
}
-
if (!script2.GetOp(pc2, opcode2, vch2))
-
break;
-
// Normal situation is to fall through
-
// to other if/else statements
-
}
-
if (opcode2 == OP_PUBKEY)
-
{
-
if (!CPubKey::ValidSize(vch1))
-
break;
-
vSolutionsRet.push_back(vch1);
-
}
-
else if (opcode2 == OP_PUBKEYHASH)
-
{
-
if (vch1.size() != sizeof(uint160))
-
break;
-
vSolutionsRet.push_back(vch1);
-
}
-
else if (opcode2 == OP_SMALLINTEGER)
-
{ // Single-byte small integer pushed onto vSolutions
-
if (opcode1 == OP_0 ||
-
(opcode1 >= OP_1 && opcode1 <= OP_16))
-
{
-
char n = (char)CScript::DecodeOP_N(opcode1);
-
vSolutionsRet.push_back(valtype(1, n));
-
}
-
else
-
break;
-
}
-
else if (opcode1 != opcode2 || vch1 != vch2)
-
{
-
// Others must match exactly
-
break;
-
}
-
}
-
}
-
vSolutionsRet.clear();
-
typeRet = TX_NONSTANDARD;
-
return false;
-
}
上面的代码针对不同的类型有不同的处理,对于P2PKH/P2PK/MULTISIG,是通过模板来进行匹配处理的。看代码可能不能一下子理解这里的逻辑,画个图来说明一下会非常清楚。以P2PKH为例:
首先锁定脚本的模板如下:
然后看看UTXO的锁定脚本是什么样的,回顾之前创建交易的代码中为交易输出生成锁定脚本的代码(CScriptVisitor),对于P2PKH类型:
-
bool operator()(const CKeyID &keyID) const {
-
script->clear();
-
*script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
-
return true;
-
}
这里要注意下CScript重载的<<操作符的以vector为参数的实现,其中keyID是160位无符号整数,占20字节。根据CScript重载的<<操作符的实现,最终生成的锁定脚本的长下面这样:
20代表脚本中随后的一个元素是20字节的公钥。
根据代码,解析的时候将从模板脚本和锁定脚本的开始处逐一解析每个操作符,首先OP_DUP和OP_HASH什么都不做,指针向前移动即可,进行到下面的状态的时候,需要留神了:
模板的指针好处理,直接前移,但是锁定脚本的处理就不一样了,看看解析操作符和参数的代码,已经根据例子对代码做了注释:
-
bool GetScriptOp(CScriptBase::const_iterator& pc, CScriptBase::const_iterator end, opcodetype& opcodeRet, std::vector<unsigned char>* pvchRet)
-
{
-
opcodeRet = OP_INVALIDOPCODE;
-
if (pvchRet)
-
pvchRet->clear();
-
if (pc >= end)
-
return false;
-
// Read instruction
-
if (end - pc < 1)
-
return false;
-
//在我们的例子中opcode将会是20,然后指针前移到下一个元素(20字节的公钥)
-
unsigned int opcode = *pc++;
-
// Immediate operand
-
//OP_PUSHDATA4的值为0x4e,例子中opcode为20,所以进入分支
-
if (opcode <= OP_PUSHDATA4)
-
{
-
unsigned int nSize = 0;
-
//opcode为20,OP_PUSHDATA1为0x4c,进入此分支
-
if (opcode < OP_PUSHDATA1)
-
{
-
nSize = opcode;
-
}
-
else if (opcode == OP_PUSHDATA1)
-
{
-
if (end - pc < 1)
-
return false;
-
nSize = *pc++;
-
}
-
else if (opcode == OP_PUSHDATA2)
-
{
-
if (end - pc < 2)
-
return false;
-
nSize = ReadLE16(&pc[0]);
-
pc += 2;
-
}
-
else if (opcode == OP_PUSHDATA4)
-
{
-
if (end - pc < 4)
-
return false;
-
nSize = ReadLE32(&pc[0]);
-
pc += 4;
-
}
-
if (end - pc < 0 || (unsigned int)(end - pc) < nSize)
-
return false;
-
//将随后20字节的公钥填充的输出参数中
-
if (pvchRet)
-
pvchRet->assign(pc, pc + nSize);
-
//指针移动20字节
-
pc += nSize;
-
}
-
opcodeRet = static_cast<opcodetype>(opcode);
-
return true;
-
}
最终,20字节的公钥值被提取并保存再来,执行完操作符解析后的状态如下:
(2) 签名
解析完UTXO的锁定脚本后,接下来就要开始签名了,还是以上面的例子来分析,SignStep函数在调用Solver解析完锁定脚本,得到锁定脚本的类型和其中的参数,然后根据不同的锁定脚本类型进行处理,对于P2PKH类型,处理如下:
-
case TX_PUBKEYHASH:
-
keyID = CKeyID(uint160(vSolutions[0]));
-
if (!Sign1(provider, keyID, creator, scriptPubKey, ret, sigversion))
-
return false;
-
else
-
{
-
CPubKey vch;
-
provider.GetPubKey(keyID, vch);
-
ret.push_back(ToByteVector(vch));
-
}
-
return true;
首先拿到Solver函数解析出的20字节的公钥,然后调用Sign1函数进行签名:
-
static bool Sign1(const SigningProvider& provider, const CKeyID& address, const BaseSignatureCreator& creator, const CScript& scriptCode, std::vector<valtype>& ret, SigVersion sigversion)
-
{
-
std::vector<unsigned char> vchSig;
-
if (!creator.CreateSig(provider, vchSig, address, scriptCode, sigversion))
-
return false;
-
ret.push_back(vchSig);
-
return true;
-
}
很简单,通过传入的参数BaseSignatureCreator::CreateSig进行签名,将生成的签名保存在输出参数ret中。这里参数中传入的BaseSignatureCreator是MutableTransactionSignatureCreator类型,回忆下前面ProduceSignature的代码:
-
if (!fHashSingle || (i < mtx.vout.size())) {
-
ProduceSignature(*keystore, MutableTransactionSignatureCreator(&mtx, i, amount, nHashType), prevPubKey, sigdata);
-
}
可以看到,创建MutableTransactionSignatureCreator时,传入了交易、要签名的交易输入的索引,交易输入引用的UTXO的比特币数量以及hashtype,来看看签名的过程:
-
bool TransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
-
{
-
//从keystore中拿到与公钥对应的私钥
-
CKey key;
-
if (!provider.GetKey(address, key))
-
return false;
-
// Signing with uncompressed keys is disabled in witness scripts
-
if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed())
-
return false;
-
//对交易生成hash摘要
-
uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);
-
//用私钥对交易的hash摘要进行数字签名,签名保存在输出参数vchSig中
-
if (!key.Sign(hash, vchSig))
-
return false;
-
vchSig.push_back((unsigned char)nHashType);
-
return true;
-
}
到此为止,对交易的签名就算是完成了。
4.3.2 校验脚本
对交易签名完成以后,交易的输入就有了解锁脚本,接下来就要进行校验,看生成的这个解锁脚本是不是能够和交易输入指向的UTXO的锁定脚本匹配(一把钥匙一把锁,一个萝卜一个坑)。无论是本地还是网络中的其他节点,对于新交易,都必须对脚本进行校验后才能将交易添加到内存交易池里等待被矿工挖取。
4.3.2.1 比特币脚本语言
比特币的脚本语言是一种基于堆栈的非图灵完备的脚本语言。我们知道区块链2.0的一个标识是智能合约,其代表就是以太坊,以太坊提供了solidity语言,利用该语言可以编写出非常复杂的去中心化DApp。比特币的脚本语言的作用和solidity类似,利用该脚本语言同样也可以写出具有复杂逻辑的执行脚本。我们之前介绍的P2PKH就是用脚本语言编写的一个简单脚本的例子。
比特币的脚本语言不支持类似于for的循环,这样可以避免不怀好意的节点编写出具有无限循环的恶意脚本引发DoS攻击。但是像if-else这样的逻辑是支持的。
比特币脚本语言支持的操作符定义在枚举opcode中,这里列出来,读者可以了解一下:
-
/** Script opcodes */
-
enum opcodetype
-
{
-
// push value
-
OP_0 = 0x00,
-
OP_FALSE = OP_0,
-
OP_PUSHDATA1 = 0x4c,
-
OP_PUSHDATA2 = 0x4d,
-
OP_PUSHDATA4 = 0x4e,
-
OP_1NEGATE = 0x4f,
-
OP_RESERVED = 0x50,
-
OP_1 = 0x51,
-
OP_TRUE=OP_1,
-
OP_2 = 0x52,
-
OP_3 = 0x53,
-
OP_4 = 0x54,
-
OP_5 = 0x55,
-
OP_6 = 0x56,
-
OP_7 = 0x57,
-
OP_8 = 0x58,
-
OP_9 = 0x59,
-
OP_10 = 0x5a,
-
OP_11 = 0x5b,
-
OP_12 = 0x5c,
-
OP_13 = 0x5d,
-
OP_14 = 0x5e,
-
OP_15 = 0x5f,
-
OP_16 = 0x60,
-
// control
-
OP_NOP = 0x61,
-
OP_VER = 0x62,
-
OP_IF = 0x63,
-
OP_NOTIF = 0x64,
-
OP_VERIF = 0x65,
-
OP_VERNOTIF = 0x66,
-
OP_ELSE = 0x67,
-
OP_ENDIF = 0x68,
-
OP_VERIFY = 0x69,
-
OP_RETURN = 0x6a,
-
// stack ops
-
OP_TOALTSTACK = 0x6b,
-
OP_FROMALTSTACK = 0x6c,
-
OP_2DROP = 0x6d,
-
OP_2DUP = 0x6e,
-
OP_3DUP = 0x6f,
-
OP_2OVER = 0x70,
-
OP_2ROT = 0x71,
-
OP_2SWAP = 0x72,
-
OP_IFDUP = 0x73,
-
OP_DEPTH = 0x74,
-
OP_DROP = 0x75,
-
OP_DUP = 0x76,
-
OP_NIP = 0x77,
-
OP_OVER = 0x78,
-
OP_PICK = 0x79,
-
OP_ROLL = 0x7a,
-
OP_ROT = 0x7b,
-
OP_SWAP = 0x7c,
-
OP_TUCK = 0x7d,
-
// splice ops
-
OP_CAT = 0x7e,
-
OP_SUBSTR = 0x7f,
-
OP_LEFT = 0x80,
-
OP_RIGHT = 0x81,
-
OP_SIZE = 0x82,
-
// bit logic
-
OP_INVERT = 0x83,
-
OP_AND = 0x84,
-
OP_OR = 0x85,
-
OP_XOR = 0x86,
-
OP_EQUAL = 0x87,
-
OP_EQUALVERIFY = 0x88,
-
OP_RESERVED1 = 0x89,
-
OP_RESERVED2 = 0x8a,
-
// numeric
-
OP_1ADD = 0x8b,
-
OP_1SUB = 0x8c,
-
OP_2MUL = 0x8d,
-
OP_2DIV = 0x8e,
-
OP_NEGATE = 0x8f,
-
OP_ABS = 0x90,
-
OP_NOT = 0x91,
-
OP_0NOTEQUAL = 0x92,
-
OP_ADD = 0x93,
-
OP_SUB = 0x94,
-
OP_MUL = 0x95,
-
OP_DIV = 0x96,
-
OP_MOD = 0x97,
-
OP_LSHIFT = 0x98,
-
OP_RSHIFT = 0x99,
-
OP_BOOLAND = 0x9a,
-
OP_BOOLOR = 0x9b,
-
OP_NUMEQUAL = 0x9c,
-
OP_NUMEQUALVERIFY = 0x9d,
-
OP_NUMNOTEQUAL = 0x9e,
-
OP_LESSTHAN = 0x9f,
-
OP_GREATERTHAN = 0xa0,
-
OP_LESSTHANOREQUAL = 0xa1,
-
OP_GREATERTHANOREQUAL = 0xa2,
-
OP_MIN = 0xa3,
-
OP_MAX = 0xa4,
-
OP_WITHIN = 0xa5,
-
// crypto
-
OP_RIPEMD160 = 0xa6,
-
OP_SHA1 = 0xa7,
-
OP_SHA256 = 0xa8,
-
OP_HASH160 = 0xa9,
-
OP_HASH256 = 0xaa,
-
OP_CODESEPARATOR = 0xab,
-
OP_CHECKSIG = 0xac,
-
OP_CHECKSIGVERIFY = 0xad,
-
OP_CHECKMULTISIG = 0xae,
-
OP_CHECKMULTISIGVERIFY = 0xaf,
-
// expansion
-
OP_NOP1 = 0xb0,
-
OP_CHECKLOCKTIMEVERIFY = 0xb1,
-
OP_NOP2 = OP_CHECKLOCKTIMEVERIFY,
-
OP_CHECKSEQUENCEVERIFY = 0xb2,
-
OP_NOP3 = OP_CHECKSEQUENCEVERIFY,
-
OP_NOP4 = 0xb3,
-
OP_NOP5 = 0xb4,
-
OP_NOP6 = 0xb5,
-
OP_NOP7 = 0xb6,
-
OP_NOP8 = 0xb7,
-
OP_NOP9 = 0xb8,
-
OP_NOP10 = 0xb9,
-
// template matching params
-
OP_SMALLINTEGER = 0xfa,
-
OP_PUBKEYS = 0xfb,
-
OP_PUBKEYHASH = 0xfd,
-
OP_PUBKEY = 0xfe,
-
OP_INVALIDOPCODE = 0xff,
-
};
这些操作符分为几类,比如OP_N(N=0,1,...,16)用来push值,if,else等流程控制的操作符,加减乘除等数学运算操作符,用来操作堆栈的操作符等等。
4.3.2.2 标准的比特币交易
比特币当前支持的标准交易都是用脚本语言编写的可执行脚本的例子,可以理解成是比特币上用脚本语言编写的智能合约。本小节我们简单的过一下这些标准交易。
(1)P2PKH交易
最常见的比特币标准交易,全称为Pay to public key hash的简称。从名字中就可以猜出这种交易是基于公钥的。这种交易会指定新的UTXO的接收者的公钥,花费UTXO的人须提供自己的公钥和交易签名,只有满足下面两个条件时才可以消耗UTXO:
(C1) 消费者提供的公钥的hash必须和UTXO上指定的公钥的hash相同;
(C2) 消费者的签名必须正确(对交易生成摘要D,然后用给定的公钥从签名中求得的交易摘要D‘必须和D相同)
只要能满足上面两个条件,就能证明某人对UTXO的所有权,而能满足上面这两个条件的脚本如下:
Signature | Public key | OP_DU | OP_HASH160 | OP_PUBKEYHASH | OP_EQUALVERIFY | OP_CHECKSIG
将交易的解锁脚本和锁定脚本合并起来就形成了上述脚本,这段脚本最终由脚本引擎解释并执行。
(2) P2PK交易
Pay To Public Key的简称,这种交易更简单,与P2PKH不同的地方在于他不用验证UTXO上指定的公钥与消费者提供的公钥的hash,只检查签名,相应的脚本如下:
Signature | Public Key | OP_CHECKSIG
(3) MULTISIG交易
多重签名交易,这种交易用在要求更加严格的场合,简单的说就是要支出一笔钱,需要得到N个人中至少M个人的签字认可(M <= N)才行。这种交易会在UTXO上指定N个公钥(N个监管人的公钥),需要提取资金时,必须提供至少M个签名,只有所有的签名都能满足才行。以3个人管理一笔资金,提取时必须得到其中2个人的签名为例,满足条件的脚本如下:
0 | Sig1 | Sig2 | 2 | Public Key 1 | Public Key 2 | Public Key 3 | 3 | OP_CHECKMULTISIG
注意这里MULTISIG的脚本必须以0开始,这源自与OP_CHECKMULTISIG操作符在执行过程中的一个bug。
(4) P2SH交易
注意到MULTISIG多重签名的交易存在一些问题,假设A要向B支付一笔资金,但是B要求这笔资金需要MULTISIG,也就是要花这笔钱需要B自己以及他的若干合伙人同时签名。这样A的钱包生成给B转账的交易时,交易的锁定脚本会包含很多长度很长的公钥,导致交易所占用的体积会相当大,这会导致A要多支付不少的交易费用。P2SH交易可以解决这个问题。如果一个交易的锁定脚本非常复杂,就可以考虑用P2SH。
仍旧以2-3多重签名为例,锁定脚本我们假设为S,则:
S = 2 | Public Key 1 | Public Key 2 | Public Key 3 | 3 | OP_CHECKMULTISIG
由于公钥的长度很长,所以上面的锁定脚本最终的体积会很大,如果改用P2SH交易,只需要对上面长长的锁定脚本进行hash处理,生成20字节的脚本hash,这样锁定脚本就变成了下面这样:
OP_HASH160 | [20 bytes script hash] | OP_EQUAL
然后消费者为了支出资金,必须提供一个赎回脚本(redeem script),这个赎回脚本redeem和S是相同的,当执行脚本校验时,首先将消费者提供的赎回脚本进行hash,然后比较hash是否和锁定脚本中的hash相同:
redeem | OP_HASH160 | 20 bytes script hash | OP_EQUAL
如果上一步执行成功,则执行解锁脚本:
Sig1 | Sig2 | 2 | Public Key 1 | Public Key 2 | Public Key 3 | 3 | OP_CHECKMULTISIG
与多重签名相比,P2SH最大的不同是锁定脚本变短了,而解锁脚本变长了,这样额外的交易费变成了由UTXO的消费者来支付,而不是支付者。
以上这些标准交易脚本最终由脚本引擎解释执行,我们在下节在详细说明。
4.3.2.3 交易脚本的运行原理
比特币的脚本引擎是基于堆栈的,脚本解释器会逐一解析脚本的操作符,根据操作符进行入栈和弹栈的操作。仍然以P2PKH交易为例,比特币会将UTXO的锁定脚本和解锁脚本合并在一起,然后在堆栈上进行操作,我们用图来表示整个过程会更清楚一些:
(1)签名入栈
(2)公钥入栈
(3)复制栈顶元素
(4)栈顶元素弹栈,进行hash后的值入栈
(5)公钥hash入栈
(6)OP_EQUALVERIFY将堆栈中的前两个操作数弹栈,并进行比较,相同则继续,不相等出错。
(7)弹出堆栈中的两个操作数,进行签名校验,成功栈顶入栈TRUE,否则栈顶入栈FALSE
最后,如果栈顶元素为TRUE则脚本运行的结果为真,脚本验证通过。
上面描述的交易脚本的运行过程,位于interpreter.cpp(名字很形象,一看就知道文件中是脚本解释器相关的实现)的EvalScript函数中,该函数的原型如下:
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
该函数的参数如下:
stack:输出参数,上文图中的堆栈;
script:要解释的脚本;
flags:校验相关的标识;
checker:用于校验交易签名;
serror:存放错误信息;
EvalScript在执行时,会从头依次开始解析输入脚本,并根据不同的操作符更改堆栈。整个过程就是前文图中描述的那样,读者可以自行在源码中阅读这段代码,这里就不在列出了。
我们在回头看看交易签名的最后一步,当ProduceSignature函数为交易输入生成了签名以后,最后一步就是要进行脚本校验了,如果脚本校验通过,说明确实有权消费交易输入引用的UTXO,该交易随后就可以添加到内存池并广播到网络中了,在SignTransaction的最后:
-
if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
-
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
-
// Unable to sign input and verification failed (possible attempt to partially sign).
-
TxInErrorToJSON(txin, vErrors, "Unable to sign input, invalid stack size (possibly missing key)");
-
} else {
-
TxInErrorToJSON(txin, vErrors, ScriptErrorString(serror));
-
}
-
}
可以看到调用了VerifyScript来校验脚本,传入了交易输入的解锁脚本以及输入引用的UTXO的锁定脚本,另外还有一个TransactionSignatureChecker对象,用于校验交易签名,这里列出VerifyScript的源码供读者参考:
-
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
-
{
-
static const CScriptWitness emptyWitness;
-
if (witness == nullptr) {
-
witness = &emptyWitness;
-
}
-
bool hadWitness = false;
-
set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);
-
if ((flags & SCRIPT_VERIFY_SIGPUSHONLY) != 0 && !scriptSig.IsPushOnly()) {
-
return set_error(serror, SCRIPT_ERR_SIG_PUSHONLY);
-
}
-
//堆栈
-
std::vector<std::vector<unsigned char> > stack, stackCopy;
-
//解析并执行解锁脚本,如果解析过程中出错则返回,以P2PKH交易为例,执行完后堆栈中将保存公钥和签名
-
if (!EvalScript(stack, scriptSig, flags, checker, SigVersion::BASE, serror))
-
// serror is set
-
return false;
-
if (flags & SCRIPT_VERIFY_P2SH)
-
stackCopy = stack;
-
//继续解析执行锁定脚本
-
if (!EvalScript(stack, scriptPubKey, flags, checker, SigVersion::BASE, serror))
-
// serror is set
-
return false;
-
//如果最终堆栈为空,则说明有错误,返回
-
if (stack.empty())
-
return set_error(serror, SCRIPT_ERR_EVAL_FALSE);
-
//堆栈中拿出脚本运行的结果
-
if (CastToBool(stack.back()) == false)
-
return set_error(serror, SCRIPT_ERR_EVAL_FALSE);
-
//其他一些交易类型的处理
-
// Bare witness programs
-
int witnessversion;
-
std::vector<unsigned char> witnessprogram;
-
if (flags & SCRIPT_VERIFY_WITNESS) {
-
if (scriptPubKey.IsWitnessProgram(witnessversion, witnessprogram)) {
-
hadWitness = true;
-
if (scriptSig.size() != 0) {
-
// The scriptSig must be _exactly_ CScript(), otherwise we reintroduce malleability.
-
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED);
-
}
-
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
-
return false;
-
}
-
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
-
// for witness programs.
-
stack.resize(1);
-
}
-
}
-
// Additional validation for spend-to-script-hash transactions:
-
if ((flags & SCRIPT_VERIFY_P2SH) && scriptPubKey.IsPayToScriptHash())
-
{
-
// scriptSig must be literals-only or validation fails
-
if (!scriptSig.IsPushOnly())
-
return set_error(serror, SCRIPT_ERR_SIG_PUSHONLY);
-
// Restore stack.
-
swap(stack, stackCopy);
-
// stack cannot be empty here, because if it was the
-
// P2SH HASH <> EQUAL scriptPubKey would be evaluated with
-
// an empty stack and the EvalScript above would return false.
-
assert(!stack.empty());
-
const valtype& pubKeySerialized = stack.back();
-
CScript pubKey2(pubKeySerialized.begin(), pubKeySerialized.end());
-
popstack(stack);
-
if (!EvalScript(stack, pubKey2, flags, checker, SigVersion::BASE, serror))
-
// serror is set
-
return false;
-
if (stack.empty())
-
return set_error(serror, SCRIPT_ERR_EVAL_FALSE);
-
if (!CastToBool(stack.back()))
-
return set_error(serror, SCRIPT_ERR_EVAL_FALSE);
-
// P2SH witness program
-
if (flags & SCRIPT_VERIFY_WITNESS) {
-
if (pubKey2.IsWitnessProgram(witnessversion, witnessprogram)) {
-
hadWitness = true;
-
if (scriptSig != CScript() << std::vector<unsigned char>(pubKey2.begin(), pubKey2.end())) {
-
// The scriptSig must be _exactly_ a single push of the redeemScript. Otherwise we
-
// reintroduce malleability.
-
return set_error(serror, SCRIPT_ERR_WITNESS_MALLEATED_P2SH);
-
}
-
if (!VerifyWitnessProgram(*witness, witnessversion, witnessprogram, flags, checker, serror)) {
-
return false;
-
}
-
// Bypass the cleanstack check at the end. The actual stack is obviously not clean
-
// for witness programs.
-
stack.resize(1);
-
}
-
}
-
}
-
// The CLEANSTACK check is only performed after potential P2SH evaluation,
-
// as the non-P2SH evaluation of a P2SH script will obviously not result in
-
// a clean stack (the P2SH inputs remain). The same holds for witness evaluation.
-
if ((flags & SCRIPT_VERIFY_CLEANSTACK) != 0) {
-
// Disallow CLEANSTACK without P2SH, as otherwise a switch CLEANSTACK->P2SH+CLEANSTACK
-
// would be possible, which is not a softfork (and P2SH should be one).
-
assert((flags & SCRIPT_VERIFY_P2SH) != 0);
-
assert((flags & SCRIPT_VERIFY_WITNESS) != 0);
-
if (stack.size() != 1) {
-
return set_error(serror, SCRIPT_ERR_CLEANSTACK);
-
}
-
}
-
if (flags & SCRIPT_VERIFY_WITNESS) {
-
// We can't check for correct unexpected witness data if P2SH was off, so require
-
// that WITNESS implies P2SH. Otherwise, going from WITNESS->P2SH+WITNESS would be
-
// possible, which is not a softfork.
-
assert((flags & SCRIPT_VERIFY_P2SH) != 0);
-
if (!hadWitness && !witness->IsNull()) {
-
return set_error(serror, SCRIPT_ERR_WITNESS_UNEXPECTED);
-
}
-
}
-
return set_success(serror);
-
}
可以看到源码中主要就是调用前面提到的EvalScript函数解析脚本并更新堆栈,还是比较容易理解的。
5 交易广播与接收
经过上一节的处理以后,交易的脚本以及脚本的验证都通过,一笔交易就正式创建成功了,接下来就需要将交易加入到节点的 内存交易池中,等待被挖矿,同时节点还要将交易广播到网络中,其他节点对交易进行验证,无误以后也加入到自己的交易池里。
5.1 广播交易
创建好的交易可以通过jsonapi sendrawtransaction命令广播到网络当中,我们直接看这个命令的实现:
-
static UniValue sendrawtransaction(const JSONRPCRequest& request)
-
{
-
//参数检查
-
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
-
throw std::runtime_error(
-
"sendrawtransaction \"hexstring\" ( allowhighfees )\n"
-
"\nSubmits raw transaction (serialized, hex-encoded) to local node and network.\n"
-
"\nAlso see createrawtransaction and signrawtransaction calls.\n"
-
"\nArguments:\n"
-
"1. \"hexstring\" (string, required) The hex string of the raw transaction)\n"
-
"2. allowhighfees (boolean, optional, default=false) Allow high fees\n"
-
"\nResult:\n"
-
"\"hex\" (string) The transaction hash in hex\n"
-
"\nExamples:\n"
-
"\nCreate a transaction\n"
-
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\" : \\\"mytxid\\\",\\\"vout\\\":0}]\" \"{\\\"myaddress\\\":0.01}\"") +
-
"Sign the transaction, and get back the hex\n"
-
+ HelpExampleCli("signrawtransaction", "\"myhex\"") +
-
"\nSend the transaction (signed hex)\n"
-
+ HelpExampleCli("sendrawtransaction", "\"signedhex\"") +
-
"\nAs a json rpc call\n"
-
+ HelpExampleRpc("sendrawtransaction", "\"signedhex\"")
-
);
-
std::promise<void> promise;
-
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL});
-
// parse hex string from parameter
-
//解码出交易
-
CMutableTransaction mtx;
-
if (!DecodeHexTx(mtx, request.params[0].get_str()))
-
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
-
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
-
const uint256& hashTx = tx->GetHash();
-
CAmount nMaxRawTxFee = maxTxFee;
-
if (!request.params[1].isNull() && request.params[1].get_bool())
-
nMaxRawTxFee = 0;
-
{ // cs_main scope
-
LOCK(cs_main);
-
CCoinsViewCache &view = *pcoinsTip;
-
//判断交易是否已经在内存池中,交易是否有输出已经被花费掉了
-
bool fHaveChain = false;
-
for (size_t o = 0; !fHaveChain && o < tx->vout.size(); o++) {
-
const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o));
-
fHaveChain = !existingCoin.IsSpent();
-
}
-
bool fHaveMempool = mempool.exists(hashTx);
-
//如果交易在内存池中不存在,并且交易所有的输出都未花费,则将交易加入内存交易池中
-
if (!fHaveMempool && !fHaveChain) {
-
// push to local node and sync with wallets
-
CValidationState state;
-
bool fMissingInputs;
-
//尝试将交易加入交易池
-
if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs,
-
nullptr /* plTxnReplaced */, false /* bypass_limits */, nMaxRawTxFee)) {
-
if (state.IsInvalid()) {
-
throw JSONRPCError(RPC_TRANSACTION_REJECTED, FormatStateMessage(state));
-
} else {
-
if (fMissingInputs) {
-
throw JSONRPCError(RPC_TRANSACTION_ERROR, "Missing inputs");
-
}
-
throw JSONRPCError(RPC_TRANSACTION_ERROR, FormatStateMessage(state));
-
}
-
} else {
-
// If wallet is enabled, ensure that the wallet has been made aware
-
// of the new transaction prior to returning. This prevents a race
-
// where a user might call sendrawtransaction with a transaction
-
// to/from their wallet, immediately call some wallet RPC, and get
-
// a stale result because callbacks have not yet been processed.
-
CallFunctionInValidationInterfaceQueue([&promise] {
-
promise.set_value();
-
});
-
}
-
} else if (fHaveChain) {
-
throw JSONRPCError(RPC_TRANSACTION_ALREADY_IN_CHAIN, "transaction already in block chain");
-
} else {
-
// Make sure we don't block forever if re-sending
-
// a transaction already in mempool.
-
promise.set_value();
-
}
-
} // cs_main
-
promise.get_future().wait();
-
if(!g_connman)
-
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
-
//发送INV消息,将交易广播到网络中
-
CInv inv(MSG_TX, hashTx);
-
g_connman->ForEachNode([&inv](CNode* pnode)
-
{
-
pnode->PushInventory(inv);
-
});
-
return hashTx.GetHex();
-
}
只有交易在交易池中不存在,并且交易的每一笔输出都未被花费,才能将交易添加到交易池中。
最后会生成一个INV消息加入到集合当中,等待广播到网络中。
5.2 接收交易
再来看看网络中的节点收到一笔新交易后如何处理。ProcessMessage中对INV消息的处理:
-
else if (strCommand == NetMsgType::INV)
-
{
-
//读出数据
-
std::vector<CInv> vInv;
-
vRecv >> vInv;
-
if (vInv.size() > MAX_INV_SZ)
-
{
-
LOCK(cs_main);
-
Misbehaving(pfrom->GetId(), 20, strprintf("message inv size() = %u", vInv.size()));
-
return false;
-
}
-
bool fBlocksOnly = !fRelayTxes;
-
// Allow whitelisted peers to send data other than blocks in blocks only mode if whitelistrelay is true
-
if (pfrom->fWhitelisted && gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY))
-
fBlocksOnly = false;
-
LOCK(cs_main);
-
uint32_t nFetchFlags = GetFetchFlags(pfrom);
-
//处理收到的每一个INV消息
-
for (CInv &inv : vInv)
-
{
-
if (interruptMsgProc)
-
return true;
-
//判断交易是否已经存在于区块链上
-
bool fAlreadyHave = AlreadyHave(inv);
-
LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(), fAlreadyHave ? "have" : "new", pfrom->GetId());
-
if (inv.type == MSG_TX) {
-
inv.type |= nFetchFlags;
-
}
-
//如果是一个区块
-
if (inv.type == MSG_BLOCK) {
-
UpdateBlockAvailability(pfrom->GetId(), inv.hash);
-
if (!fAlreadyHave && !fImporting && !fReindex && !mapBlocksInFlight.count(inv.hash)) {
-
// We used to request the full block here, but since headers-announcements are now the
-
// primary method of announcement on the network, and since, in the case that a node
-
// fell back to inv we probably have a reorg which we should get the headers for first,
-
// we now only provide a getheaders response here. When we receive the headers, we will
-
// then ask for the blocks we need.
-
connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::GETHEADERS, chainActive.GetLocator(pindexBestHeader), inv.hash));
-
LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n", pindexBestHeader->nHeight, inv.hash.ToString(), pfrom->GetId());
-
}
-
}
-
//如果收到的是一笔交易
-
else
-
{
-
pfrom->AddInventoryKnown(inv);
-
if (fBlocksOnly) {
-
LogPrint(BCLog::NET, "transaction (%s) inv sent in violation of protocol peer=%d\n", inv.hash.ToString(), pfrom->GetId());
-
} else if (!fAlreadyHave && !fImporting && !fReindex && !IsInitialBlockDownload()) {
-
//如果交易不存在,则将该交易加入到请求集合中
-
pfrom->AskFor(inv);
-
}
-
}
-
// Track requests for our stuff
-
GetMainSignals().Inventory(inv.hash);
-
}
-
}
如果节点不存在收到的交易hash,则会调用AskFor来请求交易数据,AskFor会将请求加入到队列中(mapAskFor)。之后会遍历该队列,生成GETDATA消息,批量拉取本地缺失的交易(或区块)数据(参考SendMessage函数):
-
//遍历队列
-
while (!pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow)
-
{
-
const CInv& inv = (*pto->mapAskFor.begin()).second;
-
if (!AlreadyHave(inv))
-
{
-
//交易(或者区块)数据不存在,插入vGetData集合,当集合中数据达到1000+时,发送GETDATA消息批量获取数据
-
LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(), pto->GetId());
-
vGetData.push_back(inv);
-
if (vGetData.size() >= 1000)
-
{
-
connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
-
vGetData.clear();
-
}
-
} else {
-
//If we're not going to ask, don't expect a response.
-
//已经存在了,从集合里删除
-
pto->setAskFor.erase(inv.hash);
-
}
-
//从队列中删除
-
pto->mapAskFor.erase(pto->mapAskFor.begin());
-
}
-
//如果集合不空
-
if (!vGetData.empty())
-
connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
长话短说就是等队列中集齐1000条或更多数据时,发送一条GETDATA消息,批量拉取数据。
再来看看节点收到GETDATA消息后如何处理:
-
else if (strCommand == NetMsgType::GETDATA)
-
{
-
//从流中读取数据
-
std::vector<CInv> vInv;
-
vRecv >> vInv;
-
if (vInv.size() > MAX_INV_SZ)
-
{
-
LOCK(cs_main);
-
Misbehaving(pfrom->GetId(), 20, strprintf("message getdata size() = %u", vInv.size()));
-
return false;
-
}
-
LogPrint(BCLog::NET, "received getdata (%u invsz) peer=%d\n", vInv.size(), pfrom->GetId());
-
if (vInv.size() > 0) {
-
LogPrint(BCLog::NET, "received getdata for: %s peer=%d\n", vInv[0].ToString(), pfrom->GetId());
-
}
-
//将所有GETDATA请求添加到集合中
-
pfrom->vRecvGetData.insert(pfrom->vRecvGetData.end(), vInv.begin(), vInv.end());
-
//处理请求
-
ProcessGetData(pfrom, chainparams.GetConsensus(), connman, interruptMsgProc);
-
}
很简单,主要是调用ProcessGetData来处理,继续跟进去一探究竟:
-
void static ProcessGetData(CNode* pfrom, const Consensus::Params& consensusParams, CConnman* connman, const std::atomic<bool>& interruptMsgProc)
-
{
-
AssertLockNotHeld(cs_main);
-
std::deque<CInv>::iterator it = pfrom->vRecvGetData.begin();
-
std::vector<CInv> vNotFound;
-
const CNetMsgMaker msgMaker(pfrom->GetSendVersion());
-
{
-
LOCK(cs_main);
-
//遍历集合
-
while (it != pfrom->vRecvGetData.end() && (it->type == MSG_TX || it->type == MSG_WITNESS_TX)) {
-
if (interruptMsgProc)
-
return;
-
// Don't bother if send buffer is too full to respond anyway
-
if (pfrom->fPauseSend)
-
break;
-
const CInv &inv = *it;
-
it++;
-
// Send stream from relay memory
-
//检查mapRelay或者内存交易池中是否存在交易,如果存在发送TX消息,将交易数据发送给请求方
-
bool push = false;
-
auto mi = mapRelay.find(inv.hash);
-
int nSendFlags = (inv.type == MSG_TX ? SERIALIZE_TRANSACTION_NO_WITNESS : 0);
-
if (mi != mapRelay.end()) {
-
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second));
-
push = true;
-
} else if (pfrom->timeLastMempoolReq) {
-
auto txinfo = mempool.info(inv.hash);
-
// To protect privacy, do not answer getdata using the mempool when
-
// that TX couldn't have been INVed in reply to a MEMPOOL request.
-
if (txinfo.tx && txinfo.nTime <= pfrom->timeLastMempoolReq) {
-
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *txinfo.tx));
-
push = true;
-
}
-
}
-
if (!push) {
-
vNotFound.push_back(inv);
-
}
-
// Track requests for our stuff.
-
GetMainSignals().Inventory(inv.hash);
-
}
-
} // release cs_main
-
if (it != pfrom->vRecvGetData.end() && !pfrom->fPauseSend) {
-
const CInv &inv = *it;
-
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK || inv.type == MSG_CMPCT_BLOCK || inv.type == MSG_WITNESS_BLOCK) {
-
it++;
-
ProcessGetBlockData(pfrom, consensusParams, inv, connman, interruptMsgProc);
-
}
-
}
-
//处理完以后从集合中删除掉
-
pfrom->vRecvGetData.erase(pfrom->vRecvGetData.begin(), it);
-
if (!vNotFound.empty()) {
-
// Let the peer know that we didn't find what it asked for, so it doesn't
-
// have to wait around forever. Currently only SPV clients actually care
-
// about this message: it's needed when they are recursively walking the
-
// dependencies of relevant unconfirmed transactions. SPV clients want to
-
// do that because they want to know about (and store and rebroadcast and
-
// risk analyze) the dependencies of transactions relevant to them, without
-
// having to download the entire memory pool.
-
connman->PushMessage(pfrom, msgMaker.Make(NetMsgType::NOTFOUND, vNotFound));
-
}
-
}
节点会从RelayMap和内存交易池中寻找请求的交易,如果找到将向peer发送TX消息,将交易数据发送给peer。
最后,peer收到TX消息,拿到交易,然后对交易进行校验,验证无误后将交易加入到自己的交易池中,TX消息的处理比较复杂,涉及到孤立交易的处理(两种情况:该交易依赖的交易还没收到,此时收到的交易成为孤立交易;另外一种情况是孤立交易池中有依赖于该交易的孤立交易,当该交易收到以后,需要将孤立交易从鼓励交易池中移走并加入到内存交易池中),我们只截取一部分代码,详细的处理读者可自行阅读源码net_processing.cpp的ProcessMessage方法。
-
else if (strCommand == NetMsgType::TX)
-
{
-
// Stop processing the transaction early if
-
// We are in blocks only mode and peer is either not whitelisted or whitelistrelay is off
-
if (!fRelayTxes && (!pfrom->fWhitelisted || !gArgs.GetBoolArg("-whitelistrelay", DEFAULT_WHITELISTRELAY)))
-
{
-
LogPrint(BCLog::NET, "transaction sent in violation of protocol peer=%d\n", pfrom->GetId());
-
return true;
-
}
-
std::deque<COutPoint> vWorkQueue;
-
std::vector<uint256> vEraseQueue;
-
//从流中读取交易
-
CTransactionRef ptx;
-
vRecv >> ptx;
-
const CTransaction& tx = *ptx;
-
CInv inv(MSG_TX, tx.GetHash());
-
pfrom->AddInventoryKnown(inv);
-
LOCK2(cs_main, g_cs_orphans);
-
bool fMissingInputs = false;
-
CValidationState state;
-
//交易已经获取,从相关集合中删除
-
pfrom->setAskFor.erase(inv.hash);
-
mapAlreadyAskedFor.erase(inv.hash);
-
std::list<CTransactionRef> lRemovedTxn;
-
//如果交易不存在,则调用AcceptToMemoryPool将交易加入到内存交易池中
-
if (!AlreadyHave(inv) &&
-
AcceptToMemoryPool(mempool, state, ptx, &fMissingInputs, &lRemovedTxn, false /* bypass_limits */, 0 /* nAbsurdFee */)) {
-
mempool.check(pcoinsTip.get());
-
RelayTransaction(tx, connman);
最终同样调用AcceptToMemoryPool将交易添加到交易池中。AcceptToMemoryPool这个函数的处理逻辑非常复杂,会对交易进行各种检查,包括交易的格式,交易是否是有双花问题,交易签名的验证等等。这个函数非常重要,建议读者可以仔细阅读该函数的源码加深对交易的理解。AcceptToMemoryPool 函数里会调用CheckInputs检查交易的每一笔输入,其中就包含交易签名的验证:
-
/**
-
* Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
-
* This does not modify the UTXO set.
-
*
-
* If pvChecks is not nullptr, script checks are pushed onto it instead of being performed inline. Any
-
* script checks which are not necessary (eg due to script execution cache hits) are, obviously,
-
* not pushed onto pvChecks/run.
-
*
-
* Setting cacheSigStore/cacheFullScriptStore to false will remove elements from the corresponding cache
-
* which are matched. This is useful for checking blocks where we will likely never need the cache
-
* entry again.
-
*
-
* Non-static (and re-declared) in src/test/txvalidationcache_tests.cpp
-
*/
-
bool CheckInputs(const CTransaction& tx, CValidationState &state, const CCoinsViewCache &inputs, bool fScriptChecks, unsigned int flags, bool cacheSigStore, bool cacheFullScriptStore, PrecomputedTransactionData& txdata, std::vector<CScriptCheck> *pvChecks)
-
{
-
if (!tx.IsCoinBase())
-
{
-
if (pvChecks)
-
pvChecks->reserve(tx.vin.size());
-
// The first loop above does all the inexpensive checks.
-
// Only if ALL inputs pass do we perform expensive ECDSA signature checks.
-
// Helps prevent CPU exhaustion attacks.
-
// Skip script verification when connecting blocks under the
-
// assumevalid block. Assuming the assumevalid block is valid this
-
// is safe because block merkle hashes are still computed and checked,
-
// Of course, if an assumed valid block is invalid due to false scriptSigs
-
// this optimization would allow an invalid chain to be accepted.
-
if (fScriptChecks) {
-
// First check if script executions have been cached with the same
-
// flags. Note that this assumes that the inputs provided are
-
// correct (ie that the transaction hash which is in tx's prevouts
-
// properly commits to the scriptPubKey in the inputs view of that
-
// transaction).
-
uint256 hashCacheEntry;
-
// We only use the first 19 bytes of nonce to avoid a second SHA
-
// round - giving us 19 + 32 + 4 = 55 bytes (+ 8 + 1 = 64)
-
static_assert(55 - sizeof(flags) - 32 >= 128/8, "Want at least 128 bits of nonce for script execution cache");
-
CSHA256().Write(scriptExecutionCacheNonce.begin(), 55 - sizeof(flags) - 32).Write(tx.GetWitnessHash().begin(), 32).Write((unsigned char*)&flags, sizeof(flags)).Finalize(hashCacheEntry.begin());
-
AssertLockHeld(cs_main); //TODO: Remove this requirement by making CuckooCache not require external locks
-
if (scriptExecutionCache.contains(hashCacheEntry, !cacheFullScriptStore)) {
-
return true;
-
}
-
//检查交易的每一笔输入
-
for (unsigned int i = 0; i < tx.vin.size(); i++) {
-
//找到输入指向的UTXO,确保其并未被花费
-
const COutPoint &prevout = tx.vin[i].prevout;
-
const Coin& coin = inputs.AccessCoin(prevout);
-
assert(!coin.IsSpent());
-
// We very carefully only pass in things to CScriptCheck which
-
// are clearly committed to by tx' witness hash. This provides
-
// a sanity check that our caching is not introducing consensus
-
// failures through additional data in, eg, the coins being
-
// spent being checked as a part of CScriptCheck.
-
// Verify signature
-
//校验交易签名
-
CScriptCheck check(coin.out, tx, i, flags, cacheSigStore, &txdata);
-
if (pvChecks) {
-
pvChecks->push_back(CScriptCheck());
-
check.swap(pvChecks->back());
-
} else if (!check()) {
-
if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {
-
// Check whether the failure was caused by a
-
// non-mandatory script verification check, such as
-
// non-standard DER encodings or non-null dummy
-
// arguments; if so, don't trigger DoS protection to
-
// avoid splitting the network between upgraded and
-
// non-upgraded nodes.
-
CScriptCheck check2(coin.out, tx, i,
-
flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, cacheSigStore, &txdata);
-
if (check2())
-
return state.Invalid(false, REJECT_NONSTANDARD, strprintf("non-mandatory-script-verify-flag (%s)", ScriptErrorString(check.GetScriptError())));
-
}
-
// Failures of other flags indicate a transaction that is
-
// invalid in new blocks, e.g. an invalid P2SH. We DoS ban
-
// such nodes as they are not following the protocol. That
-
// said during an upgrade careful thought should be taken
-
// as to the correct behavior - we may want to continue
-
// peering with non-upgraded nodes even after soft-fork
-
// super-majority signaling has occurred.
-
return state.DoS(100,false, REJECT_INVALID, strprintf("mandatory-script-verify-flag-failed (%s)", ScriptErrorString(check.GetScriptError())));
-
}
-
}
-
if (cacheFullScriptStore && !pvChecks) {
-
// We executed all of the provided scripts, and were told to
-
// cache the result. Do so now.
-
scriptExecutionCache.insert(hashCacheEntry);
-
}
-
}
-
}
-
return true;
-
}
代码中通过仿函数CScriptCheck来对交易签名进行校验,以交易、交易输入的索引、交易输入指向的UTXO等作为参数构建CScriptCheck对象,执行校验的过程:
-
bool CScriptCheck::operator()() {
-
const CScript &scriptSig = ptxTo->vin[nIn].scriptSig;
-
const CScriptWitness *witness = &ptxTo->vin[nIn].scriptWitness;
-
return VerifyScript(scriptSig, m_tx_out.scriptPubKey, witness, nFlags, CachingTransactionSignatureChecker(ptxTo, nIn, m_tx_out.nValue, cacheStore, *txdata), &error);
-
}
这里最终还是调用了我们在第4节介绍的VerifyScript函数来执行脚本的校验,这里就不在重复了。
5.3 时序
代码贴了一串又一串,可能很少有读者能耐着性子看完,我们这里用一张简单的图来说明一下整个过程:
阅读源代码过程中只要顺着图里面的这条主线一直往下看,就能捋清楚交易从生成、签名、广播、接收直到交易加入交易池的整个过程。
6 总结
交易是比特币中最重要的一块内容。本文从源码角度,分析了比特币交易的创建、交易的签名、‘’交易脚本的运行、交易的广播和接收。读者可以顺着本文中主线来阅读代码,一旦搞懂了这一部分代码,就能更深刻的理解比特币交易。