22 KiB
Teleport Bridge - AI Assistant Documentation
Overview
lux/teleport is the PQ-signed bridge reference implementation for
the Lux ecosystem. It defines the protocol — EIP-712 typed-data flow,
claimId-based replay protection, role-separated MPC oracle, IAM-authenticated
admin path, KMS-backed signer keys — and ships a minimal, audit-clean
implementation that exercises every security primitive.
Production deployment lives in ~/work/lux/bridge: same protocol, same
contracts, plus the multi-tenant SDK (@luxfi/bridge), the deployment
binaries, the operator tooling, and the chain-specific manifests. If you
are looking for the production bridge runtime, go there. If you are
looking for the protocol spec, the security model, or the canonical
threshold-signer abstraction, this is the right repo.
The two repos are kept aligned on:
- Contracts: canonical sources in
~/work/lux/standard/contracts/bridge/, imported by both repos.lux/teleport/contracts/contracts/is a reference mirror;lux/bridgeconsumes the standard repo via its contracts workspace. - MPC scheme abstraction: pluggable
ThresholdSignerinterface (mpc/src/signers/) withpolicyId()consumed byStrictPQProfileGate.lux/bridgereuses the same abstraction for its production oracle bus. - EIP-712 / claimId: identical
CLAIM_TYPEHASHand replay-tracking rules. Cross-deployment claims are mechanically equivalent.
Reference vs production — read this first
| Concern | Reference (lux/teleport) |
Production (lux/bridge) |
|---|---|---|
| Audience | protocol spec + minimal impl | white-label tenants + operators |
| SDK | none — direct contracts + scripts | @luxfi/bridge (npm) + mountBridge() |
| Contracts | contracts/contracts/ (mirror of standard/contracts/bridge/) |
imports standard/contracts/bridge/ |
| MPC | 3-node devnet topology | 2-of-3 m-chain (user) + separate private MPC (treasury) |
| Auth | dev creds | IAM (OIDC) + JWKS validation through Lux Gateway |
| KMS | dev keys | Lux KMS (KMSSecret-projected volumes, never logged) |
| Deploy | hardhat scripts | k8s manifests + operator + GAR images |
| Versioning | tracks protocol changes | tracks deployment cadence |
Both repos pin the same standard-contracts SHA. A protocol change lands
in lux/standard first, then bumps in both lux/teleport (reference
proof + tests) and lux/bridge (production rollout).
Security Architecture
Protocol Security
-
EIP-712 Typed Data Signing: All signatures use structured EIP-712 format instead of string concatenation, preventing malleability and encoding mismatches.
-
Destination Binding: The
bridgeBurn()function commits destination data (toChainId, recipient, vault, nonce) into the on-chain event. Oracle nodes derive all claim fields from logs, never from request parameters. -
ClaimId-Based Replay Protection: Claims are tracked by
claimId = keccak256(abi.encode(...all claim fields...))instead of signature bytes, preventing ECDSA malleability exploits. -
Token Whitelisting: Only admin-approved tokens can be bridged, preventing malicious token contract attacks.
-
Role Separation: Distinct ADMIN_ROLE, ORACLE_ROLE, and PAUSER_ROLE with proper access control.
Trustless Bridge Roadmap
Current: MPC Oracle Bridge (v1.1.0)
The current implementation uses MPC (Multi-Party Compute) oracles:
- 2-of-3 threshold signatures
- Oracle nodes watch for burn events
- Sign claims for minting on destination chain
- Trust assumption: Majority of MPC nodes are honest
Future: Trustless Light Client Bridge
For Ethereum ↔ Lux trustless bridging without MPC oracles:
1. Light Client Verification
Deploy light clients on each chain to verify the other chain's consensus:
On Lux (verify Ethereum):
contract EthereumLightClient {
// Verify Ethereum beacon chain sync committee signatures
// Update finalized headers every ~27 hours
function verifyHeader(BeaconBlockHeader header, bytes[] signatures) external;
function verifyStorageProof(bytes32 stateRoot, address account, bytes32 slot, bytes proof) external;
}
On Ethereum (verify Lux):
contract LuxLightClient {
// Verify Lux validator BLS signatures (Quasar consensus)
// Verify P-Chain validator set changes
function verifyHeader(LuxBlockHeader header, bytes signature) external;
function verifyReceiptProof(bytes32 receiptRoot, bytes proof) external;
}
2. Storage/Receipt Proofs
Instead of oracle signatures, users submit Merkle proofs:
function claimWithProof(
bytes32 burnTxHash,
bytes calldata receiptProof, // Merkle proof of receipt inclusion
bytes calldata headerProof, // Proof header is finalized
ClaimData calldata claim
) external {
// 1. Verify header is finalized via light client
require(lightClient.isFinalized(claim.sourceBlockHash));
// 2. Verify receipt proof against receipts root
bytes32 receiptRoot = lightClient.getReceiptsRoot(claim.sourceBlockHash);
require(verifyReceiptProof(receiptRoot, receiptProof, claim.burnTxHash));
// 3. Extract and verify burn event from receipt
BurnEvent memory burn = decodeBurnEvent(receiptProof);
require(burn.toChainId == block.chainid);
require(burn.recipient == msg.sender);
// 4. Mint tokens
token.bridgeMint(burn.recipient, burn.amount);
}
3. ZK State Proofs
For faster finality and lower gas costs, use ZK proofs. The verifier
plugged into IVerifier is profile-dependent: for off-chain bridges
to a classical L1 EVM (Ethereum mainnet, etc.) a Groth16 or PLONK
verifier is the cheap-gas option and is acceptable because the
relying party is itself classical; for Lux Z-Chain anchor verification
under LUX_STRICT_E2E_PQ the verifier MUST be a STARK backend
(ProofPolicySTARKFRISHA3PQ) — Groth16 / PLONK / KZG are refused in
that slot regardless of operator flag.
The Solidity surface for this is contracts/StrictPQProfileGate.sol,
a one-function library (refuseUnderStrictPQ) that mirrors
contract.RefuseUnderStrictPQ in ~/work/lux/node/quasar/contract.
Verifiers declare their backend via IVerifier.policyId() and the
gate decides whether the verifier is allowed in the active profile
slot. Verification logic and policy enforcement stay decomplected:
the verifier does its job, the gate decides admissibility.
ZK-SNARK Verification (classical-compat bridge to L1 EVM):
contract ZKBridge {
IVerifier public verifier; // Groth16 or Plonk verifier (classical-compat only;
// refused in the Z-Chain proof slot under strict-PQ)
function claimWithZKProof(
bytes calldata proof, // ZK proof of state transition
bytes32 publicInputHash, // Hash of claim data
ClaimData calldata claim
) external {
// Verify succinct proof of:
// 1. Source chain finality
// 2. Burn event inclusion
// 3. Correct claim derivation
require(verifier.verify(proof, publicInputHash));
token.bridgeMint(claim.recipient, claim.amount);
}
}
Benefits:
- Constant verification cost (~200-400k gas)
- No trust assumptions (cryptographic security)
- Fast finality (no waiting for light client updates)
4. Range Proofs for Privacy
For private bridging (amounts hidden):
contract PrivateBridge {
// Pedersen commitments for amounts
function bridgeBurnPrivate(
address token,
bytes32 amountCommitment, // Pedersen commitment
bytes calldata rangeProof, // Bulletproof that amount > 0 and < 2^64
uint256 toChainId,
address recipient
) external {
require(verifyRangeProof(amountCommitment, rangeProof));
// Burn committed amount (ZK balance check)
}
}
Implementation Priority
| Phase | Feature | Trust Model | Timeline |
|---|---|---|---|
| 1.1.0 ✅ | MPC Oracles (secure) | 2-of-3 honest | Current |
| 1.2.0 | Light Client | Cryptographic | Q2 2025 |
| 1.3.0 | Receipt Proofs | Trustless | Q2 2025 |
| 1.4.0 | ZK Proofs | Trustless + Fast | Q3 2025 |
| 1.5.0 | Range Proofs | Private + Trustless | Q4 2025 |
Research Areas
-
Ethereum Beacon Chain Light Client
- Sync committee verification (512 validators)
- ~27 hour finality for updates
- See: EIP-4788, Lodestar
-
Lux Consensus Verification
- BLS multi-signatures from validators
- P-Chain for validator set changes
- Quasar consensus proofs
-
ZK Circuits
- Ethereum block header verification in circuit
- MPT (Merkle Patricia Trie) proof verification
- Receipt decoding and event extraction
-
Cross-Chain Standards
- ERC-7683: Cross Chain Intents
- ERC-7786: Cross-Chain Messages
- IBC (Cosmos) style proofs
Package Structure
teleport/
├── contracts/ # Solidity smart contracts
│ └── contracts/
│ ├── Bridge.sol # Main bridge (EIP-712, role-based)
│ ├── ERC20B.sol # Bridgeable token
│ └── IBridgeToken.sol # Token interface
├── api/ # Bridge API service
│ └── src/
│ └── bridge.ts # POST endpoints, event parsing
├── mpc/ # MPC signing service
│ └── src/
│ └── teleporter.ts # Secure MPC signing
├── app/ # Frontend UI
└── multi-party-ecdsa/ # MPC library
Smart Contracts
Bridge.sol
The main bridge contract with security features:
- EIP-712 Domain:
TeleportBridgeversion1 - CLAIM_TYPEHASH: For typed claim verification
- Roles:
ADMIN_ROLE: Token whitelist, oracle management, fee configORACLE_ROLE: Granted to MPC signersPAUSER_ROLE: Emergency pause capability
Key Functions:
// Burn tokens with committed destination (all data in event)
function bridgeBurn(
address token,
uint256 amount,
uint256 toChainId,
address recipient,
bool vault
) external returns (bytes32 burnId);
// Mint tokens with EIP-712 signature verification
function bridgeMint(
ClaimData calldata claim,
bytes calldata signature
) external returns (bytes32 claimId);
BridgeBurned Event:
event BridgeBurned(
bytes32 indexed burnId,
address indexed token,
address indexed sender,
uint256 amount,
uint256 toChainId,
address recipient,
bool vault,
uint256 nonce
);
ERC20B.sol
Bridgeable token with role-based access:
BRIDGE_ROLE: Only bridge contracts can mint/burnPausable: Emergency token freeze- Proper event emissions
API Service (api/src/bridge.ts)
TypeScript service with security improvements:
Endpoints
POST /api/signature- Request claim signatureGET /api/claim/:claimId- Get claim statusGET /health- Health check
Security Features
- POST Only: Sensitive data in request body, not URL
- Event Parsing: All claim fields derived from on-chain logs
- Rate Limiting: 100 requests per 15 minutes per IP
- Helmet: Security headers
- Input Validation: Strict hex format validation
Request/Response
// Request
interface SignatureRequest {
burnTxHash: string; // 0x-prefixed 32-byte hex
fromChainId: number; // Source chain ID
logIndex: number; // Event log index
}
// Response
interface SignatureResponse {
claimId: string;
burnTxHash: string;
logIndex: number;
token: string;
amount: string;
toChainId: number;
recipient: string;
vault: boolean;
nonce: number;
deadline: number;
signature: string;
}
MPC Service (mpc/src/teleporter.ts)
Secure MPC signing with:
- spawn() instead of exec() - No shell injection
- Strict hex validation:
/^[0-9a-f]{64}$/i - Bounded retries: Exponential backoff, max 3 attempts
- Process management: Timeout and cleanup
- Graceful shutdown: SIGTERM handling
Pluggable threshold-signer bus (mpc/src/signers/)
MPC_SCHEME env var selects the signer; default ecdsa is byte-identical
to the legacy GG18 path. Each scheme exposes policyId() for the
StrictPQProfileGate to consult.
| MPC_SCHEME | policyId | Implementation |
|---|---|---|
ecdsa |
ECDSA-SECP256K1-GG18 |
spawn(gg18_sign_client) — existing flow |
cggmp21 |
ECDSA-SECP256K1-CGGMP21 |
Stub — TODO pick @pier-wallet/mpc-ecdsa-wasm or @leo-003/cggmp-wasm |
frost |
SCHNORR-SECP256K1-FROST-RFC9591 |
@noble/curves secp256k1_FROST (real) |
pulsar |
PULSAR-M-LWE-FIPS204T |
JSON-RPC client → luxfi/pulsar daemon |
corona |
CORONA-R-LWE |
JSON-RPC client → luxfi/corona daemon |
double-lattice |
DOUBLE-LATTICE-PULSAR+CORONA |
Composition: u32_be(len)‖pulsar‖corona |
Adding a scheme = one file in mpc/src/signers/ + one case in the
registry (signers/index.ts). All schemes implement the same
ThresholdSigner interface (sign, verify, keygenInit/Round/Finish).
Daemon RPC surface required for pulsar / corona:
pulsar.keygen { threshold, participants } -> { publicKey, shares: [hex] }
pulsar.sign { messageHex, pubKeyHex } -> { signatureHex }
pulsar.verify { messageHex, signatureHex, pubKeyHex } -> { ok }
(same shape under the corona.* namespace). Neither daemon currently
exposes this surface — pulsar/ref/go/cmd/genkat and
corona/cmd/*_oracle/ are vector generators, not long-running services.
Follow-up: ship pulsard / coronad with the above JSON-RPC methods.
Configuration
api/settings.json
{
"RPC": { "<chainId>": "<rpc_url>" },
"Bridges": { "<chainName>": "<address>" },
"AllowedTokens": { "<token>": { "<chain>": "<address>" } },
"NetNames": { "<chainId>": "<chainName>" },
"DB": "<mongodb_password>",
"SigningManagers": { "0": "<url>" },
"KeyStore": { "0": "<filename>" },
"MaxRetries": 3,
"RetryDelayMs": 2000
}
mpc/settings.json
{
"RPC": { "<chainId>": "<rpc_url>" },
"Teleporter": { "<chainName>": "<address>" },
"NetNames": { "<chainId>": "<chainName>" },
"DB": "<mongodb_password>",
"SigningManagers": { "0": "<url>" },
"MPCPeers": { "0": "<host>, <ip>" },
"KeyStore": { "0": "<filename>" },
"SMTimeout": "1"
}
Development
Prerequisites
- Node.js 18+
- MongoDB
- Hardhat (for contracts)
Build
# Contracts
cd contracts && pnpm install && pnpm build
# API
cd api && pnpm install && pnpm build
# MPC
cd mpc && pnpm install && pnpm build
Test
# Contracts
cd contracts && pnpm test
# API
cd api && pnpm test
# MPC
cd mpc && pnpm test
Security Checklist
Contract Security
- EIP-712 typed data signatures
- ClaimId-based replay protection
- Token whitelisting
- Role separation (ADMIN, ORACLE, PAUSER)
- ReentrancyGuard on external functions
- Pausable for emergencies
- SafeERC20 for token transfers
- Fee rate caps (max 10%)
- Zero address validation
API/MPC Security
- POST endpoints for sensitive operations
- Derive all claim fields from chain logs
- Strict hex validation
- spawn() instead of exec()
- Rate limiting
- Helmet security headers
- Bounded retries with backoff
- Graceful shutdown
Test Results
All 36 tests passing:
Lux Teleport Bridge
Deployment
✔ Should set the correct admin
✔ Should initialize with correct domain separator
Token Allowlist
✔ Should allow admin to add tokens
✔ Should prevent non-admin from adding tokens
Bridge Burn
✔ Should burn tokens and emit event with destination data
✔ Should reject burn for non-allowed tokens
✔ Should reject burn with zero amount
Bridge Mint with EIP-712 Signatures
✔ Should mint tokens with valid oracle signature
✔ Should reject replay of same claim
✔ Should reject expired claims
✔ Should reject invalid oracle signature
Pausability
✔ Should allow pauser to pause
✔ Should reject burns when paused
Oracle Management
✔ Should allow admin to activate/deactivate oracles
Fee Configuration
✔ Should allow admin to update fees
✔ Should reject fee rate above maximum
✔ Should correctly deduct fees on mint
Adversarial Tests
✔ Should reject burn to same chain
✔ Should reject burn to chain 0
✔ Should reject burn to zero recipient
✔ Should reject mint to zero recipient
✔ Should reject mint when paused
✔ Should reject signature from deactivated oracle
✔ Should reject emergency withdraw from non-admin
✔ Should reject setting zero address as oracle
✔ Should reject mint with zero amount
✔ Should reject mint for non-allowed token
✔ Should handle signature with manipulated s-value (malleability)
ERC20B (Bridgeable Token)
Deployment
✔ Should set correct name and symbol
✔ Should grant admin role to deployer
Bridge Operations
✔ Should allow bridge to mint
✔ Should allow bridge to burn
✔ Should reject mint from non-bridge
✔ Should reject burn from non-bridge
Pausability
✔ Should allow admin to pause
✔ Should reject transfers when paused
36 passing (546ms)
Migration from v1
- Deploy new Bridge.sol with EIP-712 support
- Configure token whitelist
- Add oracle addresses with ORACLE_ROLE
- Update API to POST endpoints (/api/signature)
- Update frontend to call bridgeBurn with destination params
- Update claim flow to use new signature format
EVM Precompiles for ZK Operations
BN254/alt_bn128 (EIP-1108) - Ready ✅
Essential for Groth16 SNARK verification. Lux EVM chains have these active from genesis:
| Address | Precompile | Gas (EIP-1108) | Status |
|---|---|---|---|
| 0x06 | ECADD | 150 | Active |
| 0x07 | ECMUL | 6,000 | Active |
| 0x08 | ECPAIRING | 45,000 + 34,000/pair | Active |
Verification:
github.com/luxfi/geth/core/vm/contracts.gocontains full implementationparams.Bn256AddGasIstanbul = 150(EIP-1108 repricing)- Fork config:
berlinBlock: 0means EIP-1108 pricing is active - Both evm and coreth import from
github.com/luxfi/geth/core/vm
BLS12-381 (EIP-2537) - Ready for Prague ✅
For BLS signature verification and future Quasar consensus proofs:
| Address | Precompile | Gas |
|---|---|---|
| 0x0b | G1Add | 375 |
| 0x0c | G1MultiExp | 12,000 |
| 0x0d | G2Add | 600 |
| 0x0e | G2MultiExp | 22,500 |
| 0x0f | Pairing | 37,700 + 32,600/pair |
| 0x10 | MapG1 | 5,500 |
| 0x11 | MapG2 | 23,800 |
Available in PrecompiledContractsPrague. Enable by setting pragueTime in chain config.
Usage for Trustless Bridge
The BN254 / BLS12-381 EVM precompiles are kept available so Lux EVM
chains can verify classical proofs and BLS signatures cheaply when
bridging to an external classical L1 EVM. Under LUX_STRICT_E2E_PQ
these precompiles are NOT used in the Z-Chain proof slot — that slot
is gated to ProofPolicySTARKFRISHA3PQ (STARK / FRI / SHA-3) and
classical pairings / BLS aggregates are refused at the profile gate.
The examples below describe the classical-compat path.
Z-Chain Groth16 Verification (classical-compat bridge to L1 EVM only; NOT canonical under strict-PQ):
// Verify Groth16 proof using BN254 pairing
function verifyProof(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[] memory input
) public view returns (bool) {
// Uses 0x08 ECPAIRING precompile
// Gas: 45,000 + 34,000 * numPairs
}
Quasar Consensus Verification (classical-compat profile only; refused under strict-PQ):
// Verify BLS aggregate signature using BLS12-381
function verifyQuasarSignature(
bytes memory signature,
bytes32 message,
bytes[] memory publicKeys
) public view returns (bool) {
// Uses 0x0f BLS12-381 pairing precompile
// Requires Prague upgrade
// NOT canonical under LUX_STRICT_E2E_PQ — strict-PQ chains use
// Pulsar / Pulsar-M threshold signatures (luxfi/pulsar,
// luxfi/pulsar-m) instead of BLS aggregates for finality.
}
Dependencies
Contracts use latest stable versions:
- Hardhat 2.22.19
- OpenZeppelin Contracts 5.2.0
- ethers 6.13.5
- TypeScript 5.7.3
Privacy layer — removed
The privacy layer that previously sat at the bottom of this file
(PrivateBridge, ZNote, ZChainAMM, BulletproofVerifier and
PrivateTeleport) was removed in commit 49522d3 ("chore: remove
deprecated privacy contracts"). The contracts no longer compile in
this repo, the tests for them no longer exist
(Privacy.test.ts / PrivateTeleport.test.ts were deleted in the
Tier A pass).
The historical content is preserved in git history; nothing in the
current codebase composes against it. See CHANGELOG.md for the
removal rationale.
For private cross-chain transfers, the canonical surface today is the
Warp 2.0 envelope path (BridgeV2.sol + WARP2_BRIDGE.md) plus the
M-Chain custody layer (LP-7330). Privacy is delegated to per-chain
shielded pools at the application layer, not built into the bridge.
Submission documentation
The Tier A submission package lives in this repo:
SUBMISSION.md— cover sheet.SPEC.md— bridge state machine.PROOF-CLAIMS.md— what is proved, what is not.TRUSTED-COMPUTING-BASE.md— TCB inventory.PATENTS.md— royalty-free grant.CRYPTOGRAPHER-SIGN-OFF.md— independent review.DEPLOYMENT-RUNBOOK.md— operator-facing playbook.LEGACY-CLASSICAL.md— opt-in classical-compat profile.CHANGELOG.md— per-release changes.WARP2_BRIDGE.md— Warp 2.0 architecture and Phase 1-5 migration.