AWSKMS-based Solana signer (ED25519) with enterprise-grade security. Sign transactions and messages using keys stored in AWS KMS without exposing private keys.
Solana KMS Signer
A TypeScript library for signing Solana transactions using AWS KMS with ED25519 keys.
Features
- AWS KMS Integration: Leverage AWS KMS's ED25519 key management for secure Solana transaction signing
- Type Safety: Full TypeScript support with strict type checking
- Solana Compatibility: Support for both legacy
TransactionandVersionedTransaction - Public Key Caching: Automatic caching to minimize KMS API calls
- Signature Verification: Built-in signature verification using tweetnacl
- Multiple Signatures: Batch signing support for multiple transactions
- Error Handling: Comprehensive error types with detailed messages
- Zero Dependencies: Minimal external dependencies (only AWS SDK, Solana Web3.js, and tweetnacl)
Prerequisites
- Node.js: Version 22.13.0 or higher
- AWS Account: With appropriate IAM permissions
- AWS KMS: ED25519 key created in KMS (see setup instructions below)
Installation
Using pnpm:
pnpm add solana-kms-signer
Using npm:
npm install solana-kms-signer
Using yarn:
yarn add solana-kms-signer
AWS KMS Setup
Creating an ED25519 Key
You can create an ED25519 key using the AWS CLI or the AWS Console.
Using AWS CLI
aws kms create-key \
--key-spec ECCNISTEDWARDS25519 \
--key-usage SIGN_VERIFY \
--description "ED25519 key for Solana transaction signing" \
--region us-east-1
Important: Use the exact KeySpec value ECCNISTEDWARDS25519 (not ECC_ED25519 or ED25519).
Using the Example Script
Please create a KMS key with the following specifications:
- KeySpec: ECCNISTEDWARDS25519
- KeyUsage: SIGN_VERIFY
- Description: ED25519 key for Solana transaction signing
- Region: us-east-1
aws kms create-key \ --key-spec ECCNISTEDWARDS25519 \ --key-usage SIGN_VERIFY \ --description "ED25519 key for Solana transaction signing" \ --region us-east-1
Required IAM Permissions
Your AWS credentials must have the following KMS permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"kms:GetPublicKey",
"kms:Sign"
],
"Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY_ID"
}
]
}
For creating keys, you also need:
{ "Effect": "Allow", "Action": [ "kms:CreateKey", "kms:TagResource" ], "Resource": "*" }
Quick Start
Environment Setup
Create a .env file:
# AWS KMS Configuration
AWS_REGION=us-east-1
AWSKMSKEY_ID=arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
AWS Credentials (optional - will use default credential chain if not provided)
AWSACCESSKEY_ID=your-access-key
AWSSECRETACCESS_KEY=your-secret-key
AWSSESSIONTOKEN=your-session-token
Solana Configuration
SOLANARPCURL=https://api.devnet.solana.com
Basic Usage
import { SolanaKmsSigner } from 'solana-kms-signer';
import { Connection, SystemProgram, Transaction } from '@solana/web3.js';
// Initialize the signer const signer = new SolanaKmsSigner({ region: 'us-east-1', keyId: 'your-kms-key-id' });
// Get the public key const publicKey = await signer.getPublicKey(); console.log('Solana Address:', publicKey.toBase58());
// Sign a message const message = Buffer.from('Hello, Solana!'); const signature = await signer.signMessage(message);
// Sign a transaction const connection = new Connection('https://api.devnet.solana.com'); const recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
const transaction = new Transaction({ recentBlockhash, feePayer: publicKey }).add( SystemProgram.transfer({ fromPubkey: publicKey, toPubkey: recipientPubkey, lamports: 1000000 // 0.001 SOL }) );
const signedTransaction = await signer.signTransaction(transaction); const txid = await connection.sendRawTransaction(signedTransaction.serialize()); console.log('Transaction ID:', txid);
API Reference
SolanaKmsSigner
The main class for signing Solana transactions with AWS KMS.
Constructor
new SolanaKmsSigner(config: KmsConfig | KmsClient)
Creates a new signer instance.
Parameters:
config: Either aKmsConfigobject or an existingKmsClientinstance
interface KmsConfig { region: string; keyId: string; credentials?: { accessKeyId: string; secretAccessKey: string; sessionToken?: string; }; }
Example:
// With KmsConfig const signer = new SolanaKmsSigner({ region: 'us-east-1', keyId: 'your-key-id' });
// With existing KmsClient import { KmsClient } from 'solana-kms-signer'; const client = new KmsClient(config); const signer = new SolanaKmsSigner(client);
Methods
getPublicKey(): Promise<PublicKey>
Retrieves the Solana PublicKey associated with the KMS key. The public key is cached after first retrieval.
Returns: Promise<PublicKey> - Solana PublicKey object
Throws:
KmsClientError- If KMS API call failsPublicKeyExtractionError- If DER decoding fails
const publicKey = await signer.getPublicKey(); console.log('Address:', publicKey.toBase58());
getRawPublicKey(): Promise<Uint8Array>
Retrieves the raw 32-byte ED25519 public key. The public key is cached after first retrieval.
Returns: Promise<Uint8Array> - Raw 32-byte public key
Throws:
KmsClientError- If KMS API call failsPublicKeyExtractionError- If DER decoding fails
const rawPublicKey = await signer.getRawPublicKey(); console.log('Raw public key length:', rawPublicKey.length); // 32
signMessage(message: Uint8Array): Promise<Uint8Array>
Signs an arbitrary message using the KMS key. The signature is verified using tweetnacl before being returned.
Parameters:
message: Message to sign as Uint8Array
Promise<Uint8Array> - ED25519 signature (64 bytes)
Throws:
KmsClientError- If KMS API call failsSignatureVerificationError- If signature verification fails
const message = new TextEncoder().encode('Hello, Solana!'); const signature = await signer.signMessage(message); console.log('Signature length:', signature.length); // 64
signTransaction(transaction: Transaction): Promise<Transaction>
Signs a Solana legacy Transaction. The transaction must have recentBlockhash and feePayer set before signing.
Parameters:
transaction: Transaction to sign
Promise<Transaction> - Signed transaction
Throws:
KmsClientError- If KMS API call failsSignatureVerificationError- If signature verification fails
const transaction = new Transaction().add(instruction); transaction.recentBlockhash = recentBlockhash; transaction.feePayer = await signer.getPublicKey(); const signedTx = await signer.signTransaction(transaction);
signVersionedTransaction(transaction: VersionedTransaction): Promise<VersionedTransaction>
Signs a Solana VersionedTransaction. The transaction must have a valid message with recentBlockhash set.
Parameters:
transaction: VersionedTransaction to sign
Promise<VersionedTransaction> - Signed versioned transaction
Throws:
KmsClientError- If KMS API call failsSignatureVerificationError- If signature verification fails
import { MessageV0, VersionedTransaction } from '@solana/web3.js';
const message = MessageV0.compile({ payerKey: await signer.getPublicKey(), instructions: [instruction], recentBlockhash: recentBlockhash }); const transaction = new VersionedTransaction(message); const signedTx = await signer.signVersionedTransaction(transaction);
signAllTransactions(transactions: Transaction[]): Promise<Transaction[]>
Signs multiple Solana transactions in parallel. All transactions must have recentBlockhash and feePayer set before signing.
Parameters:
transactions: Array of transactions to sign
Promise<Transaction[]> - Array of signed transactions in the same order
Throws:
KmsClientError- If any KMS API call failsSignatureVerificationError- If any signature verification fails
const transactions = [tx1, tx2, tx3]; const signedTxs = await signer.signAllTransactions(transactions);
Error Classes
The library exports the following error classes:
KmsClientError
Thrown when AWS KMS API calls fail.
class KmsClientError extends Error {
cause?: unknown;
}
PublicKeyExtractionError
Thrown when DER-encoded public key extraction fails.
class PublicKeyExtractionError extends Error {
cause?: unknown;
}
SignatureVerificationError
Thrown when signature verification fails after signing.
class SignatureVerificationError extends Error {
cause?: unknown;
}
Configuration
Environment Variables
The library supports the following environment variables:
| Variable | Required | Description | |----------|----------|-------------| | AWS_REGION | Yes | AWS region where the KMS key is located (e.g., us-east-1) | | AWSKMSKEY_ID | Yes | KMS key ID or ARN | | AWSACCESSKEY_ID | No | AWS access key (uses default credential chain if not provided) | | AWSSECRETACCESS_KEY | No | AWS secret key (uses default credential chain if not provided) | | AWSSESSIONTOKEN | No | AWS session token (for temporary credentials) | | SOLANARPCURL | No | Solana RPC endpoint (for examples) |
AWS Credential Chain
If you don't provide explicit credentials, the AWS SDK will use the default credential chain:
- Environment variables (
AWSACCESSKEYID,AWSSECRETACCESSKEY) - AWS credentials file (
~/.aws/credentials) - IAM role (when running on EC2, ECS, Lambda, etc.)
Examples
This library includes several example scripts demonstrating different use cases:
1. Sign Message
Sign an arbitrary message:
pnpm example:sign-message
Script: examples/sign-message.ts
2. Sign Transaction
Sign a Solana legacy transaction:
pnpm example:sign-transaction
Script: examples/sign-transaction.ts
3. Sign Versioned Transaction
Sign a Solana versioned transaction:
pnpm example:sign-versioned-transaction
Script: examples/sign-versioned-transaction.ts
4. Multiple Signatures
Sign multiple transactions in parallel:
pnpm example:multiple-signatures
Script: examples/multiple-signatures.ts
Development
Setup
Clone the repository and install dependencies:
git clone https://github.com/yourusername/solana-kms-signer.git
cd solana-kms-signer
pnpm install
Building
Build the TypeScript code:
pnpm build
Testing
Run tests:
# Run tests in watch mode
pnpm test
Run tests once
pnpm test:run
Run tests with UI
pnpm test:ui
Run tests with coverage
pnpm test:coverage
Current test coverage: 97.4% (46 tests passing)
Type Checking
Run TypeScript type checking:
pnpm type-check
Troubleshooting
Error: "InvalidKeyUsage"
Problem: The KMS key was not created with SIGN_VERIFY usage.
Solution: Create a new key with the correct KeyUsage:
aws kms create-key \ --key-spec ECCNISTEDWARDS25519 \ --key-usage SIGN_VERIFY
Error: "UnsupportedOperationException"
Problem: The KMS key was created with the wrong KeySpec.
Solution: Ensure you're using ECCNISTEDWARDS25519 (not ECC_ED25519 or ED25519):
aws kms create-key \ --key-spec ECCNISTEDWARDS25519 \ --key-usage SIGN_VERIFY
Error: "AccessDeniedException"
Problem: Your AWS credentials don't have sufficient KMS permissions.
Solution: Add the required IAM permissions:
{ "Effect": "Allow", "Action": [ "kms:GetPublicKey", "kms:Sign" ], "Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY_ID" }
Error: "Public key extraction failed"
Problem: The DER-encoded public key from KMS has an unexpected format.
Solution: This usually indicates the key was created with the wrong KeySpec. Verify your key was created with ECCNISTEDWARDS25519:
aws kms describe-key --key-id your-key-id
Error: "Signature verification failed"
Problem: The signature from KMS doesn't match the public key and message.
Solution: This is a critical error that should not occur in normal operation. Possible causes:
- Network corruption (rare)
- KMS service issue (very rare)
- Bug in the library (please report!)
Security Best Practices
Key Management
- Never export private keys: AWS KMS keys cannot be exported by design. This is a security feature, not a limitation.
- Use key policies: Restrict access to your KMS keys using IAM policies and key policies.
- Enable CloudTrail: Log all KMS API calls for audit purposes.
- Consider key rotation: While KMS doesn't support automatic rotation for asymmetric keys, you can implement manual rotation.
- Use separate keys: Use different KMS keys for different environments (dev, staging, production).
Credential Management
- Use IAM roles: When running on AWS infrastructure (EC2, ECS, Lambda), use IAM roles instead of access keys.
- Never commit credentials: Never commit AWS credentials to version control. Use
.envfiles and add them to.gitignore. - Use temporary credentials: When possible, use temporary credentials (STS) instead of long-lived access keys.
- Rotate credentials: Regularly rotate your AWS access keys.
Application Security
- Validate inputs: Always validate transaction inputs before signing.
- Verify balances: Check account balances before signing transfer transactions.
- Use recent blockhashes: Always use a recent blockhash to prevent replay attacks.
- Test on devnet first: Test your integration on Solana devnet before using on mainnet.
- Implement rate limiting: Rate limit signing operations to prevent abuse.
Cost Management
- Cache public keys: The library automatically caches public keys to minimize KMS API calls.
- Batch operations: Use
signAllTransactions()to sign multiple transactions efficiently. - Monitor usage: Monitor your KMS usage through AWS Cost Explorer.
- Key storage: ~$1/month per key
- API calls: $0.03 per 10,000 requests
- GetPublicKey: Free
License
This project is licensed under the MIT License. See the LICENSE file for details.
Acknowledgments
- Inspired by EVM KMS Signer
- Built with AWS SDK for JavaScript v3
- Uses Solana Web3.js
- Signature verification powered by TweetNaCl
Contributing
We welcome contributions from the community! Whether you're fixing bugs, improving documentation, or adding new features, your help makes this project better for everyone.
Quick Start
- Fork the repository and clone your fork
- Install dependencies:
pnpm install - Make your changes and add tests
- Run tests:
pnpm test:run - Submit a pull request
Development Setup
# Clone your fork
git clone https://github.com/YOUR_USERNAME/solana-kms-signer.git
cd solana-kms-signer
Install dependencies
pnpm install
Set up environment (for examples)
cp .env.example .env
Edit .env with your AWS credentials
Run tests
pnpm test:run
Run linter
pnpm lint
Before Submitting a PR
# Run all checks
pnpm lint # Lint check
pnpm test:run # Run tests
pnpm type-check # TypeScript check
pnpm build # Build the project
Commit Messages
We use Conventional Commits:
feat: New featurefix: Bug fixdocs: Documentation changestest: Adding or updating testsrefactor: Code refactoringchore: Maintenance tasks
feat: add support for batch transaction signing
Need Help?
- Read the full Contributing Guide for detailed guidelines
- Check out our Code of Conduct
- Look for issues labeled
good first issueorhelp wanted - Join the discussions
Current Contributors
Thank you to all our contributors!
Support
If you encounter any issues or have questions:
- Check the Troubleshooting section
- Search existing issues
- Open a new issue