BRC-31 (implemented via BRC-103
Peer+ BRC-104 HTTP transport) enables cryptographic handshakes between client and server. Both parties prove control of identity keys using signatures, and authenticated application messages are signed and verified. No shared password is required.
| Field | Value |
|---|---|
| Format | AsyncAPI 3.0 |
| Version | 1.0.0 |
| Status | stable |
| Implementations | @bsv/auth-express-middleware, @bsv/authsocket |
Verifiable peer identity without a conventional account token. BRC-31 uses signatures to prove control of an identity private key. Certificate trust and application authorization remain separate concerns. ECDSA is not post-quantum cryptography.
Replay resistance with session state. Session nonces and a fresh 32-byte request ID are bound into signed request/response payloads. The SDK session manager must preserve and validate that state; a nonce string alone is not an expiring single-use store. The signed HTTP payload does not introduce a separate timestamp field.
Certificate exchange. The handshake optionally requests verifiable certificates (e.g., age verification, credential proofs) that the client provides. The server can validate these against known certifiers, enabling selective disclosure (e.g., "prove you're over 18" without revealing your actual birthdate).
Two-phase handshake (followed by authenticated requests):
Phase 1 — Initial Exchange (non-general)
Client → Server POST /.well-known/auth with initialRequest message
x-bsv-auth-identity-key headerx-bsv-auth-nonce headerx-bsv-auth-signature headerServer → Server (validates nonce, generates its own nonce)
initialResponse messagex-bsv-auth-identity-key response headerx-bsv-auth-nonce response headerx-bsv-auth-signature headerx-bsv-auth-requested-certificates header with certificate typesClient → Server (if server requested certificates)
POST /.well-known/auth with certificate payloadPhase 2 — General Authenticated Requests
After handshake succeeds, every request/response carries:
Request headers:
x-bsv-auth-version — protocol versionx-bsv-auth-identity-key — client's public keyx-bsv-auth-nonce — server's last noncex-bsv-auth-your-nonce — client's last noncex-bsv-auth-request-id — fresh random 32-byte valuex-bsv-auth-signature — ECDSA signature over requestId || method || path || headers || bodyResponse headers (same pattern, server signs):
x-bsv-auth-identity-key, x-bsv-auth-nonce, x-bsv-auth-your-nonce, x-bsv-auth-request-id, x-bsv-auth-signaturerequestId || statusCode || headers || body| Channel | Direction | Message Type | Purpose |
|---|---|---|---|
/.well-known/auth | Request | initialRequest | Client initiates handshake with nonce |
/.well-known/auth | Response | initialResponse | Server validates, responds with nonce + signature |
/.well-known/auth | Request | certificatePayload | Client provides certificates if server requested |
| Any route | Request | general | Authenticated application request (after handshake) |
| Any route | Response | general | Authenticated application response |
import express from 'express'
import { createAuthMiddleware } from '@bsv/auth-express-middleware'
import { PrivateKey, ProtoWallet } from '@bsv/sdk'
// 1. Create wallet for signing/verifying
const wallet = new ProtoWallet(PrivateKey.fromHex(process.env.SERVER_PRIVATE_KEY!))
// 2. Create auth middleware
const authMiddleware = createAuthMiddleware({
wallet,
allowUnauthenticated: false // Reject unauthenticated requests with 401
})
const app = express()
app.use(express.json())
app.use(authMiddleware) // Install middleware early
// 3. Routes now have req.auth.identityKey set to client's public key
app.get('/protected', (req, res) => {
res.json({
message: `Hello, ${req.auth.identityKey}`,
authenticated: true
})
})
app.listen(3000)Client-side (using AuthFetch from @bsv/sdk):
import { AuthFetch } from '@bsv/sdk'
const authFetch = new AuthFetch(walletClient)
// Handshake + signature happen transparently
const response = await authFetch.fetch('https://server.com/protected', {
method: 'GET'
})
const data = await response.json()
console.log(data.message) // "Hello, 025706528f0f6894b2ba505007267ccff1133e004452a1f6b72ac716f246216366"import http from 'http'
import { AuthSocketServer } from '@bsv/authsocket'
const server = http.createServer()
// 1. Wrap with BRC-103 authentication
const io = new AuthSocketServer(server, {
wallet: serverWallet,
cors: { origin: '*' }
})
// 2. Listen for authenticated connections
io.on('connection', socket => {
console.log('Authenticated:', socket.id)
// All messages from this socket are auto-verified
socket.on('chatMessage', msg => {
console.log('Message verified:', msg)
})
})
server.listen(3000)Client connects with BRC-103 handshake; all WebSocket messages are signed/verified.
BRC-31-related portable coverage currently lives in conformance/vectors/messaging/brc31/authrite-signature.json:
| Package | Notes |
|---|---|
| @bsv/auth-express-middleware | Express.js middleware for HTTP BRC-31 authentication; intercepts response methods to sign responses |
| @bsv/authsocket | Socket.IO wrapper adding BRC-31 authentication to WebSocket connections |
| @bsv/sdk | Peer and Transport abstractions; AuthFetch client implementation |