EIP-4361という仕様や、Sign-in with Solanaという仕組みを知りました。
これらは、あるアプリケーションやサービスがブロックチェーン上のあるアカウントに対して、ユーザにコントロール権(つまり秘密鍵を知っている)があるかを確認する取り決めのようなものです。
例えば、アドレスTDK2E5VGKH4YSPVBYL2IPI2QFKXLDCSNHDOURRI
の所有者は私です、と言ったところでそれは証明にはなりません。
仮にこのアカウントがモザイクsuperevent.ticket2025
を持っており、それが入場確認であったとして、アドレスにモザイクがあることは確認できても、見せた本人がそのアカウントのコントロール権を持っている人かどうかはわかりません。
これを確認するための一番わかりやすい方法は、なんでもいいのでトランザクションを発信し、ネットワークに承認されることです。
入場に際して、モザイクを転送しなければならない、という運用をするなら、それでも良さそうですが、その場合は転送するための手数料が必要になってしまいます。
そこで、秘密鍵・公開鍵と署名の仕組みを用いて、トランザクションの発信無しに、秘密鍵を知っている(コントロール権がある)ことだけを証明しようという試みです。
デモサイト
Sign-in with Ethereum / Solana を簡単にですが、模倣したつもりのサイトを作成しました。
テストネットのアカウントのアドレスと秘密鍵があれば試すことができます。
要するに、チャレンジレスポンス認証をやっているわけで、サーバとクライアント間での検証(+α)できたなら、クライアントが言っているアカウントの持ち主であると認識してよいということです。
今回は、ホスティング、サーバサイド、アカウント管理の必要なものがすべて揃ったFirebaseへデモサイトをデプロイしました。
要求フェーズ
クライアントは、サーバにログインするアドレスを提示し、それをサーバへ送信します。
確認フェーズ
サーバから、これからログインしようとしているサーバの情報が提示されます。
クライアントは、その内容を確認し、ログインを許可するなら秘密鍵を入力して署名を行い、署名したデータをサーバへ返却します。
完了フェーズ
サーバへ送られた、署名やメッセージの検証が成功すると、そのSymbolアカウントごとにFirebase Authのアカウントが作成され、カスタムトークン機能を用いてログイン状態となり、TODOアプリのUIが表示されます。
これでSign-in with Symbolが完了しました。
アプリケーション機能
だいぶ低機能なTODOアプリが使用できます。
バックエンドでは、アドレスをIDとしたアカウントが作成されています。
適当に項目を追加してみたり、ログインしなおしてみたり、別のアカウントでログインしてみたり
してください。
なおこのサイトは、しばらく残しておきますが、いつか消しちゃうかもしれません。
リポジトリはこちらですので、消えていたら、ご自身でデプロイしてください。
注意点
必要十分と思われる検証の実装は盛り込んでいますが、ここが不十分だと、他人へのなりすましログインや他人のアカウント情報を誤爆して参照してしまうなどの危険なレベルのバグを作り込んでしまう可能性があります。
見よう見まねで実装したところもあり、致命的なミスがあるかもしれませんので、このコードをあまり信頼しないでください。
Solanaでは、仕様にそったウォレットへ認証確認を転送し、そこで署名をさせるような動作をしており、今回のコードのように直接秘密鍵を入力はしないようです。
(実は実際に挙動を確認してません…なんだかよくわからなくて…ソースコードの実装を読み解いた感じ、そのような挙動に見えました)
今回は技術的なポイントの確認のため、サイト上のフィールドへ直接入力される方法を取りました。
(もちろん秘密鍵はサーバへ送信していません)
また、実験的な実装のため、例えば認証メッセージはJSON文字列をそのまま用いていますが、オブジェクトのキー配列の保証はないため(JavaScriptでは一応順序を保ってはくれていますが)、EIP-4361やSIWS同様、文字列でペイロードを組み立てるようにしないと、検証ミスが発生する可能性はあると思います。
認証はセンシティブな実装箇所であり、ほとんどの場合は共通の仕組みが使えればいいので、実用化するのであれば、良くテストが用意され、作り込まれたライブラリやパッケージに昇華されるといいと思います。
そして、あくまでもそのアドレスの秘密鍵を知っていることしか確認できないため、複数人が鍵を知っている場合にアカウントは事実上使い回されてしまいます。
(このあたりのリスクはID/PWログイン同様、漏れていたり、共有されていたら同じことですが)
まとめ
このフロー自体はブロックチェーン自体の技術というよりは、PKI <公開鍵基盤>(Public Key Infrastructure)の特性であります。
SIWE / SIWSでのメッセージ仕様はサードパーティアプリケーションとうまくやりとりするための決め事として定義しているようなので、それ自体もブロックチェーンと直接の関係はありません。
今回の実装では、ブロックチェーンを参照していないので、あまり意味がないかもしれませんが、例えば、アカウントメタデータやモザイクの所有を参照して、ログインやアプリケーション側の機能をコントロールしたり、モザイクを送る宛先としたり、ブロックチェーン上のデータをアプリケーションの一部として利用すれば、ようやく意味がある実装になってくると思います。
全く関係ないサービス間同士で、同じアカウントを参照することで、例えばサービスAで実績の解除として送られたモザイクの履歴を別のサービスBで参照すると何らかの特典が得られる、というようなサービス間の連携を運用することもできるでしょう。
クライアントがそのアドレスについてのコントロール権を持っていることを確認する方法としては、トランザクションを発生させないだけ、ユーザーからすればハードルが低いと思います。
とは言え、秘密鍵による署名が必要だったりするので、実装したコードの方式ではなく、一般的な転送トランザクションに、認証メッセージを含めて、「任意のウォレット」で署名してもらい、そのトランザクションをアナウンスせずに送り返すことで確認できるような仕組みのほうが安心かなと思いました。
「あるアプリケーション」へ自分の署名が必要ならば、その署名には、その「あるアプリケーション」が提供する以外の方法でも署名できる方法を取りたいのです。
転送トランザクションの実装であれば、ウォレットはほぼ確実に実装しているはずなので、例にあげましたが、アナウンスすることはできてしまうので、操作ミスなどでアナウンスしてしまうかもしれません。
トランザクションを発信しない、任意のデータに署名するための機能なんかも、共通して実装されているとそういったことにも使えそうだなと思いました。
ただ、トランザクションを発信しないから良いかといえば、できることなら気軽にトランザクションを発信するようになって欲しいです。
ブロックチェーンへ、「何かを提供する側」と「それを享受する側」がお互いに安いコストを支払って、信頼できる情報を記録することで、いろいろなコストが削減され、その活動がトータルで安くなるということこそが、ブロックチェーンが生活に浸透することだと考えているからです。
他の機能でトランザクションを使わせるから、認証についてはコストを抑えたいとか、なんらかの制約があり、オフラインな状態でも、端末同士でこのやり取りができれば、マークルツリーなどのキャッシュと組み合わせて、アドレスのコントロール権の確認ができるかもしれません。
技術スタック(余談
前述の通り、Firebaseへデプロイしており、サイトはHosting、サーバサイドロジックはFunctions、データストアはFirestore、ユーザ管理はAuthenticationを利用しています。
ちょっとクセはあるものの、必要十分な機能がオールインワンで、料金は安いし、小さいアプリの基盤としてはおすすめです。
FirestoreというKVSが特にクセ強…。設計が寿命を左右します。
ただ、以前と比べてどんどん便利になってきているし、知見も多いので、使ったことがなければ是非試してみてください。
フロントエンドはSvelteを使ってみました。
ちょっと使っただけですし、ほとんどコードは書いてないのですが、直感的にリアクティブネスを実装でき、気軽な感じで作れるのは結構楽しかったです。
大規模になった時のパフォーマンスだとか、そういうのは判断できませんが…Reactはしんどいけど、手軽さとモダンな実装の両方欲張りたいとか、小さいツールくらいなら良い選択肢だと思います。
その他、具体的な認証関係のコードは自分で書いていますが、デザインはTailwindを導入し、細かい実装は面倒くさいのでAIに任せました。
後記
証明についての試みはすでにありました。
また、署名専用のアプリもあるので、署名の責任を移譲することも可能そうです。
このあたりの約束事を整えると、汎用的に実装する枠組みは実現できそうです。