Skip to main content

Omnilock Script

Omnilock is a Lock Script designed for interoperability. It comes with built-in support for verification of transaction signing methods used in Bitcoin, Ethereum, EOS, Dogecoin and more. It also includes a regulation compliance module, which allows an administrator to revoke tokens held by users under certain circumstances.

How Omnilock Works​

An Omnilock Script has the following structure:

Code hash: Omnilock script code hash
Hash type: Omnilock script hash type
Args: <21 byte auth> <Omnilock args>

There are 2 key fields in args:

  • auth: for authentication, generally with a pubkey hash in its content
  • Omnilock args: for extra checking control, allowing different modes to be enabled in the same Omnilock args

The Omnilock args can be without mode (when Omnilock flags = 0), while the auth must be present. The functionality of Omnilock Script without mode is almost the same as a traditional SECP256K1/blake160 Lock Script. The Omnilock Script can be considered as a traditional Lock Script with additional checking/modes.

Authentication​

An authentication (auth) is a 21-byte data structure containing the following components:

<1 byte flag> <20 bytes auth content>

Depending on the value of the flag, the auth content has the following interpretations:

  • 0x0: It represents the blake160 hash of a secp256k1 public key. The Lock Script performs secp256k1 signature verification, the same as the SECP256K1/blake160 lock.

  • 0x01: It follows the unlocking method used by Ethereum. The signing message hash (sighash_all, see reference implementation) is converted as following: "0x" + hex(signing message hash)

    The hex operator converts the binary into a hex string.

  • 0x03: It follows the unlocking method used by Tron. The signing message hash is converted as follows: "0x" + hex(signing message hash)

  • 0x04: It follows the unlocking method used by Bitcoin. The signing message hash is required to be converted as follows: "CKB (Bitcoin Layer) transaction: 0x" + hex(signing message hash)

    This way, it facilitates a neat presentation of messages on wallet interfaces, such as UniSat and OKX.

  • 0x05: It follows the unlocking method used by Dogecoin. The signing message hash is converted as follows: "0x" + hex(signing message hash)

  • 0x12: It follows the unlocking same method as 0x02, with the signing message hash to be converted as follows: "CKB transaction: 0x" + hex(signing message hash)

    This way, it facilitates a neat presentation of messages on wallet interfaces, such as MetaMask.

  • 0x06: It follows the same unlocking method used by CKB MultiSig with a minor modification. When a message is calculated for signing, there is a step to clear the witnesses. In Omnilock, this involves clearing the entire lock field in witnesses. But in CKB MultiSig Script, it is only a partial clearance. This part is used as signatures followed by multisig_script.

  • 0xFC: It represents the blake160 hash of a Lock Script that checks if the current transaction contains an input Cell with a matching Lock Script. Otherwise, it would return with an error, similar to P2SH in BTC.

  • 0xFD: It represents the blake160 hash of a preimage that contains exec information used to delegate signature verification to another Script via exec.

  • 0xFE: It represents the blake160 hash of a preimage that contains dynamic linking information used to delegate signature verification to the dynamic linking Script. The interface described in Swappable Signature Verification Protocol Spec is used here.

Omnilock args​

The structure of Omnilock args is as follows:

<1 byte Omnilock flags> <32 byte AdminList cell Type ID, optional> <2 bytes minimum ckb/udt in ACP, optional> <8 bytes since for time lock, optional> <32 bytes type script hash for supply, optional>
NameFlagsAffected ArgsAffected Args Size (byte)Affected Witness
administrator mode0b00000001AdminList cell Type ID32omni_identity/signature in OmniLockWitnessLock
anyone-can-pay mode0b00000010minimum ckb/udt in ACP2N/A
time-lock mode0b00000100since for timelock8N/A
supply mode0b00001000type script hash for supply32N/A

You can check out the details for different modes at RFC.

Witness Structure​

When unlocking an Omnilock, the corresponding witness must be a proper WitnessArgs data structure in Molecule format. In the lock field of the WitnessArgs, an OmniLockWitnessLock structure must be present as follows:

import xudt_rce;

array Auth[byte; 21];

table Identity {
identity: Auth,
proofs: SmtProofEntryVec,
}
option IdentityOpt (Identity);

// the data structure used in lock field of witness
table OmniLockWitnessLock {
signature: BytesOpt,
omni_identity: IdentityOpt,
preimage: BytesOpt,
}

To learn the detail of witness in Omnilock, check out the RFC

Script Deployed Info​

The Omnilock spec has been deployed to CKB's Mainnet Mirana and Testnet Pudge. The following are the deployment details:

Mainnet​

parametervalue
code_hash0x9b819793a64463aed77c615d6cb226eea5487ccfc0783043a587254cda2b6f26
hash_typetype
tx_hash0xc76edf469816aa22f416503c38d0b533d2a018e253e379f134c3985b3472c842
index0x0
dep_typecode

Testnet​

parametervalue
code_hash0xf329effd1c475a2978453c8600e1eaf0bc2087ee093c3ee64cc96ec6847752cb
hash_typetype
tx_hash0x3d4296df1bd2cc2bd3f483f61ab7ebeac462a2f336f2b944168fe6ba5d81c014
index0x0
dep_typecode

Offchain SDK Examples​

Rust SDK​

The following example demonstrates creating, signing, and sending a transaction using the Omnilock Script with the Rust SDK.

omnilock-example.rs
use ckb_sdk::{
constants::ONE_CKB,
transaction::{
builder::{CkbTransactionBuilder, SimpleTransactionBuilder},
handler::{omnilock, HandlerContexts},
input::InputIterator,
signer::{SignContexts, TransactionSigner},
TransactionBuilderConfiguration,
},
unlock::OmniLockConfig,
Address, CkbRpcClient, NetworkInfo,
};
use ckb_types::{
h256,
packed::CellOutput,
prelude::{Builder, Entity, Pack},
};
use std::{error::Error as StdErr, str::FromStr};

fn main() -> Result<(), Box<dyn StdErr>> {
let network_info = NetworkInfo::testnet();
let sender = Address::from_str("ckt1qrejnmlar3r452tcg57gvq8patctcgy8acync0hxfnyka35ywafvkqgqgpy7m88v3gxnn3apazvlpkkt32xz3tg5qq3kzjf3")?;
let receiver = Address::from_str("ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqv5dsed9par23x4g58seaw58j3ym5ml2hs8ztche")?;

let configuration = TransactionBuilderConfiguration::new_with_network(network_info.clone())?;

let iterator = InputIterator::new_with_address(&[sender.clone()], configuration.network_info());
let mut builder = SimpleTransactionBuilder::new(configuration, iterator);

let output = CellOutput::new_builder()
.capacity((128 * ONE_CKB).pack())
.lock((&receiver).into())
.build();
builder.add_output_and_data(output.clone(), ckb_types::packed::Bytes::default());
builder.set_change_lock((&sender).into());

let omni_cfg = OmniLockConfig::from_addr(&sender).unwrap();
let context = omnilock::OmnilockScriptContext::new(omni_cfg.clone(), network_info.url.clone());

let mut contexts = HandlerContexts::default();
contexts.add_context(Box::new(context) as Box<_>);

let mut tx_with_groups = builder.build(&mut contexts)?;

let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());

let private_key = h256!("0x6c9ed03816e3111e49384b8d180174ad08e29feb1393ea1b51cef1c505d4e36a");
TransactionSigner::new(&network_info).sign_transaction(
&mut tx_with_groups,
&SignContexts::new_omnilock(
[secp256k1::SecretKey::from_slice(private_key.as_bytes())?].to_vec(),
omni_cfg,
),
)?;

let json_tx = ckb_jsonrpc_types::TransactionView::from(tx_with_groups.get_tx_view().clone());
println!("tx: {}", serde_json::to_string_pretty(&json_tx).unwrap());

let tx_hash = CkbRpcClient::new(network_info.url.as_str())
.send_transaction(json_tx.inner, None)
.expect("send transaction");
// example tx: 0xc0c9954a3299b540e63351146a301438372abf93682d96c7cce691c334dd5757
println!(">>> tx {} sent! <<<", tx_hash);

Ok(())
}

Other example: