はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、匿名性を保ちながら各アカウントを一意に識別できる nullifier を作り出す署名の仕組みを提案しているERC7524についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。
概要
ZK-SNARK(ゼロ知識での正しさ証明を行う技術)は、匿名のまま「自分が特定の秘密情報を持っている」ことを示す新しいアイデンティティアプリケーションを生み出すきっかけとなりました。
特に、ある鍵を持つ人がただ一人であることを匿名のまま証明するためには、証明に使う署名が必ず同じ結果を返す「検証可能な決定的署名(verifiably deterministic signature)」が必要になります。
しかし、Ethereumが利用している署名方式はECDSA(楕円曲線デジタル署名アルゴリズム)で、署名を決定的に生成したとしてもそれが正しく決定的に生成されたかどうかを外部から検証する方法がありません。
さらに、ZK-SNARKで署名が正しく生成されたことを証明するには本来秘密鍵が必要ですが、多くのハードウェアウォレットは秘密鍵の取り出し(閲覧)すらできない設計です。
また、ハードウェアウォレット内でSNARK証明用の計算(算術化)を実行できるよう標準化することも現実的ではありません。
SNARKの方式自体が毎年のように刷新されるため、ウォレット側に証明システムを組み込むのは望ましくないという背景があります。
そのため、秘密鍵を開示せずに、ウォレットの外側でSNARK証明を生成できる新しい署名アルゴリズムの採用が必要になります。
目的は、署名から生成できる決定的な値を匿名識別子(nullifier)として利用し、個々のユーザーが一意であると証明しつつ、誰の鍵かは公開しない仕組みを作ることです。
具体例として、SNARK証明により以下を同時に満たすことが可能になります。
- 署名が正しく生成されたことを証明する
- 外部には署名そのものではなく、署名のハッシュ値だけを公開する
- 公開鍵が特定の集合に含まれることなど、追加の条件も同時に証明する
そして公開されるのは証明と署名ハッシュだけであり、この署名ハッシュが「nullifier(ヌリファイア)」として働きます。
nullifier は匿名アカウントを一意に表す公開データであり、二重実行の防止や匿名アクション同士の紐付けに利用できます。
このような特性を持つ決定的署名アルゴリズムを標準化することが、匿名性を保ちながら「一意なユーザー性」を保証する鍵となります。
動機
現在の多くのZKアプリケーションでは、同じウォレットが複数回証明を行うことに制限はありません。
これは、用途上それが自然なケースが多いためです。
しかし、匿名性を保ちながら「1ユーザーにつき1回だけ」の行動を保証したい場面が増えています。
特に、複数の偽アカウント(Sybil 攻撃)を防ぎたい場合には、一意性を保証する仕組みが必須です。
ところが、Ethereumの現状では、こうした仕組みを実現するためにアドレスを新しい特別なシステムへマッピングし、さらに秘密鍵をその新システムへ紐付ける必要がありました。
この方法は以下のような問題を抱えています。
- 追加の仕組みが必要になり複雑になる
- 処理の一体性(atomicity)が損なわれる
- Ethereumアドレスが持つ歴史や実績をそのまま活かせない
そのため、EthereumのECDSA鍵ペアから得られる決定的で一意な値が必要となります。
これにより、元のアドレスの匿名性を維持したまま、「その鍵を持つユーザーが1回だけ行動する」ことを保証できます。
この技術が必要となる具体的なアプリケーション
以下のような分野で特に重要になります。
| アプリケーション | 説明 |
|---|---|
| zk投票 | 各アカウントに対して1票だけを許可したい場合、匿名性を保ちながら重複投票を防ぐ必要があります。 |
| 匿名エアドロップ(Stealthdrop) | 複数回の請求を防ぎつつ、アドレスを特定されないようにしたいケースです。 |
| 匿名フォーラムのモデレーション | 匿名のまま「同一人物である」ことを証明し、行動ヘッダ(実績)を積み重ねたい場面です。 |
| zkプルーフによる資産証明 | 取引所が秘密鍵を持つアドレスの残高を証明する際、別の取引所が同じアドレスを主張する重複を防ぐ必要があります。 |
こうしたユースケースを実現するためには、Ethereumの鍵から生成される決定的な値を利用して、1ユーザー = 1アクションを保証する技術が不可欠です。
仕様
パラメータ
この署名方式は、Ethereumと同じ椭円曲線 secp256k1 を使います。
これは「Standards for Efficient Cryptography 2(SEC 2)」という標準文書で定義されている曲線です。
曲線には以下のような基本パラメータがあります。
- 曲線の生成点(base point / generator)
公開鍵を計算するときの基準となる点です。 - 曲線の位数(order)
スカラー(整数)で生成点を何回足し合わせると「元に戻るか」という周期に相当する値です。 - 有限体
曲線の計算が行われる「値の世界」で、secpp256k1では大きな素数を法とする有限体を使います。
ERC7524では、椭円曲線のスカラー倍(例: 「秘密鍵 × 生成点 = 公開鍵」)を、べき乗のような指数表記で書く、ということが明示されています。
公開鍵のエンコーディング(SEC1)
公開鍵の表現には、SEC1という別の標準で定義されているエンコーディング方式を使います。
ポイントは以下です。
- SEC1形式の「圧縮エンコーディング」を利用します。
-
secp256k1上の点(公開鍵)を、33バイトのバイト列として表現します。
ERC7524では、この「圧縮された公開鍵の33バイト表現」を表す専用の記号(関数)を定義して使っています。
これにより、署名やハッシュの入力長が常に一定になり、仕様の記述や実装が分かりやすくなります。
ハッシュ関数(SHA256)
ハッシュには SHA256 を使います。
これは IETF RFC4634で定義されている標準的なハッシュ関数です。
ERC7524では、複数の値を連結して SHA256 を計算する場合、以下のようなルールを取っています。
- 複数の値(メッセージ、公開鍵、曲線上の点のエンコードなど)をバイト列として順に連結する
- その連結結果に
SHA256をかけたものを「ハッシュ値」と呼ぶ - 得られた32バイトのハッシュ値を、
secp256k1のスカラーとして(ビッグエンディアンで)解釈する
ここで「スカラーとして解釈する」とは、「ハッシュ値を大きな整数として見て、曲線上の乗算(秘密鍵や乱数と同じ領域)に使える形にする」という意味です。
ハッシュ・トゥ・カーブ(Hash-to-Curve)
ハッシュ・トゥ・カーブとは、任意のバイト列から「安全に椭円曲線上の点を生成する」ための標準的な手続きです。
ERC7524では、IETF RFC9380の secp256k1 用ハッシュ・トゥ・カーブ方式を使います。
具体的には、RFC 9380の付録 J.8.1 で定義されている secp256k1_XMD:SHA-256_SSWU_RO_ という方式を使います。
文書中では、「複数の値を連結したバイト列を入力としてその結果から椭円曲線上の点を生成する」という操作を行うときに、このハッシュ・トゥ・カーブ関数を表す専用の記号で書いています。
鍵生成
鍵ペアは、通常の secp256k1 ベースの鍵生成と同じ考え方です。
- 秘密鍵
有限体上のランダムなスカラー値です。
暗号学的に十分な乱数生成器から得られた値を使います。 - 公開鍵
秘密鍵に生成点を掛けた椭円曲線上の点として定義されます。
(秘密鍵 × 生成点 = 公開鍵)
この鍵ペアは、既存のEthereumアカウント(ECDSA secp256k1)と互換性があります。
つまり、既存ウォレットの鍵をそのまま使えます。
署名生成
署名方式は、Chaum-Pedersen 署名(Chaum-Pedersen 証明)をベースにしています。
Chaum-Pedersen は「同じ離散対数を共有していること」を証明するプロトコルで、それを署名風にアレンジした形です。
32バイトのメッセージと、先ほどの鍵ペアを使って署名を生成する手順は、以下のような流れになります。
- 有限体から乱数を1つ選ぶ
署名ごとのランダムなスカラー値を取ります(非決定的な要素)。 - 乱数に対応する曲線上の点を計算する
乱数 × 生成点 のような形で、ランダムな点を1つ求めます。 - メッセージなどをハッシュ・トゥ・カーブで点に写像する
メッセージや公開鍵など、定められた値を連結してハッシュ・トゥ・カーブを適用し、曲線上の点を1つ得ます。 -
nullifierを計算する
公開情報として使う「決定的成分」をハッシュで計算します。
このnullifierは、同じメッセージと同じ鍵で署名すると必ず同じ値になります。 - チャレンジ値(ハッシュ)を計算する
メッセージとnullifierのように、定められた値を連結し、SHA256でハッシュしてスカラーとして解釈します。
これが署名スキームにおける「チャレンジ」に相当します。 - 応答成分を計算する
乱数と秘密鍵、チャレンジ値を組み合わせて、Chaum-Pedersen型の「応答」スカラーを計算します。 - 署名を構成する
最終的な署名は、「ランダム点」、「nullifier」、「応答スカラー」のような形で構成される値の組として表現されます。
ここで重要なのは、「チャレンジ値をメッセージとランダム点ではなく、メッセージと nullifier から計算している」という点です。
これにより、「どの署名をしても、同じメッセージと同じ鍵から計算される nullifier は常に同じ」になり、nullifier が決定的な識別子として使えるようになります。
署名検証(非 ZK)
ここからは「非ゼロ知識での検証」、つまり通常の署名検証のイメージを説明しています。
これは仕様のコアではないものの、ZK検証を理解するための直感を与える位置づけです。
検証者が以下の情報を知っているとします。
- メッセージ
nullifier- 署名者の公開鍵
- 署名(ランダム点、応答スカラーなど)
このとき、検証者は以下のような流れで署名をチェックできます。
- メッセージと
nullifierから、チャレンジ値を再計算する - 公開鍵とチャレンジ値、応答スカラーから、期待される点を再計算する
- 署名に含まれるランダム点などと照らし合わせ、
「Chaum-Pedersen署名の関係が成り立っているか」を確認する
具体的には、「ある曲線上の点どうしの等式が3つとも満たされているか」など、いくつかの条件をチェックします。
いずれかが偽なら署名を拒否し、すべて真なら署名を受理する、という形です。
ここまでが通常の署名検証の直感的な説明で、このあとに続くのがゼロ知識での検証仕様です。
バージョン 1:検証者最適化(Verifier Optimized)
ここでは「検証者は署名者の秘密鍵を知らないまま、署名者が正しい秘密鍵を持っていることをゼロ知識で証明する」ための方式について説明されています。
この場合、ゼロ知識証明(例: Groth16 のような SNARK)で署名検証ロジックを回路として表現し、以下のように入力を分けます。
- 公開入力
メッセージ、nullifierなど、検証者に見せたい情報 - 秘密入力
秘密鍵、乱数、内部的に使うスカラーや点など、外に出したくない情報
一部の中間値は「制約数を節約するため」にあえて秘密入力として与えることもあります
回路の中では、署名生成と同じような計算を繰り返します。
- 秘密鍵と生成点から公開鍵を再計算する
- 乱数と生成点からランダム点を再計算する
- ハッシュ・トゥ・カーブで必要な点を計算する
-
nullifierを再計算する - メッセージと
nullifierからチャレンジ値を計算する - 応答スカラーが「乱数と秘密鍵、チャレンジ値」から正しく計算されているかをチェックする
さらに、以下のような制約を課すことで、「この署名は正しく生成されたものである」ことを保証します。
- 公開鍵が秘密鍵から正しく導かれている
- nullifier がメッセージと公開鍵(または決められた値)から正しく計算されている
- 応答スカラー、ランダム点、公開鍵の間に Chaum-Pedersen 型の関係が成り立っている
これらの制約をすべて満たすことを SNARK証明で示すことで、検証者は「秘密鍵を知らなくても、署名が正しいこと」を確認できます。
バージョン 2:証明者最適化(Prover Optimized)
ブラウザ環境でのzk証明生成では、SHA-256 の計算が特に重い問題があります。
PLUME という文脈では、メッセージと nullifier から計算するハッシュ値がボトルネックになっていました。
そこで、Poseidonチームから提案されたのが、「この SHA-256 計算を証明回路の外に出し、検証者側で行う」という変更です。
この変更により、以下のような構成になります。
- 回路の公開入力
メッセージ、nullifier、署名、ハッシュ結果など - 回路の秘密入力
秘密鍵、乱数など、本当に隠したい値
回路内では、もはや SHA-256 を計算せず、以下のような確認だけを行います。
- 秘密鍵から公開鍵を再計算する
- 乱数からランダム点を再計算する
-
nullifierと署名の関係をチェックする - Chaum-Pedersen 型の等式が成り立つかをチェックする
一方で、SHA-256 の計算そのものは、回路の外で PLUME の検証者が行います。
つまり、メッセージと nullifier から SHA-256 を計算しその結果が公開入力として与えられているハッシュ値と一致するかどうかを確認する
という追加チェックを、ZK検証とは別に行う形になります。
Ethereumでは SHA-256 がプリコンパイル(ネイティブで高速に実装されている組み込み関数)として用意されているため、この外部チェックはスマートコントラクトから実行しても十分高速です。
このバージョン2によって、証明回路の中から SHA-256 を排除できるため、ブラウザでのzk証明生成時間を大幅に削減できることが期待されます。
バージョン 3
最後に、将来の改良案としての「Version 3」が触れられています。
ここでは、「hash_to_curve の性質のうち indifferentiability(ある種の理想的ハッシュと区別できない性質)を弱めることで、さらに効率化できる余地があるかもしれない」と示唆されています。
具体的な方式や設計はまだ確定していないものの、より効率的な hash_to_curve を採用することで、回路の制約数や計算量を減らし、より軽量なZK署名検証を実現できる可能性がある、という位置づけです。
補足
ここでは、署名アルゴリズムに求める性質と、それを満たすためにどのような設計方針を取っているかが説明されています。
特に、ゼロ知識IDシステムに必要な性質として、以下が挙げられています。
| 性質 | ねらい |
|---|---|
| 非対話性 | 事前のやりとりなしに、大きな匿名集合を扱えるようにする |
| 一意性 | 同じアカウントが複数回行動することを防ぐ |
| 決定性 | 同じ条件なら常に同じ結果(nullifier)が得られるようにする |
| 秘密鍵不要の検証 | 公開鍵だけで nullifier の正しさを検証できるようにする |
非対話性(Noninteractivity)
非対話性とは、「事前のやりとり(セットアップのための通信)なしに、いきなり証明や nullifier を使えること」を指します。
ゼロ知識IDシステムで非対話性が重要になる理由は、最初から大きな匿名集合(anonymity set)を持てることにあります。
匿名集合が大きいほど、1人のユーザーがどのアクションを行ったか特定されにくくなり、Sybil攻撃やスパムに強くなります。
もし、事前に「登録フェーズ」などのインタラクティブな手順が必要な設計だと、その登録を行った人だけが匿名集合に含まれるため、集合のサイズが小さくなりやすくなります。
非対話型の nullifier を使う場合、以下のようなことが可能になります。
- Merkleツリーでアカウント集合へのメンバーシップをゼロ知識で証明する
- 署名によりメッセージが正しくそのアカウントに紐づいていることを証明する
-
nullifierによって「一人一回」の制約をかける
これにより、zkエアドロップのように、「全ての対象アカウントが、事前の手続きなしで匿名集合に含まれている」というユースケースが実現できます。
一方で tornado.cash のような「インタラクティブな nullifier」を使う設計では、新しいユーザーごとにMerkleツリーを更新する必要があります。
そのため、匿名集合に入るには事前のアクションが必要となり、完全な非対話性は得られません。
一意性(Uniqueness)
二重支払い(二重スピンド)や二重請求を禁止したい場合、「あるアカウントが特定の文脈で行動できるのは一回だけ」であることを保証する必要があります。
ここで重要になるのが「一意な nullifier」です。
しかし、現在のEthereumが使っているECDSA署名は非決定的で、毎回異なるランダム値を使って署名が生成されます。
そのため、「署名そのもの」を一意な識別子として使うことはできません。
同じメッセージと同じ鍵でも、毎回違う署名が出てしまうからです。
そこで必要になるのが、公開鍵だけで検証できる新しい決定的な関数です。
この関数は以下の条件を満たす必要があります。
- 非対話的であること
- 鍵ペアを一意に識別できること
- アカウントの正体(どのアドレスか)は公開しないこと
ここでの重要な点は、「nullifier を匿名アカウントへの公開コミットメントとして使う」という考え方です。
nullifier が公開されていても、どのアカウントかは分からない一方で、「この nullifier を使えるのはこの匿名アカウントだけ」という一意性だけを外部に約束できます。
決定性(Deterministic)
決定性とは、「同じアカウントが同じ条件で署名を行ったときには、常に同じ値(nullifier など)が得られること」です。
ここで目指しているのは、各アカウントがある文脈に対しては一度だけ署名を生成して、将来同じ文脈で署名しても同じ nullifier が再生成されるという性質です。
これにより、nullifier が「その鍵ペアとコンテキストに紐づいた固定のラベル」として働きます。
ECDSAのような非決定的な署名では、この性質が満たせません。
そのため、署名スキームは「内部でランダムネスを使いつつも、nullifier 部分は決定的」であるように設計されています。
秘密鍵不要の検証(Verifiable without a secret key)
ECDSAのように署名が非決定的な場合、署名だけでは、「それがある種の決定的な関数から得られたものか」を検証することができません。
秘密鍵に依存した内部状態があるためです。
ここで目指しているのは、以下のような性質を持つ関数です。
- 出力は決定的
- 検証は公開鍵だけで行える
- 秘密鍵をどこかに貼り付けたり、エンクレーブから取り出したりする必要がない
- ハードウェアウォレットのエンクレーブでも実行しやすい、シンプルな計算で済む
nullifier が非対話的であることにより、公開鍵を知られなくても鍵ペアを一意に識別でき、その一方でアカウントの正体は隠したままにできます。
設計全体はできる限りシンプルになるように構成されており、BLS署名、Chaum-Pedersen型の同一離散対数証明(同じ離散対数を共有していることを示す証明)、Goh-JareckiのEDL論文のアイデアをベースにしつつ、secp256k1 上で動作するよう調整されています。
セキュリティ
暗号安全性と実装状況
このアルゴリズム固有の暗号的性質については、PLUME論文で形式的な証明が与えられているとされています。
理論的な部分はすでに公開されており、実装についても内部監査は一度行われています。
一方で、エンドツーエンドでの形式検証や外部監査はまだ完了しておらず、現時点では以下の状態であることが明記されています。
- 理論的には仕様通りに正しく安全性が示されている
- 実装も仕様には経験的に従っていると確認されているが、完全な外部監査はまだ
インタラクティブ性と量子時代の秘匿性のトレードオフ
ここでは、「将来の量子コンピュータが現れたときに何が起きるか」ということについて説明されています。
量子計算によるECDSA破壊とその影響
遠い将来、十分に強力な量子コンピュータが実現すると、ECDSAのような離散対数問題に基づく鍵ペアは破られる可能性があります。
そうなると、多くのEthereumの鍵ペアも破られることになります。
ただし、事前に量子耐性を持つ新しい鍵ペア(あるいはビット長を増やした鍵ペア)に移行しておけば、アクティブな資産自体は守ることができます。
具体的には、以下のような移行が想定されています。
- 既存のECDSA鍵で、新しい量子耐性鍵ペアへのコミットメントを含むメッセージに署名する
- チェーン上で、「この新しい鍵ペアを正当なものとして認める」形にフォークする
このようにすれば、将来的にECDSAが破られても、移行済みの資産は保護できる可能性があります。
また、量子コンピュータが現れた場合、ZK-SNARK自体も偽造可能(forgeable)になると考えられていますが、すでに過去に作られた zk証明については前方秘匿性(forward secrecy)が保たれる可能性があります。
理想的には、チェーン全体は大きな問題なく動き続けられる、という想定です。
決定的 nullifier が抱える匿名性の問題
一方で、決定的 nullifier を使う場合の匿名性は、量子コンピュータによる秘密鍵の漏えいに対して弱い側面があります。
決定的 nullifier では、鍵ペアだけが匿名性の唯一の「乱数源」です。
もし量子コンピュータで秘密鍵が計算されてしまえば、攻撃者は以下の手順で、匿名性を完全に壊すことができてしまいます。
- 匿名集合に含まれるすべてのアカウントの秘密鍵を計算する
- 各鍵から決定的
nullifierを再計算する - チェーン上の
nullifierと照合して、どのアカウントがどのnullifierに対応するかを突き止める
これはECDSA上で動くあらゆる決定的 nullifier 方式に共通する問題であり、「一度秘密鍵が知られてしまうと、匿名性を支えている唯一のランダム性が失われる」という構造的な制約です。
ポスト量子の秘匿性を重視する場合の代替案(semaphore 型)
量子コンピュータが現れても匿名性を守りたい場合は、どこかの性質を諦める必要があります。
ここでは、「非対話性を諦める」という選択肢がもっとも取りやすいとされています。
例として挙げられているのが、semaphore 型の構成です。
ざっくりいうと、以下のような仕組みになります。
- 匿名集合に入りたい各アカウントが、「semaphore IDへのコミットメント」に署名して公開する
(実質的にはhash[ランダム値, external nullifier, 公開鍵]のような値を公開するイメージです) - あるアクションを行いたいとき、利用者は
externalnullifierを公開し、それが匿名集合に含まれるいずれかのsemaphore IDから来ていることをゼロ知識で証明する
この方式では、匿名集合は「事前に semaphore コミットメントを公開した人たち」に限定されます。
そのため、以下の性質があります。
- 匿名集合は、すでにopt-inした人たちだけに縮む
- 匿名集合を十分な大きさにするためには、別途「登録フェーズ」が必要になる
このインタラクティブ性により、zkエアドロップや tornado.cash をより良くしたような構成は一気に扱いづらくなります。
ただし、ここで頼りにする主な安全性は「ハッシュ関数の難しさ」であり、現時点の理解では、量子コンピュータに対してもハッシュ関数の安全性は比較的保たれやすいと考えられています。
そのため、この方式なら、将来量子コンピュータが現れても、簡単には匿名性を破られない可能性が高いとされています。
実現時期に関する見通し
離散対数問題を量子アニーリングで解くために必要な量子ビット数についての近年の試算では、小さいビット長(テキストでは 6 ビット程度の素数)を超えると実験がうまくいかなかったとされています。
また、RSAを解くために必要な量子ビット数の推定が10〜40年先とされていることから、実用的な規模の離散対数を解くのには、さらに長い時間がかかると予想されています。
つまり、現実的なタイムスケールでは、量子コンピュータによるECDSA破壊はまだかなり先の話である可能性が高い、という見方です。
アプリケーションごとの選択の目安
最後に、「どのアルゴリズムを選ぶべきか」はアプリケーションごとに異なるとされています。
-
比較的短期の秘匿性が重要な用途
例として、DAOの投票や、若いときの告白のように、将来かなり先になれば気にならなくなる情報などは、ここで説明されている決定的nullifier構成を優先する選択もありえます。
非対話性と扱いやすさが大きな利点になるためです。 -
非常に長期の秘匿性が重要な用途
内部告発やジャーナリズムなど、何十年経っても匿名性が重要なケースでは、決定的nullifierではなく、semaphore型のようなインタラクティブな構成の方が適している可能性があります。
このように、「非対話性」、「一意性」、「決定性」、「秘密鍵不要の検証」と「量子時代まで含めた匿名性」のどこに重みを置くかによって、採用すべきアルゴリズムが変わる、という点が強調されています。
引用
Yush G (@Divide-By-0) aayushg@mit.edu, Kobi Gurkan (@kobigurk), Richard Liu (@rrrliu), Vivek Bhupatiraju (@vb7401), Barry Whitehat (@barryWhiteHat), "ERC-7524: PLUME Signature in Wallets [DRAFT]," Ethereum Improvement Proposals, no. 7524, September 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7524.
最後に
今回は「匿名性を保ちながら各アカウントを一意に識別できる nullifier を作り出す署名の仕組みを提案しているERC7524」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!