Skip to main content

Key handling

Every key in this system has one job and a temperature. The design goal is simple to state: a key that leaks should cost as little as possible, and the keys that could cost the most should be the hardest to reach.

Your keys​

CoRelayer never sees a private key of yours. There is no key import, no custodial wallet and no "connect by pasting your seed phrase" — those things do not exist in the product, so they cannot be phished from it.

You holdUsed for
Your wallet keySigning transactions and native-auth logins
Optionally, a guardianYour own protection; a guarded transaction is relayed like any other, with its extra gas accounted for

Our keys​

KeyWhere it livesWhat it can do
Owner (the deployer)An offline keystore. Never on a server.Everything on chain: tariff, tiers, Relay Unit schedule, rate classes, treasury, operator, unpausing, registering relayers, upgrades.
OperatorA human-held warm wallet. Not on a server.Pause anything; activate, drain, retire and reweight relayers; lower caps. Cannot unpause, reprice, or register a relayer of its own.
ReporterInside the backend's signer, on the core hosts. Hot.settleUsage, and nothing else. Bounded by escrow and a per-window cap.
Relayer keys (active)Encrypted keystores inside the signer on the hosts that use them, never as plain files. Each active key is on more than one host, so delivery survives a lost machine.Co-sign relayed transactions. Nothing else.
Relayer keys (spares)Offline, in two places. On no server.Nothing, until activated by the owner.
ReserveOffline. On no server.Receives swept relayer float; funds top-ups.

The registry of relayer addresses is public data in this repository — 30 wallets, 10 per shard, 9 marked active, 21 cold spares. The addresses are public because the registry is on chain anyway; the keys are what is protected.

Relayer wallets never enable a guardian: the protocol does not permit a guarded relayer, and the contract refuses to register one.

A role address may not also be a relayer. The contract enforces the disjointness, and the signer refuses to start with a key bundle that violates it.

The bound on a compromised signing host​

If a host holding active relayer keys were compromised, what is reachable is the relayer float: the small working balance each relayer holds to pay fees. That is CoRelayer's own working capital.

What is not reachable, because it is not there:

  • customer tokens — we never hold any;
  • credits and plans — they live in the contract, and no relayer key can touch them;
  • the tariff, the tier table or the registry — owner-only;
  • settlement beyond the reporter's bounded cap.

The response is to drain the affected relayers, activate spares from cold storage, and replace the wallets. That is why there are 21 spares and why their keys are on no server.

Environments are separated even though addresses are not​

The owner, operator, reporter and relayer wallets have the same addresses on devnet and on mainnet. Transaction replay between networks is prevented by the chain id inside the signed bytes.

Everything else that is signed or authenticated is per environment:

Per environmentConsequence
The lease key and the quote keyA devnet lease or quote never verifies on mainnet.
The sponsor-key hashing keyA crk_test_… key is refused by the mainnet API, and the reverse.
Webhook secrets, status-feed secrets, operational-flag keysNo cross-environment replay.
Payment identifiersSeparate databases.

And a hard stop in the signer itself: it pins its chain id and refuses any transaction whose chainID differs. It cross-checks that pin against the network at start-up and refuses to serve on a mismatch. Since the chain id is inside the signed bytes, a signature made for one network is worthless on the other.

The devnet hosts get full production key handling from the first deploy — encrypted keystores, signer isolation, no plain key file on any server. Not because devnet matters, but because those hosts hold the real keys.

Your API keys​

crk_<env>_<keyId>_<secret>
ShownOnce, at creation. We store a keyed hash and compare in constant time. We cannot show it again.
Scopesrelay — pay for arbitrary senders. read — private reads of the account.
Required policy for relayA non-empty receiver allow-list. Not optional. It is compared with the transaction's receiver field (what that covers).
Optional policyFunction allow-list, per-sender daily unit limit, account daily unit limit, IP allow-list.
Limit10 keys per account.
Managed byYour wallet only. A key can never create, change or revoke a key.
RequiresA plan that can sponsor any sender (Builder and up, Agent Pro, Agent Fleet), else API_KEY_SCOPE.

There are no browser-visible keys, and there will not be. Sender addresses are free to create, so a key visible in a page would let anyone burn the sponsor's whole cap within the allow-list. Named wallets (on-chain authorised senders) suit addresses whose key your own programs hold, such as bots, devices and scripts. A browser dApp whose users sign in their own wallet can't be relayed for yet. How a server uses a key: Pay for your users.

The mandatory receiver allow-list is what makes a leaked key survivable: the thief can spend your Relay Units, but only on transactions to receivers you named.

If a key leaks: revoke it (DELETE /v1/account/{erd}/keys/{keyId}), mint a new one, and check GET /v1/usage for what was spent under the old one.

Signed transactions in logs​

A user-signed relayed transaction that we never co-signed is inert — it cannot execute, by the protocol. But it is still a payload somebody else can hold, so:

  • the MCP tools that take one never echo the transaction, the signature, the guardian signature or the lease in their results or their errors;
  • their descriptions tell hosts not to log their arguments;
  • the presence proof and first-seen expiry bound what a captured payload is worth.

The residual is not zero, and it is listed as a residual rather than explained away. (MCP)