[CN/EN] CTO - 去中心化的链上时间预言机 Decentralized On-Chain Time Oracle


来自过去的现在

想象一个简单的场景:作为开发者,你想在 CKB 上开发“限时抢红包”的应用。规则很简单,收到红包的人必须在一天之内领取,逾期后红包只能由发起者收回。这不是个困难的问题,你想,直到注意到 CKB 的合约只能读到当前交易里的内容,通过附加的区块头证明来读取时间戳。

合约无法验证交易中的区块头证明是什么时候生成的,也就意味着攻击者可以今天构造交易时,引用一个 2019 年的旧区块头。在脚本看来,证据显示当前时间就是 2019 年,离红包到期还有七年,当然可以领取。这种时间回溯攻击的可能性使得任何带有“截止日期”逻辑的合约在 CKB 上都面临被绕过的风险。

在 CKB 的设计中,开发者可以轻松地证明某个区块或 Cell 是否存在,反之却难以证明其不存在。一个简单的解决方案是,对于链上难以获得的全局信息,可以请一位链下的观察者持续提供可信的数据源,这是我们所熟知的预言机(Oracle)方案。

然而,预言机方案也会引入诸如单点故障或可信度等额外的问题,极大增加了复杂度。对于获取链上最新时间这一特定需求,我们可以用更加去中心化的方式满足需求,我把它叫做 CTO (CKB Time Oracle) 方案。CTO 在链上维护了一组 Cell,通过合约要求了轮换机制和区块头证明,为其它合约提供了可信的、并发可用的时间源。


GitHub 仓库地址:Hanssen0/ckb-cto

方案设计

CTO 方案的核心思路是为链上的 Cell 赋予全局唯一的 ID,链下数据提供者不断将最新区块头的时间更新到 Cell 中,从而允许其它合约查询带有这个 ID 的 Cell 的数据来获得最新的链上时间。

多 Cell 轮转

如果链上只有一个可用的 Cell,当提供者不断更新它时,Cell 将经常处于被数据提供者的更新交易占用的状态,使得使用者发送的交易频繁地失败,因为引用的 Cell 总是在被更新交易更新至下一个状态。

因此,CTO 方案引入了一组 N 个 Cell 构成的预言机组(Oracle Group)来记录时间。每一次更新时,CTO 合约会要求被更新的必须只能是预言机组中最旧的 Cell,保证更新是顺序进行的。如果引用了预言机组中最新的 Cell,使用者可以放心地认为在 N - 1 个区块间隔之内 Cell 都不会被占用,为其它操作留下了冗余时间。

为了理解这张图,我们需要先理解 CKB 上的 Cell 更新的流程。CKB 上的 Cell 是不可变的,意味着开发者不能简单修改某个 Cell 里的数据,而是需要销毁一个旧的 Cell,再创建一个新的 Cell,用相同的 ID 来标记这两个 Cell 之间的关系。而这个 ID 就是 CKB 上的 Type ID 方案,关于 Type ID 更详细的解释可以看 CKB RFC 的描述我之前的文章

回到这张图,它一组 5 个 Cell 轮转的预言机组作为例子展示了 CTO 方案是怎么工作的。其中,横轴是当前最新的区块时间,而纵轴则是 Cell 的编号(Out Point)。在第一次更新时(4 - 5),最旧的 Cell #0 被销毁,创建了新的 Cell #5 储存最新区块的时间,以此类推,红色的 Cell 总是将要被销毁的,从而创建下一个绿色的 Cell。

因此,如果我在时间 5 时引用了 Cell #5 ,它在时间 10 之前都是可用的。去掉已经出块的时间 5 块,我的交易在四个区块内都能成功上链。

安全与并发设计

通常来讲,CTO 方案中的 Cell 任何人都可以解锁,意味着所有人都可以更新里面的数据。为了避免攻击者恶意将 Cell 锁定阻止时间被更新,CTO 要求每一次 Cell 被更新时锁定方式(Lock Script)都不能变化。

在这样的设计下,预言机使用者不需要担心数据提供者停止更新链上的预言机 —— 它是无许可的!就算数据提供者跑了,应用开发者也能接过数据更新的工作,让预言机继续运行。甚至就算开发者跑了,用户自己也能持续(或在有需要的时候)更新这个预言机来让应用正常工作。考虑到我为 CTO 方案提供了一个 JS SDK一个命令行工具一个网页前端,这应该不会太难(我晚点再介绍它们)。这样的设计消除了应用对单一中心化服务商的依赖。

所以,如果有好多个数据提供者都想要更新预言机,导致冲突了怎么办呢?在所有提供者都讲规矩,每次更新都是用最新的区块时间的话,这应当不会导致什么太大的问题,只要有一个人成功了,预言机就能正常工作。只有当一个坏蛋每次都只让时间往前挪一小步(一个区块),想要拖慢预言机更新的时候,一些太久没更新的预言机组才可能会出现问题,想要更新到最新时间的提供者总是会被这个坏蛋阻拦。

不过,在我的设想中,所有人共用同一个被良好更新的预言机组应该就已经足够满足需求了(为什么会有人想要创建自己的预言机组呢?虽然没有任何事情阻止人们这么做),所以这个坏蛋每个区块都更新这个预言机组只会让它总是保持在最新状态。况且,只要坏蛋漏了一次没能成功抢到更新,剩下的提供者就能一举让预言机组恢复正常工作。

最后值得注意的一点是,虽然越多 Cell 的预言机组能让使用者有更多的冗余时间来使用最新的 Cell,但也意味着攻击者可以更自由地挑选更老的 Cell 来骗过使用预言机组的合约。一个无限大的预言机组其实和任意区块头都可用没有区别(当然不行,CTO 最大是 255 个 Cell 一组)。考虑到 CKB 的平均区块时间大概是 10 秒,N = 13 的预言机组就能留给用户约两分钟的签名时间,这应当足够。

如果项目想要更严格的时间限制,但又不想单独维护 Cell 数量少的预言机组呢?这也可以简单通过要求在使用时提供预言机组中的多个 Cell 来达成。比如,要求 N = 13 的预言机组中的五个 Cell,其实等价于要求 N = 9 的预言机组中的一个 Cell。


工具们

就像我刚刚提到的,为了把 CTO 方案准备好给开发者/用户使用,我提供了一些配套的工具。

CLI 工具 @ckb-cto/supplier

对于有兴趣的预言机组数据提供者来说,CLI 应该是最方便的工具。CLI 提供了标准的提供者逻辑,自动监听链上的预言机组状态并周期性地更新 Cell 的时间,保证预言机总是准时。除此之外,开发者也可以用 CLI 工具部署新的预言机组,或者在终端直接查询某个预言机组的当前状态。

网页端监控面板 ckb-cto.hanssen0.com

网页端面板提供了 CLI 中的所有功能 —— 查询、部署和提供(当然,你没法指望在网页上为预言机组提供数据能有多稳定)。除此之外,网页端还提供了发现 CKB 网络上所有预言机组的功能,并接入了 CCC 允许用户连接支持的钱包来部署预言机组或提供数据。

SDK @ckb-cto/lib

当然,我把 CTO 方案的核心功能都包装成了 SDK 供开发者集成时使用。事实上,前面提到的 CLI 工具和监控面板都是基于这个 SDK 开发的。


目前,我已经将 CTO 方案部署到了 CKB 测试网,相关的工具也已经发布,并可以在 GitHub 仓库中找到源代码。欢迎社区伙伴们看看代码中是否有什么问题、提出你对 CTO 方案的建议或分享你觉得合适的 Cell 数量。如果这个时间预言机的方案激发了你的奇妙想法,我也很想听到你的点子。

5 Likes

Present from the Past

Imagine a simple scenario: as a developer, you want to build a “limited-time red envelope” application on CKB. The rules are straightforward — recipients must claim the envelope within 24 hours; otherwise, only the sender can reclaim it. You think this is an easy problem until you realize that CKB contracts can only “see” what’s inside the current transaction, reading timestamps via attached block header proofs.

A contract cannot verify when the block header proof in a transaction was generated. This means an attacker could construct a transaction today while referencing an old block header from 2019. From the script’s perspective, the evidence shows the current time is 2019, meaning there are still seven years before the envelope expires, thus allowing the claim. This possibility of a “Time-Travel Attack” puts any contract with “deadline” logic at risk of being bypassed on CKB.

In CKB’s design, it is easy for developers to prove that a certain block or Cell exists, but conversely difficult to prove its non-existence. A simple solution for obtaining global information that is hard to get on-chain is to have an off-chain observer continuously provide a trusted data source—the well-known Oracle solution.

However, Oracle solutions introduce additional issues such as single points of failure or trust concerns, greatly increasing complexity. For the specific need of obtaining the latest on-chain time, we can satisfy it in a more decentralized way, which I call the CTO (CKB Time Oracle) solution. CTO maintains a group of Cells on-chain, enforcing a rotation mechanism and block header proofs via contracts to provide a trusted, concurrently available time source for other contracts.


GitHub Repository: Hanssen0/ckb-cto

System Design

The core idea of the CTO solution is to assign a globally unique ID to on-chain Cells. Off-chain data providers continuously update these Cells with the latest block header time, allowing other contracts to query Cells with this ID to obtain the most recent on-chain time.

Multi-Cell Rotation

If there were only one available Cell on-chain, it would frequently be occupied by the data provider’s update transactions, causing user transactions to fail often because the referenced Cell is constantly being updated to its next state.

Therefore, the CTO solution introduces an Oracle Group consisting of N Cells to record time. In every update, the CTO contract requires that only the oldest Cell in the Oracle Group can be updated, ensuring that updates proceed sequentially. If a user references the newest Cell in the Oracle Group, they can rest assured that the Cell will not be occupied within an interval of N - 1 blocks, leaving redundant time for other operations.

To understand this diagram, we first need to understand the Cell update process on CKB. Cells on CKB are immutable, meaning developers cannot simply modify data within a Cell. Instead, they must destroy an old Cell and create a new one, using the same ID to mark the relationship between the two. This ID is the Type ID solution on CKB. For a more detailed explanation of Type ID, you can refer to the CKB RFC description or my previous article.

Returning to the diagram, it uses an example of a 5-Cell rotation Oracle Group to show how CTO works. The horizontal axis represents the current latest block time, while the vertical axis represents the Cell number (Out Point). In the first update (at time 4-5), the oldest Cell #0 is destroyed, and a new Cell #5 is created to store the latest block time, and so on. Red Cells are always about to be destroyed to create the next green Cell.

Thus, if I reference Cell #5 at time 5, it remains available until time 10. Subtracting the time for block 5, my transaction has a window of four blocks to be successfully packaged.

Security and Concurrency Design

Generally speaking, any Cell in the CTO solution can be unlocked by anyone, meaning anyone can update the data within. To prevent attackers from maliciously locking a Cell to stop time updates, CTO requires that the Lock Script cannot change when a Cell is updated.

Under this design, oracle users do not need to worry about the data provider stopping updates—it is permissionless! Even if the original data provider leaves, application developers can take over the update task to keep the oracle running. Even if the developers leave, users themselves can continuously (or as needed) update the oracle to keep the application functional. Considering I have provided a JS SDK, a CLI tool, and a web dashboard, this shouldn’t be too difficult. This design eliminates the application’s dependence on a single centralized service provider.

So, what happens if multiple data providers try to update the oracle and cause a conflict? As long as all providers follow the rules and use the latest block time, this shouldn’t cause major issues—if anyone succeeds, the oracle works. Problems only arise when a “bad actor” tries to slow down updates by only moving time forward by a tiny step (one block) each time. In such cases, providers wanting to update to the latest time might be blocked by this actor.

However, in my vision, everyone sharing a single, well-maintained Oracle Group should be enough to meet most needs (why would anyone want to create their own Oracle Group? Although nothing stops them). So, a bad actor updating every block would only keep the oracle in its most up-to-date state. Furthermore, if the bad actor misses even one update, the other providers can instantly restore the Oracle Group to normal operation.

Finally, it’s worth noting that while more Cells in an Oracle Group provide users with more redundant time to use the latest Cell, it also means attackers have more freedom to pick older Cells to deceive contracts using the Oracle Group. An infinitely large Oracle Group is essentially no different from allowing any block header to be used (which obviously won’t work; CTO is capped at 255 Cells per group). Considering CKB’s average block time is about 10 seconds, an N = 13 Oracle Group would leave users with about a two-minute signing window, which should be sufficient.

What if a project wants stricter time limits but doesn’t want to maintain a separate Oracle Group with fewer Cells? This can be easily achieved by requiring multiple Cells from the Oracle Group during use. For example, requiring five Cells from an N = 13 group is equivalent to requiring one Cell from an N = 9 group.


The Tools

As I mentioned earlier, to make the CTO solution ready for developers and users, I have provided a set of supporting tools.

CLI Tool @ckb-cto/supplier

For interested Oracle Group data providers, the CLI is the most convenient tool. It provides standard provider logic, automatically monitoring on-chain Oracle Group status and periodically updating Cell time to ensure the oracle is always “on time.” Additionally, developers can use the CLI to deploy new Oracle Groups or query the current status of an Oracle Group directly in the terminal.

Live Dashboard ckb-cto.hanssen0.com

The web dashboard provides all the features of the CLI—querying, deploying, and providing (though you can’t expect high stability for providing data via a browser). Beyond that, the dashboard allows for the discovery of all Oracle Groups on the CKB network and integrates CCC, allowing users to connect supported wallets to deploy groups or provide data.

SDK @ckb-cto/lib

Naturally, I have wrapped the core functionality of the CTO solution into an SDK for developers to integrate. In fact, both the CLI tool and the dashboard mentioned above are built upon this SDK.


Currently, I have deployed the CTO solution to the CKB Testnet. The related tools have been released, and the source code can be found in the GitHub repository. I welcome community members to review the code for any issues, propose suggestions for the CTO solution, or share what you think is an appropriate number of Cells. If this time oracle solution has sparked any unique ideas, I would love to hear them.

10 Likes

This section reminds me of the RGB++ SPV Type Script design, though this implementation offers a more decentralized way to handle state updates.

Additionally, I believe leveraging the since field to implement a ‘delayed start’ feature, where red packets only become claimable after a predetermined time, would be a practical functional addition.