CKB目前有xUDT和Spore/DOB两大类资产类型。
两者都有一些扩展功能,但是远不能实现一些复杂的资产。
比如组合资产,例如将两种xUDT组合在一起,或者将xUDT和Spore组合在一起等等。
在讨论具体的解决方案之前,我们先明确一下ckb种两类script(typescript和lockscript)的定位。
lockscript用来决定资产的ownership,对应的就是BTC中的lockscript。而在ETH中对应的是secp256k1+keccak的固定算法,没有可编程性。
typescript用来决定资产的类型,对应的就是ETH中的智能合约,而在BTC中没有对应物。
如果我们想把已有的资产组合起来,并赋予它们额外的业务逻辑。按照ETH的思路,只要我们把已有的资产都transfer给一个智能合约,智能合约就会像机器人一样自主的根据合约代码去操作这些资产。
那么在CKB中如何将资产tranfer给一个typescript呢?
默认情况下,CKB中资产的ownership是由lockscript描述的,换另外一个lockscript就表示资产transfer了。
对于原生资产ckb来说,因为cell里的typescript结构是空着的,所以只要填一个typescript就可以了。这也是现有资产协议,比如xUDT的铸造过程。
但是如果是一个xUDT资产,它的typescript结构已经被占用了,怎么再关联到另外一个typescript?
我们只要把这个cell从一个普通的lockscript换成一个proxylock,这个proxylock指向一个typescript就可以了。
我们先尝试一个非常简单的场景来作为示例。
把一个普通lock锁定的xUDT转给一个什么都不做的typescript(叫Echo),然后再转回普通的lock。
转入交易大概结构如下:
inputs:
Type: xUDT - arg
Lock: userlock
Data: amount(100)
outputs:
--Type: Echo - userlockhash
Lock: userlock
--Type: xUDT - arg
Lock: lock2typeProxy - typehash
Data: amount(100)
转出的交易结构就是刚好反过来,就不再赘述了。
这里的lock2typeProxy 是一个lockscript,但是它不像普通的lock一样去检查签名,而是在交易里查找是否存在某个cell它的typescript的hash刚好跟自己的arg是一样的。有则返回成功,否则返回失败。
可能有人会觉得这里没有实质的lock,资产会不会不安全。其实就像前面类比的ETH的情况,就是把资产托管给合约了,比如AA钱包。ETH的合约也是没有对应的私钥的,安全完全看合约代码。
Echo是一个typescript,但是它没什么逻辑,只是为了保证资产要原路转回,所以在arg中保存了转入的资产原来的userlock的hash。然后执行的时候需要检查自身所在的cell的lock是否跟这个lockhash是一致的,以保证只有原来的user才能赎回资产。
lock2typeProxy 的 arg就是Echo这个typescript的hash。
除了多出来的type为Echo的cell,其实这个cell不放在转入交易里,单独创建也是可以的。
其实我们做的事情就是把xUDT原来普通的userlock,换成了lock2typeProxy - typehash 这样一个特殊的lock。
其中的typehash又是 Echo - userlockhash 这个type的hash。
结合在一起lock2typeProxy(hash(Echo(hash(userlock)))),就是普通userlock在Echo这样一个合约里的对应lock。
认识到这一点之后,资产进入Echo合约之后,不转出的情况下也可以把资产转给另外一个人,只要把资产转给lock2typeProxy(hash(Echo (hash(otheruserlock))))这个lock就可以了。
当然解锁要带上多出来的type为Echo的cell。
熟悉BTC的同学应该到这里能看到,这个思路其实跟P2SH(pay to script hash)是一样的。
都是将所有权转给一个script的hash,解锁的时候要带上scripthash对应的script原文。
其他一切不变,钱包也可以正常支持,只要集成一下lock的对应关系即可:
userlock ↔ lock2typeProxy(hash(Echo(hash(userlock))))
既然标题提到了Taproot,除了前面提到的P2SH,还有一个重要技术是MAST。这个会在下面组合资产的例子中体现。
同样设置一个简单的场景:把特定数量的token和一个spore组合在一起,transfer的时候也要一起,除非销毁这个组合资产。
组合交易的大概结构如下:
inputs:
--Type: xUDT -- arg
Lock: userlock
Data: amount(100)
--Type: Spore -- arg
Lock: userlock
Data: content
outputs:
--Type: Compose -- xUDThash, Sporehash, 100, userlockhash
Lock: userlock
--Type: xUDT -- arg
Lock: lock2typeProxy -- typehash
Data: amount(100)
--Type: Spore - arg
Lock: lock2typeProxy -- typehash
Data: content
这里Compose这个Typescript的逻辑是:input和output里必须有且只能有1个对应Sporehash的spore cell,还有对应xUDThash的xUDT cell,amount必须是100,最后的userlockhash,要跟本cell的userlock对的上。
拆开的交易其实就是组合交易反过来,同样也可以在不拆开的情况下做transfer,只要提前计算出接收用户的lock在Compose合约里对应的lock就可以了。
这个例子要强调的是:
Compose合约的逻辑非常简单,只是约束了组合后的资产要遵循的一些约束,没有对原有资产进行任何干涉。原因是原有资产的typescript(这里就是xUDT和Spore),也在同时起作用,约束了原有资产各自的行为逻辑。
在cell model里,script是做校验的而不是做计算的,所以script可组合性非常好。想象写代码的时候,函数前面那一大堆参数合法性校验,新增一个校验规则基本上不用动原来的代码,哪怕有重复的也没关系。
所以Compose的参数里要放xUDThash和Sporehash,不只是要对资产类型进行约束这么简单。更深层的含义是,xUDT和Spore形成一个组合资产,这个资产的Typescript可以看作是Compose(xUDT, Spore)这样一个组合形态。
这就跟Taproot里的MAST对应起来。
钱包要支持的特殊类型的lock,也跟BTC的P2TR类似。
更复杂的合约会对应更复杂的Typescript组合,也对应着更复杂的lock构造规则。