3,脚本(多重签名) 3.1 通过 “拼接脚本” 来验证 交易的合法性 3.2 利用脚本 来实现“多重签名” 随着脚本的improvement,对于用户来说,交易过程愈加简单。 在最初的script中,当output由多个账户组成时,用户需要手动填写多个账户,从而达成转账。 而当一笔交易需要多个人的签名(多重签名)时,最初的script脚本将这一任务转嫁到了客户身上,随着script的升级,简化了对客户的操作要求,将这一任务直接划入了卖家的工作范畴。
**下图为一个交易实例,**在该交易中有 一个输入,两个输出,该交易的 输入 为 previous transaction的输出,该交易的输出中,有一个 output已经花出去了,另外一个 还未花出(存在 UTXO 中)。从图中可以看出,该交易 已经收到 23 confirmations,也即,其后已经跟了 23 个block,the possibility of double spending is very slim。该交易的 inputscript为一个很长的数,outputscript 为2行很长的数。BTC system使用的script language 很简单,其并没有类似C,C++ 等中的 local variables , global variables,动态分配的内存空间,其访问内存空间的唯一途径 就是 堆栈,所以也被称为 基于栈的脚本语言。
下图为该交易的一些 宏观内容,各个 variable 代表的内容如下: txid:transaction id; hash:H(transaction); version:the version of BTC agreement; size: the size of transaction; locktime:交易的生效时间 (the tansaction comes into effect imediately:the locktime of most of transactions = 0 some special transactions may not equal to 0); “vin” , “vout” 为 输入输出的一些info;(后续给出具体内容); blockhash:该交易所在block的 哈希值; confrimations:该交易 已有 how many confirmations; time:该交易产生的时间(从交易产生到 现在 过了多少秒); blocktime:该block产生的时间(从 block 产生到现在,过了多少秒); 以下为 " vin " 的具体内容: txid 为 input来源交易的id号; vout 表示 input 为 其 来源交易 的 第几个output; scriptsig:为 input script,一般来讲,每个交易的input 只要给出 其BTC来源 和 signature 即可。如果交易有多个input,则需给出多个signature。 以下为 " vout " 的内容: value:为转出的 BTC 金额,如果用BTC的最小单位 ”葱“ 来表示 转账金额的话,其为: 2268万4葱; n:代表 该 output 为交易中的 第几个 output; scriptPubkey:为 output script,在一个transaction中,outputscript至少要给出其publickey; reqsigs:指 该交易 中 需要多少个 signature 才能兑现,在该交易中,仅需一个 signature即可; type:为输出的类型,该交易中为 ”publickey 的 hash“; addresses:为 output的地址; 下图展示了一个 交易 的 输出过程: 图上方一系列 linked block为一个小型的 block chain,在该blockchain中有2个transaction,A->B,B->C,期间隔了2个block。从图中可以看出,the 2nd transaction 的 input 为 the 1st transaction 的 output,the 2nd 交易结构 中的 vout 指向 the 1st 交易结构 中的 output,要验证 the 2nd 交易 的 合法性,只需 将 the inputscript of 2nd transaction attached to the outputscript of the 1st transaction,then run the script,如果堆栈 返回 为 非0 值,即表明该transaction合法,否则,非法。在最初的BTC system中,the verification of transaction 会将 脚本拼接起来后,执行一遍,之后,为了improve the safty of BTC system,verification 会将 the inputscript of 2nd transaction 和 the outputscript of 1st transaction 分别执行。 如果一个交易 有 多个 input,则每个inputscript都需要和对应的outputscript 拼接执行,only if 所有 拼接脚本 执行通过时,才认为该交易为合法交易。 下面介绍 inputscript && outputscript 的几种type: type 一(the simplest one) :P2PK(pay to public key),如下图所示: outputscript : % the outputscript is from the previous transaction PUSHDATA(PubKey) % 直接给出 收款人的Publickey CHECKSIG % 确认签名(input)的操作
inputscript: % the inputscript is from the current transaction PUSHDATA(Sig) %给出(input)的 签名,该签名( privatekey) ,是对 (该input BTC 来源transaction) 整个交易的签名; 下图为 这种 脚本的 执行情况: 在实际的code中,inputscript 和 outputscript 是分别来执行的,为方便起见,本PPT中 将 inputscript 和 outputscript 放在一起执行: PUSHDATA(Sig) %来源于 inputscript,意为:将 signature 压入 栈; PUSHDATA(PubKey) %来源于outputscript,意为:将output publickey 压入 栈; CHECKSIG %将上述两条data从栈中弹出,如果 publickey 与 signature 匹配,则 验证通过,or else,verification不通过,the transaction is illegal。 下图为 Pay to PublicKey 的一个实例: the 1st line number in blue colour 为 current transaction id; inputscripts :from current transaction ,该script给出了 input 的 signature,执行语句为:ScriptSig:PUSHDATA(71); the 2nd line number in blue colour 为 previous transaction id; outputscript:from previous transaction,该script 有 2条 语句: PUSHDATA(PubKey) %给出了 the output of previous transaction 的 Publickey; CHECKSIG %verify the signature given by intputscript type 二:P2PKH (Pay to PublicKey Hash),为 最常用的一种脚本形式,如下图所示: P2PKH 与 P2PK 的 区别在于,P2PKH outputscript 给出的是 public key hash 而非 public key。output of previous transaction 的 public key 是在 inputscripts 中给出的。outputscript中的 语句 DUP; HASH160; 均用于验证 inputscript 中 signature 的正确性。 下图为P2PKH的脚本执行情况:
DUP %把栈顶的元素复制一遍; HASH160 %把栈顶的元素弹出,取hash,然后将得到的hash值 压入栈; PUSHDATA(PubKeyhash) %将output publickey hash 压入栈中;
note that:此时,栈顶的 publickeyhash 是 由 outputscript 压入, 下面的 publickeyhash 是 inputscript 中 Pubkey 经由: DUP; HASH160; 两条语句 得到;
EQUVALVERIFY; %比较栈顶最上方2个 publickeyhash 是否相同,如果相同,则从栈中弹出;
这条语句是为了防止 current transaction 中 input 用户,冒名顶替 the output publickey of previous transaction,如果这两个hash值相等, 则 用户身份 验证通过;
CHECKSIG; %验证 publickey 和 sig 是否匹配,如若match,则验证通过,栈顶留下一个true; 如果上述脚本执行过程中,任何环节出现错误,比如 输入中给出的public key 和 输出里给出的 publickeyhash值 对不上,
indicating that: 有人冒名顶替 previous transaction 中 output的 publickey;
或,输入中给出的sig 和 输入中 给出的 Publickey 对不上,
indicating that:该input用户 并非 账户(publickey of inputscript)的主人;
则,该交易为 illegal。
下图为 P2PKH scripts 的一个 交易实例(开头所举实例,即用 P2PKH): type 三:P2SH (Pay to Script Hash),如下图所示: 在这个scripts中,the outputscript of previous transaction 给出的 并不是 output publickey hash,而是,output 提供的 一个 script 的 hash ,这个hash 被称为 redeemscript hash,之后,如果 output of previous transaction 要花其账户的钱时,其(input of current transaction )要给出 redeemscript 的具体内容,同时还要给出 能让 redeemscript 正确运行 的 sig。
如果 inputscript of current transaction 中给出的 redeemscript取hash后,其hash值 和 outputscript of previous transaction 给出的 redeemscripthash 相等,则证明,input of current transaction 确为 output of previous transaction; 这一步主要用于防止有人冒名顶替 output of previous transaction; 当 确认 the HASH160(redeemscript given by inscripts of current transaction) == the redeemscripthash given by outputscript of previous transaction 后,需要 verify inputscript of current transaction 中 反序列化后的 redeemscript 与 sig 是否匹配,如若匹配,则验证通过;
下面详细说明,如何 用P2SH 来实现 P2PK 的功能:
下图分别给出了 redeemscript,inputscript,outputscript 的 语句块; 获得inputscript of current transaction 和 outputscript of previous transaction后, 开始 第一阶段的验证: 首先,执行 inputscripts,将 Sig 和 seriRS 压入堆栈; then,执行 outputscript,将inputscript 中 给出的 seriRS 取 HASH160,并将其 Hash160 值 压入栈中; then,将 outputscript 中的 redeemScriptHash(RSH) 压入栈中; then,比较 栈顶的2个 redeemscript 的 HASH值 是否相等,如果相等,第一阶段验证通过;
inputscript 和 outputscript 中 redeemscript hash 相等,说明 当前交易input 确为 previous transaction output 账户;
在 第二阶段的验证 中,需要 验证 redeemscript of inputscript 中 给出的 publickey 与 sig 是否匹配,如果match,则说明 当前input 确为 output of previous transaction 账户;
note that:redeemscript 反序列化操作 ,需要由每个节点自行完成;
P2SH在最初 version 的BTC system中是没有的,后来,通过 soft fork的形式 将其嵌入了 BTC system中。
soft fork vs hard fork:
P2SH最为常见的一个应用场景即:多重签名。
使用多重签名 不仅可以 提升 账户的 安全性,同时也可以防范 因 Private key 丢失,而造成BTC无法提取的困境。以公司为例:从公司账户中取BTC,需要使用5个合伙人 中 任意3名合伙人的签名,采用这种方式 进行withdraw ,不仅提高了公司账户的安全性,同时,从5名合伙人 任意 选3名,也有效防止了 private key丢失,account中的BTC无法取出的局面(即便其中2名合伙人 private key 丢失,依然能将 BTC 从 账户中 取出)。
早期的多重签名使用的是 P2PK 的形式,目前已经完全弃用,其多重签名的实现形式如下所示: inputscript of currrent transaction 只有提供 N 个 signature 中的 M个 Sig,即可将 BTC 转出。 下面 P2PK 中,N代表 input account 的 所有 signatures,M 代表 从 input account 中 转出BTC,所需 最少数量的 signatures。需要注意的是,inputscript 和 outputscript 中,signature 和 publickey 的 入栈顺序要一致。
下图,inputscript 中 “红色的叉子” 为应对 BTC system 中的 一个 bug 而生:早期BTC system,CHECKMULTSIG 会从堆栈 中 多弹出一个 元素,如要对此进行修改,则需 software upgrade,对 block chain 进行 hard fork,代价极大。instead,BTC 规定,在inputscript 中,多加入一个 “红色的叉子” 以应对 这个 bug。
上述这种 P2PK 的 多重签名 方式 将 transaction 中 编写script 的 繁琐 都 转嫁 给了 用户,为了 降低 用户 交易的 操作 成本,BTC system 衍生出了 B2SH script 多重签名,如下图所示:
举例说明,P2PK 多重签名 ,如何增加 用户交易的操作成本: 以电商网站中的交易为例:如果商家在交易时,使用 多重签名(即:商家从账户中提取 BTC ,需要多个合伙人的 signature)。那么,当用户 与 商家进行交易时,用户 就必须 将 BTC 转给 商家的 任意 M个 合伙人,要实现这一转账,就要求 用户 在 生成 转账交易 时,将商家的N个合伙人中 任意M名的 publickey 写入 outputscript,这 增加了 用户 transaction 的 复杂度,P2SH 就是为了handle this 而应运而生。
B2SH 多重签名 将 outputscript 的复杂度(用户 生成转账交易时,编写 outputscript的复杂度) 转入 redeemscript of inputscript (商家在其 转账交易中 编写的 redeemscript of inputscript ),用户在 填写transaction outputscript 时,仅需给出 redeemscript hash 值 即可。其他的操作 由 商家 在其 redeemscript of inputscript 中 自行写出。如下图给出的 redeemscript 所示,在P2PK 多重签名 中, outputscript 的 语句块 全部写入了 redeemscript,outputscript 只要给出 redeemscript hash 即可,其操作内容 与 P2PK 多重签名 无甚区别。 对于 商家 来说,当其 想改变 付款规则(如:其合伙人 由 原来的 N个 变为了 N1 个,与此同时,在 withdraw 时,要求 M1 个合伙人 signature),其 只需 将 inputscript 的 Sig 和 redeemscript 中的 pubkey ,M,N, 进行修改即可。用户在与商家交易时,其仅需改变 outputscript 中 的 redeemscript hash。
**note that:**在一个节点的 转账交易 中,该节点 为 input!
**note that:**在上述分析中,用户 指的是 the input of previous transaction,商家指的是 the input of current transaction。 假如,blockchain上有如下2个上链交易: A->B , B->C,则,用户 指的是 A,商家 指的是 B。对应到之前所讲 script 的内容,则 商家(B) 指的是 current transaction 的input,为了实现 B->C 这一交易,其需自行编写 Inputscript of current transaction,而 outputscript of previous transaction 则由 previous transaction 中的 用户(A) 编写。
如下为 block node 验证交易合法性 时 ,需执行的 语句块: 验证的第一阶段,block node 需将 inputscript 和 outputscript 中包含 variables 全部 压入栈,最会 用 EQUAL 语句,验证 inputscript 和 outputscript 中所提供的 redeemscript hash 是否相等,如果相等,则证明 the input of current transaction 确实 为 商家的账户。then,进入第二阶段验证: 在第二阶段的verification中,block node 需要 自行将 redeemscript 反序列化,执行其中指令,判断 redeemscript 中 给出的 pubkey 与 input script 中 给出的 signature 是否相符,如若 相符,则privatekey 验证通过,该input of current transactiion 确为 商家本人。 下图为 P2SH 多重签名 一 交易实例:
至此,BTC system 中的 3种 regular scripts 全部介绍完毕;
除上述几种 scripts,BTC system中还有一种特殊的 script,Proof of Burn(如下图示):
Proof of Burn 的 outputscripts 中,有一个 return 语句,在return 语句 下方,可以 编写anything。 Proof of Burn 的 outputscripts 在执行到 return 时,便会自动 报错返回,因此,写在 return 后面 的 writing 可被 永久保存到 账本,而无需担心 writing 被篡改。 Proof of Burn主要有2个应用场景: 1)一些小的 数字加密货币,要求 用户 销毁 一定数量的BTC 才可获得 其 digital coin(这个coin也被称为 Alternative coin)。此时,用户 可以 采用 Proof of Burn 的方式,销毁BTC,举例说明: clients -> Alt coin,在这个transaction中,clients 利用 Proof of Burn 编写 outputscript of current transaction,这样 BTC given by clients 就会被 fix 到 current transaction,由于outputscript 采用 Proof of Burn script,因此,即便 Alt coin 产生某一交易:Alt coin -> C,在进行 交易合法性 验证,运行 拼接脚本 [inputscript of next transaction(written by Alt coin) , outputscript of previous transaction(written by clients)] 时,when run “return”,BTC system 会报错,交易验证失败,该BTC 无法被spend。 2) 可以利用 Proof of Burn script 的特性,将需要永久保存的 一些 内容 取 hash 后 ,将 hash值 编写 到 outputscript return 之后。 如:clients 可 生成交易 :client -> clientself,以 Proof of Burn script 的方式,将 某知识产权内容 取hash 后,把hash值写在 outputscript 的 return 之后,这样 该hash值 即可 永久保存在 blockchain 中。当发生 知识产权 纠纷 时,clients 可以将 知识产权内容 取 hash,与该transaction中 outputscript return 语句后的 hash值 做对比,据此,可以证明,clients在 某个时间节点之前 ,已经 知道 知识产权的内容。
blockheader 中的 coinbase 域,也可应用于类似的 场景,将 一些想要 永久保存的 内容 写入其中。但是,这种方法,只有获得记账权的node才能使用,即,只有full node才能使用。而Proof of Burn 适用于BTC system的所有用户。 此外,coinbase域,还可作为 Nonce 的扩展,调节 H(blockheader),以使 搜寻的 nonce 满足:H(blockheader) < target。
下图为 Proof of Burn 的 一些 应用实例:
在这一实例中,当full node check 后,返现 Total input = Fees,就会知道,该transaction 的 output 永远无法兑现(生成交易),因此,无需将该transaction写在 UTXO(记录unspent output),这样,可以为 full node 减少管理工作量 and 降低UTXO所需内存。
note that: 以上PPT ,简化了 指令的编写,比如:CHECKSIG,实际 应写为 OP_CHECKSIG。 BTC scripting language 不支持 loop,以防止 系统 出现 死循环 停机(halting problem)。 但是,其密码学related function 很强大,如:仅用一条语句 CHECKSIG ,便能对 signature 进行 验证。
|