BRC-31 Mutual Authentication Handshake 1.0.0

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 interfaces

Protocol overview

BRC-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.

Two-phase handshake

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.

  1. 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.

  2. 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.

Certificate flow (optional)

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.

Unauthenticated pass-through

If allowUnauthenticated: true is set in middleware options, requests without auth headers proceed with req.auth.identityKey = 'unknown'.

Implementation-specific notes

  • 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.
  • The RequestId is a 32-byte random value encoded as base64.
  • Nonces are single-use; the SessionManager stores seen nonces to prevent replay.

Servers

  • https://{host}/.well-known/authhttpshttpServer

    HTTP endpoint for BRC-31 non-general (initial handshake) messages. General (authenticated) messages use normal application paths.

    object
    string
    required

Operations

  • SEND /.well-known/auth

    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.

    Operation IDsendInitialRequest

    Available only on servers:

    Accepts the following message:

    Client initiates the BRC-31 handshake.

    Message IDinitialRequest

    The client generates a nonce, signs it, and sends the initialRequest AuthMessage as the POST body.

    object

    The core BRC-31 message envelope. Transported over HTTP bodies (at /.well-known/auth) or Socket.IO authMessage events.

    object

    HTTP request headers sent by the client when initiating the BRC-31 handshake at POST /.well-known/auth.

    Examples

  • RECEIVE /.well-known/auth

    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.

    Operation IDreceiveInitialResponse

    Available only on servers:

    Accepts the following message:

    Server completes Phase 1 of the BRC-31 handshake.

    Message IDinitialResponse

    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.

    object

    The core BRC-31 message envelope. Transported over HTTP bodies (at /.well-known/auth) or Socket.IO authMessage events.

    object

    HTTP response headers set by the server completing Phase 1 of the BRC-31 handshake.

    Examples

  • SEND {applicationPath}

    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.

    Operation IDsendGeneralRequest

    Available only on servers:

    object
    applicationPathstring
    required

    The application route path (e.g. /sendMessage).

    Accepts the following message:

    Authenticated application request.

    Message IDgeneralRequestMessage

    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.

    Payloadany

    Application-defined request body (any content type).

    object

    HTTP request headers sent by the client for every authenticated application request (Phase 2).

    Examples

  • RECEIVE {applicationPath}

    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.

    Operation IDreceiveGeneralResponse

    Available only on servers:

    object
    applicationPathstring
    required

    The application route path.

    Accepts the following message:

    Authenticated application response.

    Message IDgeneralResponseMessage

    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.

    Payloadany

    Application-defined response body.

    object

    HTTP response headers set by the server on every authenticated application response (Phase 2).

    Examples

  • RECEIVE {applicationPath}

    Error responses emitted by the auth middleware when authentication fails.

    Client receives an auth error from the middleware.

    Operation IDreceiveAuthError

    Available only on servers:

    object
    applicationPathstring
    required

    The path at which the error was encountered.

    Accepts one of the following messages:

    • #0

      401 — mutual auth failed (no or bad auth headers).

      Message IDunauthorized
      object

      Returned when mutual authentication fails (no or bad auth headers).

      Examples

    • #1

      408 — server waited 30 s for client certificates and timed out.

      Message IDcertificateTimeout
      object

      Returned when the server is waiting for client certificates and the 30-second timeout elapses.

      Examples

    • #2

      500 — server failed to sign its response payload.

      Message IDsigningFailed
      object

      Returned when the server fails to sign its response payload (ERR_RESPONSE_SIGNING_FAILED).

      Examples

Schemas

  • string

    Compressed secp256k1 public key, 66 hex characters.

  • Base64Stringstring

    Base64-encoded binary data.

  • string

    Hex-encoded binary data.

  • string
    • initialRequest — first message from the initiating party
    • initialResponse — response from the receiving party completing Phase 1
    • general — signed application message (Phase 2)
  • object

    The core BRC-31 message envelope. Transported over HTTP bodies (at /.well-known/auth) or Socket.IO authMessage events.

  • object

    HTTP request headers sent by the client when initiating the BRC-31 handshake at POST /.well-known/auth.

  • object

    HTTP response headers set by the server completing Phase 1 of the BRC-31 handshake.

  • object

    HTTP request headers sent by the client for every authenticated application request (Phase 2).

  • object

    HTTP response headers set by the server on every authenticated application response (Phase 2).

  • object

    Returned when mutual authentication fails (no or bad auth headers).

  • object

    Returned when the server is waiting for client certificates and the 30-second timeout elapses.

  • object

    Returned when the server fails to sign its response payload (ERR_RESPONSE_SIGNING_FAILED).

  • object

    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):

    • Headers starting with x-bsv- (but NOT x-bsv-auth-*)
    • content-type (normalized: type only, no parameters)
    • authorization
  • object

    Informational 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):

    • Headers starting with x-bsv- (but NOT x-bsv-auth-*)
    • authorization