NIP-47
Nostr Wallet Connect
Nostrクライアントからライトニングウォレットのサーバに直接リクエストを送れるようにする仕組み
結果として、ウォレットアプリへの遷移なしでZapできるようになる! 今まで出てきたBIP-47を書き直したもの。Semisolさん提起。
Willさんも同意していたりPRでegge7氏がPoCを実装し始めていたり実現度が高そう。
対応状況
対応クライアント
対応ウォレット
/icons/hr.icon
以下、仕様の翻訳
趣旨(Rationale)
このNIPは、標準化されたプロトコルを通してクライアントがリモートのライトニングウォレットにアクセスする方法について記述する。カストディアン(Custodian、預託所 預かった資産の管理を行う機関)がこれを実装してもよいし、ユーザが自分のウォレットまたはノードとNostr Wallet Connectプロトコルのブリッジを実行してもよい。 用語
クライアント: ライトニングインボイスに対して支払いを行おうとする、任意のプラットフォーム上のNostrアプリ ユーザ: クライアントの利用者で、クライアントを自分のウォレットと接続しようとしている者
ウォレットサービス: 通常は常時起動のコンピュータ(例: クラウド・Raspberrty Pi)で実行されるNostrアプリケーション。このアプリはウォレットが提供するAPIへのアクセス手段を持つ
動作の仕組み(Theory of Operation)
1. このNIPを利用して他のNostrユーザにライトニング支払いをしたいユーザは、まず特別な「接続」URIをNIP-47に準拠したウォレットアプリケーションから取得しなければならない。ウォレットアプリケーションはこのURIをQRコード・コピペできる文字列・その他の手段で提供する。
2. 次に、ユーザはこのURIをコピペまたはQRコードの読み取りなどの方法でクライアントにコピーする。クライアントはこのURIを保存し、あとでユーザが支払いを行うたびに利用する。次にクライアントは情報(13194)イベントをURIが指定するリレーにリクエストする。ウォレットサービスは、前もってそのイベントをリレーに送信しておき、リレーはそれを上書き可能イベントとして保持する。
3. ユーザが支払いを開始する際、nostrクライアントはpay_invoiceリクエスト(kind 23194)を作成し、接続URIから取得したトークンを用いて暗号化したうえで、接続URIが指定するリレーに送信する。ウォレットサービスはそれらのリレー上で(リクエストを)待機(listen)し、リクエストを復号して、支払いを行うためにユーザのウォレットアプリケーションと交信する。接続URIがウォレットアプリのAPIへのアクセス手段を持つリレーを指定しているので、ウォレットサービスはウォレットアプリケーションと通信する方法がわかる
訳注: 最後の文がよくわからない
4. 支払いが完了したら、ウォレットサービスは暗号化されたレスポンス(kind 23195)をURIが指定するリレーを通してユーザに送信する。
イベント
3種類のイベントを用いる:
NIP-47 情報イベント: 13194
NIP-47 リクエスト: 23194
NIP-47 レスポンス: 23195
情報イベントはウォレットサービスによってリレー上に発行される上書き可能イベントで、そのウォレットサービスがサポートするコマンドを示す。contentは、サポートするコマンドをスペース区切りで並べたプレーンテキスト文字列とするべきである(例: pay_invoice get_balance)。このNIPではpay_invoiceコマンドのみを記述するが、他のコマンドが他のNIPで定義される可能性がある。
リクエストイベントとレスポンスイベントはどちらも1つのpタグを含むべき(SHOULD)で、リクエストの場合はウォレットサービスの公開鍵、レスポンスの場合はユーザの公開鍵を持つ。レスポンスイベントは対応するリクエストイベントのidを値とするeタグを含むべきである(SHOULD)。
リクエストとレスポンスのcontentはNIP-04に従い暗号化される、ほぼ固定の構造を持つJSON-RPC風のオブジェクトである: リクエスト:
code:request.json
{
"method": "pay_invoice", // メソッド, 文字列
"params": { // 引数, オブジェクト
"invoice": "lnbc50n1..." // コマンドに関連するデータ
}
}
レスポンス:
code:response.json
{
"result_type": "pay_invoice", // resultフィールドの構造を示す
"error": { // オブジェクト, エラーの場合は非null
"code": "UNAUTHORIZED", // 文字列のエラーコード, 下記参照
"message": "human readable error message" // 訳注: 人間向けのエラーメッセージ
},
"result": { // 結果を表すオブジェクト。 エラーの場合はnull
"preimage": "0123456789abcdef..." // コマンドに関連するデータ
}
}
result_typeフィールドは、応答対象のメソッド名を含まなければならない(MUST)。コマンドが失敗した場合、errorフィールドは人間が読める形式のエラーメッセージを持つmessageフィールドとエラーコードを持つcodeフィールドを含まなければならない(MUST)。コマンドが成功した場合、errorフィールドはnullとしなければならない。
エラーコード
RATE_LIMITED: クライアントがコマンドを送信する頻度が高すぎる。数秒後にリトライすべき
NOT_IMPLEMENTED: 不明なコマンド、または意図的に実装されていないコマンドが送信された
INSUFFICIENT_BALANCE: 支払い金額や手数料に対してウォレットの残高が不足している
QUOTA_EXCEEDED: ウォレットが送金制限を超過している
RESTRICTED: この公開鍵には指定の操作が許可されていない
UNAUTHORIZED: この公開鍵は接続されたウォレットを持たない
INTERNAL: 内部エラー
OTHER: その他のエラー
Nostr Wallet Connect URI
クライアントは、QRコードの読み取り・ディープリンクの処理・URIのペーストによりウォレットサービスを発見する。
ウォレットサービスが生成する接続URIは、プロトコル(スキーム)をnostr+walletconnect:、ベースパスを自身の16進エンコードされた公開鍵とし、さらに以下のクエリ文字列パラメータを持つ:
relay: 必須。ウォレットサービスが接続し、イベントを待機するリレーのURL。複数指定可
secret: 必須。32バイトのランダム値を16進エンコードした文字列。クライアントはウォレットサービスとやり取りする際、これをイベントの署名とペイロードの暗号化に用いる
認証において鍵を渡し合う必要はない
ユーザは別々のアプリケーションに対し別々の鍵を持てる。鍵は好きに失効・作成でき、任意の制約を持つことができる
鍵はユーザの目に触れたりバックアップされたりしないので、漏れる可能性は低い
ユーザのメイン鍵と支払いが紐付かないのでプライバシーが向上する
lud16: 推奨。ユーザのプロフィールにlud16フィールドが設定されていない場合に自動的にそれを設定するのに使えるライトニングアドレス
クライアントはこの接続URIを保存し、ユーザがインボイスの支払いのといったアクションを実行する際に利用する。このNIPは一時イベントを使うため、イベントが失われないように、非アクティブ時に接続を閉じないリレーを選ぶとよい。
接続URIの例
nostr+walletconnect:b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c
コマンド
pay_invoice
説明: インボイスへの支払いをリクエストする。
リクエスト:
code:pay_invoice_req.json
{
"method": "pay_invoice",
"params": {
"invoice": "lnbc50n1..." // bolt11インボイス
}
}
レスポンス:
code:pay_invoice_resp.json
{
"result_type": "pay_invoice",
"result": {
"preimage": "0123456789abcdef..." // 支払いの preimage
}
}
エラー:
PAYMENT_FAILED: 支払いに失敗した。タイムアウト・ルートを使い果たした・キャパシティ不足など
インボイス支払いフローの例
0. ユーザはクライアントアプリによってウォレットサービスが生成したQRコードを読み取り、nostr:walletconnect:ディープリンクを辿るか、接続の詳細を手動で設定する。
1. クライアントはkind 23194のイベントをウォレットサービスに送信する。contentはpay_invoiceリクエストとする。秘密鍵として接続URIに含まれるsecretを使う。
2. ウォレットサービスは、(リクエストの)発行者の鍵が支払いを実行を認可されているか検証し、ペイロードを復号して支払いを行う。
3. ウォレットサービスはkind23195の、エラーメッセージまたはpreimageを持つレスポンスをcontentに含むイベントによってリクエストイベントに応答する。
専用リレーの利用
このNIPは利用するリレーの種類について何の条件も課していない。しかし、もしユーザがカストディアルサービスを利用するのであれば、そのカストディアルサービスがホストするリレーを使うのが理にかなっているだろう。このときリレーはメタデータのリークを防ぐために認証を強制してもよい。この場合、第三者のリレーに依存しないことにより信頼性の向上にもつながる。