ZK/SEC Research notes from zkSecurity
All posts
noname · Part 2 of 4

noname meets Ethereum: Integration with SnarkJS

We are thrilled to announce that noname, zkSecurity's programming language to write ZK circuits, now supports R1CS! This update allows developers to write ZK circuits in a rust-and-golang-inspired language and deploy them to Ethereum using SnarkJS. This offers an alternative to the widely-used Circom language for zk-SNARK proofs on Ethereum. In this post, you'll see the first noname circuit deployed on Ethereum, and we'll show you how to deploy one yourself.

noname & snarkjs

The zero-knowledge field is evolving rapidly, with new proof systems and frameworks frequently emerging. Developers face a major challenge: fragmentation. Each proof system has its own language and quirks, often targeting specific projects and unavailable on other platforms. For example, Noir is for Aztec Network, Leo is for Aleo, Cairo is for Starknet, and O1js is for Mina.

What if there was a way to bridge this gap? A language that could unify the zk ecosystem, allowing developers to write circuits that work across different backends.

This is what we're doing! while noname was first created to compile programs to work with Mina's kimchi proof system, we are now updating it to work with Ethereum's SnarkJS.

The integration of noname with R1CS and SnarkJS is a pivotal step in the language's journey, and it's been made possible thanks to a grant from the Ethereum Foundation's Privacy & Scaling Explorations team.

A Sudoku as a noname program

Using Sudoku as an example demonstrates two benefits of zero-knowledge proofs:

  1. Proving correctness of a Sudoku solution without revealing the solution itself.

  2. Reducing on-chain computation costs. The verification cost on-chain is constant, regardless of the complexity of the off-chain computation.

The full code for the sudoku circuit is available here. Let's look at our main function:

fn main(pub grid: Sudoku, solution: Sudoku) {
    solution.matches(grid);
    solution.verify();
}

It receives two arguments: the Sudoku problem as a public input, and the solution as a secret.

At a high level, this sudoku circuit checks:

  1. the solution grid matches with the sudoku problem (encoded in the public inputs)

  2. verify the solution indeed follows the sudoku rules

In Noname, grid: Sudoku automatically interprets inputs as the Sudoku type:

struct Sudoku {
    inner: [Field; 81],
}

Here is how it checks if the solution matches with the grid of a solution problem.

// return the value in a given cell
fn Sudoku.cell(self, const row: Field, const col: Field) -> Field {
    return self.inner[(row * 9) + col];
}

// verifies that self matches the grid in places where the grid has numbers
fn Sudoku.matches(self, grid: Sudoku) {
    // for each cell
    for row in 0..9 {
        for col in 0..9 {
            // either the solution matches the grid
            // or the grid is zero
            let matches = self.cell(row, col) == grid.cell(row, col);
            let is_empty = grid.cell(row, col) == empty;
            assert(matches || is_empty);
        }
    }
}

Next, the program verifies the solution according to sudoku roles:

fn Sudoku.verify(self) {
    self.verify_rows();
    self.verify_cols();
    self.verify_diagonals();
}

fn Sudoku.verify_rows(self) {
    for row in 0..9 {
        for num in 1..10 {
            let mut found = false;
            for col in 0..9 {
                let found_one = self.cell(row, col) == num;
                found = found || found_one;
            }
            assert(found);
        }
    }
}

fn Sudoku.verify_cols(self) {
    for col in 0..9 {
        for num in 1..10 {
            let mut found = false;
            for row in 0..9 {
                let found_one = self.cell(row, col) == num;
                found = found || found_one;
            }
            assert(found);
        }
    }
}

fn Sudoku.verify_diagonals(self) {
    for num in 1..10 {

        // first diagonal
        let mut found1 = false;
        for row1 in 0..9 {
            let temp1 = self.cell(row1, row1) == num;
            found1 = found1 || temp1;
        }
        assert(found1);

        // second diagonal
        let mut found2 = false;
        for row2 in 0..9 {
            let temp2 = self.cell(8 - row2, row2) == num;
            found2 = found2 || temp2;
        }
        assert(found2);
    }
}

As it is shown above, the typing support behind the language makes circuit code straightforward, akin to writing in conventional languages.

Plugging into Snarkjs

Now that we have the sudoku code, we can use the noname CLI to generate and run circuits that are compatible with Snarkjs.

  1. Install noname

    cargo install --git https://www.github.com/zksecurity/noname
    

  2. The command guideline

noname-help

  1. Generate r1cs circuit and witness files compatible with snarkjs
noname run --backend r1cs-bn254 --path ../test/noname-sudoku \
--private-inputs '{"solution": { "inner": ["9", "5", "3", "6", "2", "1", "7", "8", "4", "1", "4", "8", "7", "5", "9", "2", "6", "3", "2", "7", "6", "8", "3", "4", "9", "5", "1", "3", "6", "9", "2", "7", "5", "4", "1", "8", "4", "8", "5", "9", "1", "6", "3", "7", "2", "7", "1", "2", "3", "4", "8", "6", "9", "5", "6", "3", "7", "1", "8", "2", "5", "4", "9", "5", "2", "1", "4", "9", "7", "8", "3", "6", "8", "9", "4", "5", "6", "3", "1", "2", "7"] }}' \
--public-inputs '{"grid": { "inner": ["0", "5", "3", "6", "2", "1", "7", "8", "4", "0", "4", "8", "7", "5", "9", "2", "6", "3", "2", "7", "6", "8", "3", "4", "9", "5", "1", "3", "6", "9", "2", "7", "0", "4", "1", "8", "4", "8", "5", "9", "1", "6", "3", "7", "2", "0", "1", "2", "3", "4", "8", "6", "9", "5", "6", "3", "0", "1", "8", "2", "5", "4", "9", "5", "2", "1", "4", "9", "0", "8", "3", "6", "8", "9", "4", "5", "6", "3", "1", "2", "7"] }}'

Then it should generate the .r1cs and .wtns files and tell you where the files are located.

R1CS file generated at: ../test/noname-sudoku/output.r1cs
Witness file generated at: ../test/noname-sudoku/output.wtns
  1. Test the generated r1cs circuit and witness files

There is a script to quickly test the compatibility of these outputs with the SnarkJS. The script automatically generates the proof and verifies it off-chain via SnarkJS.

Verify the proof on Ethereum

The snarkjs provides a command to export a solidity verifier. We have a deployed instance on Ethereum at 0x7c686a33ac3f2c911b03b9aa80e5175d3ab4152a.

To test a solution on-chain, you can use this SnarkJS command to generate the calldata.

Intuitively, the calldata contains the contract call arguments to represent a sudoku problem (the public inputs in the circuit) and a proof for the corresponding solution (without revealing it).

Screenshot 2024-05-30 at 16.23.34

As the screenshot shown (from remix), if the proof(_pA, _pB, _pC) and the sudoku problem matches (_pubSignals), then the contract call to verifyProof should return true.

What's next?

noname is a work-in-progress language, and many features are missing! We're looking for help and contributions. If you're interested, check the issues.

To learn more about Noname, please check out the repo, or the following blog posts and videos:

Keep reading
Latest

Optimizing Cryptography with AI

Many of us are using AI to generate code. Vibe coding cryptography is especially sensitive - you have to uphold strict mathematical correctness. This can lead to wrong security guarantees and soundness bugs. We will discuss what are some patterns to do it well.

Kobi Gurkan · August 11, 2026

Introducing zkvmBlast: Differential Fuzzing for Ethereum's zkVMs

zkVMs are moving to the center of Ethereum's roadmap, which means a bug in a zkVM is turning into a bug in Ethereum itself. We built zkvmBlast, a zkVM-agnostic differential fuzzer that runs the same program across SP1, RISC0, OpenVM, Pico, Zisk, and Airbender against a reference simulator and flags any disagreement. It hunts for both soundness and completeness bugs, with a deliberate focus on completeness, an under-explored class that can turn a single valid block into a liveness failure. We share the first batch of findings.

Stefanos Chaliasos, Martín Ochoa, Varun Thakore · August 10, 2026

Circom-Auditor: Open-Source Skills for Finding Vulnerabilities in Circom Code

We are releasing zk-skills, a set of open-source security skills for AI coding agents, starting with circom-auditor: a first line of defense against vulnerabilities in Circom circuits, compatible with both Claude Code and Codex. On the zkbugs benchmark it detects up to 66 of 70 known bugs when pointed at the vulnerable circuits, and up to 40 of 56 when let loose on the full original codebases, far ahead of existing Circom security tools.

Stefanos Chaliasos, Hao Pham, False Witness Team · August 05, 2026
Recommended

noname 3.0: Native Hints, Standard Library, Compiler Visualizer, And More!

We're super excited to introduce noname 3.0, our zk programming language inspired by Rust and Golang, now achieving full feature parity with Circom. This update brings native hints, a standard library, debugging features, and a lot more to enhance developer experience. Dive into how hint functions work with an 'unsafe' keyword to balance innovation and security, explore our new stdlib modules, and see how the compiler pipeline visualizer can help you understand the compiling process. Plus, check out our next steps and how you can contribute to shaping noname's future.

ZK/SEC · November 13, 2024

noname 2.0: Unlocking Numeric Generics, Folding Schemes, and a Playground

We're excited to introduce the preview of noname 2.0, packed with features that make developing advanced ZK circuits easier than ever. This update includes flexible generic-sized arrays, seamless integration with folding schemes for IVC, and an interactive online playground to test and share code. We've also optimized R1CS constraint generation to boost performance. Plus, there are numerous community-driven enhancements and bug fixes that make the language more robust and user-friendly. Dive in to explore the specifics of our journey, learn from the contributions of our vibrant open-source community, and see how noname is evolving into a more versatile tool for developers.

ZK/SEC · August 08, 2024

noname: ZK app developers should be able to see down to the constraints

Zero-knowledge apps are evolving, and we've been diving into their two main forms: VM instructions and arithmetic circuits. Understanding the "assembly" layer is crucial for developers, especially when optimizing and ensuring security. We’ve played around with a new toy language called **noname**, blending Golang and Rust vibes to make zkApps more understandable. With **noname**, you get detailed insights about how your code translates into gates, offering a clearer picture of the underlying "assembly" and helping pinpoint compiler bugs. If you're curious about enhancing your low-level programming skills or peeking into circuit construction, check out our experiments and see if this inspires you to create better debugging tools!

David Wong · June 03, 2023
More to explore

Announcing the S-two Book

We're thrilled to share our collaboration with Starkware on the S-two book. If you're curious about writing AIRs with the S-two prover, implementing Cairo AIR in S-two, or how Circle STARKs are utilized, this post is for you. Dive in to explore these insights and deepen your understanding of these fascinating topics.

Jason Park, Varun Thakore · November 11, 2025

Detecting boomerang values in zero-knowledge circuits using tag analysis

Ever wonder how zkApps ensure their execution is spot-on? This post digs into the idea of *boomerang values*, sneaky bugs that can crop up when zkApps mix in-circuit and out-of-circuit logic. We explore how these values disrupt your circuit's trustworthiness, especially when variables are reintegrated unverified. Plus, we share how tools like MIRAI's tag analysis can help spot these issues, making your zkApps more secure. Perfect for anyone into zero-knowledge applications or budding Rust enthusiasts looking for insights on taming complex bugs!

ZK/SEC · August 25, 2023

Halo2's Elegant Transcript As Proof

In this blog post, we explore a clever design in Zcash's halo2 implementation for securing the Fiat-Shamir transformation. By using a mutable transcript, the process ensures that values are automatically absorbed, reducing potential bugs. You'll find explanations of the distinct roles of `write` and `read` functions for points and scalars, highlighting how this abstraction makes the prover-verifier interaction seamless and secure. If you're curious about the inner workings of cryptographic protocols, this is a fascinating read.

David Wong · September 30, 2025