Co-Signer

Wallet operations

Co-Signer

The BroSettlement Co-Signer is an open-source service that runs on infrastructure you control. It:

What the Co-Signer does

The BroSettlement Co-Signer is an open-source service that runs on infrastructure you control. It:

  • polls BroSettlement for pending DKG and signing intents;
  • authenticates requests with your Ed25519 API key;
  • claims work through signed HTTPS requests;
  • exchanges MPC protocol messages through BroSettlement;
  • stores primary Share B and recovery Share C as encrypted local artifacts;
  • participates in transaction signing without reconstructing a complete private key;
  • reports the final result to BroSettlement.

Repository: BroLabel/brosettlement-mpc-co-signer

Trust boundary

BroSettlement and your Co-Signer each control separate MPC signing material. BroSettlement cannot complete an MPC signature without the client-controlled Co-Signer participating.

Normal Co-Signer signing uses Share B with BroSettlement's Share A. Share C is created during DKG for disaster recovery and is not needed for A+B signing after activation. Co-Signer v2.2.0 initially writes B and C to separate directories on the same host. Once the DKG result is final and the key is active, copy the encrypted C artifact to separately controlled storage, verify that the copy is complete, and remove the C file from the active host. Keep CO_SIGNER_RECOVERY_SHARES_DIR, its private directory, and the shared encryption-key configuration in place; future DKG still needs them. Never remove C during an unfinished DKG: startup reconciliation checks both artifacts for that operation.

Before removal, preserve the exact C artifact bytes, matching Share B recovery copy, original encryption key, and key ID in the recovery arrangement. Confirm that no live-host snapshot or backup silently retains C. The active host should hold only B after separation. A missing C file for an already active key does not prevent Co-Signer startup or normal A+B signing, but an unavailable recovery copy removes the B+C recovery path. The tagged recovery runbook still says to preserve completed artifacts on the host and must be updated to reflect this custody procedure.

The Co-Signer uses a pull model. It initiates outbound HTTPS connections to BroSettlement; you do not need to expose the Co-Signer's local health port to the public internet.

Required configuration

VariableRequiredDescription
CO_SIGNER_MONOLITH_URLYesBroSettlement API base URL. Production: https://brosettlement-api.brolabel.io/.
CO_SIGNER_API_KEY_IDYesUUID of the API key created for this Co-Signer.
CO_SIGNER_API_PRIVATE_KEYYesClient-held Ed25519 private key in PKCS#8 PEM, hex, or Base64 format.
CO_SIGNER_SHARE_ENCRYPTION_KEYYesCanonical padded Base64 encoding of exactly 32 random bytes for encrypting MPC share files.
CO_SIGNER_SHARE_ENCRYPTION_KEY_IDYesStable printable ASCII identifier for the encryption key.
CO_SIGNER_PRIMARY_SHARES_DIRYesAbsolute, pre-existing private directory for primary share B.
CO_SIGNER_RECOVERY_SHARES_DIRYesAbsolute, pre-existing private directory for recovery share C; it must not overlap the primary directory. Keep the directory and setting after moving a completed C artifact to separate storage; future DKG needs them.
CO_SIGNER_HTTP_ADDRNoLocal health server address. Default: 0.0.0.0:8081.
CO_SIGNER_MAX_CONCURRENTNoMaximum concurrent workers. Default: 4.
CO_SIGNER_POLL_MIN_INTERVALNoMinimum intent polling interval. Default: 2s.
CO_SIGNER_POLL_MAX_INTERVALNoMaximum intent polling interval. Default: 10s.
CO_SIGNER_POLL_BACKOFF_FACTORNoPolling backoff multiplier. Default: 1.5.
CO_SIGNER_FRAME_POLL_INTERVALNoMPC message polling interval. Default: 500ms.
CO_SIGNER_HTTP_TIMEOUTNoTimeout for BroSettlement API requests. Default: 30s.

CO_SIGNER_SHARES_DIR and CO_SIGNER_PARTY_ID are unsupported and prevent startup. See the selected release's README for additional options.

Secret handling

Follow these rules for every environment:

  • never commit private.pem, the share-encryption key, or the shares directory;
  • never send secrets through command-line arguments, query strings, tickets, or chat;
  • inject secrets at runtime through a secret manager or protected files;
  • restrict the API key using the required scopes and access settings shown on the API-key page;
  • bind the health endpoint to 127.0.0.1 unless a private monitoring network requires otherwise;
  • restrict both share directories to the operating-system user that runs the Co-Signer;
  • disable core dumps and prevent secrets or share contents from entering logs.

Persistent storage and backup

The Co-Signer creates encrypted MPC artifacts in the primary and recovery share directories during DKG. Files use AES-256-GCM encryption and are created with restricted permissions. After the key is active, move the verified C artifact to separately controlled storage as described above; leave the recovery directory in place.

Protect and back up:

  1. the primary share directory, recovery directory, approval journal, and their persistent parent;
  2. the exact Share C artifact in separately controlled recovery storage, with a matching Share B recovery copy;
  3. the matching share-encryption key and key ID;
  4. the Ed25519 API private key or a documented API-key rotation procedure.

Store backups of encrypted shares and their encryption key in separate protected locations. Test restoration procedures before relying on them. A share file is not usable without the matching encryption key.

Do not delete the live C file until its separate copy is verified and the DKG result is final. Never alter artifact bytes or copy share files between organizations.

Runtime and monitoring

The service writes structured JSON logs to standard output. Send stdout and stderr to your normal centralized logging system, but configure redaction and access controls appropriate for cryptographic infrastructure.

Monitor:

  • process availability;
  • /health response status;
  • ready value;
  • reported version;
  • shares-directory check;
  • Co-Signer status and last heartbeat in the BroSettlement Console;
  • authentication, polling, DKG, and signing errors.

Updates

Before updating:

  1. back up the encrypted shares directory;
  2. verify that the matching share-encryption key is available;
  3. read the release notes;
  4. build and test the target version;
  5. stop the old process cleanly;
  6. start the new version with the same persistent shares directory and secrets;
  7. verify local health and the BroSettlement heartbeat;
  8. run a testnet signing check before returning the service to production use.

Do not start two Co-Signer processes against the same organization and shares directory unless the release documentation explicitly supports that deployment model.

Troubleshooting

The Co-Signer does not start

Check that all required variables are set and that:

  • the private key is a valid Ed25519 key;
  • both share directories exist and are private;
  • the service user can read and write both directories;
  • the local health port is available.

Local health returns ready: false

Inspect the health checks for both artifact stores. Confirm that the configured directories exist and that the service user has permission to access them.

The Console shows the Co-Signer as offline

Check:

  • CO_SIGNER_MONOLITH_URL matches the environment; production uses https://brosettlement-api.brolabel.io/;
  • API Key ID;
  • private key and uploaded public key match;
  • all three MPC scopes are enabled;
  • the network and allow-list settings on the API-key page are complete;
  • outbound HTTPS access is available;
  • the API key is active and has not expired or been revoked.

MPC remains not configured

An online Co-Signer does not automatically initialize MPC. Open Multi-Party Computation and explicitly select Initialize MPC.

MPC initialization fails

Keep the Co-Signer running and inspect its JSON logs. Resolve authentication, IP allow-list, connectivity, version, or storage errors before retrying. Retry only after the previous initialization attempt reaches a terminal failed or expired state.

Wallet creation is unavailable

Confirm that:

  • the MPC key is Active or Ready;
  • the Co-Signer is Online;
  • the selected chain is Ready;
  • the organization's plan permits another wallet.

Transfer approvals

Transfer approvals are supported by stable Co-Signer v2.2.0. Use a compatible BroSettlement backend and coordinate upgrades before enabling them. Follow the Co-Signer `v2.2.0` release and approval operations guide when upgrading an existing installation.

Choose the decision mode

CO_SIGNER_APPROVAL_MODEBehavior
externalYour HTTPS approval service decides whether each transfer may proceed. Use this for human review or business rules.
auto_approveThe Co-Signer approves each new request automatically. No approval service is called.

The default is auto_approve, even when the variable is omitted. Set external explicitly if your organization requires review. Review unresolved requests before changing modes: switching to automatic approval can approve previously unseen requests. A request already recorded in the Co-Signer's approval journal retains its original mode and endpoint.

How a transfer is approved

  1. Your application creates a transfer through the BroSettlement API. BroSettlement reserves the amount and maximum network fee.
  2. The Co-Signer receives the pending approval request. In external mode, it sends the exact transfer details to your approval service over HTTPS.
  3. Your service returns PENDING while review continues, then APPROVED or REJECTED. The Co-Signer checks pending decisions again; your service does not need a callback.
  4. The Co-Signer durably records and delivers the final decision. Only an applied approval can allow MPC signing. Rejection releases the reservation.
  5. Read the transfer's approval and execution status separately. Approval does not mean the blockchain transaction is confirmed.

Configure external approval

Install Co-Signer v2.2.0 using its installation guide. Use a dedicated API key with mpc:raw. Keep its primary and recovery share directories on the same persistent volume, with a writable common parent. The Co-Signer creates its approval journal beside those directories on first startup. Preserve the complete volume, encryption key and key ID across restarts, upgrades and backups. Do not replace a missing journal with an empty one.

Set these variables in the Co-Signer's protected runtime configuration. Replace the placeholders with your own credentials and the original encryption key and key ID for an existing installation:

bash
export CO_SIGNER_MONOLITH_URL="https://brosettlement-api.brolabel.io"
export CO_SIGNER_API_KEY_ID="<co-signer-api-key-id>"
export CO_SIGNER_API_PRIVATE_KEY="<Ed25519-private-key>"
export CO_SIGNER_PRIMARY_SHARES_DIR="/var/lib/co-signer/primary"
export CO_SIGNER_RECOVERY_SHARES_DIR="/var/lib/co-signer/recovery"
export CO_SIGNER_SHARE_ENCRYPTION_KEY="<base64-32-byte-key>"
export CO_SIGNER_SHARE_ENCRYPTION_KEY_ID="<stable-key-id>"
export CO_SIGNER_APPROVAL_MODE="external"
export CO_SIGNER_APPROVAL_ENDPOINT="https://<your-host>/transfer-approval"
export CO_SIGNER_APPROVAL_BEARER_TOKEN="<dedicated-approval-credential>"

The API base URL must not include /api/v1. Both share directories must already exist and be private; they must not overlap. CO_SIGNER_SHARE_ENCRYPTION_KEY must be the canonical padded Base64 encoding of 32 random bytes and must remain unchanged with its key ID. The approval endpoint must be a direct HTTPS URL with a valid TLS certificate; redirects are not followed. Keep the approval credential separate from the Co-Signer API key. The optional CO_SIGNER_APPROVAL_JOURNAL_DIR overrides the default journal path only when that directory already exists and meets the Co-Signer's storage requirements. Follow the approval operations guide before using an override or restoring history.

For automatic approval, set CO_SIGNER_APPROVAL_MODE="auto_approve". The endpoint and bearer token are then unused, but the approval journal is still required. Do not switch modes to bypass an unavailable endpoint or a storage error.

Implement the approval endpoint

The Co-Signer sends an HTTPS POST with Content-Type: application/json, Authorization: Bearer <dedicated-approval-credential> and Idempotency-Key: <requestId>:<authorizationHash>. The JSON body includes version (1), orgId, requestId, authorizationHash, and authorization with the transfer terms. Authenticate the bearer credential and verify the organization ID. Associate your review with the organization, request ID and authorization hash; show the saved terms to the reviewer.

Review the authorization fields clientReference, walletId, chain, fromAddress, toAddress, asset, amountAtomic, networkFeeAsset, maxNetworkFeeAtomic, and transferKind. For tokens, also review tokenStandard, tokenContractCanonical, and tokenDecimals. Amounts and fees are integer strings in atomic units.

Return HTTP 200 with exactly these seven fields, copying the first four values from the request:

json
{
  "version": 1,
  "orgId": "<orgId from request>",
  "requestId": "<requestId from request>",
  "authorizationHash": "<authorizationHash from request>",
  "status": "PENDING",
  "reason": null,
  "decisionReference": null
}

Return PENDING promptly while review is open. For approval, return status: "APPROVED" with reason: null. For rejection, return status: "REJECTED" with a nonempty reason such as {"code":"BUSINESS_LIMIT_EXCEEDED","text":"Transfer exceeds the approved limit."}. The reason code must use uppercase letters, digits or underscores (maximum 64 characters); reason text is limited to 512 UTF-8 bytes. decisionReference may identify the review in your system, or remain null; it must be null for PENDING and is limited to 128 UTF-8 bytes otherwise. Save a final decision and return the same result on retries. If the same request ID arrives with a different authorization hash, investigate the conflict rather than reusing a decision.

An HTTP error, timeout, invalid response or unavailable endpoint leaves the transfer pending. External mode never falls back to automatic approval. Pending requests do not expire automatically, and their funds remain reserved. An accepted final decision cannot be changed by later responses from your endpoint.

Verify the result

Start with a small transfer on a test network. Use your normal API client to create the transfer and retain both its transaction ID and approval.requestId. Find the request in your approval service, verify its destination, asset, amount and maximum fee, then record the decision. Read GET /api/v1/transactions/{transactionId} to inspect approval.status and execution.status:

StatusMeaning
approval.status=PENDING_APPROVALWaiting for a final decision.
approval.status=APPROVEDApproved; inspect execution separately.
approval.status=REJECTEDRejected; inspect approval.rejectionReason.
execution.status=CONFIRMEDConfirmed by BroSettlement.
execution.status=BROADCAST_AMBIGUOUSSubmission is uncertain; retain the original transfer ID and investigate before retrying.

Before execution starts, you can request cancellation with POST /api/v1/transactions/{transactionId}/cancel and an empty JSON object. Cancellation can race with execution; a 409 APPROVAL_ALREADY_ACTIVATED means this endpoint can no longer stop it. Rejection or successful cancellation releases the reserved amount and fee. Keep the Co-Signer and approval service available, and use transfer status rather than local health alone to verify an individual payment.

For TRON, the Co-Signer relies on BroSettlement for network selection; Nile and Mainnet can share the same derived wallet address. Keep real funds away from test addresses and verify the intended network before funding or approving a transfer. See the TRON network trust boundary.