比特币 - NIP-47 协议

该NIP-47协议定义了一种客户端通过标准协议访问远程闪电网络钱包的方式。它描述了客户端和钱包服务之间通过Nostr中继上的E2E加密直接消息进行的通信,使用户能够进行支付、查询余额、生成发票等操作,同时支持钱包事件的通知。

NIP-47

Nostr Wallet Connect (NWC)

草案 可选

理由

这个 NIP 描述了一种客户端通过标准化协议访问远程闪电钱包的方法。托管方可以实现它,或者用户可以运行一个桥接器,桥接他们的钱包/节点和 Nostr Wallet Connect 协议。

术语

  • client:任何平台上想要与闪电钱包交互的 Nostr 应用程序。
  • user:使用 client 并希望将其钱包连接到 client 的人。
  • wallet service:通常在始终在线的计算机上运行的 Nostr 应用程序(例如,在云端或在 Raspberry Pi 上)。此应用程序可以访问它所服务的钱包的 API。

运行原理

从根本上说,NWC 是 clientwallet service 之间通过 Nostr 中继上的 E2E 加密直接消息进行的通信。中继知道 note 的种类和标签,但不知道加密 payload 的内容。不使用 user 的身份密钥,以避免将支付活动与用户联系起来。理想情况下,每个单独的连接都使用唯一的密钥。

  1. 希望使用此 NIP 允许 client(s) 与其钱包交互的 users 必须首先从兼容 NIP-47 的钱包应用程序获取一个特殊的“连接” URI。钱包应用程序可以使用 QR 屏幕、可粘贴的字符串或其他方式来提供此 URI。

  2. 然后,user 应该通过粘贴或扫描 QR 等方式将此 URI 复制到他们的 client(s) 中。client(s) 应该保存此 URI,并在以后每当 user(或代表用户的 client)想要与钱包交互时使用它。然后,client 应该从 URI 中指定的 relay 请求一个 info (13194) 事件。wallet service 之前会将该事件发送到这些 relay,并且这些 relay 会将其作为可替换事件保存。

  3. user 发起支付时,他们的 nostr client 创建一个 pay_invoice 请求,使用 URI 中的 token 对其进行加密,并将其(kind 23194)发送到连接 URI 中指定的 relay。wallet service 将会监听这些 relay,解密请求,然后联系 user 的钱包应用程序以发送支付。wallet service 将知道如何与钱包应用程序通信,因为连接 URI 指定了可以访问钱包应用程序 API 的 relay。

  4. 支付完成后,wallet service 将通过 URI 中的 relay 向 user 发送加密的 response(kind 23195)。

  5. wallet service 可能会向 client 发送有关钱包事件(例如收到的付款)的加密通知(kind 23196)。

事件

有四种事件类型:

  • NIP-47 info event: 13194
  • NIP-47 request: 23194
  • NIP-47 response: 23195
  • NIP-47 notification event: 23196

Info Event

info 事件应该是由 wallet service 在 relay 上发布的可替换事件,以表明它支持哪些功能。

内容应该是一个纯文本字符串,其中包含以空格分隔的支持的功能,例如 pay_invoice get_balance notifications

如果 wallet service 支持通知,则 info 事件应该包含一个 notifications 标签,其中包含以空格分隔的支持的通知类型,例如 payment_received payment_sent

Request 和 Response 事件

request 和 response 事件都应该包含一个 p 标签,如果这是 request,则包含 wallet service 的公钥;如果这是 response,则包含 client 的公钥。response 事件应该包含一个 e 标签,其中包含它正在响应的 request 事件的 id。 (可选)request 可以有一个 expiration 标签,其中包含以秒为单位的 unix 时间戳。如果在此时间戳之后收到 request,则应忽略它。

request 和 response 的内容使用 NIP04 加密,并且是一个具有半固定结构的类 JSON-RPC 对象:

Request:

{
    "method": "pay_invoice", // method, string
    "params": { // params, object
        "invoice": "lnbc50n1..." // command-related data
    }
}

Response:

{
    "result_type": "pay_invoice", //表明 result 字段的结构
    "error": { //object, 在出错的情况下非空
        "code": "UNAUTHORIZED", //string 错误代码,见下文
        "message": "human readable error message"
    },
    "result": { // result, object. 在出错的情况下为空。
        "preimage": "0123456789abcdef..." // command-related data
    }
}

result_type 字段必须包含此事件正在响应的方法的名称。 如果命令不成功,error 字段必须包含一个带有可读错误消息的 message 字段和一个带有错误代码的 code 字段。 如果命令成功,则 error 字段必须为空。

Notification Events

notification 事件应该包含一个 p 标签,即 client 的公钥。

notification 的内容使用 NIP04 加密,并且是一个具有半固定结构的类 JSON-RPC 对象:

{
    "notification_type": "payment_received", //表明 notification 字段的结构
    "notification": {
        "payment_hash": "0123456789abcdef..." // notification 相关的数据
    }
}

错误代码

  • RATE_LIMITED:客户端发送命令的速度太快。它应该在几秒钟后重试。
  • NOT_IMPLEMENTED:该命令未知或故意未实现。
  • INSUFFICIENT_BALANCE:钱包没有足够的资金来支付手续费或支付金额。
  • QUOTA_EXCEEDED:钱包已超过其支出限额。
  • RESTRICTED:不允许此公钥执行此操作。
  • UNAUTHORIZED:此公钥没有连接钱包。
  • INTERNAL:内部错误。
  • OTHER:其他错误。

Nostr Wallet Connect URI

clientwallet service 之间的通信需要两个密钥才能加密和解密消息。连接 URI 包括 client 的私钥和 wallet service 的公钥。

client 通过扫描 QR 码、处理 deeplink 或粘贴 URI 来发现 wallet service

wallet service 生成此连接 URI,协议为 nostr+walletconnect://,基本路径为其 32 字节的十六进制编码 pubkey,对于每个客户端连接都应该是唯一的。

连接 URI 包含以下查询字符串参数:

  • relay 必需。wallet service 连接并将侦听事件的中继的 URL。可以不止一个。
  • secret 必需。32 字节的随机生成的十六进制编码字符串。client 必须使用它来签名事件并在与 wallet service 通信时加密 payload。wallet service 必须使用此 secret 对应的公钥与 client 通信。
    • 授权不需要来回传递密钥。
    • 用户可以为不同的应用程序使用不同的密钥。可以随意撤销和创建密钥,并具有任意约束(例如,预算)。
    • 密钥更难泄露,因为它不会显示给用户并进行备份。
    • 它提高了隐私,因为用户的主要密钥不会链接到他们的付款。
  • lud16 推荐。一个闪电地址,客户端可以使用它来自动设置用户个人资料上的 lud16 字段(如果他们没有配置)。

然后,client 应该存储此连接,并在用户想要执行支付发票等操作时使用它。由于此 NIP 使用临时事件,因此建议选择不关闭非活动连接的中继,以避免丢弃事件,理想情况下,保留事件直到它们被使用或变得陈旧。

  • client 发送或接收消息时,它将使用连接 URI 中的 secretwallet servicepubkey 来加密或解密。
  • wallet service 发送或接收消息时,它将使用自己的 secret 和 clientsecret 对应的公钥来加密或解密。wallet service 不应存储它为客户端生成的 secret,并且不得依赖于知道 client secret 来进行常规操作。

连接字符串示例

nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c1407c113601079c4302dab36460f0ccd0ad506f1f2dc73b5100e4f3c

命令

pay_invoice

描述:请求支付发票。

Request:

{
    "method": "pay_invoice",
    "params": {
        "invoice": "lnbc50n1...", // bolt11 invoice
        "amount": 123, // invoice amount in msats, optional
    }
}

Response:

{
    "result_type": "pay_invoice",
    "result": {
        "preimage": "0123456789abcdef...", // preimage of the payment
        "fees_paid": 123, // value in msats, optional
    }
}

Errors:

  • PAYMENT_FAILED:支付失败。这可能是由于超时、耗尽所有路由、容量不足或类似原因造成的。

multi_pay_invoice

描述:请求支付多个发票。

Request:

{
    "method": "multi_pay_invoice",
    "params": {
        "invoices": [
          {"id":"4da52c32a1", "invoice": "lnbc1...", "amount": 123}, // bolt11 invoice and amount in msats, amount is optional
          {"id":"3da52c32a1", "invoice": "lnbc50n1..."},
        ],
    }
}

Response:

对于请求中的每个发票,都会发送单独的 response 事件。为了区分 response,每个 response 事件都包含一个 d 标签,其中包含它所响应的发票的 id;如果没有给出 id,则应使用发票的支付哈希。

{
    "result_type": "multi_pay_invoice",
    "result": {
        "preimage": "0123456789abcdef...", // preimage of the payment
        "fees_paid": 123, // value in msats, optional
    }
}

Errors:

  • PAYMENT_FAILED:支付失败。这可能是由于超时、耗尽所有路由、容量不足或类似原因造成的。

pay_keysend

Request:

{
    "method": "pay_keysend",
    "params": {
        "amount": 123, // invoice amount in msats, required
        "pubkey": "03...", // payee pubkey, required
        "preimage": "0123456789abcdef...", // preimage of the payment, optional
        "tlv_records": [ // tlv records, optional
            {
                "type": 5482373484, // tlv type
                "value": "0123456789abcdef" // hex encoded tlv value
            }
        ]
    }
}

Response:

{
    "result_type": "pay_keysend",
    "result": {
        "preimage": "0123456789abcdef...", // preimage of the payment
        "fees_paid": 123, // value in msats, optional
    }
}

Errors:

  • PAYMENT_FAILED:支付失败。这可能是由于超时、耗尽所有路由、容量不足或类似原因造成的。

multi_pay_keysend

描述:请求多个 keysend 支付。

具有一个 keysend 数组,这些 keysend 遵循与 pay_keysend 相同的语义,只是以批处理方式完成

Request:

{
    "method": "multi_pay_keysend",
    "params": {
        "keysends": [
          {"id": "4c5b24a351", "pubkey": "03...", "amount": 123},
          {"id": "3da52c32a1", "pubkey": "02...", "amount": 567, "preimage": "abc123..", "tlv_records": [{"type": 696969, "value": "77616c5f6872444873305242454d353736"}]},
        ],
    }
}

Response:

对于请求中的每个 keysend,都会发送单独的 response 事件。为了区分 response,每个 response 事件都包含一个 d 标签,其中包含它所响应的 keysend 的 id;如果没有给出 id,则应使用 pubkey。

{
    "result_type": "multi_pay_keysend",
    "result": {
        "preimage": "0123456789abcdef...", // preimage of the payment
        "fees_paid": 123, // value in msats, optional
    }
}

Errors:

  • PAYMENT_FAILED:支付失败。这可能是由于超时、耗尽所有路由、容量不足或类似原因造成的。

make_invoice

Request:

{
    "method": "make_invoice",
    "params": {
        "amount": 123, // value in msats
        "description": "string", // invoice's description, optional
        "description_hash": "string", // invoice's description hash, optional
        "expiry": 213 // expiry in seconds from time invoice is created, optional
    }
}

Response:

{
    "result_type": "make_invoice",
    "result": {
        "type": "incoming", // "incoming" for invoices, "outgoing" for payments
        "invoice": "string", // encoded invoice, optional
        "description": "string", // invoice's description, optional
        "description_hash": "string", // invoice's description hash, optional
        "preimage": "string", // payment's preimage, optional if unpaid
        "payment_hash": "string", // Payment hash for the payment
        "amount": 123, // value in msats
        "fees_paid": 123, // value in msats
        "created_at": unixtimestamp, // invoice/payment creation time
        "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable
        "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
    }
}

lookup_invoice

Request:

{
    "method": "lookup_invoice",
    "params": {
        "payment_hash": "31afdf1..", // payment hash of the invoice, one of payment_hash or invoice is required
        "invoice": "lnbc50n1..." // invoice to lookup
    }
}

Response:

{
    "result_type": "lookup_invoice",
    "result": {
        "type": "incoming", // "incoming" for invoices, "outgoing" for payments
        "invoice": "string", // encoded invoice, optional
        "description": "string", // invoice's description, optional
        "description_hash": "string", // invoice's description hash, optional
        "preimage": "string", // payment's preimage, optional if unpaid
        "payment_hash": "string", // Payment hash for the payment
        "amount": 123, // value in msats
        "fees_paid": 123, // value in msats
        "created_at": unixtimestamp, // invoice/payment creation time
        "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable
        "settled_at": unixtimestamp, // invoice/payment settlement time, optional if unpaid
        "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
    }
}

Errors:

  • NOT_FOUND: 无法通过给定的参数找到发票。

list_transactions

列出发票和付款。如果未指定 type,则返回发票和付款。 fromuntil 参数是以秒为单位的自 epoch 以来的时间戳。如果未指定 from,则默认为 0。 如果未指定 until,则默认为当前时间。事务按创建时间的降序返回。

Request:

{
    "method": "list_transactions",
    "params": {
        "from": 1693876973, // starting timestamp in seconds since epoch (inclusive), optional
        "until": 1703225078, // ending timestamp in seconds since epoch (inclusive), optional
        "limit": 10, // maximum number of invoices to return, optional
        "offset": 0, // offset of the first invoice to return, optional
        "unpaid": true, // include unpaid invoices, optional, default false
        "type": "incoming", // "incoming" for invoices, "outgoing" for payments, undefined for both
    }
}

Response:

{
    "result_type": "list_transactions",
    "result": {
        "transactions": [
            {
               "type": "incoming", // "incoming" for invoices, "outgoing" for payments
               "invoice": "string", // encoded invoice, optional
               "description": "string", // invoice's description, optional
               "description_hash": "string", // invoice's description hash, optional
               "preimage": "string", // payment's preimage, optional if unpaid
               "payment_hash": "string", // Payment hash for the payment
               "amount": 123, // value in msats
               "fees_paid": 123, // value in msats
               "created_at": unixtimestamp, // invoice/payment creation time
               "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable
               "settled_at": unixtimestamp, // invoice/payment settlement time, optional if unpaid
               "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
           }
        ],
    },
}

get_balance

Request:

{
    "method": "get_balance",
    "params": {}
}

Response:

{
    "result_type": "get_balance",
    "result": {
        "balance": 10000, // user's balance in msats
    }
}

get_info

Request:

{
    "method": "get_info",
    "params": {}
}

Response:

{
    "result_type": "get_info",
    "result": {
            "alias": "string",
            "color": "hex string",
            "pubkey": "hex string",
            "network": "string", // mainnet, testnet, signet, or regtest
            "block_height": 1,
            "block_hash": "hex string",
            "methods": ["pay_invoice", "get_balance", "make_invoice", "lookup_invoice", "list_transactions", "get_info"], // list of supported methods for this connection
            "notifications": ["payment_received", "payment_sent"], // list of supported notifications for this connection, optional.
    }
}

通知

payment_received

描述:钱包已成功收到付款。

Notification:

{
    "notification_type": "payment_received",
    "notification": {
        "type": "incoming",
        "invoice": "string", // encoded invoice
        "description": "string", // invoice's description, optional
        "description_hash": "string", // invoice's description hash, optional
        "preimage": "string", // payment's preimage
        "payment_hash": "string", // Payment hash for the payment
        "amount": 123, // value in msats
        "fees_paid": 123, // value in msats
        "created_at": unixtimestamp, // invoice/payment creation time
        "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable
        "settled_at": unixtimestamp, // invoice/payment settlement time
        "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
    }
}

payment_sent

描述:钱包已成功发送付款。

Notification:

{
    "notification_type": "payment_sent",
    "notification": {
        "type": "outgoing",
        "invoice": "string", // encoded invoice
        "description": "string", // invoice's description, optional
        "description_hash": "string", // invoice's description hash, optional
        "preimage": "string", // payment's preimage
        "payment_hash": "string", // Payment hash for the payment
        "amount": 123, // value in msats
        "fees_paid": 123, // value in msats
        "created_at": unixtimestamp, // invoice/payment creation time
        "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable
        "settled_at": unixtimestamp, // invoice/payment settlement time
        "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
    }
}

支付发票流程示例

  1. 用户使用他们的 client 应用程序扫描 wallet service 生成的 QR 码,他们遵循 nostr+walletconnect:// deeplink 或手动配置连接详细信息。
  2. clientwallet service 发送一个 kind 为 23194 的事件。内容是一个 pay_invoice 请求。私钥是上面连接字符串中的 secret。
  3. wallet service 验证作者的密钥是否已授权执行付款,解密 payload 并发送付款。
  4. wallet service 通过发送一个 kind 为 23195 的事件来响应事件,并且内容是一个 response,其中包含错误消息或 preimage。

使用专用中继

此 NIP 没有指定对所用中继类型的任何要求。但是,如果用户正在使用托管服务,则使用由托管服务托管的中继可能是有意义的。然后,中继可以强制执行身份验证以防止元数据泄漏。在这种情况下,不依赖第三方中继也可以提高可靠性。

附录

NIP-47 info 事件示例

{
  "id": "df467db0a9f9ec77ffe6f561811714ccaa2e26051c20f58f33c3d66d6c2b4d1c",
  "pubkey": "c04ccd5c82fc1ea3499b9c6a5c0a7ab627fbe00a0116110d4c750faeaecba1e2",
  "created_at": 1713883677,
  "kind": 13194,
  "tags": [
    [
      "notifications",
      "payment_received payment_sent"
    ]
  ],
  "content": "pay_invoice pay_keysend get_balance get_info make_invoice lookup_invoice list_transactions multi_pay_invoice multi_pay_keysend sign_message notifications",
  "sig": "31f57b369459b5306a5353aa9e03be7fbde169bc881c3233625605dd12f53548179def16b9fe1137e6465d7e4d5bb27ce81fd6e75908c46b06269f4233c845d8"
}
  • 原文链接: github.com/nostr-protoco...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
nostr-protocol
nostr-protocol
江湖只有他的大名,没有他的介绍。