Status: 0.30 development contract for issues #9 and #10, plus the exact artifact identity layer of #11.
cellscript-protocol-bundle-v1 is an off-chain composition object for two or
more independently compiled CKB Script artifacts. The offline checker:
- loads each referenced ELF, compile metadata, lowering record, source map, and action-builder manifest from paths confined to the input document's directory;
- admits every artifact through
cellscript-artifact-checkerand the current metadata validator; - binds the selected entry, package/lock identity, exact ELF hash, interface, typed semantics, target profile, deployment, code CellDep, and generated builder projection, then derives an exact Script/verifier receipt and fixed-width handle;
- merges explicitly named input, output, witness, CellDep, HeaderDep, fee, and change claims against one deterministic transaction skeleton;
- validates that same skeleton and its explicit builder-assumption evidence against every admitted artifact's compile metadata; and
- resolves closed cross-Script roles through checked Molecule schema, interface, ELF, and deployment identities; and
- emits a stable bundle hash, conflict report, and runtime evidence template.
This layer does not link ELF files, merge their trust boundaries, call one CKB
Script from another, query RPC, sign, submit, or claim CKB-VM/chain evidence.
The separate cellscript-ckb-adapter materialization boundary can turn a
successful report with concrete fields into one packed CKB transaction without
changing those limits.
The local authoring document uses
cellscript-protocol-bundle-input-v1. Its artifact paths are local evidence
locators and are excluded from the resolved bundle hash. Successful checking
emits a cellscript-protocol-bundle-v1 object inside a
cellscript-protocol-bundle-report-v1 report.
The bundle_hash is CKB Blake2b-256 over:
"cellscript-protocol-bundle-v1" || 0x00 || canonical_json(resolved_bundle)
Artifacts are sorted by ID. Role, witness, dependency, and policy claims are sorted by their physical key and artifact identity. Transaction arrays keep their exact order because their position is part of CKB semantics. Reordering input declarations that describe the same composition therefore retains the same hash, while changing an index, claim, deployment, checked artifact, or transaction slot changes it.
The top-level input contains:
| Field | Contract |
|---|---|
network |
Non-empty chain ID plus canonical 0x-prefixed 32-byte lowercase genesis hash |
artifacts |
2 to 64 independent checked ELF references |
deployment_lines |
Required exact active Type ID admission/code Cell evidence for every ckb-type-hash artifact; forbidden for other profiles |
transaction |
Version-0 transaction skeleton with exact ordered cell, witness, CellDep, HeaderDep, fee-policy, and change-policy commitments; optional concrete adapter fields and bounded-output-plan evidence are hash-bound when present |
roles |
Named input/output indexes with exclusive or shared-read ownership and optional exact Script/resource/cell/capacity requirements |
closed_roles |
Typed provider/consumer relations over existing Cell or witness claims, using cellscript-protocol-closed-role-v1 |
witnesses |
Exact WitnessArgs index/field, ABI, commitment, signing domain, and write/read ownership |
cell_deps / header_deps |
Named logical dependencies mapped to exact global positions |
policies |
Fee or change policy hashes with exclusive or shared ownership |
Every artifact record names a package coordinate and exact Cell.lock node
identity, one selected action/lock/function, the Script role (lock, type,
or spawned-verifier), four checked artifact files, and one exact deployment.
Action entries must also supply the cellscript-generated-action-builder-v0.23-edition-2026
manifest emitted by cellc gen-builder. Its metadata/artifact/compiler/profile
identities, runtime contract, structural manifests, and selected action
projection must agree with the admitted metadata. Lock entries may omit it
because the current generated builder surface is action-oriented.
For data, data1, and data2 deployments, the Script code hash must equal
the checked ELF's CKB hash. A type deployment binds the separately supplied
Type-hash identity. All deployments must use a hash type admitted by the
artifact target profile.
Every resolved artifact contains two additional issue #11 identities:
exact_handle_receiptusescellscript-exact-script-handle-receipt-v1to bind package coordinate, exactCell.locknode, selected entry, Script role, interface, typed semantics, ELF, target profile, the package/Registry ABI hash, verified bundle, deployment Script, code CellDep, and chain identity;exact_handleusescellscript-exact-script-handle-value-v1andCSHDLv1-fixed-202, a 202-byte value containing a magic/version class, explicit Script role, and six 32-byte commitments for the receipt, complete CKB Script, interface, ELF, target profile, and runtime ABI.
The complete Script commitment is CKB Blake2b-256 over canonical Molecule
Script bytes. Its encoding is checked against ckb-types for data,
data1, data2, and type. Data-hash deployments require the Script
code_hash to equal the admitted ELF hash; Type-hash deployments retain an
explicit exact-code-Cell policy and bind the ELF and code CellDep separately.
The handle is a copyable identity value. It does not own, create, consume,
replace, relock, execute, or authorize a Cell.
Every ckb-type-hash artifact additionally requires one
cellscript-deployment-line-admission-evidence-v1 record. It retains the full
line receipt and CSLINv1-fixed-386 value, a distinct standard TYPE_ID
admission Cell and TYPE_ID code Cell, the admission Cell's exact
CSREGv1 || line_handle_hash data, both data hashes, and the two direct CellDep
positions. The checker binds the current exact receipt to the independently
admitted artifact and rejects yanked state or any Script, data, out-point, or
position substitution. The runtime adapter remains responsible for obtaining
these Cell records from the selected chain and proving both out points are live
immediately before signing. It now does so in the dependency-resolution stage:
the node query set is the union of artifact code CellDeps and deployment-line
admission/code CellDeps, and the ready-to-sign evidence records the number of
verified deployment-line Cells. The adapter also retains the resolved target
profile name and independently rejects a missing line or a data2/type
profile substitution.
Paths must be relative, must resolve to regular files inside the input document's directory, and are read only after byte budgets are checked. The standalone checker must report verified binding, structure, lowering, and typed semantics for every artifact.
exclusive means one artifact owns the cell role. shared-read means the
artifact only observes it. Two exclusive claims on one physical cell conflict.
One exclusive role and compatible shared readers can coexist, which permits a
Lock Script and Type Script to validate the same Cell. Explicit expected Lock,
Type, resource, cell, and capacity values must still agree with each other and
the transaction skeleton.
Witness fields use exclusive-write or shared-read. Multiple writers
conflict. A writer and readers may share a field only when their ABI, value
commitment, and signing domain agree. Optional lock_bytes,
input_type_bytes, and output_type_bytes values are exact lowercase hex. If
the corresponding commitment is present, it must equal CKB Blake2b-256 of
those bytes. This records ownership and materialization; it does not
manufacture a signature or canonical signing message.
An action that uses the admitted bounded output Plan contract adds one
cellscript-bounded-output-plan-evidence-v1 record to the transaction
skeleton. The record binds the action and parameter, WitnessArgs.input_type
index, exact inner Plan bytes, current Type Script hash, and the strictly
ordered GroupOutput indexes. ProtocolBundle projects the committed witness
bytes plus each Cell's Script hashes and capacity into the per-artifact
metadata validator. This lets PB212 validate the same Plan-to-output
correspondence before materialization while retaining the canonical skeleton
as the bundle hash input.
A closed role connects already admitted artifacts; it does not select a Script at runtime. The provider and every consumer reference an existing physical Cell or witness claim:
{
"schema": "cellscript-protocol-closed-role-v1",
"role_id": "settlement-record",
"kind": "cell",
"schema_identity": {
"type_name": "SettlementRecord",
"schema_hash": "...64 lowercase hex digits..."
},
"provider": { "artifact": "order-type", "claim": "settlement-output" },
"consumers": [
{ "artifact": "token-type", "claim": "settlement-output" }
],
"correspondence": "exact-physical-binding"
}The provider must own the physical Cell claim with exclusive, or the witness
claim with exclusive-write. Consumers must use shared-read. Every claim
must resolve to the identical Cell slot or WitnessArgs field and name the
same resource/ABI as schema_identity.type_name. Checked metadata for every
participant must expose that exact Molecule type name and schema hash.
The resolved record copies each participant's package coordinate, selected entry, Script role, interface hash, ELF hash, complete deployment identity, and exact fixed-width handle into the canonical bundle. This makes the relation auditable and hash-bound; it does not mean that CKB-VM links the ELFs or calls one Script from another. Runtime-selected/open roles remain outside this schema and are tracked as post-v1 work in #29, dependent on #28's separately versioned compatible-open Script-handle contract.
The checker returns conflicts in stable code/key/artifact order and never chooses a winner:
| Code | Class | Rejection condition |
|---|---|---|
PB200 |
input ownership | Multiple artifacts claim exclusive ownership of one cell slot |
PB201 |
output placement | A cell commitment or output placement disagrees |
PB202 |
witness ABI | Witness field writers, ABIs, or commitments disagree |
PB203 |
dependency ordering | A CellDep/HeaderDep identity or logical position disagrees |
PB204 |
Script identity | Expected Lock/Type Script identity disagrees |
PB205 |
resource identity | Explicit logical/Type-ID resource identities disagree |
PB206 |
capacity | Exact or minimum capacity is not satisfied |
PB207 |
fee/change | Fee/change policy identity or exclusive ownership disagrees |
PB208 |
network/deployment | An artifact deployment uses another chain identity |
PB209 |
profile/version | Target, VM, source encoding, or ABI profile hashes disagree |
PB210 |
signature policy | Signing domains disagree for one witness field |
PB211 |
skeleton binding | A claimed global index does not exist |
PB212 |
builder validation | The shared transaction skeleton or supplied evidence violates an artifact's metadata builder assumptions |
PB213 |
closed typed role | A provider/consumer has the wrong ownership, physical source, schema, interface-bound artifact, or deployment identity |
Malformed schemas, unknown fields, duplicate artifact/claim identities, unknown artifact references, non-canonical hashes, escaped paths, over-budget inputs, and failed artifact admission are structural errors rather than resolvable conflicts.
cellc protocol bundle check protocol-bundle.json --json
cellc protocol bundle check protocol-bundle.json --output build/protocol-bundle.report.jsonOn success, status is ok, conflicts is empty,
evidence.structural_verification is verified, and every
evidence.metadata_transaction_validation entry is ok. On conflict, the
command exits unsuccessfully after writing the requested report so tooling can
inspect the complete conflict set. No signing or network operation occurs.
cellscript_ckb_adapter::materialize_protocol_bundle_report consumes the exact
JSON report emitted by cellc protocol bundle check. It independently:
- checks the report state, per-artifact standalone admission, and metadata transaction-validation coverage;
- recomputes the canonical resolved-bundle hash;
- requires an
out_pointfor every input and exactdatabytes for every output; - verifies committed witness bytes and preserves ordered inputs, outputs,
witnesses, CellDeps, and HeaderDeps in a Molecule
TransactionView; - retains bounded-output-plan evidence in the canonical bundle while excluding it from Molecule transaction serialization;
- checks occupied output capacity and computes the capacity remainder as the candidate fee; and
- resolves each selected Lock or Type artifact to global and group-relative indexes, with the same complete serialized transaction hash attached to every group record.
Its cellscript-protocol-bundle-materialization-v1 evidence records the raw
transaction hash, complete serialized transaction hash and byte size, input,
output, occupied-capacity and fee totals, and per-artifact Script Group
identity. Transaction serialization is verified; every group execution,
CKB-VM evidence, and chain evidence remains not-executed. Spawned verifiers
bind their exact code CellDep but are not misreported as direct CKB Script
Groups. Input capacity and the resulting fee are sourced from the bundle
skeleton until live Cell resolution verifies them.
CkbSdkAcceptance::verify_protocol_bundle_live_inputs first checks the
connected node's chain ID and genesis hash, then resolves every transaction
input with get_live_cell(..., true). Each Cell must still be live and its
packed CellOutput, data hash, capacity, OutPoint, and order must match the
hash-bound materialization expectation. The resulting
cellscript-protocol-bundle-live-resolution-v1 record changes
capacity_source to live-node and recomputes the fee from verified live
inputs. This is uncommitted live-state evidence; the Cell can still be spent
before submission.
CkbSdkAcceptance::verify_protocol_bundle_live_dependencies then resolves
every artifact code CellDep on the same connected chain. A direct code dep must
contain bytes whose CKB data hash equals the admitted ELF hash. A dep-group root
must decode as a canonical Molecule OutPointVec; every listed member must be
live and one must satisfy the artifact identity. Data, data1, and data2 Scripts
bind that data hash directly, while type Scripts bind the live code Cell's Type
Script hash. The dependency evidence is rejected unless the earlier live-input
record preserves every input observation and the exact materialized transaction
identity.
The signing path is an ordered state machine:
protocol_bundle_ready_to_sign_evidencerequires exact live-input and live-dependency evidence.unlock_protocol_bundle_transactionruns caller-supplied CKB SDK unlockers, rejects remaining Lock Groups, preserves the raw transaction and all compiler-owned witness fields, and records only changed witness lock indexes.dry_run_signed_protocol_bundleexecutes those signed bytes and upgrades signature verification from pending to node-verified.test_signed_protocol_bundlebinds tx-pool cycles and the node-computed fee to the same signed serialization hash.submit_signed_protocol_bundlechecks that evidence before RPC submission and emits an explicitly uncommitted receipt.wait_for_protocol_bundle_confirmationpolls the canonical transaction location to a caller-selected depth, restarts if an observed inclusion disappears or changes, and emitsConfirmedProtocolBundleTxonly after a final location recheck.
Private keys are never fields in the ProtocolBundle or its evidence. Hardware,
wallet, and software signers remain behind CKB SDK ScriptUnlocker interfaces.
Generated TypeScript builders expose the same rule through
ProtocolBundleSigningRequest.privateKeysIncluded = false. They also export
bindClosedProtocolRole, which requires every bound generated artifact to
expose the exact Molecule type/hash before producing a closed-role input. Their
createProtocolBundleClient rejects duplicate artifact IDs, wrong stage names,
and any bundle or raw-transaction identity change between stages. Each package
also exports bindProtocolBundleArtifact, which refuses a deployment ELF hash
different from the generated builder's admitted artifact hash.
Builder manifests bind the exact receipt/value schemas, fixed-width encoding,
target-profile hash, and existing ABI hash. bindCheckedExactScriptHandle
accepts a receipt/value returned by a successful bundle check only when its
role, entry, interface, typed semantics, ELF, profile, ABI, verified bundle,
deployment, and fixed-width shape match the generated artifact. Cryptographic
receipt/value recomputation remains the Rust checker's responsibility.
The compiler-side script_handle::exact_script_handle_value_hash function
computes the CKB Blake2b-256 literal consumed by source-level exact-handle
runtime helpers. It hashes the full fixed value, so changing any receipt,
interface, artifact, profile, ABI, role, or Script identity byte changes the
literal.
Successful bundle artifacts and closed-role participants emit that value as
exact_handle_hash; generated TypeScript binders retain it as handleHash.
For Type-hash artifacts, deploymentLineHandleFromCheckedBundle returns the
checked 386-byte active line value and its admission/code CellDep positions.
deploymentLineHandleEvidence projects those fields, the selected source, and
the exact WitnessArgs.input_type index into the evidence shape consumed by
cellc tx validate; the on-chain helper still recomputes the full handle hash
against its compiled literal.
Registry verified-build evidence makes that capability discoverable without
weakening admission. A release carries protocol_bundle_schema,
protocol_bundle_artifact_binding_schema, and
protocol_bundle_runtime_adapter only when its CKB ELF, compile metadata,
lowering record, and source map pass the standalone checker. The values are
exactly cellscript-protocol-bundle-v1,
cellscript-protocol-bundle-artifact-binding-v1, and
cellscript-ckb-adapter. The Registry rejects partial or unknown triples and
does not attach them to generic hash-bound executables or source-only
snapshots. Composition still re-admits the exact local files and, for action
entries, the generated builder manifest.
CkbSdkAcceptance::dry_run_protocol_bundle sends that exact packed transaction
to CKB estimate_cycles. A successful response emits
cellscript-protocol-bundle-dry-run-v1: every direct Lock/Type group is marked
accepted-by-aggregate-estimate-cycles under the same serialized byte hash,
and the aggregate cycle count is retained. The RPC does not expose per-group
cycles, so each group cycle field remains null and cycle_attribution states
that limitation. Spawned-verifier records remain not-independently-observed
unless later execution evidence proves that path ran. Dry-run evidence is
uncommitted and does not imply tx-pool acceptance or chain confirmation.
The v1 offline report retains the standalone checker report and metadata
transaction-validation report for every artifact. Generated action-builder
manifests are admitted and exact selected-action projections are checked.
For Type-hash lines it also records structurally verified deployment-line
admission bindings. Adapter dependency evidence advances those bindings to
live-node observations without treating the uncommitted node response as final
chain evidence.
transaction_serialization, ckb_vm_execution, and chain_evidence remain
not-executed, with no exact transaction hash. This is the Phase 0
format/threat-model contract plus the builder-contract portion of Phase 1
offline composition. The adapter evidence advances a concrete report to a
byte-exact transaction while preserving the offline report unchanged.
Later bundle phases may add builder-selected live Cells, independently attributed per-group cycles from a backend that can report them, and product-specific finality policies. None is inferred by the closed-role contract or the current adapter evidence.
The original compiler report remains offline structural evidence. Adapter materialization, live resolution, dependency resolution, signed dry-run, tx-pool, and submission records advance the same hash-bound transaction without rewriting that report. Submission is still uncommitted. The versioned confirmation record upgrades it only to an observed canonical inclusion at the required depth and explicitly does not claim absolute finality.