- Overview
- Tutorials
- How Tos
- Download
- Install
- Configure
- Secure
- TLS API Configuration
- Configure API Authentication and Authorization with JWT
- Configure API Limits
- Set Resource Limits
- Crypto key management
- Restrict key usage
- Namespace Key Management
- Key management service (KMS) configuration
- Optimize
- Observe
- Operate
- Initializing node identity manually
- Canton Console
- Synchronizer connections
- High Availability Usage
- Manage Daml packages and archives
- Participant Node pruning
- Party Management
- Party Replication
- Decentralized party overview
- Setup an External Party
- Ledger API User Management
- Node Traffic Management
- Identity Management
- Upgrade
- Decommission
- Recover
- Troubleshoot
- Explanations
- Reference
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 the usage
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 = 122070511b47...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
)
Some predefined sets are available for common usages: 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.
Note
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.
If you are using a Key Management Service (KMS) to handle Canton’s keys in external key storage mode,
and want to use manually generated keys that are already stored in the KMS, set the 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",
)
The returned key can be used in other key management commands.
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 = 122070511b47...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
),
name = Some(KeyName(mySigningKey))
)
)
If you omit the 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 = 122070511b47...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
),
name = Some(KeyName(mySigningKey))
),
SigningPublicKeyWithName(
publicKey = SigningPublicKey(
id = 1220ed3a8a56...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = Set(signing, proof-of-ownership)
),
name = Some(KeyName(mediator1-signing))
)
)
All the keys that have been assigned at least one of the usages specified in 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 with Namespace
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 least Namespace
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 = 12200a176d27...,
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(
122009299340...,
SigningPublicKey(
id = 12200a176d27...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
CanSignAllButNamespaceDelegations
),
serial = 1,
operation = Replace
),
signatures = 122009299340...
)
Like all topology mappings, the delegation restrictions on a namespace key can be updated.
To do so, issue a new propose_delegation
command with the updated delegation restrictions.
Warning
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[topology.ListNamespaceDelegationResult] = Vector(
ListNamespaceDelegationResult(
context = BaseResult(
storeId = Authorized,
validFrom = 2025-07-17T22:20:03.987545Z,
validUntil = None,
sequenced = 2025-07-17T22:20:03.987545Z,
operation = Replace,
transactionHash = <ByteString@3cfe1da5 size=34 contents="\022 \347\373\000\025Z\265\325\335\023\025\233\235\373\355\b\343x7\223\276\030X*\352\200H\026\324\276V\236\236">,
serial = PositiveNumeric(value = 1),
signedBy = Vector(122009299340...)
),
item = NamespaceDelegation(
122009299340...,
SigningPublicKey(
id = 12200a176d27...,
format = DER-encoded X.509 SubjectPublicKeyInfo,
keySpec = EC-Curve25519,
usage = namespace
),
CanSignAllButNamespaceDelegations
)
)
)
If you omit the filterTargetKey
parameter, all the existing namespace delegations will be returned.