比特币中最常用的交易方式 P2PKH 中签名的内容 ‘message’ 的组成
上图是 P2PKH 形式的交易的解锁使用过程。也是比特币最常用的交易方式。
记录一下sig的生成过程。
1 遍历自己地址的可用 UTXO 是否大于 交易的数额 amount。
2 计算交易费用fee
3 如果如果可用UTXO >= fee + amount 则开始组织交易。
1> 生成输出 一个一个的 TxOut,并添加到数组中 (包含转账地址和找零地址)。
2> 根据UTXO生成 TxIn,并添加到数组中。
3> 对 TxIn 数组中的 TxIn 进行逐一签名。
4> 发送交易信息。
其中对TxIn进行逐一签名,签名的包括下面几种类型:
以下表格信息取自: btcd源码解析 - 签名机制(1) - 基础知识
“签名类型”,更准确地称为Signature Hash Types,主要包括4种基本类型:SIGHASH_ALL
, SIGHASH_NONE
, SIGHASH_SINGLE
, SIGHASH_ANYONECANPAY
. 4种类型的简述如下表所示
基本类型 | 简单描述 | 用途 |
---|---|---|
SIGHASH_ALL |
message 中包含所有的output |
默认类型,使用最为广泛 |
SIGHASH_NONE |
message 中不包含任何output |
允许任何人通过构建output 来花费这笔钱 |
SIGHASH_SINGLE |
message 中包含特定 (对应input 的) output |
保证自己的output 不被篡改,但允许其他人的output 被改动 |
SIGHASH_ANYONECANPAY |
message 中包含当前input |
允许任何人构建input ,也即允许任何人往交易里面输入金额 |
容易发现,前三者 (SIGHASH_ALL
, SIGHASH_NONE
, SIGHASH_SINGLE
)主要是用来对output
进行限制,而SIGHASH_ANYONECANPAY
是对input
进行限制。在任何一次签名中,需要指定前三者中的一项,(可选择地)搭配使用SIGHASH_ANYONECANPAY
进行修饰。
分类 | 组合类型 | 简单描述 | 用途 |
---|---|---|---|
无修饰类 | SIGHASH_ALL |
message 中包含所有的input 和output |
默认类型,使用最为广泛 |
无修饰类 | SIGHASH_NONE |
message 中包含所有的input ,不包含任何output |
允许任何人通过构建output 来花费这笔钱 |
无修饰类 | SIGHASH_SINGLE |
message 中包含所有input 和特定 (对应input 的) output |
保证自己的output 不被篡改,但允许其他人的output 被改动 |
有修饰类 | SIGHASH_ALL | SIGHASH_ANYONECANPAY |
message 中包含所有output 和当前input |
常被用来做资金“众筹” |
有修饰类 | SIGHASH_NONE | SIGHASH_ANYONECANPAY |
message 中只包含当前input ,不包含任何output |
允许任何人输入金额,也允许任何人花费金额 |
有修饰类 | SIGHASH_SINGLE | SIGHASH_ANYONECANPAY |
message 中只包含当前input ,和对应的output |
允许任何人输入金额,也保证对应的output 不被篡改 |
这里我们只关注最常见的形式 SIGHASH_ALL
.
1 hashPrevouts; // 所有输入(vin数组)中引用的UTXO的 交易id(hash) 和 序号n 汇总后 做sha256得到的hash。
2 hashSequence; // 所有如数中的 nSequence汇总后 做sha256得到的hash。
3 hashOutputs; // 所有输出 (vout数组) 的 nValue和 scriptPubKey 汇总后 做sha256得到的hash。
除了上述三个变量外,还有其他包括 nVersion 、nIn(一共多少个输入)等等信息组成。
函数调用流程:
其中Solver(…)函数是 获取脚本锁定方式为 P2PKH 还是 P2SH 等等。
CWallet::CreateTransaction(…)
-> ProduceSignature(…)
-> SignStep(…)
-> Solver(…)
->Sign1(…)
-> TransactionSignatureCreator::CreateSig(…)
-> SignatureHash(…)
和 CKey::Sign(…)
;
这里主要贴一下 SignatureHash() 函数
uint256 SignatureHash(const CScript& scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType, const CAmount& amount, SigVersion sigversion, const PrecomputedTransactionData* cache)
{
assert(nIn < txTo.vin.size());
if (sigversion == SIGVERSION_WITNESS_V0) {
uint256 hashPrevouts;
uint256 hashSequence;
uint256 hashOutputs;
const bool cacheready = cache && cache->ready;
// 获取 上述三个主要数据
if (!(nHashType & SIGHASH_ANYONECANPAY)) {
hashPrevouts = cacheready ? cache->hashPrevouts : GetPrevoutHash(txTo);
}
if (!(nHashType & SIGHASH_ANYONECANPAY) && (nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) {
hashSequence = cacheready ? cache->hashSequence : GetSequenceHash(txTo);
}
if ((nHashType & 0x1f) != SIGHASH_SINGLE && (nHashType & 0x1f) != SIGHASH_NONE) {
hashOutputs = cacheready ? cache->hashOutputs : GetOutputsHash(txTo);
} else if ((nHashType & 0x1f) == SIGHASH_SINGLE && nIn < txTo.vout.size()) {
CHashWriter ss(SER_GETHASH, 0);
ss << txTo.vout[nIn];
hashOutputs = ss.GetHash();
}
// 汇总包括上面三个主要数据在内的所有用到的签名数据(message)
CHashWriter ss(SER_GETHASH, 0);
// Version
ss << txTo.nVersion;
// Input prevouts/nSequence (none/all, depending on flags)
ss << hashPrevouts;
ss << hashSequence;
// The input being signed (replacing the scriptSig with scriptCode + amount)
// The prevout may already be contained in hashPrevout, and the nSequence
// may already be contain in hashSequence.
ss << txTo.vin[nIn].prevout;
ss << scriptCode;
ss << amount;
ss << txTo.vin[nIn].nSequence;
// Outputs (none/one/all, depending on flags)
ss << hashOutputs;
// Locktime
ss << txTo.nLockTime;
// Sighash type
ss << nHashType;
return ss.GetHash();
}
其中三个主要数据 hashPrevouts、hashSequence、hashOutputs 的生成过程:
uint256 GetPrevoutHash(const CTransaction& txTo) {
CHashWriter ss(SER_GETHASH, 0);
for (const auto& txin : txTo.vin) {
ss << txin.prevout; // prevout是 COutPoint类型
}
return ss.GetHash();
}
class COutPoint
{
public:
uint256 hash;
uint32_t n;
...
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
// 将 hash 和 n 序列化到 字节流s中
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(hash);
READWRITE(n);
}
...
};
uint256 GetSequenceHash(const CTransaction& txTo) {
CHashWriter ss(SER_GETHASH, 0);
for (const auto& txin : txTo.vin) {
ss << txin.nSequence; // nSequence是int类型
}
return ss.GetHash();
}
uint256 GetOutputsHash(const CTransaction& txTo) {
CHashWriter ss(SER_GETHASH, 0);
for (const auto& txout : txTo.vout) {
ss << txout; // txout 是 CTxOut类型
}
return ss.GetHash();
}
class CTxOut
{
public:
CAmount nValue;
CScript scriptPubKey;
...
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
// 将 nValue 和 scriptPubKey 序列化到 字节流 s中
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(nValue);
READWRITE(scriptPubKey);
}
...
};
CKey::Sign() 函数就是使用 secp256k1 椭圆曲线加密函数库进行签名的过程了:
bool CKey::Sign(const uint256 &hash, std::vector<unsigned char>& vchSig, uint32_t test_case) const
{
if (!fValid)
return false;
vchSig.resize(72);
size_t nSigLen = 72;
unsigned char extra_entropy[32] = {0};
WriteLE32(extra_entropy, test_case);
secp256k1_ecdsa_signature sig;
int ret = secp256k1_ecdsa_sign(secp256k1_context_sign, &sig, hash.begin(), begin(), secp256k1_nonce_function_rfc6979, test_case ? extra_entropy : nullptr);
assert(ret);
secp256k1_ecdsa_signature_serialize_der(secp256k1_context_sign, (unsigned char*)vchSig.data(), &nSigLen, &sig);
vchSig.resize(nSigLen);
return true;
}
通过 SignatureHash() 函数获取到需要签名的所有内容后,调用 Key::Sign() 函数进行签名。然后将签名的信息更新到vin中的 scriptSig字段,就完成了。
*注:本文是个人笔记之用,如有不妥,还望指正。