この記事について
Decentralized SSOはすっごく前に作った作品ですが、それにZK(Zero Knowledge Proof)を追加したプロトコルを作りました。 この記事では、そのプロコトル制作の背景やプロコトルについて書きます。 正直、プロトコルと言うほど崇高なものではないですが、便宜上そう言ってます。
Zero Knowledge SSOに行き着くまで
前まではZKの意義深さがよく分からずにスルーしていたのですが、今年(2024年)の7月にZKの重要性を理解しました。 これは回路を書いたりと手を動かしてみたことが大きいと思います。 ZKの数学は難しいのですが、有用なライブラリ多いため実際に手を動かす分には数学無しでも扱えるようになっています。 そこからアプリケーションでもっとZKが使われて欲しい!との思いから、数学なしでZKのアプリケーションを作るための記事を書いたりしていました。
さて、それが一段落したところで、ZKを使ってSSOできないかな、と思い立ちました。 というのも、以前に作っていたDecentralized SSOでの課題を解決できそうな気がしたためです。 Decentralized SSOでは複数のIdPからユーザの信頼情報を取得しますが、その際に複数のサービス間で同一のアイデンティティを利用するためアイデンティティが紐づくというプライバシー上の懸念がありました。 そこでZKを使うことで、ユーザの信頼情報のみを取得し、アイデンティティを紐付けないでSSOを実現できるのではないか、と考えました。
色々と調べていくうちに、Ethereum財団のPSE(Privacy and Scaling Explorations)がSemaphoreというアイデンティティ関連のライブラリを作っていたため、それを利用することにしました。 また、PSEがAcceleration Programというタスクにお金を払うプログラムをやっていたため、それに応募できそうだと思いました。 ということで、1週間程度で雑にプロトタイプと仕様を作り、それを提出しました。
PSEの人がレビューをした後、今ちょうどスマホの鍵管理アプリを作っているプロジェクトがあって、それと統合したらどうだろうか?と言われました。 スマホアプリで実際に実現が出来そうだったので、それに同意してそのプロジェクトが完了するのを待っている最中です(2024/10/07 現在)。 待ち続けて2ヶ月以上経つので、一旦アウトプットとして書いておきます。
なお、以下はEthResearchにも投稿した内容の日本語版です。
プロトコル
概要
Anoidenは、ゼロ知識証明を用いた匿名シングルサインオンプロトコルです。このプロトコルは、アイデンティティプロバイダー(IdP)とサービスプロバイダー(SP)が共謀しても、ユーザのアイデンティティを秘密に保ちながらIdPからの情報を使ってSPへのサインインを可能にします。これはSemaphoreライブラリを利用します。
- Connect
- ユーザは、IdPが指定する方法(Eメールや電話番号の登録など)でアカウントを作成し、ゼロ知識証明の鍵と接続します。
- Auth
- SPはIdPを信頼しており、認証のエンドポイントを知っています。ユーザはSPに対してIdPでのログインを要求し、ノンスを取得します。
- その後、ユーザは自身のアイデンティティの証明を作成しIdPに送信します。IdPは証明を確認し、ユーザが正当であれば対応する署名を作成します。
用語
- 拡張機能: ブラウザ拡張機能(アプリでも可)で、ユーザの鍵管理や証明の作成等を行います。SPとIdPが結託してもアイデンティティが秘匿されるために必要です。
- anoiden.js: ウェブサイトのクライアントに拡張機能と通信するためのインターフェイスを提供します。
- アイデンティティプロバイダー(IdP): ユーザの正当性の情報をSPに提供する主体です。
- サービスプロバイダー(SP): IdPから提供されるユーザの正当性を利用する主体です。
IdPへの登録(Connect)
ユーザがIdPでアカウント登録を完了すると、そのアカウントと拡張機能側の鍵を連携できます。 この連携により、ユーザは今後、認証を要求された際にこのIdPを使用可能になります。 また、鍵を使って匿名でIdPにログインすることも可能になります。
IdPのクライアントはanoiden.jsを通じて、署名と公開鍵を取得します。
const {signature, publicKey} = await connect(serviceName, nonce);
ここでserviceNameはIdPのサービス名で、鍵の管理に使用されます。 nonceは推測が不可能な文字列で、IdPのサーバサイドから取得します。
以下の図はIdPのクライアントがユーザの署名を取得するまでのフローです。
クライアントは取得した署名、公開鍵、ノンスをサーバに送信します。 サーバはセッションを確認し、そのユーザに対してそのノンスを発行したかを確認します。 有効であれば署名を検証し、identifier(公開鍵のposeidonハッシュ)を取得し、Merkle Treeにidentifierを追加し、追加後のMerkle Rootを保存します。
Authorize
SPはIdPを利用する際、事前に利用することを伝えており、IdPからクライアントIDを受け取っていることとします。
SPのクライアントはanoiden.jsを通じて、以下のようにIdPからの署名を取得します。
const idpSignature = await auth(endpoint, nonce, params);
endpointはIdPのエンドポイントです。 nonceはSPのサーバサイドから取得します。 paramsはSPとIdP間で定めた任意のパラメータですが、clientIdは必須です。
拡張機能はendpoint, nonce, paramsを受け取った後、IdPのエンドポイントからidentifiersを取得し、Semaphoreを利用して以下のように証明を作成します。
// https://js.semaphore.pse.dev/functions/_semaphore_protocol_proof.generateProof.html
await generateProof(identity, group, nonce, "signIn");
identityはIdP別に作成した鍵、groupはidentifiersから作成したオブジェクト、nonceはSPから渡されたノンスです。
拡張機能は、paramsにSPのhostnameを追加します。 IdPは、署名を要求したSPを識別するためにclientIdと合わせてhostnameを利用します。
IdPは証明とパラメータを受け取り、証明を検証した後でSPと事前に共有した形式の鍵でノンスとパラメータの署名を作成します。
クライアントは受け取った署名をサーバに送信し、サーバ側で検証します。
Endpoint interface
IdPのエンドポイントはGETとPOSTのメゾットを持ちます。 GETは全アカウントのidentifierを返します。 POSTは証明を検証し、ユーザの正当性を確認します。
GET endpoint/identifiers
パラメータはありません。レスポンスはJSONで、identifiersキーにidentifierのリストが文字列で返されます。
返答例
{
"identifiers": ["6423154662976160105169106896701549153516891642211172349909782921108153674476"]
}
POST endpoint/auth
リクエストボディには、証明(proof)とパラメータ(params)を含むJSONオブジェクトが必要です。
{
"proof": "Semaphore proof here",
"params": {
"clientId": "exampleClientId",
"hostname": "example.com",
"other_params": "additional parameters here"
}
}
レスポンスはJSON形式で返され、signatureキーにはIdPの署名が文字列として含まれます。
{
"signature": "example_signature"
}
Security
nonce
はリプレイ攻撃の防止に利用されます。
clientId
とhostname
は、IdPが承認したSPに対してのみ、ユーザの正当性を渡すためのロジックです。
不正なドメインからのリクエストを防止できます。
Concerns
SPが一つのIdPのみを利用する場合、IdPに対する絶対的な信頼が必要になります。 しかし、サインイン時に同時に複数のIdPを利用すること(Decentralized SSO)によって、IdPに必要な信頼が低減します。
アイデンティティが多くなった場合、非効率になります。 そのためアイデンティティのグループを分割することを考えていますが、その場合はプライバシーに関する懸念があります。
所感
よく巷では
処女作には、その作家のすべてが出揃っている
と言われます。 私の最初の作品はDecentralized SSOです。 この言葉は、エンジニアにとってもある程度は当てはまるのではないかと思います。
しかし、この直線上にしかエンジニア人生がないのだとしたら、少し悲しいとも思います。 Portable Webのアーキテクチャも書いてEthResearch投稿しましたが、フィードバックが全くなかったです。 一旦はやり切った感があるので、今後はクリプト(に関連する作品作り)を離れて、少なくとも今後10数ヶ月はLLMを勉強するつもりです。