Proving That A Number Is Prime
Let’s dive into Cairo by working through a hands-on project together! This section introduces you to key Cairo concepts and the process of generating zero-knowledge proofs locally, a powerful feature enabled by Cairo in combination with the Stwo prover. You’ll learn about functions, control flow, executable targets, Scarb workflows, and how to prove a statement — all while practicing the fundamentals of Cairo programming. In later chapters, we’ll explore these ideas in more depth.
For this project, we’ll implement a classic mathematical problem suited for zero-knowledge proofs: proving that a number is prime. This is the ideal project to introduce you to the concept of zero-knowledge proofs in Cairo, because while finding prime numbers is a complex task, proving that a number is prime is straightforward.
Here’s how it works: the program will take an input number from the user and check whether it’s prime using a trial division algorithm. Then, we’ll use Scarb to execute the program and generate a proof that the primality check was performed correctly, so that anyone can verify your proof to trust that you found a prime number. The user will input a number, and we’ll output whether it’s prime, followed by generating and verifying a proof.
Setting Up a New Project
To get started, ensure you have Scarb 2.10.0 or later installed (see Installation for details). We’ll use Scarb to create and manage our Cairo project.
Open a terminal in your projects directory and create a new Scarb project:
scarb new prime_prover
cd prime_prover
The scarb new command creates a new directory called prime_prover
with a basic project structure. Let’s examine the generated Scarb.toml file:
Filename: Scarb.toml
[package]
name = "prime_prover"
version = "0.1.0"
edition = "2024_07"
[dependencies]
[dev-dependencies]
cairo_test = "2.10.0"
This is a minimal manifest file for a Cairo project. However, since we want to create an executable program that we can prove, we need to modify it. Update Scarb.toml to define an executable target and include the cairo_execute
plugin:
Filename: Scarb.toml
[package]
name = "prime_prover"
version = "0.1.0"
edition = "2024_07"
[[target.executable]]
[cairo]
enable-gas = false
[dependencies]
cairo_execute = "2.10.0"
Here’s what we’ve added:
[[target.executable]]
specifies that this package compiles to a Cairo executable (not a library or Starknet contract).[cairo] enable-gas = false
disables gas tracking, which is required for executable targets since gas is specific to Starknet contracts.[dependencies] cairo_execute = "2.10.0"
adds the plugin needed to execute and prove our program.
Now, check the generated src/lib.cairo
, which is a simple placeholder. Since we’re building an executable, we’ll replace this with a function annotated with #[executable]
to define our entry point.
Writing the Prime-Checking Logic
Let’s write a program to check if a number is prime. A number is prime if it’s greater than 1 and divisible only by 1 and itself. We’ll implement a simple trial division algorithm and mark it as executable. Replace the contents of src/lib.cairo
with the following:
Filename: src/lib.cairo
/// Checks if a number is prime
///
/// # Arguments
///
/// * `n` - The number to check
///
/// # Returns
///
/// * `true` if the number is prime
/// * `false` if the number is not prime
fn is_prime(n: u32) -> bool {
if n <= 1 {
return false;
}
if n == 2 {
return true;
}
if n % 2 == 0 {
return false;
}
let mut i = 3;
let mut is_prime = true;
loop {
if i * i > n {
break;
}
if n % i == 0 {
is_prime = false;
break;
}
i += 2;
};
is_prime
}
// Executable entry point
#[executable]
fn main(input: u32) -> bool {
is_prime(input)
}
Let’s break this down:
The is_prime
function:
- Takes a
u32
input (an unsigned 32-bit integer) and returns abool
. - Checks edge cases: numbers ≤ 1 are not prime, 2 is prime, even numbers > 2 are not prime.
- Uses a loop to test odd divisors up to the square root of
n
. If no divisors are found, the number is prime.
The main
function:
- Marked with
#[executable]
, indicating it’s the entry point for our program. - Takes a u32 input from the user and returns a bool indicating whether it’s prime.
- Calls is_prime to perform the check.
This is a straightforward implementation, but it’s perfect for demonstrating proving in Cairo.
Executing the Program
Now let’s run the program with Scarb to test it. Use the scarb execute command and provide an input number as an argument:
scarb execute -p prime_prover --print-program-output --arguments 17
-p prime_prover
specifies the package name (matches Scarb.toml).--print-program-output
displays the result.--arguments 17
passes the number 17 as input.
You should see output like this:
$ scarb execute -p prime_prover --print-program-output --arguments 17
Compiling prime_prover v0.1.0 (listings/ch01-getting-started/prime_prover/Scarb.toml)
Finished `dev` profile target(s) in 2 seconds
Executing prime_prover
Program output:
0
1
Saving output to: target/execute/prime_prover/execution2
The output represents whether the program executed successfully and the result of the program. Here, 0
indicates success (no panic), and 1
represents true (17 is prime). Try a few more numbers:
$ scarb execute -p prime_prover --print-program-output --arguments 4
[0, 0] # 4 is not prime
$ scarb execute -p prime_prover --print-program-output --arguments 23
[0, 1] # 23 is prime
The execution creates a folder under ./target/execute/prime_prover/execution1/
containing files like air_public_input.json
, air_private_input.json
, trace.bin
, and memory.bin
. These are the artifacts needed for proving.
Generating a Zero-Knowledge Proof
Now for the exciting part: proving that the primality check was computed correctly without revealing the input! Cairo 2.10 integrates the Stwo prover via Scarb, allowing us to generate a proof directly. Run:
$ scarb prove --execution-id 1
Proving prime_prover
warn: soundness of proof is not yet guaranteed by Stwo, use at your own risk
Saving proof to: target/execute/prime_prover/execution1/proof/proof.json
--execution_id 1
points to the first execution (from the execution1
folder).
This command generates a proof.json
file in ./target/execute/prime_prover/execution1/proof/
. The proof demonstrates that the program executed correctly for some input, producing a true or false output.
Verifying the Proof
To ensure the proof is valid, verify it with:
$ scarb verify --execution-id 1
Verifying prime_prover
Verified proof successfully
If successful, you’ll see a confirmation message. This verifies that the computation (primality check) was performed correctly, aligning with the public inputs, without needing to re-run the program.
Improving the Program: Handling Input Errors
Currently, our program assumes the input is a valid u32
. What if we want to handle larger numbers or invalid inputs? Cairo’s u32
has a maximum value of 2^32 - 1 (4,294,967,295)
, and inputs must be provided as integers. Let’s modify the program to use u128
and add a basic check. Update src/lib.cairo
:
Filename: src/lib.cairo
/// Checks if a number is prime
///
/// # Arguments
///
/// * `n` - The number to check
///
/// # Returns
///
/// * `true` if the number is prime
/// * `false` if the number is not prime
fn is_prime(n: u128) -> bool {
if n <= 1 {
return false;
}
if n == 2 {
return true;
}
if n % 2 == 0 {
return false;
}
let mut i = 3;
let mut is_prime = true;
loop {
if i * i > n {
break;
}
if n % i == 0 {
is_prime = false;
break;
}
i += 2;
};
is_prime
}
#[executable]
fn main(input: u128) -> bool {
if input > 1000000 { // Arbitrary limit for demo purposes
panic!("Input too large, must be <= 1,000,000");
}
is_prime(input)
}
Changed u32
to u128
for a larger range (up to 2^128 - 1
). Added a check to panic if the input exceeds 1,000,000 (for simplicity; adjust as needed). Test it:
$ scarb execute -p prime_prover --print-program-output --arguments 1000001
Compiling prime_prover v0.1.0 (listings/ch01-getting-started/prime_prover2/Scarb.toml)
Finished `dev` profile target(s) in 2 seconds
Executing prime_prover
Program output:
1
Saving output to: target/execute/prime_prover/execution2
error: Panicked with "Input too large, must be <= 1,000,000".
If we pass a number greater than 1,000,000, the program will panic - and thus, no proof can be generated. As such, it's not possible to verify a proof for a panicked execution.
Ringkasan
Congratulations! You’ve built a Cairo program to check primality, executed it with Scarb, and generated and verified a zero-knowledge proof using the Stwo prover. This project introduced you to:
- Defining executable targets in Scarb.toml.
- Writing functions and control flow in Cairo.
- Using
scarb execute
to run programs and generate execution traces. - Proving and verifying computations with
scarb prove
andscarb verify
.
In the next chapters, you’ll dive deeper into Cairo’s syntax (Chapter 2), ownership (Chapter 4), and other features. For now, experiment with different inputs or modify the primality check — can you optimize it further?