01Executive Summary
▼
What This System Does
The Canton-Zenith Bridge is a two-way token bridge that allows digital assets to move between two distinct financial infrastructure layers: the Canton ledger (a privacy-preserving, enterprise-grade distributed ledger built on Daml) and the Zenith EVM (an Ethereum-compatible blockchain). A user with assets on Canton can "bridge in" to receive equivalent ERC-20 tokens on the EVM; those tokens can later be "bridged out" to reclaim the original Canton assets. At no point are assets duplicated: the Canton-side tokens are held in escrow for the duration of their EVM representation, and the EVM tokens are burned before the Canton escrow is released.
The bridge is designed for regulated, institutional use. It uses a multi-party validator threshold (M-of-N) rather than trusting any single operator. Every mint on the EVM requires cryptographic signatures from at least M independent validators. Every Canton escrow release requires the same M-of-N threshold, verified in Daml smart contract code. Admin operations on the EVM are time-locked behind a 48-hour governance delay, enforced by a TimelockController controlled by a Safe multisig.
Atomicity is enforced at the Daml transaction layer using Canton's externalCall primitive, which embeds the EVM transaction submission inside the same Canton transaction as the escrow state change.
Current Security Posture
All critical and high vulnerabilities identified during the red-team review phase have been remediated. The system has moved from a devnet-only proof-of-concept (with hardcoded private keys, unauthenticated endpoints, and shell injection) to a hardened architecture with authenticated endpoints, HSM-ready key management, mutual TLS, AML/sanctions screening, SQLite-backed replay prevention, and supply parity monitoring.
The architecture is not yet production-ready. It requires external security audits (Solidity, Daml, backend, cryptographic protocol), completion of the BridgeOperator external party migration, and deployment of production HSM key storage.
Key Risks and Mitigations
| Risk | Mitigation | Residual |
|---|---|---|
| Unlimited EVM minting | M-of-N validator threshold (EVM + Canton), BRIDGE_KEY from env, BRIDGE_API_KEY auth | External audit pending |
| Canton escrow drained without authority | M-of-N secp256k1 in VerifiedBridgeOut, intentHash bound to escrow CID | External audit pending |
| Admin key compromise | TimelockController 48h delay on EVM; ValidatorConfigProposal M-of-N on Canton | Single BridgeAdmin key on Canton |
| Cross-deployment sig replay | bridgeVaultAddress + chainId in intent hash | None |
| Stuck bridge funds | Supply parity monitor, recovery runbook, 1-hour escrow expiry with user cancellation | No persistent operation journal |
| Validator collusion | Minimum M colluders required; Safe governs validator set changes | Depends on M/N choice |
02System Architecture
▼
Component Overview
+---------------------------------------------------------------------+
| Canton Ledger |
| +--------------------------------------------------------------+ |
| | Bridge.daml contracts | |
| | BridgeWorkflow BridgeRegistry AddressMapping | |
| | BridgeOperation BridgeEscrowHolding ValidatorConfigProposal | |
| +--------------------------------------------------------------+ |
| Parties: EndUser · BridgeOperator · TokenIssuer · Validator1/2 |
+---------------------------+------------------------------------------+
| Canton HTTP Ledger API v2
+---------------------------v------------------------------------------+
| Bridge Backend (Node.js) |
| +---------------------+ +--------------------------------------+ |
| | coordinator.js | | canton-http-client.js | |
| | settlement-intent | | bridge-reactor.js | |
| | nonce-store-sqlite | | supply-parity-monitor.js | |
| | sanctions-screener | | canton-signer.js (HSM / KMS) | |
| +----------+----------+ +--------------------------------------+ |
| | mTLS (mutual cert auth) |
| +----------v-----------------------------------------------------+ |
| | Validator Nodes (N) | |
| | POST /api/sign-settlement-intent (rate-limited, mTLS auth) | |
| | secp256k1 signing via SoftwareKeySigner / KmsSigner | |
| +----------------------------------------------------------------+ |
+---------------------------+------------------------------------------+
| eth_sendRawTransaction / eth_call
+---------------------------v------------------------------------------+
| Zenith EVM |
| +---------------------------+ +--------------------------------+ |
| | BridgeVault.sol | | BridgedToken.sol | |
| | M-of-N ecrecover | | Beacon-upgradeable ERC-20 | |
| | EIP-712 MintIntent | | onlyMinter (=BridgeVault) | |
| | usedIntents mapping | | ERC-7201 storage | |
| +----------+----------------+ +--------------------------------+ |
| | |
| +----------v---------------------------------------------------+ |
| | Safe multisig --> TimelockController (48h) --> BridgeVault | |
| +--------------------------------------------------------------+ |
+---------------------------------------------------------------------+
Technology Stack
| Layer | Technology |
|---|---|
| Canton ledger | Daml/Canton 3.5, patched externalCall primitive (ZenithVB) |
| EVM contracts | Solidity ^0.8.20, Foundry/Forge, OpenZeppelin v5 |
| Bridge backend | Node.js 20+, ethers.js, @noble/secp256k1, better-sqlite3 |
| Key management | SoftwareKeySigner (devnet), KmsSigner/AWS KMS (production) |
| EVM Governance | Safe multisig + OpenZeppelin TimelockController |
| Build system | Podman containers, just task runner |
| Monitoring | SSE supply parity alerts, structured logging |
Trust Boundaries
| Component | Can Do | Cannot Do |
|---|---|---|
| EndUser | Allocate own holdings, sign bridge requests | Release others' escrows, update address mappings unilaterally |
| BridgeOperator | Exercise BridgeIn/VerifiedBridgeOut, manage registry | Mint EVM tokens (requires BridgeVault threshold) |
| Validator node | Sign settlement intents (secp256k1) | Unilaterally release escrow below threshold |
| Coordinator | Aggregate signatures, submit to Canton and EVM | Forge valid sig bundles without M validator compromises |
| BridgeVault | Mint tokens if M-of-N sigs valid | Mint without threshold, bypass replay check |
| TimelockController | Execute governance actions after 48h delay | Bypass delay |
| Safe multisig | Propose governance actions, emergency pause | Execute immediately (subject to timelock) |
03Bridge-In Flow (Canton → EVM)
▼
A user locks their Canton tokens, the bridge verifies the lock, collects cryptographic approval from multiple independent validators, and mints an equivalent number of ERC-20 tokens on the EVM.
Failure Modes and Recovery
| Failure | Effect | Recovery |
|---|---|---|
| EVM mint fails (gas, RPC error) | Canton tx rolls back; tokens remain allocated | User retries from Step 2 |
| Coordinator crashes after collecting sigs | Sigs lost; Canton not yet submitted | Re-collects sigs with new intent (new nonce/deadline) |
| BridgeEscrowHolding stuck | Supply parity delta > 0 (alert fires) | Cancel via /api/admin/recovery/cancel-bridge-in after expiry |
04Bridge-Out Flow (EVM → Canton)
▼
A user burns their EVM tokens, the bridge collects cryptographic approval from multiple validators, and releases the equivalent Canton escrow back to the user.
Failure Modes and Recovery
| Failure | Effect | Recovery |
|---|---|---|
| EVM burn fails | Canton tx not submitted; user still holds EVM tokens | User corrects and retries |
| Canton submission fails (timeout) | EVM burned, Canton escrow NOT released | Re-POST /api/bridge/bridge-out-request; fresh sigs with new deadline |
| Intent deadline expired | VerifiedBridgeOut rejected | Re-collect with fresh intent (new nonce + deadline) |
| Coordinator crash after EVM burn | EVM burned, Canton pending | Restart; re-POST bridge-out-request (EVM rejects duplicate tx) |
05Cryptographic Architecture
▼
Key Types by Role
| Role | Key Scheme | Usage |
|---|---|---|
| Validator (settlement signing) | secp256k1 (compressed, 33 bytes) | Sign SettlementIntents; verified by BridgeVault ecrecover and DA.Crypto.Text.secp256k1 |
| BridgeOperator (post-migration) | Ed25519 (32 bytes) | Sign Canton interactive submission txs via PartyToKeyMapping threshold |
| EndUser (Canton signing) | Ed25519 (32 bytes) | Sign Canton bridge-in / bridge-out txs (CIP-103 interactive submission) |
| Safe owner | secp256k1 EOA | Approve Safe multisig governance proposals |
Signing Convention: Double-Hash
intentHash = keccak256(encodeIntent(intent)) [32 bytes]
signingDigest = sha256(intentHash) [32 bytes]
signature = secp256k1.sign(signingDigest, privKey) [64 bytes compact]
Coordinator verification: secp.verify(sig, sha256(intentHash), pubKey)
Canton in-contract (DA.Crypto.Text.secp256k1):
Crypto.secp256k1(sig, intentHash, pk)
-- internally computes sha256(bytes(intentHash)) then ECDSA verify
EVM BridgeVault (EIP-712 ecrecover):
keccak256("\x19\x01" || DOMAIN_SEPARATOR || structHash(MintIntent))
-- separate signing path for EVM lane
SettlementIntent Encoding
Fields (in order, deterministic encoding): version : uint32 BE (= 1) escrowContractId : len(uint32 BE) + UTF-8 bytes recipient : len(uint32 BE) + UTF-8 bytes amount : uint64 BE (base units, BigInt) assetId : len(uint32 BE) + UTF-8 bytes nonce : uint64 BE deadline : uint64 BE (Unix milliseconds) bridgeVaultAddress : len(uint32 BE) + UTF-8 bytes <-- cross-deployment binding chainId : uint64 BE <-- cross-chain binding intentHash = keccak256(encodedBytes)
M-of-N Threshold: Dual Layer
| Layer | Location | Mechanism | Key Type |
|---|---|---|---|
| EVM (mint) | BridgeVault.executeMint | EIP-712 + ecrecover; sorted sig array; usedIntents replay prevention | secp256k1 |
| Canton (escrow release) | VerifiedBridgeOut | DA.Crypto.Text.secp256k1 in-contract; dedup by pubkey; intentEscrowContractId binding | secp256k1 |
| Canton (tx submission, post-migration) | PartyToKeyMapping topology | Canton protocol-level threshold; M Ed25519 sigs required for BridgeOperator txs | Ed25519 |
06Smart Contract Security (EVM)
▼
BridgeVault.sol
| Property | Implementation |
|---|---|
| Replay prevention | usedIntents[intentId] = true before calling mint; IntentAlreadyUsed reverts on duplicate |
| Signature deduplication | Signatures sorted ascending by recovered address; DuplicateSigner reverts on collision |
| s-value malleability | Explicit check: uint256(s) ≤ 0x7FFF...A0; non-canonical signatures rejected |
| Chain binding | DOMAIN_SEPARATOR computed from immutable _chainId = block.chainid |
| Cross-contract binding | DOMAIN_SEPARATOR includes address(this) — sig bundles are vault-address-specific |
| Emergency pause | PAUSER_ROLE for instant pause; only owner can unpause (deliberate recovery) |
| Reentrancy | ReentrancyGuard (nonReentrant) on executeMint |
| Ownership transfer | Ownable2Step — two-step transfer prevents accidental ownership loss |
| Mint authority migration | transferMintAuthority(newVault) calls IBridgedToken.setMinter(newVault) atomically; old vault self-revokes |
BridgedToken.sol (BridgedTokenV1)
| Property | Implementation |
|---|---|
| Upgrade safety | UpgradeableBeacon + BeaconProxy; one beacon upgrade updates all asset proxies atomically |
| Storage isolation | ERC-7201 namespaced storage; no slot collision on upgrade |
| Sole mint authority | onlyMinter modifier; bridgeMinter stored in namespaced storage |
| Atomic minter handoff | setMinter(newMinter) callable only by current minter; self-revokes atomically |
| Emergency pause | PAUSER_ROLE for instant pause; onlyOwner for unpause |
TimelockController
- Minimum delay: 172,800 seconds (48 hours)
- PROPOSER_ROLE: Safe multisig
- CANCELLER_ROLE: Safe multisig (abort window for malicious proposals)
- EXECUTOR_ROLE:
address(0)— anyone can execute after delay - TIMELOCK_ADMIN_ROLE:
address(0)— self-governing, no superadmin
Test Coverage
42 Forge unit tests across BridgeVault, BridgedToken, TimelockController integration, and upgrade scenarios. Supply invariant fuzz test: 256 runs × 128,000 calls verifying totalSupply never exceeds total minted minus total burned.
07Canton / Daml Security
▼
VerifiedBridgeOut (production bridge-out path)
The only permitted bridge-out path in production (devMode = False). Enforces atomically:
- Intent expiry:
now ≤ intentDeadline - M-of-N secp256k1:
DA.Crypto.Text.secp256k1(sig, intentHash, pk)for each (pk, sig); dedup prevents one validator counting twice - Escrow binding (Fix 1):
show escrowHoldingCid == intentEscrowContractId— sig bundle bound to specific escrow contract ID; cannot release a different escrow - Custodian check:
holding.custodian == admin - Registry check:
!globalPaused,asset.bridgeOutEnabled - Atomic EVM execution: burn tx submitted via DelegatedApplyTransactionWithReceipt; asserts receipt.success
BridgeEscrowHolding
- Contains
evmBurnTxHash+evmBurnLogIndexfor unique identification (prevents confusion between concurrent bridge-outs of equal amounts) expiresAt: 1 hour from creation; user cancels after expiry via CancelBridgeIn; admin force-cancels at any time
AddressMapping — Fix 3
UpdateEvmAddress requires both admin AND cantonParty as controllers. A compromised admin cannot silently redirect bridge-in recipients without the end user's co-signature.
ValidatorConfigProposal
Validator pubkey rotation uses multi-party approval: requires adminApprovalThreshold approvals from bridgeAdmins list. Single compromised admin key cannot rotate validator set.
Legacy BridgeOut — Gated
The legacy BridgeOut choice (no validator sigs, admin-only) is gated behind devMode == True. Production deployments must set devMode = False.
DA.Crypto.Text Alpha API
DA.Crypto.Text.secp256k1 is the sole alpha API in use (warning suppressed with -Wno-crypto-text-is-alpha). Migration plan documented in deploy/docs/daml-alpha-api-tracking.md. Behavior expected to be preserved when stable API is released.
External Party Migration (Scaffolded)
| Layer | Before | After Migration |
|---|---|---|
| BridgeOperator keys | Participant node (hosted) | Each validator holds one Ed25519 key |
| Transaction submission | submitCommands (single auth) | prepare → collect M-of-N Ed25519 sigs → executeAndWait |
| Canton topology enforcement | None | PartyToKeyMapping threshold = M |
| In-contract secp256k1 | Active | Active (unchanged) |
08Backend Security
▼
API Authentication
| Endpoint Group | Auth Required |
|---|---|
| POST /api/bridge/* (bridge-in, bridge-out) | BRIDGE_API_KEY (Bearer) |
| POST /api/canton/* (prepare, submit) | BRIDGE_API_KEY (Bearer) |
| POST /api/canton/headless/* (CI/testing) | BRIDGE_API_KEY (Bearer) |
| GET/POST /api/admin/* (supply parity, recovery) | ADMIN_API_KEY (Bearer) |
| POST /api/sign-settlement-intent (validator) | mTLS (prod) / X-Coordinator-Secret (devnet) |
Key Management
| Key | How Loaded | Startup Behavior if Missing |
|---|---|---|
| BRIDGE_KEY | Environment variable only | Server refuses to start |
| ENDUSER_SIGNING_KEY | Environment variable only | Headless endpoint unavailable |
| BRIDGE_API_KEY | Environment variable only | All bridge endpoints return 401 |
| ADMIN_API_KEY | Environment variable only | All admin endpoints return 401 |
| FIREBLOCKS_WEBHOOK_SECRET | Environment variable only | Server refuses to start |
No keys are hardcoded. Production validators use KmsSigner (VALIDATOR_HSM_PROVIDER=aws-kms) — the raw private key never leaves hardware.
Rate Limiting
| Endpoint | Limit |
|---|---|
| POST /api/sign-settlement-intent | 20 requests/min per IP |
| All /api/bridge/* endpoints | 10 requests/min per IP |
Shell Injection — Fixed
coordinator.js previously used execSync with string-interpolated intent fields. Fixed to execFileSync(binary, argv_array) — no shell is invoked; metacharacters in argv values cannot be interpreted.
Nonce Store — SQLite Persistence
Validator nonce store backed by SQLite (NONCE_DB_PATH). Nonces persist across restarts; a restart does not open a replay window. Each nonce is marked used atomically before signing.
Fireblocks Webhooks
- v2 format required (v1 deprecated June 15 2026 — already migrated)
- HMAC computed over
timestamp + "." + rawBodywith FIREBLOCKS_WEBHOOK_SECRET (required at startup) - Timestamp freshness window: 5 minutes
- HMAC comparison uses
crypto.timingSafeEqual(timing-safe)
mTLS: Coordinator ↔ Validators
Coordinator uses a mutual TLS HTTPS Agent (COORDINATOR_CERT_PATH, COORDINATOR_KEY_PATH, VALIDATOR_CA_CERT_PATH). Certificate setup documented in deploy/docs/mtls-setup.md. Devnet fallback (X-Coordinator-Secret) is explicitly logged at startup and must not be used in production.
AML / Sanctions Screening
All bridge requests screened against AML/sanctions databases before processing. Supported providers: Chainalysis, Elliptic, mock (testing). Sanctioned addresses rejected with 403 before any Canton or EVM operations are initiated.
BigInt Arithmetic
All token amounts use JavaScript BigInt throughout the coordinator and server. Floating-point representations (Math.round(amount * 1e10)) fully replaced. No precision loss possible in amount encoding, intent hashing, or EVM calldata.
09Operational Security
▼
Supply Parity Monitor
Continuously compares BridgedToken.totalSupply() on EVM against the sum of all BridgeEscrowHolding.amount values on Canton. Any discrepancy indicates a stuck bridge operation. Alerts sent via SSE to GET /api/admin/supply-parity. Triggers recovery procedures documented in deploy/docs/recovery-runbook.md.
Validator Set Synchronization
CLI sync tool (just sync-validators) detects and reports mismatches between EVM BridgeVault.validatorSet and Canton BridgeWorkflow.validatorPubKeys. Out-of-sync sets cause SignerNotValidator errors on EVM or signature threshold failures on Canton.
Transaction Recovery Runbook
Stuck bridge-in (Canton locked, EVM mint failed)
- Detect via supply parity alert (delta > 0) or stale escrow scan
- Verify EVM mint did not succeed (check EVM logs)
- Cancel via
POST /api/admin/recovery/cancel-bridge-inwith escrow contract ID - User accepts ReleaseProposal to reclaim FungibleHolding
Stuck bridge-out (EVM burned, Canton release not submitted)
- Detect via EVM burn events cross-referenced against Canton ACS
- Re-POST to
POST /api/bridge/bridge-out-requestwith original burn tx - Coordinator re-collects fresh M-of-N sigs with new deadline
- VerifiedBridgeOut submits; user accepts ReleaseProposal
HSM Key Storage
| Provider | Class | Status |
|---|---|---|
| SoftwareKeySigner | Raw private key in process memory | Devnet Only |
| KmsSigner | AWS KMS (kms:Sign); key never leaves hardware | Available |
| HsmSigner (PKCS#11) | Hardware security module | Stub |
| HsmSigner (Azure Key Vault) | Azure Key Vault Premium (HSM-backed) | Stub |
10Threat Analysis
▼
Click any row to expand attack vector and mitigation details.
| Severity | Threat | Status | |
|---|---|---|---|
| Critical | T-C1: Sig Bundle Recycling — VerifiedBridgeOut drains any escrow | Fixed | |
A valid M-of-N sig bundle collected for escrow A (small amount) replayed against escrow B (large amount). intentHash was caller-supplied with no binding to the actual escrow being consumed. Bridge.daml VerifiedBridgeOut did not cross-check intentHash against fetched holding fields. Fix 1: intentEscrowContractId binding. In-contract assertion: | |||
| Critical | T-C2: Hardcoded Private Key — Unlimited Token Minting | Fixed | |
server.js hardcoded BRIDGE_KEY as Anvil devnet key #0 (0xac0974...) — a universally known test credential. Anyone with access to the server or the public key could mint arbitrary tokens by controlling the bridge EOA. BRIDGE_KEY loaded exclusively from environment variable. Server fails hard at startup (process.exit(1)) if BRIDGE_KEY or ENDUSER_SIGNING_KEY are missing. Production deployments use KmsSigner — key never exists as plaintext. | |||
| Critical | T-C3: Unauthenticated Bridge Endpoints — Arbitrary Bridge Operations | Fixed | |
POST /api/bridge-in and /api/bridge-out accepted requests from any unauthenticated caller. Amount came from req.body with only a positivity check. External attacker could trigger arbitrary EVM minting and Canton escrow releases. BRIDGE_API_KEY Bearer token required on all /api/bridge/* and /api/canton/* endpoints. Missing or incorrect token returns 401. | |||
| Critical | T-C4: Shell Injection via Intent Fields (RCE on Coordinator) | Fixed | |
coordinator.js submitSettlement() built a shell command by string-interpolating intent fields into execSync(). evmBurnTxData containing Replaced execSync(shell-string) with execFileSync(binary, argv_array). No shell is invoked; metacharacters in argv values cannot be interpreted by the OS shell. | |||
| High | T-H1: Settlement Intent Replay Across Deployments | Fixed | |
A sig bundle collected on one bridge deployment (testnet) replayed on another (mainnet) sharing the same validator keys but different BridgeVault address. bridgeVaultAddress and chainId included in encodeIntent() and bound into intentHash. A signature is deployment-specific and chain-specific. | |||
| High | T-H2: Canton Ledger API Proxy Endpoints Unauthenticated | Fixed | |
Internal Canton Ledger API endpoints proxied at /api/canton/interactive-submission/* with no authentication, allowing unauthorized party onboarding and transaction submission. Legacy proxy routes removed. All Canton HTTP API routes require BRIDGE_API_KEY. Only connected-synchronizers state endpoint remains proxied. | |||
| High | T-H3: Fireblocks Webhook Spoofing | Fixed | |
FIREBLOCKS_WEBHOOK_SECRET was optional — if unset, all webhooks accepted unauthenticated. String equality HMAC comparison vulnerable to timing oracle attacks allowing secret recovery. Secret required at startup (server exits if missing). v2 HMAC format (timestamp + "." + rawBody). crypto.timingSafeEqual for comparison. 5-minute timestamp freshness window. | |||
| High | T-H4: Float Precision on Token Amounts (Supply Drift / Double-Spend) | Fixed | |
BigInt(Math.round(amount * 1e10)) introduced floating-point precision loss. Supply parity invariant could drift; carefully crafted amounts could cause double-spend scenarios. All token amounts use native BigInt arithmetic throughout. No floating-point operations on amounts anywhere in coordinator or server. | |||
| Medium | T-M1: In-Memory Nonce Store Replay After Restart | Fixed | |
nonce-store.js stored nonces in process memory. Process restart reset the nonce counter. Attacker resubmitting a previous intent with nonce=0 and a future deadline would succeed after validator restart. SQLite-backed nonce store (NONCE_DB_PATH). Nonces persist across restarts. addNonce() is atomic and idempotent. | |||
| Medium | T-M2: HMAC Timing Oracle | Fixed | |
Fireblocks webhook HMAC compared with === (JavaScript string equality). Not constant-time — enables statistical timing attacks to recover the webhook secret byte-by-byte. crypto.timingSafeEqual used for all HMAC comparisons. Comparison is constant-time regardless of where strings diverge. | |||
| Medium | T-M3: Legacy BridgeOut — Admin-Only Escrow Release (No Validator Sigs) | Fixed | |
BridgeOut choice in Bridge.daml had no validator signature requirement. A single compromised BridgeAdmin party key could release any escrow to any Canton party without M-of-N approval. BridgeOut gated behind devMode == True. Production deployments set devMode = False; only VerifiedBridgeOut is active. | |||
| Medium | T-M4: AddressMapping Admin Redirect (Steal Bridge-In Recipients) | Fixed | |
AddressMapping.UpdateEvmAddress was admin-controlled alone. A compromised admin could silently redirect bridge-in EVM recipients to an attacker address, stealing incoming funds. UpdateEvmAddress requires both admin and cantonParty as controllers (Fix 3). User must co-sign address updates — admin alone cannot redirect. | |||
| Medium | T-M5: Admin Routes Unauthenticated (State Exposure) | Fixed | |
Admin routes (/api/admin/events, /api/admin/supply-parity, /api/admin/validator-set) mounted with no authentication, exposing bridge state, validator set, and Canton escrow data to any caller. ADMIN_API_KEY Bearer token required on all /api/admin/* routes. Missing or incorrect token returns 401. | |||
| Low | T-L1: secp256k1 s-Value Malleability in ecrecover | Fixed | |
ecrecover in BridgeVault._recoverSigner() accepted high-s signatures (non-canonical). An attacker could submit a second valid-but-different signature for the same intent, potentially interfering with duplicate-signer detection. Explicit high-s check per EIP-2: | |||
| Low | T-L2: transferMintAuthority — Old Vault Retains Mint Rights | Fixed | |
transferMintAuthority() previously only emitted an event. Both old and new vaults could mint tokens simultaneously after a vault migration. transferMintAuthority() now calls IBridgedToken(bridgedToken).setMinter(newVault) — atomically moves the minter role in one transaction. Old vault self-revokes. | |||
Residual Risks
R-2: BridgeOperator Still Participant-Hosted High Until Fixed
BridgeOperator remains a participant-hosted party. A single participant compromise gives full Canton-side control. External party migration (topology threshold M-of-N) is scaffolded but not yet active.
R-4: No Production HSM Deployed High Until Fixed
KmsSigner (AWS KMS) is available and documented. SoftwareKeySigner uses process memory — vulnerable to memory dumps and container extraction. PKCS#11 / Azure Key Vault stubs exist but are not implemented.
R-3: Coordinator Shared-Secret Fallback in Devnet Devnet Only
When mTLS cert paths are not set, coordinator falls back to X-Coordinator-Secret header. Explicitly logged at startup. Must not be active in production.
R-1: DA.Crypto.Text Alpha API Tracked
DA.Crypto.Text.secp256k1 is marked alpha. Migration plan documented. Function checked against each SDK upgrade.
Governance Attack Vectors
G-1: Safe Multisig Compromise (EVM)
If a Safe owner key quorum is compromised, an attacker can propose any governance action. The 48-hour TimelockController delay provides detection and response window. Any Safe owner can submit cancel() to abort a scheduled operation. Response team must act within 48 hours. Recommended: 3-of-5 Safe with hardware wallets, geographically distributed owners.
G-2: Single BridgeAdmin Key (Canton)
Canton BridgeAdmin is currently a single key. Compromise grants full BridgeRegistry and AddressMapping control. However, validator config changes now require multi-party approval (ValidatorConfigProposal) — a single compromised key cannot rotate validator keys. Residual risk: Medium until external party migration completes.
G-3: Validator Collusion Below Threshold
If exactly M validators collude, they can forge arbitrary SettlementIntent signatures — minting unlimited EVM tokens or releasing any Canton escrow. M/N should be > 2/3 for meaningful security. Minimum recommended for mainnet: 3-of-5, operated by independent organizations, geographically distributed.
11Pending External Audits
▼
Six audits are required before the bridge handles real funds, listed in priority order.
Solidity Smart Contract Audit
Scope: BridgeVault.sol, BridgedToken.sol, MinimalMultisig.sol, all deployment scripts. Focus: reentrancy, EIP-712 correctness, ecrecover edge cases, Beacon proxy upgrade safety, TimelockController integration, access control, integer arithmetic, supply invariant. Recommended: Trail of Bits, OpenZeppelin, Spearbit.
Daml / Canton Contract Audit
Scope: Bridge.daml, Token.daml, ZenithVB.daml; Canton authorization model; CIP-56 allocation mechanics; externalCall integration. Focus: VerifiedBridgeOut sig verification, intentEscrowContractId binding, CancelBridgeIn timing, ValidatorConfigProposal flow, DA.Crypto alpha risk. Recommended: Digital Asset partner audit network.
Backend Penetration Test
Scope: All webapp/ API endpoints, coordinator.js, admin-routes.js, Fireblocks webhook handler, mTLS configuration, AML integration. Focus: auth bypass, command injection residuals, SSRF via coordinator fan-out, webhook HMAC bypass, rate limit bypass, nonce replay via SQLite manipulation, information disclosure.
Cryptographic Protocol Audit
Scope: settlement-intent.js, canton-signer.js, BridgeVault EIP-712, Bridge.daml DA.Crypto usage. Focus: double-hash convention correctness across all implementations, intent encoding determinism, cross-chain replay after hard fork, cross-direction replay (bridge-in sigs used for bridge-out), intentEscrowContractId binding completeness.
Regulatory Compliance Review
Scope: Full bridge operational model, AML/KYC integration, FATF Travel Rule applicability, OFAC screening, data residency, audit trail completeness. Deliverable: Gap-to-compliance matrix and remediation plan for applicable framework (MiCA, FinCEN, MAS, etc.).
Key Management Attestation
Scope: key-ceremony.md, hsm-setup.md, Safe multisig key distribution, BridgeAdmin key custody. Focus: tamper-resistant key generation, witness attestation, hardware wallet usage, key rotation testing, incident response documentation. Deliverable: Key custody attestation document for financial regulators and institutional partners.
12Production Readiness Checklist
▼
All items must be complete before the bridge handles real user funds.