Auth 登录
Auth 签名证明用户持有钱包身份私钥。它不是 BTC 地址或 Lightning 节点所有权证明,也不会直接建立业务登录会话。当前身份签名仅支持 regtest;新接入优先使用 V2。
V2 请求与响应
请求结构
interface AuthSignatureRequestV2 {
signature_version: 2
challenge_id: string
nonce: string
network: Network
client_id: string
purpose: 'login'
wallet_type: 'stashwallet'
timestamp: number
expires_at: number
origin?: string
}origin 在请求类型中可选,但后端签发和验签上下文必须包含正确来源。
字段约束
challenge_id:32 位小写十六进制。nonce:64 位小写十六进制,由后端安全随机生成。timestamp、expires_at:Unix 秒,必须为安全整数。client_id:1–64 字符,首位为字母或数字,之后允许字母、数字、点、下划线、连字符。- challenge 有效期最多 120 秒;
expires_at > timestamp。
后端应返回完整且经过校验的 V2 challenge。以下示例中的后端函数由业务方实现,不属于 SDK:
import type { AuthSignatureRequestV2 } from 'stash-wallet-sdk'
async function login(
client: import('stash-wallet-sdk').StashWalletClient,
fetchChallenge: () => Promise<AuthSignatureRequestV2>,
verifyOnBackend: (
challenge: AuthSignatureRequestV2,
signed: import('stash-wallet-sdk').AuthSignatureResponse,
) => Promise<void>,
) {
const caps = await client.getCapabilities()
const { network } = await client.getNetwork()
if (!caps.canAuthSign || !caps.authNetworks.includes(network)) {
throw new Error('Auth signing is unavailable')
}
const challenge = await fetchChallenge()
if (challenge.network !== network) throw new Error('Network mismatch')
const signed = await client.requestAuthSignature(challenge)
await verifyOnBackend(challenge, signed)
}响应字段
- 身份公钥:
auth_pubkey,66 位 hex 压缩 secp256k1 公钥。 - 签名:
signature,128 位 hex compactr || s签名。 - 协议与挑战:
signature_version、challenge_id、message。 - 绑定信息:
btc_address、node_id。
V2 响应上下文
client_id、purpose、wallet_type、network、timestamp、expires_at 在公共响应类型中为可选。V2 验证时必须核对必需上下文,不能自动降级接受 V1。
空 btc_address / node_id 表示未绑定;非空也不能仅凭 Auth 身份签名当作独立的地址或节点所有权证明。
规范消息与后端验证
V2 规范消息为:
RGBS-AUTH-V2|{challenge_id}|{nonce}|{origin}|{network}|{wallet_type}|{client_id}|{purpose}|{timestamp}|{expires_at}可从无浏览器副作用的协议入口导入辅助函数:
import { buildAuthV2Message, validateAuthV2Context } from 'stash-wallet-sdk/protocol'
import type { AuthV2Context } from 'stash-wallet-sdk/protocol'
function canonicalMessage(context: AuthV2Context, expectedOrigin: string): string {
validateAuthV2Context(context, expectedOrigin)
return buildAuthV2Message(context)
}validateAuthV2Context() 检查格式、origin、时间窗口与 client ID;buildAuthV2Message() 负责消息拼接和基本格式检查。它们不执行 ECDSA 验签,也不存储或消费 challenge。 服务端还必须:
- 从服务端保存的 challenge 重建上下文,不信任网页提交的
message、origin 或时间。 - 核对版本、用途、钱包类型、网络、client ID 与服务端允许值。
- 拒绝过期、未来签发、已使用或不匹配的 challenge。
- 按钱包实际签名算法验证 compact secp256k1 签名,不能只检查字符串格式。
- 验证成功后原子地消费 challenge,防止重放;再建立业务会话。
client_id 等公共元数据本身不是业务客户端真实性证明。JWT、邀请名单、节点权限与可信地址准入由业务方处理。
V1 兼容
V1 请求必须显式提供 signature_version: 1、challenge_id、nonce、network,可选 origin。buildCanonicalMessage(challengeId, nonce, origin, network) 生成 RGBS-AUTH-V1|… 消息。
AUTH_PROTOCOL_VERSION / AUTH_MESSAGE_PREFIX 当前仍对应 V1,不能用来生成 V2 消息。AUTH_KDF_* 是旧 passphrase 路径兼容常量,不是要求业务网站派生或获取用户身份私钥。新接入不要采用旧 KDF 建立网站身份。
参数错误
requestAuthSignature() 在发送前调用 assertAuthSignatureRequest()。参数错误可能抛出普通 Error('INVALID_PARAMETER'),而非 TransportError。SDK 的请求检查不是后端验证的替代品。参见 错误与兼容性。