CellScript Wiki
Tutorial 15: LS-IDL for CKB Lock Scripts
Tutorial 15: LS-IDL for CKB Lock Scripts
Status: current CellScript 0.24 workflow for LS-IDL 0.1 Lock Script interfaces.
LS-IDL describes the witness fields expected by a CKB Lock Script. CellScript Registry preserves that description as exact bytes and binds it to the deployed executable with this commitment:
code Cell data = executable bytes || SHA-256(raw idl.json bytes)This tutorial follows the complete implemented workflow:
raw idl.json
-> cellc validate
-> cellc bind to a clean CKB executable
-> cellc bundle and publish
-> deploy the bound executable
-> record chain evidence
-> cellc fetch the exact IDL by Script identityThe word raw matters. Whitespace, key order, and the final newline are part of the committed IDL identity. Parsing and reserialising equivalent JSON can produce different bytes and therefore a different SHA-256 digest.
What cellc Does
The LS-IDL command group is:
cellc artifact ls-idl <validate|bind|bundle|fetch>It provides four bounded operations:
validatechecks the supported LS-IDL 0.1 schema and reports the SHA-256 of the exact file bytes;bindappends that 32-byte digest to a CKB executable;bundlecreates the immutable Registry bundle andArtifact.toml; andfetchresolves an IDL by a chain-verified CKB Script identity, validates the Registry response contract, and writes the exact response bytes.
cellc does not derive idl.json from a Rust type, compile an external Rust,
C, or JavaScript Lock Script, deploy a code Cell, or sign a deployment
transaction. Use
`ckb-idl-derive`,
`ckb-idl-client`, and the Lock
Script's own build and deployment workflow for those steps. The executable
passed to cellc must already be a real CKB RISC-V artifact.
The checked-in walkthrough inputs are under
`examples/registry_ls_idl`.
lock.rs and idl.json show the derive-to-document relationship, but the
example intentionally does not pretend to be an audited or deployable Lock
Script.
Prerequisites
Prepare all of the following before publishing:
- CellScript 0.24
cellc; - the original LS-IDL 0.1
idl.json; - a clean, unbound CKB Lock Script executable;
- the source file to include in the Registry bundle;
- an immutable 40- or 64-hex source revision;
- the intended CKB
hash_typeanddep_type; and - a compatible CKB wallet for Registry writes and deployment.
Validation and lookup are public read operations and do not require a wallet. Wallet authorisation is required when publishing or attaching deployment evidence.
1. Prepare the IDL Document
A small document looks like this:
{
"idl_version": "0.1",
"name": "demo_lock",
"witness": [
{
"name": "signature",
"type": "secp256k1_sig",
"required": true,
"description": "Recoverable CKB secp256k1 signature"
},
{
"name": "nonce",
"type": "uint64",
"required": true
},
{
"name": "memo",
"type": "bytes",
"required": false
}
]
}The Registry profile accepts a non-empty JSON object no larger than 256 KiB.
witness is required and may contain at most 256 fields. Field names must be
unique.
The implemented field types are:
| LS-IDL type | Linear encoding |
|---|---|
uint8 |
one unsigned byte |
uint32 |
four-byte little-endian unsigned integer |
uint64 |
eight-byte little-endian unsigned integer |
secp256k1_sig |
65 bytes |
secp256k1_pubkey |
33 bytes |
schnorr_sig |
64 bytes |
bytes |
four-byte little-endian length followed by the payload |
The optional top-level fields are idl_version, name, description,
script_version, and signing. When present, signing contains exactly the
non-empty string fields algorithm, message, and hasher.
In LS-IDL 0.1, required: false is descriptive interface metadata. The current
linear decoder has no presence bitmap and does not skip that field on the
wire. Do not use required: false as an optional-field encoding rule.
2. Validate the Exact Bytes
Run validation before touching the executable:
cellc artifact ls-idl validate --idl idl.json --jsonThe JSON result includes:
{
"status": "valid",
"format": "ls-idl",
"format_version": "0.1",
"sha256": "<SHA-256 of the exact idl.json bytes>",
"executable_suffix_bound": false
}This step rejects malformed JSON, unknown keys, unsupported types, duplicate field names, missing required field properties, and profile budget violations. It does not modify the IDL or executable.
Treat the reported digest as part of the release identity. If idl.json
changes afterward, even only in formatting, repeat validation and binding.
3. Bind the IDL to the Executable
Always bind from a clean build output:
cellc artifact ls-idl bind \
--idl idl.json \
--executable build/demo-lock \
--output build/demo-lock.ls-idl \
--jsonbind validates the IDL, computes SHA-256(raw idl.json bytes), and writes:
build/demo-lock bytes || 32-byte IDL digestIt never silently overwrites an existing output. Choose a new output path or
pass --force only when replacing that exact intended file. If the input
already ends with the same digest, bind does not append it a second time.
Do not repeatedly bind updated IDLs onto an already bound artifact. Rebuild or return to the clean executable, then bind the new IDL once. The bound output is the artifact that must be tested, hashed, deployed, and published; the original unbound executable has a different CKB data hash and is not the registered LS-IDL artifact.
Verify the final pair explicitly:
cellc artifact ls-idl validate \
--idl idl.json \
--executable build/demo-lock.ls-idl \
--jsonThe result now reports executable_suffix_bound: true. A suffix mismatch is a
hard error.
4. Create the Registry Bundle
Create the immutable bundle only from the bound executable:
cellc artifact ls-idl bundle \
--idl idl.json \
--executable build/demo-lock.ls-idl \
--source src/lib.rs \
--namespace example \
--name demo-lock \
--release 0.1.0 \
--language rust \
--hash-type data1 \
--dep-type code \
--toolchain 'rustc 1.97.1 + ckb-std' \
--source-revision <40-or-64-hex-immutable-revision> \
--output artifact.bundle.json \
--artifact-manifest-output Artifact.toml \
--jsonThe accepted --language values are cellscript, rust, c, javascript,
and other. --hash-type defaults to data1; --dep-type defaults to
code. State both explicitly in release automation so deployment evidence
cannot inherit an accidental default.
The command writes two files:
artifact.bundle.jsoncontains exactly onesource,executable, andabiobject as Base64-encoded bytes; andArtifact.tomlnames thedeployable_contractrelease and points to that bundle.
It also reports four different identities:
| Output field | Algorithm and object | Purpose |
|---|---|---|
source_hash |
CKB Blake2b-256 of the source object | immutable source-object identity |
artifact_hash |
CKB Blake2b-256 of the bound executable | CKB executable/data identity |
abi_hash |
CKB Blake2b-256 of the raw IDL object | Registry ABI object identity |
idl_sha256 |
SHA-256 of the raw IDL object | LS-IDL executable-suffix commitment |
Do not substitute one hash for another. In particular, LS-IDL uses SHA-256 for the suffix even though the Registry artifact and ABI objects also have CKB Blake2b-256 identities.
The generated profile records script_role = "lock",
encoding = "linear-le-v0", build.reproducible = false, and
security.status = "review_required". The command does not manufacture a
reproducibility or audit claim.
5. Dry-Run and Publish
Validate the generated bundle locally before any Registry write:
cellc publish \
--artifact-manifest Artifact.toml \
--dry-run \
--jsonThe dry-run rechecks the coordinate, object roles, size limits, profile contract, raw IDL schema and hashes, and executable suffix. It does not upload anything.
For a first production publish, let cellc create a scoped delegated key and
open the wallet authorisation flow:
cellc publish \
--artifact-manifest Artifact.toml \
--authorise \
--jsoncellc prints the pending cap_... publishing key ID, stores its private key
in the local OS keychain, opens a 15-minute browser session, waits for CKB
wallet approval, and then continues the publish. The private key does not enter
the browser.
This short interactive path requests only
publish:example/demo-lock. It deliberately does not grant permission to
attach deployment evidence or change availability. Those scopes are
independent.
For the isolated Pudge Testnet Registry, select its API explicitly:
cellc publish \
--artifact-manifest Artifact.toml \
--authorise \
--api-url https://api.testnet.registry.cellscript.dev \
--jsonProduction and Testnet have separate API origins, databases, object stores, wallet state, and chain evidence. Do not publish to one environment and record the deployment in the other.
After admission, the release starts with independent states similar to:
verification_status = pending
deployment_status = undeployed
availability_status = activeThe Registry worker must accept the immutable bundle and promote its integrity
evidence before deployment evidence can be attached. A hash_bound result is
an identity/integrity statement, not a security review.
6. Deploy the Bound Artifact and Record Evidence
Deploy build/demo-lock.ls-idl, not build/demo-lock. Deployment transaction
construction, capacity, fees, witnesses, signing, and broadcast remain in the
external CKB builder and wallet.
After the deployment transaction is committed, attach its OutPoint to the
published release. First authorise a delegated key with the exact deployment
scope. The principal_id is the normalized identity binding derived from the
connected signer, not the displayed CKB address. Choose joyid_ckb or
ckb_secp256k1 for --principal-type:
cellc auth capability create \
--principal-type <principal-type> \
--principal-id <normalized-wallet-principal-id> \
--scope deployment:example/demo-lock \
--expires 90d \
--json > deployment-capability.jsonSign the payload with the matching CKB wallet, save the wallet result as
deployment-wallet-signature.json, and submit it:
cellc auth capability submit \
--payload deployment-capability.json \
--wallet-signature deployment-wallet-signature.json \
--jsoncreate stores the generated delegated private key in the local OS keychain;
submit returns its cap_... key ID after the Registry accepts the
wallet-rooted grant. For Testnet, add
--registry-origin https://api.testnet.registry.cellscript.dev to create
and --api-url https://api.testnet.registry.cellscript.dev to submit.
The manual flow may request publish: and deployment: together when one key
must perform both operations, but neither scope implies the other. This
tutorial keeps them separate so a deployment key cannot publish a new release.
Then record the Testnet deployment:
cellc artifact record-deployment example/demo-lock@0.1.0 \
--network testnet \
--api-url https://api.testnet.registry.cellscript.dev \
--code-hash 0x<64-hex> \
--hash-type data1 \
--dep-type code \
--tx-hash 0x<64-hex-deployment-transaction-hash> \
--index 0 \
--capability-key-id cap_<deployment-key-id> \
--jsonFor production, use --network mainnet and the default production Registry
origin. The command signs the deployment record with the delegated key, while
the Registry verifies the configured RPC network, live code Cell, committed
creation transaction, confirmations, artifact data hash, Script identity, and
declared deployment mode.
The immutable profile and the evidence command must agree on hash_type and
dep_type:
- for
data,data1, ordata2,code_hashidentifies the bound code Cell data; and - for
type,code_hashis the code Cell Type Script hash. Later LS-IDL lookup also needs the current code Celldata_hashto disambiguate the executable bytes.
Do not describe a locally generated deployment payload as chain evidence. The Registry lookup becomes available only after the release is active, public, and backed by accepted chain-verified deployment evidence.
7. Fetch the Exact IDL by Script Identity
For a mainnet data1 Script:
cellc artifact ls-idl fetch \
--code-hash 0x<64-hex> \
--hash-type data1 \
--network mainnet \
--output fetched-idl.json \
--jsonFor Testnet, use the matching Registry API:
cellc artifact ls-idl fetch \
--code-hash 0x<64-hex> \
--hash-type data1 \
--network testnet \
--api-url https://api.testnet.registry.cellscript.dev \
--output fetched-idl.json \
--jsonFor a Type Hash deployment, add the live code Cell data hash:
cellc artifact ls-idl fetch \
--code-hash 0x<64-hex-type-script-hash> \
--hash-type type \
--data-hash 0x<64-hex-code-cell-data-hash> \
--network mainnet \
--output fetched-idl.json \
--jsonfetch refuses to overwrite an existing output unless --force is explicit.
Before writing, it requires the LS-IDL content type and
schema-and-suffix-bound verification header, enforces the 256 KiB limit,
validates the LS-IDL schema, hashes the response body directly, and compares
that digest with the Registry header.
The public browser flows expose the same lookup for Mainnet and the isolated Pudge Testnet. Lookup and download are read-only; connecting a wallet is needed only for Registry writes.
8. Understand the Evidence Boundary
The workflow deliberately separates evidence:
| Stage | What it establishes |
|---|---|
validate --idl |
supported schema and SHA-256 of the exact IDL bytes |
validate --executable |
the executable ends with that exact 32-byte digest |
bundle |
source, executable, ABI objects, hashes, and closed profile agree |
| Registry verification | admitted immutable objects satisfy the same profile contract |
record-deployment |
accepted live-chain deployment evidence matches the published artifact |
fetch |
the returned exact bytes match the Registry digest and verification contract |
None of these stages proves that the Lock Script actually decodes every field as described, applies the intended signing rules, authorises the correct user, accepts a complete valid transaction set, rejects every invalid transaction, or is secure. LS-IDL Registry support is a byte-identity and deployment-binding contract. Implementation tests, CKB-VM transaction tests, review, and audit remain separate responsibilities.
cellc verify-artifact is also a different boundary. It independently checks
the four-file lowering/source-map bundle emitted by CellScript CKB builds; it
is not a replacement LS-IDL verifier for an arbitrary Rust or C executable.
Common Failures
The executable suffix does not match
The IDL was changed or reformatted after binding, or the unbound executable was selected. Return to the clean executable and bind the final IDL bytes again.
cellc refuses to overwrite a file
This is intentional. Use a new output path, or pass --force only after
checking the exact target.
Type Hash lookup requires --data-hash
A Type Script hash may identify more than one executable data revision. Supply
the current code Cell data hash; the Registry returns 409 rather than choosing
an ambiguous deployment.
Fetch returns not found
Check the Registry environment, network, code_hash, hash_type, and optional
data_hash. A published bundle is not enough: the release must also be active,
public, and chain-verified on the requested network.
Publish succeeds but lookup is not ready
Publication, verification, and deployment are separate states. Wait for bundle verification, deploy the bound artifact, and record the committed deployment OutPoint before expecting Script-identity lookup to succeed.
Next
Read Registry Artifacts End to End for publisher capabilities, deployment evidence, availability, and artifact consumption. Use the LS-IDL Registry Profile as the closed schema and API reference.