iCKB journey into CoBuild

I’d like to thank you again for your time and support, I appreciate a lot!! Usually I discuss these topics with @jm9k and we agree on most decisions, so hearing your perspective (which usually differs from mine) would be beneficial for both iCKB and me :pray:

Current design

Let’s start with Limit Order then. After talking with @doitian, I adopted the following molecule template schema:

array Uint64 <byte; 8>;
array Hash32 <byte; 32>;

array Bytes<T> <byte; T>

struct lock<T> {
    codeHash: Hash32,     // 32 bytes
    hashType: byte,       // 1 byte
    args: Bytes<T>        // T bytes
}

struct OrderArgs<T> {
    userLock: lock<T>           // 33 + T bytes
    sudtHash: Hash32,           // 32 bytes
    isSudtToCkb: byte,          // 1 byte
    ckbMultiplier: Uint64,      // 8 bytes
    sudtMultiplier: Uint64,     // 8 bytes
    logMinFulfillment: number,  // 1 byte
}

union OrderArgs {
    OrderArgs<0>,
    OrderArgs<1>,
    ...
    OrderArgs<255>,
}

Currently the limit order script memorize all these parameters in its args, no data memorized anywhere else. The script validates that each interaction with a limit order cell is either a valid (partial) fulfillment or a cancel action.

The contract is positional based, so if at input index i there is a cell with limit order lock, the output cell at index i is either a limit order (similarly to NervosDao withdrawal request) or the complete fulfilled cell with user lock.

Also there is the possibility to cancel a limit order (at index i) by signature delegation (in input must exist a cell with user lock) and the output cell at index i is a cell with user lock.

Pitfall of transition from limit order to user lock without a signature

My constant fear with this design is that it may exists another script, let’s call it cooler limit order, with slightly different validation rules. A user may use both at the same time, place a order in both and unknowingly both orders have compatible completely fulfilled cells. (These cells have user lock for improved user experience as they effectively remove one transaction for the user.) The attack vector is the following: an attacker can completely fulfill both limit order and cooler limit order with the same output cell (so both scripts validate) and be able to steal the rest. This is a remote possibility, still it is troubling.

Ideas for a CoBuild OTX design

  1. Due to the possible attack, we could ask that the completely fulfilled cell step happens only with user signature delegation, same as with cancelling. If necessary we could also memorize user lock as Hash32 and this would also avoid having a dynamic length field.

  2. Since CoBuild OTX is definitely not positional friendly (or better its positional to the OTX), the output cell must be able to reference the input cell in other ways. For example the action could store both the input outpoint of the cell is matching against and a reference to the output cell. An alternative is to define a synthetic ID that identify that particular limit order. Then again I see no way to guarantee its unicity. It could be unique in the OTX, but it may still create issues. Another possibility could be to keep the positional approach but relative to the OTX.

  3. The signature delegation in a CoBuild OTX Order would need to check that input user cell exists in the very same OTX. Same tx is not enough to assure safety as it could be attacked easily.

  4. We don’t need to require for the corresponding output of fulfilled cell or cancelled cell to have user lock. Same as when completing the withdrawal from a NervosDao, it can be right away used for anything the user decides. This should be safe since the OTX contains user signature.

  5. Since now the action Cancel and action Withdraw Fulfilled have the very same logic, they can be defined as Melt, so there are only three CoBuild Actions: Mint, Fulfill and Melt. Only Melt requires delegated user signature, while Fulfill is exclusively validated by Order logic.

  6. Limit Order will support both sUDT and xUDT.

  7. All data could be memorized into the CoBuild witnesses’s action and lock script may only need to memorize the hash of this data as script args or XudtData lock for next fulfillment round. In case of sUDT the very same XudtData convention can be used.

  8. If we impose the rule for all limit orders to have already the correct UDT Type (so not undefined) at Mint time, then it could be possible to drop the sudtHash field from Args. Then again if we follow the design of 7 it could still be required in Action.

  9. Another idea concurrent to 7 (memorize only hash of data) is to store limit order data into a secondary cell, with limit order type and user lock. This cell is then referenced as CellDep and its outpoint is the synthetic ID needed to identify the order. The limit order lock script could contain either directly the outpoint (as args or XudtData) or just referece its position in the otx CellDeps (so the weaker unique ID in OTX). This design also improves on the security of Melt as it requires a specific user cell (this secondary cell) to be consumed in the same OTX of the limit order Melt. Then again this solution would require an additional disbursing of CKB for the secondary cell and for referencing it from the limit order cell.

All in All, probably a variation of 9 would be the most future proof design I can think of, then again 7 would have a smaller CKB footprint.

2 Likes