Auth login
Auth signatures prove possession of a wallet identity key. They do not prove BTC address or Lightning node ownership and do not create an application session by themselves. Identity signing currently supports regtest only. Prefer V2 for new integrations.
V2 request and response
Request shape
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
}Request origin is optional in the type, but the backend-issued and verified context must include the correct origin.
Field constraints
challenge_id: 32 lowercase hexadecimal characters.nonce: 64 lowercase hexadecimal characters, securely generated by the backend.timestamp,expires_at: Unix seconds, represented as safe integers.client_id: 1–64 characters, starting with a letter or digit; remaining characters may include letters, digits, dots, underscores, and hyphens.- Maximum challenge lifetime is 120 seconds;
expires_at > timestamp.
The backend must return a complete, validated V2 challenge. Backend callbacks in this example are application-provided, not SDK methods:
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)
}Response fields
- Identity key:
auth_pubkey, a 66-hex compressed secp256k1 public key. - Signature:
signature, a 128-hex compactr || ssignature. - Protocol and challenge:
signature_version,challenge_id,message. - Bindings:
btc_address,node_id.
V2 response context
client_id, purpose, wallet_type, network, timestamp, and expires_at are optional in the public response type. V2 verification must check required context and must not silently downgrade to V1.
Empty btc_address / node_id means unbound. Nonempty values are also not independent ownership proof merely because an Auth identity signature is present.
Canonical message and backend verification
V2 canonical format:
RGBS-AUTH-V2|{challenge_id}|{nonce}|{origin}|{network}|{wallet_type}|{client_id}|{purpose}|{timestamp}|{expires_at}Import helpers from the browser-side-effect-free protocol entry point:
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() checks format, origin, time window, and client ID. buildAuthV2Message() formats the message and performs basic shape checks. Neither verifies ECDSA signatures nor stores or consumes challenges. The server must also:
- Reconstruct context from its stored challenge, not the page-supplied message, origin, or timestamps.
- Match version, purpose, wallet type, network, and client ID against server policy.
- Reject expired, future-issued, used, or mismatching challenges.
- Verify compact secp256k1 signatures using the wallet's actual signing algorithm, not just string-format checks.
- Atomically consume the challenge after successful verification to prevent replay, then establish an application session.
Public metadata such as client_id does not prove client authenticity. JWTs, invite lists, node permissions, and trusted address admission belong to the application.
V1 compatibility
V1 requests explicitly require signature_version: 1, challenge_id, nonce, and network, with optional origin. buildCanonicalMessage(challengeId, nonce, origin, network) builds a RGBS-AUTH-V1|… message.
AUTH_PROTOCOL_VERSION / AUTH_MESSAGE_PREFIX still refer to V1, not V2. AUTH_KDF_* are legacy passphrase-path compatibility constants, not an instruction for websites to derive or obtain user identity keys. New integrations should not use that legacy KDF for website identity.
Parameter errors
requestAuthSignature() invokes assertAuthSignatureRequest() before sending. Invalid parameters may throw a plain Error('INVALID_PARAMETER'), not a TransportError. SDK request checks do not replace backend verification. See Errors and compatibility.