Appendix: Lock/Type Script Validation Rules
This section describes the validation rules that a lock/type script needs to include in order to meet the requirements of the CoBuild protocol.
From a convenience and practicality standpoint, we recommend all lock/type scripts support legacy WitnessArgs, then add support for SighashAll Variant, and support for Otx Variant.
Legacy Flow Handling
Considering forward compatibility, lock / type scripts that support the CoBuild protocol may also need to support the legacy flow, which is the existing process based on WitnessArgs structure.
A lock/type script can simultaneously support both CoBuild flow and legacy flow. However, for a specific transaction, a lock/type script must choose one of the two for verification: either use CoBuild flow or use legacy flow.
Usually, all scripts in a transaction should use the same flow for verification. But there’s an exception: if a tranasction uses script A that supports both flows and script B that only supports legacy flow, it’s possible that script A verifies according to CoBuild flow while script B verifies according to legacy flow.
Lock/type scripts should follow these rules to determine the flow to use:
- If there is a witness of the type
WitnessLayoutin current transaction, use the CoBuild Flow.- Note: The condition here is its existence, not uniqueness, not if different
WitnessLayoutwitnesses coexist, nor its compliance with other CoBuild rules.
- Note: The condition here is its existence, not uniqueness, not if different
- Otherwise, use the Legacy Flow.
Lock Script
The main responsibility of a CKB lock script is to perform ownership (owner signature) verification - checking that the input cell is unlocked under the correct conditions, while also ensuring that data in transactions are not tampered with. A lock script that supports both SighashAll and Otx modes should verify transactions according to the following rules:
- Maintain a set of variables: is, ie, os, oe, cs, ce, hs, he and set them all to 0.
- is: input start
- ie: input end
- os: output start
- oe: output end
- cs: cell dep start
- ce: cell dep end
- hs: header start
- he: header end
- Loop through all witnesses of current tx (loop variable i), check if there’s a witness in current tx using
WitnessLayout::OtxStarttype. - If no witness in current tx uses
WitnessLayout::OtxStart, skip to step 8 for further validation. - Ensure only one witness is of type
WitnessLayout::OtxStartin current tx or else exit with an error message. Set loop variable i as this witness’sindexvalue. - Set values of is and ie as
start_input_cellinOtxStart, os and oe asstart_output_cellinOtxStart, cs and ce asstart_cell_depsinOtxStart, hs and he asstart_header_depsinOtxStart. - Starting from witness at
indexi+1 , loop through eachWitnessLayout::Otxtyped witness (exit loop directly if encounter any witness not inWitnessLayout::Otxtype), validate eachWitnessLayout::Otxwitness (i.e. each Otx in this tx) as follows:- a. Let variable ‘otx’ store the
WitnessLayout::Otxtype witness, then the following data constitutes current Otx (open transaction):- i. Input cells with index within
[ie, ie + otx.input_cells) - ii. Output cells with index within
[oe, oe + otx.output_cells) - iii. Cell deps with index within
[ce, ce + otx.cell_deps) - iv. Header deps with index within
[he, he + otx.header_deps) - v. The current
WitnessLayout::Otxtyped witness
- i. Input cells with index within
- b. Check
otx, if all values for ‘otx.input_cells’, ‘otx.output_cells’, ‘otx.cell_deps’, and ‘otx.header_deps’ are 0, the script should exit with an error. - c. Parse
otx.message, check eachActionin the message, thescript_hashinActionmust either be the same as the type script hash of one input/output cells in transaction, or be the same as the lock script hash of one input cells in transaction. Otherwise, the script shall terminate with an error.- Note that this check applies to the entire transaction not just current otx.
- d. Check input cells in the range
[ie, ie + ox.input_cells), skip to next iteration if none use current lock script. - e. Calculate corresponding
signing message hashbased onOtxmode rules defined in Appendix: CoBuild Hashes. - f. Fetch
SealPairfromseals, using the current lock script’s lock script hash as key . Exit with error if not found. - g. Extract signature from
SealPairaccording to the lock’s own logic, verify it against previously generatedsigning message hash. Exit with error if verification fails. - h.
ie += otx.input_cells; oe += otx.output_cells; ce += otx.cell_deps; he += otx.header_deps
- a. Let variable ‘otx’ store the
- Assume the first witness not of type
WitnessLayout::Otxstarting fromindexi+1is atindexj, check that all witnesses in the the range[0, i)and[j, +infinity)are not of typeWitnessLayout::Otxor else exit with an error. - Check the input cells within the range of
[0, is)and[ie, +infinity), if any of them uses current lock script, perform aSighashAllsignature verification:- a. Check if there’s a
WitnessLayout::SighashAllvariant among all witnesses in the current tx. - b. If there exists a witness of type
WitnessLayout::SighashAll, ensure that only one such witness exists in the current tx and store itswitness.messagein variablemessage. If no such witness exists in the current tx, set variablemessageas empty. - c. Assuming that
messageis not empty, check that for eachActioncontained inmessage, thescript_hashinActionmust either be the same as the type script hash of one input/output cells in transaction, or be the same as the lock script hash of one input cells in transaction. Otherwise, the script shall terminate with an error. - d. Read the first witness from the script group corresponding to current lock script. If this witness isn’t of type
WitnessLayout::SighashAllorWitnessLayout::SighashAllOnly, exit with error. - e. Ensure that apart from the first witness position inside current lock script group, there’re either no other witnesses or they’re empty.
- f. Extract
sealfrom the first witness within current lock script group and retrieve signature according to the lock script’s own custom logic. - g. Calculate
signing message hashby following theSighashAll/SighashAllOnlyrules outlined under Appendix: CoBuild Hashes. - h. Verify the extracted signature along with the
signing message hash; if verification fails then exit with error.
- a. Check if there’s a
Type Script
CKB type scripts mainly verify application-specific on-chain state transitions. For instance, in UDT type scripts validate token issuance and transfer. In CoBuild process, apart from validating those state transitions, type scripts should also verify consistency between message and full/open transaction behaviors. During message consistency verification, a type script needs to validate each otx individually as well as a possible SighashAll witness containing message.
A type script supporting both SighashAll and Otx variant should follow these validation rules:
- Execute the app-specific state transition logic defined by the type script.
- Maintain a set of variables is, ie, os, oe, cs, ce, hs, he all initialized to 0.
- Loop through all witnesses in current tx (loop variable i), checking if there’s a witness of type
WitnessLayout::OtxStart. - If no witness of type
WitnessLayout::OtxStartexists in current tx, jump to step 9 for further verification. - Ensure only one witness is of type
WitnessLayout::OtxStartin the transaction; otherwise exit with error. Set variable i asindexvalue of this witness. - Set is and ie values equal to
start_input_cellinOtxStart; similarly set os and oe values equal tostart_output_cellinOtxStart; set cs and ce values equal tostart_cell_depsinOtxStart; finally, set hs and he values equal tostart_header_depsinOtxStart. - Starting from the witness with
indexof i + 1, loop through each witness of typeWitnessLayout::Otx(exit the loop immediately when encountering a witness that is not of typeWitnessLayout::Otx) and perform the following verification for eachWitnessLayout::Otxtyped witness (i.e. for each Otx in tx):- a. Assume that the variable
otxstores the witness of typeWitnessLayout::Otx, then the following data constitutes the current Otx (Open Transaction):- i. Input cells within index range
[ie, ie + otx.input_cells) - ii. Output cells within index range
[oe, oe + otx.output_cells) - iii. Cell deps within index range
[ce, ce + otx.cell_deps) - iv. Header deps within index range
[he, he + otx.header_deps) - v. The currently processed witness of type
WitnessLayout::Otx
- i. Input cells within index range
- b. Check input cells within the range of
[ie, ie+otx.input_cells)and output cells within the range of[oe, oe+otx.output_cells). If all these input/output cells do not use current type script then skip to next iteration. - c. Verify
messageinWitnessLayout::OTXbased on current OTX scope. (Refer to the next section for specific verification process). - d.
ie += otx.input_cells; oe += otc.output_cells; ce += otz.cell_deps; he+= otz.header_deps
- a. Assume that the variable
- Assume starting from the witness at
indexi+1, the first witness that isn’t of typeWitnessLayout::OTXhas anindexj. Make sure witnesses within[0,i)and[j,+infinity)ranges are not ofWitnesslayout::OTXtype, otherwise exit with error. - Check input and output cells within
[0, is)and[ie, +infinity), if any of these cells use the current type script, perform the following verification:- a. Scan all witnesses of the current transaction to determine whether there is a witness of
WitnessLayout::SighashAlltype. - b. If no witness of
WitnessLayout::SighashAlltype exists in the current transaction, consider the script validation successful and exit execution with return code 0. - c. Check if there’s exactly one
Witnesslayout::SighashAlltyped witness in current tx, else exit with an error. - d. Verify
messageinWitnessLayout::SighashAll, based on the scope including input cells within[0, is)&[ie, +infinity)and output cells within[0, os)&[oe, +infinity). (Refer to the next section for specific verification rules.)
- a. Scan all witnesses of the current transaction to determine whether there is a witness of
Message Validation
This section describes how the type script validates messages.
Whether it’s an Otx or SighashAll mode transaction, we need to validate the Action in message corresponding with this type script against a range of related input/output cells.
Note that the validation is against “a range of cells”: due to the possible existence of otx , this range may not necessarily be the entire tx but could be intervals like [3,6), or the union of two intervals such as [0,4) || [8,11). However there will only ever be union of two ranges maximum; never more than that.
The type script needs to traverse the actions in message, find an Action where Action.script_hash matches its own script hash. This Action is the one corresponding to the current type script.
- If multiple
Actions are found that correspond to the type script hash, it is recommended to throw an error. The purpose is to prevent third parties from insertingActions calling the same type script into the transaction later to override user’sAction.- Third-party
Actions may appear after user’sActionon wallet UX or even be hide due to insufficient display space, leading users to believe that only their own actions are being signed.
- Third-party
- Example code
Note: The on-chain type script cannot fetch the complete ScriptInfo of the current Action, nor can it verify the correctness of Action.script_info_hash.
Therefore, type scripts can make the following two assumptions:
- The content of
Action, includingAction.script_info_hash, has been covered by lock scripts during verification process, ensuring no tampering. - In the signing process, wallets will get the correct
ScriptInfothrough some offchain channel, verify the consistency between theScriptInfoandAction.script_info_hash, parse ‘Action.data’ with the correct ‘ScriptInfo.schema’, and present the correctly parsed data to the cell holder for signing.
Although a type script does not have the ScriptInfo, it should have information like ScriptInfo.schema and ScriptInfo.message_type in its source code. Type scripts can use these information to parse Action.data, get the structured action data (as input) and validating cells. For instance, if Action.data presents a Spore NFT Mint operation, the type script needs to verify that cells within certain scope indeed expressed a mint operation; if Action.data presents a UDT transfer, similar verification is required by type script to ensure correct amount of UDT has been transferred to the right recipient and change UDT returned to payer.