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.
@@ -0,0 +1,230 @@
1
+ import { PublicKey, } from '@solana/web3.js';
2
+ import nacl from 'tweetnacl';
3
+ import { KmsClient } from './client.js';
4
+ import { extractEd25519PublicKey } from '../utils/publicKey.js';
5
+ import { SignatureVerificationError } from '../errors/index.js';
6
+ /**
7
+ * Solana transaction signer using AWS KMS ED25519 keys.
8
+ *
9
+ * Provides methods to sign Solana transactions and arbitrary messages
10
+ * using AWS KMS-managed ED25519 keys. Caches the public key after
11
+ * first retrieval to minimize KMS API calls.
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * // Create with KmsConfig
16
+ * const signer = new SolanaKmsSigner({
17
+ * region: 'us-east-1',
18
+ * keyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
19
+ * });
20
+ *
21
+ * // Or create with existing KmsClient
22
+ * const client = new KmsClient(config);
23
+ * const signer = new SolanaKmsSigner(client);
24
+ *
25
+ * // Get public key
26
+ * const publicKey = await signer.getPublicKey();
27
+ *
28
+ * // Sign message
29
+ * const message = new TextEncoder().encode('Hello, Solana!');
30
+ * const signature = await signer.signMessage(message);
31
+ *
32
+ * // Sign transaction
33
+ * const transaction = new Transaction().add(instruction);
34
+ * transaction.recentBlockhash = recentBlockhash;
35
+ * transaction.feePayer = publicKey;
36
+ * const signedTx = await signer.signTransaction(transaction);
37
+ * ```
38
+ */
39
+ export class SolanaKmsSigner {
40
+ /**
41
+ * Creates a new SolanaKmsSigner instance.
42
+ *
43
+ * @param config - Either KmsConfig or an existing KmsClient instance
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * // With KmsConfig
48
+ * const signer = new SolanaKmsSigner({
49
+ * region: 'us-east-1',
50
+ * keyId: 'key-id'
51
+ * });
52
+ *
53
+ * // With KmsClient
54
+ * const client = new KmsClient(config);
55
+ * const signer = new SolanaKmsSigner(client);
56
+ * ```
57
+ */
58
+ constructor(config) {
59
+ if (config instanceof KmsClient) {
60
+ this.kmsClient = config;
61
+ }
62
+ else {
63
+ this.kmsClient = new KmsClient(config);
64
+ }
65
+ }
66
+ /**
67
+ * Retrieves the Solana PublicKey associated with the KMS key.
68
+ *
69
+ * The public key is cached after first retrieval to minimize KMS API calls.
70
+ *
71
+ * @returns Solana PublicKey object
72
+ * @throws {KmsClientError} If KMS API call fails
73
+ * @throws {PublicKeyExtractionError} If DER decoding fails
74
+ *
75
+ * @example
76
+ * ```typescript
77
+ * const publicKey = await signer.getPublicKey();
78
+ * console.log('Address:', publicKey.toBase58());
79
+ * ```
80
+ */
81
+ async getPublicKey() {
82
+ // Return cached public key if available
83
+ if (this.publicKey) {
84
+ return this.publicKey;
85
+ }
86
+ // Get DER-encoded public key from KMS
87
+ const derPublicKey = await this.kmsClient.getPublicKey();
88
+ // Extract raw 32-byte ED25519 public key
89
+ const rawPublicKey = extractEd25519PublicKey(derPublicKey);
90
+ // Create Solana PublicKey and cache both forms
91
+ this.rawPublicKey = rawPublicKey;
92
+ this.publicKey = new PublicKey(rawPublicKey);
93
+ return this.publicKey;
94
+ }
95
+ /**
96
+ * Retrieves the raw 32-byte ED25519 public key.
97
+ *
98
+ * The public key is cached after first retrieval to minimize KMS API calls.
99
+ *
100
+ * @returns Raw 32-byte public key as Uint8Array
101
+ * @throws {KmsClientError} If KMS API call fails
102
+ * @throws {PublicKeyExtractionError} If DER decoding fails
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * const rawPublicKey = await signer.getRawPublicKey();
107
+ * console.log('Raw public key length:', rawPublicKey.length); // 32
108
+ * ```
109
+ */
110
+ async getRawPublicKey() {
111
+ // Return cached raw public key if available
112
+ if (this.rawPublicKey) {
113
+ return this.rawPublicKey;
114
+ }
115
+ // Get DER-encoded public key from KMS
116
+ const derPublicKey = await this.kmsClient.getPublicKey();
117
+ // Extract raw 32-byte ED25519 public key
118
+ this.rawPublicKey = extractEd25519PublicKey(derPublicKey);
119
+ return this.rawPublicKey;
120
+ }
121
+ /**
122
+ * Signs an arbitrary message using the KMS key.
123
+ *
124
+ * The signature is verified using tweetnacl before being returned
125
+ * to ensure cryptographic correctness.
126
+ *
127
+ * @param message - Message to sign as Uint8Array
128
+ * @returns ED25519 signature (64 bytes)
129
+ * @throws {KmsClientError} If KMS API call fails
130
+ * @throws {SignatureVerificationError} If signature verification fails
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * const message = new TextEncoder().encode('Hello, Solana!');
135
+ * const signature = await signer.signMessage(message);
136
+ * console.log('Signature length:', signature.length); // 64
137
+ * ```
138
+ */
139
+ async signMessage(message) {
140
+ // Get signature from KMS
141
+ const signature = await this.kmsClient.sign(message);
142
+ // Get raw public key for verification
143
+ const rawPublicKey = await this.getRawPublicKey();
144
+ // Verify signature using tweetnacl
145
+ const isValid = nacl.sign.detached.verify(message, signature, rawPublicKey);
146
+ if (!isValid) {
147
+ throw new SignatureVerificationError('Signature verification failed: signature does not match public key and message');
148
+ }
149
+ return signature;
150
+ }
151
+ /**
152
+ * Signs a Solana legacy Transaction.
153
+ *
154
+ * The transaction must have recentBlockhash and feePayer set before signing.
155
+ *
156
+ * @param transaction - Transaction to sign
157
+ * @returns Signed transaction
158
+ * @throws {KmsClientError} If KMS API call fails
159
+ * @throws {SignatureVerificationError} If signature verification fails
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * const transaction = new Transaction().add(instruction);
164
+ * transaction.recentBlockhash = recentBlockhash;
165
+ * transaction.feePayer = await signer.getPublicKey();
166
+ * const signedTx = await signer.signTransaction(transaction);
167
+ * ```
168
+ */
169
+ async signTransaction(transaction) {
170
+ // Get public key for signing
171
+ const publicKey = await this.getPublicKey();
172
+ // Serialize transaction message
173
+ const message = transaction.serializeMessage();
174
+ // Sign the serialized message
175
+ const signature = await this.signMessage(message);
176
+ // Add signature to transaction
177
+ transaction.addSignature(publicKey, Buffer.from(signature));
178
+ return transaction;
179
+ }
180
+ /**
181
+ * Signs a Solana VersionedTransaction.
182
+ *
183
+ * The transaction must have a valid message with recentBlockhash set.
184
+ *
185
+ * @param transaction - VersionedTransaction to sign
186
+ * @returns Signed versioned transaction
187
+ * @throws {KmsClientError} If KMS API call fails
188
+ * @throws {SignatureVerificationError} If signature verification fails
189
+ *
190
+ * @example
191
+ * ```typescript
192
+ * const message = MessageV0.compile({
193
+ * payerKey: await signer.getPublicKey(),
194
+ * instructions: [instruction],
195
+ * recentBlockhash: recentBlockhash
196
+ * });
197
+ * const transaction = new VersionedTransaction(message);
198
+ * const signedTx = await signer.signVersionedTransaction(transaction);
199
+ * ```
200
+ */
201
+ async signVersionedTransaction(transaction) {
202
+ // Serialize transaction message
203
+ const message = transaction.message.serialize();
204
+ // Sign the serialized message
205
+ const signature = await this.signMessage(message);
206
+ // Add signature to transaction's signatures array
207
+ transaction.addSignature(await this.getPublicKey(), Buffer.from(signature));
208
+ return transaction;
209
+ }
210
+ /**
211
+ * Signs multiple Solana transactions in parallel.
212
+ *
213
+ * All transactions must have recentBlockhash and feePayer set before signing.
214
+ *
215
+ * @param transactions - Array of transactions to sign
216
+ * @returns Array of signed transactions in the same order
217
+ * @throws {KmsClientError} If any KMS API call fails
218
+ * @throws {SignatureVerificationError} If any signature verification fails
219
+ *
220
+ * @example
221
+ * ```typescript
222
+ * const transactions = [tx1, tx2, tx3];
223
+ * const signedTxs = await signer.signAllTransactions(transactions);
224
+ * ```
225
+ */
226
+ async signAllTransactions(transactions) {
227
+ return Promise.all(transactions.map((transaction) => this.signTransaction(transaction)));
228
+ }
229
+ }
230
+ //# sourceMappingURL=signer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/kms/signer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,GAGV,MAAM,iBAAiB,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,OAAO,eAAe;IAK1B;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,MAA6B;QACvC,IAAI,MAAM,YAAY,SAAS,EAAE,CAAC;YAChC,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,YAAY;QAChB,wCAAwC;QACxC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,OAAO,IAAI,CAAC,SAAS,CAAC;QACxB,CAAC;QAED,sCAAsC;QACtC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC;QAEzD,yCAAyC;QACzC,MAAM,YAAY,GAAG,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAE3D,+CAA+C;QAC/C,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,IAAI,SAAS,CAAC,YAAY,CAAC,CAAC;QAE7C,OAAO,IAAI,CAAC,SAAS,CAAC;IACxB,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,eAAe;QACnB,4CAA4C;QAC5C,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC,YAAY,CAAC;QAC3B,CAAC;QAED,sCAAsC;QACtC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE,CAAC;QAEzD,yCAAyC;QACzC,IAAI,CAAC,YAAY,GAAG,uBAAuB,CAAC,YAAY,CAAC,CAAC;QAE1D,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,WAAW,CAAC,OAAmB;QACnC,yBAAyB;QACzB,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAErD,sCAAsC;QACtC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAElD,mCAAmC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CACvC,OAAO,EACP,SAAS,EACT,YAAY,CACb,CAAC;QAEF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,0BAA0B,CAClC,gFAAgF,CACjF,CAAC;QACJ,CAAC;QAED,OAAO,SAAS,CAAC;IACnB,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,KAAK,CAAC,eAAe,CAAC,WAAwB;QAC5C,6BAA6B;QAC7B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAE5C,gCAAgC;QAChC,MAAM,OAAO,GAAG,WAAW,CAAC,gBAAgB,EAAE,CAAC;QAE/C,8BAA8B;QAC9B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAElD,+BAA+B;QAC/B,WAAW,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QAE5D,OAAO,WAAW,CAAC;IACrB,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,KAAK,CAAC,wBAAwB,CAC5B,WAAiC;QAEjC,gCAAgC;QAChC,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;QAEhD,8BAA8B;QAC9B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QAElD,kDAAkD;QAClD,WAAW,CAAC,YAAY,CAAC,MAAM,IAAI,CAAC,YAAY,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QAE5E,OAAO,WAAW,CAAC;IACrB,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,mBAAmB,CACvB,YAA2B;QAE3B,OAAO,OAAO,CAAC,GAAG,CAChB,YAAY,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CACrE,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Configuration for AWS KMS client.
3
+ */
4
+ export interface KmsConfig {
5
+ /**
6
+ * AWS region where the KMS key is located (e.g., 'us-east-1').
7
+ */
8
+ region: string;
9
+ /**
10
+ * KMS key ID or ARN to use for signing operations.
11
+ */
12
+ keyId: string;
13
+ /**
14
+ * Optional AWS credentials. If not provided, the AWS SDK will use
15
+ * environment variables, IAM roles, or other credential providers.
16
+ */
17
+ credentials?: {
18
+ /**
19
+ * AWS access key ID.
20
+ */
21
+ accessKeyId: string;
22
+ /**
23
+ * AWS secret access key.
24
+ */
25
+ secretAccessKey: string;
26
+ /**
27
+ * Optional session token for temporary credentials.
28
+ */
29
+ sessionToken?: string;
30
+ };
31
+ }
32
+ /**
33
+ * Configuration for Solana KMS Signer.
34
+ * Currently extends KmsConfig with no additional fields.
35
+ * Additional configuration options can be added in the future.
36
+ */
37
+ export interface SolanaKmsSignerConfig extends KmsConfig {
38
+ }
39
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,CAAC,EAAE;QACZ;;WAEG;QACH,WAAW,EAAE,MAAM,CAAC;QAEpB;;WAEG;QACH,eAAe,EAAE,MAAM,CAAC;QAExB;;WAEG;QACH,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAsB,SAAQ,SAAS;CAEvD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Extracts a 32-byte ED25519 public key from DER-encoded SubjectPublicKeyInfo.
3
+ *
4
+ * AWS KMS GetPublicKey returns DER-encoded public keys in X.509 SubjectPublicKeyInfo format:
5
+ * ```
6
+ * 30 2a # SEQUENCE, 42 bytes
7
+ * 30 05 # SEQUENCE, 5 bytes (AlgorithmIdentifier)
8
+ * 06 03 # OID, 3 bytes
9
+ * 2b 65 70 # 1.3.101.112 (Ed25519)
10
+ * 03 21 00 # BIT STRING, 33 bytes (32 bytes + 1 byte padding)
11
+ * [32 bytes] # ED25519 public key
12
+ * ```
13
+ *
14
+ * This function extracts the raw 32-byte public key from the DER structure.
15
+ *
16
+ * @param derEncoded - DER-encoded SubjectPublicKeyInfo from AWS KMS
17
+ * @returns Raw 32-byte ED25519 public key
18
+ * @throws {PublicKeyExtractionError} If the DER encoding is invalid or unexpected
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * const derEncoded = await kmsClient.getPublicKey();
23
+ * const rawPublicKey = extractEd25519PublicKey(derEncoded);
24
+ * // rawPublicKey is Uint8Array of 32 bytes
25
+ * ```
26
+ */
27
+ export declare function extractEd25519PublicKey(derEncoded: Uint8Array): Uint8Array;
28
+ //# sourceMappingURL=publicKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicKey.d.ts","sourceRoot":"","sources":["../../src/utils/publicKey.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CAyC1E"}
@@ -0,0 +1,56 @@
1
+ import { PublicKeyExtractionError } from '../errors/index.js';
2
+ /**
3
+ * Extracts a 32-byte ED25519 public key from DER-encoded SubjectPublicKeyInfo.
4
+ *
5
+ * AWS KMS GetPublicKey returns DER-encoded public keys in X.509 SubjectPublicKeyInfo format:
6
+ * ```
7
+ * 30 2a # SEQUENCE, 42 bytes
8
+ * 30 05 # SEQUENCE, 5 bytes (AlgorithmIdentifier)
9
+ * 06 03 # OID, 3 bytes
10
+ * 2b 65 70 # 1.3.101.112 (Ed25519)
11
+ * 03 21 00 # BIT STRING, 33 bytes (32 bytes + 1 byte padding)
12
+ * [32 bytes] # ED25519 public key
13
+ * ```
14
+ *
15
+ * This function extracts the raw 32-byte public key from the DER structure.
16
+ *
17
+ * @param derEncoded - DER-encoded SubjectPublicKeyInfo from AWS KMS
18
+ * @returns Raw 32-byte ED25519 public key
19
+ * @throws {PublicKeyExtractionError} If the DER encoding is invalid or unexpected
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const derEncoded = await kmsClient.getPublicKey();
24
+ * const rawPublicKey = extractEd25519PublicKey(derEncoded);
25
+ * // rawPublicKey is Uint8Array of 32 bytes
26
+ * ```
27
+ */
28
+ export function extractEd25519PublicKey(derEncoded) {
29
+ // Validate DER structure - first byte must be SEQUENCE tag (0x30)
30
+ if (derEncoded[0] !== 0x30) {
31
+ throw new PublicKeyExtractionError('Invalid DER encoding: missing SEQUENCE tag');
32
+ }
33
+ // Parse AlgorithmIdentifier SEQUENCE to find where BIT STRING starts
34
+ // Position 2 should be another SEQUENCE tag for AlgorithmIdentifier
35
+ if (derEncoded[2] !== 0x30) {
36
+ throw new PublicKeyExtractionError('Invalid DER encoding: missing AlgorithmIdentifier SEQUENCE');
37
+ }
38
+ // Get length of AlgorithmIdentifier content
39
+ const algorithmIdentifierLength = derEncoded[3];
40
+ // BIT STRING starts after AlgorithmIdentifier SEQUENCE
41
+ // Position = 4 (first content byte) + algorithmIdentifierLength
42
+ const bitStringIndex = 4 + algorithmIdentifierLength;
43
+ // Verify BIT STRING tag (0x03)
44
+ if (derEncoded[bitStringIndex] !== 0x03) {
45
+ throw new PublicKeyExtractionError('Invalid DER encoding: missing BIT STRING');
46
+ }
47
+ // Verify BIT STRING length (0x21 = 33 bytes: 1 byte unused bits + 32 bytes public key)
48
+ const bitStringLength = derEncoded[bitStringIndex + 1];
49
+ if (bitStringLength !== 0x21) {
50
+ throw new PublicKeyExtractionError(`Unexpected BIT STRING length: expected 0x21 (33 bytes), got 0x${bitStringLength.toString(16)}`);
51
+ }
52
+ // First byte after length is unused bits (0x00), next 32 bytes is the public key
53
+ const publicKeyStart = bitStringIndex + 3;
54
+ return derEncoded.slice(publicKeyStart, publicKeyStart + 32);
55
+ }
56
+ //# sourceMappingURL=publicKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publicKey.js","sourceRoot":"","sources":["../../src/utils/publicKey.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,uBAAuB,CAAC,UAAsB;IAC5D,kEAAkE;IAClE,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,wBAAwB,CAChC,4CAA4C,CAC7C,CAAC;IACJ,CAAC;IAED,qEAAqE;IACrE,oEAAoE;IACpE,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,wBAAwB,CAChC,4DAA4D,CAC7D,CAAC;IACJ,CAAC;IAED,4CAA4C;IAC5C,MAAM,yBAAyB,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAEhD,uDAAuD;IACvD,gEAAgE;IAChE,MAAM,cAAc,GAAG,CAAC,GAAG,yBAAyB,CAAC;IAErD,+BAA+B;IAC/B,IAAI,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE,CAAC;QACxC,MAAM,IAAI,wBAAwB,CAChC,0CAA0C,CAC3C,CAAC;IACJ,CAAC;IAED,uFAAuF;IACvF,MAAM,eAAe,GAAG,UAAU,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC;IACvD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,wBAAwB,CAChC,iEAAiE,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAChG,CAAC;IACJ,CAAC;IAED,iFAAiF;IACjF,MAAM,cAAc,GAAG,cAAc,GAAG,CAAC,CAAC;IAC1C,OAAO,UAAU,CAAC,KAAK,CAAC,cAAc,EAAE,cAAc,GAAG,EAAE,CAAC,CAAC;AAC/D,CAAC"}
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "solana-kms-signer",
3
+ "version": "0.1.0",
4
+ "description": "AWS KMS-based Solana signer with ED25519 support",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "vitest",
17
+ "test:run": "vitest run",
18
+ "test:ui": "vitest --ui",
19
+ "test:coverage": "vitest run --coverage",
20
+ "type-check": "tsc --noEmit",
21
+ "example:sign-message": "tsx examples/sign-message.ts",
22
+ "example:sign-transaction": "tsx examples/sign-transaction.ts",
23
+ "example:sign-versioned-transaction": "tsx examples/sign-versioned-transaction.ts",
24
+ "example:multiple-signatures": "tsx examples/multiple-signatures.ts",
25
+ "example:create-kms-key": "tsx examples/create-kms-key.ts"
26
+ },
27
+ "keywords": [
28
+ "solana",
29
+ "kms",
30
+ "aws",
31
+ "signature",
32
+ "ed25519",
33
+ "transaction",
34
+ "signing",
35
+ "key-management",
36
+ "cryptography",
37
+ "blockchain"
38
+ ],
39
+ "author": "Taegeon Alan Go",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/gtg7784/solana-kms-signer.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/gtg7784/solana-kms-signer/issues"
47
+ },
48
+ "homepage": "https://github.com/gtg7784/solana-kms-signer#readme",
49
+ "files": [
50
+ "dist",
51
+ "src",
52
+ "LICENSE",
53
+ "README.md"
54
+ ],
55
+ "engines": {
56
+ "node": ">=16.0.0"
57
+ },
58
+ "devDependencies": {
59
+ "@types/node": "^24.10.0",
60
+ "@vitest/coverage-v8": "^4.0.8",
61
+ "@vitest/ui": "^4.0.8",
62
+ "dotenv": "^17.2.3",
63
+ "tsx": "^4.20.6",
64
+ "typescript": "^5.9.3",
65
+ "vitest": "^4.0.8"
66
+ },
67
+ "packageManager": "pnpm@9.0.1+sha1.0e0a9c2d140ddf9aab730067eb7bcfb9e18bdee7",
68
+ "dependencies": {
69
+ "@aws-sdk/client-kms": "^3.928.0",
70
+ "@solana/web3.js": "^1.98.4",
71
+ "tweetnacl": "^1.0.3"
72
+ }
73
+ }
@@ -0,0 +1,173 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { KmsClientError, PublicKeyExtractionError, SignatureVerificationError } from './index.js';
3
+
4
+ describe('Error Classes', () => {
5
+ describe('KmsClientError', () => {
6
+ it('should create error with message', () => {
7
+ // given
8
+ const message = 'Test KMS error';
9
+
10
+ // when
11
+ const error = new KmsClientError(message);
12
+
13
+ // then
14
+ expect(error).toBeInstanceOf(Error);
15
+ expect(error).toBeInstanceOf(KmsClientError);
16
+ expect(error.message).toBe(message);
17
+ expect(error.name).toBe('KmsClientError');
18
+ expect(error.cause).toBeUndefined();
19
+ });
20
+
21
+ it('should create error with message and cause', () => {
22
+ // given
23
+ const message = 'Test KMS error';
24
+ const cause = new Error('Original error');
25
+
26
+ // when
27
+ const error = new KmsClientError(message, cause);
28
+
29
+ // then
30
+ expect(error.message).toBe(message);
31
+ expect(error.cause).toBe(cause);
32
+ expect(error.name).toBe('KmsClientError');
33
+ });
34
+
35
+ it('should create error with cause as object', () => {
36
+ // given
37
+ const message = 'Test KMS error';
38
+ const cause = { code: 'AccessDeniedException', statusCode: 403 };
39
+
40
+ // when
41
+ const error = new KmsClientError(message, cause);
42
+
43
+ // then
44
+ expect(error.cause).toBe(cause);
45
+ });
46
+
47
+ it('should have stack trace', () => {
48
+ // given
49
+ const message = 'Test KMS error';
50
+
51
+ // when
52
+ const error = new KmsClientError(message);
53
+
54
+ // then
55
+ expect(error.stack).toBeDefined();
56
+ expect(error.stack).toContain('KmsClientError');
57
+ });
58
+ });
59
+
60
+ describe('PublicKeyExtractionError', () => {
61
+ it('should create error with message', () => {
62
+ // given
63
+ const message = 'Invalid DER encoding';
64
+
65
+ // when
66
+ const error = new PublicKeyExtractionError(message);
67
+
68
+ // then
69
+ expect(error).toBeInstanceOf(Error);
70
+ expect(error).toBeInstanceOf(PublicKeyExtractionError);
71
+ expect(error.message).toBe(message);
72
+ expect(error.name).toBe('PublicKeyExtractionError');
73
+ expect(error.cause).toBeUndefined();
74
+ });
75
+
76
+ it('should create error with message and cause', () => {
77
+ // given
78
+ const message = 'Invalid DER encoding';
79
+ const cause = new Error('DER parsing failed');
80
+
81
+ // when
82
+ const error = new PublicKeyExtractionError(message, cause);
83
+
84
+ // then
85
+ expect(error.message).toBe(message);
86
+ expect(error.cause).toBe(cause);
87
+ expect(error.name).toBe('PublicKeyExtractionError');
88
+ });
89
+
90
+ it('should have stack trace', () => {
91
+ // given
92
+ const message = 'Invalid DER encoding';
93
+
94
+ // when
95
+ const error = new PublicKeyExtractionError(message);
96
+
97
+ // then
98
+ expect(error.stack).toBeDefined();
99
+ expect(error.stack).toContain('PublicKeyExtractionError');
100
+ });
101
+ });
102
+
103
+ describe('SignatureVerificationError', () => {
104
+ it('should create error with message', () => {
105
+ // given
106
+ const message = 'Signature verification failed';
107
+
108
+ // when
109
+ const error = new SignatureVerificationError(message);
110
+
111
+ // then
112
+ expect(error).toBeInstanceOf(Error);
113
+ expect(error).toBeInstanceOf(SignatureVerificationError);
114
+ expect(error.message).toBe(message);
115
+ expect(error.name).toBe('SignatureVerificationError');
116
+ expect(error.cause).toBeUndefined();
117
+ });
118
+
119
+ it('should create error with message and cause', () => {
120
+ // given
121
+ const message = 'Signature verification failed';
122
+ const cause = { signature: new Uint8Array(64), publicKey: new Uint8Array(32) };
123
+
124
+ // when
125
+ const error = new SignatureVerificationError(message, cause);
126
+
127
+ // then
128
+ expect(error.message).toBe(message);
129
+ expect(error.cause).toBe(cause);
130
+ expect(error.name).toBe('SignatureVerificationError');
131
+ });
132
+
133
+ it('should have stack trace', () => {
134
+ // given
135
+ const message = 'Signature verification failed';
136
+
137
+ // when
138
+ const error = new SignatureVerificationError(message);
139
+
140
+ // then
141
+ expect(error.stack).toBeDefined();
142
+ expect(error.stack).toContain('SignatureVerificationError');
143
+ });
144
+ });
145
+
146
+ describe('Error Chaining', () => {
147
+ it('should support error chaining with nested causes', () => {
148
+ // given
149
+ const rootCause = new Error('Root cause error');
150
+ const intermediateCause = new PublicKeyExtractionError('Intermediate error', rootCause);
151
+
152
+ // when
153
+ const topLevelError = new KmsClientError('Top level error', intermediateCause);
154
+
155
+ // then
156
+ expect(topLevelError.cause).toBe(intermediateCause);
157
+ expect((topLevelError.cause as PublicKeyExtractionError).cause).toBe(rootCause);
158
+ });
159
+
160
+ it('should preserve cause immutability', () => {
161
+ // given
162
+ const message = 'Test error';
163
+ const cause = { code: 'TEST_ERROR' };
164
+ const error = new KmsClientError(message, cause);
165
+
166
+ // when/then
167
+ // TypeScript should prevent reassignment:
168
+ // error.cause = { code: 'DIFFERENT_ERROR' }; // This would cause TypeScript error
169
+
170
+ expect(error.cause).toBe(cause);
171
+ });
172
+ });
173
+ });