@workit-poa/hedera-kms-wallet
v0.2.0
Published
AWS KMS-backed Hedera wallet provisioning and signing utilities.
Maintainers
Readme
@workit-poa/hedera-kms-wallet
AWS KMS-backed Hedera wallet utilities for secure key management, account provisioning, transaction signing, and key rotation.
This package is designed for backend services that want to:
- keep private keys non-exportable inside AWS KMS,
- use secp256k1 signatures for Hedera transactions,
- enforce explicit key ownership and least-privilege IAM boundaries,
- support audited provisioning and controlled key rotation.
Table of Contents
- What This Package Solves
- Features
- Requirements
- Requirement Coverage Matrix
- Installation
- Architecture and Trust Boundaries
- Configuration
- Quick Start
- Usage Guide
- IAM and Key Policy Guidance
- Audit and Compliance Logging
- Run the Demo
- Testing
- API Reference
- Publishing
- Troubleshooting
- References
- License
What This Package Solves
Hedera transactions need signatures from account keys. This library lets you:
- generate and store secp256k1 keys in AWS KMS (
ECC_SECG_P256K1,SIGN_VERIFY), - derive Hedera-compatible public keys from KMS public key material,
- sign Hedera transaction bytes without exporting private keys,
- provision new Hedera accounts tied to KMS keys,
- rotate account keys with explicit dual-signature account updates.
Features
- KMS key management for user wallets:
- create user-tagged keys with aliases,
- validate key type and enabled state,
- assert key ownership via required tags (
app,userId).
- Hedera signing and transaction helpers:
- convert KMS DER ECDSA signatures into Hedera raw 64-byte
(r||s)signatures, - submit topic message and tinybar transfer transactions.
- convert KMS DER ECDSA signatures into Hedera raw 64-byte
- Account lifecycle:
- provision Hedera account from existing or newly-created KMS key,
- rotate account key by co-signing with current and replacement keys.
- Security and operations:
- enforced least-privilege key policy bindings for key creation,
- structured audit hook for provisioning/key/signing events.
Requirements
- Node.js
>=18 - AWS account with KMS permissions for your runtime/admin roles
- Hedera operator account (
OPERATOR_ID+OPERATOR_KEYorHEDERA_OPERATOR_*) - Package manager:
pnpm(repo standard),npm, oryarn
Requirement Coverage Matrix
This section maps project requirements to implementation and documentation artifacts.
1) Secure key management solution with compliance and auditability
- Key lifecycle is implemented in
kmsKeyManagerandwalletProvisioning. - Least-privilege key policy generation is enforced using
policyBindings. - Structured audit events are emitted via
auditLoggerfor key and signing operations. - CloudTrail verification guidance is documented in Audit and Compliance Logging.
Code references:
2) Use AWS KMS for secure key generation, storage, and rotation
- Key generation:
createUserKmsKey()createsECC_SECG_P256K1+SIGN_VERIFYkeys. - Key storage/security: private keys are KMS-managed and never exported.
- Key rotation:
rotateHederaAccountKmsKey()performs managed replacement + Hedera account key update. - Automatic in-place rotation limits for asymmetric keys are documented with mitigation workflow.
Code references:
3) Submit a transaction on Hedera
- Transaction helpers:
submitTopicMessageWithKmsSignature()submitTinybarTransferWithKmsSignature()
- End-to-end demo transaction flow is implemented in:
examples/hedera-kms-wallet-demo/src/kms-hedera-demo.ts
Code references:
4) Implement proper access controls and audit logging
- Access control:
buildLeastPrivilegeKeyPolicy()kmsAccessPolicyGuidance()- ownership enforcement with
assertKmsKeyOwnershipForUser()
- Audit:
- structured
auditLoggerhook across key/sign/provision/rotation operations - CloudTrail event and field guidance documented
- structured
Code references:
5) Demonstrate secure transaction signing without exposing private keys
createKmsHederaSigner()uses KMSSignAPI with digest mode.- Private keys remain in KMS; only signature/public key material is handled by application code.
- DER ECDSA signatures are canonicalized into Hedera raw 64-byte format.
Code references:
6) Working prototype + documentation for architecture, security controls, and Hedera integration
- Working prototype:
- standalone demo app in
examples/hedera-kms-wallet-demo - integrated app prototype in
apps/webauth flow (NextAuth+ OTP/OAuth + managed wallet provisioning) - runnable command for standalone flow:
pnpm demo:kms-hedera
- standalone demo app in
- Documentation:
- architecture and trust boundaries
- IAM and key policy guidance
- audit/compliance logging
- integration and usage examples for Hedera transactions
Code and doc references:
examples/hedera-kms-wallet-demo/src/kms-hedera-demo.tsapps/web/lib/next-auth-options.tslibs/auth/src/auth.service.tslibs/auth/src/wallet-provisioning.tsdocs/authentication.md- Architecture and Trust Boundaries
- IAM and Key Policy Guidance
- Audit and Compliance Logging
- Run the Demo
Screenshot placeholder: Compliance checklist screenshot showing each requirement mapped to a README section and source file.
Installation
From npm
pnpm add @workit-poa/hedera-kms-walletor:
npm install @workit-poa/hedera-kms-walletIn This Monorepo
Install workspace dependencies from repository root:
pnpm installArchitecture and Trust Boundaries
Components
- Your backend calls this package for provisioning/signing operations.
- AWS KMS stores and signs with non-exportable private keys.
- Hedera SDK freezes, signs, submits, and resolves transaction receipts.
- Hedera testnet/mainnet receives signed transactions.
Identity mapping
userId -> kmsKeyId/kmsKeyArn -> hederaAccountId (+ public key fingerprint)
Trust boundaries
- Private key material never leaves KMS.
- Runtime role is scoped to signing + metadata reads.
- Key creation is guarded by explicit policy bindings.
- Existing/replacement keys are tag-validated before use.
Screenshot placeholder: Architecture diagram showing backend -> AWS KMS -> Hedera network, with private key boundary around KMS.
Configuration
Copy .env.example and fill values for your environment.
Core environment variables
| Variable | Required | Description |
| --- | --- | --- |
| AWS_REGION | Yes | AWS region for KMS client |
| OPERATOR_ID or HEDERA_OPERATOR_ID | Yes | Hedera operator account ID used to submit transactions |
| OPERATOR_KEY or HEDERA_OPERATOR_KEY | Yes | Hedera operator private key |
| HEDERA_NETWORK | No | testnet (default) or mainnet |
| OPERATOR_KEY_TYPE | No | Force parsing mode: ecdsa, secp256k1, ed25519, or der |
| HEDERA_KMS_ALIAS_PREFIX | No | Default alias prefix when creating keys (default: alias/workit-user) |
| HEDERA_KMS_KEY_DESCRIPTION_PREFIX | No | Default key description prefix |
Variables required for secure key creation
These are required when your flow allows creating new KMS keys:
| Variable | Required when creating keys | Description |
| --- | --- | --- |
| AWS_ACCOUNT_ID | Yes | 12-digit AWS account ID |
| KMS_KEY_ADMIN_PRINCIPAL_ARN | Yes | IAM principal ARN for key administration |
| KMS_RUNTIME_SIGNER_PRINCIPAL_ARN | Yes | IAM principal ARN for runtime signing role |
Quick Start
import { KMSClient } from "@aws-sdk/client-kms";
import {
createHederaClient,
createKmsHederaSigner,
submitTopicMessageWithKmsSignature
} from "@workit-poa/hedera-kms-wallet";
const kms = new KMSClient({ region: process.env.AWS_REGION });
const signer = await createKmsHederaSigner({
kms,
keyId: process.env.KMS_KEY_ID!
});
const client = createHederaClient({
network: (process.env.HEDERA_NETWORK as "testnet" | "mainnet") || "testnet",
operatorId: process.env.OPERATOR_ID!,
operatorKey: process.env.OPERATOR_KEY!
});
const result = await submitTopicMessageWithKmsSignature({
client,
signer,
payerAccountId: "0.0.12345",
message: "hello from KMS signer"
});
console.log(result.transactionId, result.receiptStatus, result.mirrorLink);
kms.destroy();
client.close();Usage Guide
1) Create a KMS-backed signer
import { KMSClient } from "@aws-sdk/client-kms";
import { createKmsHederaSigner } from "@workit-poa/hedera-kms-wallet";
const kms = new KMSClient({ region: "us-east-1" });
const signer = await createKmsHederaSigner({
kms,
keyId: "your-kms-key-id-or-arn",
auditLogger: event => console.log(event)
});2) Provision a Hedera account for a user
Runtime-safe mode (recommended): use pre-created key.
import { provisionHederaAccountForUser } from "@workit-poa/hedera-kms-wallet";
const provisioned = await provisionHederaAccountForUser({
userId: "user-123",
existingKeyId: "kms-key-id",
awsRegion: process.env.AWS_REGION,
operatorId: process.env.OPERATOR_ID,
operatorKey: process.env.OPERATOR_KEY
});Admin mode: allow secure key creation.
const provisioned = await provisionHederaAccountForUser({
userId: "user-123",
allowKeyCreation: true,
initialHbar: 1,
policyBindings: {
accountId: process.env.AWS_ACCOUNT_ID!,
keyAdminPrincipalArn: process.env.KMS_KEY_ADMIN_PRINCIPAL_ARN!,
runtimeSignerPrincipalArn: process.env.KMS_RUNTIME_SIGNER_PRINCIPAL_ARN!
}
});3) Submit a tinybar transfer
import { submitTinybarTransferWithKmsSignature } from "@workit-poa/hedera-kms-wallet";
const transfer = await submitTinybarTransferWithKmsSignature({
client,
signer,
fromAccountId: "0.0.123",
toAccountId: "0.0.456",
payerAccountId: "0.0.123",
amountTinybar: 10
});payerAccountId lets you charge network fees to the KMS-managed wallet account instead of the operator account.
4) Rotate Hedera account key
AWS asymmetric secp256k1 keys do not support in-place automatic rotation. Use managed replacement:
import { rotateHederaAccountKmsKey } from "@workit-poa/hedera-kms-wallet";
const rotated = await rotateHederaAccountKmsKey({
userId: "user-123",
accountId: "0.0.12345",
currentKeyId: "current-kms-key-id",
policyBindings: {
accountId: process.env.AWS_ACCOUNT_ID!,
keyAdminPrincipalArn: process.env.KMS_KEY_ADMIN_PRINCIPAL_ARN!,
runtimeSignerPrincipalArn: process.env.KMS_RUNTIME_SIGNER_PRINCIPAL_ARN!
}
});Rotation flow:
- Create or reuse replacement key.
- Verify ownership tags for current + replacement keys.
- Build
AccountUpdateTransactionwith replacement public key. - Co-sign with current and replacement keys.
- Submit transaction and persist new key details.
Screenshot placeholder: Console output showing successful key rotation with previous/new key fingerprints and transaction ID.
IAM and Key Policy Guidance
Use separate IAM responsibilities:
- Key admin role: create/manage key lifecycle and aliases.
- Runtime signer role: sign and read key metadata/public key.
Helpers:
buildLeastPrivilegeKeyPolicy(bindings)kmsAccessPolicyGuidance(keyArn?, aliasArn?)
Enforced safeguards:
keyPolicyoverrides are intentionally rejected.allowUnsafeDefaultKeyPolicybypass is intentionally rejected.policyBindingsis required whenever a new key is created.
Recommended runtime permissions:
kms:Signkms:GetPublicKeykms:DescribeKeykms:ListResourceTags
Scope runtime policies to explicit key ARNs whenever possible.
Audit and Compliance Logging
Pass auditLogger to emit structured events for:
CreateKeyCreateAliasEnableKeyRotationDescribeKeyGetPublicKeyListResourceTagsSignProvisionAccountRotateAccountKey
CloudTrail should also record KMS API calls. Useful fields for audit evidence:
eventTimeeventNameuserIdentityrequestParameters.keyIdsourceIPAddressawsRegion
Screenshot placeholder: CloudTrail event history filtered to
SignandCreateKeyfor the relevant KMS key ARN.
Run the Demo
Demo source:
1) Configure demo env
cp examples/hedera-kms-wallet-demo/.env.example examples/hedera-kms-wallet-demo/.envThe demo loads:
examples/hedera-kms-wallet-demo/.env- repo root
.envas fallback
2) Choose demo mode
DEMO_MODE=topic(default): creates topic + submits messageDEMO_MODE=transfer: submits tinybar transfer
Validation behavior:
DEMO_TRANSFER_TINYBARmust be a positive safe integer.- If provisioning a new account,
HEDERA_NEW_ACCOUNT_INITIAL_HBARmust be> 0. - If creating a new KMS key, key policy bindings env vars are mandatory.
3) Run
From repo root:
pnpm demo:kms-hederaor:
pnpm --filter @workit-poa/hedera-kms-wallet-demo demo:kms-hederaExpected output includes:
- key/account details,
- transaction ID + receipt status,
- Hashscan transaction link.
Note: the demo now sets payerAccountId to the managed wallet account, so transaction fees are charged to the KMS-backed wallet account (not the operator).
Screenshot placeholder: Terminal output from successful
DEMO_MODE=topicrun including topic ID and Hashscan URL. Screenshot placeholder: Hashscan transaction page confirming SUCCESS status.
Testing
Run package tests:
pnpm --filter @workit-poa/hedera-kms-wallet testRun coverage:
pnpm --filter @workit-poa/hedera-kms-wallet test:coverageWhat is covered:
- key creation/validation/tag ownership checks,
- DER-to-raw signature conversion and low-S normalization,
- signer behavior and audit logging,
- Hedera client and transaction helper behavior,
- provisioning and rotation happy paths + validation failures.
Test env loading:
libs/hedera-kms-wallet/.env.testlibs/hedera-kms-wallet/.env.test.local(local override)
Current tests are mock-driven and do not require live AWS/Hedera credentials.
API Reference
All exports are re-exported from src/index.ts.
Key management (kmsKeyManager)
createUserKmsKey(params)validateKmsSecp256k1SigningKey(kms, keyId, auditLogger?)getPublicKeyBytes(kms, keyId, auditLogger?)assertKmsKeyOwnershipForUser({ kms, keyId, userId, expectedAppTag?, auditLogger? })buildLeastPrivilegeKeyPolicy(bindings)kmsAccessPolicyGuidance(keyArn?, aliasArn?)
Signer (kmsSigner)
createKmsHederaSigner({ kms, keyId, auditLogger? })- Returns
KmsHederaSigner:keyId,keyArnhederaPublicKeycompressedPublicKey,uncompressedPublicKeysign(message)
Hedera helpers (hederaClient)
createHederaClient({ network?, operatorId, operatorKey })createHederaClientFromEnv()addKmsSignatureToFrozenTransaction(transaction, signer)executeSignedTransaction(client, transaction)submitTopicMessageWithKmsSignature({ client, signer, message, topicMemo?, payerAccountId?, network? })submitTinybarTransferWithKmsSignature({ client, signer, fromAccountId, toAccountId, amountTinybar, payerAccountId?, network? })mirrorLinkForTransaction(network, transactionId)getWalletDetails(accountId, network?)
Wallet lifecycle (walletProvisioning)
provisionHederaAccountForUser(params)rotateHederaAccountKmsKey(params)
Publishing
Pack/publish checks:
pnpm --filter @workit-poa/hedera-kms-wallet prepack
pnpm --filter @workit-poa/hedera-kms-wallet packprepack runs:
cleanlinttestbuild
Published files are restricted to:
distREADME.md.env.exampleLICENSE
Troubleshooting
Missing AWS_REGION- Set
AWS_REGION(or passawsRegionexplicitly where supported).
- Set
Missing operator credentials- Set
OPERATOR_ID+OPERATOR_KEY(orHEDERA_OPERATOR_*alternatives).
- Set
existingKeyId is required unless allowKeyCreation=true- Runtime path expects pre-provisioned keys.
policyBindings is required when creating a new key- Provide
AWS_ACCOUNT_ID, admin signer principal ARNs, and passpolicyBindings.
- Provide
KMS key ... must use KeySpec ECC_SECG_P256K1- Use secp256k1 signing keys only.
Signer must return a 64-byte (r||s) secp256k1 signature- Ensure signatures come from this package’s signer or equivalent canonical formatter.
References
- Hedera account model: https://docs.hedera.com/hedera/core-concepts/accounts/account-properties
- Hedera SDK and KMS signing guidance: https://docs.hedera.com/hedera/sdks-and-apis/sdks/client#how-to-sign-a-transaction-with-aws-kms
- HIP-222 (ECDSA secp256k1 transaction signatures): https://hips.hedera.com/hip/hip-222
- AWS KMS
GetPublicKey: https://docs.aws.amazon.com/kms/latest/APIReference/API_GetPublicKey.html - AWS KMS + CloudTrail: https://docs.aws.amazon.com/kms/latest/developerguide/logging-using-cloudtrail.html
License
MIT
