# Bridge signer — wiring EIP-712 for MeterCall L4

Status: **scaffolded**. Current `_bridgeSign(payload)` in `server.js` returns a well-shaped but deterministic stub (SHA-256 derived). Replace before any mainnet value transits the bridge.

Last updated: 2026-04-17.

---

## 1. The shape we're emulating

Every bridge attestation is an EIP-712 typed data envelope:

```js
{
  domain: {
    name: 'MeterCallBridge',
    version: '1',
    chainId: <BRIDGE_DOMAIN_CHAIN_ID>,
    verifyingContract: '0x<BridgeVerifier.sol deploy address>'
  },
  types: {
    BridgeAttestation: [
      { name: 'requestId',  type: 'bytes32' },
      { name: 'apiSlug',    type: 'string'  },
      { name: 'resultHash', type: 'bytes32' },
      { name: 'timestamp',  type: 'uint256' },
      { name: 'chainId',    type: 'uint256' }
    ]
  },
  primaryType: 'BridgeAttestation',
  message: { requestId, apiSlug, resultHash, timestamp, chainId }
}
```

On-chain verifier: [`/contracts/BridgeVerifier.sol`](../contracts/BridgeVerifier.sol) — `verifyAttestation(requestId, apiSlug, resultHash, timestamp, sig)` must recover the `signer` address and assert it's in an allow-list controlled by the DAO multisig.

---

## 2. Production wiring (ethers v6)

```js
import { Wallet, TypedDataEncoder } from 'ethers';

const wallet = new Wallet(process.env.BRIDGE_SIGNER_KEY);

export async function bridgeSign(envelope) {
  // strict types: no EIP712Domain field in `types` when using signTypedData
  return await wallet.signTypedData(envelope.domain, envelope.types, envelope.message);
}

// to compute the digest for on-chain debugging:
export function bridgeDigest(envelope) {
  return TypedDataEncoder.hash(envelope.domain, envelope.types, envelope.message);
}
```

Drop this into `server.js` in place of `_bridgeSign`. No other code changes should be required — `envelope` already matches what ethers expects.

---

## 3. Key custody tradeoffs

| Mode         | Latency | Ops burden | Security | When to use                     |
| ------------ | ------- | ---------- | -------- | ------------------------------- |
| Hot wallet   | <5 ms   | low        | weak     | Testnet, bounty programs        |
| KMS (AWS/GCP)| ~20 ms  | medium     | medium   | Mainnet pre-scale (≤$1M/day)    |
| HSM (YubiHSM2, CloudHSM) | ~50 ms | high | strong | $1M+/day or any custodial funds |
| Multi-sig threshold sig (FROST/MPC) | ~200 ms | very high | strongest | Enterprise, regulated deployments |

Recommendation: **KMS at launch, migrate to MPC once node operator count > 25.**

---

## 4. Key rotation policy

- **Quarterly scheduled rotations** — new signer key published via DAO governance proposal, 7-day timelock.
- **Emergency rotation** — any verifiable compromise triggers immediate allow-list removal + fresh deploy of `BridgeVerifier.sol` if necessary.
- **Overlap window** — old and new key BOTH in allow-list for 24 hours after rotation so in-flight attestations verify.
- **On-chain event** — `SignerRotated(oldSigner, newSigner, rotatedAt)` emitted by `BridgeVerifier.sol`.

---

## 5. HMAC fallback for dev

For local dev (`NODE_ENV !== 'production'`), fall back to an HMAC-SHA256 over the typed data hash using `process.env.BRIDGE_DEV_SECRET`. Verifier in dev accepts either ECDSA or HMAC so devs don't need a real wallet to test end-to-end flows.

```js
if (process.env.NODE_ENV !== 'production' && !process.env.BRIDGE_SIGNER_KEY) {
  // HMAC fallback — never enable in prod
  return '0xhmac:' + crypto.createHmac('sha256', process.env.BRIDGE_DEV_SECRET || 'dev').update(hash).digest('hex');
}
```

---

## 6. Verification checklist before first mainnet attestation

- [ ] `BRIDGE_SIGNER_KEY` loaded from KMS or HSM (never env file)
- [ ] `BridgeVerifier.sol` deployed with correct `chainId` and `verifyingContract`
- [ ] Signer address added to on-chain allow-list via DAO vote
- [ ] 100-call fuzz test: every signature recovers to the expected address
- [ ] Replay protection: `requestId` is globally unique across chains
- [ ] Monitor wired: `SignerRotated` and `AttestationRejected` events alert on-call
- [ ] Post-mortem template filed for first compromise drill
