文章转载来源: CKB 中文
以下内容转载自 Nervos Talk 论坛,作者 Ajian(比特币内容平台 BTC Study 主编)。
摘要
理解一个系统的可编程性要求我们辨识这个系统在结构上的特征。对基于比特币脚本的应用编程的探索,有助于我们理解 CKB Cell 的基本结构及其编程范式。不仅如此,它还能将 CKB 的编程元件分解为恰当的部分,并帮助我们理解每一部分所带来的可编程性增益。
一. 引言
“可编程性(programmability)” 是人们在比较区块链系统时经常采取的一个维度。然而,关于可编程性的描述方法,却常见分歧。一种常见的表述是,“XX 区块链支持图灵完备的编程语言”,或者, “XX 区块链支持通用的编程”,意在表示这里的 “XX 区块链” 具备强大的可编程性。这些语句的暗示有一些道理:支持图灵完备编程的系统一般都比不支持的更容易编程。但是,智能合约系统的结构性特征有多个方面,这一语句只涉及其中一个方面,因此,不足以凭它获得足够深的理解:开发者从中得不到指引,普通用户也无法凭此分辨诈骗。
智能合约系统在结构上的特征包括:
状态表达(合约)的基本形式(账户 vs. 交易输出)是否允许编程任意计算(“图灵完备” 的说法关涉的就是这个方面)执行过程可创造新数据,还是只传出布尔值?(计算 vs. 验证)是否允许在合约内记录额外的状态一个合约在执行的时候是否可以访问另一个合约的状态
所以,在 “可否编程任意计算” 之外,至少还有四个方面的特征会影响一个智能合约系统的可编程性。甚至可以说,这些其它方面的特征是更为重要的,因为它们更深层地决定了什么容易实现、什么难以实现;什么是较为经济的实现,而什么是较为低效的实现。
举个例子,人们常常拿以太坊作为良好可编程性的例子,但是,以太坊的状态表达的基本形式是账户,它难以编程点对点的合约(例如,支付通道、一对一的打赌合约) —— 并非绝对不能实现,只是吃力不讨好。以太坊生态并非从未有过尝试实现 支付通道/状态通道 的项目,理论探讨也有很多,但时至今日这些项目似乎都不活跃了 —— 这显然不能归咎于开发者不努力。如今在以太坊上活跃的项目都采取了 “资金池” 的形式,而非 “点对点合约” 的形式,也不是偶然。同样地,当前人们也许对以太坊的可编程性很满意,但是,若要实现 “账户抽象(account abstract)”(也可以理解为钱包概念的泛化) ,账户模型却可以说是先天不足。
同理,探究 CKB 的可编程性,也要求我们理解 CKB 智能合约系统在这些方面的结构特征。我们已经知晓的是,CKB 允许编程任意计算、允许在合约内记录额外的状态、也允许一个合约在执行时访问另一个合约的状态。但是,其合约的形式是交易的输出(称为 “Cell”),这使得它跟以太坊产生了根本性的差异。因此,对以太坊智能合约系统以及其中的合约实例的了解,并不能帮助我们理解 CKB 是如何实现这些结构特性的,也不能帮助我们认识 CKB 的可编程性。
幸运的是,比特币上的智能合约,似乎为我们理解 CKB 的可编程性提供了最好的基础。这不仅是因为比特币的状态表达的基本形式也是交易的输出(称为 “UTXO”),更是因为,借助比特币社区提出的一个概念 “限制条款(covenants)”,我们可以理解 CKB 具备上述结构特性的原因,并将最终的效果恰当地分拆成几个部分、逐一辨识它们所带来的可编程性增益。
二. CKB v.s. BTC:多了什么?
基本结构
作为比特币状态表达的基本形式,比特币的 UTXO(“未花费的交易输出”)有两个字段:
数额,以聪(Satoshi)为单位,表示该 UTXO 具备的比特币价值;脚本公钥,也称锁定脚本,表示花费这笔资金所需满足的条件,也即为解锁这笔资金设定条件的智能合约程序。
与后来出现的智能合约系统相比,比特币脚本是相当受限的:
它不允许编程任意计算;可用来验证的较为实用的计算只有几种(签名检查、哈希原像检查、时间检查)它不允许在合约内记录额外的状态;(例如,你无法用脚本来限制单次花费的 比例/额度 上限;也无法在其中暗藏一种 token)它也不允许在执行的时候访问另一个合约的状态(每个脚本都是独立的宇宙,互不依赖)。
这种脚本虽然有限,但并不缺乏编程出让人惊叹的应用的能力,而且也正好是我们探索 CKB 可编程性的基础。后文将有专门的一节来介绍比特币脚本编程的两个例子。
与之相对的,CKB 的状态单元称为 “Cell”,有四个字段:
Capacity,类似于 UTXO 的数额,表达的是该 Cell 可以占据的空间大小,以字节(Bytes)为单位;Lock Script,类似于 UTXO 的脚本公钥,定义该 Cell 的所有权;只有所提供的数据能够通过 Lock Script 时,才能 “更新” 这个 Cell(也可以说释放这个 Cell 并用这些 Capacity 来铸造新的 Cell);Data,数据,任意数据,其体积受 Capacity 的限制;Type Script,可选的脚本,用于为 Data 的更新设定条件。
此外,Lock Script 和 Type Script 还可以编程任意计算。你可以编程出任意的签名验证算法,也可以编程出任意一种哈希算法的原像检查,等等等等。
读者很容易就能看出,Cell 相比 UTXO 在可编程性上的提升:
Cell 可以编程任意计算,而不是只能编程特定的几种计算;它的所有权验证会更加灵活;因为 Data 和 Type Script 字段,Cell 可以记录额外的状态;这就允许 Cell 承载所谓的 “UDT(用户自定义的 Token”)。
结合 Cell 本身的 “交易输出” 结构,这两点本身能带来的好处已然非常非常巨大,但是,仅从上面的描述,我们尚不知晓 Cell 是如何实现 “一个合约在运行时访问另一个合约的状态” 的。为此,我们需要借助比特币社区探讨了很长时间的一个概念:“限制条款(covenants)”。
限制条款与内省
限制条款的本意是限制一笔钱能被花到哪里去。在当前的比特币(尚未部署任何限制条款提议)上,一笔资金一旦解锁,就可以花到任何地方(可以支付给任意的脚本公钥)。但限制条款的想法是,可以用某种方式,限制它只能花到某些地方去,比如,某一个 UTXO 将只能被某一笔交易花费,那么,即便有人能够为这个 UTXO 提供签名,它可以花到什么地方也已经被这笔交易决定了。这种功能看起来有点奇怪,却能产生一些有趣的应用,后文会有专门的一节介绍。重要的是,它是我们进一步理解 CKB 可编程性的关键。
Rusty Russell 正确地指出,限制条款可以理解为对交易的 “内省” 能力,即,当一个 UTXO A 被一笔交易 B 花费时,脚本运算程序可以读取交易 B 的部分(或者全部),然后检查它们是否与脚本预先要求的参数一致。例如,交易 A 的第一个输出的脚本公钥,是否与 UTXO A 的脚本公钥所要求的一致(这就是限制条款的最初含义)。
敏锐的读者会意识到,如果具备了完全的内省能力,那么一个交易的输入就可以读取同一交易的另一个输入的状态,这就实现了我们前面说的 “一个合约在运行时访问另一个合约的状态” 的能力。事实上,CKB Cell 正是这么设计的。
基于此,我们又可以将这种完全的内省能力分成四种情形:
Lock Script 读取其它(输入和输出)的 Lock ScriptLock Script 读取其它(输入和输出)的 Type Script(以及 Data)Type Script 读取其它(输入和输出)的 Lock ScriptType Script 读取其它(输入和输出)的 Type Script(以及 Data)
这就允许我们在一定的假设(Lock Script 和 Type Script 的功能分工)之下分析每一部分的内省能力在不同应用场景中的作用,也即分析每一部分为我们带来的可编程性增益。
在下面的两个章节,我们将分别了解当前(尚未限制条款提议)的比特币脚本编程,以及限制条款提议有望实现功能,从而具体地理解 CKB Cell 如何编程并做得更好。
三. 比特币脚本编程
本节将使用 “闪电通道/闪电网络” 和 “谨慎日志合约(DLC)” 作为基于比特币脚本的应用编程的案例。在展开之前,我们要先了解两个概念。
OP_IF 以及 “承诺交易”
第一个概念是比特币脚本中的流程控制操作码,比如:OP_IF 、OP_ELSE。这些操作码跟计算机编程中的 IF 没有什么区别,它的作用就是根据不同的输入执行不同的的语句。在比特币脚本的语境下,这意味着我们可以设置资金的多个解锁路径;搭配时间锁特性,这意味着我们可以分配行动的优先权。
以著名的 “哈希时间锁合约(HTLC)” 为例,这种脚本翻译成大白话就是:
要么,Bob 可以揭晓某个哈希值 H 背后的原像,再给出自己的签名,即可花费这笔资金;
要么,Alice 可以在一段时间 T 过后,凭借自己的签名花费这笔资金。
这种 “要么 …… 要么 ……” 的效果,就是通过流程控制操作码实现的。
HTLC 最突出的优点是它可以将多个操作捆绑在一起、实现原子化。例如,Alice 希望跟 Bob 以 BTC 交换 CKB,那么,Bob 可以先给出一个哈希值,并在 Nervos Network 上创造一个 HTLC;然后 Alice 在比特币上创造一个使用相同哈希值的 HTLC。要么,Bob 在比特币上拿走 Alice 支付的 BTC,同时也揭晓原像,从而允许 Alice 在 Nervos Network 上取走 CKB。要么,Bob 不揭晓原像,两个合约都过期,Alice 和 Bob 都可以取回自己投入的资金。
在 Taproot 软分叉激活之后,这种多解锁路径的特性因为 MAST(默克尔抽象语法树) 的引入而得到进一步的强化:我们可以将一条解锁路径变成默克尔树上的一个叶子,每个叶子都是独立的,因此不再需要使用这样的流程控制操作码;而且,因为揭晓一条路径时无需曝光其它路径,我们可以为一个输出加入更多数量的解锁路径,而不必担心经济性问题。
第二个概念是 “承诺交易”。承诺交易的想法是,在一些情况下,一笔有效的比特币交易,即使它不得到区块链的确认,也同样是真实的,有约束力的。
例如,Alice 和 Bob 共同拥有一个 UTXO,这个 UTXO 需要他们两人的签名才能花费。这时候,Alice 构造一笔交易来花费它,将其中 60% 的价值转移给 Bob,剩下的价值转移给自己;Alice 为这笔交易提供自己的签名,然后发送给 Bob。那么,对 Bob 来说,不必将这笔交易广播到比特币网络中,也不必让这笔交易得到区块链的确认,这笔交易的支付效果也是真实的,可信的。因为 Alice 无法独自花费这个 UTXO(因此无法重复花费),也因为 Alice 所提供的签名是有效的,Bob 随时可以加上自己的签名,然后广播该交易,从而兑现这笔支付。也即,Alice 通过这笔有效的(不上链的)交易,给 Bob 提供了一个 “可信的承诺”。
承诺交易是比特币应用编程的核心概念。如前所述,比特币的合约是基于验证的、无状态的、不允许交叉访问的;但是,如果合约不携带状态,那这些状态存放在哪里、合约如何安全推进(变更状态)?承诺交易给出了直截了当的答案:合约的状态可以用交易的形式来表达,从而,合约的参与者可以自己保存状态,而不必将它们展现在区块链上;合约的状态变更问题,也可以转化成如何安全地更新承诺交易的问题;此外,如果我们担心进入一个合约会有危险(例如,进入一个要求双方都签名才能花费的合约,会面临对方不响应从而卡死的风险),那么,只需提前生成花费该合约的承诺交易并获得签名,就可以化解风险、消除对其他参与者的信任。
闪电通道与闪电网络
闪电通道是一种一对一的合约,在这种合约中,双方可以无限次地相互支付,而不必让任何一次支付获得区块链的确认。如你所料,它用到了承诺交易。
在解释 “承诺交易” 的部分,我们已经介绍了一种支付通道。但是,这种仅利用 2-of-2 多签名的合约仅能实现单向支付。即,要么一直是 Alice 向 Bob 支付,要么一直是 Bob 向 Alice 支付,直至用尽自己在合约中的余额。如果是双向支付,那就意味着,在某一次状态更新之后,一方的余额可能变得比以前更少,但是,TA 却拥有对方签过名的上一笔承诺交易 —— 有什么办法阻止 TA 广播旧的这笔承诺交易、让 TA 只能广播最新一笔承诺交易呢?
闪电通道解决这个问题的办法叫做 “LN-Penalty”。现在,假设 Alice 和 Bob 在一条通道中各拥有 5 BTC;现在 Alice 要给 Bob 支付 1 BTC ,于是签名这样一笔承诺交易,并发送给 Bob:
输入 #0,10 BTC: Alie-Bob 2-of-2 多签名输出(即通道合约)
输出 #0,4 BTC: Alice 单签名
输出 #1,6 BTC: 要么 Alice-Bob 联合临时公钥 #1 单签名 要么 T1 时间锁,Bob 单签名
Bob 也签名一笔(跟上述交易恰成对应的)承诺交易,并发送给 Alice:
输入 #0,10 BTC: Alie-Bob 2-of-2 多签名输出(即通道合约)
输出 #0,6 BTC: Bob 单签名
输出 #1,4 BTC: 要么 Bob-Alice 联合临时公钥 #1 单签名 要么 T1 时间锁,Alice 单签名
这里的诀窍,就在于这个 “联合临时公钥”,它是使用己方的一个公钥和对方提供的一个公钥生成的,例如,Alice-Bob 联合临时公钥,是 Alice 使用自己的一个公钥,和 Bob 提供的一个公钥,各自乘以一个哈希值再相加,得出来的。这样一个公钥,在生成出来的时候,是谁也不知道其私钥的。但是,如果 Bob 把自己所提供的公钥的私钥告诉了 Alice,Alice 就可以计算出这个联合临时公钥的私钥。—— 这就是闪电通道可以 “撤销” 旧状态的关键。
在下一次双方要更新通道状态(发起支付)时,双方就交换上一轮中交给对方的临时公钥的私钥。如此一来,参与者就再也不敢广播自己得到的上一笔承诺交易:这笔承诺交易为己方分配价值的输出有两个路径,而临时公钥路径的私钥已被对方知道;所以一旦广播旧的承诺交易,对方就可以立即动用这个联合临时私钥,从而将这个输出中的资金全部拿走。—— 这就是 “LN-Penalty” 的含义。
具体来说,交互的顺序是:发起支付的一方先向对方请求新的临时公钥,然后构造一笔新的承诺交易并交给对方;得到了承诺交易的一方向对方曝光自己在上一轮给出的临时公钥的私钥。这样的交互顺序保证了参与者总是先得到新的承诺交易,然后才作废自己在上一轮中得到的承诺交易,因此是免信任的。
综上,闪电通道的关键设计有:
双方总是使用承诺交易来表达合约内部的状态,并以数额的变化来表示支付;
承诺交易总是花费同一个输入(需要双方同时提供签名的输入),因此所有承诺交易都是相互竞争的,最终只有一笔能够得到区块链的确认;
两个参与者签名的并不是同一笔承诺交易(虽然它们是成对的);而他们所签名的总是对自己更有利的交易,换句话说,参与者收到的承诺交易,总是对自己不利的;
这种不利体现在,为自己分配价值的输出带有两个解锁路径:一条路径可以凭自己的签名解锁,却需要等待一段时间;而另一条路径则用到了对方的公钥,仅当自己的一个临时私钥不暴露,才受到保护;
在每一次支付中,双方都以新的一笔承诺交易来交换对方在上一轮使用的临时私钥,从而,交出了临时私钥的一方就不再敢广播旧的一笔承诺交易,因此,就 “撤销” 了上一笔承诺交易、更新了合约的状态;(实际上,这些承诺交易都是有效的交易,都是可以广播到区块链上的,只是参与者迫于惩罚,不敢再广播了)
任何一方随时都可以拿对方签过名的承诺交易退出合约;但是,如果双方愿意合作,他们可以签名一笔新的交易,让双方都可以立即拿回属于自己的钱。
最后,因为承诺交易中也可置入 HTLC,所以,闪电通道也可以转发支付。假定 Alice 可以找出一条由闪电通道前后相接所组成的路径、触达 Daniel,那么无需跟 Daniel 开设通道就可以实现免信任的多跳支付。这便是闪电网络:
Alice -- HTLC --
评论(0)