Skip to content

Set up an Azure Key Vault key ​

This guide creates a secp256k1 signing key in Azure Key Vault or Azure Managed HSM, allows a deployer to get and sign with it and nothing else, signs in, adds the key to a Hardhat project and checks that it signs.

You need the az CLI.

With @hardhat-kms/azure, a connection lists the key's account and signs transactions, messages and typed data with it. kms history lists who signed with the key, when and from where, from the Key Vault audit log that a diagnostic setting sends to a Log Analytics workspace; see Audit logs.

NOTE

Audience: users who sign with a key in Azure Key Vault or Azure Managed HSM.

The plugin's live tests on Sepolia ran against a real vault, with the developer's own identity, not with the roles in step 2.

1. Create a secp256k1 signing key ​

Ethereum signs with secp256k1, the curve Key Vault calls P-256K, so the key must be an elliptic-curve key on it.

Check the vault before you create the key. How long a deleted key stays recoverable is the vault's soft-delete retention, 7 to 90 days, 90 by default, and it "can only be configured during key vault creation" (Soft-delete behavior). Purge protection is off by default and can be turned on later (Purge protection); "when purge protection is enabled, it cannot be disabled or overridden by anyone including Microsoft" (Azure Key Vault recovery overview). Read all three:

sh
az keyvault show --name my-vault \
  --query "{softDelete: properties.enableSoftDelete, retentionDays: properties.softDeleteRetentionInDays, purgeProtection: properties.enablePurgeProtection}"

purgeProtection prints null if purge protection was never turned on, and true once it is on; it cannot be turned off again. For a Managed HSM, use --hsm-name my-hsm. Give a key that will hold value the full 90 days: if this vault keeps deleted keys for less, create the key in a vault made with az keyvault create --retention-days 90 instead, and see Prevent and recover from losing a key for purge protection.

Now create the key. Allow it to sign:

sh
az keyvault key create \
  --vault-name my-vault \
  --name deployer \
  --kty EC \
  --curve P-256K \
  --ops sign verify

--kty EC keeps the private key in software in a Standard vault. --kty EC-HSM keeps it in an HSM and needs a Premium vault. In a Managed HSM, pass --hsm-name my-hsm instead of --vault-name; its keys are always EC-HSM. The plugin accepts both, and refuses any other key type or curve.

Print the key's versioned id, which the config uses:

sh
az keyvault key show --vault-name my-vault --name deployer --query key.kid --output tsv
# https://my-vault.vault.azure.net/keys/deployer/0123456789abcdef0123456789abcdef

Deleting the key loses its address for good, along with any funds it holds, once the vault's soft-delete retention period ends or the key is purged; Prevent and recover from losing a key covers recovering a key, purge protection and retiring a key.

2. Allow get and sign, and nothing else ​

The identity that runs Hardhat needs two permissions on the key: get, to read the public key, and sign. How you grant them depends on the vault's permission model.

Vaults that use Azure RBAC ​

New vaults use Azure role-based access control. No built-in Key Vault role grants only these two permissions, so create a custom role with the two data actions the plugin uses: Microsoft.KeyVault/vaults/keys/read, to read the public key, and Microsoft.KeyVault/vaults/keys/sign/action, to sign. Save the definition as key-vault-ethereum-signer.json:

json
{
  "Name": "Key Vault Ethereum Signer",
  "IsCustom": true,
  "Description": "Read a key's public part and sign digests with it.",
  "Actions": [],
  "NotActions": [],
  "DataActions": [
    "Microsoft.KeyVault/vaults/keys/read",
    "Microsoft.KeyVault/vaults/keys/sign/action"
  ],
  "NotDataActions": [],
  "AssignableScopes": ["/subscriptions/<subscription id>"]
}

Create the role once in the subscription. This needs permission to create custom roles, such as the Owner or User Access Administrator role (Create or update Azure custom roles using Azure CLI):

sh
az role definition create --role-definition key-vault-ethereum-signer.json

Assign it on the key alone, not on the vault, so the identity can use no other key. If the assignment says the role does not exist right after you created it, wait a few minutes and run it again. A new assignment can take up to 10 minutes to take effect (Troubleshoot Azure RBAC):

sh
az role assignment create \
  --role "Key Vault Ethereum Signer" \
  --assignee-object-id <principal object id> \
  --assignee-principal-type <User or ServicePrincipal> \
  --scope "$(az keyvault show --name my-vault --query id --output tsv)/keys/deployer"

<principal object id> is the Microsoft Entra object id of the identity that runs Hardhat. Its principal type is User for a person, and ServicePrincipal for a service principal or a managed identity. --assignee-object-id with --assignee-principal-type assigns the role without a Microsoft Graph lookup, which an identity without Graph read access, such as a CI service principal, cannot make. Find the object id with:

sh
# Your own account, signed in with az login:
az ad signed-in-user show --query id --output tsv
# A service principal, by its application (client) id:
az ad sp show --id <application id> --query id --output tsv
# A user-assigned managed identity:
az identity show --resource-group my-rg --name <identity name> --query principalId --output tsv

On 2026-10-11, the published 0.10.1 packages read the public key and signed a message with an isolated service principal holding only this custom role on the key. Both versions of a two-version key passed. Deleting the key made both versions unavailable; recovering it restored signing with both (test record). This test used a standard Key Vault, not Managed HSM.

If you cannot create a custom role, the built-in role with the fewest permissions that still covers both is Key Vault Crypto User (12338af0-0e69-4776-bea7-57ae8d297424). Assign it the same way, with --role "Key Vault Crypto User". It also holds seven data actions the plugin does not use: encrypt, decrypt, wrap, unwrap, verify, update and backup (Azure built-in roles):

  • update changes the key's attributes, so it can disable the key or change its permitted operations (az keyvault key set-attributes).
  • backup writes a copy of the key that can be restored into another vault in the same subscription and geography. Whoever can restore that copy can sign as the key's address, and disabling or deleting the original does not stop it (Back up a key).

The 0.10.1 acceptance run on 2026-10-10 signed and deployed on Sepolia with a fresh service principal holding only Key Vault Crypto User on the key.

Creating the key in step 1 needs a broader role, such as Key Vault Crypto Officer, which the identity that only signs should not have.

Vaults that use access policies ​

Older vaults grant access with access policies, which apply to every key in the vault. Microsoft calls them "a legacy authorization system" and recommends Azure RBAC, which is the default for vaults created with Key Vault API version 2026-02-01 or later (Azure RBAC vs. access policies). To move a vault to RBAC, see Migrate to Azure role-based access control. Until then, grant only the two key permissions to the identity's object id, found as in Vaults that use Azure RBAC:

sh
az keyvault set-policy --name my-vault --object-id <principal object id> --key-permissions get sign

az keyvault show --name my-vault --query properties.enableRbacAuthorization prints true for an RBAC vault and false (or nothing) for an access-policy vault. A vault whose enableRbacAuthorization is unset keeps using access policies.

Key Vault control-plane API versions before 2026-02-01 retire on 2027-02-27. From then on, the az keyvault commands that create and configure vaults need Azure CLI 2.90.0 or later, the first version that supports 2026-02-01 (Plan for Azure RBAC as the default). Signing is not affected: the plugin calls only the vault's data plane.

Managed HSM ​

A Managed HSM has its own local RBAC, with its own roles and data actions. Its built-in Managed HSM Crypto User role can read and sign, and also create, import, delete, back up and restore keys (Managed HSM built-in roles). Create a local custom role with only the two data actions instead. This needs a local role that can write role definitions, such as Managed HSM Administrator, Managed HSM Crypto Officer or Managed HSM Policy Administrator (Managed HSM role management):

sh
az keyvault role definition create --hsm-name my-hsm --role-definition '{
  "roleName": "Managed HSM Ethereum Signer",
  "description": "Read the public key and sign digests with it.",
  "actions": [],
  "notActions": [],
  "dataActions": [
    "Microsoft.KeyVault/managedHsm/keys/read/action",
    "Microsoft.KeyVault/managedHsm/keys/sign/action"
  ],
  "notDataActions": []
}'

Assign it on the key:

sh
az keyvault role assignment create \
  --hsm-name my-hsm \
  --role "Managed HSM Ethereum Signer" \
  --assignee-object-id <principal object id> \
  --assignee-principal-type <User or ServicePrincipal> \
  --scope /keys/deployer

On 2026-10-11, the published 0.10.1 packages read the address of an EC-HSM key on P-256K and signed a message with a fresh service principal holding only this custom role on the key. Local signature verification passed, and a key-attribute update was refused with HTTP 403 (test record).

To use the built-in role instead, pass --role "Managed HSM Crypto User". An identity with it can delete the key, or back it up and restore the copy.

3. Sign in ​

hardhat-kms tries these credential sources in order and uses the first that returns a token:

  1. A service principal from the environment: AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_CLIENT_SECRET, or AZURE_CLIENT_CERTIFICATE_PATH with AZURE_CLIENT_CERTIFICATE_PASSWORD for a protected certificate. AZURE_CLIENT_SEND_CERTIFICATE_CHAIN=true sends the certificate chain, and AZURE_ADDITIONALLY_ALLOWED_TENANTS allows a vault in a tenant other than AZURE_TENANT_ID. This is for a CI system or server that holds the service principal's secret or certificate. Prefer a federated credential, such as workload identity or azure/login with OIDC, or a certificate over a secret: Microsoft's security best practices for app registration say "use certificate credentials" when a managed identity or another external identity provider is not possible, and "Don't use password credentials, also known as secrets".
  2. Workload identity, when AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_FEDERATED_TOKEN_FILE are set. AKS sets them in a pod that uses workload identity.
  3. The Azure CLI (az login), then the Azure Developer CLI (azd auth login). This is for a laptop, and for GitHub Actions: the azure/login action signs the Azure CLI in, with OIDC federation or a secret, and sets no AZURE_* variables, so the plugin gets its token from the Azure CLI.
  4. A managed identity, user-assigned when AZURE_CLIENT_ID is set. This is for code that runs on Azure, such as a virtual machine or a container app. It gets 10 seconds for a token and 3 seconds for each request. In Azure Cloud Shell and Service Fabric, where a user-assigned identity cannot be chosen, it is left out when AZURE_CLIENT_ID is set.

This is the order proposed for Foundry's Azure Key Vault signer in foundry-rs/foundry#17120, which no Foundry release includes yet. The developer tools come before the managed identity, so a local az login works without waiting for the managed identity endpoint, which outside Azure may never answer. A source that is not configured is skipped; a source that is configured but fails, such as a service principal with a wrong secret, stops the search with its error.

Every Azure key of a run signs as the identity this chain finds; Credentials lists the variables that turn each source on, and Cloud credentials for KMS signing shows which source a laptop, a CI job and a server use.

On a laptop, az login is enough. In GitHub Actions, run azure/login with OIDC federation rather than a client secret.

A service principal in the environment takes precedence over every other source. With AZURE_TENANT_ID, AZURE_CLIENT_ID and AZURE_CLIENT_SECRET (or AZURE_CLIENT_CERTIFICATE_PATH) set, the plugin signs in as that service principal even after az login or azure/login. Unset AZURE_CLIENT_SECRET or AZURE_CLIENT_CERTIFICATE_PATH to use another source.

The plugin does not sign in with a username and password, since that sign-in cannot do multifactor authentication. With AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_USERNAME and AZURE_PASSWORD set and no secret or certificate, it fails with an error that names the variables. Sign in with a service principal, az login, workload identity or a managed identity instead.

When you sign in as a user, commands that create, change or delete Azure resources need a sign-in that completed multifactor authentication (MFA). In Azure's public cloud, this applies to every such request to Azure Resource Manager, from the Azure CLI, Azure PowerShell, the SDKs or the REST API. Enforcement began on 2025-10-01, and a tenant could postpone it to 2026-07-01 at the latest. Reads are exempt, and so are workload identities such as service principals and managed identities (Mandatory Microsoft Entra MFA). On this page, they include az keyvault create, az role definition create, az role assignment create, az keyvault set-policy, az monitor log-analytics workspace create and az monitor diagnostic-settings create. Signing and kms history are not: the plugin calls the vault and Log Analytics, not Azure Resource Manager.

4. Install the plugin and configure the key ​

sh
npm install --save-dev hardhat-kms @hardhat-kms/azure
sh
pnpm add --save-dev hardhat-kms @hardhat-kms/azure
sh
yarn add --dev hardhat-kms @hardhat-kms/azure

pnpm 11 and later need an allowBuilds entry, and Yarn 4 needs nodeLinker: node-modules: Install hardhat-kms gives the settings for each package manager.

@hardhat-kms/azure brings the Azure SDK (@azure/keyvault-keys and @azure/identity) with it, so there is nothing else to install. Add it to plugins; it loads hardhat-kms itself:

ts
import hardhatKmsAzure from "@hardhat-kms/azure";
import { configVariable, defineConfig } from "hardhat/config";

export default defineConfig({
  plugins: [hardhatKmsAzure],
  kms: {
    keys: {
      deployer: {
        provider: "azure",
        keyId: "https://my-vault.vault.azure.net/keys/deployer/0123456789abcdef0123456789abcdef",
        // Optional, recommended: run `npx hardhat kms accounts` and replace the next line with
        // the `address` line it prints for this key. The plugin then refuses to sign if the key
        // derives to another address.
        // address: "0x…",
      },
    },
  },
  networks: {
    sepolia: {
      type: "http",
      url: configVariable("SEPOLIA_RPC_URL"),
      chainId: 11155111,
      kmsAccounts: ["deployer"],
    },
  },
});

keyId can also leave out the version, or the key can be given as vaultUrl, keyName and an optional keyVersion; the configuration reference lists the forms and the accepted hosts. Prefer the versioned id: without a version, the plugin uses the version that is current when it first reads the key, so rotating the key changes the address on the next run.

To pin the key's address, run npx hardhat kms accounts. For a key without a pin it prints an address line; paste it into the key in place of the commented-out line (kms accounts).

To use a key without a config entry, set AZURE_KEY_VAULT_KEY_ID (or AZURE_KEY_VAULT_KEY_IDS for several) and pass --kms azure; see Move KMS signing from Foundry to Hardhat. Such keys are added to the network selected with --network, or to default without one.

configVariable("SEPOLIA_RPC_URL") reads the RPC URL when a network needs it: from an environment variable of that name (export SEPOLIA_RPC_URL=https://…), or from the Hardhat keystore (npx hardhat keystore set SEPOLIA_RPC_URL) when the config loads the keystore plugin. The config above does not: add import hardhatKeystore from "@nomicfoundation/hardhat-keystore"; and put hardhatKeystore in plugins, or load a Hardhat toolbox, which includes it. The script in step 5 uses it.

5. Check that the key signs ​

Save this script as scripts/check-kms.ts. It lists the accounts on sepolia, then signs the message hello with the last one, which is the KMS account:

ts
import { network } from "hardhat";

const { provider } = await network.create("sepolia");
const accounts = await provider.request({ method: "eth_accounts" });
const address: unknown = Array.isArray(accounts) ? accounts.at(-1) : undefined;
if (typeof address !== "string") {
  throw new Error("no accounts");
}
const signature = await provider.request({
  method: "personal_sign",
  params: ["0x68656c6c6f", address],
});
console.log(address, signature);

Run it with npx hardhat run scripts/check-kms.ts. Each run reads the key once, before the first signature, then signs once for the signature, or more if a request is retried (How many sign requests one call can send). An address pin does not save the read: the plugin checks the public key against the pin before it releases a signature, and it pins the key's version from the read. A pin saves the read only where the plugin needs just the address, such as listing accounts with eth_accounts; the first signature and the kms tasks still read the key (address).

How the plugin uses the key ​

  • It reads the key once and checks that it is an EC or EC-HSM key on P-256K, that it is enabled and within its activation and expiry dates, and that its permitted operations include sign.
  • It pins the version from that read. An unversioned key id is resolved to the current version once, and every signature in the run uses that version.
  • It signs the 32-byte digest with ES256K against the versioned key id. Key Vault signs the digest as given and returns 64 bytes, r || s.
  • It checks that the kid of each response names the configured vault and key, and that the kid of each sign response names the pinned version. It refuses the key or the signature otherwise.
  • It normalizes each signature to low-S, recovers the parity and verifies it against the public key before using it; see the security model.
  • It puts hardhat-kms/<version> at the start of the user agent of every request, so the ClientInfo column of the AZKVAuditLogs table starts with hardhat-kms/1.0.0 (with your installed version) when a diagnostic setting sends audit events to a workspace. The client reports this tag and anyone can send the same string, so it marks the plugin's calls but proves nothing.

Audit logs ​

Key Vault records each sign request in its audit log, whoever makes it, as a KeySign event. kms history lists them for one key:

sh
npx hardhat kms history deployer --since 7d
sh
pnpm hardhat kms history deployer --since 7d
sh
yarn hardhat kms history deployer --since 7d

Without --since, the task reads the last 24 hours, and it lists at most 100 events, the newest; --limit takes up to 1000 (kms history).

Key Vault keeps no audit log you can query by itself. A diagnostic setting on the vault sends the AuditEvent category to a Log Analytics workspace, and the task reads the AZKVAuditLogs table there. Nothing is logged before the setting exists.

Send the audit log to a workspace ​

Create a workspace, or reuse one, then add a diagnostic setting on the vault with the resource-specific destination:

sh
az monitor log-analytics workspace create \
  --resource-group my-rg \
  --workspace-name kms-audit \
  --location eastus

az monitor diagnostic-settings create \
  --name hardhat-kms-audit \
  --resource "$(az keyvault show --name my-vault --query id -o tsv)" \
  --workspace "$(az monitor log-analytics workspace show --resource-group my-rg --workspace-name kms-audit --query id -o tsv)" \
  --export-to-resource-specific true \
  --logs '[{"category":"AuditEvent","enabled":true}]'

--export-to-resource-specific true matters: without it, the events go to the older AzureDiagnostics table, which the task does not read, and the task fails with azure.history.no-table. The table appears in the workspace with the first event.

Creating the setting needs Microsoft.Insights/diagnosticSettings/write on the vault, which the Monitoring Contributor role grants, for example. Keep AZKVAuditLogs on the Analytics table plan: the task's query API cannot read a table on the Basic or Auxiliary plan.

Then give the plugin the workspace id, the GUID that az monitor log-analytics workspace show --resource-group my-rg --workspace-name kms-audit --query customerId -o tsv prints. It is not the workspace's resource id or name:

ts
import hardhatKmsAzure from "@hardhat-kms/azure";
import { configVariable, defineConfig } from "hardhat/config";

export default defineConfig({
  plugins: [hardhatKmsAzure],
  kms: {
    keys: { deployer: { provider: "azure", keyId: configVariable("DEPLOYER_KEY_ID") } },
    audit: { azure: { workspaceId: configVariable("KMS_AUDIT_WORKSPACE_ID") } },
  },
});

A literal GUID works too. Without kms.audit.azure.workspaceId, kms history fails on Azure keys with azure.history.no-workspace; signing does not need it.

The read permission ​

The identity that runs kms history uses the same credential chain as signing (step 3) and needs to query the workspace and read the AZKVAuditLogs table: Microsoft.OperationalInsights/workspaces/query/read and table read access. The Log Analytics Data Reader role on the workspace grants both:

sh
az role assignment create \
  --role "Log Analytics Data Reader" \
  --assignee-object-id <principal object id> \
  --assignee-principal-type <User or ServicePrincipal> \
  --scope "$(az monitor log-analytics workspace show --resource-group my-rg --workspace-name kms-audit --query id -o tsv)"

It lets the identity read every table of the workspace, so a workspace that holds only Key Vault audit events keeps that access narrow. A refused read fails with the permissions to grant. An identity whose access is limited to other tables of the workspace may get no rows from this one, or the azure.history.no-table error; we have not checked which. Neither is proof that the key signed nothing.

How it reads ​

The task sends one Log Analytics query to api.loganalytics.azure.com, in Azure's public cloud. It reads the KeySign rows whose key URL (Id, or RequestUri when Id is empty) has the key's vault host and key name, without regard to case, for any version of the key, in the range. It sorts them newest first and keeps one more than --limit. The query is built only from the checked vault host and key name and the two times, so no configuration value can change it. The host and name go in obfuscated string literals (h"…"), so the workspace's own query log, LAQueryLogs, does not record them. The server ends the query within 100 seconds (Prefer: wait=100). Log Analytics allows 200 queries per 30 seconds per user; a throttled query is retried twice, honouring Retry-After, before the task reports it. A firewall or proxy must let the task reach api.loganalytics.azure.com.

Vaults in Azure China or Azure Government use other Log Analytics endpoints and are not supported yet. Managed HSM keys are not supported yet either. The task fails with an error for both rather than show an empty history.

What Key Vault logs, and what it does not ​

Each row comes from one AZKVAuditLogs row:

Column or fieldAZKVAuditLogs column
timeTimeGenerated, to the 100 nanoseconds; shown to the millisecond
principalfrom the token claims in Identity: upn, else unique_name, else, for an application, appid, else the object id oid. A token is an application's when its idtyp is app, or when it has neither idtyp nor scp. For a user, appid names the client application, such as the Azure CLI, so it is never shown as the principal. For an application, or a user with no name claim, the principal is a GUID (appid or oid), shown as logged like every principal
sourceIpCallerIpAddress
userAgentClientInfo; the plugin's calls start with hardhat-kms/<version>
requestIdCorrelationId: the x-ms-request-id that Key Vault returned to the caller, not the client's x-ms-client-request-id
keyVersionthe version segment of Id
errorfor a request whose HttpStatusCode is not 2xx: ResultSignature, such as Bad Request, and ResultDescription with --show-ids. Key Vault logs a refused sign request with ResultType Success, so the status decides
keyResourceId, the versioned key URL, shown with --show-ids
extraResultType, ResultSignature, HttpStatusCode, Algorithm (ES256K), DurationMs, OperationVersion (the API version), the token's idtyp, IsRbacAuthorized, IsAccessPolicyMatch and the TLS version, when logged
extra, with --show-idsthe oid and appid claims that are not the principal, AppliedAssignmentId (the role assignment that allowed the call) and SubnetId

Key Vault never logs the digest, the message, the transaction or the signature, so the task cannot tell which signature an event made.

Delay, retention and cost ​

  • Delay. Key Vault documents that events reach the workspace at most 10 minutes after the request. Log Analytics ingestion can add to that. In our tests on 2026-10-02, KeySign rows took 2.7 minutes on average and at most about 9.2 minutes from TimeGenerated to being queryable, and did not always arrive in order. A history that ends in the last 15 minutes says that recent events may be missing.
  • Retention. The workspace keeps rows for its retention period, 30 days by default and up to 730. The task cannot know it, so a range that starts earlier just shows what is left.
  • Cost. Log Analytics bills ingestion beyond the free allowance of its pricing tier. A KeySign row is about 1.9 KB, so a thousand signatures add about 2 MB. The setting also sends the vault's other events, such as KeyGet and Authentication. Remove the diagnostic setting to stop.

A key deleted and created again ​

Key Vault names a key by its vault and name. If a key is deleted (and purged) and a new key is created under the same name, the new key's history and the old one's are one: the task matches by vault host and key name, so it lists the old key's KeySign rows too. Their key version tells them apart, since a new key starts with new versions: check the version of each row when that matters.

Several settings and workspaces ​

A vault can have up to five diagnostic settings, each sending to its own workspace, storage account or event hub. The task reads only the workspace in kms.audit.azure.workspaceId, so it never claims to see every sign request on the key. An empty history gets the logging-not-confirmed note with what to check.

Errors ​

Each message starts with the provider, the operation and the key, for example azure, sign, key azure:https://my-vault.vault.azure.net/keys/deployer: Key Vault answered 403 Forbidden: …. The table lists the part after the colon.

ErrorCause and fix
Azure Key Vault keys need the @hardhat-kms/azure pluginRun npm install --save-dev @hardhat-kms/azure in the Hardhat project, and add hardhatKmsAzure to plugins in the config.
@hardhat-kms/azure … needs hardhat-kms …, but hardhat-kms … is installedThe two packages are released together and must be the same version. Run the install command the error prints.
no Azure credential returned a token (AggregateAuthenticationError)No credential source in step 3 is configured. Run az login, or set the variables of a service principal or workload identity.
AZURE_TENANT_ID is not a tenant idThe variable holds a character other than a letter, a digit, - or .. Set it to the tenant id that az account show --query tenantId -o tsv prints, or unset it.
… which selects username and password sign-inThe environment selects username and password sign-in, which the plugin refuses. Unset AZURE_USERNAME and AZURE_PASSWORD, or set AZURE_CLIENT_SECRET or AZURE_CLIENT_CERTIFICATE_PATH.
a configured Azure credential could not sign in (AuthenticationError)A source in step 3 is configured but its sign-in failed, for example a wrong or expired AZURE_CLIENT_SECRET, or a wrong AZURE_TENANT_ID. Fix its settings, or unset the variables of a source you do not mean to use; the chain does not fall through to az login after this.
Key Vault answered 401 …: the credential was not acceptedThe token is for another tenant, or has expired. Run az login again, or check AZURE_TENANT_ID.
Key Vault answered 403 …: the identity may not use this keyThe identity lacks get or sign on the key (step 2), the role assignment has not taken effect yet (it can take a few minutes), the key is disabled, or the vault firewall blocks the network.
Key Vault answered 404 …: the key or key version does not existThe vault has no key with this name or version. Check keyId; a deleted key must be recovered first.
Key Vault could not be reachedThe request did not get an answer: check the vault URL, DNS, the network and any proxy.
the key type is …, not EC or EC-HSM or the key curve is …, not P-256KThe key is not a secp256k1 key. A key's type and curve cannot be changed, so create a new key as in step 1.
the key version is disabledEnable it with az keyvault key set-attributes --vault-name my-vault --name deployer --version <version> --enabled true.
the key version is not valid before … or the key version expired at …The key's activation or expiry date excludes now. The plugin checks the dates when it reads the key and again before each signature, so a key that expires during a run fails here too. Change it with az keyvault key set-attributes --vault-name my-vault --name deployer --version <version> and --not-before or --expires, or use another version.
the key's permitted operations do not include signThe key was created without sign in --ops. Add it with az keyvault key set-attributes --vault-name my-vault --name deployer --version <version> --ops sign verify.
the key derives to 0x…, but the configured address is 0x…The key id names another key or version than the one the pin was taken from, or the pin is wrong. Nothing was signed. Find out why before you change the pin; see When the pin fails.
no answer within … msKey Vault did not answer in time. Check the network, or raise timeoutMs.
kms.audit.azure.workspaceId is not setkms history needs the workspace that the vault's diagnostic setting sends to. See Audit logs.
the Log Analytics workspace has no AZKVAuditLogs tableNo diagnostic setting sends Key Vault audit events to this workspace in resource-specific mode. Add one with --export-to-resource-specific true, or check the workspace id.
cannot read the audit log: the credentials lack Microsoft.Operational…The identity may not query the workspace. Assign Log Analytics Data Reader on it, as in The read permission.

Errors show the HTTP status and Key Vault's error code, never the service's message, which names the vault, the key and the caller. Run with DEBUG=hardhat:kms:* to see each call; see Debug output. The table lists the most common errors; the errors reference lists every one, with its id, cause and fix, and the core plugin's errors too.

MIT licensed. A community plugin, not affiliated with or endorsed by Nomic Foundation, Amazon Web Services, Google or Microsoft.