In a ZK-SNARK, the distinction between public inputs and private inputs is foundational to the proof's utility and security. Public inputs are values that are revealed to the verifier and are part of the proof statement. They are often the 'known' or 'shared' parameters of a computation, such as a public account balance or a Merkle root. Private inputs, conversely, are the secret witness data known only to the prover, like a secret key or a specific leaf in a Merkle tree. This separation allows a prover to convince a verifier that they know a secret satisfying a public condition without revealing the secret itself.
How to Choose Public and Private Inputs
Introduction to Input Selection in ZK-SNARKs
A guide to defining public and private inputs for zero-knowledge proofs, covering design principles, security implications, and practical implementation patterns.
Choosing what to make public versus private is a critical design decision. A common pattern is to use public inputs to represent the current state of a system and private inputs to represent the secret knowledge required to transition to a new state. For example, in a zk-rollup, the public input might be the new state root after a batch of transactions, while the private inputs are the individual transactions and signatures that justify the transition. Poor input selection can leak information, create inefficient circuits, or undermine the trust model of the entire application.
The selection directly impacts the circuit constraint system. Every public input adds a constraint that must be satisfied for the proof to verify, but they do not increase prover workload significantly. Private inputs, however, are part of the witness computation and directly affect proving time and memory. A circuit with many private variables will be more expensive to prove. Furthermore, all inputs must be encoded as finite field elements compatible with the SNARK's elliptic curve, such as the BN254 scalar field used by Circom and snarkjs.
Consider a simple example: proving you know the preimage to a hash. The circuit function is hash(private preimage) == public hashOutput. Here, hashOutput is public—it's the claim being verified. The preimage is private—it's the secret you keep. In Circom, this is declared as:
circomtemplate Hasher() { signal input preimage; signal output hashOutput; // ... constraint logic } component main {public [hashOutput]} = Hasher();
The public [hashOutput] designation in the main component explicitly marks it as a public input.
For complex applications like Tornado Cash, public inputs include the nullifier (to prevent double-spends) and the Merkle root (the current commitment state). The private inputs are the secret note, the Merkle path, and the nullifier key. This design ensures the user can prove membership and generate a unique spend proof without revealing which specific deposit they are spending. Always audit the input selection: could a set of public inputs over time deanonymize a user? Does the private data truly need to be kept secret for the application's security?
In summary, effective input selection requires balancing transparency with privacy, optimizing for circuit efficiency, and rigorously analyzing information leakage. Public inputs define the verifiable claim, while private inputs contain the prover's secret witness. This dichotomy is what enables ZK-SNARKs to provide both robust verification and strong privacy guarantees for blockchain scaling, identity, and confidential transactions.
How to Choose Public and Private Inputs
Understanding the distinction between public and private inputs is fundamental to designing effective zero-knowledge proof circuits. This guide explains the criteria for choosing which data to keep private and which to reveal.
In zero-knowledge proof systems like zk-SNARKs and zk-STARKs, the prover's inputs are partitioned into two categories: public inputs and private inputs. Public inputs are revealed to the verifier and are part of the proof's public statement. Private inputs remain hidden, known only to the prover, yet their correctness is cryptographically guaranteed. This separation is the core mechanism that enables privacy-preserving verification. For example, in a transaction proof, the sender's balance and the transaction amount might be private, while the new public state root is public.
Choosing what to make public involves assessing what information the verifier needs to know to validate the statement's truth. Public inputs typically include commitments, final state hashes, or public keys that anchor the proof to a shared context. In a ZK-Rollup, the new Merkle root of the state is a public input, allowing anyone to verify the batch's integrity without seeing individual transactions. Conversely, data that must remain confidential for privacy, competitive advantage, or security—such as a user's secret key, a bid in an auction, or the solution to a puzzle—should be designated as private inputs.
The choice directly impacts circuit design and performance. Each public input adds to the proof's verification key and the cost of generating a verification contract on-chain. Tools like Circom and Halo2 require you to explicitly label inputs as public or private during circuit compilation. A best practice is to minimize public inputs to only what is essential for verification. For instance, a proof of age might take a hashed birth date as a private input and the statement "is over 18" as a single public boolean output, rather than revealing the actual date.
Core Concepts: Public vs. Private Inputs
Understanding the distinction between public and private inputs is fundamental to designing effective zero-knowledge circuits for applications like private transactions and identity proofs.
In zero-knowledge proof systems like zk-SNARKs and zk-STARKs, a circuit's inputs are categorized as either public or private. This classification determines what information is revealed in the final proof and what remains hidden. Public inputs are values that are known to both the prover and the verifier, and they are explicitly included in the proof's verification key. Common examples include a public recipient's address, a token amount in a shielded pool, or a root hash of a Merkle tree. Private inputs are the secret witnesses known only to the prover, such as a secret spending key, a hidden account balance, or the specific leaf in a Merkle tree that proves membership.
The choice of what to make public or private directly defines the privacy and functionality of your application. For a private payment, the public input might be the output note commitment, proving a valid transaction occurred, while the private inputs would be the sender's secret key and the note's details. In a Tornado Cash-like mixer, the public input is the nullifier (preventing double-spends) and the Merkle root, while the private inputs are the secret note and the Merkle proof path. Misclassifying an input can leak information or break the circuit's logic. A value that must be checked against a public on-chain state, like a current block hash, must be public.
When designing your circuit, start by defining the verification statement. Ask: "What must the verifier know to be convinced, without learning my secrets?" Everything necessary for that public verification becomes a public input. For example, to prove you are over 18 without revealing your birthdate, your circuit's public input would be true/false (or a commitment), and the private input would be your actual date of birth and the current date. Tools like Circom and Halo2 require you to declare these inputs explicitly in your circuit code, enforcing this logical separation from the start.
Consider performance and cost implications. Each public input adds to the proof's size and the verification gas cost on-chain, as the verifier contract must receive and process these values. In Ethereum, using a Groth16 verifier, every 32-byte public input increases verification gas. Therefore, minimize public inputs to only what is essential. You can often hash multiple related public values into a single public input commitment. Conversely, private inputs only affect proving time and do not directly impact on-chain costs, allowing for complex private logic without bloating the verification step.
To implement this, structure your circuit constraints around these inputs. A classic pattern is: assert(publicValue == hash(privateWitness1, privateWitness2)). The public value is verified, but the private witnesses remain hidden. Always audit your design: could a subset of public inputs, combined with the proof's existence, infer anything about the private inputs? A well-designed circuit should reveal nothing beyond the intended public statement. For further reading on circuit patterns, refer to the Circom documentation and ZK Whiteboard sessions.
Public vs. Private Inputs: Characteristics and Use
A comparison of input types for zero-knowledge circuits, detailing their visibility, data handling, and typical applications.
| Characteristic | Public Inputs | Private Inputs |
|---|---|---|
Visibility | Verifiable on-chain | Hidden from verifier |
Data Proven | Statement being proven | Witness data for proof |
On-Chain Storage | Stored in calldata/state | Not stored, only commitment hash |
Gas Cost Impact | Higher (data on-chain) | Lower (only hash on-chain) |
Example Use | Token recipient address, total amount | Secret key, account balance, personal data |
Circuit Constraint | Used in final verification equation | Used internally for constraint system logic |
Data Integrity | Immutable once proven | Can be tampered with before proof generation |
Typical Size | Small (1-10 field elements) | Large (100s-1000s of field elements) |
Step-by-Step Process for Choosing Inputs
A practical guide to defining public and private inputs for zero-knowledge circuits, balancing privacy, verifiability, and gas efficiency.
The first step in designing a zero-knowledge circuit is to clearly define the statement to be proven. This statement determines what information is public (known to the verifier) and what is private (known only to the prover). Public inputs are included in the proof's verification key and are essential for the verifier to check the proof's validity against a specific claim. Common examples include a hashed commitment, a Merkle root, or a recipient's public address. Private inputs, conversely, are the secret witnesses that satisfy the circuit's constraints, such as a secret preimage, a private key, or the leaf values for a Merkle proof.
To choose inputs effectively, analyze the verification requirements. Ask: "What minimal public data must the verifier have to be convinced the statement is true, without learning the secrets?" For a proof of membership in a Merkle tree, the public input is the root; the private inputs are the leaf and the sibling hash path. For a token transfer, the public inputs might be the sender's nullifier (to prevent double-spends) and the recipient's public key; the private inputs are the sender's secret key and the amount. Tools like Circom and Halo2 require you to explicitly declare these public and private signal types in your circuit template.
Consider gas optimization and on-chain verification. Every public input adds to the calldata cost on Ethereum. For efficiency, minimize public data by using commitments. Instead of making an amount public, you could publicly commit to it via a hash, with the actual amount as a private input. However, some data, like a recipient address, must be public for the contract to act on it. The trade-off is between verifier cost and the necessary transparency for the application's logic, a key consideration for protocols like zkSync or Scroll.
Finally, rigorously test input boundaries and constraints. Your circuit must constrain all private inputs to valid ranges (e.g., a uint64) and enforce logical relationships to prevent prover misuse. For example, a circuit proving a * b = c with c as public must have a and b as private, with constraints ensuring the multiplication is correct. Use testing frameworks like snarkjs or language-specific test suites to simulate proofs with varied inputs, ensuring the circuit rejects invalid private witnesses and correctly verifies with valid ones, securing the system's integrity.
Common Design Patterns and Examples
Understanding the distinction between public and private inputs is fundamental to designing effective zero-knowledge circuits. This guide addresses common developer questions and patterns for structuring your inputs.
In a zero-knowledge circuit, inputs are the data the prover submits to generate a proof. They are categorized based on what is revealed in the final proof.
- Public Inputs: Data that is revealed to the verifier. Anyone with the proof can see these values. They are often used as public parameters, commitments, or statements to be verified. For example, a public input could be a Merkle root representing the state of a system.
- Private Inputs: Data that remains hidden from the verifier. The prover uses this secret data to satisfy the circuit's constraints without exposing it. For example, a private input could be a secret key or the pre-image of a hash.
The circuit's logic defines the relationship between these inputs, proving a statement like "I know a private value x such that hash(x) = public_hash."
How to Choose Public and Private Inputs
A guide to structuring circuit inputs for optimal security, privacy, and verifier efficiency in zero-knowledge applications.
In zero-knowledge proof systems like Circom, SnarkJS, or Halo2, a circuit's inputs are categorized as public or private. This distinction is fundamental to the protocol's security model and performance. Public inputs are revealed to the verifier and become part of the final proof, while private inputs remain concealed. The choice of what data to make public directly impacts the trust assumptions, proof size, and computational cost of verification. Misclassifying inputs can leak sensitive information or create unnecessarily large proofs.
Public inputs should be minimized to only the information necessary for the verifier to accept the statement. Common examples include: a Merkle root committing to a set, a recipient's public address, or a threshold value. Everything else should be private. For instance, in a proof of membership, the Merkle root is public (proving inclusion in a known set), while the specific leaf value and the Merkle path are private. This keeps the user's identity or asset secret while allowing the verifier to check the proof against the public state.
The technical consequence of adding a public input is an increase in the proof's size and verification time. Each public input typically adds a group element to the proof and requires an additional elliptic curve operation during verification. In Groth16, for example, the proof consists of three curve points (A, B, C), and the verifier checks a pairing equation that includes all public inputs. Adding ten unnecessary public inputs can increase verification gas costs on Ethereum by hundreds of thousands of gas. Therefore, rigorous input minimization is a key optimization.
A critical security consideration is ensuring private inputs do not inadvertently become inferable from public outputs. If a circuit's logic creates a direct, invertible relationship between a private input and a public output, privacy is broken. For example, a circuit that outputs the hash of a private secret H(s) as public is safe, but one that outputs s + 5 is not. Design circuits so public outputs are commitments or zero-knowledge statements, not raw transformations of secrets. Always audit the circuit's constraints to confirm no private variable can be solved for algebraically.
Use real-world examples to guide your design. In a ZK-SNARK-based voting system, the public input would be the encrypted vote tally or a commitment, while the private inputs are the voter's identity, signature, and ballot choice. For a private transaction, the public inputs are the nullifier (to prevent double-spends) and a commitment to the new output note, while the private inputs are the spending key, the note details, and the Merkle path. Tools like the Circom compiler and ZoKrates enforce this separation in their DSLs, requiring explicit signal input declarations.
Finally, document the input schema clearly. For any ZK circuit library or application, provide a specification listing each public and private input, its type (field element, scalar), and its semantic meaning. This prevents misuse by downstream developers and auditors. Testing should include edge cases where private inputs are at field boundaries and where public inputs are maliciously crafted. Proper input design is not an afterthought; it is the foundation of a secure, efficient, and truly private zero-knowledge application.
Implementation Examples in Circom and Halo2
Choosing public and private inputs is a foundational design decision that impacts proof size, verification cost, and application logic. This guide compares practical approaches in Circom and Halo2.
In zero-knowledge proofs, public inputs are values known to both the prover and verifier, forming part of the proof's public statement. They are included in the proof and must be provided during verification. Private inputs (or witness values) are known only to the prover; they remain hidden but are used to generate the proof that a statement about them is true.
For example, in a proof of age, the user's birth date is a private input, while the legal age threshold (e.g., 21) is a public input. The proof demonstrates birth_date < (current_date - 21 years) without revealing the birth date. Structuring inputs correctly is critical for security and efficiency, as public inputs directly affect verification gas costs on-chain.
Common Mistakes to Avoid
Choosing inputs incorrectly is a major source of errors in zero-knowledge proof development. This guide addresses frequent pitfalls and their solutions.
This error occurs because the witness generation and proof verification logic depend on the input's visibility. Public inputs are part of the statement being proven and are known to the verifier. Private inputs are part of the witness, which is kept secret.
If you declare an input as public in your circuit but later treat it as private during witness assignment (or vice-versa), the proof system's constraints will be inconsistent. The prover and verifier must agree on which data is in the public instance. Always ensure the visibility modifier in your circuit code (e.g., circom, Noir) matches how you pass the value in your application code.
Example in Noir:
rust// Circuit expects public input `x` fn main(x: pub Field, y: Field) { // ... constraints using x and y }
Your prover code must provide x to the public inputs array and y to the private inputs array.
Resources and Further Reading
Selecting the right public and private inputs is a core design decision in zero-knowledge systems. These resources focus on threat modeling, circuit design constraints, and real-world zk tooling to help you make correct input visibility choices.
Threat Modeling for Input Visibility
Before writing circuits, define who learns what from your proof. Threat modeling clarifies which values must be public and which must remain private to avoid data leaks.
Key questions to answer:
- Which inputs must be publicly verifiable on-chain, for example, balances, nullifiers, or state roots
- Which inputs should remain private to preserve user privacy or business confidentiality
- What an adversary can infer from repeated proofs, timing, or correlations
Practical guidance:
- Treat anything placed in public inputs as permanently observable and indexable
- Minimize public inputs to reduce metadata leakage
- Document assumptions explicitly, including what off-chain parties may already know
This step often prevents expensive redesigns after audits uncover accidental disclosure paths.
Frequently Asked Questions
Common questions and clarifications for developers working with public and private inputs in zero-knowledge circuits.
In a zero-knowledge proof, public inputs are data that is revealed to the verifier and becomes part of the final proof. They are used to verify the proof's validity against a specific statement. Common examples include a public key, a hash commitment, or a root hash of a Merkle tree.
Private inputs (also called witness data) are kept secret. The prover uses them to generate the proof, but they are never revealed. The circuit's logic proves that the prover knows valid private inputs that satisfy the public constraints. For example, you can prove you know a private key corresponding to a public address without revealing the key itself.
The distinction is enforced by the proving system; marking an input as public ensures its value is hashed into the proof's verification key.
Conclusion and Next Steps
Choosing the correct inputs for your zero-knowledge proof is a critical step that defines the proof's purpose, security, and efficiency. This guide has outlined the fundamental principles.
To summarize, public inputs are the shared, verifiable claims of your proof, such as a valid Merkle root or a correct hash output. They are part of the proof itself and must be known to and agreed upon by the verifier. In contrast, private inputs contain the secret knowledge that proves the claim, like the specific leaf in the Merkle tree or the pre-image of the hash. Keeping this data private is the core value proposition of ZK proofs. Misclassifying an input can break the system's security or functionality.
Your next step is to implement these concepts. For a practical exercise, modify a simple ZK circuit (e.g., in Circom or Noir) that proves knowledge of a hash pre-image. First, run it with the hash as a public input and the secret as private. Then, try making the secret public and the hash private—observe how the proof's meaning and verification requirements change completely. This hands-on test solidifies the abstract distinction between public and private declarations.
For further learning, explore how real-world protocols apply these principles. Study the zk-SNARK circuit for Tornado Cash, where the nullifier hash and Merkle root are public inputs, while the note secret and path are private. Review zkRollup designs like zkSync or StarkNet, where the new state root is public, and the batch of transactions is private. Analyzing production code reveals nuanced decisions, like using public inputs for efficiency gains or to reduce proof size.
Finally, remember that input design is iterative. Start by clearly defining the statement you want to prove: "I know a secret s such that Hash(s) = public_hash." Map each variable to its role. Use tools like snarkjs or your chosen framework's CLI to test verification. As you build more complex circuits, you'll encounter advanced patterns like public inputs that are computed from private ones or the use of signals for intermediate values, which build directly upon this foundational skill.