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

The Year Finding and Exploiting Bugs Became Cheap, and What to Do About It

The economics of security have changed. AI has made finding and exploiting bugs cheaper, including in cryptographic and zero-knowledge code, while validation and remediation remain slow. Here is our view of what happened, what comes next, and how teams should change the way they secure their stack to defend against AI-assisted attackers.

Stefanos Chaliasos · September 07, 2026

Variants of KZG: Part V, Multilinear Commitments with Mercury

In this final post of the series, we extend univariate KZG commitments to multilinear polynomials through Mercury. Building on Gemini, we fold half of the variables at once, use polynomial division to bind this large fold to the original commitment and reduce the remaining multilinear evaluations to a batched inner product check. We then walk through the end-to-end opening protocol. We conclude by examining its proof size, prover cost and verifier cost.

Varun Thakore · August 17, 2026

Variants of KZG: Part IV, Multilinear Commitments with Gemini

In this blog post, we extend univariate KZG commitments to multilinear polynomials through Gemini. We introduce recursive partial evaluations, derive the split-and-fold identity and express each fold as a univariate identity that can be checked using KZG openings. We then walk through the end-to-end opening protocol. We conclude by examining its proof size, prover cost, and verifier cost.

Varun Thakore · August 12, 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