AsyncAPI 3.0 specification for the BRC-31 mutual authentication protocol as implemented in:
packages/middleware/auth-express-middleware/src/index.ts
(ExpressTransport, createAuthMiddleware)packages/messaging/authsocket/src/SocketServerTransport.ts
(SocketServerTransport)@bsv/sdk Peer and Transport interfacesBRC-31 mutual authentication uses ECDH-derived key pairs to create a shared, forward-secret session between two parties. Neither party trusts the other's identity until the signed handshake is verified.
Phase 1 — Non-general (initial exchange)
Carried over the special endpoint POST /.well-known/auth for HTTP
transports, or the authMessage Socket.IO event for WebSocket transports.
Client → Server initialRequest
Client generates a fresh nonce, signs it with its identity key, and
sends the auth message. Headers on the HTTP path:
x-bsv-auth-version: <version>
x-bsv-auth-identity-key: <clientPubKeyHex>
x-bsv-auth-nonce: <base64Nonce>
Body (HTTP): the AuthMessage JSON object.
Server → Client initialResponse
Server validates the client's nonce/signature, generates its own nonce,
signs the response, and returns the AuthMessage. HTTP response headers:
x-bsv-auth-version: <version>
x-bsv-auth-message-type: initialResponse
x-bsv-auth-identity-key: <serverPubKeyHex>
x-bsv-auth-nonce: <base64ServerNonce>
x-bsv-auth-your-nonce: <base64ClientNonce>
x-bsv-auth-signature: <hexDERSignature>
x-bsv-auth-requested-certificates: <JSON> (optional)
Phase 2 — General (authenticated request/response)
Once the session is established all subsequent HTTP requests carry:
x-bsv-auth-version: <version>
x-bsv-auth-identity-key: <clientPubKeyHex>
x-bsv-auth-nonce: <base64Nonce>
x-bsv-auth-your-nonce: <base64ServerNonce>
x-bsv-auth-request-id: <base64RequestId>
x-bsv-auth-signature: <hexDERSignature>
The signed payload includes: requestId || method || pathname || search || headers (sorted) || body.
The server responds with:
x-bsv-auth-version: <version>
x-bsv-auth-identity-key: <serverPubKeyHex>
x-bsv-auth-nonce: <base64Nonce>
x-bsv-auth-your-nonce: <base64ClientNonce>
x-bsv-auth-request-id: <base64RequestId>
x-bsv-auth-signature: <hexDERSignature>
The signed response payload includes: requestId || statusCode || headers (sorted) || body.
If the server declares certificatesToRequest, it embeds the request set
in the x-bsv-auth-requested-certificates header of the initialResponse.
The client then provides certificates in a follow-up /.well-known/auth
call before the next() middleware proceeds. The server waits up to
30 seconds; timeout returns 408.
If allowUnauthenticated: true is set in middleware options, requests
without auth headers proceed with req.auth.identityKey = 'unknown'.
ExpressTransport intercepts res.status, res.json, res.send,
res.set, res.end, res.sendFile, and res.text to buffer the
response until after Peer.toPeer signs and re-emits it. Original
methods are saved as res.__status, res.__json, etc.RequestId is a 32-byte random value encoded as base64.SessionManager stores seen nonces to
prevent replay.HTTP endpoint for BRC-31 non-general (initial handshake) messages. General (authenticated) messages use normal application paths.
HTTP channel used for Phase 1 (non-general) BRC-31 handshake messages.
The client POSTs an AuthMessage JSON body; the server replies with an
AuthMessage JSON body and the x-bsv-auth-* response headers.
In Socket.IO transports the same exchange happens over the authMessage
Socket.IO event (see authsocket-asyncapi.yaml) rather than this HTTP
endpoint.
Client initiates BRC-31 handshake (Phase 1, step 1).
Client POSTs an initialRequest AuthMessage to /.well-known/auth.
The body is a JSON-serialized AuthMessage with messageType: initialRequest.
Available only on servers:
Accepts the following message:
Client initiates the BRC-31 handshake.
The client generates a nonce, signs it, and sends the
initialRequest AuthMessage as the POST body.
The core BRC-31 message envelope. Transported over HTTP bodies
(at /.well-known/auth) or Socket.IO authMessage events.
HTTP request headers sent by the client when initiating the BRC-31
handshake at POST /.well-known/auth.
{
"messageType": "initialRequest",
"version": "0.1",
"identityKey": "028d37b941208cd6b8a4c28288eda5f2f16c2b3ab0fcb6d13c18b47fe37b971fc1",
"nonce": "dGVzdE5vbmNlMTIzNA==",
"initialNonce": "dGVzdE5vbmNlMTIzNA==",
"payload": [],
"signature": []
}
{
"x-bsv-auth-version": "string",
"x-bsv-auth-identity-key": "string",
"x-bsv-auth-nonce": "string"
}
HTTP channel used for Phase 1 (non-general) BRC-31 handshake messages.
The client POSTs an AuthMessage JSON body; the server replies with an
AuthMessage JSON body and the x-bsv-auth-* response headers.
In Socket.IO transports the same exchange happens over the authMessage
Socket.IO event (see authsocket-asyncapi.yaml) rather than this HTTP
endpoint.
Client receives the server's Phase 1 challenge response.
Server replies with initialResponse. If requestedCertificates is
non-empty the client must send a follow-up request with the required
certificates before proceeding to Phase 2.
Available only on servers:
Accepts the following message:
Server completes Phase 1 of the BRC-31 handshake.
The server validates the client's nonce/signature, generates its own
nonce, signs the response, and replies. The body is an AuthMessage
JSON object. Response headers carry the x-bsv-auth-* fields.
If certificatesToRequest is configured, the
x-bsv-auth-requested-certificates header contains the JSON-encoded
request set. The client must supply certificates in a follow-up
POST before the session is fully established.
The core BRC-31 message envelope. Transported over HTTP bodies
(at /.well-known/auth) or Socket.IO authMessage events.
HTTP response headers set by the server completing Phase 1 of the BRC-31 handshake.
{
"messageType": "initialRequest",
"version": "string",
"identityKey": "string",
"nonce": "string",
"yourNonce": "string",
"initialNonce": "string",
"payload": [
255
],
"signature": [
255
],
"requestedCertificates": {}
}
{
"x-bsv-auth-version": "string",
"x-bsv-auth-message-type": "initialResponse",
"x-bsv-auth-identity-key": "string",
"x-bsv-auth-nonce": "string",
"x-bsv-auth-your-nonce": "string",
"x-bsv-auth-signature": "string",
"x-bsv-auth-requested-certificates": "string"
}
Every authenticated application HTTP request (Phase 2). The path is
the actual application endpoint (e.g. /sendMessage, /listMessages).
The auth headers are attached alongside any application-specific headers.
Client sends an authenticated application request (Phase 2).
Any application HTTP request after the handshake. The client attaches
x-bsv-auth-* headers and a fresh signed payload covering the full
request. The server uses buildAuthMessageFromRequest to reconstruct
and verify the payload.
Available only on servers:
The application route path (e.g. /sendMessage).
Accepts the following message:
Authenticated application request.
The client sends application-level request headers and body, augmented
with x-bsv-auth-* mutual-auth headers. The x-bsv-auth-signature
covers the entire request (method, path, query string, signed headers,
and body) as described in RequestAuthPayload.
Application-defined request body (any content type).
HTTP request headers sent by the client for every authenticated application request (Phase 2).
{
"x-bsv-auth-version": "string",
"x-bsv-auth-identity-key": "string",
"x-bsv-auth-nonce": "string",
"x-bsv-auth-your-nonce": "string",
"x-bsv-auth-request-id": "string",
"x-bsv-auth-signature": "string"
}
Every authenticated application HTTP response (Phase 2). The server signs the response status code, relevant headers, and body before sending.
Client receives the server's authenticated application response (Phase 2).
The server's response includes x-bsv-auth-* headers and a signature
over the response payload. The client can verify the response origin
using buildResponsePayload semantics.
Available only on servers:
The application route path.
Accepts the following message:
Authenticated application response.
The server buffers the handler's response via ResponseWriterWrapper,
calls Peer.toPeer to sign it, and flushes the signed response
including x-bsv-auth-* response headers. The x-bsv-auth-signature
covers requestId || statusCode || signed response headers || body.
Application-defined response body.
HTTP response headers set by the server on every authenticated application response (Phase 2).
{
"x-bsv-auth-version": "string",
"x-bsv-auth-identity-key": "string",
"x-bsv-auth-nonce": "string",
"x-bsv-auth-your-nonce": "string",
"x-bsv-auth-request-id": "string",
"x-bsv-auth-signature": "string"
}
Error responses emitted by the auth middleware when authentication fails.
Client receives an auth error from the middleware.
Available only on servers:
The path at which the error was encountered.
Accepts one of the following messages:
401 — mutual auth failed (no or bad auth headers).
Returned when mutual authentication fails (no or bad auth headers).
{
"status": "error",
"code": "UNAUTHORIZED",
"message": "string"
}
408 — server waited 30 s for client certificates and timed out.
Returned when the server is waiting for client certificates and the 30-second timeout elapses.
{
"status": "error",
"code": "CERTIFICATE_TIMEOUT",
"message": "string"
}
500 — server failed to sign its response payload.
Returned when the server fails to sign its response payload
(ERR_RESPONSE_SIGNING_FAILED).
{
"status": "error",
"code": "ERR_RESPONSE_SIGNING_FAILED",
"description": "string"
}
Compressed secp256k1 public key, 66 hex characters.
Base64-encoded binary data.
Hex-encoded binary data.
initialRequest — first message from the initiating partyinitialResponse — response from the receiving party completing Phase 1general — signed application message (Phase 2)The core BRC-31 message envelope. Transported over HTTP bodies
(at /.well-known/auth) or Socket.IO authMessage events.
HTTP request headers sent by the client when initiating the BRC-31
handshake at POST /.well-known/auth.
HTTP response headers set by the server completing Phase 1 of the BRC-31 handshake.
HTTP request headers sent by the client for every authenticated application request (Phase 2).
HTTP response headers set by the server on every authenticated application response (Phase 2).
Returned when mutual authentication fails (no or bad auth headers).
Returned when the server is waiting for client certificates and the 30-second timeout elapses.
Returned when the server fails to sign its response payload
(ERR_RESPONSE_SIGNING_FAILED).
Informational schema: the signed payload for a general REQUEST.
Assembled by buildAuthMessageFromRequest in ExpressTransport.
Wire encoding (binary, concatenated):
| Field | Encoding |
|---|---|
| requestId | 32 raw bytes (decoded from base64) |
| method | VarInt(len) + UTF-8 bytes |
| pathname | VarInt(len) + UTF-8 bytes |
| search | VarInt(len) + UTF-8 bytes, or VarInt(-1) |
| nHeaders | VarInt |
| headers (sorted) | VarInt(keyLen)+key + VarInt(valLen)+val |
| bodyLength | VarInt(len) or VarInt(-1) |
| body | raw bytes |
Only these headers are included (sorted, lowercase):
x-bsv- (but NOT x-bsv-auth-*)content-type (normalized: type only, no parameters)authorizationInformational schema: the signed payload for a general RESPONSE.
Assembled by buildResponsePayload in ExpressTransport.
Wire encoding (binary, concatenated):
| Field | Encoding |
|---|---|
| requestId | 32 raw bytes (decoded from base64) |
| statusCode | VarInt |
| nHeaders | VarInt |
| headers (sorted) | VarInt(keyLen)+key + VarInt(valLen)+val |
| bodyLength | VarInt(len) or VarInt(-1) |
| body | raw bytes |
Only these response headers are included (sorted, lowercase):
x-bsv- (but NOT x-bsv-auth-*)authorization