LUD-04 描述了一种使用比特币钱包进行授权的规范,允许用户通过linkingKey
登录服务或授权敏感操作,而无需泄露用户身份。该规范包括服务端生成授权URL和签名验证的流程,以及钱包与服务交互的详细步骤,通过linkingKey
来保证用户在一个服务上的身份一致性。
auth
基础规范作者: akumaigorodski
一个特殊的 linkingKey
可以被用来让用户登录服务或授权敏感操作。这最好在不泄露用户身份的情况下完成,所以普通的 LN 节点密钥不能在这里使用。服务不应该要求用户凭证,而是可以展示一个包含特定 LNURL
的“登录”二维码。
当创建一个 LNURL-auth
处理程序时,LN SERVICE
必须在其中包含一个 k1
查询参数,该参数由随机生成的 32 字节数据组成,以及可选的 action
枚举,例如 https://site.com?tag=login&k1=hex(32 bytes of random data)&action=login
。
之后,一旦 LN SERVICE
在指定的 LNURL-auth
处理程序处收到调用,它必须获取 k1
,压缩的 (33 字节) secp256k1
公钥 key
编码为十六进制,以及 DER 十六进制编码的 ECDSA sig
,并使用 secp256k1
验证签名。一旦签名被成功验证,用户提供的 key
可以被用作标识符,并可以存储在会话、数据库或 LN SERVICE
认为合适的任何地方。
LN SERVICE
必须确保不接受意外的 k1
:强烈建议 LN SERVICE
拥有未使用的 k1
的缓存,仅继续验证该缓存中存在的 k1
,并在成功授权尝试时移除已使用的 k1
。
LN SERVICE
应该仔细选择哪个子域名(如果有)将用作 LNURL-auth 端点,并在将来坚持使用所选的子域名。例如,如果最初选择了 auth.site.com
,那么将其更改为 login.site.com
将导致每个用户都有不同的帐户,因为钱包使用完整的域名作为密钥派生的材料。
LN SERVICE
应该考虑为所选的子域名提供有意义的名称,因为 LN WALLET
可能会在登录尝试时向用户显示完整的域名。例如,auth.site.com
比 ksf03.site.com
更不容易混淆。
LN WALLET
扫描一个二维码并解码一个 URL,该 URL 应该具有以下查询参数:
tag
的值设置为 login
,这意味着还不应该进行 GET 请求。k1
(十六进制编码的 32 字节挑战),它将由用户的 linkingPrivKey
签名。action
枚举,可以是以下四个字符串之一:register | login | link | auth
。LN WALLET
显示一个“登录”对话框,其中必须包含从 LNURL
查询字符串中提取的域名和 action
枚举(如果存在 action
查询参数,则将其翻译成人类可读的文本)。LN WALLET
使用 linkingPrivKey
在 secp256k1
上签名 k1
,并对签名进行 DER 编码。然后,LN WALLET
使用 <LNURL_hostname_and_path>?<LNURL_existing_query_parameters>&sig=<hex(sign(hexToBytes(k1), linkingPrivKey))>&key=<hex(linkingKey)>
向 LN SERVICE
发出 GET 请求。LN SERVICE
将回复以下 JSON:
{"status": "OK"}
或
{"status": "ERROR", "reason": "error details..."}
action
枚举的含义:
register
:服务将创建一个链接到用户 linkingKey
的新帐户。login
:服务将用户登录到链接到用户 linkingKey
的现有帐户。link
:服务将用户提供的 linkingKey
链接到用户的现有帐户(如果该帐户最初不是使用 lnurl-auth
创建的)。auth
:将授予一些不需要登录(甚至可能不需要事先注册)的无状态操作。linkingKey
派生LNURL-auth
的工作原理是从用户种子中派生特定于域名的 linkingKey
。这种方法有两个目标:第一个是简单性(用户只需要保留助记词即可保留资金和身份),第二个是可移植性(用户应该能够通过输入相同的助记词来切换钱包并获得相同的身份)。
然而,第二个目标在实践中是无法实现的,因为存在不同的种子格式,无法在所有现有钱包中传输。因此,一个实际的方法是为不同的钱包类型提供推荐的 linkingKey
派生方式。
在 Python 中
from binascii import unhexlify
from secp256k1 import PublicKey
k1 = unhexlify('e2af6254a8df433264fa23f67eb8188635d15ce883e8fc020989d5f82ae6f11e')
key = unhexlify('02c3b844b8104f0c1b15c507774c9ba7fc609f58f343b9b149122e944dd20c9362')
sig = unhexlify('304402203767faf494f110b139293d9bab3c50e07b3bf33c463d4aa767256cd09132dc5102205821f8efacdb5c595b92ada255876d9201e126e2f31a140d44561cc1f7e9e43d')
pubkey = PublicKey(key, raw=True)
sig_raw = pubkey.ecdsa_deserialize(sig)
r = pubkey.ecdsa_verify(k1, sig_raw, raw=True)
assert r == True
- 原文链接: github.com/lnurl/luds/bl...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!