A Canton Wallet Where the Server Never Holds a Private Key
A REST API for Canton Network external parties where all key generation and signing happen client-side, so the server executes transactions it can never unilaterally authorize.
The default way to build a wallet API is custodial: the server holds the keys, signs on the user's behalf, and the user trusts it not to misbehave. This project takes the opposite stance. It is a REST API for Canton Network external parties where the server never holds private keys at all. Users generate keys locally, sign transaction hashes locally, and hand the server only the finished signatures to submit for execution.
Splitting authority from execution
The whole design rests on separating who can authorize a transaction from who can execute one. The server orchestrates: it builds transactions, talks to the network, injects markers, runs the round-runner pipeline. But it can never move funds on its own, because it does not possess the material needed to sign. That capability lives only on the user's machine, in the Ed25519 private keys stored as .pem files that, as the README puts it bluntly, should never leave that machine.
For a wallet handling real value, that is the property you want. A server compromise leaks operational data but not signing authority. The blast radius of the most dangerous failure is contained by construction rather than by policy.
The namespace gotcha
The part that took real work to get right was party identity. Canton party IDs have the format {hint}::{namespace}, and the intuitive assumption is that the namespace is just a hash of the public key. It is not. Canton derives the namespace internally through its own protobuf-based process, so it is not simply SHA256(raw_key) or SHA256(SPKI_DER). Trying to compute it yourself is a dead end.
The clean resolution was to stop fighting it. You generate a key locally, call POST /api/party/generate with the public key hex, and Canton hands back the canonical partyId with the correct namespace already attached. You store that and use it for everything after. At signing time you pass the partyId to the signing helper so it can extract the namespace for the signedBy field. The rule that made the rest of the system simple was: never derive the namespace, always ask Canton for it and treat the returned party ID as the source of truth.
Key material is tracked across a few manifests, splitting existing parties, newly generated ones pending registration, and fully registered external parties, so the system always knows which keys are live and which are still in flight.
The takeaway
Non-custodial design is less about cryptography and more about discipline over where authority physically lives. Keep signing on the client, keep execution on the server, and never let the two blur. The Canton namespace lesson generalizes too: when a platform derives an identifier through its own internal rules, do not reverse-engineer it, request the canonical value and build on that.
Have something that needs building, or stabilizing?
These notes are the work log. The paid work runs through Moonshine Labs, my product and engineering studio. Tell us what you're building, or grab a call.
Work with us →