solana-kms-signer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +610 -0
- package/dist/errors/index.d.ts +54 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +63 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/kms/client.d.ts +60 -0
- package/dist/kms/client.d.ts.map +1 -0
- package/dist/kms/client.js +108 -0
- package/dist/kms/client.js.map +1 -0
- package/dist/kms/signer.d.ts +170 -0
- package/dist/kms/signer.d.ts.map +1 -0
- package/dist/kms/signer.js +230 -0
- package/dist/kms/signer.js.map +1 -0
- package/dist/types/index.d.ts +39 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/publicKey.d.ts +28 -0
- package/dist/utils/publicKey.d.ts.map +1 -0
- package/dist/utils/publicKey.js +56 -0
- package/dist/utils/publicKey.js.map +1 -0
- package/package.json +73 -0
- package/src/errors/index.test.ts +173 -0
- package/src/errors/index.ts +61 -0
- package/src/index.ts +27 -0
- package/src/kms/client.test.ts +285 -0
- package/src/kms/client.ts +132 -0
- package/src/kms/signer.test.ts +446 -0
- package/src/kms/signer.ts +274 -0
- package/src/types/index.ts +44 -0
- package/src/utils/publicKey.test.ts +135 -0
- package/src/utils/publicKey.ts +70 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Taegeon Alan Go
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
# Solana KMS Signer
|
|
2
|
+
|
|
3
|
+
A TypeScript library for signing Solana transactions using AWS KMS with ED25519 keys.
|
|
4
|
+
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **AWS KMS Integration**: Leverage AWS KMS's ED25519 key management for secure Solana transaction signing
|
|
12
|
+
- **Type Safety**: Full TypeScript support with strict type checking
|
|
13
|
+
- **Solana Compatibility**: Support for both legacy `Transaction` and `VersionedTransaction`
|
|
14
|
+
- **Public Key Caching**: Automatic caching to minimize KMS API calls
|
|
15
|
+
- **Signature Verification**: Built-in signature verification using tweetnacl
|
|
16
|
+
- **Multiple Signatures**: Batch signing support for multiple transactions
|
|
17
|
+
- **Error Handling**: Comprehensive error types with detailed messages
|
|
18
|
+
- **Zero Dependencies**: Minimal external dependencies (only AWS SDK, Solana Web3.js, and tweetnacl)
|
|
19
|
+
|
|
20
|
+
## Prerequisites
|
|
21
|
+
|
|
22
|
+
- **Node.js**: Version 16.0.0 or higher
|
|
23
|
+
- **AWS Account**: With appropriate IAM permissions
|
|
24
|
+
- **AWS KMS**: ED25519 key created in KMS (see setup instructions below)
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
Using pnpm:
|
|
29
|
+
```bash
|
|
30
|
+
pnpm add solana-kms-signer
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Using npm:
|
|
34
|
+
```bash
|
|
35
|
+
npm install solana-kms-signer
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Using yarn:
|
|
39
|
+
```bash
|
|
40
|
+
yarn add solana-kms-signer
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## AWS KMS Setup
|
|
44
|
+
|
|
45
|
+
### Creating an ED25519 Key
|
|
46
|
+
|
|
47
|
+
You can create an ED25519 key using the AWS CLI or the AWS Console.
|
|
48
|
+
|
|
49
|
+
#### Using AWS CLI
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
aws kms create-key \
|
|
53
|
+
--key-spec ECC_NIST_EDWARDS25519 \
|
|
54
|
+
--key-usage SIGN_VERIFY \
|
|
55
|
+
--description "ED25519 key for Solana transaction signing" \
|
|
56
|
+
--region us-east-1
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Important**: Use the exact KeySpec value `ECC_NIST_EDWARDS25519` (not `ECC_ED25519` or `ED25519`).
|
|
60
|
+
|
|
61
|
+
#### Using the Example Script
|
|
62
|
+
|
|
63
|
+
This library includes a helper script to create KMS keys:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# Set up environment
|
|
67
|
+
cp .env.example .env
|
|
68
|
+
# Edit .env and add your AWS_REGION
|
|
69
|
+
|
|
70
|
+
# Run the create key script
|
|
71
|
+
pnpm example:create-kms-key
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Required IAM Permissions
|
|
75
|
+
|
|
76
|
+
Your AWS credentials must have the following KMS permissions:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"Version": "2012-10-17",
|
|
81
|
+
"Statement": [
|
|
82
|
+
{
|
|
83
|
+
"Effect": "Allow",
|
|
84
|
+
"Action": [
|
|
85
|
+
"kms:GetPublicKey",
|
|
86
|
+
"kms:Sign"
|
|
87
|
+
],
|
|
88
|
+
"Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY_ID"
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For creating keys, you also need:
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"Effect": "Allow",
|
|
98
|
+
"Action": [
|
|
99
|
+
"kms:CreateKey",
|
|
100
|
+
"kms:TagResource"
|
|
101
|
+
],
|
|
102
|
+
"Resource": "*"
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Quick Start
|
|
107
|
+
|
|
108
|
+
### Environment Setup
|
|
109
|
+
|
|
110
|
+
Create a `.env` file:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# AWS KMS Configuration
|
|
114
|
+
AWS_REGION=us-east-1
|
|
115
|
+
AWS_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
|
|
116
|
+
|
|
117
|
+
# AWS Credentials (optional - will use default credential chain if not provided)
|
|
118
|
+
# AWS_ACCESS_KEY_ID=your-access-key
|
|
119
|
+
# AWS_SECRET_ACCESS_KEY=your-secret-key
|
|
120
|
+
# AWS_SESSION_TOKEN=your-session-token
|
|
121
|
+
|
|
122
|
+
# Solana Configuration
|
|
123
|
+
SOLANA_RPC_URL=https://api.devnet.solana.com
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Basic Usage
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { SolanaKmsSigner } from 'solana-kms-signer';
|
|
130
|
+
import { Connection, SystemProgram, Transaction } from '@solana/web3.js';
|
|
131
|
+
|
|
132
|
+
// Initialize the signer
|
|
133
|
+
const signer = new SolanaKmsSigner({
|
|
134
|
+
region: 'us-east-1',
|
|
135
|
+
keyId: 'your-kms-key-id'
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Get the public key
|
|
139
|
+
const publicKey = await signer.getPublicKey();
|
|
140
|
+
console.log('Solana Address:', publicKey.toBase58());
|
|
141
|
+
|
|
142
|
+
// Sign a message
|
|
143
|
+
const message = Buffer.from('Hello, Solana!');
|
|
144
|
+
const signature = await signer.signMessage(message);
|
|
145
|
+
|
|
146
|
+
// Sign a transaction
|
|
147
|
+
const connection = new Connection('https://api.devnet.solana.com');
|
|
148
|
+
const recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
149
|
+
|
|
150
|
+
const transaction = new Transaction({
|
|
151
|
+
recentBlockhash,
|
|
152
|
+
feePayer: publicKey
|
|
153
|
+
}).add(
|
|
154
|
+
SystemProgram.transfer({
|
|
155
|
+
fromPubkey: publicKey,
|
|
156
|
+
toPubkey: recipientPubkey,
|
|
157
|
+
lamports: 1000000 // 0.001 SOL
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const signedTransaction = await signer.signTransaction(transaction);
|
|
162
|
+
const txid = await connection.sendRawTransaction(signedTransaction.serialize());
|
|
163
|
+
console.log('Transaction ID:', txid);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## API Reference
|
|
167
|
+
|
|
168
|
+
### `SolanaKmsSigner`
|
|
169
|
+
|
|
170
|
+
The main class for signing Solana transactions with AWS KMS.
|
|
171
|
+
|
|
172
|
+
#### Constructor
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
new SolanaKmsSigner(config: KmsConfig | KmsClient)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Creates a new signer instance.
|
|
179
|
+
|
|
180
|
+
**Parameters:**
|
|
181
|
+
- `config`: Either a `KmsConfig` object or an existing `KmsClient` instance
|
|
182
|
+
|
|
183
|
+
**KmsConfig Type:**
|
|
184
|
+
```typescript
|
|
185
|
+
interface KmsConfig {
|
|
186
|
+
region: string;
|
|
187
|
+
keyId: string;
|
|
188
|
+
credentials?: {
|
|
189
|
+
accessKeyId: string;
|
|
190
|
+
secretAccessKey: string;
|
|
191
|
+
sessionToken?: string;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**Example:**
|
|
197
|
+
```typescript
|
|
198
|
+
// With KmsConfig
|
|
199
|
+
const signer = new SolanaKmsSigner({
|
|
200
|
+
region: 'us-east-1',
|
|
201
|
+
keyId: 'your-key-id'
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// With existing KmsClient
|
|
205
|
+
import { KmsClient } from 'solana-kms-signer';
|
|
206
|
+
const client = new KmsClient(config);
|
|
207
|
+
const signer = new SolanaKmsSigner(client);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
#### Methods
|
|
211
|
+
|
|
212
|
+
##### `getPublicKey(): Promise<PublicKey>`
|
|
213
|
+
|
|
214
|
+
Retrieves the Solana PublicKey associated with the KMS key. The public key is cached after first retrieval.
|
|
215
|
+
|
|
216
|
+
**Returns:** `Promise<PublicKey>` - Solana PublicKey object
|
|
217
|
+
|
|
218
|
+
**Throws:**
|
|
219
|
+
- `KmsClientError` - If KMS API call fails
|
|
220
|
+
- `PublicKeyExtractionError` - If DER decoding fails
|
|
221
|
+
|
|
222
|
+
**Example:**
|
|
223
|
+
```typescript
|
|
224
|
+
const publicKey = await signer.getPublicKey();
|
|
225
|
+
console.log('Address:', publicKey.toBase58());
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
##### `getRawPublicKey(): Promise<Uint8Array>`
|
|
229
|
+
|
|
230
|
+
Retrieves the raw 32-byte ED25519 public key. The public key is cached after first retrieval.
|
|
231
|
+
|
|
232
|
+
**Returns:** `Promise<Uint8Array>` - Raw 32-byte public key
|
|
233
|
+
|
|
234
|
+
**Throws:**
|
|
235
|
+
- `KmsClientError` - If KMS API call fails
|
|
236
|
+
- `PublicKeyExtractionError` - If DER decoding fails
|
|
237
|
+
|
|
238
|
+
**Example:**
|
|
239
|
+
```typescript
|
|
240
|
+
const rawPublicKey = await signer.getRawPublicKey();
|
|
241
|
+
console.log('Raw public key length:', rawPublicKey.length); // 32
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
##### `signMessage(message: Uint8Array): Promise<Uint8Array>`
|
|
245
|
+
|
|
246
|
+
Signs an arbitrary message using the KMS key. The signature is verified using tweetnacl before being returned.
|
|
247
|
+
|
|
248
|
+
**Parameters:**
|
|
249
|
+
- `message`: Message to sign as Uint8Array
|
|
250
|
+
|
|
251
|
+
**Returns:** `Promise<Uint8Array>` - ED25519 signature (64 bytes)
|
|
252
|
+
|
|
253
|
+
**Throws:**
|
|
254
|
+
- `KmsClientError` - If KMS API call fails
|
|
255
|
+
- `SignatureVerificationError` - If signature verification fails
|
|
256
|
+
|
|
257
|
+
**Example:**
|
|
258
|
+
```typescript
|
|
259
|
+
const message = new TextEncoder().encode('Hello, Solana!');
|
|
260
|
+
const signature = await signer.signMessage(message);
|
|
261
|
+
console.log('Signature length:', signature.length); // 64
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
##### `signTransaction(transaction: Transaction): Promise<Transaction>`
|
|
265
|
+
|
|
266
|
+
Signs a Solana legacy Transaction. The transaction must have `recentBlockhash` and `feePayer` set before signing.
|
|
267
|
+
|
|
268
|
+
**Parameters:**
|
|
269
|
+
- `transaction`: Transaction to sign
|
|
270
|
+
|
|
271
|
+
**Returns:** `Promise<Transaction>` - Signed transaction
|
|
272
|
+
|
|
273
|
+
**Throws:**
|
|
274
|
+
- `KmsClientError` - If KMS API call fails
|
|
275
|
+
- `SignatureVerificationError` - If signature verification fails
|
|
276
|
+
|
|
277
|
+
**Example:**
|
|
278
|
+
```typescript
|
|
279
|
+
const transaction = new Transaction().add(instruction);
|
|
280
|
+
transaction.recentBlockhash = recentBlockhash;
|
|
281
|
+
transaction.feePayer = await signer.getPublicKey();
|
|
282
|
+
const signedTx = await signer.signTransaction(transaction);
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
##### `signVersionedTransaction(transaction: VersionedTransaction): Promise<VersionedTransaction>`
|
|
286
|
+
|
|
287
|
+
Signs a Solana VersionedTransaction. The transaction must have a valid message with `recentBlockhash` set.
|
|
288
|
+
|
|
289
|
+
**Parameters:**
|
|
290
|
+
- `transaction`: VersionedTransaction to sign
|
|
291
|
+
|
|
292
|
+
**Returns:** `Promise<VersionedTransaction>` - Signed versioned transaction
|
|
293
|
+
|
|
294
|
+
**Throws:**
|
|
295
|
+
- `KmsClientError` - If KMS API call fails
|
|
296
|
+
- `SignatureVerificationError` - If signature verification fails
|
|
297
|
+
|
|
298
|
+
**Example:**
|
|
299
|
+
```typescript
|
|
300
|
+
import { MessageV0, VersionedTransaction } from '@solana/web3.js';
|
|
301
|
+
|
|
302
|
+
const message = MessageV0.compile({
|
|
303
|
+
payerKey: await signer.getPublicKey(),
|
|
304
|
+
instructions: [instruction],
|
|
305
|
+
recentBlockhash: recentBlockhash
|
|
306
|
+
});
|
|
307
|
+
const transaction = new VersionedTransaction(message);
|
|
308
|
+
const signedTx = await signer.signVersionedTransaction(transaction);
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
##### `signAllTransactions(transactions: Transaction[]): Promise<Transaction[]>`
|
|
312
|
+
|
|
313
|
+
Signs multiple Solana transactions in parallel. All transactions must have `recentBlockhash` and `feePayer` set before signing.
|
|
314
|
+
|
|
315
|
+
**Parameters:**
|
|
316
|
+
- `transactions`: Array of transactions to sign
|
|
317
|
+
|
|
318
|
+
**Returns:** `Promise<Transaction[]>` - Array of signed transactions in the same order
|
|
319
|
+
|
|
320
|
+
**Throws:**
|
|
321
|
+
- `KmsClientError` - If any KMS API call fails
|
|
322
|
+
- `SignatureVerificationError` - If any signature verification fails
|
|
323
|
+
|
|
324
|
+
**Example:**
|
|
325
|
+
```typescript
|
|
326
|
+
const transactions = [tx1, tx2, tx3];
|
|
327
|
+
const signedTxs = await signer.signAllTransactions(transactions);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Error Classes
|
|
331
|
+
|
|
332
|
+
The library exports the following error classes:
|
|
333
|
+
|
|
334
|
+
#### `KmsClientError`
|
|
335
|
+
|
|
336
|
+
Thrown when AWS KMS API calls fail.
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
class KmsClientError extends Error {
|
|
340
|
+
cause?: unknown;
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### `PublicKeyExtractionError`
|
|
345
|
+
|
|
346
|
+
Thrown when DER-encoded public key extraction fails.
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
class PublicKeyExtractionError extends Error {
|
|
350
|
+
cause?: unknown;
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### `SignatureVerificationError`
|
|
355
|
+
|
|
356
|
+
Thrown when signature verification fails after signing.
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
class SignatureVerificationError extends Error {
|
|
360
|
+
cause?: unknown;
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Configuration
|
|
365
|
+
|
|
366
|
+
### Environment Variables
|
|
367
|
+
|
|
368
|
+
The library supports the following environment variables:
|
|
369
|
+
|
|
370
|
+
| Variable | Required | Description |
|
|
371
|
+
|----------|----------|-------------|
|
|
372
|
+
| `AWS_REGION` | Yes | AWS region where the KMS key is located (e.g., `us-east-1`) |
|
|
373
|
+
| `AWS_KMS_KEY_ID` | Yes | KMS key ID or ARN |
|
|
374
|
+
| `AWS_ACCESS_KEY_ID` | No | AWS access key (uses default credential chain if not provided) |
|
|
375
|
+
| `AWS_SECRET_ACCESS_KEY` | No | AWS secret key (uses default credential chain if not provided) |
|
|
376
|
+
| `AWS_SESSION_TOKEN` | No | AWS session token (for temporary credentials) |
|
|
377
|
+
| `SOLANA_RPC_URL` | No | Solana RPC endpoint (for examples) |
|
|
378
|
+
|
|
379
|
+
### AWS Credential Chain
|
|
380
|
+
|
|
381
|
+
If you don't provide explicit credentials, the AWS SDK will use the default credential chain:
|
|
382
|
+
|
|
383
|
+
1. Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)
|
|
384
|
+
2. AWS credentials file (`~/.aws/credentials`)
|
|
385
|
+
3. IAM role (when running on EC2, ECS, Lambda, etc.)
|
|
386
|
+
|
|
387
|
+
## Examples
|
|
388
|
+
|
|
389
|
+
This library includes several example scripts demonstrating different use cases:
|
|
390
|
+
|
|
391
|
+
### 1. Create KMS Key
|
|
392
|
+
|
|
393
|
+
Create a new ED25519 key in AWS KMS:
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
pnpm example:create-kms-key
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
**Script:** [`examples/create-kms-key.ts`](examples/create-kms-key.ts)
|
|
400
|
+
|
|
401
|
+
### 2. Sign Message
|
|
402
|
+
|
|
403
|
+
Sign an arbitrary message:
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
pnpm example:sign-message
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**Script:** [`examples/sign-message.ts`](examples/sign-message.ts)
|
|
410
|
+
|
|
411
|
+
### 3. Sign Transaction
|
|
412
|
+
|
|
413
|
+
Sign a Solana legacy transaction:
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
pnpm example:sign-transaction
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Script:** [`examples/sign-transaction.ts`](examples/sign-transaction.ts)
|
|
420
|
+
|
|
421
|
+
### 4. Sign Versioned Transaction
|
|
422
|
+
|
|
423
|
+
Sign a Solana versioned transaction:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
pnpm example:sign-versioned-transaction
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Script:** [`examples/sign-versioned-transaction.ts`](examples/sign-versioned-transaction.ts)
|
|
430
|
+
|
|
431
|
+
### 5. Multiple Signatures
|
|
432
|
+
|
|
433
|
+
Sign multiple transactions in parallel:
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
pnpm example:multiple-signatures
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Script:** [`examples/multiple-signatures.ts`](examples/multiple-signatures.ts)
|
|
440
|
+
|
|
441
|
+
## Development
|
|
442
|
+
|
|
443
|
+
### Setup
|
|
444
|
+
|
|
445
|
+
Clone the repository and install dependencies:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
git clone https://github.com/yourusername/solana-kms-signer.git
|
|
449
|
+
cd solana-kms-signer
|
|
450
|
+
pnpm install
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Building
|
|
454
|
+
|
|
455
|
+
Build the TypeScript code:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
pnpm build
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Testing
|
|
462
|
+
|
|
463
|
+
Run tests:
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
# Run tests in watch mode
|
|
467
|
+
pnpm test
|
|
468
|
+
|
|
469
|
+
# Run tests once
|
|
470
|
+
pnpm test:run
|
|
471
|
+
|
|
472
|
+
# Run tests with UI
|
|
473
|
+
pnpm test:ui
|
|
474
|
+
|
|
475
|
+
# Run tests with coverage
|
|
476
|
+
pnpm test:coverage
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
Current test coverage: **97.4%** (46 tests passing)
|
|
480
|
+
|
|
481
|
+
### Type Checking
|
|
482
|
+
|
|
483
|
+
Run TypeScript type checking:
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
pnpm type-check
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
## Troubleshooting
|
|
490
|
+
|
|
491
|
+
### Error: "InvalidKeyUsage"
|
|
492
|
+
|
|
493
|
+
**Problem:** The KMS key was not created with `SIGN_VERIFY` usage.
|
|
494
|
+
|
|
495
|
+
**Solution:** Create a new key with the correct KeyUsage:
|
|
496
|
+
```bash
|
|
497
|
+
aws kms create-key \
|
|
498
|
+
--key-spec ECC_NIST_EDWARDS25519 \
|
|
499
|
+
--key-usage SIGN_VERIFY
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Error: "UnsupportedOperationException"
|
|
503
|
+
|
|
504
|
+
**Problem:** The KMS key was created with the wrong KeySpec.
|
|
505
|
+
|
|
506
|
+
**Solution:** Ensure you're using `ECC_NIST_EDWARDS25519` (not `ECC_ED25519` or `ED25519`):
|
|
507
|
+
```bash
|
|
508
|
+
aws kms create-key \
|
|
509
|
+
--key-spec ECC_NIST_EDWARDS25519 \
|
|
510
|
+
--key-usage SIGN_VERIFY
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Error: "AccessDeniedException"
|
|
514
|
+
|
|
515
|
+
**Problem:** Your AWS credentials don't have sufficient KMS permissions.
|
|
516
|
+
|
|
517
|
+
**Solution:** Add the required IAM permissions:
|
|
518
|
+
```json
|
|
519
|
+
{
|
|
520
|
+
"Effect": "Allow",
|
|
521
|
+
"Action": [
|
|
522
|
+
"kms:GetPublicKey",
|
|
523
|
+
"kms:Sign"
|
|
524
|
+
],
|
|
525
|
+
"Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY_ID"
|
|
526
|
+
}
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
### Error: "Public key extraction failed"
|
|
530
|
+
|
|
531
|
+
**Problem:** The DER-encoded public key from KMS has an unexpected format.
|
|
532
|
+
|
|
533
|
+
**Solution:** This usually indicates the key was created with the wrong KeySpec. Verify your key was created with `ECC_NIST_EDWARDS25519`:
|
|
534
|
+
```bash
|
|
535
|
+
aws kms describe-key --key-id your-key-id
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Error: "Signature verification failed"
|
|
539
|
+
|
|
540
|
+
**Problem:** The signature from KMS doesn't match the public key and message.
|
|
541
|
+
|
|
542
|
+
**Solution:** This is a critical error that should not occur in normal operation. Possible causes:
|
|
543
|
+
- Network corruption (rare)
|
|
544
|
+
- KMS service issue (very rare)
|
|
545
|
+
- Bug in the library (please report!)
|
|
546
|
+
|
|
547
|
+
If this error occurs consistently, please [open an issue](https://github.com/yourusername/solana-kms-signer/issues).
|
|
548
|
+
|
|
549
|
+
## Security Best Practices
|
|
550
|
+
|
|
551
|
+
### Key Management
|
|
552
|
+
|
|
553
|
+
1. **Never export private keys**: AWS KMS keys cannot be exported by design. This is a security feature, not a limitation.
|
|
554
|
+
2. **Use key policies**: Restrict access to your KMS keys using IAM policies and key policies.
|
|
555
|
+
3. **Enable CloudTrail**: Log all KMS API calls for audit purposes.
|
|
556
|
+
4. **Consider key rotation**: While KMS doesn't support automatic rotation for asymmetric keys, you can implement manual rotation.
|
|
557
|
+
5. **Use separate keys**: Use different KMS keys for different environments (dev, staging, production).
|
|
558
|
+
|
|
559
|
+
### Credential Management
|
|
560
|
+
|
|
561
|
+
1. **Use IAM roles**: When running on AWS infrastructure (EC2, ECS, Lambda), use IAM roles instead of access keys.
|
|
562
|
+
2. **Never commit credentials**: Never commit AWS credentials to version control. Use `.env` files and add them to `.gitignore`.
|
|
563
|
+
3. **Use temporary credentials**: When possible, use temporary credentials (STS) instead of long-lived access keys.
|
|
564
|
+
4. **Rotate credentials**: Regularly rotate your AWS access keys.
|
|
565
|
+
|
|
566
|
+
### Application Security
|
|
567
|
+
|
|
568
|
+
1. **Validate inputs**: Always validate transaction inputs before signing.
|
|
569
|
+
2. **Verify balances**: Check account balances before signing transfer transactions.
|
|
570
|
+
3. **Use recent blockhashes**: Always use a recent blockhash to prevent replay attacks.
|
|
571
|
+
4. **Test on devnet first**: Test your integration on Solana devnet before using on mainnet.
|
|
572
|
+
5. **Implement rate limiting**: Rate limit signing operations to prevent abuse.
|
|
573
|
+
|
|
574
|
+
### Cost Management
|
|
575
|
+
|
|
576
|
+
1. **Cache public keys**: The library automatically caches public keys to minimize KMS API calls.
|
|
577
|
+
2. **Batch operations**: Use `signAllTransactions()` to sign multiple transactions efficiently.
|
|
578
|
+
3. **Monitor usage**: Monitor your KMS usage through AWS Cost Explorer.
|
|
579
|
+
|
|
580
|
+
**AWS KMS Pricing:**
|
|
581
|
+
- Key storage: ~$1/month per key
|
|
582
|
+
- API calls: $0.03 per 10,000 requests
|
|
583
|
+
- GetPublicKey: Free
|
|
584
|
+
|
|
585
|
+
## License
|
|
586
|
+
|
|
587
|
+
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
588
|
+
|
|
589
|
+
## Acknowledgments
|
|
590
|
+
|
|
591
|
+
- Inspired by [EVM KMS Signer](https://github.com/gtg7784/evm-kms-signer)
|
|
592
|
+
- Built with [AWS SDK for JavaScript v3](https://github.com/aws/aws-sdk-js-v3)
|
|
593
|
+
- Uses [Solana Web3.js](https://github.com/solana-labs/solana-web3.js)
|
|
594
|
+
- Signature verification powered by [TweetNaCl](https://github.com/dchest/tweetnacl-js)
|
|
595
|
+
|
|
596
|
+
## Contributing
|
|
597
|
+
|
|
598
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
599
|
+
|
|
600
|
+
## Support
|
|
601
|
+
|
|
602
|
+
If you encounter any issues or have questions:
|
|
603
|
+
|
|
604
|
+
1. Check the [Troubleshooting](#troubleshooting) section
|
|
605
|
+
2. Search [existing issues](https://github.com/yourusername/solana-kms-signer/issues)
|
|
606
|
+
3. [Open a new issue](https://github.com/yourusername/solana-kms-signer/issues/new)
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
Made with ❤️ by [Taegeon Alan Go](https://github.com/gtg7784)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class for AWS KMS API-related errors.
|
|
3
|
+
*
|
|
4
|
+
* Thrown when AWS KMS operations fail, including:
|
|
5
|
+
* - GetPublicKey failures (AccessDeniedException, KeyNotFoundException, etc.)
|
|
6
|
+
* - Sign failures (ThrottlingException, KeyUnavailableException, etc.)
|
|
7
|
+
* - Network timeouts and service unavailability
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* throw new KmsClientError('Failed to get public key from KMS', originalError);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare class KmsClientError extends Error {
|
|
15
|
+
readonly cause?: unknown | undefined;
|
|
16
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Custom error class for DER-encoded public key extraction errors.
|
|
20
|
+
*
|
|
21
|
+
* Thrown when extracting ED25519 public key from DER format fails, including:
|
|
22
|
+
* - Invalid DER structure (missing SEQUENCE tag)
|
|
23
|
+
* - Missing BIT STRING in DER encoding
|
|
24
|
+
* - Unexpected BIT STRING length (not 33 bytes)
|
|
25
|
+
* - Malformed SubjectPublicKeyInfo structure
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* throw new PublicKeyExtractionError('Invalid DER encoding: missing SEQUENCE tag');
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare class PublicKeyExtractionError extends Error {
|
|
33
|
+
readonly cause?: unknown | undefined;
|
|
34
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Custom error class for ED25519 signature verification errors.
|
|
38
|
+
*
|
|
39
|
+
* Thrown when signature validation fails, including:
|
|
40
|
+
* - Signature verification returns false (invalid signature)
|
|
41
|
+
* - Incorrect signature length (not 64 bytes)
|
|
42
|
+
* - Signature does not match public key and message
|
|
43
|
+
* - Cryptographic verification failure
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* throw new SignatureVerificationError('Signature verification failed', { signature, publicKey });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare class SignatureVerificationError extends Error {
|
|
51
|
+
readonly cause?: unknown | undefined;
|
|
52
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,qBAAa,cAAe,SAAQ,KAAK;aACM,KAAK,CAAC,EAAE,OAAO;gBAAhD,OAAO,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,OAAO,YAAA;CAI7D;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;aACJ,KAAK,CAAC,EAAE,OAAO;gBAAhD,OAAO,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,OAAO,YAAA;CAI7D;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;aACN,KAAK,CAAC,EAAE,OAAO;gBAAhD,OAAO,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,OAAO,YAAA;CAI7D"}
|