Lightning Networkの署名を活用したログイン認証の実装をAIに書かせたら間違いだらけだった件💦
こんにちは、かずみょんです。
自分のノードのルーティング状況を公開するWebアプリを作ったのですが、ピアの情報まで見えてしまうのは問題かなと思い、私のノードにチャンネルを開いているノードだけが情報を閲覧できるようにログイン認証を実装することにしました。
例のごとくGitHub CopilotでAIにコードを書かせれば簡単にできるだろうと安易に考えていたのですが、思いのほかハマってしまいました。果たして自分が悪いのか、AIが悪いのか?と考えながら、最終的にはECDSA署名アルゴリズムについて勉強する羽目に……。
今回は、どのような間違いがあったのかを記録しておきます。
1.背景
このWebアプリは、Pythonで簡単にWebアプリを作成できるフレームワーク「Gradio」を使用しています。Gradioには認証機能を簡単に実装できる関数があり、以下のように記述すると、ユーザー名とパスワードを使った認証が可能になります。
def same_auth(username, password):
return username == password
demo.launch(auth=same_auth)
この機能を利用し、ユーザー名欄にはノードの公開鍵(pubkey)、パスワード欄には指定したカスタムメッセージをLightning Networkで署名したデータを入力してもらうことで認証を行う仕様にしました。
2.認証の仕組み
認証におけるチェックポイントは以下の2つです。
-
-
入力されたpubkeyが、自分のノードとチャンネルを持っているか?チャンネルリスト内に入力されたpubkeyが含まれているかを確認(pubkey同士を比較)
-
署名データが、入力されたpubkeyに対応する秘密鍵で署名されているか
-
署名の構造
認証コードでは、パスワード欄に入力された署名データからr
とs
を抽出し、ユーザー名欄に入力された公開鍵(pubkey)とz
(メッセージのハッシュ値)を用いてRを算出します。そしてRのx座標を用いて、以下の検証式を満たすかを確認します。
つまり、正しく署名されたデータであることを証明できれば認証OKとする仕組みです。
3.Github copilotにコードを書かせたら結構間違ってた件
Lightning Networkの認証処理を行う関数(pubkey、署名データ、メッセージを入力し、認証OKならTrueを返す)をPythonでAIに書かせました。
しかし、いくつもの地雷が埋め込まれていました……。
① 署名データの処理が間違っていた
本来、署名データからr
(32バイト)とs
(32バイト)を抽出する必要があります。しかし、AIが書いたコードは、署名データを単純にHexのバイト配列に変換しているだけでした。
実は、Lightning NetworkではBOLT仕様により、署名データはzbase32
というフォーマットでエンコードされています。これを知らなかったので、半日悩みました……。
② r
とs
の抽出ミス
zbase32
でデコードした署名データは65バイトで、そのうち1バイトは公開鍵を復元するためのリカバリーIDです。本来、最初の1バイト目がリカバリーIDになります。
しかし、GPT-4oはなぜか最後の65バイト目をリカバリーIDとして処理していました……。1日かかってようやく気づいたミスです。
③ メッセージデータのハッシュ化
メッセージデータをハッシュ化し、z
を求める必要があります。しかし、本来はSHA-256を2回適用する必要があるのに、AIは1回しかハッシュ化していませんでした。
「俺はAIに試されているのか?それともAIがバカなのか?」
これはすぐに気づいたので大丈夫でした。
④ メッセージデータの処理
Lightning Networkのカスタムメッセージ署名では、メッセージの前に
"Lightning Signed Message:"
という文字列を追加しなければなりません。
これを知らずにそのままメッセージを使っていたため、署名が一致せず認証に失敗していました。
なお、ビットコインの場合は"Bitcoin Signed Message:"
になるようです。LNDのコードを読んで初めて知りました。まだまだ知らないことが多いですね……。
4.まとめ
最終的に、認証の仕様を「署名データから復元した公開鍵が、入力されたpubkeyと一致するか」に変更しました。
GitHub Copilotにコードを書かせると、確かに雛形を作るのは楽ですが、仕様に基づいた細かい処理を正確に書かせるには、AIに適切な指示を与える必要があると痛感しました。
最後に、署名データをzbase32
でデコードし、メッセージをハッシュ化したデータを入力として、復元した公開鍵を返すPythonの関数を有料エリアに掲載しています。このコードもAIに書かせたらRecovery idの処理間違ってたのでLNDのGoのコードを学ばせて修正させときました(笑)。