A TypeScript library for signing Solana transactions using AWS KMS with ED25519 keys.
- 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)
- 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)
Using pnpm:
pnpm add solana-kms-signerUsing npm:
npm install solana-kms-signerUsing yarn:
yarn add solana-kms-signerYou can create an ED25519 key using the AWS CLI or the AWS Console.
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).
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-1Your 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": "*"
}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.comimport { 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);The main class for signing Solana transactions with AWS KMS.
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);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());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); // 32Signs 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); // 64Signs 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);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);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);The library exports the following error classes:
Thrown when AWS KMS API calls fail.
class KmsClientError extends Error {
cause?: unknown;
}Thrown when DER-encoded public key extraction fails.
class PublicKeyExtractionError extends Error {
cause?: unknown;
}Thrown when signature verification fails after signing.
class SignatureVerificationError extends Error {
cause?: unknown;
}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) |
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.)
This library includes several example scripts demonstrating different use cases:
Sign an arbitrary message:
pnpm example:sign-messageScript: examples/sign-message.ts
Sign a Solana legacy transaction:
pnpm example:sign-transactionScript: examples/sign-transaction.ts
Sign a Solana versioned transaction:
pnpm example:sign-versioned-transactionScript: examples/sign-versioned-transaction.ts
Sign multiple transactions in parallel:
pnpm example:multiple-signaturesScript: examples/multiple-signatures.ts
Clone the repository and install dependencies:
git clone https://github.com/yourusername/solana-kms-signer.git
cd solana-kms-signer
pnpm installBuild the TypeScript code:
pnpm buildRun 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)
Run TypeScript type checking:
pnpm type-checkProblem: 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_VERIFYProblem: 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_VERIFYProblem: 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"
}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-idProblem: 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.
- 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).
- 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.
- 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.
- 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
This project is licensed under the MIT License. See the LICENSE file for details.
- Inspired by EVM KMS Signer
- Built with AWS SDK for JavaScript v3
- Uses Solana Web3.js
- Signature verification powered by TweetNaCl
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.
- 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
# 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# Run all checks
pnpm lint # Lint check
pnpm test:run # Run tests
pnpm type-check # TypeScript check
pnpm build # Build the projectWe 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
- 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
Thank you to all our contributors!
If you encounter any issues or have questions:
- Check the Troubleshooting section
- Search existing issues
- Open a new issue