Midnight’s developer proposition is deceptively simple: write a privacy-aware application in Compact, let the platform handle the zero-knowledge machinery, and ship a contract without turning every application engineer into a proving-system researcher.

That abstraction is the point. It is also where the security boundary moves.

CoinDesk Research reported that Compact, originally a TypeScript-inspired language, was integrated into a web IDE and later merged into the Minokawa language under LF Decentralized Trust. The same report framed the move as a way to abstract away zero-knowledge complexity for mainstream developers. That is a welcome direction, but it changes the question an engineer must ask before trusting the stack. The question is no longer only whether the contract source code is correct. It is whether the source-level privacy and authorization rules survive every lowering step between the source file and the proof a Midnight validator accepts.

Midnight’s public debut followed creation of its genesis block on March 17, 2026, according to The Block, which reported a full public launch later that month. CoinDesk’s December 11, 2025 article described NIGHT’s market debut as a concrete rollout step; CoinDesk Research dates its official launch December 4. Those dates matter because production use turns a compiler pipeline from developer tooling into consensus-adjacent infrastructure. A defect in a text editor is annoying. A defect that lets a source rule disappear before constraint generation can become a way to mint, transfer, disclose or authorize something the author never intended.

The essential rule is straightforward:

A zero-knowledge proof can prove that constraints were satisfied. It cannot prove that the compiler generated the constraints the developer meant to write.

That distinction should govern how Compact and Minokawa applications are designed, built, reviewed and audited.

The source program is not the thing validators execute

A conventional smart contract is often treated as the final artifact. A reviewer reads Solidity, Rust or another contract language, then asks whether an attacker can exploit its visible logic.

A privacy-preserving contract adds several layers between human intent and network acceptance:

  1. The engineer writes source code that expresses public state, private state, access rules and disclosure rules.

  2. The compiler parses and type-checks that code.

  3. It lowers the program into one or more intermediate representations.

  4. A circuit compiler converts private computation into arithmetic or Boolean constraints.

  5. The proving system uses those constraints, along with private witness data and public inputs, to generate a proof.

  6. A verifier checks the proof against a verification key and the transaction’s public inputs.

  7. Consensus accepts the state transition if the proof and other transaction conditions validate.

At the end, validators do not read the Compact source. They check a proof about a specific mathematical statement. If that statement is weaker than the source program’s intended meaning, the verifier can do its job perfectly and still accept an unsafe transaction.

This is why compiler correctness is not merely a developer-experience issue. It defines the semantic bridge between policy and proof.

PRIVATE TRANSFER POLICYCompact source code
privacy policy source
PRIVACY COMPILERlowers policy into a proving circuit
generated constraints
GENERATED CONSTRAINTSencoded transfer and authorization rules
verification-key material
VERIFICATION KEYderived from the proving circuit
verification key
MIDNIGHT TRANSACTION VERIFIERchecks proof, key, and public inputs
TRANSACTIONpublic inputs and private witness
private witness and public inputs
PROVERuses constraints and the witness
  • GENERATED CONSTRAINTS → PROVER: proving circuit
  • PROVER → MIDNIGHT TRANSACTION VERIFIER: zero-knowledge proof
  • TRANSACTION → MIDNIGHT TRANSACTION VERIFIER: public inputs

The verifier checks the proof against the verification key and transaction public inputs, not the constraints directly

How a private transfer policy becomes a proof checked by the Midnight transaction verifier

Consider a simple private transfer rule. In source-level terms, an engineer may mean:

  • The sender owns the consumed note.
  • The note has not already been spent.
  • The transfer amount is positive.
  • The sender has enough private balance.
  • The recipient receives exactly that amount.
  • The old note is made unspendable by publishing and checking its nullifier; its commitment can remain in the append-only commitment tree.
  • A new commitment binds the recipient and amount.
  • Only a permitted compliance predicate may be revealed publicly.

That is not one rule. It is a collection of invariants spanning ownership, arithmetic, state continuity, authorization and disclosure. A successful proof needs to bind all of them.

If one disappears, the proof remains valid for the weaker relation.

For example, suppose a compiler generates a constraint requiring that an input note opens to some amount, but omits the equality tying that amount to the amount placed in the recipient’s output commitment. The user can prove possession of a valid input and produce a syntactically valid output, yet the circuit may permit inflation. If the output amount is constrained only by a public maximum rather than by the input amount, the resulting proof is not forged. It is honestly proving the wrong transaction.

Likewise, if a disclosure condition appears in source as an authorization predicate but is lowered into an optional metadata field, a developer may believe disclosure requires a regulator’s public key while the circuit merely permits a transaction to include that key. Those are radically different properties. One enforces access control. The other accepts a label.

Follow one private transfer all the way to verification

A reviewable privacy application needs a transaction model that separates what is visible from what remains private.

Imagine Alice transferring 40 private units to Bob. The system may expose a nullifier, output commitment, contract identifier and perhaps a policy result. It should not expose Alice’s secret key, the opening randomness for her note, the amount if it is shielded, or the recipient’s confidential attributes.

The prover’s wallet assembles a witness. That witness could include:

  • Alice’s secret authorization material.
  • The private note being spent.
  • The note’s amount and randomness.
  • A Merkle path showing that the note commitment belongs to the current shielded state.
  • The intended recipient information.
  • New commitment randomness.
  • Any private credential needed for a compliance policy.

The transaction exposes public inputs. These are values the verifier needs to anchor the proof in network state:

  • The contract or circuit identifier.
  • The old state root.
  • A nullifier that, together with the proof and ledger’s spent-nullifier check, prevents the note from being spent twice without revealing which commitment was consumed.
  • One or more new commitments.
  • A new state root or state-transition anchor.
  • Public policy outputs, where the application deliberately exposes them.
  • The verifier uses an authenticated verification key selected by the deployed circuit or circuit-version identifier; the key itself is ordinarily a verifier parameter, not a proof public input.

The circuit relation must connect these two worlds. It must demonstrate that the private witness produces the public inputs under the specified rules.

witness datawallet; private note; secret key; Merkle path; recipient data; compliance credential
private witness
proverconstructs the proof from the private witness
proof plus public inputs
proof verifiertransaction public inputs; verification key; nullifier check; accepted state transition

No arrow carries witness data across the trust boundary to the verifier

This architecture is powerful because it limits what reaches the network. CoinDesk described Midnight as using a dual-state model that separates public and private data while permitting controlled disclosure to auditors, institutions or counterparties. The Block similarly described its hybrid ledger as supporting public and private data within a transaction.

But privacy does not remove the need for ordinary application correctness. It intensifies it. In a public application, an auditor may inspect a failed condition directly from transaction data. In a shielded application, the critical condition may be hidden inside the relation. The circuit becomes the effective specification that the chain enforces.

A strong review therefore asks the following question for every invariant:

Where, exactly, is this property enforced?

“Compact source” is not a sufficient answer. The answer should name the generated circuit, the relevant constraints, the public inputs to which it binds, and the test case that would fail if the invariant were absent.

Three failures that look similar from the outside

A rejected transaction, missing balance or unauthorized action can be blamed loosely on “the ZK layer.” That hides important distinctions. A serious security program separates compiler defects, circuit defects and witness-construction defects because the remediation differs in each case.

1. Compiler bugs: the source meaning changes on the way down

A compiler bug exists when the source program has one meaning but the generated lower-level artifact has another.

Potential examples include:

  • A private field is treated as public because of an incorrect visibility annotation or type-lowering rule.
  • An authorization check is evaluated but its result is not connected to the circuit’s acceptance output.
  • Integer arithmetic changes width during lowering, creating truncation or overflow behavior not present in the source model.
  • A branch condition is mishandled, so both branch outputs are constrained incompletely.
  • An equality check is optimized away because the compiler falsely treats two values as identical.
  • A contract upgrade changes circuit selection while preserving an interface that callers assume is compatible.

The dangerous compiler bug is not necessarily a crash. A crash is visible. A semantic omission that yields valid proofs is quieter and potentially much worse.

The core defense is translation validation. Do not trust only that the compiler finished. Independently check that the generated constraints preserve key source-level assertions.

For critical methods, teams should maintain an explicit property map:

Source-level promise Lower-level enforcement evidence
Only note owner can spend Signature or key-ownership constraints bind witness key to note commitment
Note cannot be spent twice Nullifier is derived correctly and checked against the ledger’s spent set
Value is conserved Input amount equals output amount plus declared fee, under correct range constraints
Recipient is bound Output commitment includes recipient identifier, amount and fresh randomness
Disclosure is deliberate Public disclosure output is constrained by the intended policy and authorized key material

The point is not paperwork. It is to stop vague assurances from becoming audit conclusions.

2. Unsound circuits: the compiler does exactly what it was told, but the relation is wrong

An unsound circuit can arise even when compilation is faithful. Here, the source specification itself is incomplete, or the circuit design fails to encode a necessary condition.

A classic failure is a missing range check. In finite-field arithmetic, values wrap according to field rules. If the circuit checks that input = output + fee but never constrains amounts into an expected integer range, an attacker may exploit field arithmetic to satisfy the equation with economically absurd values.

Another failure is an unbound public input. Suppose a proof verifies against a public recipient commitment, but the circuit does not actually recompute that commitment from the witness recipient and amount. The proof may then be replayable or redirectable across contexts.

Other examples include:

  • Checking membership in a Merkle tree without binding the root to the ledger state used by the transaction.
  • Creating a nullifier without proving it derives from the note actually consumed.
  • Verifying a credential without binding its subject to the spending key.
  • Proving a threshold requirement while failing to constrain the threshold parameter.
  • Accepting a policy flag that is not tied to the private facts that supposedly justify it.
  • Forgetting that a hash domain separator differentiates notes, credentials, contracts and network environments.

The phrase “the contract compiled successfully” tells an auditor almost nothing about these questions. Compilation proves a tool ran. Soundness depends on the relation it produced.

3. Wallet-side witness mistakes: correct rules, incorrect private inputs

The third category is operational. The circuit and compiler can be correct while the wallet constructs the wrong witness.

A wallet may select an obsolete state root, create a nullifier from the wrong derivation path, mix a credential from the wrong network, use stale note-selection data, or reuse commitment randomness that should be fresh. It may prove a transaction using a recipient key that is valid syntactically but not the key displayed to the user.

These mistakes typically cause rejection or asset loss rather than a universal break. Still, they matter because wallet software is responsible for constructing the confidential facts the proof asserts.

The safest design reduces ambient authority. A wallet should not hand a generic prover a bag of secrets and ask it to “make the transaction work.” It should construct a typed witness for a named circuit version, bind every selected note and key to the displayed transaction summary, and reject unknown proving-key identifiers.

OLD SHIELDED LEDGER STATEstate root R0; Alice note commitment C1
R0 and C1
CIRCUIT CONSTRAINTStyped checks for the named circuit version
N1, C2, R1, policy output
PUBLIC TRANSACTION DATAnullifier N1; new commitment C2; state root R1; policy output
PRIVATE WITNESSopening of C1; Alice authorization key; amount 40; Merkle path to R0; Bob recipient data; fresh randomness r2
  • PRIVATE WITNESS → CIRCUIT CONSTRAINTS: ownership
  • PRIVATE WITNESS → CIRCUIT CONSTRAINTS: membership
  • PRIVATE WITNESS → CIRCUIT CONSTRAINTS: nullifier binding
  • PRIVATE WITNESS → CIRCUIT CONSTRAINTS: value conservation
  • PRIVATE WITNESS → CIRCUIT CONSTRAINTS: recipient binding
  • PRIVATE WITNESS → CIRCUIT CONSTRAINTS: authorized disclosure

Where source-level rules are most likely to disappear

Engineers should be especially suspicious at language boundaries. These are places where an abstraction makes code pleasant to write but can obscure an expensive or unusual proving obligation.

Private conditionals

A normal conditional says: if a predicate is true, execute one branch; otherwise execute another.

A circuit cannot simply “not execute” a branch in the same way. In many proving architectures, both paths must be represented through constraints and a selector. If the selector is private, the circuit must enforce that exactly the intended branch influences every output.

The review question is: Does the branch predicate constrain all state updates, public outputs and disclosure outputs?

A condition that selects the recipient but not the amount, for example, can create a mixed transaction that no source-level reading intended.

Authorization checks

An authorization condition must do more than evaluate to true somewhere in an intermediate representation. It has to bind the caller, capability or credential to the protected action.

An auditor should trace:

  1. The identity or credential used in the witness.
  2. The rule that verifies it.
  3. The state object being modified.
  4. The action identifier being authorized.
  5. The contract and network domain.
  6. The resulting public state transition.

If any binding is missing, a proof might be transplantable. A valid credential for one user, contract or operation must not become a bearer token for another.

Privacy labels and selective disclosure

A privacy label is a statement of intent. It is not a guarantee that data stays private.

A field remains private only if it never becomes a public input, transaction field, error message, event, indexable commitment preimage or deterministic output that makes it inferable. Likewise, a selective-disclosure policy is real only if the circuit constrains the disclosed statement and, where appropriate, encrypts it for an authorized recipient.

Engineers should distinguish three different promises:

  1. Hidden: the raw value is not published.

  2. Unlinkable: observers cannot reliably connect multiple uses of the value or user.

  3. Selectively disclosable: a defined party can recover or verify a specific fact under a defined authorization model.

Those properties require different mechanisms. Hiding a value does not automatically prevent linkage. Encrypting data does not automatically prove the authorized recipient relationship. A proof of eligibility does not automatically create an auditable disclosure trail.

State and transaction ordering

Private applications may support concurrent local computation. That makes state anchoring essential.

A proof that references an old root must be accepted only under clear rules for root validity. A nullifier must be checked atomically against spent state. A transaction that updates related public and private state must bind both changes into one accepted transition.

Otherwise, developers can accidentally write an application that is locally consistent but globally unsafe under races, stale witnesses or reordered submissions.

Reproducible builds are necessary, but they establish limited things

A reproducible build means independent parties can take the same source, dependencies, compiler version and build configuration and reproduce the same artifacts. In a privacy compiler pipeline, that should include at least:

  • The normalized source tree.
  • Dependency lockfiles and hashes.
  • The Compact or Minokawa compiler release.
  • The circuit backend version.
  • Compiler flags and optimization settings.
  • Generated intermediate representations.
  • Constraint-system artifacts.
  • Proving and verification key derivation inputs or deterministic identifiers.
  • Artifact hashes embedded in deployment metadata.

This is valuable because it answers a narrow but vital question: Did the audited source produce the artifact actually deployed?

It does not answer whether the audited source is correct. It does not establish that the compiler is sound. It does not prove the proving system’s implementation is free from defects. It does not make a malicious dependency harmless.

Treat reproducibility as provenance, not as a correctness certificate.

Pinned toolchains complement reproducibility. “Latest compiler” is an operational anti-pattern for deployed privacy contracts because a compiler update can change circuit layout, optimization behavior or key generation. A contract repository should specify exact compiler and backend versions, their cryptographic hashes, build container image identifiers and the expected hashes of generated artifacts.

The practical target is not only that a team can rebuild an old contract. It is that an external auditor can rebuild the exact deployed circuit, derive or validate the expected verifier artifact, and compare the result byte for byte or through canonical hashes.

SOURCE, LOCKFILE, TOOLCHAIN MANIFEST, TESTSARTIFACT HASHES AND REPRODUCIBLE BUILD ATTESTATIONVALIDATED ARTIFACT HASHES FOR DEPLOYMENT METADATASOURCE, LOCKFILEARTIFACT HASHESVALIDATED ARTIFACT HASHESDEVELOPERcommitssource,lockfileCONTINUOUSINTEGRATIONproduces IRhash,constraintINDEPENDENTAUDITORindependentlyregeneratesartifactsDEPLOYMENTMETADATAembeds theexpectedhashes
Figure 4 - How an external auditor verifies the exact circuit artifacts before deployment

What differential testing can actually catch

Differential tests compare independent implementations or representations of the same intended behavior.

For Compact applications, useful comparisons include:

  • A straightforward reference model that executes the business rule over ordinary integers and records expected outputs.
  • The compiled circuit evaluated through a witness checker.
  • A prover and verifier run using the generated artifacts.
  • A second independently implemented constraint generator for a small critical subset.
  • Property-based tests generating both valid and adversarial transaction cases.

For a transfer circuit, test more than “Alice can send 40 to Bob.” Include failed cases:

  • A sender tries to spend a note without the right key.
  • The note exists under a different Merkle root.
  • The nullifier does not match the consumed note.
  • Outputs exceed inputs.
  • A negative or out-of-range amount is encoded.
  • A recipient commitment is swapped after proof generation.
  • A compliance credential belongs to another subject.
  • A disclosure output is requested without the authorizing predicate.
  • A transaction is replayed after its nullifier is spent.
  • A proof generated for one circuit version is submitted to another.

The most useful failures are metamorphic. Change one semantically important input and verify that proof generation or verification fails. If changing the recipient does not invalidate the proof, recipient binding is broken. If changing an amount by a field modulus does not fail, range handling deserves scrutiny. If changing the contract identifier does not fail, domain separation may be missing.

Independently regenerated constraints go a step further. An auditor should not merely inspect a vendor-produced constraint count or a generated proving key. They should recreate the constraints from the declared source and pinned toolchain, calculate a canonical digest, and compare it against what deployment and wallets expect.

Where the source compiler itself is in scope, the highest assurance comes from an independent path, even if limited in coverage. A second compiler, a formally specified subset, or a hand-built reference circuit for the most valuable transaction types can expose correlated assumptions that ordinary tests miss.

The Compact build pipeline an auditor should be able to reproduce

A reviewable release process should be designed around artifacts, not confidence.

Before deployment, the project should publish or retain the following package:

  1. Source snapshot A signed, immutable source revision, including generated source where relevant.

  2. Dependency record Exact dependency versions, package hashes and any vendored code.

  3. Toolchain manifest Compiler, circuit backend, proving backend, operating environment and build flags.

  4. Semantic specification Plain-language invariants, state-transition rules, disclosure rules, threat model and trusted assumptions.

  5. Intermediate artifacts Typed intermediate representation and circuit representation in deterministic, reviewable formats where possible.

  6. Constraint commitment A canonical hash of the final constraint system, plus a documented procedure for regeneration.

  7. Key binding record A mapping from circuit identifier and constraint hash to the verifier artifact or verification key used in production.

  8. Test evidence Unit tests, property tests, adversarial test vectors and differential-test results.

  9. Deployment manifest The exact hashes referenced by the deployed contract and accepted by the wallet or transaction builder.

  10. Upgrade policy

    Clear rules for circuit changes, verifier-key rotation, migration of private state and rollback handling.

An auditor should reproduce items 1 through 7. Inspection alone is insufficient. Reading a source file does not show what was deployed. Reading a verification key does not show which source relation produced it. Reading a dashboard does not show whether a wallet is proving against an unexpected circuit revision.

The key audit question is therefore not, “Did we review the contract?”

It is, “Can we regenerate the relation that the network verifies, and can we demonstrate that it enforces each security promise the product makes?”

That standard is demanding. It should be. Midnight is positioning Compact as a route to programmable privacy for ordinary engineers, and reports have described Compact as deliberately lowering the entry barrier for developers without deep zero-knowledge expertise.

The abstraction can succeed without becoming a black box. But the industry needs to treat the compiler, the constraint generator, artifact provenance and wallet witness construction as parts of the application’s attack surface.

In a privacy system, the source code is where people state the policy. The circuit is where the policy becomes enforceable. The proof only confirms that the circuit’s policy was followed.

That gap is where trust must be earned.

#Midnight#Compact#Minokawa#zero-knowledge#ZK proofs#privacy#compiler security#circuit security#smart contracts#reproducible builds#cryptographic auditing
Jared Zimmerman writes the long technical pieces at NightRiders: zero-knowledge proof systems, the Compact toolchain, partner-chain consensus, and what selective disclosure means in practice rather than in a whitepaper. He reads the specifications and the code, and prefers a diagram to an adjective.

This article was written with the assistance of an AI system and published automatically.