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 This file replaces the incorrectly named `brc31-handshake.yaml`. BRC-31 Authrite is a separate protocol, not an alias for BRC-103. BRC-104 covers the HTTP binding. ## Protocol overview BRC-103 uses derived signing keys and nonce-bound signatures for peer authentication. It does not by itself provide forward-secret transport encryption; applications must choose their transport and authorization policy. ### 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` The SDK emits exactly `version`, `messageType`, `identityKey`, `initialNonce`, and `requestedCertificates`. The initial request has no `nonce`, `payload`, or `signature` member. The HTTP transport posts this JSON with `Content-Type: application/json`; general-message auth headers are not added to this handshake request. 2. **Server → Client** `initialResponse` The server creates its own `initialNonce`, echoes the client's nonce as `yourNonce`, and signs the pair. It returns a JSON AuthMessage containing `version`, `messageType`, `identityKey`, `initialNonce`, `yourNonce`, `signature`, `requestedCertificates`, and optional `certificates`. The client validates the response before treating the peer as authenticated. **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 `requestedCertificates` field of the `initialResponse` JSON body. 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. In v0.1 the initial request is unsigned, and the initial-response signature covers the nonce pair rather than the optional `requestedCertificates` or `certificates` fields. Built-in proving encrypts revelation keys to the destination identity, but applications must not disclose plaintext or authorize side effects from an initial certificate-request callback. Authenticate policy-sensitive requests after the handshake. `RequestedCertificateSet` is a legacy allowlist without all-of, any-of, threshold, or optional-field semantics. Validation does not assert that every listed certificate type or field was supplied. Relying applications must inspect the actual validated certificates and decrypted fields before granting access. ### 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. - A wallet-authenticated nonce is not independently a single-use or expiring token. Preserve the protocol's signature and session checks and apply application-level idempotency to protected operations. - Shared `AsyncSessionManager` stores must retain the complete PeerSession, including local `certificatePolicy` and `pendingCertificateRequests`, and coordinate concurrent writers. These fields are never part of AuthMessage. - Certificate responses are checked against locally recorded policies, never against a policy supplied in the response. Dynamic requests are scoped to their session. v0.1 responses do not echo a request ID, so a response must match one complete outstanding local set or the local handshake set. - Certificate listeners are observers: validation is committed and waiters are released first. A listener error cannot revoke or veto that validation.
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. General-message header requirements do not apply. 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. General-message header requirements do not apply. 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 sends its identity, initial nonce, protocol version and requested certificate set as an unsigned initialRequest JSON body.
headers
1 field
$ref
#/components/schemas/InitialRequestHeaders
payload
1 field
$ref
#/components/schemas/AuthMessage
examples
1 item
  1. name
    example-initial-request
    payload
    5 fields
    version
    0.1
    messageType
    initialRequest
    identityKey
    0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
    initialNonce
    Z9+3SZ9qiJvtpQN1RNmmGue247vPuQLeqfIV2eFIMtvRRqFb0FpYckzLbawd0bdR
    requestedCertificates
    2 fields
    certifiers
    0 itemsempty list
    types
    0 fieldsempty object
initialResponse
5 fields
name
initialResponse
summary
Server completes Phase 1 of the BRC-103 handshake.
description
The server returns its initial nonce, the echoed client nonce and a signature over that pair in the AuthMessage JSON body. The body also carries requestedCertificates and any supplied certificates. The client verifies the response; no initialRequest signature is required.
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 method, path, query string, declared signed-header subset, and body as described in `RequestAuthPayload`. Authority, cookies, forwarding metadata, and other standard headers are not covered by the v0.1 frame.
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
5 items
  1. initialRequest
  2. initialResponse
  3. certificateRequest
  4. certificateResponse
  5. 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. The SDK's in-process signed-message claims fail closed at a per-session bound. Its unsigned initial-request cache uses a bounded time/cardinality replay window and evicts the oldest claim at capacity so unauthenticated traffic cannot globally stop new handshakes.
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
The sender's session nonce in initialRequest, initialResponse, certificateRequest and certificateResponse. It is not a per-request correlation identifier.
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` (bodyLength is -1 with no body bytes when the body is empty). Absent from SDK initialRequest and initialResponse messages.
signature
3 fields
type
array
items
3 fields
type
integer
minimum
0
maximum
255
description
Message-specific DER signature; absent from initialRequest. InitialResponse signs the nonce pair, while general and certificate messages sign their respective payloads.
requestedCertificates
3 fields
type
object
description
Locally chosen certificate request set in initialRequest, initialResponse and certificateRequest. CertificateResponse does not echo this field; receivers validate against their local policy.
additionalProperties
true

InitialRequestHeaders

The SDK HTTP transport sends handshake data in the JSON body.
type
object
description
The SDK HTTP transport sends handshake data in the JSON body.
required
1 item
  1. Content-Type
properties
1 field
Content-Type
2 fields
type
string
enum
1 item
  1. application/json

InitialResponseHeaders

Handshake fields are in the JSON response body. General-message auth headers are not required.
type
object
description
Handshake fields are in the JSON response body. General-message auth headers are not required.
properties
1 field
Content-Type
1 field
type
string

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` BRC-104 v0.1 does not encode the URI scheme or authority (`Host`), cookies, forwarding headers, or arbitrary standard headers. Those values are outside this signature. A receiver must pin authority at a trusted edge and must not choose a tenant or grant authority from omitted metadata. Carry any security-relevant application value in an exact signed `x-bsv-*` or `authorization` field and use distinct server identity keys for virtual authorities that are separate principals.
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` BRC-104 v0.1 does not encode the URI scheme or authority (`Host`), cookies, forwarding headers, or arbitrary standard headers. Those values are outside this signature. A receiver must pin authority at a trusted edge and must not choose a tenant or grant authority from omitted metadata. Carry any security-relevant application value in an exact signed `x-bsv-*` or `authorization` field and use distinct server identity keys for virtual authorities that are separate principals.

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` Standard response metadata such as `location`, `set-cookie`, and `content-type` is outside this signature. Do not encode an authenticated authorization decision solely in omitted response metadata.
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` Standard response metadata such as `location`, `set-cookie`, and `content-type` is outside this signature. Do not encode an authenticated authorization decision solely in omitted response metadata.
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

    This file replaces the incorrectly named `brc31-handshake.yaml`.
    BRC-31 Authrite is a separate protocol, not an alias for BRC-103.
    BRC-104 covers the HTTP binding.

    ## Protocol overview

    BRC-103 uses derived signing keys and nonce-bound signatures for peer
    authentication. It does not by itself provide forward-secret transport
    encryption; applications must choose their transport and authorization policy.

    ### 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`
       The SDK emits exactly `version`, `messageType`, `identityKey`,
       `initialNonce`, and `requestedCertificates`. The initial request has no
       `nonce`, `payload`, or `signature` member. The HTTP transport posts this
       JSON with `Content-Type: application/json`; general-message auth headers
       are not added to this handshake request.

    2. **Server → Client** `initialResponse`
       The server creates its own `initialNonce`, echoes the client's nonce as
       `yourNonce`, and signs the pair. It returns a JSON AuthMessage containing
       `version`, `messageType`, `identityKey`, `initialNonce`, `yourNonce`,
       `signature`, `requestedCertificates`, and optional `certificates`.
       The client validates the response before treating the peer as authenticated.

    **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 `requestedCertificates` field of the `initialResponse` JSON body.
    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.

    In v0.1 the initial request is unsigned, and the initial-response signature
    covers the nonce pair rather than the optional `requestedCertificates` or
    `certificates` fields. Built-in proving encrypts revelation keys to the
    destination identity, but applications must not disclose plaintext or
    authorize side effects from an initial certificate-request callback.
    Authenticate policy-sensitive requests after the handshake.

    `RequestedCertificateSet` is a legacy allowlist without all-of, any-of,
    threshold, or optional-field semantics. Validation does not assert that
    every listed certificate type or field was supplied. Relying applications
    must inspect the actual validated certificates and decrypted fields before
    granting access.

    ### 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.
    - A wallet-authenticated nonce is not independently a single-use or expiring
      token. Preserve the protocol's signature and session checks and apply
      application-level idempotency to protected operations.
    - Shared `AsyncSessionManager` stores must retain the complete PeerSession,
      including local `certificatePolicy` and `pendingCertificateRequests`, and
      coordinate concurrent writers. These fields are never part of AuthMessage.
    - Certificate responses are checked against locally recorded policies, never
      against a policy supplied in the response. Dynamic requests are scoped to
      their session. v0.1 responses do not echo a request ID, so a response must
      match one complete outstanding local set or the local handshake set.
    - Certificate listeners are observers: validation is committed and waiters
      are released first. A listener error cannot revoke or veto that validation.

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, certificateRequest, certificateResponse, 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. The SDK's in-process signed-message claims fail
            closed at a per-session bound. Its unsigned initial-request cache
            uses a bounded time/cardinality replay window and evicts the oldest
            claim at capacity so unauthenticated traffic cannot globally stop
            new handshakes.
        yourNonce:
          $ref: '#/components/schemas/Base64String'
          description: Echo of the peer's nonce from the previous message.
        initialNonce:
          $ref: '#/components/schemas/Base64String'
          description: |
            The sender's session nonce in initialRequest, initialResponse,
            certificateRequest and certificateResponse. It is not a per-request
            correlation identifier.
        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` (bodyLength is -1
            with no body bytes when the body is empty).
            Absent from SDK initialRequest and initialResponse messages.
        signature:
          type: array
          items:
            type: integer
            minimum: 0
            maximum: 255
          description: Message-specific DER signature; absent from initialRequest. InitialResponse signs the nonce pair, while general and certificate messages sign their respective payloads.
        requestedCertificates:
          type: object
          description: |
            Locally chosen certificate request set in initialRequest,
            initialResponse and certificateRequest. CertificateResponse does not
            echo this field; receivers validate against their local policy.
          additionalProperties: true

    # -------------------------------------------------------------------------
    # HTTP header shapes (informational — not AsyncAPI schema objects)
    # -------------------------------------------------------------------------
    InitialRequestHeaders:
      type: object
      description: The SDK HTTP transport sends handshake data in the JSON body.
      required: [Content-Type]
      properties:
        Content-Type:
          type: string
          enum: [application/json]

    InitialResponseHeaders:
      type: object
      description: Handshake fields are in the JSON response body. General-message auth headers are not required.
      properties:
        Content-Type:
          type: string

    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`

        BRC-104 v0.1 does not encode the URI scheme or authority (`Host`),
        cookies, forwarding headers, or arbitrary standard headers. Those
        values are outside this signature. A receiver must pin authority at a
        trusted edge and must not choose a tenant or grant authority from
        omitted metadata. Carry any security-relevant application value in an
        exact signed `x-bsv-*` or `authorization` field and use distinct server
        identity keys for virtual authorities that are separate principals.

    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`

        Standard response metadata such as `location`, `set-cookie`, and
        `content-type` is outside this signature. Do not encode an
        authenticated authorization decision solely in omitted response
        metadata.

# ---------------------------------------------------------------------------
# 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. General-message header requirements do not apply.

      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 sends its identity, initial nonce, protocol version and
          requested certificate set as an unsigned initialRequest JSON body.
        headers:
          $ref: '#/components/schemas/InitialRequestHeaders'
        payload:
          $ref: '#/components/schemas/AuthMessage'
        examples:
          - name: example-initial-request
            payload:
              version: '0.1'
              messageType: 'initialRequest'
              identityKey: '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'
              initialNonce: 'Z9+3SZ9qiJvtpQN1RNmmGue247vPuQLeqfIV2eFIMtvRRqFb0FpYckzLbawd0bdR'
              requestedCertificates: { 'certifiers': [], 'types': {} }

      initialResponse:
        name: initialResponse
        summary: Server completes Phase 1 of the BRC-103 handshake.
        description: |
          The server returns its initial nonce, the echoed client nonce and a
          signature over that pair in the AuthMessage JSON body. The body also
          carries requestedCertificates and any supplied certificates. The
          client verifies the response; no initialRequest signature is required.
        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 method, path, query string, declared signed-header subset,
          and body as described in `RequestAuthPayload`. Authority, cookies,
          forwarding metadata, and other standard headers are not covered by
          the v0.1 frame.
        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'