上图左边是一笔交易中的输入(vin)引用一个输出(UTXO)是需要提供的解锁脚本 —— 签名(通过私钥获得) 和 公钥。
上图右边是UTXO的花费条件,即满足这个条件才能花费本UTXO。
比特币的脚本语言被称为基于堆栈的语言,因为它使用一种被称为堆栈的数据结构。堆栈是一个非常简单的数据结构,
可以被视为一叠卡片。栈允许两个操作:push和pop(推送和弹出)。 Push(推送)在堆栈顶部添加一个项 目。
Pop(弹出)从堆栈中删除最顶端的项。栈上的操作只能作用于栈最顶端项目。堆栈数据结构也被称为“后进 先出”
(Last-In-First-Out)或 “LIFO” 队列。 脚本语言通过从左到右处理每个项目来执行脚本。数字(数据常量)
被推到堆栈上。操作码(Operators)从堆栈 中推送或弹出一个或多个参数,对其进行操作,并可能将结果推送到
堆栈上。例如,操作码 OP_ADD 将从堆栈中 弹出两个项目,添加它们,并将结果的总和推送到堆栈上。 条件操作码
(Conditional operators)对一个条件进行评估,产生一个 TRUE 或 FALSE 的布尔结果(boolean result)。
例如, OP_EQUAL 从堆栈中弹出两个项目,如果它们相等,则推送为 TRUE(由数字1表示),否则推 送为 FALSE
(由数字0表示)。比特币交易脚本通常包含条件操作码,以便它们可以产生用来表示有效交易的 TRUE 结果。
在比特币的脚本验证中,执行简单的数学运算时,脚本“ 2 3 OP_ADD 5 OP_EQUAL ”演示了算术加法
操作码 OP_ADD ,该操作码将两个数字相加,然后把结果推送到堆栈, 后面的条件操作符 OP_EQUAL 是验算
之前的 两数之和是否等于 5 。
push 2 —— 将2入栈
push 3 —— 将3入栈
a = pop; b = pop; s = a ADD b; push s; (伪码表示) —— 取出栈顶的两个元素,并做相加操作并将结果入栈
push 5 —— 将5 入栈
c = pop; d = pop; r = (c EQUAL d) ? TRUE : FALSE; push r; —— 取出栈顶的两个元素,并做判断相等操作并将结果入栈
其中 (c EQUAL d) ? TRUE : FALSE; 是一个运算符,逻辑是如果 ?前面的值为true 则 返回 :前面的,否则返回:后面的。
比特币网络处理的大多数交易花费的都是由“付款至公钥哈希”(或P2PKH)脚本锁定的输出,这些输出都含有一个 锁定脚本,将输入锁定为一个公钥哈希值,即我们常说的比特币地址。由P2PKH脚本锁定的输出可以通过提供一个 公钥和由相应私钥创建的数字签名来解锁(使用)。
我们以Alice向Bob咖啡馆支付的案例:
OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
脚本中的 Cafe Public Key Hash 即为咖啡馆的比特币地址,但该地址不是基于Base58Check编码。事实上,大多数比特币地址的公钥哈希值都显示为十六进制码,而不是大家所熟知的以1开头的基于Bsase58Check编码的比特币地址。
上述锁定脚本相应的解锁脚本是:
<Cafe Signature> <Cafe Public Key>
将两个脚本结合起来可以形成如下组合验证脚本:
<Cafe Signature> <Cafe Public Key> OP_DUP OP_HASH160 <Cafe Public Key Hash> OP_EQUALVERIFY OP_CHECKSIG
只有当解锁脚本与锁定脚本的设定条件相匹配时,执行组合验证脚本时才会显示结果为真(TRUE)。换句话说, 只有当解锁脚本得到了咖啡馆的有效签名,交易执行结果才会被通过(结果为真),该有效签名是从与公钥哈希相 匹配的咖啡馆的私钥中所获取的。
push <sig>
push <PubK>
DUP 命令为 拷贝一份栈顶元素并将其入栈
HASH160 <PubKHash> 命令为 弹出栈顶元素并做 Hash160操作,然后将结果入栈
EQUALVERIFY 命令为 连续弹出两个栈顶元素,并对两个元素做相等判断,如果不等则直接结束本次脚本验证并返回FALSE
CHECKSIG 命令为 验证签名<sig> 是否为<PubK>的正确签名,并将结果 相等(TRUE) 或 不相等 (FALSE) 入栈
主要看一下CHECKSIG对应的代码:
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* serror)
{
......
case OP_CHECKSIG:
case OP_CHECKSIGVERIFY:
{
// (sig pubkey -- bool)
if (stack.size() < 2)
return set_error(serror, SCRIPT_ERR_INVALID_STACK_OPERATION);
valtype& vchSig = stacktop(-2);
valtype& vchPubKey = stacktop(-1);
// Subset of script starting at the most recent codeseparator
CScript scriptCode(pbegincodehash, pend);
// Drop the signature in pre-segwit scripts but not segwit scripts
if (sigversion == SIGVERSION_BASE) {
scriptCode.FindAndDelete(CScript(vchSig));
}
if (!CheckSignatureEncoding(vchSig, flags, serror) || !CheckPubKeyEncoding(vchPubKey, flags, sigversion, serror)) {
//serror is set
return false;
}
bool fSuccess = checker.CheckSig(vchSig, vchPubKey, scriptCode, sigversion);
if (!fSuccess && (flags & SCRIPT_VERIFY_NULLFAIL) && vchSig.size())
return set_error(serror, SCRIPT_ERR_SIG_NULLFAIL);
popstack(stack);
popstack(stack);
stack.push_back(fSuccess ? vchTrue : vchFalse);
if (opcode == OP_CHECKSIGVERIFY)
{
if (fSuccess)
popstack(stack);
else
return set_error(serror, SCRIPT_ERR_CHECKSIGVERIFY);
}
}
break;
......
}
EvalScript 中对应的是执行各种命令的方法。
其中OP_CHECKSIG主要的验证方法为 checker.CheckSig();
其方法是TransactionSignatureChecker类中重写的CheckSig函数。
bool TransactionSignatureChecker::CheckSig(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
{
CPubKey pubkey(vchPubKey);
if (!pubkey.IsValid())
return false;
// Hash type is one byte tacked on to the end of the signature
std::vector<unsigned char> vchSig(vchSigIn);
if (vchSig.empty())
return false;
int nHashType = vchSig.back();
vchSig.pop_back();
uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata);
if (!VerifySignature(vchSig, pubkey, sighash))
return false;
return true;
}
然后最终调用到了CPubKey::Verify() 函数:
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const
{
if (!IsValid())
return false;
secp256k1_pubkey pubkey;
secp256k1_ecdsa_signature sig;
if (!secp256k1_ec_pubkey_parse(secp256k1_context_verify, &pubkey, &(*this)[0], size())) {
return false;
}
if (!ecdsa_signature_parse_der_lax(secp256k1_context_verify, &sig, vchSig.data(), vchSig.size())) {
return false;
}
/* libsecp256k1's ECDSA verification requires lower-S signatures, which have
* not historically been enforced in Bitcoin, so normalize them first. */
secp256k1_ecdsa_signature_normalize(secp256k1_context_verify, &sig, &sig);
return secp256k1_ecdsa_verify(secp256k1_context_verify, &sig, hash.begin(), &pubkey);
}
可以看到,最终验证 签名和公钥是否匹配是用到了 secp256k1库中的函数。这里就不再往下分析了,我自己也没有看,感兴趣的可以看一下 secp256k1 这个库的用法。
文中大部分内容摘自 《精通比特币》。
*注:本文是个人笔记之用,如有不妥,还望指正。
欢迎关注 [懒人漫说] 公众号,期待与你一起分享技术。