Skip to content

3.2 Bridge Pattern Analysis

The method used to authenticate the Canton intent on the public chain dictates the trust model and gas efficiency. Three primary patterns are supported, each with distinct trade-offs.

Pattern Comparison Overview

PatternTrust LevelSpeedGas CostSecurity Model
P1: Signed Intent RelayHigh trust in RelayerFastLowRelayer + Canton sigs
P2: Proof PublicationLow trust (cryptographic)ModerateHighOn-chain verification
P3: MPC-Signed TxTrust in MPC providerVery FastLowestStandard custody

Pattern 1: Signed Intent Relay

Overview

Canton signs the ExecutionIntent payload. The Relayer verifies this intent, packages it, and then co-signs the resulting public chain transaction.

Architecture

┌────────────┐
│   Canton   │
│  (Signs    │
│   Intent)  │
└──────┬─────┘
       │ cantonSig

┌──────▼─────┐
│  Relayer   │
│ (Verifies  │
│  + Signs   │
│  TX)       │
└──────┬─────┘
       │ relayerSig

┌──────▼─────┐
│   Vault    │
│  Contract  │
│ (Verifies  │
│  both)     │
└────────────┘

Vault Contract Implementation

solidity
contract VaultP1 {
    address public cantonSigner;
    address public authorizedRelayer;
    uint256 public nonce;

    function executeIntent(
        bytes calldata intentPayload,
        bytes calldata cantonSig,
        bytes calldata /* proof - unused */
    ) external {
        // 1. Verify caller is authorized Relayer
        require(
            msg.sender == authorizedRelayer,
            "Unauthorized relayer"
        );

        // 2. Verify Canton signature
        bytes32 intentHash = keccak256(intentPayload);
        address signer = recoverSigner(intentHash, cantonSig);
        require(signer == cantonSigner, "Invalid Canton signature");

        // 3. Parse and execute intent
        Intent memory intent = abi.decode(intentPayload, (Intent));
        require(intent.nonce > nonce, "Invalid nonce");
        require(block.timestamp <= intent.deadline, "Intent expired");

        nonce = intent.nonce;
        _executeActions(intent.actions);
    }
}

Advantages

  • Low gas overhead: Only one signature verification
  • High speed: Fast submission and confirmation
  • Simple implementation: Minimal smart contract complexity

Disadvantages

  • Relayer trust: High dependency on Relayer honesty
  • Single point of failure: Relayer compromise = system compromise
  • Equivocation risk: Malicious Relayer could submit conflicting transactions

Use Cases

  • Internal deployments with trusted infrastructure
  • Development and testing environments
  • Low-value operations with acceptable risk
  • High-frequency trading requiring minimal latency

Pattern 2: Proof Publication + On-chain Verification

Overview

This pattern enhances non-repudiation by making the Canton policy verifiable directly by the public smart contract.

Architecture

┌────────────┐
│   Canton   │
│ (Signs     │
│  Intent +  │
│  Publishes │
│  PubKey)   │
└──────┬─────┘

       ├──────────────────┐
       │ cantonSig        │ publicKey
       │                  │
┌──────▼─────┐    ┌───────▼────────┐
│  Relayer   │    │  Key Registry  │
│ (Submits   │───▶│  Contract      │
│  Intent)   │    │  (Stores Keys) │
└──────┬─────┘    └────────────────┘
       │                  │
       │                  │ lookup
┌──────▼──────────────────▼─┐
│      Vault Contract        │
│  (Verifies Canton sig      │
│   against Registry)        │
└────────────────────────────┘

Key Registry Contract

solidity
contract CantonKeyRegistry {
    mapping(bytes32 => address) public cantonPartyToKey;
    address public admin;

    event KeyRegistered(
        bytes32 indexed partyId,
        address publicKey
    );

    function registerKey(
        bytes32 partyId,
        address publicKey
    ) external {
        require(msg.sender == admin, "Not authorized");
        cantonPartyToKey[partyId] = publicKey;
        emit KeyRegistered(partyId, publicKey);
    }
}

Vault Contract Implementation

solidity
contract VaultP2 {
    ICantonKeyRegistry public keyRegistry;
    bytes32 public authorizedPartyId;
    uint256 public nonce;

    function executeIntent(
        bytes calldata intentPayload,
        bytes calldata cantonSig,
        bytes calldata proof
    ) external {
        // 1. Recover signer from Canton signature
        bytes32 intentHash = keccak256(intentPayload);
        address recoveredSigner = recoverSigner(intentHash, cantonSig);

        // 2. Verify against Canton Key Registry
        address authorizedKey = keyRegistry.cantonPartyToKey(
            authorizedPartyId
        );
        require(
            recoveredSigner == authorizedKey,
            "Invalid Canton signer"
        );

        // 3. Parse and execute intent
        Intent memory intent = abi.decode(intentPayload, (Intent));
        require(intent.nonce > nonce, "Invalid nonce");
        require(block.timestamp <= intent.deadline, "Intent expired");

        nonce = intent.nonce;
        _executeActions(intent.actions);
    }
}

Canton Key Synchronization

The Relayer must synchronize Canton public keys to the registry:

typescript
async function syncCantonKeys(): Promise<void> {
  // 1. Fetch authorized parties from Canton
  const parties = await canton.getTopology();

  // 2. Update on-chain registry
  for (const party of parties) {
    const currentKey = await keyRegistry.cantonPartyToKey(
      party.partyId
    );

    if (currentKey !== party.publicKey) {
      await keyRegistry.registerKey(
        party.partyId,
        party.publicKey
      );
    }
  }
}

Advantages

  • Trustless verification: Vault contract trusts Canton directly
  • No Relayer trust: Relayer is just a messenger
  • Strong non-repudiation: Cryptographic proof on-chain
  • Transparent: Anyone can verify intent authenticity

Disadvantages

  • Higher gas cost: ECDSA recovery + registry lookup
  • Key management complexity: Must sync Canton topology
  • Registry dependency: Additional infrastructure component

Use Cases

  • High-value operations requiring maximum security
  • Multi-institution deployments
  • Regulatory environments requiring proof
  • Long-term archival and auditability

Pattern 3: MPC-Signed Transactions

Overview

For institutions using professional custody solutions, the Multichain Party Computation (MPC) model offers the fastest execution.

Architecture

┌────────────┐
│   Canton   │
│  (Emits    │
│   Intent)  │
└──────┬─────┘
       │ unsigned intent

┌──────▼─────┐
│  Relayer   │
│ (Prepares  │
│   Raw TX)  │
└──────┬─────┘
       │ unsigned tx

┌──────▼─────┐
│    MPC     │
│  Signing   │
│  Cluster   │
└──────┬─────┘
       │ signed tx

┌──────▼─────┐
│   Public   │
│   Chain    │
└────────────┘

Relayer Integration

typescript
async function executeMPCPattern(
  intent: ExecutionIntent
): Promise<TransactionReceipt> {
  // 1. Prepare unsigned transaction
  const unsignedTx = {
    to: vaultAddress,
    data: encodeSwapCall(intent.actions),
    value: 0,
    chainId: intent.targetChain,
    nonce: await getChainNonce()
  };

  // 2. Send to MPC for policy check + signing
  const signedTx = await mpcService.signTransaction({
    transaction: unsignedTx,
    cantonIntent: intent,
    policyValidation: true
  });

  // 3. Broadcast to chain
  return await provider.sendTransaction(signedTx);
}

MPC Service Policy Enforcement

The MPC provider must implement internal policy checks:

typescript
// MPC Service Internal Logic
async function signTransaction(
  request: SignRequest
): Promise<SignedTransaction> {
  // 1. Verify Canton intent signature
  const intentValid = await verifyCantonSignature(
    request.cantonIntent
  );
  if (!intentValid) throw new Error('Invalid intent');

  // 2. Check internal policy constraints
  await enforceMPCPolicy(request.transaction, request.cantonIntent);

  // 3. Multi-party signing ceremony
  const signature = await mpcSign(
    request.transaction,
    this.vaultKeyShares
  );

  return {
    ...request.transaction,
    signature
  };
}

async function enforceMPCPolicy(
  tx: Transaction,
  intent: ExecutionIntent
): Promise<void> {
  // Mirror Daml VaultPolicy constraints
  const policy = await fetchPolicyFromCanton(intent.policyId);

  // Check transaction value limits
  if (tx.value > policy.maxTransactionValue) {
    throw new Error('Exceeds max transaction value');
  }

  // Check destination whitelisting
  if (!policy.allowedContracts.includes(tx.to)) {
    throw new Error('Destination not whitelisted');
  }

  // Check daily limits
  await checkDailyLimits(policy.dailyLimit);
}

Vault Contract (Simplified)

solidity
contract VaultP3 {
    // No special verification needed
    // Standard custody model with EOA/multisig

    function swap(
        address tokenIn,
        address tokenOut,
        uint256 amountIn
    ) external {
        // Only callable by the MPC-controlled vault address
        require(msg.sender == vaultOwner, "Not authorized");

        // Execute swap logic
        _performSwap(tokenIn, tokenOut, amountIn);
    }
}

Advantages

  • Fastest execution: No on-chain verification overhead
  • Lowest gas cost: Standard transaction fees
  • Professional custody: Industry-standard MPC providers
  • Multichain consistency: Same address across chains

Disadvantages

  • MPC trust: Must trust MPC provider's policy enforcement
  • Policy drift risk: MPC and Canton policies must stay synchronized
  • Vendor lock-in: Dependent on specific MPC provider
  • Less transparent: Policy enforcement not visible on-chain

Use Cases

  • High-frequency trading operations
  • Multi-strategy institutional vaults
  • Cross-chain operations requiring speed
  • Large AUM requiring professional custody

Pattern Selection Guide

Decision Matrix

                    Speed    Gas     Trust      Auditability
                    ─────    ───     ─────      ────────────
Pattern 1 (Relay)   ★★★★     ★★★★    ★★         ★★★
Pattern 2 (Proof)   ★★★      ★★      ★★★★★      ★★★★★
Pattern 3 (MPC)     ★★★★★    ★★★★★   ★★★        ★★★★

Recommendation Flow

Start

  ├─ Need professional custody?
  │    Yes → Pattern 3 (MPC)
  │    No ↓

  ├─ Need maximum trustlessness?
  │    Yes → Pattern 2 (Proof)
  │    No ↓

  ├─ Internal deployment only?
  │    Yes → Pattern 1 (Relay)
  │    No → Pattern 2 (Proof)

Hybrid Approach

Institutions can use different patterns for different operation types:

typescript
function selectPattern(intent: ExecutionIntent): BridgePattern {
  if (intent.value > HIGH_VALUE_THRESHOLD) {
    return 'ProofPublication'; // Pattern 2 for high-value
  }

  if (intent.frequency === 'high') {
    return 'MPCSigned'; // Pattern 3 for high-frequency
  }

  return 'SignedIntentRelay'; // Pattern 1 for routine ops
}

Implementation Checklist

Pattern 1 (Signed Intent Relay)

  • [ ] Deploy Relayer with secure key management
  • [ ] Whitelist Relayer address in vault contract
  • [ ] Implement Canton signature verification
  • [ ] Set up monitoring for Relayer equivocation
  • [ ] Regular Relayer key rotation

Pattern 2 (Proof Publication)

  • [ ] Deploy Canton Key Registry contract
  • [ ] Sync Canton Topology to registry
  • [ ] Implement on-chain ECDSA verification
  • [ ] Set up automated key sync service
  • [ ] Test key rotation procedures

Pattern 3 (MPC-Signed)

  • [ ] Integrate with MPC provider API
  • [ ] Mirror Canton policies in MPC service
  • [ ] Implement policy sync mechanism
  • [ ] Test MPC signing latency
  • [ ] Set up MPC cluster monitoring

Canton DeFi - Multichain DeFi Technical Reference