A design for timestamp oracle on CKB

Due to the deterministic design of Nervos CKB, timestamp does not naturally exist on chain. While you could use other solutions, such as since or header deps to obtain a lower bound on the timestamp, we don’t yet have a solution to set upper bound on the timestamp in CKB. Here, we are proposing a design that provides timestamp to Nervos CKB as an oracle.

Design Principles

The timestamp oracle is designed with the following principles:

  • Decentralized: anyone should be able to update the timestamp value on chain.
  • Profitable: participants who help update the timestamps should be rewarded from users who use the timestamp on chain.
  • Parallel: the timestamp oracle should not require shared state so as to function well on CKB.

Data Structure

In order for the timestamp oracle to function, 4 scripts will be used:

  • Micro payment script: a way to quickly make recurring payments to someone on CKB. For example, a user who uses the timestamp oracle, might keep paying the timestamp updater a small amount of tokens so as to be able to use the timestamp values. A typical payment solution won’t work here due to the nature of timestamp oracle: it should only cost a dime or so to be able to use a timestamp.
  • Timestamp lock script: lock used to guard the timestamp cell.
  • Invoking script: a script used to fetch current timestamp from timestamp cell, while also ensuring payments are made to the timestamp updater.
  • Validating script: a script used to ensure consistent behavior from invoking script.

Later we will talk about the details of the 4 scripts used here, right now, we are merely introducing them, so we can discuss about the cells used in timestamp oracle:

Timestamp cell

Timestamp cell keeps the latest timestamp information. A timestamp cell has the following format:

  • Timestamp lock script is used as lock script
  • Validating script is used as type script
  • The actual timestamp is kept in the cell data part
  • An updater can also keep the identity information of themselves in the cell data part for receiving micro payments.

Any user should be able to update the timestamp cell, assuming:

  • Correct format is used
  • A pre-defined interval(for example, 1 minute) has been passed since last time someone updates the timestamp cell

Notice we don’t define the exact format of timestamp stored in the cell data part. In fact, the timestamp format here will never be in the specification of timestamp oracle. Later we shall see this is the key to ensuring users of timestamp oracle make proper payments.

Timestamp invoking cell

Timestamp invoking cell has the following format:

  • Any lock script can be used as the lock script here, most likely, a governance lock from the timestamp oracle community is used
  • Validating script is used as the type script
  • The actual binary code of the invoking script is kept in the cell data part

Timestamp invoking cell is used as a container for the invoking script. When a user is actually using the timestamp, they should dynamically load the invoking script from the live timestamp invoking cell, then invoke a designated function provided by the invoking script to fetch the timestamp. The function will also require a proof of micro payment. This way we can ensure that proper payments are made to the timestamp updater, which ensures a sustainable ecosystem.

The reason invoking script is kept directly in the cell data part of timestamp invoking cell, is that invoking script is supposed to be updated periodically. Recall that previously, it is stated that the actual format of timestamp value stored in timestamp cell is not defined: the invoking script used here, actually defines the format, when a new invoking script is used, the format might also change. If someone cheats by reading the cell data of timestamp cell directly, their dapp might stop functioning when invoking script is updated, since the new timestamp format might not be properly parsed. This way, we are ensuring all users should make proper payments in order to use the timestamp order.

However, an updatable script brings a different problem: how can one ensure that invoking script won’t change its API? This is ensured by validating script. Validating script will ensure that given a micro-payment in correct format, invoking script will properly execute, parse timestamp cell, and return a timestamp value in correct format. While invoking script can be updated, validating script will be freezed, ensuring a unified behavior that dapp developers can rely on.

Transaction Structure

Here, real examples will be used to better illustrate the above points:

Updating timestamp

The following transaction updates the timestamp stored in timestamp oracle:

Inputs:
    Timestamp Cell
        Data:
            <timestamp: 1601187880> <updater: A>
        Type: Validating script
        Lock: Timestamp lock script
    <...>
Outputs:
    Timestamp Cell
        Data:
            <timestamp: 1601187942> <updater: B>
        Type: Validating script
        Lock: Timestamp lock script
    <...>
Deps:
    Timestamp Invoking Cell
        Data: Invoking script
        Type: Validating script
        Lock: <...>
    <...>

For simplicity, the above structure does not include auxiliary cells, such as cells used to pay transaction fees. Assuming an update interval of 60 seconds, user B here tries to update timestamp cell, with a new timestamp that is 62 seconds after the original timestamp, user B also updates the micro payment from user A to themselves.

Referencing timestamp

The following transaction can be used to reference timestamp kept in timestamp oracle:

Inputs:
    Micro payment cell (if needed)
    <...>
Outputs:
    Micro payment cell (if needed)
    <...>
Deps:
    Timestamp Cell
        Data:
            <timestamp: 1601187942> <updater: B>
        Type: Validating script
        Lock: Timestamp lock script
    Timestamp Invoking Cell
        Data: Invoking script
        Type: Validating script
        Lock: <...>
    <...>

Any script executed in this transaction, will be able to load invoking script, then use invoking script to load the current timestamp stored on chain(which is 1601187942). A micro payment to updater B is also required by invoking script.

Due to incentive reasons, after 60 seconds of timestamp 1601187942, other timestamp updater might send new transactions updating the timestamp cell, which means the above transaction either gets accepted in the timestamp range from 1601187942 to (1601187942 + 60), or it gets rejected since the referenced timestamp cell is not live. In other words, incentive will help provide an upper bound on the timestamp used here.

Updating Invoking Scripot

And finally, the following transaction allows updating invoking script:

Inputs:
    Timestamp Invoking Cell
        Data: Old Invoking script
        Type: Validating script
        Lock: <...>
    <...>
Outputs:
    Timestamp Invoking Cell
        Data: New Invoking script
        Type: Validating script
        Lock: <...>
    <...>
Deps:
    Timestamp Cell
        Data:
            <timestamp: 1601187942> <updater: B>
        Type: Validating script
        Lock: Timestamp lock script

Validating script will ensure that the new invoking script provides the same ABI, so everyone can continue using the timestamp oracle in the same way as before this transaction is commited.

Script Details

Here, the implementation details of each involved script are discussed

Micro payment script

A micro payment script is unique in the following ways:

  • Each individual payment might be so small, that regular transaction is overkill.
  • Frequent recurring payments are needed.

One obvious choice here is payment channel, which sounds perfect for the job. But even without payment channel, there are also some simple solutions we can explore:

Payment information in cell data

One solution, is that the payer can create a micro payment cell first, whose cell data part contains a list of payment information:

| Payee A | Amount | Payee B | Amount | Payee C | Amount | ... |
|---------|--------|---------|--------|---------|--------|-----|

Each payment information just contains the identity(e.g., public key hash) of the payee, together with the amount due to the payee. One micro-payment transaction can then just be the insertion of a new payee entry, or updating of an existing payee entry. Each payee can freely transfer the amount due for them out of the micro payment cell. The payer can also withdraw those amounts that are not paid to a payee out of the micro payment cell, or the payer can destroy the cell and send out payments to each payee, hoping to reclaim all the CKB required in the cell.

Signature only

One additional solution, is that a micro payment can just be a signature in the open transaction, however instead of signing a component in the transaction input, or output, it can instead sign the difference between the input amount, and the output amount. A payee then gathers all the signatures from all the micro payments, and do a full withdraw from the payer’s account. In the event that the payer wants to withdraw from his/her micro payment account, a wait period will be required, so payees can still safely get their payments, even though the payer might want to play evil and withdraw the amounts they have already paid someone.

Notice those are just some initial ideas on the implementation of a micro payment script, it is likely there are also more solutions we haven’t listed here.

Timestamp lock script

A timestamp lock script can be unlocked only when all of the following conditions are met:

  • The since of the input cell is set to the pre-defined interval past the timestamp stored in the input cell data
  • An output timestamp oracle cell is created with the following conditions:
    • The since value above is set in the cell data
    • The lock is the same as current oracle lock script
    • The type is the same as current oracle type script
  • An identity information denoting the payee(such as a secp256k1 public key hash) can also be attached to the cell data part.

Invoking script

Invoking script is a dynamically linked script. It offers one public function, which takes a micro payment as an input. If the micro payment is valid, it then parses the current timestamp from the referenced timestamp cell, then return the correct timestamp. Otherwise it returns an error.

Validating script

A validating script loads the currently live invoking script, feed it a valid micro payment information, if the invoking script successfully returns a valid timestamp information, the validating script thus returns a success state, otherwise it returns a failure.

Conclusion

This concludes the design of the timestamp oracle. We believe this might be a feasible design on Nervos CKB that can achieve its decentralized, profitable, and parallel design goals.

5 Likes

How to determine the amount a payee can withdraw at a certain time?

Typo for payer?

Have we got any examples on this kind of invoking approach?

1 Like
1 Like

Depending on the actual micro payment solution used, either the due amount can be stored in cell data, or encoded and signed in the signature.

This is a very interesting design! But I have some confusion about how to force dapps to read timestamps via invoking.

So if a dapp is willing to update the way it reads timestamps with the same frequency, then it can use the timestamp oracle for free, right? (Of course, this may be more demanding for dapp developers and will affect the user experience at the same time.)

Theoretically yes, there is no effective way to stop this for now. But in practice, the hurdles of upgrading complicated smart contracts using timestamps, would IMHO outweigh the costs of using the timestamp service. I would bet most developers would leverage the timestamp service instead of going through the hassles(not to mention community criticism for cheating, which can be an important factor as well on chain)