AsyncAPI 3.1.0 · specs/auth/brc103-mutual-auth.yaml

BRC-103 Mutual Authentication Handshake

v1.0.0
AsyncAPI 3.0 specification for the BRC-103 peer-to-peer mutual authentication protocol (https://bsv.brc.dev/peer-to-peer/0103) as implemented in: - `packages/middleware/auth-express-middleware/src/index.ts` (`ExpressTransport`, `createAuthMiddleware`) — BRC-104 HTTP transport (https://bsv.brc.dev/peer-to-peer/0104) - `packages/messaging/authsocket/src/SocketServerTransport.ts` (`SocketServerTransport`) — Socket.IO transport - `@bsv/sdk` `Peer` and `Transport` interfaces Note: this file replaces the earlier `brc31-handshake.yaml`. BRC-31 was the legacy identifier; the protocol is now standardised as BRC-103 with BRC-104 covering the HTTP binding. ## Protocol overview BRC-103 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. Deployments that run multiple server instances should use a shared `AsyncSessionManager` store so every instance can resolve the same nonce/session state during the handshake.
1Servers4Channels5Operations0Messages14Schemas

Servers

1

httpServer

https://{host}/.well-known/auth

HTTP endpoint for BRC-103 non-general (initial handshake) messages. General (authenticated) messages use normal application paths.
host
{host}
pathname
/.well-known/auth
protocol
https
description
HTTP endpoint for BRC-103 non-general (initial handshake) messages. General (authenticated) messages use normal application paths.
variables
1 field
host
1 field
default
messagebox.babbage.systems

Channels

4

wellKnownAuth

/.well-known/auth

HTTP channel used for Phase 1 (non-general) BRC-103 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.
address
/.well-known/auth
description
HTTP channel used for Phase 1 (non-general) BRC-103 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.
messages
2 fields
initialRequest
6 fields
name
initialRequest
summary
Client initiates the BRC-103 handshake.
description
The client generates a nonce, signs it, and sends the `initialRequest` AuthMessage as the POST body.
headers
1 field
$ref
#/components/schemas/InitialRequestHeaders
payload
1 field
$ref
#/components/schemas/AuthMessage
examples
1 item
  1. name
    example-initial-request
    payload
    7 fields
    messageType
    initialRequest
    version
    0.1
    identityKey
    028d37b941208cd6b8a4c28288eda5f2f16c2b3ab0fcb6d13c18b47fe37b971fc1
    nonce
    dGVzdE5vbmNlMTIzNA==
    initialNonce
    dGVzdE5vbmNlMTIzNA==
    payload
    0 itemsempty list
    signature
    0 itemsempty list
initialResponse
5 fields
name
initialResponse
summary
Server completes Phase 1 of the BRC-103 handshake.
description
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.
headers
1 field
$ref
#/components/schemas/InitialResponseHeaders
payload
1 field
$ref
#/components/schemas/AuthMessage

generalRequest

{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.
address
{applicationPath}
description
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.
parameters
1 field
applicationPath
1 field
description
The application route path (e.g. /sendMessage).
messages
1 field
generalRequestMessage
5 fields
name
generalRequestMessage
summary
Authenticated application request.
description
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`.
headers
1 field
$ref
#/components/schemas/GeneralRequestHeaders
payload
1 field
description
Application-defined request body (any content type).

generalResponse

{applicationPath}

Every authenticated application HTTP response (Phase 2). The server signs the response status code, relevant headers, and body before sending.
address
{applicationPath}
description
Every authenticated application HTTP response (Phase 2). The server signs the response status code, relevant headers, and body before sending.
parameters
1 field
applicationPath
1 field
description
The application route path.
messages
1 field
generalResponseMessage
5 fields
name
generalResponseMessage
summary
Authenticated application response.
description
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`.
headers
1 field
$ref
#/components/schemas/GeneralResponseHeaders
payload
1 field
description
Application-defined response body.

authError

{applicationPath}

Error responses emitted by the auth middleware when authentication fails.
address
{applicationPath}
description
Error responses emitted by the auth middleware when authentication fails.
parameters
1 field
applicationPath
1 field
description
The path at which the error was encountered.
messages
3 fields
unauthorized
3 fields
name
unauthorized
summary
401 — mutual auth failed (no or bad auth headers).
payload
1 field
$ref
#/components/schemas/AuthError401
certificateTimeout
3 fields
name
certificateTimeout
summary
408 — server waited 30 s for client certificates and timed out.
payload
1 field
$ref
#/components/schemas/AuthError408
signingFailed
3 fields
name
signingFailed
summary
500 — server failed to sign its response payload.
payload
1 field
$ref
#/components/schemas/AuthError500

Operations

5

sendInitialRequest

send
Client POSTs an `initialRequest` AuthMessage to `/.well-known/auth`. The body is a JSON-serialized `AuthMessage` with `messageType: initialRequest`.
action
send
channel
1 field
$ref
#/channels/wellKnownAuth
summary
Client initiates BRC-103 handshake (Phase 1, step 1).
description
Client POSTs an `initialRequest` AuthMessage to `/.well-known/auth`. The body is a JSON-serialized `AuthMessage` with `messageType: initialRequest`.
messages
1 item
  1. $ref
    #/channels/wellKnownAuth/messages/initialRequest

receiveInitialResponse

receive
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.
action
receive
channel
1 field
$ref
#/channels/wellKnownAuth
summary
Client receives the server's Phase 1 challenge response.
description
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.
messages
1 item
  1. $ref
    #/channels/wellKnownAuth/messages/initialResponse

sendGeneralRequest

send
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.
action
send
channel
1 field
$ref
#/channels/generalRequest
summary
Client sends an authenticated application request (Phase 2).
description
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.
messages
1 item
  1. $ref
    #/channels/generalRequest/messages/generalRequestMessage

receiveGeneralResponse

receive
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.
action
receive
channel
1 field
$ref
#/channels/generalResponse
summary
Client receives the server's authenticated application response (Phase 2).
description
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.
messages
1 item
  1. $ref
    #/channels/generalResponse/messages/generalResponseMessage

receiveAuthError

receive
Client receives an auth error from the middleware.
action
receive
channel
1 field
$ref
#/channels/authError
summary
Client receives an auth error from the middleware.
messages
3 items
  1. $ref
    #/channels/authError/messages/unauthorized
  2. $ref
    #/channels/authError/messages/certificateTimeout
  3. $ref
    #/channels/authError/messages/signingFailed

Schemas

14

PubKeyHex

Compressed secp256k1 public key, 66 hex characters.
type
string
pattern
^0[23][0-9a-fA-F]{64}$
description
Compressed secp256k1 public key, 66 hex characters.

Base64String

Base64-encoded binary data.
type
string
description
Base64-encoded binary data.

HexString

Hex-encoded binary data.
type
string
pattern
^[0-9a-fA-F]+$
description
Hex-encoded binary data.

AuthMessageType

- `initialRequest` — first message from the initiating party - `initialResponse` — response from the receiving party completing Phase 1 - `general` — signed application message (Phase 2)
type
string
enum
3 items
  1. initialRequest
  2. initialResponse
  3. general
description
- `initialRequest` — first message from the initiating party - `initialResponse` — response from the receiving party completing Phase 1 - `general` — signed application message (Phase 2)

AuthMessage

The core BRC-103 message envelope. Transported over HTTP bodies (at `/.well-known/auth`) or Socket.IO `authMessage` events.
type
object
description
The core BRC-103 message envelope. Transported over HTTP bodies (at `/.well-known/auth`) or Socket.IO `authMessage` events.
required
3 items
  1. messageType
  2. version
  3. identityKey
properties
9 fields
messageType
1 field
$ref
#/components/schemas/AuthMessageType
version
2 fields
type
string
description
Auth protocol version (e.g. "0.1").
identityKey
2 fields
$ref
#/components/schemas/PubKeyHex
description
Public identity key of the sender of this message.
nonce
2 fields
$ref
#/components/schemas/Base64String
description
Fresh single-use random value generated by the sender. Stored in the `SessionManager` to prevent replay. Multi-instance deployments should store it in a shared `AsyncSessionManager` implementation.
yourNonce
2 fields
$ref
#/components/schemas/Base64String
description
Echo of the peer's nonce from the previous message.
initialNonce
2 fields
$ref
#/components/schemas/Base64String
description
Present in `initialRequest` only. The very first nonce from the initiating party before a session key is established.
payload
3 fields
type
array
items
3 fields
type
integer
minimum
0
maximum
255
description
For `general` messages: the signed application payload. Encoding: `requestId(32) || VarInt(statusCode) || VarInt(nHeaders) || [header pairs] || VarInt(bodyLength) || body`. For handshake messages: empty or absent.
signature
3 fields
type
array
items
3 fields
type
integer
minimum
0
maximum
255
description
DER-encoded ECDSA signature over the payload.
requestedCertificates
3 fields
type
object
description
BRC-52 certificate request set. Present in `initialResponse` when the server requires certificates from the client.
additionalProperties
true

InitialRequestHeaders

HTTP request headers sent by the client when initiating the BRC-103 handshake at POST `/.well-known/auth`.
type
object
description
HTTP request headers sent by the client when initiating the BRC-103 handshake at POST `/.well-known/auth`.
required
3 items
  1. x-bsv-auth-version
  2. x-bsv-auth-identity-key
  3. x-bsv-auth-nonce
properties
3 fields
x-bsv-auth-version
1 field
type
string
x-bsv-auth-identity-key
1 field
$ref
#/components/schemas/PubKeyHex
x-bsv-auth-nonce
1 field
$ref
#/components/schemas/Base64String

InitialResponseHeaders

HTTP response headers set by the server completing Phase 1 of the BRC-103 handshake.
type
object
description
HTTP response headers set by the server completing Phase 1 of the BRC-103 handshake.
required
6 items
  1. x-bsv-auth-version
  2. x-bsv-auth-message-type
  3. x-bsv-auth-identity-key
  4. x-bsv-auth-nonce
  5. x-bsv-auth-your-nonce
  6. x-bsv-auth-signature
properties
7 fields
x-bsv-auth-version
1 field
type
string
x-bsv-auth-message-type
2 fields
type
string
enum
1 item
  1. initialResponse
x-bsv-auth-identity-key
1 field
$ref
#/components/schemas/PubKeyHex
x-bsv-auth-nonce
1 field
$ref
#/components/schemas/Base64String
x-bsv-auth-your-nonce
1 field
$ref
#/components/schemas/Base64String
x-bsv-auth-signature
1 field
$ref
#/components/schemas/HexString
x-bsv-auth-requested-certificates
2 fields
type
string
description
JSON-encoded BRC-52 certificate request set (optional).

GeneralRequestHeaders

HTTP request headers sent by the client for every authenticated application request (Phase 2).
type
object
description
HTTP request headers sent by the client for every authenticated application request (Phase 2).
required
6 items
  1. x-bsv-auth-version
  2. x-bsv-auth-identity-key
  3. x-bsv-auth-nonce
  4. x-bsv-auth-your-nonce
  5. x-bsv-auth-request-id
  6. x-bsv-auth-signature
properties
6 fields
x-bsv-auth-version
1 field
type
string
x-bsv-auth-identity-key
1 field
$ref
#/components/schemas/PubKeyHex
x-bsv-auth-nonce
1 field
$ref
#/components/schemas/Base64String
x-bsv-auth-your-nonce
1 field
$ref
#/components/schemas/Base64String
x-bsv-auth-request-id
2 fields
$ref
#/components/schemas/Base64String
description
32-byte random value, base64-encoded.
x-bsv-auth-signature
2 fields
$ref
#/components/schemas/HexString
description
ECDSA signature over: `requestId(32B) || VarInt(method.length) || method || VarInt(pathname.length) || pathname || VarInt(search.length) || search (or -1 if empty) || VarInt(nHeaders) || [sorted header pairs] || VarInt(bodyLength) || body (or -1 if empty)`.

GeneralResponseHeaders

HTTP response headers set by the server on every authenticated application response (Phase 2).
type
object
description
HTTP response headers set by the server on every authenticated application response (Phase 2).
required
6 items
  1. x-bsv-auth-version
  2. x-bsv-auth-identity-key
  3. x-bsv-auth-nonce
  4. x-bsv-auth-your-nonce
  5. x-bsv-auth-request-id
  6. x-bsv-auth-signature
properties
6 fields
x-bsv-auth-version
1 field
type
string
x-bsv-auth-identity-key
1 field
$ref
#/components/schemas/PubKeyHex
x-bsv-auth-nonce
1 field
$ref
#/components/schemas/Base64String
x-bsv-auth-your-nonce
1 field
$ref
#/components/schemas/Base64String
x-bsv-auth-request-id
1 field
$ref
#/components/schemas/Base64String
x-bsv-auth-signature
2 fields
$ref
#/components/schemas/HexString
description
ECDSA signature over: `requestId(32B) || VarInt(statusCode) || VarInt(nHeaders) || [sorted non-auth x-bsv header pairs] || VarInt(bodyLength) || body (or -1 if empty)`.

AuthError401

Returned when mutual authentication fails (no or bad auth headers).
type
object
description
Returned when mutual authentication fails (no or bad auth headers).
required
3 items
  1. status
  2. code
  3. message
properties
3 fields
status
2 fields
type
string
enum
1 item
  1. error
code
2 fields
type
string
enum
2 items
  1. UNAUTHORIZED
  2. ERR_AUTH_FAILED
message
1 field
type
string

AuthError408

Returned when the server is waiting for client certificates and the 30-second timeout elapses.
type
object
description
Returned when the server is waiting for client certificates and the 30-second timeout elapses.
required
3 items
  1. status
  2. code
  3. message
properties
3 fields
status
2 fields
type
string
enum
1 item
  1. error
code
2 fields
type
string
enum
1 item
  1. CERTIFICATE_TIMEOUT
message
1 field
type
string

AuthError500

Returned when the server fails to sign its response payload (`ERR_RESPONSE_SIGNING_FAILED`).
type
object
description
Returned when the server fails to sign its response payload (`ERR_RESPONSE_SIGNING_FAILED`).
required
3 items
  1. status
  2. code
  3. description
properties
3 fields
status
2 fields
type
string
enum
1 item
  1. error
code
2 fields
type
string
enum
2 items
  1. ERR_RESPONSE_SIGNING_FAILED
  2. ERR_INTERNAL_SERVER_ERROR
description
1 field
type
string

RequestAuthPayload

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`
type
object
description
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`

ResponseAuthPayload

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`
type
object
description
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`
Raw YAML source
asyncapi: "3.1.0"

info:
  title: BRC-103 Mutual Authentication Handshake
  version: "1.0.0"
  description: |
    AsyncAPI 3.0 specification for the BRC-103 peer-to-peer mutual
    authentication protocol (https://bsv.brc.dev/peer-to-peer/0103) as
    implemented in:

    - `packages/middleware/auth-express-middleware/src/index.ts`
      (`ExpressTransport`, `createAuthMiddleware`) — BRC-104 HTTP transport
      (https://bsv.brc.dev/peer-to-peer/0104)
    - `packages/messaging/authsocket/src/SocketServerTransport.ts`
      (`SocketServerTransport`) — Socket.IO transport
    - `@bsv/sdk` `Peer` and `Transport` interfaces

    Note: this file replaces the earlier `brc31-handshake.yaml`. BRC-31 was
    the legacy identifier; the protocol is now standardised as BRC-103 with
    BRC-104 covering the HTTP binding.

    ## Protocol overview

    BRC-103 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. Deployments that run multiple server instances should
      use a shared `AsyncSessionManager` store so every instance can resolve
      the same nonce/session state during the handshake.

servers:
  httpServer:
    host: "{host}"
    pathname: "/.well-known/auth"
    protocol: https
    description: |
      HTTP endpoint for BRC-103 non-general (initial handshake) messages.
      General (authenticated) messages use normal application paths.
    variables:
      host:
        default: messagebox.babbage.systems

# ---------------------------------------------------------------------------
# Components
# ---------------------------------------------------------------------------
components:
  schemas:
    PubKeyHex:
      type: string
      pattern: "^0[23][0-9a-fA-F]{64}$"
      description: Compressed secp256k1 public key, 66 hex characters.

    Base64String:
      type: string
      description: Base64-encoded binary data.

    HexString:
      type: string
      pattern: "^[0-9a-fA-F]+$"
      description: Hex-encoded binary data.

    AuthMessageType:
      type: string
      enum: [initialRequest, initialResponse, general]
      description: |
        - `initialRequest`  — first message from the initiating party
        - `initialResponse` — response from the receiving party completing Phase 1
        - `general`         — signed application message (Phase 2)

    AuthMessage:
      type: object
      description: |
        The core BRC-103 message envelope. Transported over HTTP bodies
        (at `/.well-known/auth`) or Socket.IO `authMessage` events.
      required: [messageType, version, identityKey]
      properties:
        messageType:
          $ref: "#/components/schemas/AuthMessageType"
        version:
          type: string
          description: Auth protocol version (e.g. "0.1").
        identityKey:
          $ref: "#/components/schemas/PubKeyHex"
          description: Public identity key of the sender of this message.
        nonce:
          $ref: "#/components/schemas/Base64String"
          description: |
            Fresh single-use random value generated by the sender.
            Stored in the `SessionManager` to prevent replay. Multi-instance
            deployments should store it in a shared `AsyncSessionManager`
            implementation.
        yourNonce:
          $ref: "#/components/schemas/Base64String"
          description: Echo of the peer's nonce from the previous message.
        initialNonce:
          $ref: "#/components/schemas/Base64String"
          description: |
            Present in `initialRequest` only. The very first nonce from the
            initiating party before a session key is established.
        payload:
          type: array
          items:
            type: integer
            minimum: 0
            maximum: 255
          description: |
            For `general` messages: the signed application payload.
            Encoding: `requestId(32) || VarInt(statusCode) || VarInt(nHeaders)
            || [header pairs] || VarInt(bodyLength) || body`.
            For handshake messages: empty or absent.
        signature:
          type: array
          items:
            type: integer
            minimum: 0
            maximum: 255
          description: DER-encoded ECDSA signature over the payload.
        requestedCertificates:
          type: object
          description: |
            BRC-52 certificate request set. Present in `initialResponse` when
            the server requires certificates from the client.
          additionalProperties: true

    # -------------------------------------------------------------------------
    # HTTP header shapes (informational — not AsyncAPI schema objects)
    # -------------------------------------------------------------------------
    InitialRequestHeaders:
      type: object
      description: |
        HTTP request headers sent by the client when initiating the BRC-103
        handshake at POST `/.well-known/auth`.
      required:
        - x-bsv-auth-version
        - x-bsv-auth-identity-key
        - x-bsv-auth-nonce
      properties:
        x-bsv-auth-version:
          type: string
        x-bsv-auth-identity-key:
          $ref: "#/components/schemas/PubKeyHex"
        x-bsv-auth-nonce:
          $ref: "#/components/schemas/Base64String"

    InitialResponseHeaders:
      type: object
      description: |
        HTTP response headers set by the server completing Phase 1 of the
        BRC-103 handshake.
      required:
        - x-bsv-auth-version
        - x-bsv-auth-message-type
        - x-bsv-auth-identity-key
        - x-bsv-auth-nonce
        - x-bsv-auth-your-nonce
        - x-bsv-auth-signature
      properties:
        x-bsv-auth-version:
          type: string
        x-bsv-auth-message-type:
          type: string
          enum: [initialResponse]
        x-bsv-auth-identity-key:
          $ref: "#/components/schemas/PubKeyHex"
        x-bsv-auth-nonce:
          $ref: "#/components/schemas/Base64String"
        x-bsv-auth-your-nonce:
          $ref: "#/components/schemas/Base64String"
        x-bsv-auth-signature:
          $ref: "#/components/schemas/HexString"
        x-bsv-auth-requested-certificates:
          type: string
          description: JSON-encoded BRC-52 certificate request set (optional).

    GeneralRequestHeaders:
      type: object
      description: |
        HTTP request headers sent by the client for every authenticated
        application request (Phase 2).
      required:
        - x-bsv-auth-version
        - x-bsv-auth-identity-key
        - x-bsv-auth-nonce
        - x-bsv-auth-your-nonce
        - x-bsv-auth-request-id
        - x-bsv-auth-signature
      properties:
        x-bsv-auth-version:
          type: string
        x-bsv-auth-identity-key:
          $ref: "#/components/schemas/PubKeyHex"
        x-bsv-auth-nonce:
          $ref: "#/components/schemas/Base64String"
        x-bsv-auth-your-nonce:
          $ref: "#/components/schemas/Base64String"
        x-bsv-auth-request-id:
          $ref: "#/components/schemas/Base64String"
          description: 32-byte random value, base64-encoded.
        x-bsv-auth-signature:
          $ref: "#/components/schemas/HexString"
          description: |
            ECDSA signature over:
            `requestId(32B) || VarInt(method.length) || method
            || VarInt(pathname.length) || pathname
            || VarInt(search.length) || search (or -1 if empty)
            || VarInt(nHeaders) || [sorted header pairs]
            || VarInt(bodyLength) || body (or -1 if empty)`.

    GeneralResponseHeaders:
      type: object
      description: |
        HTTP response headers set by the server on every authenticated
        application response (Phase 2).
      required:
        - x-bsv-auth-version
        - x-bsv-auth-identity-key
        - x-bsv-auth-nonce
        - x-bsv-auth-your-nonce
        - x-bsv-auth-request-id
        - x-bsv-auth-signature
      properties:
        x-bsv-auth-version:
          type: string
        x-bsv-auth-identity-key:
          $ref: "#/components/schemas/PubKeyHex"
        x-bsv-auth-nonce:
          $ref: "#/components/schemas/Base64String"
        x-bsv-auth-your-nonce:
          $ref: "#/components/schemas/Base64String"
        x-bsv-auth-request-id:
          $ref: "#/components/schemas/Base64String"
        x-bsv-auth-signature:
          $ref: "#/components/schemas/HexString"
          description: |
            ECDSA signature over:
            `requestId(32B) || VarInt(statusCode)
            || VarInt(nHeaders) || [sorted non-auth x-bsv header pairs]
            || VarInt(bodyLength) || body (or -1 if empty)`.

    # -------------------------------------------------------------------------
    # Error shapes
    # -------------------------------------------------------------------------
    AuthError401:
      type: object
      description: Returned when mutual authentication fails (no or bad auth headers).
      required: [status, code, message]
      properties:
        status:
          type: string
          enum: [error]
        code:
          type: string
          enum: [UNAUTHORIZED, ERR_AUTH_FAILED]
        message:
          type: string

    AuthError408:
      type: object
      description: |
        Returned when the server is waiting for client certificates
        and the 30-second timeout elapses.
      required: [status, code, message]
      properties:
        status:
          type: string
          enum: [error]
        code:
          type: string
          enum: [CERTIFICATE_TIMEOUT]
        message:
          type: string

    AuthError500:
      type: object
      description: |
        Returned when the server fails to sign its response payload
        (`ERR_RESPONSE_SIGNING_FAILED`).
      required: [status, code, description]
      properties:
        status:
          type: string
          enum: [error]
        code:
          type: string
          enum: [ERR_RESPONSE_SIGNING_FAILED, ERR_INTERNAL_SERVER_ERROR]
        description:
          type: string

    RequestAuthPayload:
      type: object
      description: |
        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`

    ResponseAuthPayload:
      type: object
      description: |
        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`

# ---------------------------------------------------------------------------
# Channels
# ---------------------------------------------------------------------------
channels:

  wellKnownAuth:
    address: "/.well-known/auth"
    description: |
      HTTP channel used for Phase 1 (non-general) BRC-103 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.
    messages:
      initialRequest:
        name: initialRequest
        summary: Client initiates the BRC-103 handshake.
        description: |
          The client generates a nonce, signs it, and sends the
          `initialRequest` AuthMessage as the POST body.
        headers:
          $ref: "#/components/schemas/InitialRequestHeaders"
        payload:
          $ref: "#/components/schemas/AuthMessage"
        examples:
          - name: example-initial-request
            payload:
              messageType: initialRequest
              version: "0.1"
              identityKey: "028d37b941208cd6b8a4c28288eda5f2f16c2b3ab0fcb6d13c18b47fe37b971fc1"
              nonce: "dGVzdE5vbmNlMTIzNA=="
              initialNonce: "dGVzdE5vbmNlMTIzNA=="
              payload: []
              signature: []

      initialResponse:
        name: initialResponse
        summary: Server completes Phase 1 of the BRC-103 handshake.
        description: |
          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.
        headers:
          $ref: "#/components/schemas/InitialResponseHeaders"
        payload:
          $ref: "#/components/schemas/AuthMessage"

  generalRequest:
    address: "{applicationPath}"
    description: |
      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.
    parameters:
      applicationPath:
        description: The application route path (e.g. /sendMessage).
    messages:
      generalRequestMessage:
        name: generalRequestMessage
        summary: Authenticated application request.
        description: |
          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`.
        headers:
          $ref: "#/components/schemas/GeneralRequestHeaders"
        payload:
          description: Application-defined request body (any content type).

  generalResponse:
    address: "{applicationPath}"
    description: |
      Every authenticated application HTTP response (Phase 2). The server
      signs the response status code, relevant headers, and body before sending.
    parameters:
      applicationPath:
        description: The application route path.
    messages:
      generalResponseMessage:
        name: generalResponseMessage
        summary: Authenticated application response.
        description: |
          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`.
        headers:
          $ref: "#/components/schemas/GeneralResponseHeaders"
        payload:
          description: Application-defined response body.

  authError:
    address: "{applicationPath}"
    description: |
      Error responses emitted by the auth middleware when authentication fails.
    parameters:
      applicationPath:
        description: The path at which the error was encountered.
    messages:
      unauthorized:
        name: unauthorized
        summary: 401 — mutual auth failed (no or bad auth headers).
        payload:
          $ref: "#/components/schemas/AuthError401"

      certificateTimeout:
        name: certificateTimeout
        summary: 408 — server waited 30 s for client certificates and timed out.
        payload:
          $ref: "#/components/schemas/AuthError408"

      signingFailed:
        name: signingFailed
        summary: 500 — server failed to sign its response payload.
        payload:
          $ref: "#/components/schemas/AuthError500"

# ---------------------------------------------------------------------------
# Operations
# ---------------------------------------------------------------------------
operations:

  sendInitialRequest:
    action: send
    channel:
      $ref: "#/channels/wellKnownAuth"
    summary: Client initiates BRC-103 handshake (Phase 1, step 1).
    description: |
      Client POSTs an `initialRequest` AuthMessage to `/.well-known/auth`.
      The body is a JSON-serialized `AuthMessage` with `messageType: initialRequest`.
    messages:
      - $ref: "#/channels/wellKnownAuth/messages/initialRequest"

  receiveInitialResponse:
    action: receive
    channel:
      $ref: "#/channels/wellKnownAuth"
    summary: Client receives the server's Phase 1 challenge response.
    description: |
      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.
    messages:
      - $ref: "#/channels/wellKnownAuth/messages/initialResponse"

  sendGeneralRequest:
    action: send
    channel:
      $ref: "#/channels/generalRequest"
    summary: Client sends an authenticated application request (Phase 2).
    description: |
      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.
    messages:
      - $ref: "#/channels/generalRequest/messages/generalRequestMessage"

  receiveGeneralResponse:
    action: receive
    channel:
      $ref: "#/channels/generalResponse"
    summary: Client receives the server's authenticated application response (Phase 2).
    description: |
      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.
    messages:
      - $ref: "#/channels/generalResponse/messages/generalResponseMessage"

  receiveAuthError:
    action: receive
    channel:
      $ref: "#/channels/authError"
    summary: Client receives an auth error from the middleware.
    messages:
      - $ref: "#/channels/authError/messages/unauthorized"
      - $ref: "#/channels/authError/messages/certificateTimeout"
      - $ref: "#/channels/authError/messages/signingFailed"