solana-kms-signer
v1.0.6
Published
AWS KMS-based Solana signer with ED25519 support
Maintainers
Readme
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 16.0.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-signerUsing npm:
npm install solana-kms-signerUsing yarn:
yarn add solana-kms-signerAWS 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 ECC_NIST_EDWARDS25519 \
--key-usage SIGN_VERIFY \
--description "ED25519 key for Solana transaction signing" \
--region us-east-1Important: Use the exact KeySpec value ECC_NIST_EDWARDS25519 (not ECC_ED25519 or ED25519).
Using the Example Script
Please create a KMS key with the following specifications:
- KeySpec: ECC_NIST_EDWARDS25519
- KeyUsage: SIGN_VERIFY
- Description: ED25519 key for Solana transaction signing
- Region: us-east-1
Example:
aws kms create-key \
--key-spec ECC_NIST_EDWARDS25519 \
--key-usage SIGN_VERIFY \
--description "ED25519 key for Solana transaction signing" \
--region us-east-1Required 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
AWS_KMS_KEY_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)
# AWS_ACCESS_KEY_ID=your-access-key
# AWS_SECRET_ACCESS_KEY=your-secret-key
# AWS_SESSION_TOKEN=your-session-token
# Solana Configuration
SOLANA_RPC_URL=https://api.devnet.solana.comBasic 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
KmsConfig Type:
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
Example:
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
Example:
const rawPublicKey = await signer.getRawPublicKey();
console.log('Raw public key length:', rawPublicKey.length); // 32signMessage(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
Returns: Promise<Uint8Array> - ED25519 signature (64 bytes)
Throws:
KmsClientError- If KMS API call failsSignatureVerificationError- If signature verification fails
Example:
const message = new TextEncoder().encode('Hello, Solana!');
const signature = await signer.signMessage(message);
console.log('Signature length:', signature.length); // 64signTransaction(transaction: Transaction): Promise<Transaction>
Signs a Solana legacy Transaction. The transaction must have recentBlockhash and feePayer set before signing.
Parameters:
transaction: Transaction to sign
Returns: Promise<Transaction> - Signed transaction
Throws:
KmsClientError- If KMS API call failsSignatureVerificationError- If signature verification fails
Example:
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
Returns: Promise<VersionedTransaction> - Signed versioned transaction
Throws:
KmsClientError- If KMS API call failsSignatureVerificationError- If signature verification fails
Example:
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
Returns: Promise<Transaction[]> - Array of signed transactions in the same order
Throws:
KmsClientError- If any KMS API call failsSignatureVerificationError- If any signature verification fails
Example:
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) |
| AWS_KMS_KEY_ID | Yes | KMS key ID or ARN |
| AWS_ACCESS_KEY_ID | No | AWS access key (uses default credential chain if not provided) |
| AWS_SECRET_ACCESS_KEY | No | AWS secret key (uses default credential chain if not provided) |
| AWS_SESSION_TOKEN | No | AWS session token (for temporary credentials) |
| SOLANA_RPC_URL | 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 (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) - 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-messageScript: examples/sign-message.ts
2. Sign Transaction
Sign a Solana legacy transaction:
pnpm example:sign-transactionScript: examples/sign-transaction.ts
3. Sign Versioned Transaction
Sign a Solana versioned transaction:
pnpm example:sign-versioned-transactionScript: examples/sign-versioned-transaction.ts
4. Multiple Signatures
Sign multiple transactions in parallel:
pnpm example:multiple-signaturesScript: 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 installBuilding
Build the TypeScript code:
pnpm buildTesting
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:coverageCurrent test coverage: 97.4% (46 tests passing)
Type Checking
Run TypeScript type checking:
pnpm type-checkTroubleshooting
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 ECC_NIST_EDWARDS25519 \
--key-usage SIGN_VERIFYError: "UnsupportedOperationException"
Problem: The KMS key was created with the wrong KeySpec.
Solution: Ensure you're using ECC_NIST_EDWARDS25519 (not ECC_ED25519 or ED25519):
aws kms create-key \
--key-spec ECC_NIST_EDWARDS25519 \
--key-usage SIGN_VERIFYError: "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 ECC_NIST_EDWARDS25519:
aws kms describe-key --key-id your-key-idError: "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!)
If this error occurs consistently, please open an issue.
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.
AWS KMS Pricing:
- 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 lintBefore Submitting a PR
# Run all checks
pnpm lint # Lint check
pnpm test:run # Run tests
pnpm type-check # TypeScript check
pnpm build # Build the projectCommit Messages
We use Conventional Commits:
feat: New featurefix: Bug fixdocs: Documentation changestest: Adding or updating testsrefactor: Code refactoringchore: Maintenance tasks
Example: 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
