Module 6 · Lesson 1 · ~20 min read

Crypto Primitives You Will See in Canton

Canton uses cryptography heavily — every transaction is signed, every contract has a hash-based identity, key management is a first-class concept. You don't need to be a cryptographer; you need to recognize the primitives, use the standard library safely, and avoid the common pitfalls.

The four primitives

PrimitiveWhat it gives youGo package
HashFingerprint of data — same input → same output, different inputs → almost certainly different outputs.crypto/sha256, crypto/sha512
HMACHash that only the holder of a secret key can produce. Used for tamper detection.crypto/hmac
Symmetric encryptionSame key encrypts and decrypts. Fast.crypto/aes + crypto/cipher
Asymmetric (public-key) cryptoSign with private, verify with public; encrypt to public, decrypt with private. Slow but enables identity and trust without shared secrets.crypto/ed25519, crypto/ecdsa, crypto/rsa

Hashing — the workhorse

import "crypto/sha256"

h := sha256.Sum256([]byte("hello"))
fmt.Printf("%x\n", h)
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

For streaming, use the hash.Hash interface (which is also an io.Writer):

h := sha256.New()
io.Copy(h, file)         // hash everything in file
sum := h.Sum(nil)         // returns []byte of length 32

SHA-256 is the default in modern systems and Canton uses it broadly. Don't reach for MD5 or SHA-1 — both are broken for security purposes.

Hashes for identity, not security alone

In Canton, contract IDs, transaction IDs, package IDs are typically prefixed hashes — content-addressed. A contract's identity is a hash of its template, payload, and metadata. This means:

This pattern (content-addressing) is everywhere in distributed-ledger and version-control systems (Git uses SHA-1, IPFS uses multi-hash, etc.). When you see a long hex string in Canton's docs, it's almost always a content-addressed identifier.

Asymmetric crypto and signatures

The pattern that matters most for Canton: sign with a private key, anyone can verify with the public key.

import "crypto/ed25519"
import "crypto/rand"

// One-time setup: generate a key pair
pub, priv, _ := ed25519.GenerateKey(rand.Reader)

// Sign a message
msg := []byte("submit command #1")
sig := ed25519.Sign(priv, msg)

// Verify (anyone with the public key)
ok := ed25519.Verify(pub, msg, sig)  // true

Ed25519 is the modern default — fast, small keys (32 bytes), small signatures (64 bytes), no parameter choices to get wrong. ECDSA over P-256 is also common in Canton's ecosystem (it integrates better with HSMs and JCA-style Java tooling). RSA is rarer in new code but you'll still see it in some certs.

Why Canton signs commands

When your Go code submits a command to a Canton participant, the participant's view of "who is allowed to do what" depends on:

  1. The party's identity (which keys are authorized).
  2. A signature over the command from one of those keys.

This is what makes "Alice authorized this transfer" provable later, and what makes "Mallory can't impersonate Alice" enforceable. Real Canton key management is more nuanced (key rotation, hierarchical keys, HSM integration), but the underlying primitive is asymmetric signatures.

Random — use crypto/rand, never math/rand

import "crypto/rand"

// Bytes
buf := make([]byte, 16)
rand.Read(buf)

// Bounded integer
n, _ := rand.Int(rand.Reader, big.NewInt(100))

For anything security-relevant — keys, nonces, command IDs that must be unguessable — use crypto/rand. math/rand is fine for jitter or test fixtures but is predictable and unsafe for crypto.

Encoding and serialization

Hashes and signatures are byte slices. To pass them through JSON, headers, logs, or text protocols, encode them:

// Hex — most common in distributed systems for IDs and short payloads
hex.EncodeToString(hash)              // "2cf24..."

// Base64 — denser, common for keys and signatures in PEM/JWT
base64.StdEncoding.EncodeToString(sig)

// PEM — wraps base64 with framing, used for keys/certs in files
pem.Encode(out, &pem.Block{Type: "PUBLIC KEY", Bytes: pub})

Canton APIs and configs use a mix — JSON tokens carry base64, contract IDs come back as hex, key files are PEM. Recognize each and don't try to convert without checking.

JWTs — for Ledger API auth

Canton participants typically authenticate Ledger API calls via JWT (JSON Web Token) with a configurable claim shape. JWTs are: header.payload.signature, base64-url-encoded.

// Decode a JWT for inspection (NOT for verification)
import "encoding/base64"
import "encoding/json"
import "strings"

parts := strings.Split(token, ".")
payloadJSON, _ := base64.RawURLEncoding.DecodeString(parts[1])
var claims map[string]any
json.Unmarshal(payloadJSON, &claims)

For real verification (signature check, expiration, audience), use github.com/golang-jwt/jwt/v5. Don't roll your own JWT verification.

Common mistakes

Don't

Use md5 or sha1 for security. Both are broken. sha256 is the default. sha512 if you specifically need 64-byte digests.

Don't

Compare cryptographic outputs with ==. Use hmac.Equal or subtle.ConstantTimeCompare for things like comparing MAC tags or session tokens — short-circuit comparison leaks timing info.

Don't

Implement crypto yourself. Use the stdlib or well-known libraries. The bar is "don't even consider this." Half the world's CVEs are home-rolled crypto.

Don't

Hold private keys in memory longer than needed. For HSM-backed keys, sign through the HSM API; the key never reaches your process. For software keys, scope them tightly and consider crypto/x509's helpers.

What actually shows up in Canton Go work

Takeaways