区块链比特币学习 - 1 - 交易
Posted 宣之于口
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了区块链比特币学习 - 1 - 交易相关的知识,希望对你有一定的参考价值。
比特币学习 - 1 - 交易
一、交易概念
1、 交易形式
比特币交易中的基础构建单元是交易输出。在比特币的世界里既没有账户,也没有余额,只有分散到区块链里的UTXO[未花费的交易输出]。
例如,你有20比特币的UTXO并且想支付1比特币,那么你的交易必须消耗掉整个20比特币的UTXO并且产生两个输出:一个是支付了1比特币给接收人,另一个是支付19比特币的找零到你的钱包。
代码目录:src/coins.h
// 一个基本的UTXO,未花费交易输出
class Coin
public:
CTxOut out;
// 该UTXO是否是coinbase交易
unsigned int fCoinBase : 1;
// 包含该UTXO的交易所在区块在区块链上的高度
uint32_t nHeight : 31;
...
;
2、交易结构
代码目录:src/primitives/transaction.h
A. 交易输入
被消耗的UTXO
class CTxIn
public:
COutPoint prevout; // 前一个交易对应的输出,该输入引用的UTXO
CScript scriptSig; // 解锁脚本
uint32_t nSequence; // 指定交易什么时候可以被写到区块链中
CScriptWitness scriptWitness; // ! 隔离见证
// 规则1:如果nSequence被赋值为这个值,交易立刻执行,nLockTime无效,无需考虑锁定时间和要到达那个区块号再执行
static const uint32_t SEQUENCE_FINAL = 0xffffffff;
// 规则2:如果设置了这个变量,那么规则1就失效了[同交易立刻执行,nLockTime无效,无需考虑锁定时间和要到达那个区块号再执行]
static const uint32_t SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31);
// 规则3:如果规则1有效并且设置了此变量,那么相对锁定时间就为512秒,否则锁定时间就为1个区块
static const uint32_t SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22);
// 规则4:如果规则1有效,那么这个变量就用来从nSequence计算对应的锁定时间
static const uint32_t SEQUENCE_LOCKTIME_MASK = 0x0000ffff;
// 区块生成时间:512 = 2^9
static const int SEQUENCE_LOCKTIME_GRANULARITY = 9;
...
;
其中COutPoint
: 用于定位该交易输入的来源(UTXO),起到point的作用
class COutPoint
public:
uint256 hash; // UTXO所在的交易的哈希值
uint32_t n; // UTXO的索引号
...
;
B. 交易输出
由交易创建的UTXO,即未花费交易输出
class CTxOut
public:
CAmount nValue; // 比特币数量
CScript scriptPubKey; // 锁定脚本,决定了谁可以花费这笔UTXO
...
;
C. 交易
下面就是在网络中广播然后被打包进区块的最基本的交易的结构,一个交易可能包含多个交易输入和输出。
class CTransaction
public:
// 默认的交易版本信息。0.13版本以前的代码是1,从0.14版本开始版本是2
static const int32_t CURRENT_VERSION=2;
// 标准交易的最高版本,为的是兼容原来的版本。这个参数是从0.12的版本出现的。
static const int32_t MAX_STANDARD_VERSION=2;
const std::vector<CTxIn> vin; // 交易输入
const std::vector<CTxOut> vout; // 交易输出
const int32_t nVersion; // 版本
const uint32_t nLockTime; // 交易时间锁,用来控制交易的输出只有在一段时间后才能被花费
private:
/** Memory only. */
const uint256 hash;
const uint256 m_witness_hash;
uint256 ComputeHash() const;
uint256 ComputeWitnessHash() const;
...
;
二、交易脚本
1、基本概念
前面提到交易的基本结构,我们注意到比特币流从一个交易流动到了另一个交易,像这样一直传递下去。CTxIn 和 CTxOut 的属性 scriptSig和scriptPubkey 就是钥匙和锁。scriptSig[解锁脚本] 就是用于对应签名的钥匙,而 scriptPubkey[锁定脚本]就是根据地址而生成的锁。
2、数据结构
代码目录:src/script/script.h
CScript 实际上就是一个vector<unsigned char>
也就是说 Script 实际上就是一串 Bytes 流。只不过这个字节流是可以被解析为 <指令> 或者 <指令> <数据> 这样的一个一个元信息。而一个 Script 就是这些元信息组成的字节流。
其中我们需要关注的是指令:它的验证需要一个 VM 来执行(脚本)
/** Script opcodes */
enum opcodetype
OP_0 = 0x00,
OP_FALSE = OP_0,
...
OP_DEPTH = 0x74,
OP_DROP = 0x75,
OP_DUP = 0x76,
OP_NIP = 0x77,
...
OP_AND = 0x84,
OP_OR = 0x85,
OP_XOR = 0x86,
OP_EQUAL = 0x87,
...
;
3、标准交易
A. P2PKH
例如:A支付B 0.1BTC
锁定脚本:OP_DUP OP_HASH160 <B Public Key Hash> OP_EQUAL OP_CHECKSIG
解锁脚本:<B Signature> <B Public Key>
B. P2PK
例如:A支付B 0.1BTC
锁定脚本:<Public Key A> OP_CHECKSIG
解锁脚本:<Signature from Private Key A>
C. MS[多重签名]
例如:M-N多重签名,M是使得多重签名生效的最少数目,如2-3:
锁定脚本:2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG
解锁脚本(3个存档公钥中的任意2个相一致的私钥签名组合予以解锁):OP_0 <Signature B> <Signature C>
D. P2SH
P2SH是MS多重签名的简化版本,对锁定脚本进行加密,验证过程分两步,首先验证的是接收方附上的赎回脚本是否符合发送方的锁定脚本,如果是,便执行该脚本,进行多重签名的验证。这样暂缓节点存储的压力。
锁定脚本(1):2 <Public Key A> <Public Key B> <Public Key C> 3 OP_CHECKMULTISIG
对于锁定脚本(1)首先采用SHA256哈希算法,随后对其运用RIPEMD160算法得到:
锁定脚本(2): OP_HASH160 8ac1d7a2fa204a16dc984fa81cfdf86a2a4e1731 OP_EQUAL
解锁脚本: <Sig1> <Sig2> <2 PK1 PK2 PK3 PK4 PK5 5 OP_CHECKMULTISIG>
4、脚本执行
我们以P2PKH交易为例,A支付B 0.1BTC,来看一下脚本执行流程:
交易输入,包含解锁脚本:<A Signature> <A Public Key>
上一笔交易的输出,包含锁定脚本:OP_DUP OP_HASH160 <A Public Key Hash> OP_EQUAL OP_CHECKSIG
代码目录:src/script/interpreter.cpp
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror)
...
// 基本数据结构:栈
std::vector<std::vector<unsigned char> > stack, stackCopy;
// 执行解锁脚本,执行完之后,stack中包含 <A Signature> <A Public Key>
if (!EvalScript(stack, scriptSig, flags, checker, SigVersion::BASE, serror))
return false;
// 由于下面EvalScript会破坏stack,而再后面仍然需要stack当前的数据,因而需要做一次拷贝
if (flags & SCRIPT_VERIFY_P2SH)
stackCopy = stack;
// 执行锁定脚本
if (!EvalScript(stack, scriptPubKey, flags, checker, SigVersion::BASE, serror))
return false;
...
三、脚本创建
1. 锁定脚本创建
交易输出的锁定脚本如何生成。生成锁定脚本的代码如下:
CScript scriptPubKey = GetScriptForDestination(destination);
代码目录:src/script/standard.cpp
CScript GetScriptForDestination(const CTxDestination& dest)
CScript script;
boost::apply_visitor(CScriptVisitor(&script), dest);
return script;
// 可以看出一个标准的支付脚本
bool operator()(const CKeyID &keyID) const
script->clear();
*script << OP_DUP << OP_HASH160 << ToByteVector(keyID) << OP_EQUALVERIFY << OP_CHECKSIG;
return true;
2. 解锁脚本创建
参考博客:here
交易输入中包含解锁脚本[scriptSig]:<A Signature> <A Public Key>
代码目录:src/script/sign.cpp
A. ProduceSignature
/** 入参
* provider: keystore,存放了公钥-私钥配对
* creator:BaseSignatureCreator类型的实例,用于最后对交易生成签名
* fromPubKey:CScript类型,交易输入引用的UTXO的锁定脚本
* sigData:SignatureData类型,是输出参数,用于存放生成的解锁脚本
*/
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata)
// 进行签名
bool solved = SignStep(provider, creator, fromPubKey, result, whichType, SigVersion::BASE, sigdata);
bool P2SH = false;
CScript subscript;
sigdata.scriptWitness.stack.clear();
// P2SH交易,需要对子脚本进行签名
if (solved && whichType == TX_SCRIPTHASH) ...
// P2WKH交易,需要对见证脚本签名
if (solved && whichType == TX_WITNESS_V0_KEYHASH) ...
// P2WSH交易
else if (solved && whichType == TX_WITNESS_V0_SCRIPTHASH) ...
else if (solved && whichType == TX_WITNESS_UNKNOWN) ...
if (P2SH) ...
// 将生成的解锁脚本写入到sigdata中
sigdata.scriptSig = PushAll(result);
// 校验脚本
sigdata.complete = solved && VerifyScript(sigdata.scriptSig, fromPubKey, &sigdata.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker());
return sigdata.complete;
B. SignStep
static bool SignStep ...
...
// 解析交易输入引用的UTXO的锁定脚本,锁定脚本的类型存在输出参数whichTypeRet中,锁定脚本的数据存放在向量vSolutions中
if (!Solver(scriptPubKey, whichTypeRet, vSolutions))
return false;
// 根据不同的锁定脚本的类型执行签名
switch (whichTypeRet)
...
case TX_PUBKEY:
if (!CreateSig(creator, sigdata, provider, sig, CPubKey(vSolutions[0]).GetID(), scriptPubKey, sigversion)) return false;
ret.push_back(std::move(sig));
return true;
...
C. Solver
它根据传入的scriptPubKey,判断交易输出类型(P2PKH、P2SH、P2WPKH或P2WSH),并返回相应的数据
类型 | 格式 | 返回值 |
---|---|---|
P2SH | OP_HASH160 20 [20 byte hash] OP_EQUAL | 返回20字节的Redeem Script Hash |
P2WPKH | 隔离见证,见证版本见证程序,长度20 | 返回20字节的PubKey Hash |
P2WSH | 隔离见证,见证版本见证程序,长度32 | 返回32字节的Redeem Script Hash |
P2PK | / | 返回65/33字节的PubKey Hash,33为压缩版 |
P2PKH | / | 返回20字节的PubKey Hash |
MS | required A pubkey B pubkey … OP_CHECKMULTISIG | 返回m PubKeys n |
D. CreateSig
bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
CKey key;
if (!provider.GetKey(address, key))
return false;
// 见证脚本必须是压缩版
if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed())
return false;
// 生成交易哈希,用于签名
uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);
// 使用ECDSA椭圆曲线加密算法进行签名
if (!key.Sign(hash, vchSig))
return false;
vchSig.push_back((unsigned char)nHashType);
return true;
四、交易创建
了解前面内容后,我们来看一下一个完整的交易创建,以A向B转账0.1BTC,采用P2PK为例:
代码目录:src/wallet/rpcwallet.cpp
发起转账:
// 转账
static CTransactionRef SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, const CCoinControl& coin_control, mapValue_t mapValue, std::string fromAccount)
CAmount curBalance = pwallet->GetBalance();
// 检查金额
if (nValue <= 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount");
if (nValue > curBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds");
if (pwallet->GetBroadcastTransactions() && !g_connman)
throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled");
// 处理目的地址,生成锁定脚本
CScript scriptPubKey = GetScriptForDestination(address);
// 创建交易
CReserveKey reservekey(pwallet);
CAmount nFeeRequired;
std::string strError;
std::vector<CRecipient> vecSend;
int nChangePosRet = -1;
CRecipient recipient = scriptPubKey, nValue, fSubtractFeeFromAmount;
vecSend.push_back(recipient);
CTransactionRef tx;
if (!pwallet->CreateTransaction(vecSend, tx, reservekey, nFeeRequired, nChangePosRet, strError, coin_control))
if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance)
strError = strprintf("Error: This transaction requires a transaction fee of at least %s", FormatMoney(nFeeRequired));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
// 确认并发送交易
CValidationState state;
if (!pwallet->CommitTransaction(tx, std::move(mapValue), /* orderForm */, std::move(fromAccount), reservekey, g_connman.get(), state))
strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state));
throw JSONRPCError(RPC_WALLET_ERROR, strError);
return tx;
创建交易:
bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CReserveKey& reservekey, CAmount& nFeeRet,int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign)
...
// 金额不能为负,至少有一个收款人
for (const auto& recipient : vecSend) ...
CMutableTransaction txNew;
// 把nLockTime 设置为当前区块高度, 目地是为了防止费用狙击[fee sniping]
txNew.nLockTime = chainActive.Height();
// 偶尔允许nLockTime往前推, 因为确实有些交易会被耽误,比如高延迟混合网络,或者为了保护隐私的压缩交易
if (GetRandInt(10) == 0)
txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
...
/* 第一步:找到所有可用的币 */
std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, true, &coin_control);
// 选币参数
CoinSelectionParams coin_selection_params;
// 改变找零地址的脚本
CScript scriptChange;
/* 第二步:设定找零地址 */
// 已设定了找零地址:直接用
if (!boost::get<CNoDestination>(&coin_control.destChange))
scriptChange = GetScriptForDestination(coin_control.destChange);
// 未设定找零地址:生成新地址
else
// 从钥匙池中生成新地址
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS))
strFailReason = _("Can't generate a change-address key. Private keys are disabled for this wallet.");
return false;
...
scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, change_type));
...
/* 第三步:计算交易需要的最低费率 */
CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control, ::mempool, ::feeEstimator, &feeCalc);
nFeeRet = 0;
bool pick_new_inputs = true;
CAmount nValueIn = 0;
// 从零费用开始循环,直到手续费足够
while (true)
...
/* 第四步:凑出最接近目标值(含手续费)的交易集合 */
setCoins.clear();
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used))
if (bnb_used)
coin_selection_params.use_bnb = false;
continue;
else
strFailReason = _("Insufficient funds"); // 金额不足
return false;
/* 第五步:构造vout, v[0]接收者,v[1]自己*/
// 把接收者加入进vout
txNew.vout.push_back(txout);
// 需要找零
if (nChange > 0)
// 把自己加入进vout
CTxOut newTxOut(nChange, scriptChange);
txNew.vout.insert(position, newTxOut);
else
nChangePosInOut = -1;
/* 第六步:用选出的交易,构造vin */
for (const auto& coin : setCoins)
txNew.vin.push_back(CTxIn(coin.outpoint,CScript()));
/* 第七步:估算手续费 */
...
// 如果没有找零,返回任何key
if (nChangePosInOut == -1) reservekey.ReturnKey();
// 打乱币序,填充最终vin
txNew.vin.clear();
std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end());
std::shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext());
...
// 生成解锁脚本
if (!ProduceSignature(*this, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) ...
...
以上是关于区块链比特币学习 - 1 - 交易的主要内容,如果未能解决你的问题,请参考以下文章