feat: OP_VERIFY_ZKP opcode (Groth16 + PLONK)#78
Open
Kukks wants to merge 2 commits into
Open
Conversation
Adds OP_VERIFY_ZKP at 0xf6 (replacing the OP_UNKNOWN246 reserved slot) with Groth16 and PLONK verifier backends over BLS12-381. Tracks issue #77. Stack expectation (top to bottom): zkp_type:u8 proof publicInputs vk The verifying key is committed in the script body, not the witness — a tapleaf using OP_VERIFY_ZKP includes PUSH <vk_bytes> as a constant, so the tapleaf hash binds the spend to a specific circuit. Type registry: 0x01 = Groth16 (BLS12-381) — per-circuit trusted setup, ~200 B proof 0x02 = PLONK (BLS12-381 + KZG) — universal updatable setup, ~500 B proof Wire format for proof / vk / public-inputs follows gnark's standard binary marshaling (WriteTo / ReadFrom). Implementation: pkg/arkade/zkp/ — verifier interface + dispatch registry pkg/arkade/zkp/groth16.go — Groth16 backend (consensys/gnark) pkg/arkade/zkp/plonk.go — PLONK backend (consensys/gnark) pkg/arkade/zkp/zkp_test.go — round-trip tests with real proofs pkg/arkade/op_verify_zkp.go — opcode handler pkg/arkade/op_verify_zkp_test.go — handler tests with real proofs Tests generate proofs in-test via gnark's prover (no precomputed fixtures), exercising the full prove → marshal → verify-via-opcode round trip on a trivial x*x == y circuit. Tampered proofs and wrong-input proofs are verified to reject.
Address two HIGH-severity CVEs flagged by Trivy: - CVE-2025-57801 in gnark v0.13.0: signature malleability in EdDSA and ECDSA implementations. Fixed in v0.14.0. - GHSA-fj2x-735w-74vq in gnark-crypto v0.18.0: unchecked memory allocation during vector deserialization. Fixed in v0.19.2. Neither vulnerability is reachable via OP_VERIFY_ZKP's verification path (we use Groth16 / PLONK over BLS12-381, not EdDSA / ECDSA, and gnark's own bounded WriteTo / ReadFrom serializers, not raw vector deserializers). But they're shipped in the binary, and the security scan blocks merge, so the upgrade is the right move regardless of reachability.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements
OP_VERIFY_ZKP(0xf6, replacing theOP_UNKNOWN246reserved slot), a polymorphic SNARK verification opcode dispatching toconsensys/gnark-backed Groth16 and PLONK verifiers over BLS12-381. Tracks #77.Stack contract
The verifying key is committed in the script body (not the witness), so the tapleaf hash binds the spend to a specific circuit. On success the opcode pushes 1; on any failure (unknown
zkp_type, malformed inputs, soundness rejection) it returns a script error.Type registry
zkp_type0x010x020x03+Wire format for
proof,vk, andpublicInputsfollows gnark's standard binary marshaling (WriteTo/ReadFrom). Public inputs are gnarkwitness.Witnessbytes for the public-only witness vector.Layout
pkg/arkade/zkp/zkp.goVerifierinterface,Typeenum, dispatch registrypkg/arkade/zkp/groth16.gopkg/arkade/zkp/plonk.gopkg/arkade/zkp/zkp_test.gopkg/arkade/op_verify_zkp.gopkg/arkade/op_verify_zkp_test.gopkg/arkade/opcode.goOP_VERIFY_ZKP = 0xf6+opcodeArrayentrypkg/arkade/opcode_test.goinvalidSpec(OP_UNKNOWN246)with averifyZkpSpec()that exercises malformed-input rejection pathsWhy polymorphic instead of one opcode per system
Standard Bitcoin practice would allocate distinct opcodes per system. For Arkade specifically, the introspector binary is upgraded in coordination with
arkd, so extending the dispatch table is operationally feasible. One opcode covers a growing family of proof systems without further allocation pressure, and a script can accept multiplezkp_typevalues via runtime branching.The dispatch overhead at verification is a single map lookup — negligible against the underlying pairing math.
Tests
All tests generate proofs in-test via gnark's prover (no precomputed fixtures), exercising the full prove → marshal → verify-via-opcode round trip on a trivial
x*x == ycircuit. Includes both honest-proof acceptance and tamper / wrong-input rejection.PLONK tests use
gnark/test/unsafekzg.NewSRSfor the in-test KZG SRS — explicitly not suitable for production deployments, which must use a public ceremony's SRS (Aztec Ignition, Polygon Ignition, etc.).Test plan
pkg/arkadetests pass with the renamed opcode constantOP_VERIFY_ZKPhonest Groth16 proof verifies and pushes 1OP_VERIFY_ZKPhonest PLONK proof verifies and pushes 1zkp_typerejects withErrUnknownTypewrapzkp_typebyte (length != 1) rejects withErrInvalidStackOperationErrInvalidStackOperationgo vet ./...passesgo build ./...passesOpen follow-ups (out of scope for this PR)
(zkp_type, proof, public_inputs, vk)tripleszkp_typeallocations (Halo 2, Plonky2, STARK, Binius) — exceed practical witness budgets today, kept as footnote in the issue's type registry