| name | midnight-verify:verify-by-witness |
| description | Cross-domain witness verification pipeline. Compiles the Compact contract, type-checks the TypeScript witness against the compiled contract's generated Witnesses type, runs structural checklist analysis (name matching, return tuple shape, WitnessContext usage, private state immutability, side effects), executes the circuit with the witness via JS runtime, and recommends devnet E2E to the orchestrator if needed. Loaded by @"midnight-verify:witness-verifier (agent)". |
| version | 0.5.0 |
Verify by Witness
You are verifying that a TypeScript witness implementation correctly matches and works with a Compact contract. Follow these phases in order.
Critical Rule
Witness verification is cross-domain. You need both the .compact contract file and the .ts witness implementation file. If you only have one, ask for the other. Do not attempt verification with only one file.
Phase 1: Setup and Locate Files
The workspace lives at ~/.midnight-expert/verify/witness-workspace/ in your home directory. It is home-based and independent of the project you are working in.
First time (workspace does not exist):
mkdir -p "$HOME/.midnight-expert/verify/witness-workspace"
cd "$HOME/.midnight-expert/verify/witness-workspace"
npm init -y
npm install @midnight-ntwrk/compact-runtime typescript
Subsequent times:
cd "$HOME/.midnight-expert/verify/witness-workspace"
npm ls typescript
If errors, npm install to repair.
Create the job directory:
JOB_ID=$(uuidgen | tr '[:upper:]' '[:lower:]')
mkdir -p "$HOME/.midnight-expert/verify/witness-workspace/jobs/$JOB_ID"
Locate the files:
- Two files provided: Verify both exist. The
.compact file stays where it is (it may have imports and dependencies on other .compact files in its directory). Copy the .ts witness file to the job directory.
- Claim provided: Identify the relevant files from the claim text. If the claim names specific files or paths, locate them. If not, use
AskUserQuestion to ask which files to verify.
Phase 2: Compile and Type-Check
Compile the Compact contract
Compile the contract where it lives, directing build output to the job directory:
compact compile -- --skip-zk <source-path> "$HOME/.midnight-expert/verify/witness-workspace/jobs/$JOB_ID/build/"
If the orchestrator indicated this claim also needs PLONK verification (Witness + ZKIR), compile without --skip-zk instead:
compact compile -- <source-path> "$HOME/.midnight-expert/verify/witness-workspace/jobs/$JOB_ID/build/"
This produces build/contract/index.js and build/contract/index.d.ts which export the generated Witnesses type.
Type-check the witness
Create a type-check harness in the job directory that imports the witness file and validates it against the generated types:
import type { Witnesses } from './build/contract/index.js';
import witnesses from '<absolute-path-to-witness-file>';
const _typeCheck: Witnesses<any> = witnesses;
Create a tsconfig.json for the job:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"strict": true,
"noEmit": true,
"skipLibCheck": false,
"esModuleInterop": true
},
"include": ["witness-check.ts"]
}
Run the type check:
cd "$HOME/.midnight-expert/verify/witness-workspace/jobs/$JOB_ID"
npx tsc --noEmit --project tsconfig.json 2>&1
If tsc exits 0: Types match — the witness satisfies the generated Witnesses type.
If tsc exits non-zero: Type mismatches found — the compiler errors are evidence of what doesn't match.
Note: The exact shape of the type-check harness depends on how the compiled contract exports the Witnesses type. Read build/contract/index.d.ts to understand the export shape and adapt the harness accordingly.
Phase 3: Structural Checklist
Read both the compiled build/contract/index.d.ts (for witness declarations) and the .ts witness file (for implementations). Perform these automated checks:
Check 1: Name Matching
Parse the witness function names from the compiled type declarations. Check that every declared witness name exists in the TypeScript implementation with exact casing.
PASS: All declared witness names have matching implementations.
FAIL: List missing or misspelled names.
Check 2: Return Tuple Shape
Check that each witness function returns [PrivateState, ReturnValue] — a two-element tuple where the first element is the private state type. Look for return type annotations or actual return statements.
PASS: All witnesses return a tuple with private state as the first element.
FAIL: List witnesses that return just the value without the private state wrapper.
Check 3: WitnessContext First Parameter
Check that each witness function's first parameter is the WitnessContext type (containing ledger, privateState, contractAddress).
PASS: All witnesses accept WitnessContext as their first parameter.
FAIL: List witnesses with wrong or missing first parameter.
Check 4: Private State Immutability
Check that witness functions create new state objects rather than mutating the existing privateState in place. Look for:
- Correct patterns: Object spread (
{ ...context.privateState, key: newValue }), Object.assign({}, ...), creating new objects
- Incorrect patterns: Direct property assignment (
context.privateState.key = value), array .push(), .splice(), etc.
PASS: No direct mutation patterns found.
FAIL: List locations where private state appears to be mutated directly.
Check 5: No Side Effects
Check for non-deterministic or side-effecting code that should not appear in witnesses:
console.log, console.warn, console.error
fetch, XMLHttpRequest
fs.readFile, fs.writeFile, any fs usage
Math.random, Date.now, crypto.getRandomValues
setTimeout, setInterval
PASS: No side effects found.
FAIL: List locations of non-deterministic or side-effecting code.
Note: These checks are heuristic — they read source text and look for patterns. They are not as authoritative as compilation or execution. Report findings alongside the mechanical results.
Phase 4: Execute with Witness
Import the compiled contract and execute the circuit(s) with the witness:
import { Contract } from '<job-dir>/build/contract/index.js';
import witnesses from '<absolute-path-to-witness-file>';
const contract = new Contract(witnesses);
const initialZswapLocalState = { coinPublicKey: new Uint8Array(32) };
const state = contract.initialState({
initialZswapLocalState,
initialPrivateState: { }
});
const context = compactRuntime.createCircuitContext(
compactRuntime.dummyContractAddress(),
initialZswapLocalState.coinPublicKey,
state.currentContractState.data,
state.currentPrivateState
);
const result = contract.circuits.<circuitName>(context);
Check build/compiler/contract-info.json — circuits with a non-empty witnesses array use witnesses.
Success: The circuit executed without error and produced valid proof data. The contract + witness combination works.
Failure: Capture the error. Common witness execution errors:
"expected tuple, got <type>" — witness returns wrong shape
"missing witness: <name>" — witness function not provided
"Contract constructor: expected 1 argument" — witnesses object not passed
- Type errors at runtime — witness returns wrong types
Phase 5: Devnet E2E Recommendation
You cannot dispatch other agents. Phase 5 is a recommendation to the orchestrator (the /midnight-verify:verify command), not something you execute.
In your report, include a recommendation:
- If the claim would benefit from a full deploy+call lifecycle test, state: "Recommend devnet E2E: The orchestrator should dispatch @"midnight-verify:sdk-tester (agent)" with the compiled contract and witness for full lifecycle verification."
- If local verification (phases 1-4) is sufficient for the claim, state: "Devnet E2E not required for this claim."
The orchestrator will decide whether to dispatch @"midnight-verify:sdk-tester (agent)" based on your recommendation and devnet availability.
Report
### Witness Verification Report
**Contract:** [path to .compact]
**Witness:** [path to .ts]
**Type Check:** PASS / FAIL
[tsc output if failed]
**Structural Checklist:**
- Name matching: PASS / FAIL — [details]
- Return tuple shape: PASS / FAIL — [details]
- WitnessContext pattern: PASS / FAIL — [details]
- Private state immutability: PASS / FAIL — [details]
- No side effects: PASS / FAIL — [details]
**Execution:** PASS / FAIL
[execution output or error]
**Devnet E2E Recommendation:** Recommended / Not required
**Interpretation:** [Confirmed / Refuted / Inconclusive] — [summary]
If the orchestrator indicated PLONK verification is needed, include the build output path in the report so the orchestrator can pass it to @"midnight-verify:zkir-checker (agent)":
**Build output:** ~/.midnight-expert/verify/witness-workspace/jobs/$JOB_ID/build/
Clean Up
rm -rf "$HOME/.midnight-expert/verify/witness-workspace/jobs/$JOB_ID"
Do NOT remove the base workspace — it's shared across jobs. If the orchestrator needs the build output for @"midnight-verify:zkir-checker (agent)", do NOT clean up until the orchestrator confirms the zkir-checker is done.