😺

ZKPのAppの開発の流れがわかる:Minaでスクラッチ実装

2025/02/01に公開

はじめに

Minaプロトコルは、ゼロ知識証明(Zero Knowledge Proofs)を活用したブロックチェーンプロトコルです。このプロトコル上でzkApps(ゼロ知識アプリケーション)を開発することで、プライバシーを保護しながら計算の正当性を証明することができます。
Minaではスマートコントラクトとしてコードがネットワークにはありません。あるのは検証鍵とStateのみ。そしてトランザクションの実行自体はローカルでして、その実行の証明までローカルで生成して... という新しい感覚でMinaプロトコルの挙動などが学べるのでぜひ読み進めていってください。

動画で学びたい方はこちら

https://www.youtube.com/watch?v=Pb9nB0W8rQE

ゼロ知識証明とは

ゼロ知識証明についてここでは簡単な説明にとどめますが、証明者が「ある事柄を知っていること」を検証者にその事実以外の知識を何も与えることなく証明する技術です。これをコンピューティングに置き換えると、特定のコードを正しく実行したことを証明できますが、その際にプライベートデータや入力を開示する必要はありません。

開発環境のセットアップ

Githubコード
https://github.com/Mameta29/learning-mina

zkApps開発には以下のツールが必要です:

  1. Node.js (nodejs.orgからダウンロード)
  2. Visual Studio Code (コードエディタ)
  3. zkApp CLI (Node Package Manager経由でインストール)

インストール手順

# Node.jsのバージョン確認。なければインストールしてください。
node --version

# zkApp CLIのインストール
npm install -g zkapp-cli

# インストール確認
zk --version

# システム情報の確認
zk system

プロジェクトの作成とデプロイ

プロジェクトの作成

zk project learning  # "learning"は任意のプロジェクト名

このコマンドにより、以下のような構造のプロジェクトが作成されます:

  • src/
    • Add.ts - スマートコントラクトで
    • interact.ts - コントラクトとの対話用スクリプト

テストネットの設定例

zk config

# 設定項目
✔ Deploy Alias Name: devnet
✔ Target Network Kind: Testnet
✔ Mina GraphQL API URL: https://api.minascan.io/node/devnet/v1/graphql
✔ Transaction Fee: 0.1
✔ Choose an account to pay transaction fees: · Create a new fee payer key pair
  NOTE: The private key is created on this computer and is stored in plain text.
✔ Create an alias for this account · deploy-account

上記のように設定すると下記のように出力されますので、下記のURLからfaucetをゲット(Requestボタン)しましょう!

Success!

Next steps:
  - If this is the testnet, request tMINA at:
    https://faucet.minaprotocol.com/?address=B62qiUNhFxbhdqG1pdWF5hHGcTkS346sBo74dpxn7de5EdX6BQPeu7R&explorer=minascan
  - To deploy zkApp, run: `zk deploy devnet`

デプロイ

zk deploy devnet

このようにデプロイできました!テーブルで表示されているのは前段階のzk configでもろもろ設定した値です。

デプロイトランザクション(出力から作成されるURLで確認できます)
https://minascan.io/devnet/tx/5Jv48bEYMUHTssrGr2LuChFrtrLxwZsn3EFEw9JQUxfmSdUdFqCr?type=zk-tx

Minaでデプロイされるもの

スキャンにてトランザクションをスクロールして見ていくと下記のように検証鍵(Verification Key)があることがわかります。これがあることでzkAppsであることがわかります。つまりこのデプロイしたスマートコントラクトアカウントが検証者としてMinaネットワークにデプロイされたということです。

興味深いのはMinaでデプロイされるのはコードではありません。コードから生成された検証鍵ということになります。(検証鍵の上部にあるStateもデプロイ対象です。)

スマートコントラクトとのやりとり

それではデプロイしたスマートコントラクトやりとりしていきたいと思います。現在の既に存在している src/interact.tsでも動くのですが、学習用にわかりやすく書いていきます。一度全て消して下記のように書き換えます。

import { Mina, PublicKey, fetchAccount} from 'o1js';
import { Add } from './Add.js';

// ネットワーク接続設定
const Network = Mina.Network("https://api.minascan.io/node/devnet/v1/graphql");

// ネットワーク設定をアクティブにする
Mina.setActiveInstance(Network);

// zkAppsのスマートコントラクトアドレスを読み込む
const appKey = PublicKey.fromBase58("B62qm3CZ7rgfFKjHdB3P7oX7NxByGsNgsvUxMrkGLwMHUsCU6eE2qry");

// コントラクトのインスタンスを生成
const zkApp = new Add(appKey)
// アカウントの現在のstateを取得 -> これしないとstate読み取りができない
await fetchAccount({publicKey: appKey})
console.log("zkApp count", zkApp.num.get().toString())

このように設定すると下記コマンドで実行&出力が確認できます。

mameta learning %npm run build && node build/src/interact.js

> learning@0.1.0 build
> tsc

zkApp count 1

Minaのスマートコントラクトとやりとりすることができました!

Proofを生成してStateを更新する

それでは遂にProofを生成して初期値が1になっていたStateを更新しましょう!

Add.tsのupdateメソッドを使用して状態の更新をします。2を加算しているのがわかるので結果は3になっていたら嬉しいです。

  @method async update() {
    const currentState = this.num.getAndRequireEquals();
    const newState = currentState.add(2);
    this.num.set(newState);
  }

interact.tsで追加するコード。ただ accountPrivateKey については generateKey.tsというファイルを生成してそこでランダムに作成した秘密鍵なので、作成方法などは場合はgithubを参照ください。

import { PrivateKey } from 'o1js';

// アカウントの秘密鍵をBase58形式の文字列から生成
const accountPrivateKey = PrivateKey.fromBase58("EKEmBeQKgn4yzMyzZsTnu9jDbzAjpeAU8L5Zy7sbSbpAPEj133iH")

// 秘密鍵から対応する公開鍵を生成
const accountPublicKey = accountPrivateKey.toPublicKey()

console.log("compiling...")
// スマートコントラクトをコンパイルし、証明キーを生成
await Add.compile()

// トランザクションを作成。sender(送信者)とfee(手数料)を指定し、zkApp.update()を実行
const tx = await Mina.transaction(
  { sender: accountPublicKey, fee: 0.1e9 }, // 0.1 MINA = 100,000,000 ナノMINA
  async () => {
    await zkApp.update() // コントラクトのupdate関数を呼び出し
  }
);

console.log("proving...")
// トランザクションの正当性を証明するゼロ知識証明を生成
await tx.prove()

console.log("signing...")
// トランザクションに署名し、ネットワークに送信
const sentTx = await tx.sign([accountPrivateKey]).send()

// 送信されたトランザクションのハッシュを出力
console.log("sent tx", sentTx.hash)

これを実行すると下記のように出力がでて、スキャン上では無事stateが変更できたことが確認できました!

mameta learning %npm run build && node build/src/interact.js   

> learning@0.1.0 build
> tsc

zkApp count 1
compiling...
proving...
sent tx 5JtYrgjvti7XXuvYF4orcWEUXcaGnk1BiwK1tjjRRrZUdfLbDwE7

トランザクション
https://minascan.io/devnet/tx/5JtYrgjvti7XXuvYF4orcWEUXcaGnk1BiwK1tjjRRrZUdfLbDwE7?type=zk-tx
スキャン(2回実行後にキャプチャ取ったので 3 ではなく 5になってしまっています。)

結局のところMinaってどんな仕組み?

ここまででMinaの仕組みをまとめます。まず、Minaに限らずZKPで登場するモジュールは大きく分けて検証者と証明者です。AddコントラクトをMinaテストネットワークにデプロイしましたが、その際にネットワークにデプロイされたのはAddコントラクトのStateとコントラクトから生成された検証鍵です。つまり検証者になります。
次にinteract.tsを実行しました。これはトランザクション作成・実行し、証明とともにネットワークに送りました。つまりinteract.tsは完全にローカルで実行されたのです。ローカルで実行し、その実行結果の証明を生成しネットワークに送信し、最終的に検証鍵でtureと判断できればStateを変更する、というMinaの一連の流れが掴めたかと思います。

  1. zkApps (スマートコントラクトであり検証者)

    • ネットワーク上にデプロイされる
    • 実行の正当性を検証する
    • 検証キーを保持
  2. 証明者(Prover)

    • ローカルで動作
    • ゼロ知識証明を生成
    • コントラクトとの対話を行う

まとめ

CircomなどZKPドメイン言語やzkSNARKsなど意識することなくTypescriptでかけちゃうのは、いいですね。ただ簡単な実行しかまだ触ってないのでなんとも言えませんが。ブロックチェーンを使ったZKPアプリの挙動ってどんな感じなんだろということだけでも学べると思うのでぜひMina触ってみてください。

Discussion