Delegate Lock is a lock script that proxies verification to another script stored in a separate cell. This allows users to update the ownership or unlocking logic of cells without changing the lock script hash, preserving the cell's on-chain identity.
The args field stores the blake160 hash of the type script of the Type ID cell containing the actual lock script. This truncation follows the same convention as other CKB lock scripts and provides sufficient collision resistance while minimizing on-chain storage.
Delegate Lock delegates the verification logic. Therefore, the witness structure should follow the format expected by the actual lock script. Delegate Lock itself does not impose additional witness requirements.
First, create a Type ID cell to store the actual lock script:
Inputs:
Seed Cell
Outputs:
Type ID Cell:
Lock: <lock script for Type ID cell itself>
Type:
code_hash: <Type ID code hash>
hash_type: type
args: <type id>
Data: <actual lock script, molecule-encoded>
code_hash: <actual lock script code hash>
hash_type: <actual lock script hash type>
args: <actual lock script args>
Second, lock the target cell using Delegate Lock, referencing the Type ID cell:
Outputs:
Target Cell:
Lock:
code_hash: <Delegate Lock code hash>
hash_type: type
args: <blake160 hash of the Type ID cell's type script>
To unlock the cell, the transaction must include the Type ID cell and the actual lock script binary cell as dependencies:
CellDeps:
Type ID Cell:
Type:
code_hash: <Type ID code hash>
hash_type: type
args: <type id>
Data: <actual lock script, molecule-encoded>
code_hash: <actual lock script code hash>
hash_type: <actual lock script hash type>
args: <actual lock script args>
Actual Lock Script Binary Cell:
Data: <actual lock script binary>
Inputs:
Delegate Lock Cell:
Lock:
code_hash: <Delegate Lock code hash>
hash_type: type
args: <blake160 hash of the Type ID cell's type script>
Witnesses:
<as required by the actual lock script>
To change the ownership, update the data in the Type ID cell.
Inputs:
Old Type ID Cell:
Lock: <lock script for Type ID cell itself>
Type:
code_hash: <Type ID code hash>
hash_type: type
args: <type id>
Data: <old actual lock script, molecule-encoded>
Outputs:
New Type ID Cell:
Lock: <lock script for Type ID cell itself>
Type:
code_hash: <Type ID code hash>
hash_type: type
args: <type id>
Data: <new actual lock script, molecule-encoded>
The actual lock script in a Type ID cell can itself be Delegate Lock, enabling multiple levels of delegation where each level can be updated independently.
CellDeps:
Outer Type ID Cell:
Type:
args: <outer type id>
Data: <Script pointing to Delegate Lock with blake160(inner type script) as args>
Inner Type ID Cell:
Type:
args: <inner type id>
Data: <actual lock script, molecule-encoded>
Actual Lock Script Binary Cell:
Data: <actual lock script binary>
Inputs:
Delegate Lock Cell:
Lock:
code_hash: <Delegate Lock code hash>
args: <blake160 hash of the Type ID cell's type script>
Witnesses:
<as required by the actual lock script>
A lock script typically loads its arguments via the ckb_load_script syscall. However, when used with Delegate Lock, the actual lock script runs in Delegate Lock's context, so ckb_load_script returns the Delegate Lock script instead of the expected arguments.
To address this, Delegate Lock uses the following convention when invoking the actual lock script:
- It uses
ckb_execto execute the actual lock script. sourceis set toCKB_SOURCE_CELL_DEP(3),placeis set to 0 (cell data), andboundsis set to 0 (entire data).- The lock script arguments from the Type ID cell data are passed via
argcandargvparameters ofckb_exec.argcis always 2argv[0]contains the magic string"DELEGATE_LOCK", which the actual lock script must verify to confirm it was invoked by Delegate Lock.argv[1]contains the hex-encoded script args from the Type ID cell data.
- Delegate lock itself does not impose any additional witness requirements. The actual lock script can load its witness as usual.
Therefore, the actual lock script must read its arguments from argc and argv instead of ckb_load_script. Since most existing lock scripts use ckb_load_script, modified versions are provided in this repository (see Migrated Lock Scripts below).
To adapt an existing lock script to work with Delegate Lock, follow the following migration guide:
// Old way: load from script directly
fn run() -> Result<(), Error> {
let script = ckb_std::high_level::load_script()?;
let args: Bytes = script.args().unpack();
// ...
}// New way: load from argv
const DELEGATE_LOCK_MAGIC: &[u8] = b"DELEGATE_LOCK";
fn run() -> Result<(), Error> {
let argv = ckb_std::env::argv();
if argv.len() != 2 || argv[0].to_bytes() != DELEGATE_LOCK_MAGIC {
return Err(Error::ArgsInvalid);
}
let args_hex = argv[1].to_bytes();
let args = decode_hex(args_hex)?;
// ...
}
fn decode_hex(hex: &[u8]) -> Result<Vec<u8>, Error> {
if hex.len() % 2 != 0 {
return Err(Error::ArgsInvalid);
}
let mut bytes = Vec::with_capacity(hex.len() / 2);
for chunk in hex.chunks(2) {
let high = hex_digit_to_value(chunk[0])?;
let low = hex_digit_to_value(chunk[1])?;
bytes.push((high << 4) | low);
}
Ok(bytes)
}
fn hex_digit_to_value(c: u8) -> Result<u8, Error> {
match c {
b'0'..=b'9' => Ok(c - b'0'),
b'a'..=b'f' => Ok(c - b'a' + 10),
b'A'..=b'F' => Ok(c - b'A' + 10),
_ => Err(Error::ArgsInvalid),
}
}The following lock scripts have been adapted to work with Delegate Lock by reading arguments from argv instead of ckb_load_script:
| Script | Original |
|---|---|
| secp256k1-blake160-sighash-all | CKB System Scripts |
| secp256k1-blake160-multisig-all | CKB System Scripts |
| ccc-btc | CCC Locks |
| ccc-eth | CCC Locks |
| ccc-sol | CCC Locks |
- Type ID Cell Protection: The security of cells locked by Delegate Lock depends entirely on the lock script of the Type ID cell. If an attacker can modify the Type ID cell's data, they gain control over all cells referencing that Type ID. Choose a secure lock script for the Type ID cell.
- Actual Lock Script Trust: The actual lock script executed via
ckb_execmust be trusted. Delegate Lock does not verify the correctness of the delegated script beyond matching the Type ID.