Enhancing User Interaction in Zero-Knowledge Proofs with Rust and Bellman

Deep RnD
5 min readDec 27, 2023

--

Welcome back to our ongoing exploration of zero-knowledge proofs using the Rust programming language and the Bellman library. In our previous article, we delved into the fascinating world of zk-SNARKs and demonstrated how they can be used to verify the correctness of a Sudoku solution without revealing the solution itself. However, astute readers and cryptographic enthusiasts pointed out a critical aspect of user interaction that was amiss. Let’s remedy that and enhance our Sudoku solver with real user interaction.

Shortcomings of the Initial Implementation

In our initial foray, the example focused on verifying a secret Sudoku board against a known solution within the constraints of a zero-knowledge proof. While this showcased the power and privacy of zk-SNARKs, it lacked a critical element of any Sudoku game: user interaction. Typically, a user (or verifier in cryptographic terms) suggests values for the missing cells, and the game (or prover) confirms whether these values are correct. Our initial example did not allow for this interaction, which is crucial for a more realistic and practical application of zk-SNARKs in Sudoku games.

Enhancing User Interaction

To make our Sudoku example more interactive and realistic, we’ve updated the program to allow a verifier to suggest values for the missing Sudoku cells. The verifier submits their proposed solution, and the program verifies its correctness using the magic of zero-knowledge proofs, all while keeping the actual solution hidden. This approach mirrors the real Sudoku experience much more closely, where the thrill lies in solving the puzzle, not just verifying an existing solution.

The Dynamics of Proving Without Knowing

In the context of zero-knowledge proofs, particularly zk-SNARKs, the prover (who creates the proof) must know the solution they are proving; however, they do not reveal this solution directly to the verifier. This might seem a bit counterintuitive when we talk about enhancing user interaction, especially when users propose solutions to a Sudoku puzzle. Let’s clarify how a prover who knows the solution can validate proposals from users, how the real solution gets “encoded” in the proof, and the implications for larger Sudoku boards.

How the Prover Validates Proposals

In the typical zk-SNARK setup for something like a Sudoku game, the prover is assumed to know the actual solution to the puzzle. This knowledge is necessary to construct the proof.

When users (verifiers) suggest values for the missing cells in the Sudoku puzzle, they are essentially proposing a complete solution that they believe is correct. The proof itself is constructed so that it will only be valid if the proposed solution is correct.

Encoding the Real Solution in the Proof

The real solution is “encoded” in the proof through the construction of the circuit in the zk-SNARK. When the prover generates the proof, they are essentially proving that they know a solution (the real solution) that satisfies the constraints laid out in the circuit (the rules of Sudoku, in this case) without revealing what that solution is.

When the verifier validates a proposed solution, the proof essentially includes checks for each cell against the constraints. If the proposed solution satisfies all constraints (meaning it’s the same as the real solution for a proper Sudoku game), the proof will be valid. Otherwise, the verification process will fail.

Throughout this process, the actual solution is never revealed to anyone except the prover. The verifier learns nothing about the solution except whether their proposed solution is correct.

The Updated Solution in Rust

Let’s break down the updated solution. The core of our enhancement lies in the Sudoku struct and the synthesize method within our circuit. We now include both the actual solution and the proposed solution within our struct. During the synthesis process, we add constraints to verify that the proposed solution matches the actual solution for each cell. Crucially, this comparison is done in a way that still does not reveal the actual solution to the verifier.

Here is the revised Rust implementation:

// Updated Sudoku struct to include proposed solution
struct Sudoku {
solution: Option<Vec<u32>>, // The actual solution of the Sudoku
proposed_solution: Option<Vec<u32>>, // The proposed solution from the verifier/user
}

// Implementing the Circuit trait for Sudoku
impl Circuit<Fr> for Sudoku {
fn synthesize<CS: ConstraintSystem<Fr>>(self, cs: &mut CS) -> Result<(), SynthesisError> {
let mut grid = Vec::with_capacity(9);

// ... [Previous setup for the grid and other constraints]
// Constraints for verifying user's proposed solution
if let (Some(solution), Some(proposed_solution)) = (self.solution, self.proposed_solution) {
for i in 0..9 {
let actual_val = Fr::from(solution[i]);
let proposed_val = Fr::from(proposed_solution[i]);

// Enforce that the proposed value equals the actual value for each cell
cs.enforce(
|| format!("match_{}", i),
|lc| lc + CS::one() * actual_val,
|lc| lc + CS::one(),
|lc| lc + CS::one() * proposed_val,
);
}
}

Ok(())
}
}

Effect on Proof Size with Larger Sudoku Boards

As the Sudoku board size increases, say from 3x3 to 1000x1000 or even 1Mx1M, the number of constraints and variables in the circuit increases linearly with the number of cells. Each cell, and the relationships between cells (rows, columns, and regions must contain unique values), adds to the complexity of the circuit.

One of the advantages of zk-SNARKs is that the proof size remains relatively small and constant, regardless of the size of the computation being proved. However, the time and computational resources required to generate and verify these proofs will increase with larger and more complex circuits.

While theoretically possible, practical limitations arise with very large Sudoku boards. The time to construct the circuit, generate the proof, and especially the setup phase for the CRS might become impractical for exceedingly large sizes. The computational cost for both the prover and verifier increases, and the initial setup (trusted setup or its decentralized version) becomes more cumbersome.

Conclusion

In enhancing user interaction for zk-SNARK-based applications, it’s crucial to understand the mechanics of how solutions are proposed, verified, and encoded within the proofs. While zk-SNARKs offer powerful capabilities for maintaining privacy and verifying correctness, the scalability in terms of computation and resources is an important consideration, especially as the complexity of the problem increases. Understanding these dynamics allows for more effective and practical implementations of zero-knowledge proofs in real-world applications.

--

--