Skip to main content

原文準拠 Phase 2:ゼロ知識証明

3.1 取引のライフサイクルと証明生成

Midnight Academy Phase 2 / Unit 3 / 3.1 の原文準拠版。取引が「準備→検証→確定」の3段階を進む様子と、証明サーバーでの証明生成のしくみを、正確に・やさしく。


📘 Academy原文準拠 | Phase 2 · Unit 3 · Lesson 3.1 Transaction Lifecycle and Proof Generation 内容に忠実な日本語版です。原文(英語)・図・動画は公式 Academy(外部リンク・別タブで開きます)を正本に。

ここまでは、Midnight のしくみを部品ごとにバラバラに見てきました。このレッスンでは、それらが実際にどうつながって動くのかを1本の流れとして見ていきます。

DApp で「送信」を押した瞬間、裏では何が起きているのでしょう。これまで学んだ部品が、ここで全部つながります。

3つのフェーズ

取引が「1. 準備(トランザクションを構築→ZK証明を生成)」「2. 検証(ネットワークに送信→証明を検証→ブロックに含める)」「3. 確認(状態変更を実行→ウォレットが同期)」の3フェーズを左から右へ進むフロー図

Midnight のすべての取引は、大きく3つのフェーズを通ります。

  1. 準備(Preparation):あなたのアプリが取引を組み立て、証明を生成する
  2. 検証(Validation):ネットワークがすべてをチェックし、ブロックに取り込む
  3. 確定(Confirmation):状態が更新され、ウォレットが結果を同期する

🖼️ 原文の図:取引が「準備 → 検証 → 確定」の3フェーズを左から右へ進んでいく流れ図。図そのものは公式 Academy(外部リンク・別タブで開きます) を参照。

ひとつずつ歩いて見ていきましょう。

フェーズ1:取引を組み立てる

ユーザー・DApp・ウォレット・証明サーバーの4者のシーケンス図。操作を開始し、コントラクトをローカルで実行、コントラクト証明を生成してZK証明を返し、トランザクションの残高計算と証明、支出証明を生成してZK証明書を返し、証明済みトランザクションを経て送信準備完了に至る流れ

DApp を使ってトークンを送りたい、としましょう。裏側では次のことが起きています。

まず DApp は、契約の遷移関数(transition function)をローカルで・オフチェーンに実行します。ここが大事なところです。契約コードはブロックチェーン上ではなく、あなたの端末で実行されます。この実行の間、システムは次の2つを記録します。

  • public transcript(公開トランスクリプト):オンチェーンに見えることになる部分(オラクル呼び出し、公開状態の変更)
  • private transcript(プライベートトランスクリプト):秘密の部分(あなたの private input、witness データ)

実際の状態変更はロールバック(巻き戻し)されます。まだ何も永続化されません。DApp は、トランスクリプトと、証明に必要なデータだけを手元に持っておきます。

次に来るのが、いちばん重い処理――証明生成(proof generation)です。

なぜ証明生成が必要なのか

Lesson 17 で学んだとおり、Kachina のおかげで、秘密データそのものを明かさずに「自分の private state が、この public state の変更を正当化している」と証明できます。それが実際に起きるのが、まさにここです。

DApp は、circuit(コンパイル済みの契約ロジック)witness(あなたの秘密データ)Proof Server(証明サーバー)に送ります。これは多くの場合あなたの端末でローカルに動いている、別プロセスで、重い暗号計算を一手に引き受けます。

なぜ別サーバーなのか

ZK 証明の生成は計算がとても重い処理です。ミリ秒ではなく、数十秒かかる世界です。これをブラウザの中で動かすと、つらいほど遅くなります。でも証明サーバーでネイティブコードとして動かせば、実用的な速さになります。

証明サーバーは数字を計算しきって、あなたの契約呼び出しに対する ZK 証明を返してきます。

取引のバランス調整(Balancing)

提出する前に、もうひとつ段階があります。取引は “balanced(バランスが取れている)” でなければなりません。つまり、入力(input)と出力(output)が正しく合計で釣り合い、かつ手数料を支払っている必要があります。

ここで登場するのが、あなたのウォレットです。DApp はウォレットを呼び出して、支出(spend)側を任せます。ウォレットがやること:

  • 入力に使うコイン(UTXO)を選ぶ
  • おつり用の出力(output)を追加する
  • DUST の手数料を計算してまかなう
  • 支出操作のための ZK 証明を生成する

ウォレットはあなたのコインを追跡し、どれが未使用かを把握し、それらのコインが存在することを示す Merkle 証明(Merkle proof)を計算できます。UTXO の帳簿づけを全部やってくれるので、あなたが考える必要はありません。

🖼️ 原文の図:DApp・ウォレット・Proof Server がやり取りして、証明つきの取引を組み立てていく様子。図そのものは公式 Academy(外部リンク・別タブで開きます) を参照。

この時点で、あなたの手元には完全に組み立てられ、証明済みの取引ができています。すべての ZK 証明が添付された状態です。

フェーズ2:ネットワークによる検証

取引を提出すると、まず transaction pool(トランザクションプール)に届きます。ノードは well-formedness(正しい形をしているか)のチェックを行います。

  • すべての ZK 証明がそろっていて、有効か?
  • 入力と出力は正しく釣り合っているか?
  • 手数料の計算は正しいか?
  • 構造は正しいか?

このチェックは state-independent(状態に依存しない)ものです。ノードはまだ何も実行しておらず、取引がきちんとした形になっているかを確かめているだけです。

すべて問題なければ、取引はブロックへの取り込みを待ちます。

ブロックへの取り込みと実行

block producer(ブロック生成者)があなたの取引を拾うと、本当の検証が始まります。埋め込まれた ZK 証明が、現在の台帳(ledger)状態に対してフルに検証されます。

shielded(秘匿)取引では、ノードは次をチェックします。

  • spend proof:使おうとしているコインを、あなたが本当に所有していることの証明
  • contract call proof:あなたの実行が正当だったことの証明
  • nullifier以前に使われていないこと(二重支払い=double-spend の防止)

unshielded(非秘匿)取引では、詳細が見えているのでもっとシンプルです。

すべての証明が検証できたら、状態遷移(state transition)が実行されます。

  • Zswap(トークン移転)の場合:coin-commitment の Merkle ツリーが更新され、nullifier が記録され、新しいコインが作られる
  • contract 操作の場合:証明された実行に従って契約の状態が更新される

guaranteed フェーズ と fallible フェーズ

ここに Midnight ならではの特徴があります。実は各取引には、2つの実行フェーズがあります。

  1. guaranteed フェーズ(保証フェーズ):手数料の支払いや、必ず成功しなければならない操作を扱う。ここが失敗すると、取引はそもそもブロックに取り込まれません
  2. fallible フェーズ(失敗しうるフェーズ):契約ロジック次第で失敗するかもしれない操作を扱う。このフェーズが失敗しても、guaranteed フェーズの効果は残ります。つまり手数料はすでに払っており、それは返ってきません

この設計は、よくある攻撃――わざと失敗すると分かっている取引を提出してネットワークの資源を浪費させる――を防ぎます。Midnight では、取引の一部が失敗したとしても、使った分は支払うのです。

フェーズ3:確定の後で

トランザクション確定からインデクサーが検知し、「ウォレットが更新(コイン集合を更新・残高を再計算)」と「DAppが更新(公開状態を取得・非公開トランスクリプトを取得・ユーザーインターフェースを更新)」へ枝分かれするツリー図

取引が検証され、ブロックに取り込まれると、いくつかのことが起こります。

indexer(インデクサー:チェーンを監視するサービス)が、確定した取引を見つけます。あなたのウォレットは indexer を購読(subscribe)しており、自分のアドレスに関係する取引について通知を受け取ります。

そしてウォレットは次を行います。

  • 自分の coin set を更新する(使ったコインを「消えた」と印付けし、新しいコインを追加する)
  • 追跡している Merkle ツリーのデータを更新する
  • あなたの残高を計算し直す

もし DApp を使っていたなら、DApp は公開状態についての見え方をリフレッシュし、先ほど保存しておいた private transcript を取り出します。これで、オンチェーンの公開結果と、ローカルにとどまった private データの両方を含む、完全な結果をあなたに見せられます。

🖼️ 原文の図:確定後に indexer → ウォレット → DApp へと通知が伝わり、コインセット・Merkle ツリー・残高が更新される様子。図そのものは公式 Academy(外部リンク・別タブで開きます) を参照。

証明サーバー(Proof Server)をもう少し

証明サーバーはすべての中心なので、もう少し詳しく見ましょう。

Midnight の開発環境をインストールしたり、ウォレットを動かしたりするとき、ふつうはローカルで証明サーバーを動かします。これは JavaScript ではなくネイティブのバイナリです。証明生成には生の性能(raw performance)が必要だからです。

証明サーバーは2種類の証明を扱います。

  • contract call proof:あなたの契約実行が正当だったことの証明
  • spend proof:使おうとしているコインを所有していることの証明

どちらも同じパターンに従います。circuit(何を証明すべきか)witness(それを真にする秘密データ)を送ると、サーバーは誰でも素早く検証できる簡潔な証明(succinct proof)を返します。生成にはかなりの計算がかかったとしても、検証は速いのです。

この非対称性(asymmetry)が鍵です。証明(proving)は重い。検証(verification)は軽い。

これこそがシステム全体を実用的にしています。重い作業は、取引を作るときに一度だけやる。ほかのみんなは、ただ速い検証をするだけでいいのです。

Ethereum との違い

Ethereum を使ったことがあるなら、別のモデルに慣れているはずです。

Ethereum では:

  • 署名つきの取引を提出する
  • すべてのノードが契約コードを再実行する
  • 結果がオンチェーンに保存される

Midnight では:

  • 契約をローカルで実行する
  • その実行が正しかったという証明を生成する
  • (実行ではなく)証明を提出する
  • ノードは(遅い再実行ではなく)証明を検証する(速い)

これが、Midnight が private state を扱える理由です。実際の計算は、あなたの秘密データを使ってあなたの端末で起きます。オンチェーンに乗るのは証明だけで、その証明は private input について何も明かしません。

取引のタイプ

すべての取引がスマートコントラクトを伴うわけではありません。ただトークンを送りたいだけのこともあります。ウォレット API はこれを簡単にしてくれます。

  1. Transfer transaction(送金取引):宛先・金額・トークン種別を指定する。ウォレットが適切な UTXO を選び、おつりを計算して取引を組み立てる。
  2. Balance transaction(バランス取引):DApp とやり取りする場合、DApp が取引の一部(contract call)を作り、あなたのウォレットが、手数料や価値の流れをまかなう入力・出力を足してバランスを取る
  3. Prove transaction(証明取引):未証明の取引を受け取り、すべての ZK 証明を生成する。
  4. Submit transaction(提出取引):証明済みの取引をネットワークに送る。

実際には、ウォレットがこれらの手順をまとめて行うことがよくあります。balanceAndProveTransaction 関数が、バランス調整と証明を1回の呼び出しで扱ってくれます。ほとんどの DApp はこれを使います。

ぜんぶをつないでみる

完全な例を1つ追ってみましょう。DAO でプライベートな投票を投じたい、とします。

  1. DApp で「Vote Yes(賛成に投票)」をクリックする
  2. DApp が、あなたの投票内容(private)で投票契約をローカルに実行する
  3. public transcript(投票が行われた)と private transcript(どちらに投票したか)を記録する
  4. circuit + witness を証明サーバーに送る
  5. 証明サーバーが、選択を明かさずに「正当に投票した」という証明を返す
  6. DApp がウォレットに取引のバランス調整を頼む(DUST の手数料を払う)
  7. ウォレットが、使う DUST のための spend proof を生成する
  8. 完全な、証明済みの取引が提出される
  9. ネットワークがすべての証明を検証し、ブロックに取り込む
  10. 投票数がオンチェーンで更新される(public state
  11. あなたの投票内容はあなたの端末にとどまる(private state
  12. ウォレットが同期し、取引が完了したと表示する

公開の記録が示すのは:「正当な投票が1票行われた」だけ。

秘密のまま残るのは:あなたが誰で、どちらに投票したか

これが、動いている Midnight の取引ライフサイクルです。

開発者として押さえる点

  • 取引は 準備 → 検証 → 確定 の3フェーズ。契約はローカル実行し、証明だけをチェーンに乗せる(Ethereum のように全ノードが再実行しない)
  • 実行時に public transcriptprivate transcript を記録し、状態変更は一旦ロールバック。証明生成は Proof Server(ローカルのネイティブバイナリ)が担う=数十秒かかる重い処理
  • ウォレットがバランス調整を担当:UTXO 選択・おつり・DUST 手数料spend proof 生成。実装では balanceAndProveTransaction が balancing と proving を1回でこなす
  • 検証では spend proof / contract call proof / nullifier 未使用(二重支払い防止)をチェック。確定後は Zswap の coin-commitment Merkle ツリーと nullifier が更新される
  • guaranteed フェーズ(手数料など必須)と fallible フェーズ(失敗しうる操作)。fallible が失敗しても guaranteed の効果と手数料は残る=資源浪費攻撃への対策
  • proving は重く、verification は軽いという非対称性が、システム全体を実用的にしている

やさしい版・公式へ

つぎに読むページ

➡️ 原文準拠コースの入口へ戻る。このコースについて(次のレッスンは順次追加します)