🗝️

Noirで始めるZKP開発入門|ZKアプリ開発の新定番を理解する

に公開

想定読者: Circom / zk-SNARK を触ったことがあるが、Noir は未経験の人
ゴール: Noir の特徴を理解し、実際に動かせるようになる


TL;DR

  • Noir は Rust ライクな文法で書けるゼロ知識証明(ZKP)専用言語
  • Circom より高レベルで、暗号の知識がなくても ZK プログラムが書ける
  • Trusted Setup 不要で、セットアップの信頼リスクがない

この記事は Web3 Advent Calendar 2025 の19日目の記事です。

https://qiita.com/advent-calendar/2025/web3


この記事で話すこと / 話さないこと

話すこと

  • Noir の概要と特徴
  • Circom との比較
  • 環境セットアップからオンチェーン検証までのハンズオン

話さないこと

  • ZKP の数学的な詳細(楕円曲線、ペアリングなど)
  • 本番環境でのセキュリティ考慮事項

0. なぜ今 Noir なのか?

以前の記事 ZK-SNARKs実践入門 では、Circom + snarkjs で ZK 証明を実装しました。しかし、Circom には以下の課題がありました。

課題 詳細
独自の回路DSL 算術回路の概念を理解しないと書けない
Trusted Setup 必須 Groth16 の信頼設定が必要
開発体験 デバッグが難しく、エラーメッセージもわかりにくい

Noir はこれらの課題を解決する、次世代の ZK プログラミング言語です。最近では Circom ではなく Noir を選択するプロジェクトが増えており、ZK 開発のスタンダードが変わりつつあります。


1. Noir とは?

Noir(ノワール) は、Aztec Labs が開発したゼロ知識証明専用のドメイン特化言語(DSL)です。

Noir is an open-source Domain-Specific Language for safe and seamless construction of privacy-preserving Zero-Knowledge programs, requiring no previous knowledge on the underlying mathematics or cryptography.
— 公式ドキュメント

このセクションで学ぶこと:

  • Noir の基本的な特徴
  • なぜ「次世代」と呼ばれるのか

1.1 Noir の動作フロー

Noir プログラムは ACIR(Abstract Circuit Intermediate Representation) という中間表現にコンパイルされ、Barretenberg という証明システムで証明が生成されます。


2. Noir の特徴

Rust ライクな文法で書きやすい

fn main(age: u8, min_age: pub u8) {
    assert(age >= min_age);
}

Circom の独自構文と比較して、Rust 経験者なら直感的に書ける設計です。

暗号の知識が不要

算術回路や制約システムの詳細を知らなくても、通常のプログラミングと同じ感覚で ZK プログラムが書けます。

Trusted Setup 不要

Noir のデフォルト証明システムである Barretenberg(UltraHonk)は、Trusted Setup が不要です。Groth16 で必要だった Powers-of-Tau セレモニーが不要になり、セットアップのリスクを心配する必要がありません。

補足: Noir は ACIR という中間表現を経由するため、理論上は複数の証明システムに対応可能な設計です。ただし、現時点で実用的に使えるのは Barretenberg のみです。

ACIR による柔軟な設計

Noir は ACIR(Abstract Circuit Intermediate Representation)という中間表現にコンパイルされます。この設計により、理論上は複数の証明システムに対応可能です。現時点では Barretenberg が標準ですが、将来的に他の証明システムも選択できるようになる可能性があります。

公開/秘密の直感的な指定

fn main(
    secret_value: Field,      // デフォルトは秘密(private)
    public_value: pub Field   // pub で公開を明示
)

秘密がデフォルトという設計は、ZK プログラムの本質に合っています。

豊富なエコシステム

標準ライブラリに加え、Awesome Noir に多数のライブラリがまとめられています。


3. Circom vs Noir 比較

このセクションの要点:

  • 開発体験、証明システム、ユースケースの違いを理解する

3.1 技術比較表

観点 Circom Noir
言語タイプ 算術回路DSL ZKプログラミング言語
文法 独自構文(回路寄り) Rust ライク
学習コスト 高(回路の理解必須) 低〜中
Trusted Setup 必要(Groth16) 不要(Barretenberg)
証明システム snarkjs (Groth16) Barretenberg (UltraHonk)
デバッグ 難しい 比較的容易
主な用途 回路設計・既存プロジェクト 新規ZKアプリ開発

3.2 コード比較:「年齢が18歳以上か」を証明

Circom の場合

pragma circom 2.0.0;

template AgeCheck() {
    signal input age;
    signal input minAge;
    signal output valid;
    
    // 比較演算は直接書けない
    // LessThan コンポーネントなどを使う必要がある
    component lt = LessThan(8);
    lt.in[0] <== minAge;
    lt.in[1] <== age + 1;
    valid <== lt.out;
}

component main = AgeCheck();

Noir の場合

fn main(age: u8, min_age: pub u8) {
    assert(age >= min_age);
}

Noir では4行で同じことが表現できます。

3.3 証明サイズ・検証コスト比較

指標 Circom (Groth16) Noir (UltraHonk)
証明サイズ 約128〜192バイト 約16KB〜
検証ガス 約23万gas 約230万gas
Trusted Setup 必要 不要

注意: 上記の値は hello_world サンプル(単純な年齢検証回路)での実測値です。実際の値は回路の複雑さや証明構造によって大きく変動します。

Groth16 は証明サイズ・検証コストで大きな優位性があります。一方、UltraHonk は Trusted Setup が不要で、開発速度も速いという利点があります。ガスコストの差は大きいですが、L2 での利用や、Trusted Setup のリスクを避けたいケースでは Noir が選ばれています。


4. Noir プログラムの書き方

このセクションで学ぶこと:

  • Noir の基本的な文法
  • 公開入力と秘密入力の扱い方

4.1 基本構造

fn main(x: Field, y: pub Field) {
    assert(x != y);
}
要素 説明
fn main() エントリポイント(必須)
x: Field 秘密入力(デフォルト)
y: pub Field 公開入力(pub で明示)
assert() 制約を定義

4.2 主要なデータ型

型 説明 用途
Field 有限体の要素 汎用的な数値計算
u8, u16, u32, u64 符号なし整数 ビット演算、範囲制限
bool 真偽値 条件分岐
[T; N] 固定長配列 データの集合
str<N> 固定長文字列 文字列処理

詳細は 公式ドキュメント - Data Types を参照してください。

4.3 実用的な例:秘密の値の検証

fn main(x: Field, y: Field, expected_hash: Field) {
    let hash = std::hash::pedersen_hash([x, y]);
    assert_eq(hash, expected_hash);
}

このプログラムにより、秘密の値 x と y を明かさずに「それらを Pedersen ハッシュした結果が期待値と一致する」ことを証明できます。

Note: Noir には標準ライブラリやコミュニティライブラリとして、ECDSA署名検証や各種ハッシュ関数が用意されています。詳細は 公式ドキュメント - Cryptographic Primitives を参照してください。


5. ハンズオン:環境セットアップから検証まで

このハンズオンで達成すること:

  • Noir 開発環境のセットアップ
  • 証明の生成と検証
  • Solidity Verifier によるオンチェーン検証

5.1 ツールのインストール

Nargo(Noir コンパイラ)のインストール

# インストーラを実行
curl -L https://raw.githubusercontent.com/noir-lang/noirup/refs/heads/main/install | bash

# Nargo をインストール
noirup

パスが通っていない場合は、シェルの設定ファイルに追加します。

# ~/.zshrc または ~/.bashrc に追加
export PATH="$HOME/.noirup/bin:$PATH"

# 設定を再読み込み
source ~/.zshrc

インストール確認:

nargo --version

Barretenberg(証明システム)のインストール

# インストーラを実行
curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/refs/heads/next/barretenberg/bbup/install | bash

# Barretenberg をインストール
bbup

パスが通っていない場合:

# ~/.zshrc または ~/.bashrc に追加
echo 'export PATH="$HOME/.bb:$PATH"' >> ~/.zshrc

# 設定を再読み込み
source ~/.zshrc

インストール確認:

bb --version

5.2 プロジェクトの作成

# 新しいプロジェクトを作成
nargo new hello_world
cd hello_world

生成されるファイル構成:

hello_world/
├── Nargo.toml      # プロジェクト設定
├── Prover.toml     # 入力値の設定(nargo check 後に編集)
└── src/
    └── main.nr     # メインプログラム

5.3 回路の実装

src/main.nr を以下のように編集します:

fn main(age: u8, min_age: pub u8) {
    assert(age >= min_age);
}

回路の説明:

  • age: 秘密入力(実際の年齢は明かさない)
  • min_age: 公開入力(検証者は最低年齢を知ることができる)
  • この回路で「年齢が最低年齢以上である」ことを証明できます

5.4 回路のチェックと入力値の設定

まず回路をチェックして Prover.toml を生成します。

nargo check --overwrite

次に、生成された hello_world/Prover.toml に入力値を設定します。

age = "25"
min_age = "18"

5.5 コンパイルと実行

nargo execute

成功すると target/ ディレクトリに以下が生成されます:

  • hello_world.json - コンパイル済み回路
  • hello_world.gz - witness データ

5.6 証明の生成と検証

# 証明を生成(検証鍵も同時に生成)
bb prove -b ./target/hello_world.json -w ./target/hello_world.gz --write_vk -o ./target

# 証明を検証
bb verify -p ./target/proof -k ./target/vk

エラーが出なければ検証成功です。

5.7 Solidity Verifier の生成

オンチェーンで証明を検証するための Solidity コントラクトを生成します。

# 検証鍵を生成(Solidity 用に keccak ハッシュを使用)
bb write_vk -b ./target/hello_world.json -o ./target --oracle_hash keccak

# Solidity Verifier を生成
bb write_solidity_verifier -k ./target/vk -o ./Verifier.sol

重要: Solidity Verifier を使う場合、証明生成時にも --oracle_hash keccak フラグが必須です。

# 証明を生成(keccak フラグを指定)
bb prove -b ./target/hello_world.json -w ./target/hello_world.gz -o ./target --oracle_hash keccak

これにより以下が生成されます:

  • proof - 証明データ(バイナリ形式、約16KB)
  • public_inputs - 公開入力(バイナリ形式、32バイト × 公開入力数)

5.8 Foundry でのオンチェーン検証テスト

# プロジェクトディレクトリに戻る
cd ..

# Foundry プロジェクトを作成
mkdir onchain-verification && cd onchain-verification
forge init --force

# Verifier.sol をコピー
cp ../hello_world/Verifier.sol src/

test/Verifier.t.sol を作成:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {HonkVerifier} from "../src/Verifier.sol";

contract VerifierTest is Test {
    HonkVerifier verifier;

    function setUp() public {
        verifier = new HonkVerifier();
    }

    function testVerifyProof() public {
        // proof と public_inputs を読み込む
        bytes memory proof = vm.readFileBinary("../hello_world/target/proof");
        bytes memory publicInputs = vm.readFileBinary("../hello_world/target/public_inputs");
        
        // public_inputs を bytes32[] に変換
        bytes32[] memory inputs = new bytes32[](publicInputs.length / 32);
        for (uint i = 0; i < inputs.length; i++) {
            bytes32 input;
            assembly {
                input := mload(add(add(publicInputs, 0x20), mul(i, 0x20)))
            }
            inputs[i] = input;
        }
        
        // 証明を検証
        bool verified = verifier.verify(proof, inputs);
        assertTrue(verified, "Proof verification failed");
    }
}

foundry.toml にファイルアクセス許可を追加:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# ファイルシステムアクセス許可(親ディレクトリのproofファイルにアクセスするため)
fs_permissions = [
    { access = "read", path = "../hello_world/target" }
]

テストを実行:

forge test

テストが通れば、オンチェーン検証の準備完了です。


6. よくあるエラーと対処法

エラー 原因 対処法
nargo: command not found パスが通っていない export PATH="$HOME/.noirup/bin:$PATH" を追加
bb: command not found パスが通っていない export PATH="$HOME/.bb:$PATH" を追加
ProofLengthWrongWithLogN 証明生成時のフラグ不一致 --oracle_hash keccak を追加
vm.readFileBinary: path not allowed Foundry のアクセス権限 foundry.toml に fs_permissions を追加

まとめ

Noir は従来の Circom と比較して、以下の点で優れています。

観点 Circom Noir
開発体験 独自DSL、学習コスト高 Rust ライク、直感的
Trusted Setup 必要(Groth16) 不要(Barretenberg)
コード量 冗長になりがち シンプル
エコシステム 成熟 急成長中

Groth16 の証明サイズ・検証コストの優位性は依然として大きく(検証ガス:約23万 vs 約230万)、L1 でのコスト重視なら Circom + Groth16 が有利です。一方、Trusted Setup のリスク回避、開発速度、L2 での利用を重視する場合は Noir が選ばれています。


サンプルリポジトリ

本記事のコードは以下のリポジトリで公開しています。

https://github.com/naizo01/noir-sample


参考資料


おわりに

最後までお読みいただきありがとうございます。
改めまして、naizo と申します。普段はブロックチェーン周りの開発をしています。

Noir のような開発者フレンドリーなツールの登場により、より多くのエンジニアが ZK アプリケーション開発に参入できるようになっています。

この記事が皆さんにとって、Noir を使った ZK 開発に挑戦するきっかけになれば幸いです。
質問やフィードバックなどありましたら、コメントや SNS などでお気軽にご連絡ください。

https://x.com/naizo_eth

https://zenn.dev/naizo01

Discussion