Skip to content

feat: add Canton chain integration#740

Draft
Pessina wants to merge 9 commits intodevelopfrom
feat/canton-chain-integration
Draft

feat: add Canton chain integration#740
Pessina wants to merge 9 commits intodevelopfrom
feat/canton-chain-integration

Conversation

@Pessina
Copy link
Copy Markdown
Contributor

@Pessina Pessina commented Apr 5, 2026

Summary

  • Adds Canton as a first-class chain in the MPC node following the existing plugin architecture
  • Canton supports bidirectional sign flow only (Canton → EVM → Canton)
  • Implements Canton JSON Ledger API client: JWT ES256 auth, WebSocket event streaming, EIP-712 request ID, RLP tx encoding, DER signature encoding

Key decisions

  • Native Rust (no TypeScript sidecar)
  • canton:global as synthetic CAIP-2 ID (Canton has no registered namespace in ChainAgnostic)
  • sender field on SignBidirectionalEvent is the pre-computed predecessorId from the Vault contract
  • Per-chain respond serialization: ABI for Canton, Borsh for Solana/Hydration
  • canton_operators/canton_requester threaded through BidirectionalTx for full round-trip

Files

  • New: chain-signatures/node/src/indexer_canton/mod.rs (1026 lines)
  • Modified: 16 files across primitives, crypto, node, and integration-tests

What's NOT included

  • Integration tests against cn-quickstart (separate plan — requires Canton container infra)
  • TypeScript POC update to use canton:global chain ID (separate PR to canton-mpc-poc)

Pessina added 9 commits April 5, 2026 20:49
Implements the Canton chain integration following the existing plugin
architecture (Solana/Ethereum/Hydration pattern). Canton supports the
bidirectional sign flow only (Canton → EVM → Canton).

- Add Chain::Canton to primitives with all match arms
- Add derive_epsilon_canton KDF function with golden tests
- Add Canton indexer module with JWT ES256 auth, WebSocket streaming
  (/v2/updates, daml.ws.auth subprotocol), EIP-712 request ID
  computation, RLP encoding, and DER signature encoding
- Add Canton variants to SignBidirectionalEvent (15 arms),
  RespondBidirectionalEvent (5 arms), SignatureRespondedEvent (3 arms)
- Add per-chain respond serialization format (ABI for Canton, Borsh
  for Solana/Hydration)
- Wire CantonClient into RPC executor with try_publish_canton
- Wire CantonArgs into CLI with stream startup
- Thread canton_operators/canton_requester through BidirectionalTx
  for the full bidirectional round-trip
Add stream-only and full-cluster Canton integration tests following
the Solana/Ethereum patterns. Introduce a shared `canton-types` crate
with typed Rust structs for the Canton JSON Ledger API v2 and Daml
contract payloads, replacing hand-crafted json!() and Value parsing
in both the MPC node indexer and integration tests.

- Add `canton-types` workspace crate (ledger_api + contracts modules)
- Add `CantonSandbox` test harness with JWT auth, party/user setup
- Add 6 stream-only tests and 1 full-cluster bidirectional test
- Wire Canton into ClusterSpawner/Cluster (follows Solana pattern)
- Migrate indexer_canton to typed deserialization (net -120 lines)
- Fix JWT scope claim (add scope: "daml_ledger_api")
- Fix predecessor_id derivation (use pre-computed sender field)
Start sandbox with auth enabled (-c auth.conf) but without --dar.
Upload DAR via POST /v2/packages with admin JWT after readiness.
This follows the cn-quickstart pattern and ensures Canton validates
all JWT tokens end-to-end during integration tests.
Add .github/workflows/canton.yml that installs dpm + Java 21, builds
the MPC node, and runs Canton stream tests with --ignored flag.
Check in the pre-built daml-vault DAR as a test fixture to avoid
building the canton-mpc-poc dependency chain in CI.
Replace canton_operators, canton_requester, canton_sender Option fields
on BidirectionalTx/RespondBidirectionalTx with a single ChainContext
enum. Canton's epsilon() now uses the original sender string stored in
ChainContext::Canton instead of the lossy keccak256 round-trip. Remove
unused PendingDepositPayload and predecessor_id() from canton-types.
- Switch to TRANSACTION_SHAPE_LEDGER_EFFECTS (Canton 3.4+)
- Add ExercisedEvent verification (SignBidirectional on pinned Signer template)
- Add nonce replay prevention via consuming ExercisedEvent on SigningNonce
- Replace manual DER with k256 to_der/from_der, fix 50% parity corruption
- Add resolve_signature_recovery_id via ecrecover against derived public key
- Replace manual RLP with alloy TxEip1559::encode_for_signing
- Fix serialized_transaction() returning empty vec for Canton
- Add stall watchdog (60s) and connect timeout (30s) on WebSocket
- Cache EncodingKey (parse PEM once, not per JWT)
- Use deterministic entropy via keccak256(request_id)
- Pin signer contract/template IDs via CLI args
- Verify sig_network matches node party_id
- Add template constants with module boundary enforcement
- Add Canton to startup log, fix checkpoint test interval
- Propagate channel send errors, remove dead code
- Add MAX_SECP256K1_SCALAR to mpc-primitives
- Use fresh DAR from canton-mpc-poc (matches current Signer/Vault split)
- Fix nonce exhaustion in concurrent tests (update nonce_cid after each request)
- Replace invalid dummy DER signature with valid k256-parseable one (r=1, s=1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant