附录: CoBuild Hashes
本节详细介绍 Cobuild 流程中用到的各种 hash 算法。
Signing Message Hash
下面用 signing message hash
来表示 lock script 校验签名时,签名对应的数据。
Lock 的实现需要考虑钱包运行环境的影响。由于 CKB 具有模仿其他链交易签名算法的能力,CKB 用户可能使用其他链的钱包来操纵 CKB 上的资产。在这种场景中,lock 就需要考虑非 CKB 专门钱包的签名流程中 signing message 是如何计算的。由于我们无法调整这些非 CKB 专门钱包的 signing message 计算流程,而这些钱包的signing message 计算流程通常又不一样,因此 CKB lock script 实际计算 signing mesage hash
时,也不可能有统一的算法和流程。因此 CoBuild 协议只定义 signing message hash
中需要覆盖的 CKB 交易中的相关数据。CoBuild-compatible lock script 只需要确保这些必须包含的数据都被 signing message hash
覆盖即可保证安全性。
计算 signing message hash
时,首先根据当前流程执行是处于 SighashAll 还是 Otx 模式,从相应的位置取出 Message
。在所有情况下, Message
都是 lock script 一定要包含在 signing message hash
中的内容。Lock script 还需要获取与当前交易相关的一些其他数据,比如当前 tx hash,某些 cell 的内容等。然后 lock script 需要根据所处模式的不同,将 CoBuild 协议这里所规定的内容,与其他 lock script 自身逻辑需要的信息拼接起来,经过一次或多次 cryptographic hashing,即可构造出 CoBuild-compatible lock 验签使用的 signing message hash
。
虽然 CoBuild 流程对于 signing message hash
需要覆盖的 Message
,tx hash,cell data 这些内容没有强制的顺序要求,但是在构造 cryptographic hash 时,需要尽可能做到不同的交易会向 hash function 提供不同的源数据。
举个例子:假设一个 CoBuild-compatible lock 的 signing message hash
覆盖了 Message
和某个 cell data(注意实际场景中需要被 signing message hash
覆盖的数据不只这些,这里只是一个简单的例子),lock 的具体做法是把 Message
和 cell data 直接字符串拼接,然后进行一次 hash,得到 signing message hash
。那么对于如下两种情况,这个方法生成的 signing message hash
是完全一样的:
Message
为0xaabbccddaabbccdd
,而 cell data 为0x10101010
Message
为0xaabbccdd
,而 cell data 为0xaabbccdd10101010
这种算法显然不够好,因为对于两个不同的交易,两个不同 Message
,最后构造出的 signing message hash
是一样的,这就为伪造交易的提供了可能性。
为了避免这种问题,通常的一个做法,是 lock script 可以把一段数据的长度,拼接在这段数据的最前面。比如上面的例子,cell data 的长度分别为 4 或者 8。如果我们把 cell 的长度也作为信息 hash 到 signing message hash
中,这两种情况最终就会生成不同的 signing message hash
了。
同时注意:CoBuild 流程中会用到很多 molecule 格式的数据结构,而 molecule 具有 canonical encoding 的性质。简单来说,以 molecule 格式表述的数据结构,要么只有固定的长度,要么像 Message
这种,它的前四个字节已经包含了整个数据结构的长度。所以对于以 molecule 格式表达的数据结构,无需手动在数据前面拼上长度也能避免上述的问题。
SighashAll Variant
SighashAll
模式下, signing message hash
应覆盖如下内容:
- 当前交易中,唯一存在的
SighashAll
结构中包含的Message
结构。由于Message
已经是一个 molecule 格式的数据结构 ,我们在这里拼接数据计算signing message hash
时,并不需要把Message
部分的长度再包含在 hash 中。 - 可以通过 CKB syscall 获取的 tx hash,注意 tx hash 永远是 32 字节,也不需要再额外拼接 tx hash 的长度。
- 可以通过 CKB_syscall 获取的所有 input cell 和 data, 由于 tx hash 已经覆盖了 inputs 数量, 在这里拼接的时候不需要把再加入其中。input cell 本身以 molecule 格式的 CellOutput 结构表述,无需再拼接长度,但是 data 本身是一段可变长度的无格式二进制数据,为确保安全需要在前面拼接 data 数据的长度。
- 超出 input cell 个数的所有 witness 的长度及内容。
计算 signing message hash
时,可以按照确定的顺序将这些内容全部拼接到一起,需要的话可以再加上 lock script specific 的数据,然后经过一次 cryptographic hash 计算得到结果。
注意这个计算方法只是一个例子,实际上 CoBuild 只要求 lock script 确保这些数据都被 cryptographic hash 覆盖即可。具体的计算方式,只要是确定定义好的,均可实现 cobuild 模式下的安全性。
再举个例子,对另一些(也许有特殊需求的)lock script,可以用如下算法:
- 首先把 tx hash,所有的 input cell 和 data,超出 input cell 个数的所有 witness 长度及内容拼接在一起,单独为这部分数据计算一次 cryptographic hash。假设我们这里把计算出的 hash 称之为
skeleton hash
- 接下来,lock script 把一段固定的 prefix,
Message
结构以及skeleton hash
拼接到一起,再计算一次 cryptographic hash,得到的结果,即作为signing message hash
。
以下图为例:
一个 CoBuild-compatible lock script 验签时,应该在 signing message hash
中包含如下内容:
- 当前交易中
SighashAll
类型的 witness 中包含的Message
结构 - 当前的 tx hash
- I1 对应的 CellOutput, 以 molecule 格式 序列化
- I1 对应的 CellOutput Data, 以 little-endian encoding 的
u32
表示的 Data 长度和 Data 的实际内容 - I2 对应的 CellOutput, 以 molecule 格式 序列化
- I2 对应的 CellOutput Data, 以 little-endian encoding 的
u32
表示的 Data 长度和 Data 的实际内容 - 以 little-endian encoding 的
u32
表示的 Witness 3 的长度和 Witness 3 的实际内容 - 以 little-endian encoding 的
u32
表示的 Witness 4 的长度和 Witness 4 的实际内容 - 以 little-endian encoding 的
u32
表示的 Witness 5 的长度和 Witness 5 的实际内容
SighashAllOnlyVariant
WitnessLayout
除了 SighashAll
Variant, 还有一个比较特殊的 SighashAllOnly
Variant,这个 Variant 中只有 seal
没有 message
。 如果在一个交易中,所有的 witness 都是 SighashAllOnly
类型,那意味着这个交易没有包含任何 Message
。
在这种情况下,lock script 验签时,应该在 signing message hash
中覆盖如下内容:
- 可以通过 CKB syscall 获取的 tx hash
- 可以通过 CKB_syscall 获取的所有 input cell 和 data, 由于 tx hash 已经覆盖了 inputs 数量, 在这里拼接的时候不需要把再加入其中
- 超出 input cell 个数的所有 witness 的长度及内容
注意如果一个 lock script 要同时支持包含和不包含 Message
的模式,应该通过添加不同的 prefix 或者利用哈希算法自带的方法(比如 blake2b 算法提供的 personalization),区分开包含 Message
以及不包含 Message
的两类 transaction,以避免这两类不同的 transaction,误生成相同的 signing message hash
。
Otx Variant
Otx 模式下, signing message hash
应覆盖如下内容:
- 对应当前 Otx 的 witness 中包含的
Message
结构 - Otx 覆盖的 input cells 的个数,以
CellInput
格式表示的所有 input cells 的内容, 以CellOutput
格式表达原始数据, 以及以 raw data 形式存在的原始 Cell Data - Otx 覆盖的 output cells 的个数,以及所有 output cells 的内容,包含以 CellOutput 格式表达的结构,和 raw data 形式存在的 cell data
- Otx 覆盖的 cell deps 的个数,以及以 CellDep 格式表示的所有 cell deps 的内容
- Otx 覆盖的 header deps 的个数,以及以 Byte32 格式表示的所有 header deps 的内容
注意这里以 raw data 形式存在的 cell data 是变长的无格式字符串,为确保安全,需要在这些 cell data 的前面相应拼接上 cell data 字段的长度。除此之外,这里包含的其他数据结构均为 molecule 类型不需要再拼接长度。
计算 signing message hash
时,可以按照确定的顺序将这些内容全部拼接到一起,需要的话可以再加上 lock script specific 的数据,然后经过一次 cryptographic hash 计算得到结果。
出于安全性的考虑,我们建议区分开 SighashAll
模式与 Otx
模式的交易,在计算 signing message hash
时使用不同的 hashing prefix 或是 hash function 自带的方法(如 blake2b 中用不同的 personalization)。
更具体的说,CoBuild 规范建议为如下三个不同场景,都使用不同的 prefix:
SighashAll
模式,带有Message
的交易SighashAllOnly
模式,没有Message
的交易Otx
模式的交易
以下图为例:
一个 CoBuild-compatible lock script 验签时,应该在 signing message hash
中覆盖如下内容:
- 当前 otx 中 唯一的,以
Otx
类型存在的 witness 中包含的Message
结构 2
(表示 input cells 的个数,以 little endian encoding 的 u32 表示)- I1 对应的 CellInput 结构(注意以 molecule 格式表述的 CellInput 结构已经包含长度信息,无需单独再包含 CellInput 结构的长度,下文中对所有 molecule 相关的结构,均类似处理)
- I1 对应的 CellOutput, 以 molecule 格式 序列化
- I1 对应的 CellOutput Data, 以 little-endian encoding 的
u32
表示的 Data 长度和 Data 的实际内容 - I2 对应的 CellInput 结构
- I2 对应的 CellOutput, 以 molecule 格式 序列化
- I2 对应的 CellOutput Data, 以 little-endian encoding 的
u32
表示的 Data 长度和 Data 的实际内容 3
(表示 output cells 的个数,以 little endian encoding 的 u32 表示)- O1 对应的 CellOutput 结构
- O1 对应的 cell data 长度
- O1 对应的 cell data 内容
- O2 对应的 CellOutput 结构
- O2 对应的 cell data 长度
- O2 对应的 cell data 内容
- O3 对应的 CellOutput 结构
- O3 对应的 cell data 长度
- O3 对应的 cell data 内容
1
(表示 cell deps 的个数,以 little endian encoding 的 u32 表示)- D1 对应的 CellDep 结构
4
(表示 header deps 的个数,以 little endian encoding 的 u32 表示)- H1 对应的 Byte32 结构
- H2 对应的 Byte32 结构
- H3 对应的 Byte32 结构
- H4 对应的 Byte32 结构
Example
这个仓库 提供了计算 signing message hash
的样例实现。
这个样例展示了在三种不同的模式中,根据 CoBuild 规范要求把需要覆盖的数据拼接在一起,经过一次 blake2b hash 计算得到结果,作为 signing message hash
。
在样例的三种不同模式中,我们选用了不同的 blake2b personalization,来避免对不同类型的交易误生成同样的 hash:
SighashAll
模式,带有Message
的交易: 使用ckb-tcob-sighash
SighashAllOnly
模式,没有Message
的交易: 使用ckb-tcob-sgohash
(‘sgo’ means ‘sighash only’)Otx
模式: 使用ckb-tcob-otxhash
如上文所说,这里只是一种 signing message hash
的实现样例。对一个 CoBuild-compatible lock script 来说,只要在计算 signing message hash
的过程中正确的覆盖了 CoBuild 规范中指定的所有数据,并且数据拼接不会产生歧义,最终对通过 cryptographic hash function 对拼接好的数据生成一个 hash 进行验签,即可保证协议上的安全性。