🔐

Googleのゼロ知識証明による年齢認証

に公開

先日、Google がゼロ知識証明(Zero-Knowledge Proof, ZKP)を用いてプライバシーを保護しながら年齢確認を行えるライブラリ「longfellow-zk」をオープンソースとして公開しました。

私はこの分野に全く詳しくないのですが、せっかくなので後学のためゼロ知識証明の概念を理解し、実際のコードやプロトコルにどう落とし込まれているのかを調査してみました。そのような状態なので、誤りを含む可能性がある点はご了承ください。また、このライブラリの具体的な使い方についても言及しません。

https://github.com/google/longfellow-zk

https://blog.google/technology/safety-security/opening-up-zero-knowledge-proof-technology-to-promote-privacy-in-age-assurance/

はじめに

ゼロ知識証明の概念は、「洞窟」の比喩で理解した気になっています。秘密の呪文を知っている証明者が、その呪文を教えることなく、秘密の扉を開けられるという事実だけを検証者に納得させる話です。個人的にこの比喩は、ゼロ知識証明の仕組みの概要を理解するうえでは非常に良いと思っています。

https://ja.wikipedia.org/wiki/ゼロ知識証明

https://tech.gmogshd.com/zkp1/

しかし、概要を理解したとしても「その魔法の洞窟は現実のデジタル社会でどのように構築するのか?」という疑問が残ります。「どのように」目的を達成するのか、その実装の詳細はまったく理解していません。

本記事では Google が公開した longfellow-zk を題材に、抽象的な概念が具体的なコードやプロトコルにどう落とし込まれ、現実世界の問題解決にどう貢献するのかを広く薄く調査してみました。

用語解説

本記事で扱う主要な専門用語は以下の通りです。

用語 説明
ZKP (Zero-Knowledge Proof) ゼロ知識証明の略。ある事実を知っていることを、その事実に関する具体的な情報を一切明かすことなく証明する技術。
証明者 (Prover) ある事実を証明したい側。
検証者 (Verifier) 証明が正しいかを確認する側。
TTP (Trusted Third Party) 信頼できる第三者機関。証明の根拠となる情報(デジタル身分証など)を発行する信頼された機関。例:政府、自治体。
コミットメント ある情報を、後から変更できないように「封印」する暗号技術。
範囲証明 (Range Proof) ある数値が、特定の範囲内にあることを、その数値を明かさずに証明すること。
非対話型証明 証明者と検証者の間で情報のやり取り(対話)を必要とせず、証明者が一方的に証明データを送るだけで完結する証明方式。
ECDSA (Elliptic Curve Digital Signature Algorithm) 楕円曲線デジタル署名アルゴリズム。少ない鍵長で高い安全性を実現するデジタル署名方式の一つ。
mdoc (mobile driving licence) モバイル運転免許証の国際標準規格(ISO/IEC 18013-5)で定義されるデータ形式。
JPKI (Japanese Public Key Infrastructure) 公的個人認証サービス。マイナンバーカードに搭載される電子証明書を用いて、オンラインでの本人確認を行うための社会基盤。

ゼロ知識証明による年齢認証

そもそも、なぜこのタイミングでゼロ知識証明による年齢認証ライブラリが出たのかと言うと、どうやら EU でのプライバシー保護に関する規制の動きが影響しているようです。EU では2026年に、個人情報を開示せずに年齢だけを証明できる「ゼロ知識証明」技術を用いた年齢確認手法を推奨する規則が発行される予定であり、このようなプライバシー保護の重要性や規制の動向が、技術開発を後押しされたようです。

https://gigazine.net/news/20250704-google-zero-knowledge-proof/

オンラインサービスで商品を購入したり、特定のコンテンツにアクセスしたりする際、年齢確認を求められる場面は少なくありません。従来、こうした年齢確認は運転免許証やパスポートといった身分証明書の画像をアップロードする方法が一般的でした。

しかし、この方法ではユーザーの生年月日、氏名、住所といった機微な個人情報をサービス提供者に渡す必要があり、情報漏洩のリスクが常につきまといます。ゼロ知識証明は、この課題に対する有力な解決策の1つです。これにより、プライバシーを最大限に保護しつつ、年齢確認という目的を達成できます。

longfellow-zkのコア技術

longfellow-zk による年齢証明は使われていると思われる技術。

コミットメント

コミットメントは、ある情報を相手に隠したまま、その情報を使うことを「約束」するための技術とのこと。

  • コミットメント: 証明者は、秘密の情報(例:生年月日)を、誰にも見えない形で「封印」する。この封印されたデータが「コミットメント」である。
  • オープン: 後で、証明者はその封印を解くことで、「私が最初に封印したのは、確かにこの情報である」と証明できる。

longfellow-zk の文脈で用いられる Pedersen Commitment は、C = g^v * h^r という数式で表されるようです。

https://zenn.dev/joeee/books/6f600be4fd9623/viewer/1a1456

  • v が秘密の情報(生年月日など)。
  • r は、証明者だけが知っているランダムな数字(ブラインディングファクター)で、コミットメントを隠す。
  • gh は、全員が知っている公開された定数である。

この方式の利点は、コミットメント C から元の情報 v を割り出すことが計算上ほぼ不可能なため(離散対数問題の困難性)、秘匿性が高くなります。また、一度 C を公開すると、証明者は後から別の情報 v'に対応するとしてコミットメントを開くことができないため、拘束性(約束を破れない性質)も保証されます。

https://crypto.stackexchange.com/questions/64437/what-is-a-pedersen-commitment

対話型証明と非対話型証明

多くのゼロ知識証明プロトコルは、その理論的な基礎を対話型(Interactive)に置いています。これは、証明の安全性を確率論的に積み上げる、直感的で分かりやすいモデルに思えます。

  • 対話型証明: 証明者と検証者が、キャッチボールのように情報のやり取りを繰り返す。検証者はランダムな質問(チャレンジ)を投げかけ、証明者はその質問に対して、秘密の情報を知っていれば正しく応答できる。
  • 健全性の向上: 1回の対話だけでは、不正な証明者が偶然正解してしまう可能性がある(例えば、1/2の確率でごまかせるなど)。しかし、この対話を k 回繰り返すと、不正な証明者が全ての質問をごまかし通せる確率は(1/2)^k と指数関数的に減少する。例えば100回繰り返せば、その確率は2^{-100}となり、天文学的に低くなる。これにより、検証者は「これだけ連続で正解できるのだから、彼は本当に秘密を知っているに違いない」と高い確信を得る。

この対話型モデルは強力ですが、実世界の多くのアプリケーションには不向きな場合があります。例えば、ブロックチェインへの証明の記録や、非同期のメッセージングでの証明送信など、検証者がリアルタイムに応答できない状況は頻繁にあるからです。毎回対話が必要だと、利便性が著しく損なわれます。

Fiat-Shamir変換

この対話のプロセスを証明者側で完結させ、一方的に証明を送りつけられるようにする非対話型(Non-Interactive)の仕組みが求められ、その実現手段の1つが Fiat-Shamir 変換であるようです。(非対話型にする手法を Fiat-Shamir 変換と呼んでいるのか、Fiat-Shamir 変換と呼ばれるゼロ知識証明法があるのかは、いまいちわからなかった。多分前者だと理解している。)

https://ja.wikipedia.org/wiki/Fiat-Shamirヒューリスティック

https://techmedia-think.hatenablog.com/entry/2022/04/19/193400

Fiat-Shamir変換のアルゴリズム概要

アルゴリズムの流れを Gemini くんに聞いてみた。

  • 前提
    • 証明者 (Prover): 証明したい秘密の情報を持つ。
    • 検証者 (Verifier): 証明を受け取る側。
    • 公開パラメータ: 証明者と検証者の両方が、使用するハッシュ関数(例: SHA-256)について事前に合意している。
  • 証明生成プロセス(証明者側の内部処理)
    1. 最初のメッセージ(コミットメント)の計算: 証明者は、自身の秘密情報に基づき、プロトコルの最初の公開メッセージ m1 を計算し、内部の「トランスクリプト(やり取りの記録)」に追加する。
    2. 最初のチャレンジの自己生成: 証明者は、検証者を待たずに、c1 = Hash(m1) を計算する。トランスクリプト全体をハッシュ関数に入力し、その出力を検証者からのランダムなチャレンジ c1 とみなす。
    3. 応答の計算: 証明者は、秘密情報と自己生成したチャレンジ c1 を使い、プロトコルが要求する次のメッセージ m2 を計算してトランスクリプトに追加する。
    4. プロセスの繰り返し: プロトコルが必要とするラウンド数だけ、ステップ2と3を繰り返す。例えば、次のチャレンジ c2c2 = Hash(m1, m2) として計算し、それに対する応答 m3 を計算する。
    5. 証明の完成と送信: 全てのラウンドが完了したら、証明者は最終的なトランスクリプト、すなわち一連のメッセージの組 Proof = (m1, m2, m3, ...) を構築し、検証者へ単一のデータとして送信する。
  • 検証プロセス(検証者側の処理)
    1. 証明の受信: 検証者は証明者から Proof = (m1, m2, m3, ...) を受け取る。
    2. チャレンジの再計算: 検証者は、証明者と同じハッシュ関数を使い、受け取ったメッセージからチャレンジを再計算する (c1' = Hash(m1) など)。
    3. 応答の検証: 検証者は、再計算した各チャレンジに対し、証明に含まれる応答がプロトコルのルール上、有効であるかをチェックする。
    4. 判定: 全てのチェックをパスすれば、証明を有効と判定する。

...なるほど、よくわからん😇

ただ、いわゆるランダムオラクル (=ハッシュ関数) 前提として、本来対話的に送られてくるはずのチャレンジを証明者が自分で計算してしまうことで、検証者との対話を必要としない非対話型の証明に変化する手法だという雰囲気は理解できました。

範囲証明

「18歳以上である」と証明するには、「年齢が18以上の範囲にある」ことを示す範囲証明が必要です。

その基本的なアイデアは、「ある数が特定の範囲内にあること」を「その数を2進数のビット列で表現できること」に置き換えて証明する点にあります。

例えば、「私の年齢 v[0, 127]の範囲にある」を証明したいとします。これは、v が7つのビット b_0, b_1, ..., b_6で表現できることと同じ意味です。これをゼロ知識で証明するには、以下の2つの条件が満たされていることを、具体的な v やビットの値を明かすことなく示します。

  1. 各要素がビットであること: 各 b_i は全て0か1のどちらかである。これは、b_i * (1 - b_i) = 0 という数式が全ての i について成り立つことで証明できる。
  2. ビットの合計が元の値と一致すること: 各ビットを重み付けして合計すると、元の値 v になる。数式では v = b_0*2^0 + b_1*2^1 + ... + b_6*2^6 である。

要は18以上を示すのに具体的な年齢は不要で、ビット列の形で「18以上である」という条件を満たすことを示せば良く、その証明のコミットメントの作成のために Pedersen Commitment を利用するということのようですね。

全体の流れ

このライブラリを利用した年齢確認の全体的な流れは以下のようになるようです。

情報の流れ

情報の流れを以下に整理しました。

登場人物 知っている具体的な秘密情報 知ることのない情報 信頼の基点
あなた 自身の生年月日、氏名、住所など全ての個人情報。 - 自分自身の知識。
第三者機関 (TTP) あなたの本人確認を通じて得た、生年月日などの個人情報。 あなたのウォレットアプリの秘密鍵。 公的な権威と、厳格な本人確認プロセス。
あなたのウォレット TTPから発行されたデジタル身分証(mdoc)に含まれる具体的な生年月日。 オンラインストアの内部情報。 スマートフォンのセキュアな領域に保管されていること。
オンラインストア - あなたの具体的な生年月日。 TTPの発行した署名と、ゼロ知識証明の数学的な正しさ。

今更ですが、「ゼロ知識」とは検証者(オンラインストア)に対して知識がゼロであるという意味であり、証明を生成するあなたのデバイス内では、信頼できる情報源から取得した具体的な個人情報が安全に利用されます。

オンラインストアでの年齢確認プロセス

オンラインストアでお酒を購入するシナリオを例に、具体的なステップを見ていきます。

登場人物

  • あなた(証明者): スマートフォンを持っているユーザー。
  • 信頼できる第三者機関(TTP): 政府や自治体など、あなたの身元を保証できる機関。
  • オンラインストア(検証者): 年齢確認が必要なサービス。

ステップ1:証明のもととなる情報を準備する

まず、年齢を証明するためのもととなる情報を TTP から取得し、スマートフォンに保存しておく必要があります。

  1. TTP への認証
    • TTP が提供するデジタル身分証アプリ(例:マイナンバーカード連携アプリ)を使い、自身の本人確認をする。
  2. コミットメントの生成
    • 本人確認が完了すると、TTP はあなたの生年月日データを直接保存するのではなく、「Pedersen Commitment」を用いて情報を秘匿化したデータを生成する。
    • これは、コミットメント C = g^{生年月日} * h^{乱数} のような数式で表されるデータである。中身(生年月日)は見えないが、後から「確かに私の生年月日は XX だ」と証明できる仕組みである。
    • このコミットメントは、あなたの生年月日を誰にも知られることなく、その存在を数学的に「約束」する仕組みである。
  3. スマートフォンへの保存
    • TTP は、生成したコミットメントに自身のデジタル署名を付与する。これは「このコミットメントは確かに当機関が発行した正式なものである」という証明となる。
    • この「TTP の署名付きコミットメント」が、あなたのスマートフォンの安全な領域(セキュア要素など)に保存される。

この段階であなたのスマートフォンに保存されているのは、暗号化されたコミットメントであり、生年月日そのものではありません。

ステップ2:オンラインストアで「証明」を生成する

次にお酒を購入するためオンラインストアにアクセスし、年齢確認を求められた際の動きです。

  1. 証明要求
    • オンラインストアが「18歳以上であることの証明」を要求する。
  2. ゼロ知識証明の生成
    • スマートフォンアプリは、リクエストに応じてデバイス上でゼロ知識証明の生成を開始する。
    • アプリは、ステップ1で保存したコミットメントと、今日の日付の情報を入力として使う。
    • これらの情報から、「コミットメントに含まれる生年月日から計算した年齢が、18歳以上である」という事実を証明する、範囲証明を構築する。
    • このプロセス全体、つまり証明者(あなたのウォレット)が検証者(ストア)と一切やり取りすることなく、デバイス内部で完結して証明を生成する部分が、Fiat-Shamir 変換によって非対話化されている箇所である。

生成された証明データには、あなたの具体的な生年月日や年齢といった個人情報は含まれず、「ある条件(18歳以上)を満たしている」という数学的な証明だけが含まれます。

ステップ3:ストアによる「検証」

最後に、生成された証明をオンラインストアが検証します。

  1. 証明の送信
    • あなたのスマートフォンは、生成したゼロ知識証明のデータをオンラインストアのサーバーに送信する。
  2. 検証プロセス
    • オンラインストアのサーバーは、受け取った証明データに対して2つの検証をする。
      • TTP の署名検証: まず、証明に付帯する TTP のデジタル署名が正当なものかを確認する。これにより、その証明が信頼できる機関によって発行されたコミットメントに基づいていると確認できる。
      • ゼロ知識証明の検証: 次に、ゼロ知識証明そのものが数学的に正しいかを検証する。
  3. 認証完了
    • 両方の検証に成功すれば、オンラインストアは「このユーザーは信頼できる機関が保証した情報に基づき、18歳以上であることが数学的に証明された」と判断し、購入プロセスを許可する。

この一連の流れにおいて、オンラインストアはあなたの生年月日や年齢を知ることなく、目的であった「年齢確認」を完了できました。

まとめ

Google の longfellow-zk は、範囲証明の考え方と、Fiat-Shamir 変換による非対話化を組み合わせることで、プライバシーを保護しながら実用的な年齢認証を実現する仕組みを提供しているようです。

最近は、パスキーなどのパスワードレス認証や生体認証の普及が進んでいますが、年齢確認のような特定の条件を満たすことを証明するための技術も、今後ますます重要になってくるでしょう。

とある通信会社の有志

Discussion