NIP-57
Lightning Zaps
/icons/hr.icon
翻訳 commit=c30971f
kind 9735はzapレシートで、zapリクエストに応じて発行されたインボイスへの支払いが受取人のライトニングウォレットによって確認されたことを表す。 Nostr上にライトニング支払いのレシートを残すことで、クライアントはネットワーク上の実体からのライトニング支払いを表示できる。これは楽しみのために、またはスパムの抑止力として利用できる。
プロトコルの流れ
1. クライアントは、zap対象のイベントに含まれるzapタグ(別表G参照)より、または受取人のプロフィールのlud06, lud16フィールドをlnurl仕様に従ってデコードすることにより、受取人のlnurl-payリクエストURLを割り出す。クライアントはそのURLにGETリクエストを送り、レスポンスをパースしなければならない(MUST)。(レスポンスに)allowNostrが存在してtrueであり、さらにnostrPubkeyが存在して値がBIP 340のhex形式の有効な公開鍵であれば、クライアントはこの情報をレスポンスのcallback, minSendable, maxSendableの値とともにユーザに関連付けるべきである。 (訳注)
lnurl-payリクエストURLとの通信については、LUD-06の(3)を参照。 lud06フィールド(LNURLアドレス)のデコード仕様はLUD-01を参照。 lud16フィールド(Internet Identifier)の仕様はLUD-16を参照。 nostrPubkeyはBIP-340へのリンクがあるが、通常のnostr公開鍵と同じ形式
2. クライアントは各投稿上またはユーザのプロフィール上にライトニングzapボタンを表示してもよい。もしユーザのlnurl-payリクエストエンドポイントがNostrをサポートしているなら、クライアントは通常のlnurlインボイスの代わりにzapレシートをリクエストするためにこのNIPを使うべきである(SHOULD)。
(訳注)
(1)でallowNostrフィールドがtrueであるならば、Nostrをサポートしていることを意味する。
lnurl-payリクエストエンドポイントは、(1)のlnurl-payリクエストURLのこと。
lnurlインボイスは単にライトニングインボイスと解釈してよい。lud06で発行されたインボイスのことを指していると思われる。
「代わりに」とあるが、後述のcallbackの応答としてはライトニングインボイスが返り、zapレシートはリレーに送信される。
3. あるユーザ(「支払人」)が他のユーザ(「受取人」)にzapを送りたいという意思を示したら、クライアントは別表Aで説明された通りにzapリクエストイベントを生成し、署名を行うべきである。
4. zapリクエストは(リレーに)送信するのではなく、受取人のlnurl-payエンドポイントからGETリクエストで取得したcallbackURLに送信されるべきものである。
(訳注)
lnurl-payエンドポイントは、(1)のlnurl-payリクエストURLのこと。callbackは(1)で取得したJSONに含まれる。
5. 受取人のlnurlサーバはこのzapリクエストを受け取り、検証する。zapをサポートするためにlnurlサーバを正しく設定する方法については別表Cを参照。nostrクエリパラメータ(の内容)を検証する方法の詳細については別表Dを参照。
6. zapリクエストが有効なら、(lnurl)サーバはdescriptionとしてそのzapリクエストnoteのみを含むdescription hash invoiceを取得する。それ以外のlnurlメタデータは含めない。これはLUD-06に従うレスポンスとして(クライアントに)返される。 (訳注)
このステップはLUD-06の(6)と(7)に相当する。LNURLサーバはライトニングインボイスを作成してクライアントに返す。そのライトニングインボイスのhタグにはzapリクエストのハッシュ値を含めるようにすべきということを言っている。元になっている仕様LUD-06では(3)のmetadataのハッシュ値をhタグに含めるように定めているが、それは含めないようにする。 言葉について
description hash invoiceはライトニングインボイス(lnbc1..., BOLT-11)のことと理解して良い。 description hashはライトニングインボイスのhフィールドのこと。zapリクエストのSHA-256ハッシュ値を含める。dタグには何も含めない。
LUD-06に従うレスポンスはLUD-06の(6)のprこと。 7. インボイスを受け取ったら、クライアントはそれに対して(自分で)支払いを行うか、それを支払い機能を持つアプリに渡してよい(MAY)。
8. インボイスに対し支払いが行われたら、受取人のlnurlサーバは別表Eに記載のとおりにzapレシートを生成し、zapリクエストに指定されているリレー(relays)に送信しなければならない(MUST)。
9. クライアントは投稿・プロフィール上のzapレシートを取得してもよい(MAY)が、このとき別表Fに記載の方法で正当性を確認しなければならない(MUST)。zapリクエストが空でないcontentを含むならば、zapのコメントとして表示してよい。一般にクライアントはユーザのzapリクエストを表示すべきで、zapレシートは「...によって承認されたzap」という表示のために使えるが、これは任意。
参考情報と例
※JSONやコードの例については 原文 を参照してください 別表A: Zapリクエストイベント
zapリクエストはkind9734のイベントで、リレーにではなく(zap)受取人のlnurl-payのcallbackURLに送信する。
このイベントのcontentには、支払いにあわせて送信するメッセージを含めてもよい(MAY)。
このイベントには次のタグを含めなければならない(MUST):
relays: 受取人のウォレットがzapレシートを送るべきリレーのリスト
amount: 支払人が送信しようとしている金額(millisats(satsの1/1000)単位)。推奨されているが必須ではない
lnurl: 受取人のlnurl-pay URL。lnurlから始まるbech32形式で指定。推奨されているが必須ではない
p: 受取人のhex形式の公開鍵
さらに、イベントには以下のタグを含めてもよい(MAY):
e: hex形式のイベントIDの。個人ではなくイベントに対してzapする場合、これを含めなければならない(MUST)。
a: パラメータつき上書き可能イベントを特定する「座標」。NIP-23の長文投稿のようなへのzapに使う (訳注) ["a", "<kind>:<pubkey>:<dタグ>", "<relay url>"] という形式
別表B: ZapリクエストのHTTPリクエスト
署名されたzapリクエストイベントは(リレーに)送信するのではなく、受取人の(lnurl-payの)callback URLにHTTP GETリクエストによって送信する。callback URLは受取人のlnurl-payエンドポイントから提供される。
このリクエストは次のクエリパラメータを含むべきである:
amount: 支払人が送信しようとしている金額(millisats単位)
nostr: zapリクエストイベントをJSONエンコード・URIエンコードしたもの
lnurl: 受取人のlnurl-pay URLをlnurlから始まるbech32形式にエンコードしたもの
このリクエストに対し、(受取人のLNURLサーバは)prフィールドを含むJSONレスポンスを返すべきである。prフィールドはzapを完了するために支払いを行うべきインボイス。
(訳注)
インボイスはライトニングインボイスのこと。
(リクエスト処理の例: 原文参照)
別表C: LNURLサーバの設定
lnurlサーバがzapインボイスをサポートしていることをクライアントに知らせるため、追加設定が必要:
1. lnurl-pay静的エンドポイント/.well-known/lnurlp/<user>(のレスポンス)にnostrPubkeyを追加する。nostrPubkeyは、lnurlサーバがzapレシートイベントに署名する際に使う秘密鍵に対応する公開鍵。クライアントはこれをzapレシートの検証に用いる
2. allowsNostrフィールドを追加し、値をtrueに設定する
別表D: LNURLサーバによるZapリクエスト検証
クライアントがzapリクエストイベントをサーバのlnurl-payコールバックURLに送信する際、そのイベントをJSON・URIエンコードした結果を値とするnostrクエリパラメータが含まれているはず。もしnostrクエリパラメータが存在するならば、以下の方法でzapリクエストを検証しなければならない:
1. 正当な署名を持たなければならない(MUST)
2. タグを持たなければならない(MUST)
3. ただ1つのpタグを持たなければならない(MUST)
4. 0個か1個のeタグを持たなければならない(MUST)
5. zapレシートの送信先リレーを指定するrelaysタグを持つべき
(訳注) 「べき」という文末だが、別表AではMUSTとされている。
6. amountタグを持つ場合、その値はamountクエリパラメータの値と一致していなければならない(MUST)
7. aタグを持つ場合、それは妥当なイベント座標でなければならない(MUST)
8. Pタグが0個〜1個なければならない(MUST)。存在する場合、zapレシートのpubkeyと等しくなければならない(MUST)。
このイベントは後でインボイスに支払いが行われた際に使うため、保存しておかなければならない(MUST)。
別表E: Zapレシートイベント
zapレシートは、zapリクエストから生成されたインボイスに対し支払いが行われた際に、ライトニングノードが作成する。zapレシートは、インボイスのdescriptionがzapリクエストを含む場合に限り作成する。
支払いを受けたら、次のステップを実行する:
1. インボイスのdescriptionを取得する。これはdescription hash invoiceを生成するタイミングでどこかに保存しておく必要がある。参照実装であるCLN (core lightning) では自動的に保存される (訳注)インボイスのdescriptionはzapリクエストのこと。
(訳注)description hash bolt11 インボイスはライトニングインボイスのこと。生成するタイミングは(6)。別表Dも参照。
2. bolt11 descriptionをJSON形式のNostrイベントとしてパースする。これを別表Dに示した仕様に基づいて検証すべきである(SHOULD)。
(訳注)bolt11 descriptionはE1のインボイスのdescription=zapリクエストのこと。
3. 以下に示す仕様を満たす、kind9735のNostrイベントを作成し、zapリクエストのrelaysタグで指定されたリレーに送信する
zapレシートイベントは以下を満たすべきである:
contentは空であるべき(SHOULD)
created_atには、冪等性のためインボイスのpaid_atの値を設定すべき(SHOULD)
tagsにはzapリクエストと同じpタグ(zapの受取人)、zapリクエストのeタグ(任意)、zapリクエストのaタグ(任意)、zapリクエストの公開鍵(zapの支払人)を含めたPタグを含めなければならない(MUST)。
description hash bolt11インボイスを含むbolt11タグを持たなければならない(MUST)
JSONエンコードされたZapリクエストを含むdescriptionタグを持たなければならない(MUST)
descriptionのSHA256ハッシュ値はbolt11インボイスのdescription hashと一致していなければならない(MUST)
(訳注)bolt11インボイス(=ライトニングインボイス)のhタグにはzapリクエストのSHA-256ハッシュ値が含まれているはず。
bolt11インボイスのpayment hashと突合するためのpreimageタグを含めてもよい(MAY)。これは支払証明ではなく、インボイスが本物かどうかあるいは支払い済みかどうかを証明する方法はない。決済の正当性に関しては、zapレシートの作成者を信用することになる
(訳注)payment hashはライトニングインボイスのpフィールドのこと。BOLT-11を参照。 zapレシートは支払証明ではなく、あるNostrユーザがインボイスを取得したことの証明でしかない。zapレシートの存在はそのインボイスに支払いが行われたことを示唆するが、悪意ある実装のもとでは正しいとは限らない。
zapをサポートするlnurlサーバの参照実装はこちら (zapレシートの例: 原文参照)
別表F: Zapレシートの検証
クライアントはNIP-01のフィルタを使い、イベントと公開鍵に関連付けられたzapレシートを取得できる(例: {"kinds": [9735], "#e": [...]})。Zapは次の手順にしたがって検証されなければならない(MUST)。
zapレシートイベントの公開鍵は、受取人のLNURLプロバイダの(プロトコルフローの手順1で取得した)nostrPubkeyと同じでなければならない(MUST)。
zapレシートのbolt11タグに含まれるinvoiceAmountは(存在する場合には)zapリクエストのamountタグと等しくなければならない(MUST)。
zapリクエストのlnurlタグは(存在する場合には)受取人のlnurlと等しいべきである(SHOULD)。
(訳注)
「存在する場合には」がどこに掛かっているのかについて
Appendix Aにおいてzap リクエストamount, lnurlタグが「This is recommended, but optional.」とあるので存在しない場合がある。 tanakei.icon
zapリクエストはzapレシートのdescriptionタグに含まれる syusui_s.icon
zapレシートのdescriptionタグには、インボイスのdescriptionが含まれる
zapレシートは、インボイスのdescriptionがzapリクエストを含む場合に限り作成する
プロトコルの流れの(6)で作ったインボイス(BOLT-11)には、description(dタグ)としてzapレシートが含まれている invoiceAmountはライトニングインボイスに含まれる金額のこと。bolt11タグにはライトニングインボイスが含まれている。
(special thanks: syusui_s.icon)
別表G: "zap"タグ (zap分配)
イベントが1つ以上のzapタグを含む場合、クライアントはプロフィールのフィールドの代わりにそのタグの値に基づいてlnurl-payリクエストを導出するべきである(SHOULD)。
このタグの2番めの引数は受取人の公開鍵の16進数文字列で、3番目の引数(任意)は受取人のメタデータ(kind 0)をダウンロードするためのリレーとする。
4番目の引数(任意)は各受取人に割り当てる重みを指定する。クライアントはすべての重みを取得して合計を求め、受取人ごとの(重みの)割合を計算すべきである。重みが指定されていなければ、クライアントはzapをすべての受取人に均等に分配すべき。重みが一部のみ指定されている場合、重みが指定されていない受取人にはzapを届けるべきでない(重み=0とみなす)。
(zapタグの例: 原文参照)
クライアントは投稿の中にzap分配設定を表示してもよい(MAY)。
今後の展望
対象のユーザへのzapリクエストを暗号化することによって、zapをよりプライベートな方向に拡張できるが、簡単のためこの初期草案では対象外としている。
/icons/hr.icon
以下、訳注
シーケンス図 (by tanakei.icon)
https://scrapbox.io/files/65418d0c991aef001b480ca6.png
実装に関するTips
仕様について
投稿に対するZapを取得するには
{ "kinds":[9735], "#e":[投稿のID] }
ユーザに対するZapの一覧を取得するには
{ "kinds":[9735], "#p":[受取人のpubkey], "limit": 50 }
Zapの完了を検知するには
{ "kinds":[9735], "#e":[投稿のID], "#p":[受取人のpubkey], since: 現在時刻 }
ユーザにライトニングインボイスを提示して、ユーザにより支払いがされると、Zapレシートが送られる
Zapレシートのbolt11タグにそのライトニングインボイスが含まれていれば完了と見做せる
Zapレシートの検証
ライトニングインボイス(BOLT-11)のパースが必要です。別表Fにしたがって、amountを検証しなくてはいけません。 ウォレットをリンクから開く方法、QRコードを表示する方法
lightning:lnbc10n1......のようにlightning:を先頭につけて、<a>タグのhrefに含める、QRコードにする
インボイスをWallet of Satoshi等のスマートフォン向けのウォレットで読み取り、あるいは開くことができるようになります。
ウォレットを乗り換えるとLNURLが変わるので、古いZapについては検証が失敗するようになってしまいます
LNURLをパースしてLNURLサーバに問い合わせて kind:9735の署名に使われるpubkeyを取得しているため、LNURLが変われば署名に使われるpubkeyも変わる
アイデアとしては以下のようなものがあります
Zapレシートの表示する前に警告を表示する
Zapレシートのpubkeyをユーザが信頼するか、ミュートするかどうかを選ばせる
過去に特定のpubkeyから多数のZapを受け取っていた場合、信頼するかどうかをユーザに選ばせる
Alby等の一部のウォレットはブラウザ拡張経由の送金に対応しています→WebLN Nostr Wallet Connectを使うと、ウォレットアプリの画面に移ることなくZapできます→NIP-47 関連項目: