> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tplus.cx/llms.txt
> Use this file to discover all available pages before exploring further.

# Multisig

> Weighted co-signers, per-tier thresholds, signature schemes, and the current add-signer flow.

Every account carries a multisig configuration. By default it is a single Ed25519 master key that can authorize anything. Initial setup can include weighted co-signers and per-tier thresholds. After setup, the public OMS runtime write path is add-only: `POST /multisig/add-signer` can add co-signers, but signer removal, threshold changes, and recovery flows are not available yet.

A request is authorized when the combined weight of its valid signatures meets the threshold for that request's action tier. Multisig is checked at the OMS before submission and re-enforced at the clearing engine, so a compromised gateway cannot bypass it.

## Signers and weights

A configuration has three parts:

* **Master key** - the account's Ed25519 key, counted at `master_weight`.
* **Additional signers** - zero or more co-signer keys, each with its own weight and optional time limits.
* **Thresholds** - three integers, `low` / `medium` / `high`, one per [action tier](#action-tiers).

Each additional signer entry:

| Field                   | Meaning                                                                                                                |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `key`                   | The co-signer public key and its scheme (see [signer key types](#signer-key-types)).                                   |
| `weight`                | How much this key contributes toward a threshold when it signs.                                                        |
| `expires_at_ns`         | Optional. After this time the signer is rejected outright.                                                             |
| `max_signing_window_ns` | Optional. The signer may only sign requests created within this many nanoseconds of when it was added - a session key. |

The master key is just the heaviest signer by default; it has no powers beyond its weight. A request can be authorized by co-signers alone, and with `master_weight` set to `0` the master key contributes nothing toward any threshold.

**Default.** A new account has `master_weight = 1`, no additional signers, and all three thresholds `1` - so the master key alone clears every tier.

**Anti-lockout.** A configuration is rejected unless every threshold is at least `1` and the total weight of all keys is at least the `high` threshold, so the configured keys can always satisfy the strictest tier. The master key cannot also be listed as an additional signer, and no key may appear twice. This guards against the obvious lockout; it cannot stop you assigning weight to a key you do not actually hold.

## Action tiers

Every request type is fixed to one of three tiers. A request passes when the summed weight of its valid signers is greater than or equal to that tier's threshold.

| Tier   | Threshold | Actions                                                           |
| ------ | --------- | ----------------------------------------------------------------- |
| Low    | `low`     | Placing orders, closing positions                                 |
| Medium | `medium`  | Sub-account transfers, settlements, cross-margin changes          |
| High   | `high`    | Withdrawals and withdrawal cancellations, multisig config changes |

You will normally set `low <= medium <= high` so value-moving actions need more signatures than trading. The protocol does not enforce that ordering - only that every threshold is at least `1` and that the combined weight of all your keys covers the `high` threshold.

## Signer key types

Four schemes are supported. The master key is always Ed25519; co-signers may be any scheme.

| Scheme    | Key                | Notes                                                                                |
| --------- | ------------------ | ------------------------------------------------------------------------------------ |
| Ed25519   | 32-byte public key | The master key's scheme; also usable as a co-signer.                                 |
| secp256k1 | 33-byte compressed | ECDSA. EVM-wallet keys.                                                              |
| P-256     | 33-byte compressed | ECDSA (secp256r1).                                                                   |
| WebAuthn  | 33-byte compressed | A P-256 key signed through a WebAuthn assertion (passkeys, hardware authenticators). |

Every signer - master and co-signers alike - signs the **same payload**: the request's `inner` object serialized to compact JSON, with whitespace stripped and fields in declaration order. The signature is over the raw payload bytes, not a hash.

ECDSA signatures (secp256k1 and P-256) are normalized to low-`s` form on verification, so signature malleability cannot be used to produce a distinct-but-valid signature.

WebAuthn co-signers submit a full assertion - `authenticatorData`, `clientDataJSON`, and a P-256 signature over `authenticatorData || SHA256(clientDataJSON)`. Tplus checks the user-present flag, that the assertion type is `webauthn.get`, the relying-party ID and origin, and that the assertion's challenge equals the request payload. That last check binds a passkey signature to exactly one request.

## Signing a request

A signed request carries the master signature and any co-signatures separately:

```json theme={null}
{
  "inner": { "...": "the request body" },
  "signature": [],
  "additional_signers": [{ "signer": { "Secp256k1": [] }, "signature": [] }]
}
```

`signature` holds the master Ed25519 signature bytes, or is empty when co-signers authorize instead. Each `additional_signers` entry pairs a `signer` - tagged by scheme (`Ed25519`, `Secp256k1`, `P256`, or `WebAuthn`) - with its signature, and is matched to a registered signer by exact key. Verification:

* Each co-signer must be registered on the account, unexpired, and within its signing window, and its signature must verify over the payload. An unknown, duplicate, expired, or out-of-window signer rejects the whole request.
* The master signature, if present, adds `master_weight`; each valid co-signer adds its entry's weight.
* If the total is below the tier threshold, the request is rejected with an insufficient-weight error.

For orders, co-signers ride in the same `additional_signers` array and are validated when the order is finalized, with the signing-window check measured against the order's creation time.

## Configuring multisig

**Initial setup is onchain, at first deposit.** A first-time depositor calls the [`deposit` overload](/contracts/deposit-vault) that also registers a configuration: `masterWeight`, the three thresholds, and an array of `SignerConfig` entries (`key`, `keyType`, `keyPrefix`, `weight`, `expiresAtNs`, `maxSigningWindowNs`), plus a `masterConfigSignature` - the master key's signature over the configuration. The clearing engine applies it once and ingests it into the ledger; the plain `deposit` leaves the default single-key config in place.

The OMS exposes these runtime endpoints. Only `POST /multisig/add-signer` is a supported multisig write.

| Endpoint                         | Use                                                                                                                                                                                                   |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /multisig/config/{user_id}` | Read the current configuration (master weight, signers, thresholds). Authenticated; the `config_nonce` comes from `GET /nonce/{user_id}` as `ce_nonce`.                                               |
| `POST /multisig/add-signer`      | Add one co-signer. A signed, high-tier request carrying the new `signer`, its `weight`, and a session duration that time-bounds the key. Forwarded to the clearing engine, which confirms or rejects. |
| `POST /multisig/config`          | Disabled full-config replacement route. It accepts POST and returns `405 METHOD_NOT_ALLOWED`; do not use it for threshold changes, signer removal, or recovery.                                       |
| `POST /multisig/signers`         | Public reverse lookup: given a signer key, return the master accounts that list it, so a co-signer device can discover which accounts it can sign for.                                                |

Successful `/multisig/add-signer` requests are replay-protected by `config_nonce` - a monotonic counter supplied as `ce_nonce` on each change and bumped on success.

There are no public OMS endpoints yet for removing or disabling signers, changing thresholds, or recovering a multisig configuration.

See [signing and replay protection](/security/trust-model#signing-and-replay-protection) for where multisig sits in the trust model.
