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