概述
写在前面
这篇博客主要总结一下之前做过的区块链作业中的一些有趣的东西。
实验环境搭建
按照老师的要求以及助教给的一些问题的解决方案。把python从3.8装回3.7再装回3.6,好像都没什么用。在pycharm运行代码总是说缺少依赖,然而明明已经装过了,点击它给的解决方案安装依赖,然后安装失败。于是我灵机一动,把之前装过的都卸掉了,再点那个安装,还是失败,于是我打开里面的虚拟终端通过命令行来安装。终于不提示缺少依赖。然而运行的时候,提示我的python-bitcoinlib库有问题,点进去查看源码依然毫无头绪。然后打开了虚拟机里的Ubuntu,安装依赖后可以跑代码。于是我的环境终于有了。
领取测试币
进到比特币测试网络领取比特币,在找完公交车或者自行车以及斑马线之后总是卡住不同,于是挂vpn再访问终于施舍给了我点币子。
比特币脚本(暂时了解的)
比特币脚本语言是一种基于栈的脚本语言(每个命令只执行一次),不是图灵完备的。比特币脚本包含两个部分,解锁脚本+锁定脚本,连在一起才是完整的,运行正确之后在栈中返回一个Ture。
公钥支付 P2PK (Pay to Publish Key)
锁定脚本
<pubkey> #公钥
OP_CKECKSIG #验证签名的脚本语言
解锁脚本
<Sig> #签名
OP_CKECKSIG
的部分函数定义,大致看起来就是利用pubkey
生成一个数字签名,再与输入的签名比较,最后如果是ok
就会在栈里留下一个Ture
def _CheckSig(sig, pubkey, script, txTo, inIdx, err_raiser):
key = bitcoin.core.key.CECKey()
key.set_pubkey(pubkey)
if len(sig) == 0:
return False
hashtype = _bord(sig[-1])
sig = sig[:-1]
(h, err) = RawSignatureHash(script, txTo, inIdx, hashtype)
return key.verify(h, sig)
-------------------------------------------------------
elif sop == OP_CHECKSIG or sop == OP_CHECKSIGVERIFY:
check_args(2)
vchPubKey = stack[-1]
vchSig = stack[-2]
tmpScript = CScript(scriptIn[pbegincodehash:])
tmpScript = FindAndDelete(tmpScript, CScript([vchSig]))
ok = _CheckSig(vchSig, vchPubKey, tmpScript, txTo, inIdx,
err_raiser)
if not ok and sop == OP_CHECKSIGVERIFY:
err_raiser(VerifyOpFailedError, sop)
else:
stack.pop()
stack.pop()
if ok:
if sop != OP_CHECKSIGVERIFY:
stack.append(b"x01")
else:
# FIXME: this is incorrect, but not caught by existing
# test cases
stack.append(b"x00")
完整的脚本
<Sig>
----------
<pubkey>
OP_CKECKSIG
执行过程
NULL|初始状态,栈为空
<Sig>|<Sig>压栈
<Sig><pubkey>|<pubkey>压栈
Ture|执行OP_CHECKSIG,成功会把前两个东西pop掉,压入一个Ture
P2PKH(Pay To Public Key Hash)
刚开始不知道公钥哈希值是什么,原来就是我们的地址。私钥通过SHA256得到公钥,公钥经过RIPEMD160得到公钥哈希,公钥哈希经过Base58编码就是地址,某种意义上,地址就是公钥哈希
锁定脚本
OP_DUP
OP_HASH160
<address>
OP_EQUALVERIFY
OP_CHECKSIG
解锁脚本
<sig>
<pubkey>
完整脚本
<sig>
<pubkey>
----------
OP_DUP
OP_HASH160
<address>
OP_EQUALVERIFY #检验两个值是否相等,不会返回值,只会删去栈顶两个元素
OP_CHECKSIG
执行过程
NULL | 初始状态栈为空
<sig> | sig压栈
<sig><pubKey> | pubKey压栈
<sig><pubKey><pubKey> | OP_DUP复制栈顶元素并将其压栈
<sig><pubKey><pubKeyHash> | OP_HASH160计算公钥哈希值并用其替换栈顶
<sig><pubKey><pubKeyHash><pubKeyHash?> | 已知的address压栈,即不知道
<sig><pubKey> | 比较address和pubKeyHash,相等后抛弃栈顶开始的两位,不相等就终止
true | 用CHECKSIG验证签名有效,有效则true,无效则false
多重签名P2MS(Multiple Signatures)
对于一个M-N的交易(M是需要认证的人数,N是总人数,即只要N中M个人的签名),锁定脚本为:
OP_M
<PublicKey 1>
<PublicKey 2>
...
<PublicKey N>
OP_N
OP_CHECKMULTISIG
解锁脚本
OP_0 # OP_CHECKMULTISIG的一个bug,下面会解释
<Sig1>
<Sig2>
...
<SigM> # N个人中随意M个人的签名
完整脚本
OP_0
<Sig1>
<Sig2>
...
<SigM>
----------
OP_M
<PublicKey 1>
<PublicKey 2>
...
<PublicKey N>
OP_N
OP_CHECKMULTISIG
执行过程
NULL |初始状态,栈为空
0 |OP_0将0压栈
0<Sig1><Sig2>···<SigM>|M个签名入栈
0<Sig1><Sig2>···<SigM>M|OP_M把M压栈
0<Sig1><Sig2>···<SigM>M<PublicKey 1><PublicKey 2>···<PublicKey N> |N个PublicKey入栈
True |OP_CHECKMULTISIG进行验证,成功则返回一个ture
OP_0是OP_CHECKMULTISIG的一个bug,不过由于历史遗留问题,debug成本过高,所以就遗留下来了,没什么具体含义,可以当成一个小彩蛋。OP_CHECKMULTISIG所做的具体操作
弹出一个N,得到公钥数量
弹出N个公钥的值
弹出一个M,得到签名数量
弹出M个签名
得到了所有数据,计算脚本是否有效
在弹出M个签名之后,会再pop一次,如果没有OP_0,存在,这时候栈已经空了,pop会出错,脚本没法运行,OP_0就是为了这个bug而生的,弹出的OP_0不会对脚本运行结果产生影响,只是让它能够运行,所以压入什么数应该都可以
脚本哈希支付P2SH(Pay to Script Hash)
一开始以为这个和Multiple Signatures是一样的东西,后来看了很多博客才知道是可以通过这种方法实现P2MS,P2SH是2012年提出的,个人理解这个脚本是为了防止Script过于长而导致交易失败,通过HASH160得到脚本的哈希值,这样就能解决长度问题
P2SH中的脚本包括三部分,赎回脚本(redeem Script)、锁定脚本(locking Script)、解锁脚本(unlocking Script)
赎回脚本(redeem Script)类似于之前脚本里的锁定脚本(locking Script)内容是一致的
锁定脚本(locking Script)形式是固定的:
OP_HASH160
<redeem Script hash> #redeem Script的哈希
OP_EQUAL
解锁脚本(unlocking Script)与赎回脚本相关
···
<Sig>
··· #需要的签名以及其他内容
<serialized redeem Script> #序列化的赎回脚本,作为数据而不作为脚本语言压栈
脚本的执行过程
首先就是解锁的脚本压栈
此时栈顶就是<serialized redeem Script>
然后锁定脚本依次压栈
先得到赎回栈顶的哈希值
再与锁定脚本中的哈希值对比两者不匹配验证就失败
成功则序列化脚本会被反序列化再与栈内的剩余内容(也就是解锁脚本中剩下的所有<Sig>或者其他内容)构成完整的脚本
下面以实现P2PKH与P2MS两种脚本为例写两个P2SH脚本
P2PKH
赎回脚本(redeem Script)
OP_DUP
OP_HASH160
<address>
OP_EQUALVERIFY
OP_CHECKSIG
锁定脚本(locking Script)
OP_HASH160
<redeem Script hash> #redeem Script的哈希
OP_EQUAL
解锁脚本(unlocking Script)
<Sig>
<Pubkey>
<serialized redeem Script>
完整脚本
<Sig>
<Pubkey>
<serialized redeem Script>
----------
OP_HASH160
<redeem Script hash>
OP_EQUAL
P2MS
赎回脚本(redeem Script)
OP_M
<PublicKey 1>
<PublicKey 2>
...
<PublicKey N>
OP_N
OP_CHECKMULTISIG
锁定脚本(locking Script)
OP_HASH160
<redeem Script hash> #redeem Script的哈希
OP_EQUAL
解锁脚本(unlocking Script)
OP_0
<Sig1>
<Sig2>
...
<SigM> # N个人中随意M个人的签名
<serialized redeem Script>
完整脚本
OP_0
<Sig1>
<Sig2>
...
<SigM>
<serialized redeem Script>
----------
OP_HASH160
<redeem Script hash>
OP_EQUAL
作业中的unknown脚本(P2PK+P2MS)
作业要求
- 生成一个涉及四方的多签名交易,这样交易可以由第一方(银行)与另外三方(客户)中的任何一方(客户)共同赎回,而不仅仅只是客户或银行。对于这个问题,你可以假设是银行的角色,这样银行的私钥就是你的私钥,而银行的公钥就是你的公钥。使用keygen.py生成客户密钥并将它们粘贴到ex2a.py中。
- 赎回事务并确保scriptPubKey尽可能小。可以使用任何合法的签名组合来赎回交易,但要确保所有组合都有效
解题思路
该交易中一共有四位参与者,银行与三位客户,需要银行与其他任何一个用户的签名才能赎回交易,如果只用多重签名,那么不需要银行的签名也能赎回,所以我们要把实验分两步,一步是验证银行签名,另外一步是验证客户签名。所以可以用P2PK+P2MS构成的脚本
脚本实现
其实上述两个步骤没有先后顺序,银行和客户身份先验证后验证都无所谓,反正逃不过验证,只不过用的脚本会不一样
先银行后客户
锁定脚本
<pubkey bank> #银行公钥
OP_CHECKSIGVERIFY #使用OP_CHECKSIGVERIFY而不用OP_CHECKSIG是因为它不会有在栈里压数剧,后面脚本执行不会出错
OP_1
<pubkey 1>
<pubkey 2>
<pubkey 3> #三个客户的公钥
OP_3
OP_CHECKMULTISIG
解锁脚本
OP_0
<sig cust>
<sig bank> #注意顺序,因为先验证银行,所以银行的pubkey要在栈顶
完整脚本
OP_0
<sig cust>
<sig bank>
----------
<pubkey bank>
OP_CHECKSIGVERIFY
OP_1
<pubkey 1>
<pubkey 2>
<pubkey 3>
OP_3
OP_CHECKMULTISIG
执行过程
NULL | 起始状态
0 | OP_0压栈
0<sig cust> | <sig cust>压栈
0<sig cust><sig bank> | <sig bank>压栈
0<sig cust><sig bank><pubkey bank> | pubkey bank压栈
0<sig cust> | CHECKSIGVERIFY验证<sig bank>和<pubkey bank>是否匹配并将它们pop掉,匹配则继续,不匹配直接就验证不成功
0<sig cust> 1 | OP_1压栈,确定需要检查的参数
0<sig cust> 1 <pubkey 1><pubkey 2><pubkey 3> | 3个pubkey压栈
0<sig cust> 1 <pubkey 1><pubkey 2><pubkey 3> 3 | OP_3确定3个参数作为检查的范围
true | OP_CHECKMULTISIG检查多重签名,成功返回一个ture,否则验证失败
先客户后银行
原理相同只给出完整脚本(注意脚本指令变了)
<sig bank>
OP_0
<sig cust>
----------
OP_1
<pubkey 1>
<pubkey 2>
<pubkey 3>
OP_3
OP_CHECKMULTISIGVERIFY
<pubkey bank>
OP_CHECKSIG
最后
以上就是义气水池为你收集整理的区块链学习笔记——一些交易脚本(P2PK、P2PKH、P2MS、P2SH)及作业回顾写在前面实验环境搭建领取测试币比特币脚本(暂时了解的)的全部内容,希望文章能够帮你解决区块链学习笔记——一些交易脚本(P2PK、P2PKH、P2MS、P2SH)及作业回顾写在前面实验环境搭建领取测试币比特币脚本(暂时了解的)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复