Key Management
Crypto key management
This page explains how to list, rotate, generate, deactivate, and delete keys of a Canton node. It also explains how to configure the cryptographic schemes supported by a node. There is a separate section on the management of namespace keys.List keys
Use the following command to enumerate the public keys that the nodemyNode stores in its vault:
@ myNode.keys.public.list()
res1: Seq[PublicKeyWithName] = Vector(
SigningPublicKeyWithName(
publicKey = SigningPublicKey(
id = 122009299340...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
name = Some(KeyName(mediator1-namespace))
),
..
myNode is running and has created keys during startup, which is the default behavior.
Similarly, a user can enumerate the private keys of myNode:
@ myNode.keys.secret.list()
res2: Seq[com.digitalasset.canton.crypto.admin.grpc.PrivateKeyMetadata] = Vector(
PrivateKeyMetadata(
publicKeyWithName = SigningPublicKeyWithName(
publicKey = SigningPublicKey(
id = 122009299340...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
name = Some(KeyName(mediator1-namespace))
..
@ myNode.topology.owner_to_key_mappings
.list(store = mySynchronizerId, filterKeyOwnerUid = myNode.id.filterString)
.flatMap(_.item.keys)
res3: Seq[PublicKey] = Vector(
SigningPublicKey(
id = 1220422808e4...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(sequencer-auth, proof-of-ownership)
),
SigningPublicKey(
id = 1220856f2f04...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
..
Rotate keys
Canton supports rotating the keys of a nodemyNode with a single command:
@ myNode.keys.secret.rotate_node_keys()
myNode. But the command does not rotate namespace keys. The node deactivates the old keys on all Synchronizers, but they remain in the vault of myNode.
The following command rotates a single key of myNode:
@ myNode.keys.secret.rotate_node_key(oldKeyFingerprint, "newKeyName")
res5: PublicKey = SigningPublicKey(
id = 122030fa0bcd...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(sequencer-auth, proof-of-ownership)
)
Key rotation should be interleaved with taking a backup.
Generate and activate keys
Canton creates keys with default parameters for all nodes. To have different key parameters, generate keys manually. To generate and activate a new signing key formyNode, run the following commands:
@ val key = myNode.keys.secret.generate_signing_key(
"mySigningKeyName",
SigningKeyUsage.ProtocolOnly,
Some(SigningKeySpec.EcCurve25519)
)
key : SigningPublicKey = SigningPublicKey(
id = 12207417431d...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
)
@ myNode.topology.owner_to_key_mappings.add_key(key.fingerprint, key.purpose)
@ val key = myNode.keys.secret.generate_encryption_key(
"myEncryptionKeyName",
Some(EncryptionKeySpec.EcP256)
)
key : EncryptionPublicKey = EncryptionPublicKey(
id = 12201fe41afb...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-P256
)
@ myNode.topology.owner_to_key_mappings.add_key(key.fingerprint, key.purpose)
SigningKeyUsage. Keys are generated according to specific cryptographic key formats. Please refer to the following tables for more information on the expected key formats in Canton.
Deactivate keys
To deactivate a key on all Synchronizers run the following command:@ myNode.topology.owner_to_key_mappings.remove_key(key.fingerprint, key.purpose)
Delete keys
After rotating or deactivating a key it remains in the node’s vault. To permanently delete a key from the vault, use the following console command:@ myNode.keys.secret.delete(key.fingerprint, force = true)
Exercise caution when deleting a private key. Ensure that the key is no longer in use and is not needed for decrypting or creating signatures for old messages. Therefore, only delete a key after deactivating the key and pruning the node for a timestamp later than the deactivation. This is especially crucial for sequencers in open networks. For example, if a participant lags in retrieving sequenced events, and the operator of the sequencer rolls the sequencer’s signing key, the old signing key must remain accessible to sign the events from before the key roll for lagging participants. Otherwise, deleting the key prematurely may cause irreversible issues for these participants.
Configure cryptographic schemes
Canton supports several cryptographic schemes. By default, a node allows for using all cryptographic schemes supported by Canton. To configure the default and allowed schemes for a node include a snippet like the following into the Canton configuration:canton {
participants.participant1 {
crypto {
provider = jce
# signing
signing.algorithms {
default = ed-25519
allowed = [ed-25519, ec-dsa-sha-256, ec-dsa-sha-384]
}
signing.keys {
default = ec-curve-25519
allowed = [ec-curve-25519, ec-p-256, ec-p-384, ec-secp-256k-1]
}
# asymmetric encryption
encryption.algorithms {
default = ecies-hkdf-hmac-sha-256-aes-128-cbc
allowed = [ecies-hkdf-hmac-sha-256-aes-128-cbc, rsa-oaep-sha-256]
}
encryption.keys {
default = ec-p-256
allowed = [ec-p-256, rsa-2048]
}
}
}
}
Every Synchronizer imposes a minimum set of cryptographic schemes that its members must support. If a node does not support this minimum set of schemes, it is unable to connect to the Synchronizer.
Disable Session keys
An explanation of the purpose and security implications of session keys is available in Cryptographic keys in Canton. While session keys improve performance, they also introduce a security risk, as the keys are stored in memory—even if only for a short duration. If you prefer to disable session keys and accept the resulting performance degradation, you can do so by setting the following configurations. To disable session encryption keys:canton.participants.participant3.caching.session-encryption-key-cache.enabled = false
canton.participants.participant3.crypto.kms.session-signing-keys.enabled = false
Restrict key usage
This page explains how to limit the usage of cryptographic keys of a Canton node to specific purposes, as best practice to contain damage of potentially compromised keys.Signing key usage
Canton defines the following key usages for signing keys:Namespace: a key that defines a node’s identity and signs topology requests (see namespace key management)SequencerAuthentication: a key that authenticates members of the network towards a sequencerProtocol: a key that signs various structures as part of the synchronization protocol execution
Restrict the usage of a signing key
Set theusage parameter with the desired usages when you generate a new key.
For example, use the following command to generate a signing key restricted to Protocol usage only:
@ val myKey = myNode.keys.secret.generate_signing_key(
name = "mySigningKey",
usage = SigningKeyUsage.ProtocolOnly,
)
myKey : SigningPublicKey = SigningPublicKey(
id = 12208d0f7a70...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
)
NamespaceOnly, SequencerAuthenticationOnly, ProtocolOnly, All. Individual usages can otherwise be combined in a Set, but this is not recommended to avoid that keys are used for multiple purposes.
Once specified during key generation, the set of usages is fixed and cannot be modified during the lifetime of that key. Instead, a new key must be generated and activated, and the old one deactivated. See key management for more information regarding the key management commands.
usage parameter with the desired usages when you register the keys with Canton:
node.keys.secret.register_kms_signing_key(
kmsKeyId,
usage = SigningKeyUsage.ProtocolOnly,
name = s"${node.name}-signing-new",
)
Determine the usage of an existing signing key
To find what usage a signing key has been assigned, use the following command:@ val intermediateKey = myNode.keys.public.list(
filterFingerprint = myKey.id.unwrap,
)
intermediateKey : Seq[PublicKeyWithName] = Vector(
SigningPublicKeyWithName(
publicKey = SigningPublicKey(
id = 12208d0f7a70...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
),
name = Some(KeyName(mySigningKey))
)
)
filterFingerprint parameter, all the existing keys will be returned.
Determine the existing keys with a given usage
To find all the signing keys that have been assigned a given usage, use the following command:@ val intermediateKey = myNode.keys.public.list(
filterUsage = SigningKeyUsage.ProtocolOnly,
)
intermediateKey : Seq[PublicKeyWithName] = Vector(
SigningPublicKeyWithName(
publicKey = SigningPublicKey(
id = 12208d0f7a70...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
),
name = Some(KeyName(mySigningKey))
),
SigningPublicKeyWithName(
publicKey = SigningPublicKey(
id = 1220a1b7aea3...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
),
name = Some(KeyName(mediator1-signing))
)
)
filterUsage will be returned.
Namespace key delegation restrictions
As described in the identity management page, Canton uses topology transactions to manage identities, and these transactions are signed by keys withNamespace usage. Namespace key management shows that you can create intermediate keys to sign topology transactions, thereby limiting exposure of the root namespace key. These intermediate keys can be further restricted in terms of which kind of topology transactions they can sign using fine-grained delegation restrictions.
Canton defines the following restrictions for namespace keys:
// the key can sign all currently known mappings and all mappings that will be added in future releases
message CanSignAllMappings {}
// the key can sign all currently known mappings and all mappings that will be added in future releases, except for
// namespace delegations
message CanSignAllButNamespaceDelegations {}
// the key can only sign the explicitly specified mappings
message CanSignSpecificMappings {
repeated Enums.TopologyMappingCode mappings = 1;
}
Restrict the types of mappings that a key can sign
You must first create a new key with at leastNamespace usage, then delegate the signing privileges for a namespace to that key, specifying the desired restrictions in the delegationRestriction parameter.
For example, in the following, the first command creates an intermediate key for the root namespace, then the second one delegates the signing privileges to it, allowing it to sign all types of topology mappings except namespace delegations themselves:
@ val intermediateKey = myNode.keys.secret.generate_signing_key(
name = "intermediate-key",
usage = SigningKeyUsage.NamespaceOnly,
)
intermediateKey : SigningPublicKey = SigningPublicKey(
id = 1220e3a84d5e...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
)
@ myNode.topology.namespace_delegations.propose_delegation(
namespace = myNode.namespace,
targetKey = intermediateKey,
delegationRestriction = CanSignAllButNamespaceDelegations,
)
res5: SignedTopologyTransaction[TopologyChangeOp, NamespaceDelegation] = SignedTopologyTransaction(
TopologyTransaction(
NamespaceDelegation(
namespace = 122009299340...,
target = SigningPublicKey(
id = 1220e3a84d5e...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
restriction = not-nsd
),
serial = 1,
operation = Replace,
hash = SHA-256:292ba7211977...
),
signatures = 122009299340...
)
propose_delegation command with the updated delegation restrictions.
If you define intermediate namespace keys with restricted delegations, you must ensure that all authorizations are covered and that by combining multiple namespace keys together, they can authorize all topology transactions. Otherwise, some operational procedures may fail. For example, rotating a key will fail if there is not at least one namespace key that can authorize
OwnerToKey mappings.In such cases, the operation will fail with a “Could not find an appropriate signing key to issue the topology transaction” error message and code TOPOLOGY_NO_APPROPRIATE_SIGNING_KEY_IN_STORE.Determine the restrictions of an existing namespace key
To find what signing restrictions have been defined on a namespace key, use the following command:@ myNode.topology.namespace_delegations.list(
store = TopologyStoreId.Authorized,
filterTargetKey = Some(intermediateKey.id),
)
res6: Seq[com.digitalasset.canton.admin.api.client.data.topology.ListNamespaceDelegationResult] = Vector(
ListNamespaceDelegationResult(
context = BaseResult(
storeId = Authorized,
validFrom = 2026-06-30T12:43:02.807275Z,
validUntil = None,
sequenced = 2026-06-30T12:43:02.807275Z,
operation = Replace,
transactionHash = TxHash(hash = SHA-256:292ba7211977...),
serial = PositiveNumeric(value = 1),
signedBy = Vector(122009299340...)
),
item = NamespaceDelegation(
namespace = 122009299340...,
target = SigningPublicKey(
id = 1220e3a84d5e...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
restriction = not-nsd
)
)
)
filterTargetKey parameter, all the existing namespace delegations will be returned.
Namespace Key Management
This document is a work in progress. The feature will be developed and released in the future, following the procedures described in the document.
Online Root Namespace Key
By default, the Canton node creates a root namespace key during the initialization of the node. Operators can manually create an intermediate key on the node to authorize topology transactions. Only the intermediate key can be changed. Therefore, it is important to keep the root key secure. If a Canton node is configured to use a KMS system, then the root key can be administratively isolated, using the KMS Administration controls to remove node access to the root key. As the root key is only required when authorizing a new intermediate key or revoking an existing authorization, it is sufficient to just selectively allow the node to access the key only whenever such operations are performed. Only the root key can be isolated. All the other keys are essential for Canton to operate correctly. Optionally, the intermediate key can also be isolated but will have to be turned on and off as needed during administrative operations such as uploading DARs or adding parties.The offline root key procedure changed substantially in Canton 3.5 — renamed scripts, a restructured flow, and Ed25519 in place of NIST P-256. Select the tab for your node’s Canton version. Canton 3.5 covers current MainNet, TestNet, and DevNet releases; use Canton 3.4 only for nodes still on a pre-3.5 release.
- Canton 3.5
- Canton 3.4
Offline Root Namespace Key
A more secure way to manage the root namespace key is to use an offline root namespace key. In this case, the root namespace key is stored in a secure, possibly air gapped location, inaccessible from the Canton node.Where the root key is stored depends on your organization’s security requirements. For the remainder of this guide, assume that there are two sites, the online site of the node with its intermediate key and the offline site of the root key. The offline root key is used to sign a delegation to the node’s intermediate key, which is then used to authorize topology transactions.The offline root key procedure is supported by a set of utility scripts, which can be found in the Cantonscripts directory (scripts/offline-root-key).Channel for Key Exchange
There must be a channel or method which allows to exchange the public intermediate key and the signed intermediate namespace delegations between the online and offline sites. The channel must be trusted in terms of authenticity, but not confidentiality, as no secret information is exchanged. This means you need to ensure that the data is not tampered with during transport, but the data itself does not need to be encrypted.This can be done using multiple methods, depending on whether the sites are air-gapped or connected through a network. Possible examples are: secure file transfer, QR codes, physical storage medium.Assuming that such a trusted channel exists, the following steps are required to set up the offline root namespace key:1. Configure Node To Expect External Root Key
Before the first start-up, the Canton node must be configured not to initialize automatically. This is done by settingparticipant1.init.identity.type = manual
2. Export Public Key of Node
Start by creating a temporary directory where we’ll store keys and certificates during the initialization process.mkdir -p /tmp/canton/certs
@ val intermediateKey = participant1.keys.secret.generate_signing_key(name = "NamespaceDelegation", usage = com.digitalasset.canton.crypto.SigningKeyUsage.NamespaceOnly)
intermediateKey : SigningPublicKey = SigningPublicKey(
id = 1220bbf4353d...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
)
@ val intermediateKeyPath = better.files.File("/tmp/canton/certs/intermediate_key.pub").pathAsString
intermediateKeyPath : String = "/tmp/canton/certs/intermediate_key.pub"
@ participant1.keys.public.download_to(intermediateKey.id, intermediateKeyPath)
enum SigningKeySpec {
SIGNING_KEY_SPEC_UNSPECIFIED = 0;
// Elliptic Curve Key from Curve25519
// as defined in http://ed25519.cr.yp.to/
SIGNING_KEY_SPEC_EC_CURVE25519 = 1;
// Elliptic Curve Key from the NIST P-256 curve (aka secp256r1)
// as defined in https://doi.org/10.6028/NIST.FIPS.186-4
SIGNING_KEY_SPEC_EC_P256 = 2;
// Elliptic Curve Key from the NIST P-384 curve (aka secp384r1)
// as defined in https://doi.org/10.6028/NIST.FIPS.186-4
SIGNING_KEY_SPEC_EC_P384 = 3;
// Elliptic Curve Key from SECG P256k1 curve (aka secp256k1)
// commonly used in bitcoin and ethereum
// as defined in https://www.secg.org/sec2-v2.pdf
SIGNING_KEY_SPEC_EC_SECP256K1 = 4;
}
grpcurl -d '{}' <sequencer_endpoint> com.digitalasset.canton.sequencer.api.v30.SequencerConnectService/GetSynchronizerParameters
3. Share Public Key of Node with Offline Site
Next, the intermediate public key must be transported to the offline site as described above. Ensure that the public key is not tampered with during transport.4. Generate Root Key and The Root Certificate
Ensure that the necessary scripts are available on the secure site. These scripts are included in the Canton release packages atscripts/offline-root-key. This directory is self-contained and can be inspected / copied over to the secure site
to generate and sign the certificates required to initialize the node and rotate delegations.The following tools are required for running those scripts:- openssl: CLI tool used to detect key formats and encode data to base64. It’s also used in the example scripts (see below) to generate keys and signatures.
- buf: CLI tool used to convert between protobuf binary and JSON formats. Used to build and visualize transactions before signing them.
- xxd: Linux command to convert between bytes and hexadecimal representation
- gunzip: Linux command to decompress gzipped files. Used to inspect the buf image and provide better error reporting.
- jq: CLI tool used to manipulate JSON data, used to build and display protobuf messages in JSON format.
Using OpenSSL
From this point forward, all commands must be run from thescripts/offline-root-key directory for this example.This section generates keys with OpenSSL which stores the private key unencrypted on disk.
This is NOT a secure way to store private keys. For production deployments, make sure to secure the private key or use a KMS to manage it.
openssl genpkey -algorithm Ed25519 -outform DER -out "/tmp/canton/certs/root_private_key.der"
openssl pkey -in "/tmp/canton/certs/root_private_key.der" -pubout -outform DER -out "/tmp/canton/certs/root_public_key.der"
./prepare-cert.sh --root-delegation --root-pub-key "/tmp/canton/certs/root_public_key.der" --target-pub-key "/tmp/canton/certs/root_public_key.der" --output "/tmp/canton/certs/root_namespace"
x509 SPKI DER format. For more information on Canton’s supported key
formats, please refer to the following supported cryptographic schemes and formats reference.
This generates two files, root-delegation.prep and root-delegation.hash.
.prep files contain unsigned topology transactions serialized to bytes.
If you really want to be sure what you are signing, inspect the prepare-cert.sh script to see how it generates the topology transaction and how it computes
the hash. Next, the hash needs to be signed.Sign the hash:openssl pkeyutl -rawin -inkey "/tmp/canton/certs/root_private_key.der" -keyform DER -sign -in "/tmp/canton/certs/root_namespace.hash" -out "/tmp/canton/certs/root_namespace.signature"
./assemble-cert.sh --prepared-transaction "/tmp/canton/certs/root_namespace.prep" --signature "/tmp/canton/certs/root_namespace.signature" --signature-algorithm "ed25519" --output "/tmp/canton/certs/root_namespace"
assemble-cert.sh script for how it matches the value of signature-algorithm argument to canton signature and format:ed25519)
SIGNATURE_ALGORITHM_SPEC="SIGNING_ALGORITHM_SPEC_ED25519"
SIGNATURE_FORMAT="SIGNATURE_FORMAT_CONCAT"
;;
ecdsa256)
SIGNATURE_ALGORITHM_SPEC="SIGNING_ALGORITHM_SPEC_EC_DSA_SHA_256"
SIGNATURE_FORMAT="SIGNATURE_FORMAT_DER"
;;
ecdsa384)
SIGNATURE_ALGORITHM_SPEC="SIGNING_ALGORITHM_SPEC_EC_DSA_SHA_384"
SIGNATURE_FORMAT="SIGNATURE_FORMAT_DER"
;;
Using GCP KMS
Use the KMS CLI (https://cloud.google.com/kms/docs/create-validate-signatures) with the following commands to generate the key:gcloud kms keyrings create key-ring --location location (APPROXIMATE)
gcloud kms asymmetric-sign \
--version key-version \
--key <root-key> \
--keyring key-ring \
--location location \
--digest-algorithm digest-algorithm \
--input-file root-delegation.prep \
--signature-file root-delegation.signature
5. Create the Intermediate Certificate
If the root key and the self-signed root delegation are available, you can create the intermediate certificate. The steps are very similar to the root certificate, but the target is the public key of the intermediate key, and the--intermediate-delegation flag is used instead of --root-delegation../prepare-cert.sh --intermediate-delegation --root-pub-key "/tmp/canton/certs/root_public_key.der" --canton-target-pub-key "/tmp/canton/certs/intermediate_key.pub" --output "/tmp/canton/certs/intermediate_namespace"
openssl pkeyutl -rawin -inkey "/tmp/canton/certs/root_private_key.der" -keyform DER -sign -in "/tmp/canton/certs/intermediate_namespace.hash" -out "/tmp/canton/certs/intermediate_namespace.signature"
./assemble-cert.sh --prepared-transaction "/tmp/canton/certs/intermediate_namespace.prep" --signature "/tmp/canton/certs/intermediate_namespace.signature" --signature-algorithm "ed25519" --output "/tmp/canton/certs/intermediate_namespace"
7. Copy the Certificates to the Online Site
The generated certificates (never the root private key) need to be transferred to the online site. The public keys are included in the certificates and don’t need to be transported separately. You need to transfer both certificates, the root delegation and the intermediate delegation, to the online site.8. Import the Certificates to the Node
On the target site, import the certificates into the waiting node using the console command@ participant1.topology.init_id(identifier = "participant1", delegationFiles = Seq("/tmp/canton/certs/root_namespace.cert", "/tmp/canton/certs/intermediate_namespace.cert"))
@ participant1.health.status
res5: NodeStatus[ParticipantStatus] = Participant id: PAR::participant1::1220f13f352ab3a5e2567cb06b9e8f958e47e5e200087016065df3ba7822b19b2c71
Uptime: 0.061886s
Ports:
ledger: 30040
admin: 30041
Connected synchronizers: None
Unhealthy synchronizers: None
Active: true
Components:
memory_storage : Ok()
connected-synchronizer : Not Initialized
sync-ephemeral-state : Not Initialized
sequencer-client : Not Initialized
acs-commitment-processor : Not Initialized
Version: 3.5.1-SNAPSHOT
Supported protocol version(s): 35
grpccurl to initialize the node.Pre-Generated Certificates
The certificates can also be provided directly via the node’s configuration file if they’ve been generated beforehand. In this scenario, instead of generating the intermediate key via the node’sgenerate_signing_key command as described above,
the key must be generated on a KMS and its public key material downloaded. See Run with manually-generated keys for details.
The same scripts can then be used to generate the certificate,
with the exception that the intermediate public key will not be in the Canton format but in a DER format and should therefore be set with --target-pub-key.
Once the certificates are available, they can be configured on the node as such:canton.participants.mynode.init.node-identity = {
type = external
prefix = "mynodename" // optional prefix, random string generated otherwise
certificates = ["root-delegation.cert", "intermediate-delegation.cert"]
}
Delegation Restrictions
You can further restrict the kind of topology transactions a delegation can authorize. Theprepare-cert script exposes a --delegation-restrictions flag for that purpose.For example, to create a delegation that can only sign namespace delegations, let’s first create a new key for that delegation:@ val keyWithRestrictionsPath = "/tmp/canton/certs/restricted_key.pub"
keyWithRestrictionsPath : String = "/tmp/canton/certs/restricted_key.pub"
@ val keyWithRestrictions = participant1.keys.secret.generate_signing_key(name = "RestrictedKey", usage = Set(SigningKeyUsage.Namespace))
keyWithRestrictions : SigningPublicKey = SigningPublicKey(
id = 122095ad731e...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
)
@ participant1.keys.public.download_to(keyWithRestrictions.id, keyWithRestrictionsPath)
--delegation-restrictions flag on the prepare script this time:./prepare-cert.sh --delegation-restrictions PARTY_TO_PARTICIPANT,PARTY_TO_KEY_MAPPING --root-pub-key "/tmp/canton/certs/root_public_key.der" --canton-target-pub-key "/tmp/canton/certs/restricted_key.pub" --output "/tmp/canton/certs/restricted_key_namespace"
openssl pkeyutl -rawin -inkey "/tmp/canton/certs/root_private_key.der" -keyform DER -sign -in "/tmp/canton/certs/restricted_key_namespace.hash" -out "/tmp/canton/certs/restricted_key_namespace.signature"
./assemble-cert.sh --prepared-transaction "/tmp/canton/certs/restricted_key_namespace.prep" --signature "/tmp/canton/certs/restricted_key_namespace.signature" --signature-algorithm "ed25519" --output "/tmp/canton/certs/restricted_key_namespace"
@ participant1.topology.transactions.load_single_from_file("/tmp/canton/certs/restricted_key_namespace.cert", TopologyStoreId.Authorized)
Rotate the Intermediate Key
Rotating the intermediate key involves creating a new key, and revoking the current one.Create new Intermediate Key
Creating a new key follow the same steps as before:@ val newIntermediateKey = participant1.keys.secret.generate_signing_key(
name = "NewIntermediateKey",
usage = Set(SigningKeyUsage.Namespace)
)
newIntermediateKey : SigningPublicKey = SigningPublicKey(
id = 1220a89c7f67...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
)
@ participant1.keys.public.download_to(newIntermediateKey.id, "/tmp/canton/certs/new_intermediate_key.pub")
Export delegation to revoke
@ val delegationToRevokePath = s"/tmp/canton/certs/delegation_to_revoke.tx"
delegationToRevokePath : String = "/tmp/canton/certs/delegation_to_revoke.tx"
@ participant1.topology.namespace_delegations.list(TopologyStoreId.Authorized).find(_.item.target == intermediateKey).get.toTopologyTransaction.writeToFile(delegationToRevokePath)
Generate revocation and new intermediate certificates
Prepare revocation and new delegation certificates:./prepare-cert.sh --revoke-delegation "/tmp/canton/certs/delegation_to_revoke.tx" --output "/tmp/canton/certs/revoked_delegation"
./prepare-cert.sh --intermediate-delegation --root-pub-key "/tmp/canton/certs/root_public_key.der" --canton-target-pub-key "/tmp/canton/certs/new_intermediate_key.pub" --output "/tmp/canton/certs/new_delegation"
openssl pkeyutl -rawin -inkey "/tmp/canton/certs/root_private_key.der" -keyform DER -sign -in "/tmp/canton/certs/revoked_delegation.hash" -out "/tmp/canton/certs/revoked_delegation.signature"
openssl pkeyutl -rawin -inkey "/tmp/canton/certs/root_private_key.der" -keyform DER -sign -in "/tmp/canton/certs/new_delegation.hash" -out "/tmp/canton/certs/new_delegation.signature"
./assemble-cert.sh --prepared-transaction "/tmp/canton/certs/revoked_delegation.prep" --signature "/tmp/canton/certs/revoked_delegation.signature" --signature-algorithm "ed25519" --output "/tmp/canton/certs/revoked_delegation"
./assemble-cert.sh --prepared-transaction "/tmp/canton/certs/new_delegation.prep" --signature "/tmp/canton/certs/new_delegation.signature" --signature-algorithm "ed25519" --output "/tmp/canton/certs/new_delegation"
@ participant1.topology.namespace_delegations.list(store = TopologyStoreId.Authorized)
res14: Seq[com.digitalasset.canton.admin.api.client.data.topology.ListNamespaceDelegationResult] = Vector(
ListNamespaceDelegationResult(
context = BaseResult(
storeId = Authorized,
validFrom = 2026-05-21T22:39:26.923100Z,
validUntil = None,
sequenced = 2026-05-21T22:39:26.923100Z,
operation = Replace,
transactionHash = TxHash(hash = SHA-256:bbacbd47a13c...),
serial = PositiveNumeric(value = 1),
signedBy = Vector(1220f13f352a...)
),
item = NamespaceDelegation(
namespace = 1220f13f352a...,
target = SigningPublicKey(
id = 1220f13f352a...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
restriction = none
)
),
ListNamespaceDelegationResult(
context = BaseResult(
storeId = Authorized,
validFrom = 2026-05-21T22:39:26.924955Z,
validUntil = None,
sequenced = 2026-05-21T22:39:26.924955Z,
operation = Replace,
transactionHash = TxHash(hash = SHA-256:112962d6302d...),
serial = PositiveNumeric(value = 1),
signedBy = Vector(1220f13f352a...)
),
item = NamespaceDelegation(
namespace = 1220f13f352a...,
target = SigningPublicKey(
id = 1220bbf4353d...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
restriction = not-nsd
)
),
ListNamespaceDelegationResult(
context = BaseResult(
storeId = Authorized,
validFrom = 2026-05-21T22:39:28.544665Z,
validUntil = None,
sequenced = 2026-05-21T22:39:28.544665Z,
operation = Replace,
transactionHash = TxHash(hash = SHA-256:c91fdd18a9d6...),
serial = PositiveNumeric(value = 1),
signedBy = Vector(1220f13f352a...)
),
item = NamespaceDelegation(
namespace = 1220f13f352a...,
target = SigningPublicKey(
id = 122095ad731e...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
restriction = PartyToParticipant,PartyToKeyMapping
)
)
)
- The root delegation
- The intermediate delegation (about to be rotated)
- The restricted delegation
@ participant1.topology.transactions.load_single_from_files(Seq("/tmp/canton/certs/revoked_delegation.cert", "/tmp/canton/certs/new_delegation.cert"), TopologyStoreId.Authorized)
@ participant1.topology.namespace_delegations.list(store = TopologyStoreId.Authorized)
res16: Seq[com.digitalasset.canton.admin.api.client.data.topology.ListNamespaceDelegationResult] = Vector(
ListNamespaceDelegationResult(
context = BaseResult(
storeId = Authorized,
validFrom = 2026-05-21T22:39:26.923100Z,
validUntil = None,
sequenced = 2026-05-21T22:39:26.923100Z,
operation = Replace,
transactionHash = TxHash(hash = SHA-256:bbacbd47a13c...),
serial = PositiveNumeric(value = 1),
signedBy = Vector(1220f13f352a...)
),
item = NamespaceDelegation(
namespace = 1220f13f352a...,
target = SigningPublicKey(
id = 1220f13f352a...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
restriction = none
)
),
ListNamespaceDelegationResult(
context = BaseResult(
storeId = Authorized,
validFrom = 2026-05-21T22:39:28.544665Z,
validUntil = None,
sequenced = 2026-05-21T22:39:28.544665Z,
operation = Replace,
transactionHash = TxHash(hash = SHA-256:c91fdd18a9d6...),
serial = PositiveNumeric(value = 1),
signedBy = Vector(1220f13f352a...)
),
item = NamespaceDelegation(
namespace = 1220f13f352a...,
target = SigningPublicKey(
id = 122095ad731e...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
restriction = PartyToParticipant,PartyToKeyMapping
)
),
ListNamespaceDelegationResult(
context = BaseResult(
storeId = Authorized,
validFrom = 2026-05-21T22:39:30.214374Z,
validUntil = None,
sequenced = 2026-05-21T22:39:30.214374Z,
operation = Replace,
transactionHash = TxHash(hash = SHA-256:fbb5800fb069...),
serial = PositiveNumeric(value = 1),
signedBy = Vector(1220f13f352a...)
),
item = NamespaceDelegation(
namespace = 1220f13f352a...,
target = SigningPublicKey(
id = 1220a89c7f67...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
restriction = not-nsd
)
)
)
Rotating the Root Namespace Key
You cannot rotate the root namespace key. If you need to discontinue the usage of the namespace, you need to create a new namespace, new parties and participants in that new namespace, and transfer the contracts to the new parties.Offline Root Namespace Key
A more secure way to manage the root namespace key is to use an offline root namespace key. In this case, the root namespace key is stored in a secure, possibly air gapped location, inaccessible from the Canton node.Where the root key is stored depends on your organization’s security requirements. For the remainder of this guide, assume that there are two sites, the online site of the node with its intermediate key and the offline site of the root key. The offline root key is used to sign a delegation to the node’s intermediate key, which is then used to authorize topology transactions.The offline root key procedure is supported by a set of utility scripts, which can be found in the Cantonscripts directory (scripts/offline-root-key).Channel for Key Exchange
There must be a channel or method which allows to exchange the public intermediate key and the signed intermediate namespace delegations between the online and offline sites. The channel must be trusted in terms of authenticity, but not confidentiality, as no secret information is exchanged. This means you need to ensure that the data is not tampered with during transport, but the data itself does not need to be encrypted.This can be done using multiple methods, depending on whether the sites are air-gapped or connected through a network. Possible examples are: secure file transfer, QR codes, physical storage medium.Assuming that such a trusted channel exists, the following steps are required to set up the offline root namespace key:1. Configure Node To Expect External Root Key
Before the first start-up, the Canton node must be configured not to initialize automatically. This is done by settingparticipant1.init.identity.type = manual
2. Export Public Key of Node
Assuming you have access to the remote console of the node, create a new signing key to use as the intermediate key:@ val key = participant1.keys.secret.generate_signing_key(name = "NamespaceDelegation", usage = com.digitalasset.canton.crypto.SigningKeyUsage.NamespaceOnly)
key : SigningPublicKey = SigningPublicKey(
id = 12205e85c626...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
)
@ val intermediateKeyPath = better.files.File.newTemporaryFile(prefix = "intermediate-key.pub").pathAsString
intermediateKeyPath : String = "/tmp/intermediate-key.pub10902663533700447198"
@ participant1.keys.public.download_to(key.id, intermediateKeyPath)
enum SigningKeySpec {
SIGNING_KEY_SPEC_UNSPECIFIED = 0;
// Elliptic Curve Key from Curve25519
// as defined in http://ed25519.cr.yp.to/
SIGNING_KEY_SPEC_EC_CURVE25519 = 1;
// Elliptic Curve Key from the NIST P-256 curve (aka secp256r1)
// as defined in https://doi.org/10.6028/NIST.FIPS.186-4
SIGNING_KEY_SPEC_EC_P256 = 2;
// Elliptic Curve Key from the NIST P-384 curve (aka secp384r1)
// as defined in https://doi.org/10.6028/NIST.FIPS.186-4
SIGNING_KEY_SPEC_EC_P384 = 3;
// Elliptic Curve Key from SECG P256k1 curve (aka secp256k1)
// commonly used in bitcoin and ethereum
// as defined in https://www.secg.org/sec2-v2.pdf
SIGNING_KEY_SPEC_EC_SECP256K1 = 4;
}
grpcurl -d '{}' <sequencer_endpoint> com.digitalasset.canton.sequencer.api.v30.SequencerConnectService/GetSynchronizerParameters
grpccurl against the Admin API to
invoke the commands.3. Share Public Key of Node with Offline Site
Next, the intermediate public key must be transported to the offline site as described above. Ensure that the public key is not tampered with during transport.4. Generate Root Key and The Root Certificate
Using OpenSSL
Ensure that the necessary scripts are available on the secure site. These scripts are included in the Canton release packages atscripts/offline-root-key.
An example demonstrating usage of those scripts using openssl to generate keys and sign certificates is available at examples/10-offline-root-namespace-init.
Run the next set of commands from the examples/10-offline-root-namespace-init directory.Start by initializing variables used in the snippets below# Points to the location of the offline root key scripts under the scripts/offline-root-key directory in the release artifact
SCRIPTS_ROOT="$(dirname "$0")/../../scripts/offline-root-key"
PRIVATE_KEY="$OUTPUT_DIR/root_private_key.der"
PUBLIC_KEY="$OUTPUT_DIR/root_public_key.der"
ROOT_NAMESPACE_PREFIX="$OUTPUT_DIR/root_namespace"
INTERMEDIATE_NAMESPACE_PREFIX="$OUTPUT_DIR/intermediate_namespace"
mkdir -p tmp/certs
OUTPUT_DIR="tmp/certs"
CANTON_NAMESPACE_DELEGATION_PUB_KEY=<Path to the intermediate key downloaded from Canton. intermediateKeyPath in this example>
CURRENT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null && pwd)
export BUF_PROTO_IMAGE="$CURRENT_DIR/../../scripts/offline-root-key/root_namespace_buf_image.json.gz"
source "$CURRENT_DIR/../../scripts/offline-root-key/utils.sh"
openssl ecparam -name prime256v1 -genkey -noout -outform DER -out "$PRIVATE_KEY"
openssl ec -inform der -in "$PRIVATE_KEY" -pubout -outform der -out "$PUBLIC_KEY" 2> /dev/null
"$SCRIPTS_ROOT/prepare-certs.sh" --root-delegation --root-pub-key "$PUBLIC_KEY" --target-pub-key "$PUBLIC_KEY" --output "$ROOT_NAMESPACE_PREFIX"
x509 SPKI DER format. For more information on Canton’s supported key
formats, please refer to the following supported cryptographic schemes and formats reference.
This generates two files, root-delegation.prep and root-delegation.hash.
.prep files contain unsigned topology transactions serialized to bytes.
If you really want to be sure what you are signing, inspect the prepare-certs.sh script to see how it generates the topology transaction and how it computes
the hash. Next, the hash needs to be signed.If you are using openssl, the following command can be used to sign the hash:openssl pkeyutl -rawin -inkey "$PRIVATE_KEY" -keyform DER -sign < "$ROOT_NAMESPACE_PREFIX.hash" > "$ROOT_NAMESPACE_PREFIX.signature"
"$SCRIPTS_ROOT/assemble-cert.sh" --prepared-transaction "$ROOT_NAMESPACE_PREFIX.prep" --signature "$ROOT_NAMESPACE_PREFIX.signature" --signature-algorithm ecdsa256 --output "$ROOT_NAMESPACE_PREFIX.cert"
Using GCP KMS
If you are using GCP KMS, you can use KMS CLI (https://cloud.google.com/kms/docs/create-validate-signatures) with the following commands to generate the key:gcloud kms keyrings create key-ring --location location (APPROXIMATE)
gcloud kms asymmetric-sign \
--version key-version \
--key <root-key> \
--keyring key-ring \
--location location \
--digest-algorithm digest-algorithm \
--input-file root-delegation.prep \
--signature-file root-delegation.signature
5. Create the Intermediate Certificate
If the root key and the self-signed root delegation are available, you can create the intermediate certificate. The steps are very similar to the root certificate, but the target is the public key of the intermediate key, and the--intermediate-delegation flag is used instead of --root-delegation."$SCRIPTS_ROOT/prepare-certs.sh" --intermediate-delegation --root-pub-key "$PUBLIC_KEY" --canton-target-pub-key "$CANTON_NAMESPACE_DELEGATION_PUB_KEY" --output "$INTERMEDIATE_NAMESPACE_PREFIX"
openssl pkeyutl -rawin -inkey "$PRIVATE_KEY" -keyform DER -sign < "$INTERMEDIATE_NAMESPACE_PREFIX.hash" > "$INTERMEDIATE_NAMESPACE_PREFIX.signature"
"$SCRIPTS_ROOT/assemble-cert.sh" --prepared-transaction "$INTERMEDIATE_NAMESPACE_PREFIX.prep" --signature "$INTERMEDIATE_NAMESPACE_PREFIX.signature" --signature-algorithm ecdsa256 --output "$INTERMEDIATE_NAMESPACE_PREFIX.cert"
7. Copy the Certificates to the Online Site
The generated certificates (never the root private key) need to be transferred to the online site. The public keys are included in the certificates and don’t need to be transported separately. You need to transfer both certificates, the root delegation and the intermediate delegation, to the online site.8. Import the Certificates to the Node
On the target site, import the certificates into the waiting node using the console command@ participant1.topology.init_id(identifier = "participant1", delegationFiles = Seq("tmp/certs/root_namespace.cert", "tmp/certs/intermediate_namespace.cert"))
@ participant1.health.status
res5: NodeStatus[ParticipantStatus] = Participant id: PAR::participant1::12201d8e60ec141a279bf5be0943f5bbcf80ea7b88ff44ab1e0d3f65ee12fb99ae07
Uptime: 0.068572s
Ports:
ledger: 30232
admin: 30233
Connected synchronizers: None
Unhealthy synchronizers: None
Active: true
Components:
memory_storage : Ok()
connected-synchronizer : Not Initialized
sync-ephemeral-state : Not Initialized
sequencer-client : Not Initialized
acs-commitment-processor : Not Initialized
Version: 3.4.12-SNAPSHOT
Supported protocol version(s): 34
grpccurl to initialize the node.Pre-Generated Certificates
The certificates can also be provided directly via the node’s configuration file if they’ve been generated beforehand. In this scenario, instead of generating the intermediate key via the node’sgenerate_signing_key command as described above,
the key must be generated on a KMS and its public key material downloaded. The same scripts can then be used to generate the certificate,
with the exception that the intermediate public key will not be in the Canton format but in a DER format and should therefore be set with --target-pub-key.
Once the certificates are available, you must register the intermediate KMS key by running:val intermediateKey = node.keys.secret
.register_kms_signing_key(
intermediateNsKmsKeyId,
SigningKeyUsage.NamespaceOnly,
name = s"${node.name}-${SigningKeyUsage.Namespace.identifier}",
)
Delegation Restrictions
You can further restrict the kind of topology transactions a delegation can authorize. Theprepare-certs script exposes a --delegation-restrictions flag for that purpose."$SCRIPTS_ROOT/prepare-certs.sh" --delegation-restrictions PARTY_TO_PARTICIPANT,PARTY_TO_KEY_MAPPING --root-pub-key "$PUBLIC_KEY" --canton-target-pub-key "$CANTON_RESTRICTED_PUB_KEY" --output "$RESTRICTED_KEY_NAMESPACE_PREFIX"
participant1.topology.transactions.load_single_from_file(s"$opensslKeysDirectory/restricted_key_namespace.cert", TopologyStoreId.Authorized)
Rotate the Intermediate Key
Create new Intermediate Key
In order to create another intermediate key, we follow the same steps as before. Create the key on the online site and export it.Follow the same steps to create a new intermediate delegation for the new intermediate key:- copy to secure site
- generate the intermediate delegation (skip self-signed root delegation as it has already been generated)
- copy the certificate to the node site
Revoking the Intermediate Key
To revoke the intermediate key, the root key needs to be used to sign a revocation transaction. The revocation transaction is prepared in the same way as the intermediate delegation:"$SCRIPTS_ROOT/prepare-certs.sh" --revoke-delegation "$CANTON_NAMESPACE_DELEGATION_TO_REVOKE" --output "$REVOKED_DELEGATION_PREFIX"
openssl pkeyutl -rawin -inkey "$PRIVATE_KEY" -keyform DER -sign -in "$REVOKED_DELEGATION_PREFIX.hash" > "$REVOKED_DELEGATION_PREFIX.signature"
participant1.topology.transactions.load_single_from_file(s"$opensslKeysDirectory/revoked_delegation.cert", TopologyStoreId.Authorized)
Rotating the Root Namespace Key
You cannot rotate the root namespace key. If you need to discontinue the usage of the namespace, you need to create a new namespace, new parties and participants in that new namespace, and transfer the contracts to the new parties.Configure session keys
Canton uses session keys to reduce expensive cryptographic operations during protocol execution, improving performance. There are two types: session encryption keys, which reduce the number of asymmetric encryptions, and session signing keys, which help avoid frequent calls to external signers such as a KMS. You can read more about the rationale and security considerations in Session Keys. Currently, session encryption keys are enabled by default, whereas session signing keys, being directly tied to a KMS, are disabled by default. However, the latter can be enabled when using an external KMS to store private keys via a configuration parameter.Important:
Extending the lifetime of session keys minimizes the need for repeated key negotiation or remote signing/encryption,
but it also increases the window during which keys are stored in memory, raising the risk of compromise.
Increase session encryption keys lifetime
You can control how long a session encryption key remains active by adjusting theexpire-after-timeout values in your configuration. To globally increase the lifetime of session encryption keys, increase the expire-after-timeout for both the sender-cache and receiver-cache.
canton.participants.participant1 {
caching {
session-encryption-key-cache {
# these are the default values
enabled = true
sender-cache {
maximum-size = 10000
expire-after-timeout = 10s
}
receiver-cache {
maximum-size = 10000
expire-after-timeout = 10s
}
}
}
}
Enable session signing keys and increase lifetime
When using external KMS (Key Management Service) provider you can enable and control how long session signing keys remain active by setting theenabled = true and adjusting the key-validity-duration
and the key-eviction-period. The key-eviction-period should always be longer than the key-validity-duration
and at least as long as the defaultMaxSequencingTimeOffset and the sum of confirmation_response_timeout
and mediator_reaction_timeout, as configured in the the dynamic Synchronizer parameters.
If you want to know more about each configurable parameter and the factors to consider when modifying them,
you can refer to the session signing keys’ Configurable Parameters section.
canton.participants.participant1.crypto.kms {
session-signing-keys {
enabled = true
key-validity-duration = 5m
tolerance-shift-duration = 2m
cut-off-duration = 1m,
key-eviction-period = 10m,
signing-algorithm-spec = ed-25519,
signing-key-spec = ec-curve-25519,
}
}