Files
teleport/DEPLOYMENT-RUNBOOK.md
zeekay 78451a4d51 docs: chain routes are /v1/chain
luxd serves chain APIs under one segment from constants.ChainAliasPrefix
(constants v1.6.4, node v1.36.181):

  /v1/bc/<alias>/...   -> /v1/chain/<alias>/...
  /ext/bc/<alias>/...  -> /v1/chain/<alias>/...

Observations keep their original addresses: a dated note or a recorded status
code is a fact about when it was written, not an instruction to follow.
2026-08-30 16:42:16 -07:00

13 KiB

DEPLOYMENT-RUNBOOK — luxfi/teleport

Operator-facing runbook for deploying and operating a Teleport bridge instance. Covers initial deployment, MPC ceremony bootstrap, relay validator onboarding, custody key rotation, and profile transition (classical-compat → strict-PQ).

Audience: SRE / operator. Assumes familiarity with Hardhat, multi-sig wallets, and the Lux production stack (lux-k8s, mpcd, the threshold dispatcher).

1. Prerequisites

Tool Version
Node.js >= 18
pnpm >= 8
Hardhat ^2.28.2 (pinned in contracts/package.json)
Solidity 0.8.20
MongoDB 6.x (api + mpc persistence)
mpcd from ~/work/lux/mpc — must be running before MPC service starts

Build:

cd ~/work/lux/teleport
pnpm install -r          # all workspaces
cd contracts && pnpm build
cd ../api && pnpm install && pnpm tsc
cd ../mpc && pnpm install && pnpm build

Run tests (must all pass before any deployment):

cd ~/work/lux/teleport/contracts && pnpm test
cd ~/work/lux/teleport/mpc && pnpm test

Expected: 60 contract tests + 31 MPC tests = 91 green.

2. Initial deployment

2.1 Solidity contracts

Per-chain deployment order:

  1. Deploy LBTC / LETH / LUXD (bridgeable token contracts) on the destination chain. Each is an ERC20B derivative.

  2. Deploy Warp2VerifierStub with admin = governance multi-sig and flags = 0x07 (require all three lanes) for high-value assets, or 0x01 (Beam-only) for low-value / migration.

    cd contracts
    pnpm hardhat run scripts/deploy-warp2-stub.ts --network lux
    

    For PRODUCTION (Lux mainnet), do NOT use the stub — wire in the precompile-dispatcher verifier (LP-2517 / LP-2311 / LP-2312). The stub is for testnet / migration only and emits Registered(messageHash, true) for any admin-pre-registered hash.

  3. Deploy BridgeV2 with constructor:

    new BridgeV2(
        admin:          GOVERNANCE_MULTISIG,
        verifier:       WARP2_VERIFIER_ADDR,
        feeRecipient:   FEE_TREASURY,
        feeRate:        5e15,           // 0.5%
        minPolicyFlags: 0x07            // require all three lanes
    );
    

    The constructor seeds activeProfile = PROFILE_CLASSICAL_COMPAT (the default). Strict-PQ is opt-in via a follow-up setActiveProfile call (§4 below).

  4. Grant BRIDGE_ROLE to BridgeV2 on each token:

    token.grantRole(BRIDGE_ROLE, bridgeV2Addr);
    
  5. Set token allowlist on BridgeV2:

    bridgeV2.setTokenAllowed(LBTC_ADDR, true);
    bridgeV2.setTokenAllowed(LETH_ADDR, true);
    bridgeV2.setTokenAllowed(LUXD_ADDR, true);
    
  6. Configure per-asset policy flags (optional, default is global minPolicyFlags):

    bridgeV2.setAssetPolicyFlags(LBTC_ADDR, 0x07);  // require all three
    bridgeV2.setAssetPolicyFlags(LETH_ADDR, 0x07);
    bridgeV2.setAssetPolicyFlags(LUXD_ADDR, 0x01);  // Beam-only OK
    

2.2 API service

Per-validator deployment (each Lux validator runs its own API + MPC service for redundancy):

  1. Configure api/settings.json:

    {
      "RPC": {
        "96369": "http://luxd-headless.lux-mainnet.svc:9630/v1/chain/C/rpc",
        "1":     "https://eth-rpc.lux.network",
        "8453":  "https://base-rpc.lux.network"
      },
      "Bridges": {
        "lux":  "0x...BridgeV2_lux",
        "eth":  "0x...BridgeV2_eth",
        "base": "0x...BridgeV2_base"
      },
      "AllowedTokens": { ... },
      "NetNames": { "96369": "lux", "1": "eth", "8453": "base" },
      "DB": "<mongodb_password>",
      "SigningManagers": { "0": "http://mpc.lux.network:7100" },
      "KeyStore": { "0": "lux_keystore.json" },
      "MaxRetries": 3,
      "RetryDelayMs": 2000
    }
    
  2. Set environment:

    # Strict-PQ deployment (recommended for Lux ↔ Lux)
    export LUX_PROFILE=quasar-strict-pq
    export MPCD_URL=http://127.0.0.1:7300   # mpcd loopback
    
    # Classical-compat deployment (Lux ↔ Ethereum mainnet, see LEGACY-CLASSICAL.md)
    export LUX_PROFILE=classical-compat
    
  3. Start:

    cd api
    node dist/bridge.js
    

    Health: curl http://localhost:6000/health.

2.3 MPC service

The MPC service is a client of mpcd (which runs in M-Chain). It does NOT own keys.

  1. Ensure mpcd is running and reachable at MPCD_URL. From the ~/work/lux/mpc repo:

    ./mpcd --threshold-listen 127.0.0.1:7300 --keystore /var/lib/mpcd/keys
    
  2. Configure mpc/settings.json (same shape as api/settings.json with an additional MPCPeers section).

  3. Set environment (must match the API service):

    export LUX_PROFILE=quasar-strict-pq
    export MPC_SCHEME=double-lattice    # default under strict-PQ
    export MPCD_URL=http://127.0.0.1:7300
    
  4. Start:

    cd mpc
    node dist/teleporter.js
    

    Health: curl http://localhost:6000/health. Should log:

    >> Threshold signer scheme=double-lattice policyId=DOUBLE-LATTICE-PULSAR+CORONA
    >> Teleporter MPC Running on port 6000
    

If LUX_PROFILE is unset, the service refuses to start (fail- closed). The error message includes remediation text.

3. MPC ceremony bootstrap

For each external asset (BTC, ETH, …), an MPC keygen ceremony must run to establish the custody key. This is per-asset, one-time at deployment.

The ceremony itself runs in mpcd (M-Chain), not in Teleport. From Teleport's perspective, the steps are:

  1. Operator initiates the ceremony via mpcd admin RPC:

    curl -X POST http://127.0.0.1:7300 \
      -H 'Content-Type: application/json' \
      -d '{
        "jsonrpc":"2.0",
        "method":"cggmp21.keygen",
        "params":{"threshold":4,"participants":7},
        "id":1
      }'
    

    Response: { "publicKey": "0x...", "shares": [...] }. Each share is distributed to a custodian shareholder via the M-Chain identity layer (ML-KEM-768 sealed envelopes).

  2. Public key is published on-chain via a one-shot tx (per-asset setCustodyPublicKey on the M-Chain ThresholdVM contract; out of scope of BridgeV2).

  3. Teleport configuration: nothing to do. The bridge endpoint sees the custody public key indirectly via the Warp 2.0 envelope — the envelope's authorisation is what triggers M-Chain to release.

For intra-Lux signing (the threshold-signer bus used by the MPC service), the ceremony is similarly:

# Pulsar keygen for the bridge's own envelope-signing key
curl -X POST $MPCD_URL -d '{
  "jsonrpc":"2.0",
  "method":"pulsar.keygen",
  "params":{"threshold":5,"participants":7},
  "id":1
}'

# Corona keygen (for double-lattice composition)
curl -X POST $MPCD_URL -d '{
  "jsonrpc":"2.0",
  "method":"corona.keygen",
  "params":{"threshold":5,"participants":7},
  "id":2
}'

After both keygens complete, the MPC service can sign with MPC_SCHEME=double-lattice.

4. Strict-PQ profile transition

Once the bridge has been operating under classical-compat for the warm-up period and the PQ verifier precompile is available, transition to strict-PQ:

4.1 Off-chain prep

  1. Deploy a PQ verifier contract implementing IVerifier:

    contract MLDSAVerifier is IVerifier, IPolicyId {
        function verify(bytes calldata proof, bytes32 publicInputHash)
          external returns (bool) { /* ML-DSA-65 precompile call */ }
        function policyId() external pure returns (bytes32) {
            return PQProfileIDs.POLICY_MLDSA65_FIPS204;
        }
    }
    
  2. Verify the contract on Etherscan / Lux explorer.

4.2 On-chain transition

  1. DEFAULT_ADMIN_ROLE (multi-sig + timelock) extends the allow-list:

    bytes32 ch = MLDSAVerifier.deployedBytecodeHash();   // off-chain
    bridgeV2.setAllowedVerifierCodeHash(ch, true);
    
  2. ADMIN_ROLE flips the profile:

    bridgeV2.setActiveProfile(
        PROFILE_QUASAR_STRICT_PQ,
        MLDSAVerifier_addr
    );
    

    This call reverts if (a) MLDSAVerifier_addr == address(0), (b) policyId() != POLICY_MLDSA65_FIPS204 && != POLICY_STARK_FRI_SHA3_PQ, or (c) extcodehash(MLDSAVerifier_addr) is not in the allow-list.

  3. (Optional) Install P3Q verifier for claimWithProof:

    bridgeV2.setP3QVerifier(P3Q_VERIFIER_ADDR);
    

    Same install-time checks: policyId == POLICY_STARK_FRI_SHA3_PQ AND extcodehash in allow-list.

4.3 Off-chain transition

In the same maintenance window as the on-chain flip:

  1. Update each validator's API + MPC service env:

    export LUX_PROFILE=quasar-strict-pq
    # MPC_SCHEME defaults to double-lattice; explicit MPC_SCHEME=pulsar
    # or =corona is also accepted; classical schemes throw.
    
  2. Restart the services. Health check should log:

    >> Threshold signer scheme=double-lattice policyId=DOUBLE-LATTICE-PULSAR+CORONA
    
  3. Submit a test burn/mint round-trip with a small amount to confirm the new wire format.

If anything fails, rollback by setting LUX_PROFILE=classical-compat on both sides and calling setActiveProfile(PROFILE_CLASSICAL_COMPAT, ...) (any verifier is admissible under classical-compat).

5. Custody key rotation (LSS)

M-Chain ThresholdVM (LP-7330) refreshes per-asset MPC key shares on a schedule via Live Secret Sharing (LSS). The public key stays the same; only the shares rotate.

From Teleport's perspective: nothing to do. The bridge endpoint verifies envelopes signed under the (stable) public key. LSS is transparent to the bridge.

Verification steps for an operator during a rotation:

  1. Pre-rotation: capture a "before" envelope. Check that verifier.verify(env) returns true on the destination chain.
  2. Trigger rotation via M-Chain admin RPC:
    curl -X POST $MPCD_URL -d '{"jsonrpc":"2.0","method":"cggmp21.reshare",...}'
    
  3. Post-rotation: capture an "after" envelope. Check that verifier.verify(env) still returns true.
  4. Cross-check: the public key on the M-Chain ThresholdVM contract should be unchanged.

If the public key changes during a rotation, the rotation MUST be treated as a key change (re-publish on M-Chain, re-deploy any verifiers that bake in the public key) — this is NOT a normal LSS rotation.

6. Relay / oracle validator onboarding

To add a new Lux validator to the Teleport signing set:

  1. Validator generates an ML-DSA-65 identity key (FIPS 204) and a Pulsar share locally, both via mpcd's keygen flow.

  2. Existing validator quorum runs a reshare ceremony to include the new validator's share:

    curl -X POST $MPCD_URL -d '{
      "jsonrpc":"2.0",
      "method":"pulsar.reshare",
      "params":{
        "oldThreshold":4, "oldParticipants":5,
        "newThreshold":5, "newParticipants":7
      },
      "id":1
    }'
    
  3. New validator deploys API + MPC service per §2.

  4. Governance updates the on-chain Warp 2.0 verifier's validator registry to include the new ML-DSA-65 public key. This is out of scope of BridgeV2 (lives in the Warp 2.0 verifier dispatcher contract).

  5. Smoke test with a low-value burn → claim round-trip from a non-production chain.

7. Pause / emergency stop

If an issue is detected:

// PAUSER_ROLE can pause; subsequent burn() and claim() revert with Pausable.Paused
bridgeV2.pause();

While paused, the v1 Bridge.sol may still be operational (separate contract, separate role set). To stop v1 too:

bridge.pause();

To resume:

bridgeV2.unpause();
bridge.unpause();

8. Monitoring

Recommended metrics (Prometheus / Grafana):

Metric Source Alert threshold
teleport_api_signature_requests_total api/ Express middleware none — pure liveness
teleport_api_signature_failures_total{reason} api/ > 1% sustained → page
teleport_mpc_sign_latency_seconds mpc/ signWithMPC p99 > 60s → page
teleport_mpc_active_processes mpc/ activeProcesses.size > 50 → page (backpressure)
bridge_v2_BridgeBurnedV2_count on-chain via Lux Graph Engine none — pure liveness
bridge_v2_BridgeMintedV2_count on-chain should track BridgeBurnedV2 modulo finality lag
bridge_v2_VerifierCodeHashAllowed_count on-chain > 0 in a non-deployment window → page (governance action)
bridge_v2_ProfileUpdated_count on-chain any spike → page (profile flip)

Dashboards: see https://grafana.lux.network/d/teleport.

9. Reference

  • SUBMISSION.md — cover sheet
  • SPEC.md — bridge state machine
  • WARP2_BRIDGE.md — Warp 2.0 architecture + Phase 1-5 migration
  • LEGACY-CLASSICAL.md — classical-compat opt-in
  • TRUSTED-COMPUTING-BASE.md — TCB inventory