Expose OpZkPrecompile builtin to SilverScript front-end#125
Expose OpZkPrecompile builtin to SilverScript front-end#125trillskillz wants to merge 2 commits into
Conversation
|
Follow-up from downstream prototyping in OpenSilver: I validated that this PR's 0-arg builtin exposure is necessary but not yet sufficient for authoring a real Phase-5 contract in current SilverScript syntax. Concrete finding:
The parser errors at the first So unless there's another stack-shaping form I'm missing, downstream contracts still need one of:
I'm leaving this PR up because it still exposes the opcode name cleanly, but I wanted to flag that the current front-end syntax appears insufficient for real contract authoring beyond the minimal compile test. |
|
Concrete follow-up from local prototyping: I confirmed that I tested a local prototype by changing the builtin arity to 9 (for 5 Groth16 public inputs + So the tradeoff is now clearer:
That suggests the real design choice is probably one of:
I wanted to post the positive result too, because it shows the compiler lowering path itself is not the problem. |
|
One more concrete narrowing result from reading the compiler: Array arguments also won't solve this automatically. Relevant behavior in
So a surface like: would still need custom lowering if
In other words, the current front-end gives us:
That makes the likely viable upstream answers even narrower:
|
|
I pushed a concrete follow-up prototype onto the PR branch:
What it does:
Why this seems interesting:
Validation on the prototype branch:
I’m not claiming this is definitely the right final API, but it is now a concrete working option if the maintainers prefer a structured helper over broader explicit-push syntax. |
First real Phase-5 contract: Verified Computation compiles via the
local OpenSilver patch lane (npm run patch:silverc:zk) and runs an
actual Groth16 proof through kaspa-txscript's TxScriptEngine.
contracts/zk/verified-computation.sil
- Stateless covenant that releases funds to a recipient on receipt
of a valid Groth16 proof + prover signature.
- Verifying key, recipient pubkey, prover pubkey, public-input
count are all deploy-time contract state — never witness-supplied.
- Two-tier auth: proof attests *what was computed*; prover signature
attests *who is submitting it*. Conservative default; drop
requireProver(...) for open-redeem semantics.
- Fixed N=5 public inputs to match the vendored Groth16 fixture; for
other circuits, recompile a sibling contract with the correct N.
Compiler patch correctness fix:
The earlier patches/silverscript-opzkprecompile.patch pushed public
inputs onto the stack in source order (pi0 first/bottom, pi4 last/
top). But Groth16Precompile::verify_zk pops n_inputs times from the
top and treats them as unprepared_public_inputs[0..n], so the pop
order needs to give the engine [pi0..pi4] not [pi4..pi0]. The fix
is to push in REVERSE source order — mirrors the engine-side
reference test in rusty-kaspa/crypto/txscript/src/zk_precompiles/
groth16/mod.rs::try_verify_stack which pushes input{n-1} first and
input0 last.
This bug was undetectable from the AST-only smoke test (which only
proves the shape parses) — it took a real Groth16 proof to surface.
Caught before the upstream PR merges, so the fix can be folded
into the open PR (kaspanet/silverscript#125).
runtime-tests/tests/zk_runtime.rs — 3 tests, all green:
- verified_computation_accepts_valid_groth16_proof
Compiles the contract with the real fixture VK, builds a tx that
supplies the fixture proof + 5 public inputs + a prover sig, and
asserts kaspa-txscript's engine accepts it. The headline proof
that Phase 5 actually works.
- verified_computation_rejects_tampered_proof
Same shape but with one byte of the proof flipped — engine
rejects with ZkIntegrity (or VerifyError/EvalFalse).
- verified_computation_rejects_wrong_prover_signature
Valid proof but the prover slot is filled with attacker
credentials — the require(prover_pk == prover) gate fires.
Also adds:
- tests/zk/verified-computation-compile.test.ts — vitest compile
scaffold asserting AST presence of OpGroth16Verify +
requireProver + requireExactPayout.
- runtime-tests/Cargo.toml — serde + serde_json deps for the
fixture parser.
Test counts: 28/28 vitest files (72 tests), 61/61 cargo runtime
(51 core + 7 kcc20 + 3 zk), 0 ignored. The patch lane
(npm run patch:silverc:zk) must run before cargo test on the zk
suite — documented in the test file's header.
Reflects the headline 5.1 milestone landed in the previous commit: contracts/zk/verified-computation.sil now compiles via the local patch lane and runtime-verifies a real Groth16 proof end-to-end. Updates: - STATUS.md: PHASE_5_STATUS flipped from 'authoring-surface blocked' to '5.1 LIVE LOCALLY'. Test counts bumped to 28 vitest files (72 tests) and 61 cargo runtime tests (51 core + 7 kcc20 + 3 zk). - README.md: Phase 5 entry rewritten — 5.1 is scaffolded + runtime-verified; 5.2/5.3/5.4 still design-only. Notes the stack-order correctness fix that needs folding into upstream PR kaspanet/silverscript#125. - docs/patterns/zk/verified-computation.md: Status flipped from DESIGN to SCAFFOLDED + RUNTIME-VERIFIED. New section enumerates the three runtime tests by name and documents the patch correctness fix. - sdk/src/index.ts: manifest entry for zk-aware.verified-computation flipped from 'planned' to 'scaffolded' with a contractPath now pointing at the real source. CLI 'opensilver list' / 'opensilver get zk-aware.verified-computation' now show the correct status. - NEXT_SESSION.md: implementation order updated — 5.1 marked DONE, 5.3 ZK-Verified Oracle promoted to NEXT (reuses Groth16 fixture, adds M-of-N committee composition).
| return Err(CompilerError::Unsupported("OpGroth16Verify() expects 3 arguments: verifying_key, proof, public_inputs".to_string())); | ||
| } | ||
| let ExprKind::Array(public_inputs) = &args[2].kind else { | ||
| return Err(CompilerError::Unsupported( |
There was a problem hiding this comment.
There is no opcode called opgroth16verify. Please ensure errors are representative of the actual opcode names
| "OpNum2Bin" => compile_opcode_builtin_call(&mut ctx, name, args, 2, OpNum2Bin), | ||
| "OpBin2Num" => compile_opcode_builtin_call(&mut ctx, name, args, 1, OpBin2Num), | ||
| "OpChainblockSeqCommit" => compile_opcode_builtin_call(&mut ctx, name, args, 1, OpChainblockSeqCommit), | ||
| "OpGroth16Verify" => compile_groth16_verify_call(&mut ctx, args), |
There was a problem hiding this comment.
There should not be such an opcode. It should be invoked via opzkprecompile and maybe we should pass args as a vector in order to ensure all of the tags are supported.
Summary
Expose
OpZkPrecompileas a callable SilverScript builtin.This wires the existing engine-side KIP-16 opcode (
0xa6) through the front-end with the minimal surface needed by downstream callers:silverscript-lang/src/compiler/compile.rssilverscript-lang/src/compiler/debug_value_types.rssilverscript-lang/std/builtins.silzk_minimal.sil) proving the builtin parses/builds through the compilerWhy
OpZkPrecompileis already implemented engine-side inrusty-kaspavia KIP-16, but the pinnedsilverscript-langfront-end snapshot still has no builtin row for it. That makes it impossible to express ZK-aware patterns in plain SilverScript without brittle raw-bytecode post-processing.This PR intentionally keeps the builtin at 0 args. The opcode consumes its operands from the stack, and the operand schema varies by precompile tag, so pushing a tag-specific higher-arity wrapper into the compiler would leak verifier details into the front-end. Higher-level helpers can live in downstream SDKs.
Test plan
cargo test --manifest-path silverscript-lang/Cargo.toml --test examples_tests -- --nocaptureContext
OpenSilver tracking RFC / downstream context:
Reviewers who may care about the stack-shape/doc wording: