Skip to content

Commit 4c02e21

Browse files
committed
fix versioned entry witness placement ABI
1 parent dc66c6e commit 4c02e21

15 files changed

Lines changed: 420 additions & 95 deletions

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
- Add the explicit `cellscript-witnessargs-input-type-v2` placement ABI for
6+
parameterized CKB entries. Generated wrappers now resolve witnesses relative
7+
to the active script group, decode the `CSARGv1` payload from
8+
`WitnessArgs.input_type`, preserve wallet/multisig ownership of `lock`, reject
9+
malformed or wrongly placed payloads, and retain group-relative raw-v1
10+
compatibility. A canonical multisig-v2 CKB-VM regression covers a type group
11+
whose first input is not transaction input zero.
12+
313
## 0.22.0 - 2026-07-19
414

515
- Make GitHub publication depend on the full release gate. Release evidence now

‎Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ ckb-acceptance = []
118118
pretty_assertions = "1.4"
119119
tempfile = "3.10"
120120
ckb-testtool = "1.1"
121+
ckb-sdk = { path = "../ckb-sdk-rust" }
121122
ckb-std = { version = "1.1.0", default-features = false, features = ["type-id"] }
122123
sha2 = "0.10"
123124
regex = "1"

‎crates/cellscript-ckb-adapter/src/lib.rs‎

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -343,11 +343,19 @@ pub struct ScriptCodeDepEvidence {
343343
pub dep_type: String,
344344
}
345345

346+
pub const ENTRY_WITNESS_PLACEMENT_ABI: &str = "cellscript-witnessargs-input-type-v2";
347+
346348
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
347-
pub enum WitnessPlacement {
348-
Lock,
349-
InputType,
350-
OutputType,
349+
pub enum EntryWitnessPlacementAbi {
350+
WitnessArgsInputTypeV2,
351+
}
352+
353+
impl EntryWitnessPlacementAbi {
354+
pub const fn name(self) -> &'static str {
355+
match self {
356+
Self::WitnessArgsInputTypeV2 => ENTRY_WITNESS_PLACEMENT_ABI,
357+
}
358+
}
351359
}
352360

353361
#[derive(Debug, Clone, Serialize)]
@@ -1421,30 +1429,18 @@ pub fn require_script_code_dep(script: &Script, deps: &[ScriptCodeDep]) -> Resul
14211429
Ok(dep.to_cell_dep())
14221430
}
14231431

1424-
pub fn place_entry_witness_payload(base: &WitnessArgs, placement: WitnessPlacement, payload: Bytes) -> Result<WitnessArgs> {
1432+
pub fn place_entry_witness_payload(base: &WitnessArgs, placement: EntryWitnessPlacementAbi, payload: Bytes) -> Result<WitnessArgs> {
14251433
if payload.is_empty() {
14261434
bail!("CellScript entry witness payload must be non-empty");
14271435
}
14281436

14291437
match placement {
1430-
WitnessPlacement::Lock => {
1431-
if base.lock().to_opt().is_some() {
1432-
bail!("refusing to overwrite WitnessArgs.lock; lock signatures must stay explicit");
1433-
}
1434-
Ok(base.clone().as_builder().lock(Some(payload).pack()).build())
1435-
}
1436-
WitnessPlacement::InputType => {
1438+
EntryWitnessPlacementAbi::WitnessArgsInputTypeV2 => {
14371439
if base.input_type().to_opt().is_some() {
14381440
bail!("refusing to overwrite WitnessArgs.input_type");
14391441
}
14401442
Ok(base.clone().as_builder().input_type(Some(payload).pack()).build())
14411443
}
1442-
WitnessPlacement::OutputType => {
1443-
if base.output_type().to_opt().is_some() {
1444-
bail!("refusing to overwrite WitnessArgs.output_type");
1445-
}
1446-
Ok(base.clone().as_builder().output_type(Some(payload).pack()).build())
1447-
}
14481444
}
14491445
}
14501446

@@ -2355,13 +2351,16 @@ mod tests {
23552351
fn places_cellscript_entry_payload_without_hiding_lock_signatures() {
23562352
let base = WitnessArgs::new_builder().lock(Some(Bytes::from(vec![0x77u8; 65])).pack()).build();
23572353
let payload = Bytes::from(b"CSARGv1\0\x4d\0\0\0\0\0\0\0".to_vec());
2358-
let witness = place_entry_witness_payload(&base, WitnessPlacement::InputType, payload.clone()).unwrap();
2354+
let placement = EntryWitnessPlacementAbi::WitnessArgsInputTypeV2;
2355+
assert_eq!(placement.name(), "cellscript-witnessargs-input-type-v2");
2356+
let witness = place_entry_witness_payload(&base, placement, payload.clone()).unwrap();
23592357
assert_eq!(witness.lock().to_opt().expect("lock preserved").raw_data().len(), 65);
23602358
assert_eq!(witness.input_type().to_opt().expect("entry payload").raw_data(), payload);
23612359
assert!(witness.output_type().to_opt().is_none());
23622360

2363-
let error = place_entry_witness_payload(&base, WitnessPlacement::Lock, Bytes::from(vec![1u8])).unwrap_err().to_string();
2364-
assert!(error.contains("lock signatures must stay explicit"), "{error}");
2361+
let occupied = witness;
2362+
let error = place_entry_witness_payload(&occupied, placement, Bytes::from(vec![1u8])).unwrap_err().to_string();
2363+
assert!(error.contains("refusing to overwrite WitnessArgs.input_type"), "{error}");
23652364
}
23662365

23672366
#[test]

‎docs/CELLSCRIPT_CKB_ADAPTER.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ RPC, and exposes signer, `estimate_cycles`, `test_tx_pool_accept`, and optional
9090
submission as adapter-owned node calls. It also builds headless deploy
9191
transactions that create TYPE_ID code cells from a `DeployArtifactSpec`, and
9292
generates `DeploymentManifest` records from the resulting evidence. It also
93-
tests that CellScript entry witness bytes are placed into an explicit
94-
`WitnessArgs` field without overwriting lock signatures, and that TYPE_ID
93+
tests that CellScript entry witness bytes use the versioned
94+
`cellscript-witnessargs-input-type-v2` contract and are placed into
95+
`WitnessArgs.input_type` without overwriting lock signatures, and that TYPE_ID
9596
args are computed from the packed first input plus output index before
9697
adapter submission.
9798

‎docs/CELLSCRIPT_ENTRY_WITNESS_ABI.md‎

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,53 @@
44
tooling.
55

66
CellScript action and lock entrypoints are normal RISC-V functions at the machine
7-
level. Most public arguments come through the grouped input witness. Lock
7+
level. Most public arguments come through the current script group's witness. Lock
88
parameters declared as `lock_args T` instead come from the executing lock
99
script's `Script.args` bytes. The compiler-generated `_cellscript_entry` wrapper
1010
loads the required source(s), validates the envelope or script-args layout,
1111
decodes positional arguments, and then tail-calls the selected action or lock.
1212

13-
## Envelope
13+
## Placement ABI v2
1414

15-
Every parameterized entry witness that has witness-backed arguments starts with:
15+
The current CKB placement contract is
16+
`cellscript-witnessargs-input-type-v2`:
17+
18+
```text
19+
WitnessArgs {
20+
lock: wallet / lock-script signatures,
21+
input_type: CellScript CSARGv1 entry payload,
22+
output_type: protocol-specific output witness data,
23+
}
24+
```
25+
26+
The generated wrapper first loads `GroupInput#0`. If the active script group
27+
has no input, it loads `GroupOutput#0`. It never substitutes transaction-global
28+
`Input#0`, because the first member of one lock/type group may be any global
29+
input index. The selected witness must be a canonical three-field Molecule
30+
`WitnessArgs`; its `input_type` `BytesOpt` must contain the entry payload.
31+
32+
This split lets canonical lock scripts, including multisig-v2, retain exclusive
33+
ownership of `WitnessArgs.lock`. Builders must preserve an existing lock field
34+
and fail rather than overwrite an existing `input_type` field.
35+
36+
For compatibility with transactions built before placement v2, the same
37+
group-relative source may still contain the raw v1 payload directly. Raw-v1 is
38+
recognized only by the exact `CSARGv1\0` prefix. A malformed `WitnessArgs`, an
39+
absent `input_type`, or a payload placed in `lock`/`output_type` fails closed
40+
with runtime error `25 entry-witness-abi-invalid`; those forms are not aliases.
41+
42+
## Payload Envelope v1
43+
44+
Every parameterized entry payload that has witness-backed arguments starts with:
1645

1746
```text
1847
43 53 41 52 47 76 31 00
1948
```
2049

2150
This is the ASCII magic `CSARGv1\0`.
2251

23-
Wrong magic, missing bytes, or unsupported parameter placement fails closed with
24-
runtime error `25 entry-witness-abi-invalid`.
52+
Wrong magic, missing bytes, malformed Molecule, or unsupported parameter
53+
placement fails closed with runtime error `25 entry-witness-abi-invalid`.
2554

2655
Entries whose parameters are entirely runtime-bound or `lock_args`-backed do not
2756
require a witness envelope.

‎scripts/cellscript_0_14_scope_audit.sh‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,11 @@ for path in paths:
123123
target_profile = metadata.get("target_profile", {})
124124
require(target_profile.get("name") == "ckb", f"{path} did not compile under ckb profile")
125125
require(target_profile.get("source_encoding") == "ckb-source-group-high-bit", f"{path} missing CKB Source encoding")
126-
require(target_profile.get("witness_abi") == "ckb-molecule-witness-args+cellscript-entry-witness-v1", f"{path} missing WitnessArgs ABI")
126+
require(
127+
target_profile.get("witness_abi")
128+
== "ckb-molecule-witness-args-input-type-v2+cellscript-entry-witness-v1+raw-v1-compat",
129+
f"{path} missing WitnessArgs ABI",
130+
)
127131
require(target_profile.get("spawn_ipc_abi") == "ckb-vm-v2-spawn-ipc-syscalls-2601-2608", f"{path} missing Spawn/IPC ABI")
128132
require(target_profile.get("output_data_abi") == "ckb-outputs-and-outputs-data-index-aligned", f"{path} missing outputs_data ABI")
129133
require(target_profile.get("type_id_abi") == "ckb-type-id-v1", f"{path} missing TYPE_ID ABI")

‎src/cli/commands.rs‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use crate::{
77
compile_path, compile_path_metadata_with_diagnostics, compile_path_with_entry_action, compile_path_with_entry_lock,
88
default_metadata_path_for_artifact, default_output_path_for_input, load_modules_for_input, resolve_input_path,
99
validate_artifact_metadata, validate_source_units_on_disk, ArtifactFormat, CompileMetadata, CompileOptions, EntryWitnessArg,
10-
ParamMetadata, ProofPlanMetadata, TargetProfile, ENTRY_WITNESS_ABI,
10+
ParamMetadata, ProofPlanMetadata, TargetProfile, ENTRY_WITNESS_ABI, ENTRY_WITNESS_PLACEMENT_ABI, ENTRY_WITNESS_PLACEMENT_FIELD,
11+
ENTRY_WITNESS_PLACEMENT_SOURCE,
1112
};
1213
use base64::Engine;
1314
use camino::Utf8Path;
@@ -1863,6 +1864,10 @@ impl CommandExecutor {
18631864
let summary = serde_json::json!({
18641865
"status": if entry_constraints.unsupported { "fail" } else { "ok" },
18651866
"abi": ENTRY_WITNESS_ABI,
1867+
"placement_abi": ENTRY_WITNESS_PLACEMENT_ABI,
1868+
"witness_args_field": ENTRY_WITNESS_PLACEMENT_FIELD,
1869+
"witness_source": ENTRY_WITNESS_PLACEMENT_SOURCE,
1870+
"raw_v1_compatible": true,
18661871
"target_profile": result.metadata.target_profile.name,
18671872
"entry_kind": selected.kind,
18681873
"entry": selected.name,
@@ -2085,9 +2090,12 @@ impl CommandExecutor {
20852090
},
20862091
"witness_args_policy": {
20872092
"entry_payload_abi": ENTRY_WITNESS_ABI,
2093+
"placement_abi": ENTRY_WITNESS_PLACEMENT_ABI,
20882094
"entry_payload_owner": "compiler",
20892095
"final_witness_args_owner": "adapter",
2090-
"default_action_payload_field": "input_type",
2096+
"default_action_payload_field": ENTRY_WITNESS_PLACEMENT_FIELD,
2097+
"runtime_source": ENTRY_WITNESS_PLACEMENT_SOURCE,
2098+
"raw_v1_compatible": true,
20912099
"lock_signature_policy": "explicit-adapter-owned-do-not-overwrite",
20922100
"placement_requires_deployment_role": true,
20932101
"ckb_reference": "ckb_types::packed::WitnessArgs",
@@ -2842,9 +2850,12 @@ impl CommandExecutor {
28422850
"must_emit_lineage": true,
28432851
"witness_policy": {
28442852
"entry_payload_abi": ENTRY_WITNESS_ABI,
2853+
"placement_abi": ENTRY_WITNESS_PLACEMENT_ABI,
28452854
"entry_payload_owner": "compiler",
28462855
"final_witness_args_owner": "adapter",
2847-
"default_action_payload_field": "input_type",
2856+
"default_action_payload_field": ENTRY_WITNESS_PLACEMENT_FIELD,
2857+
"runtime_source": ENTRY_WITNESS_PLACEMENT_SOURCE,
2858+
"raw_v1_compatible": true,
28482859
"lock_signature_policy": "explicit-adapter-owned-do-not-overwrite",
28492860
"placement_requires_deployment_role": true,
28502861
},
@@ -3099,6 +3110,10 @@ impl CommandExecutor {
30993110
machine: serde_json::json!({
31003111
"status": "ok",
31013112
"abi": ENTRY_WITNESS_ABI,
3113+
"placement_abi": ENTRY_WITNESS_PLACEMENT_ABI,
3114+
"witness_args_field": ENTRY_WITNESS_PLACEMENT_FIELD,
3115+
"witness_source": ENTRY_WITNESS_PLACEMENT_SOURCE,
3116+
"raw_v1_compatible": true,
31023117
"entry_kind": selected.kind,
31033118
"entry": selected.name,
31043119
"witness_hex": witness_hex,

0 commit comments

Comments
 (0)