Prime Factors in Cairo: Complete Solution & Deep Dive Guide

macbook pro on brown wooden table

Unlocking Prime Factors: The Complete Cairo Guide from Zero to Hero

Computing the prime factors of a number is a fundamental task in computer science and cryptography. This guide provides a comprehensive walkthrough of the prime factorization algorithm, a complete implementation in the Cairo language, and a detailed explanation of its relevance in the Starknet ecosystem.


The Quest for Primes: Why Every Cairo Developer Needs This Skill

You're diving into Cairo, building the future of decentralized applications on Starknet. You're mastering concepts like felts, storage variables, and contract interactions. Then, you encounter a problem from the kodikra learning path that seems purely mathematical: finding the prime factors of a number. It's easy to dismiss this as a mere academic exercise, a puzzle disconnected from the real world of smart contracts.

Many developers share this feeling. The leap from building a dApp to implementing number theory can feel jarring. You might wonder, "When will I ever need to break down a number into its prime components? Isn't this what libraries are for?" This thinking is a trap. The truth is, concepts like prime factorization are not just abstract math; they are the bedrock of the very security that makes blockchain technology possible.

This guide promises to bridge that gap. We won't just give you the code. We will embark on a journey to understand the what, the why, and the how of prime factorization in Cairo. By the end, you'll not only have a robust Cairo function but also a deeper appreciation for the cryptographic principles that secure the entire Starknet ecosystem. You'll transform a seemingly simple math problem into a powerful tool in your developer arsenal.


What Exactly Are Prime Factors?

Before we write a single line of Cairo code, we must solidify our understanding of the core concepts. This foundation is crucial for grasping the logic behind our algorithm.

Defining Prime and Composite Numbers

In the world of natural numbers (positive integers like 1, 2, 3, ...), we can categorize them into three distinct groups:

  • The Unit: The number 1, which is in a class of its own.
  • Prime Numbers: A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. Think of them as the fundamental building blocks of integers. Examples include 2, 3, 5, 7, 11, and 13.
  • Composite Numbers: A composite number is a natural number greater than 1 that is not prime. In other words, it can be formed by multiplying two smaller natural numbers. Examples include 4 (2x2), 6 (2x3), 9 (3x3), and 60 (2x2x3x5).

The number 2 is special because it is the only even prime number. Every other even number is divisible by 2, making it composite by definition.

The Concept of Factorization

Factorization is the process of breaking down a composite number into a set of smaller integers which, when multiplied together, give the original number. When we specifically break it down into a set of prime numbers, this is called prime factorization.

The Fundamental Theorem of Arithmetic states that every integer greater than 1 is either a prime number itself or can be represented as a unique product of prime numbers. This uniqueness is key.

Let's take the example from the kodikra module instructions: 60.

  • The prime factors of 60 are 2, 2, 3, 5.
  • If you multiply them together: 2 * 2 * 3 * 5 = 60.
  • This set of prime factors is unique to the number 60. No other combination of primes will produce this result.

Why Prime Factorization is Crucial for Cairo & Starknet

This is the most important question. The answer lies in one word: cryptography. Blockchains like Starknet rely on advanced cryptographic techniques to ensure security, immutability, and privacy. Many of these techniques, especially public-key cryptography, are built upon number theory problems that are computationally difficult to solve.

The classic example is the RSA algorithm. Its security relies on the fact that it is easy to multiply two very large prime numbers together to get a massive composite number, but it is extraordinarily difficult to take that massive composite number and find its original prime factors. This "trapdoor" function is what makes secure communication possible.

While you may not be implementing the RSA algorithm directly in a daily Starknet contract, understanding its principles is vital. The computational hardness of prime factorization for large numbers is a foundational concept that underpins the security assumptions of the digital world. As Cairo is the language for building on a ZK-Rollup, where complex computations and proofs are paramount, a strong grasp of these mathematical fundamentals gives you a significant edge.

Furthermore, working on problems like this sharpens your algorithmic thinking and your ability to handle numerical computations efficiently—skills that are directly transferable to optimizing gas costs and writing performant smart contracts.


How to Calculate Prime Factors: The Trial Division Algorithm

The most intuitive and common method for finding prime factors, especially for numbers that aren't astronomically large, is called Trial Division. The logic is straightforward and follows a simple, iterative process.

The Step-by-Step Logic

  1. Start with the smallest prime: Begin with the smallest prime number, which is 2, as our potential divisor.
  2. Divide repeatedly: Take the input number (let's say `n`) and check if it's evenly divisible by our current divisor.
    • If it is, that divisor is a prime factor! Add it to our list of factors.
    • Then, update `n` by dividing it by that factor (e.g., `n = n / divisor`).
    • Repeat this step with the same divisor until `n` is no longer evenly divisible by it.
  3. Move to the next divisor: Once the number is no longer divisible by our current divisor, move to the next potential divisor. We can increment our divisor by 1 (or make a small optimization, discussed later).
  4. Repeat the process: Continue this process of dividing and incrementing the divisor until the divisor squared is greater than the remaining value of `n`.
  5. Handle the remainder: If, after the loop finishes, the remaining value of `n` is greater than 1, it means this remaining number is itself a prime factor. Add it to our list.

Visualizing the Algorithm Flow

Here is an ASCII art diagram illustrating the flow of the Trial Division algorithm for finding the prime factors of a number `n`.

    ● Start (Input: n)
    │
    ▼
  ┌──────────────────┐
  │ factors = []     │
  │ divisor = 2      │
  └────────┬─────────┘
           │
           ▼
  ┌──────────────────┐
  │ Loop while n % 2 == 0 │
  └────────┬─────────┘
           │
           ├─ Yes ──▶ factors.append(2), n = n / 2
           │
           ▼ No
  ┌──────────────────┐
  │ divisor = 3      │
  └────────┬─────────┘
           │
           ▼
  ┌───────────────────────────────┐
  │ Loop while divisor * divisor <= n │
  └───────────────┬───────────────┘
                  │
  ┌───────────────┴───────────────┐
  │                               │
  ▼                               ▼
┌───────────────────┐      ◆ Is n % divisor == 0?
│ divisor += 2      │     ╱            ╲
└───────────────────┘    Yes            No
  ▲                      │              │
  │                      ▼              │
  └──────────────[ factors.append(divisor), n = n / divisor ]
                  │
                  ▼
  ┌───────────────────┐
  │ Loop while n % divisor == 0 │
  └───────────────────┘
           │
           ▼
  ┌───────────────────┐
  │ After Main Loop   │
  └─────────┬─────────┘
            │
            ▼
       ◆ Is n > 1?
      ╱           ╲
     Yes           No
     │              │
     ▼              ▼
[ factors.append(n) ]   ● End (Return factors)
     │                      
     └──────────────────────▶ ● End (Return factors)

Where is the Cairo Implementation? The Complete Code

Now, let's translate our algorithm into working Cairo code. This implementation is designed to be clean, readable, and idiomatic for Cairo 1, using the standard library's Array for storing the results. We'll use u256 for the number type, as it's a common and versatile choice in Starknet for handling large integer values.


use core::array::ArrayTrait;
use core::option::OptionTrait;
use core::integer::{u256, u256_sqrt};

/// Computes the prime factors of a given natural number `n`.
/// This function implements the trial division algorithm.
///
/// # Arguments
///
/// * `n` - The number to factorize, of type `u256`.
///
/// # Returns
///
/// An `Array<u256>` containing the prime factors of `n` in ascending order.
fn prime_factors(n: u256) -> Array<u256> {
    // Initialize a mutable copy of the input number and an empty array for factors.
    let mut current_n = n;
    let mut factors: Array<u256> = array
![];

    // Step 1: Handle all factors of 2.
    // This is handled separately because we can then increment our divisor by 2
    // for all subsequent checks, which is a slight optimization.
    while current_n % 2 == 0 {
        factors.append(2)
;
        current_n = current_n / 2;
    }

    // Step 2: Handle all odd factors, starting from 3.
    // We only need to check divisors up to the square root of the remaining number.
    let mut divisor = 3;
    while divisor * divisor <= current_n {
        // While the current number is divisible by the current divisor...
        while current_n % divisor == 0 {
            // ...the divisor is a prime factor. Add it to the list.
            factors.append(divisor);
            // And divide the number by it to reduce it for the next iteration.
            current_n = current_n / divisor;
        }
        // Move to the next odd number.
        divisor += 2;
    }

    // Step 3: Handle the case where the remaining number is a prime.
    // If current_n is greater than 1 at this point, it must be a prime number itself.
    // For example, if the input was 14, after dividing by 2, current_n becomes 7.
    // The loop for odd divisors won't run as 3*3 > 7. The remaining 7 is a prime factor.
    if current_n > 1 {
        factors.append(current_n);
    }

    // Return the final list of prime factors.
    factors
}


A Step-by-Step Code Walkthrough

The code might seem dense at first, but it directly follows the logic we outlined. Let's break it down piece by piece to understand what each part does.

Function Signature and Initialization


fn prime_factors(n: u256) -> Array<u256> {
    let mut current_n = n;
    let mut factors: Array<u256> = array
![];
  • fn prime_factors(n: u256) -> Array<u256>: We define a function named prime_factors that accepts one argument, n, of type u256. It is declared to return a dynamic array of u256 values, which will be our list of factors.
  • let mut current_n = n;: We create a mutable variable current_n to hold the number we are factoring. We need it to be mutable (mut) because we will be dividing it down throughout the algorithm. We can't modify the input n directly as function arguments are immutable by default in Cairo.
  • let mut factors: Array<u256> = array ![];: We initialize an empty, mutable array called factors. This is where we'll store the prime factors as we find them. The array![] macro is the standard way to create a new array.

Handling the Factor '2'


while current_n % 2 == 0 {
    factors.append(2)
;
    current_n = current_n / 2;
}
  • This is a simple but effective optimization. We first handle the only even prime number, 2.
  • The while loop continues as long as current_n is evenly divisible by 2 (the modulo operator % returns 0).
  • Inside the loop, we append the number 2 to our factors array and then update current_n by dividing it by 2.
  • For an input like 60, this loop would run twice:
    1. 60 % 2 == 0 is true. Add 2 to factors. current_n becomes 30. Factors: `[2]`.
    2. 30 % 2 == 0 is true. Add 2 to factors. current_n becomes 15. Factors: `[2, 2]`.
    3. 15 % 2 == 0 is false. The loop terminates.
  • After this loop, we are guaranteed that the remaining current_n is an odd number.

Iterating Through Odd Divisors


let mut divisor = 3;
while divisor * divisor <= current_n {
    while current_n % divisor == 0 {
        factors.append(divisor);
        current_n = current_n / divisor;
    }
    divisor += 2;
}
  • let mut divisor = 3;: We initialize our next potential divisor to 3, the first odd prime.
  • while divisor * divisor <= current_n: This is the main loop's condition and a critical optimization. We only need to check for divisors up to the square root of the number. If a number `n` has a factor larger than its square root, it must also have a corresponding factor smaller than its square root. We will have already found the smaller factor, so there's no need to check beyond `sqrt(n)`.
  • while current_n % divisor == 0: This is a nested loop. For each potential divisor, we check if it can divide current_n multiple times. For an input like 45, when our divisor is 3, it will divide it once, leaving 15, and then again, leaving 5.
  • factors.append(divisor); and current_n = current_n / divisor;: Same logic as with the factor 2. We add the factor and reduce the number.
  • divisor += 2;: This is the second key optimization. Since we've already handled all factors of 2, we know the remaining number is odd. Therefore, it cannot have any other even factors. We can safely skip all even numbers (4, 6, 8, etc.) in our search for divisors by incrementing our divisor by 2 in each step.

Handling the Final Remainder


if current_n > 1 {
    factors.append(current_n);
}
  • This final step is crucial. Consider the input 14. Our first loop divides by 2, leaving current_n = 7.
  • The main loop starts with divisor = 3. The condition 3 * 3 <= 7 is false, so the main loop never runs.
  • At this point, current_n is 7. If we didn't have this final check, our function would incorrectly return `[2]` instead of `[2, 7]`.
  • This if statement checks if there's a remaining number greater than 1. If so, that number must be a prime factor itself, and we append it to our list.

When to Consider Alternative Approaches

The Trial Division method is excellent for educational purposes and for numbers within the typical range of a u256. However, it's important to understand its limitations. For truly massive numbers, like those used in real-world cryptography (which can have hundreds of digits), Trial Division is far too slow to be practical.

This is where more advanced, probabilistic algorithms come into play. You don't need to implement them for this kodikra module, but being aware of them demonstrates expert-level knowledge.

Pros and Cons of Trial Division

Pros Cons
Simple to Understand: The logic is intuitive and easy to follow, making it a great starting point. Inefficient for Large Numbers: The runtime complexity is roughly O(sqrt(n)), which becomes prohibitively slow as 'n' gets very large.
Easy to Implement: The code is short and doesn't require complex data structures or mathematical concepts. Not Suitable for Cryptography: Cannot be used to break modern cryptographic systems that rely on factoring massive semi-primes.
Guaranteed to Find All Factors: It is a deterministic algorithm that will always produce the correct and complete list of prime factors. Performs Redundant Checks: It checks for divisibility by composite numbers like 9, 15, 21, etc., which is unnecessary (though harmless, as their prime factors would have already been removed).

A Glimpse into Advanced Methods

For factoring cryptographic-scale numbers, mathematicians and computer scientists use algorithms like:

  • Pollard's Rho Algorithm: A probabilistic algorithm that is much faster than trial division for numbers with small prime factors.
  • Quadratic Sieve (QS): One of the fastest algorithms for factoring numbers up to about 100 digits long.
  • General Number Field Sieve (GNFS): The most efficient classical algorithm known for factoring integers larger than 100 digits. This is the algorithm used to demonstrate vulnerabilities in RSA keys of insufficient length.

The conceptual difference is in their approach. Trial Division is a brute-force search, while these advanced methods use sophisticated mathematical properties to find factors much more quickly.

    ● Start (Factorize N)
    │
    ▼
  ┌──────────────────┐
  │ Algorithm Choice │
  └────────┬─────────┘
           │
           ▼
    ◆ Is N very large?
   ╱ (e.g., > 10^20)  ╲
  No                   Yes
  │                     │
  ▼                     ▼
┌──────────────┐   ┌────────────────────┐
│ Use Trial    │   │ Use Advanced Method│
│ Division     │   │ (e.g., Pollard's Rho,│
│ (Efficient   │   │ Quadratic Sieve)   │
│ enough)      │   │ (Trial division is │
└──────┬───────┘   │ too slow)          │
       │           └─────────┬──────────┘
       │                     │
       └─────────┬───────────┘
                 ▼
           ┌──────────┐
           │ Get      │
           │ Factors  │
           └──────────┘
                 │
                 ▼
               ● End

Frequently Asked Questions (FAQ)

1. What is the difference between prime factors and prime factorization?

The terms are closely related. Prime factors are the individual prime numbers that multiply together to create a composite number. For 60, the prime factors are 2, 2, 3, and 5. Prime factorization is the expression of the composite number as a product of its prime factors, often written using exponents, like 60 = 2² x 3¹ x 5¹.

2. Why does the algorithm only need to check divisors up to the square root of the number?

Let's say a number n can be factored into a * b. If both a and b were greater than the square root of n, then their product a * b would be greater than sqrt(n) * sqrt(n), which is n. This is a contradiction. Therefore, at least one of the factors (either a or b) must be less than or equal to the square root of n. Our algorithm finds this smaller factor first, so we don't need to search for the larger one beyond that point.

3. Can this Cairo function handle very large numbers?

The function uses the u256 type, which can represent integers up to 2²⁵⁶ - 1. This is a massive number, far larger than what you'd typically need in many smart contract applications. However, as discussed, the Trial Division algorithm itself becomes very slow as the input number approaches the upper limits of u256, especially if the number has large prime factors. For most practical purposes within the kodikra learning path, it is perfectly sufficient.

4. Is 1 considered a prime number?

No, 1 is not a prime number. By definition, a prime number must have exactly two distinct positive divisors: 1 and itself. The number 1 has only one divisor (itself), so it doesn't fit the definition. It is considered a "unit".

5. How can I test this Cairo code?

The best way to test Cairo code is by setting up a local development environment using Scarb, the Cairo package manager. You can create a new project, place this function in your `lib.cairo` file, and then write tests for it using Cairo's built-in testing framework. You would create test functions that call `prime_factors` with known inputs (e.g., 60, 99, 13) and assert that the returned array matches the expected output.

6. Are there built-in prime factorization libraries in Cairo?

The Cairo and Starknet ecosystem is rapidly evolving. While the core library provides essential primitives for arithmetic, more specialized mathematical libraries are often developed by the community. As of now, you would typically implement such an algorithm yourself, as done here. This is common in new languages and provides a great learning opportunity. Keep an eye on community-driven projects like the Starknet-math library for future developments.


Conclusion: From Theory to Practical Mastery

We've journeyed from the fundamental definition of a prime number to a fully-functional and optimized prime factorization algorithm in Cairo. You've not only learned the "how" of implementing the code but, more importantly, the "why" — its deep connection to the cryptographic security that powers Starknet and the broader blockchain world.

The Trial Division algorithm is a perfect example of a foundational computer science concept: it's simple, elegant, and powerful enough for a wide range of applications. By mastering it, you've sharpened your problem-solving skills and deepened your understanding of how to work with numbers efficiently in a resource-constrained environment like a smart contract platform.

This knowledge is a stepping stone. As you continue your journey through the kodikra learning path for Cairo, you'll encounter more challenges that build upon these core principles. Keep experimenting, keep building, and continue to explore the fascinating intersection of mathematics and decentralized technology.

Disclaimer: The code in this article is written for Cairo 1.x. As the language evolves, syntax and best practices may change. Always refer to the latest official documentation. For a broader look at the language, check out our complete Cairo language guide.


Published by Kodikra — Your trusted Cairo learning resource.