evm-kms-signer 1.0.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 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,305 @@
1
+ # kms-signer-evm
2
+
3
+ A TypeScript library that integrates AWS KMS (Key Management Service) with [viem](https://viem.sh) to create secure Ethereum signers. This allows you to sign Ethereum transactions and messages using keys stored in AWS KMS, providing enterprise-grade security for your Ethereum operations.
4
+
5
+ ## Features
6
+
7
+ - **AWS KMS Integration**: Sign Ethereum transactions using keys securely stored in AWS KMS
8
+ - **Full EIP Compliance**: Supports EIP-191 (personal messages), EIP-712 (typed data), EIP-155 (replay protection), EIP-2 (signature normalization)
9
+ - **Type-Safe**: Built with TypeScript in strict mode with comprehensive type definitions
10
+ - **viem Compatible**: Seamlessly integrates with viem's Account system via `toAccount`
11
+ - **DER Signature Parsing**: Automatically converts AWS KMS DER-encoded signatures to Ethereum format
12
+ - **Comprehensive Error Handling**: Custom error classes for better debugging
13
+ - **Well-Tested**: 51 tests covering all functionality with 100% type safety
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add kms-signer-evm
19
+ ```
20
+
21
+ Or with npm:
22
+
23
+ ```bash
24
+ npm install kms-signer-evm
25
+ ```
26
+
27
+ Or with yarn:
28
+
29
+ ```bash
30
+ yarn add kms-signer-evm
31
+ ```
32
+
33
+ ## Prerequisites
34
+
35
+ ### AWS KMS Key Setup
36
+
37
+ 1. **Create an ECC Key in AWS KMS**:
38
+ - Go to AWS KMS Console
39
+ - Click "Create key"
40
+ - Choose "Asymmetric" key type
41
+ - Select "Sign and verify" key usage
42
+ - Choose **ECC_SECG_P256K1** as the key spec (this is secp256k1, Ethereum's curve)
43
+ - Complete the key creation process
44
+
45
+ 2. **Grant Permissions**:
46
+ Ensure your AWS credentials have the following permissions:
47
+ - `kms:GetPublicKey`
48
+ - `kms:Sign`
49
+
50
+ 3. **Note Your Key ID**:
51
+ Copy the Key ARN or Key ID for use in your application.
52
+
53
+ ### Environment Variables
54
+
55
+ Create a `.env` file in your project root:
56
+
57
+ ```env
58
+ AWS_REGION=us-east-1
59
+ KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012
60
+
61
+ # Optional: If not using IAM roles or default credentials
62
+ AWS_ACCESS_KEY_ID=your_access_key
63
+ AWS_SECRET_ACCESS_KEY=your_secret_key
64
+ ```
65
+
66
+ ## Quick Start
67
+
68
+ ### Signing a Message
69
+
70
+ ```typescript
71
+ import 'dotenv/config'
72
+ import { KmsSigner, toKmsAccount } from 'kms-signer-evm'
73
+
74
+ async function main() {
75
+ // Initialize the KMS signer
76
+ const signer = new KmsSigner({
77
+ region: process.env.AWS_REGION!,
78
+ keyId: process.env.KMS_KEY_ID!,
79
+ })
80
+
81
+ // Convert to viem account
82
+ const account = await toKmsAccount(signer)
83
+
84
+ console.log('Account address:', account.address)
85
+
86
+ // Sign a message
87
+ const message = 'Hello from AWS KMS!'
88
+ const signature = await account.signMessage({ message })
89
+
90
+ console.log('Signature:', signature)
91
+ }
92
+
93
+ main().catch(console.error)
94
+ ```
95
+
96
+ ### Sending a Transaction
97
+
98
+ ```typescript
99
+ import 'dotenv/config'
100
+ import { createWalletClient, http } from 'viem'
101
+ import { sepolia } from 'viem/chains'
102
+ import { KmsSigner, toKmsAccount } from 'kms-signer-evm'
103
+
104
+ async function main() {
105
+ // Initialize the KMS signer
106
+ const signer = new KmsSigner({
107
+ region: process.env.AWS_REGION!,
108
+ keyId: process.env.KMS_KEY_ID!,
109
+ })
110
+
111
+ // Convert to viem account
112
+ const account = await toKmsAccount(signer)
113
+
114
+ // Create a wallet client
115
+ const client = createWalletClient({
116
+ account,
117
+ chain: sepolia,
118
+ transport: http()
119
+ })
120
+
121
+ // Send a transaction
122
+ const hash = await client.sendTransaction({
123
+ to: '0xa5D3241A1591061F2a4bB69CA0215F66520E67cf',
124
+ value: 1000000000000000n, // 0.001 ETH
125
+ })
126
+
127
+ console.log('Transaction hash:', hash)
128
+ }
129
+
130
+ main().catch(console.error)
131
+ ```
132
+
133
+ ## API Documentation
134
+
135
+ ### `KmsSigner`
136
+
137
+ The main class for signing operations using AWS KMS.
138
+
139
+ #### Constructor
140
+
141
+ ```typescript
142
+ new KmsSigner(config: KmsConfig)
143
+ ```
144
+
145
+ **Parameters:**
146
+ - `config.region`: AWS region where your KMS key is located
147
+ - `config.keyId`: AWS KMS key ID or ARN
148
+ - `config.credentials` (optional): AWS credentials object with `accessKeyId` and `secretAccessKey`
149
+
150
+ #### Methods
151
+
152
+ ##### `getAddress(): Promise<Address>`
153
+
154
+ Returns the Ethereum address derived from the KMS public key.
155
+
156
+ ```typescript
157
+ const address = await signer.getAddress()
158
+ ```
159
+
160
+ ##### `getPublicKey(): Promise<Uint8Array>`
161
+
162
+ Returns the uncompressed public key (65 bytes) from AWS KMS.
163
+
164
+ ```typescript
165
+ const publicKey = await signer.getPublicKey()
166
+ ```
167
+
168
+ ##### `signMessage({ message }): Promise<Hex>`
169
+
170
+ Signs a personal message (EIP-191).
171
+
172
+ ```typescript
173
+ const signature = await signer.signMessage({ message: 'Hello World' })
174
+ ```
175
+
176
+ ##### `signTransaction(transaction, options?): Promise<Hex>`
177
+
178
+ Signs an Ethereum transaction with EIP-155 replay protection.
179
+
180
+ ```typescript
181
+ const signedTx = await signer.signTransaction({
182
+ to: '0x...',
183
+ value: 1000000000000000n,
184
+ chainId: 11155111,
185
+ nonce: 0,
186
+ maxFeePerGas: 20000000000n,
187
+ maxPriorityFeePerGas: 1000000000n,
188
+ })
189
+ ```
190
+
191
+ ##### `signTypedData(typedData): Promise<Hex>`
192
+
193
+ Signs structured data (EIP-712).
194
+
195
+ ```typescript
196
+ const signature = await signer.signTypedData({
197
+ domain: {
198
+ name: 'Ether Mail',
199
+ version: '1',
200
+ chainId: 1,
201
+ verifyingContract: '0x...'
202
+ },
203
+ types: {
204
+ Person: [
205
+ { name: 'name', type: 'string' },
206
+ { name: 'wallet', type: 'address' }
207
+ ]
208
+ },
209
+ primaryType: 'Person',
210
+ message: {
211
+ name: 'Bob',
212
+ wallet: '0x...'
213
+ }
214
+ })
215
+ ```
216
+
217
+ ### `toKmsAccount(signer: KmsSigner): Promise<LocalAccount>`
218
+
219
+ Converts a `KmsSigner` instance to a viem `LocalAccount` that can be used with viem's wallet clients.
220
+
221
+ ```typescript
222
+ const account = await toKmsAccount(signer)
223
+
224
+ const client = createWalletClient({
225
+ account,
226
+ chain: mainnet,
227
+ transport: http()
228
+ })
229
+ ```
230
+
231
+ ### Error Classes
232
+
233
+ The library provides custom error classes for better error handling:
234
+
235
+ - `KmsSignerError`: Base error class
236
+ - `DerParsingError`: Thrown when DER signature parsing fails
237
+ - `KmsClientError`: Thrown when AWS KMS operations fail
238
+ - `SignatureNormalizationError`: Thrown when signature normalization fails
239
+ - `RecoveryIdCalculationError`: Thrown when recovery ID calculation fails
240
+
241
+ ## Security Considerations
242
+
243
+ ### Key Management
244
+
245
+ - **Private keys never leave AWS KMS**: All signing operations happen within AWS KMS
246
+ - **IAM Permissions**: Use least-privilege IAM policies for KMS access
247
+ - **Key Rotation**: Consider AWS KMS key rotation policies for your use case
248
+
249
+ ### Signature Security
250
+
251
+ - **EIP-2 Compliance**: All signatures are normalized to prevent malleability attacks
252
+ - **Replay Protection**: Transaction signatures include EIP-155 chainId by default
253
+ - **Recovery ID**: Automatically calculated and verified for all signatures
254
+
255
+ ### Best Practices
256
+
257
+ 1. **Use IAM Roles**: Prefer IAM roles over hardcoded credentials in production
258
+ 2. **Environment Variables**: Never commit `.env` files with credentials
259
+ 3. **Key Policies**: Restrict KMS key usage to specific AWS principals
260
+ 4. **Audit Logging**: Enable AWS CloudTrail to monitor KMS key usage
261
+ 5. **Network Security**: Use VPC endpoints for KMS in production environments
262
+
263
+ ## Development
264
+
265
+ ### Running Tests
266
+
267
+ ```bash
268
+ pnpm test:run
269
+ ```
270
+
271
+ ### Type Checking
272
+
273
+ ```bash
274
+ pnpm type-check
275
+ ```
276
+
277
+ ### Building
278
+
279
+ ```bash
280
+ pnpm build
281
+ ```
282
+
283
+ ### Running Examples
284
+
285
+ ```bash
286
+ # Sign a message
287
+ pnpm example:sign
288
+
289
+ # Send a transaction
290
+ pnpm example:tx
291
+ ```
292
+
293
+ ## License
294
+
295
+ MIT
296
+
297
+ ## Contributing
298
+
299
+ Contributions are welcome! Please feel free to submit a Pull Request.
300
+
301
+ ## Acknowledgments
302
+
303
+ - Built with [viem](https://viem.sh) - Modern TypeScript Ethereum library
304
+ - Uses [AWS SDK for JavaScript v3](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/)
305
+ - Inspired by the need for secure key management in Ethereum applications
@@ -0,0 +1,25 @@
1
+ import type { LocalAccount } from 'viem';
2
+ import { KmsSigner } from './kms/signer';
3
+ /**
4
+ * Create a viem Account from KmsSigner.
5
+ *
6
+ * Wraps KmsSigner methods to match viem's Account interface,
7
+ * enabling use with viem clients (walletClient, etc.)
8
+ *
9
+ * @param signer - KmsSigner instance
10
+ * @returns viem Account with KMS signing capabilities
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * const signer = new KmsSigner({ region: 'us-east-1', keyId: 'key-id' })
15
+ * const account = await toKmsAccount(signer)
16
+ *
17
+ * const client = createWalletClient({
18
+ * account,
19
+ * chain: mainnet,
20
+ * transport: http()
21
+ * })
22
+ * ```
23
+ */
24
+ export declare function toKmsAccount(signer: KmsSigner): Promise<LocalAccount>;
25
+ //# sourceMappingURL=account.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAa3E"}
@@ -0,0 +1,36 @@
1
+ import { toAccount } from 'viem/accounts';
2
+ /**
3
+ * Create a viem Account from KmsSigner.
4
+ *
5
+ * Wraps KmsSigner methods to match viem's Account interface,
6
+ * enabling use with viem clients (walletClient, etc.)
7
+ *
8
+ * @param signer - KmsSigner instance
9
+ * @returns viem Account with KMS signing capabilities
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const signer = new KmsSigner({ region: 'us-east-1', keyId: 'key-id' })
14
+ * const account = await toKmsAccount(signer)
15
+ *
16
+ * const client = createWalletClient({
17
+ * account,
18
+ * chain: mainnet,
19
+ * transport: http()
20
+ * })
21
+ * ```
22
+ */
23
+ export async function toKmsAccount(signer) {
24
+ const address = await signer.getAddress();
25
+ return toAccount({
26
+ address,
27
+ signMessage: async ({ message }) => {
28
+ // Convert SignableMessage to string for KmsSigner
29
+ const messageStr = typeof message === 'string' ? message : message.raw.toString();
30
+ return signer.signMessage({ message: messageStr });
31
+ },
32
+ signTransaction: async (transaction, options) => signer.signTransaction(transaction, options),
33
+ signTypedData: async (typedData) => signer.signTypedData(typedData)
34
+ });
35
+ }
36
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAIzC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAiB;IAClD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAA;IAEzC,OAAO,SAAS,CAAC;QACf,OAAO;QACP,WAAW,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YACjC,kDAAkD;YAClD,MAAM,UAAU,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAA;YACjF,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAA;QACpD,CAAC;QACD,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,OAAO,CAAC;QAC7F,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC;KACpE,CAAC,CAAA;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ export declare class KmsSignerError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class DerParsingError extends KmsSignerError {
5
+ constructor(message: string);
6
+ }
7
+ export declare class KmsClientError extends KmsSignerError {
8
+ readonly cause?: Error | undefined;
9
+ constructor(message: string, cause?: Error | undefined);
10
+ }
11
+ export declare class SignatureNormalizationError extends KmsSignerError {
12
+ constructor(message: string);
13
+ }
14
+ export declare class RecoveryIdCalculationError extends KmsSignerError {
15
+ constructor(message: string);
16
+ }
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,eAAgB,SAAQ,cAAc;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,cAAe,SAAQ,cAAc;aACH,KAAK,CAAC,EAAE,KAAK;gBAA9C,OAAO,EAAE,MAAM,EAAkB,KAAK,CAAC,EAAE,KAAK,YAAA;CAI3D;AAED,qBAAa,2BAA4B,SAAQ,cAAc;gBACjD,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,0BAA2B,SAAQ,cAAc;gBAChD,OAAO,EAAE,MAAM;CAI5B"}
@@ -0,0 +1,32 @@
1
+ export class KmsSignerError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'KmsSignerError';
5
+ }
6
+ }
7
+ export class DerParsingError extends KmsSignerError {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'DerParsingError';
11
+ }
12
+ }
13
+ export class KmsClientError extends KmsSignerError {
14
+ constructor(message, cause) {
15
+ super(message);
16
+ this.cause = cause;
17
+ this.name = 'KmsClientError';
18
+ }
19
+ }
20
+ export class SignatureNormalizationError extends KmsSignerError {
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = 'SignatureNormalizationError';
24
+ }
25
+ }
26
+ export class RecoveryIdCalculationError extends KmsSignerError {
27
+ constructor(message) {
28
+ super(message);
29
+ this.name = 'RecoveryIdCalculationError';
30
+ }
31
+ }
32
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,cAAc;IACjD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAA;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,cAAe,SAAQ,cAAc;IAChD,YAAY,OAAe,EAAkB,KAAa;QACxD,KAAK,CAAC,OAAO,CAAC,CAAA;QAD6B,UAAK,GAAL,KAAK,CAAQ;QAExD,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,2BAA4B,SAAQ,cAAc;IAC7D,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAA;IAC3C,CAAC;CACF;AAED,MAAM,OAAO,0BAA2B,SAAQ,cAAc;IAC5D,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAA;IAC1C,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ export { KmsSigner } from './kms/signer';
2
+ export { toKmsAccount } from './account';
3
+ export type { KmsConfig, DerSignature, SignatureData } from './types';
4
+ export { KmsSignerError, DerParsingError, KmsClientError, SignatureNormalizationError, RecoveryIdCalculationError } from './errors';
5
+ export type { Address, Hex } from 'viem';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAGxC,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAGrE,OAAO,EACL,cAAc,EACd,eAAe,EACf,cAAc,EACd,2BAA2B,EAC3B,0BAA0B,EAC3B,MAAM,UAAU,CAAA;AAGjB,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ // Main classes and functions
2
+ export { KmsSigner } from './kms/signer';
3
+ export { toKmsAccount } from './account';
4
+ // Errors
5
+ export { KmsSignerError, DerParsingError, KmsClientError, SignatureNormalizationError, RecoveryIdCalculationError } from './errors';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAKxC,SAAS;AACT,OAAO,EACL,cAAc,EACd,eAAe,EACf,cAAc,EACd,2BAA2B,EAC3B,0BAA0B,EAC3B,MAAM,UAAU,CAAA"}
@@ -0,0 +1,60 @@
1
+ import type { KmsConfig } from '../types';
2
+ /**
3
+ * KmsClient wraps AWS KMS SDK operations for key management and signing.
4
+ *
5
+ * This class provides a simplified interface to AWS KMS for:
6
+ * - Retrieving public keys from KMS
7
+ * - Signing message digests with KMS-stored private keys
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const client = new KmsClient({
12
+ * region: 'us-east-1',
13
+ * keyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
14
+ * })
15
+ *
16
+ * const publicKey = await client.getPublicKey()
17
+ * const signature = await client.sign(messageHash)
18
+ * ```
19
+ */
20
+ export declare class KmsClient {
21
+ private client;
22
+ private keyId;
23
+ /**
24
+ * Creates a new KMS client instance.
25
+ *
26
+ * @param config - KMS configuration including region, keyId, and optional credentials
27
+ *
28
+ * @remarks
29
+ * If credentials are not provided, the AWS SDK will use the default credential provider chain:
30
+ * - Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
31
+ * - Shared credentials file (~/.aws/credentials)
32
+ * - IAM role for EC2 instances or ECS tasks
33
+ */
34
+ constructor(config: KmsConfig);
35
+ /**
36
+ * Retrieves the public key from AWS KMS.
37
+ *
38
+ * @returns The public key in DER-encoded SubjectPublicKeyInfo (SPKI) format
39
+ * @throws {KmsClientError} If the KMS API call fails or returns no public key
40
+ *
41
+ * @remarks
42
+ * The returned public key is in SPKI format with a variable-length DER header.
43
+ * The last 65 bytes contain the uncompressed secp256k1 public key (0x04 + x + y).
44
+ */
45
+ getPublicKey(): Promise<Uint8Array>;
46
+ /**
47
+ * Signs a message hash using the KMS-stored private key.
48
+ *
49
+ * @param messageHash - The pre-hashed message to sign (32 bytes for keccak256)
50
+ * @returns The signature in DER-encoded format (SEQUENCE { INTEGER r, INTEGER s })
51
+ * @throws {KmsClientError} If the KMS API call fails or returns no signature
52
+ *
53
+ * @remarks
54
+ * - Uses MessageType.DIGEST because the message is already hashed
55
+ * - Uses ECDSA_SHA_256 signing algorithm (required for secp256k1)
56
+ * - The returned signature is DER-encoded and needs to be parsed to extract r and s values
57
+ */
58
+ sign(messageHash: Uint8Array): Promise<Uint8Array>;
59
+ }
60
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/kms/client.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAGzC;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,KAAK,CAAQ;IAErB;;;;;;;;;;OAUG;gBACS,MAAM,EAAE,SAAS;IAQ7B;;;;;;;;;OASG;IACG,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;IAmBzC;;;;;;;;;;;OAWG;IACG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;CAuBzD"}
@@ -0,0 +1,98 @@
1
+ import { KMSClient, GetPublicKeyCommand, SignCommand, MessageType, SigningAlgorithmSpec, } from '@aws-sdk/client-kms';
2
+ import { KmsClientError } from '../errors';
3
+ /**
4
+ * KmsClient wraps AWS KMS SDK operations for key management and signing.
5
+ *
6
+ * This class provides a simplified interface to AWS KMS for:
7
+ * - Retrieving public keys from KMS
8
+ * - Signing message digests with KMS-stored private keys
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const client = new KmsClient({
13
+ * region: 'us-east-1',
14
+ * keyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
15
+ * })
16
+ *
17
+ * const publicKey = await client.getPublicKey()
18
+ * const signature = await client.sign(messageHash)
19
+ * ```
20
+ */
21
+ export class KmsClient {
22
+ /**
23
+ * Creates a new KMS client instance.
24
+ *
25
+ * @param config - KMS configuration including region, keyId, and optional credentials
26
+ *
27
+ * @remarks
28
+ * If credentials are not provided, the AWS SDK will use the default credential provider chain:
29
+ * - Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
30
+ * - Shared credentials file (~/.aws/credentials)
31
+ * - IAM role for EC2 instances or ECS tasks
32
+ */
33
+ constructor(config) {
34
+ this.client = new KMSClient({
35
+ region: config.region,
36
+ ...(config.credentials && { credentials: config.credentials }),
37
+ });
38
+ this.keyId = config.keyId;
39
+ }
40
+ /**
41
+ * Retrieves the public key from AWS KMS.
42
+ *
43
+ * @returns The public key in DER-encoded SubjectPublicKeyInfo (SPKI) format
44
+ * @throws {KmsClientError} If the KMS API call fails or returns no public key
45
+ *
46
+ * @remarks
47
+ * The returned public key is in SPKI format with a variable-length DER header.
48
+ * The last 65 bytes contain the uncompressed secp256k1 public key (0x04 + x + y).
49
+ */
50
+ async getPublicKey() {
51
+ try {
52
+ const command = new GetPublicKeyCommand({ KeyId: this.keyId });
53
+ const response = await this.client.send(command);
54
+ if (!response.PublicKey) {
55
+ throw new KmsClientError('No public key returned from KMS');
56
+ }
57
+ return new Uint8Array(response.PublicKey);
58
+ }
59
+ catch (error) {
60
+ if (error instanceof KmsClientError)
61
+ throw error;
62
+ throw new KmsClientError(`Failed to get public key from KMS: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
63
+ }
64
+ }
65
+ /**
66
+ * Signs a message hash using the KMS-stored private key.
67
+ *
68
+ * @param messageHash - The pre-hashed message to sign (32 bytes for keccak256)
69
+ * @returns The signature in DER-encoded format (SEQUENCE { INTEGER r, INTEGER s })
70
+ * @throws {KmsClientError} If the KMS API call fails or returns no signature
71
+ *
72
+ * @remarks
73
+ * - Uses MessageType.DIGEST because the message is already hashed
74
+ * - Uses ECDSA_SHA_256 signing algorithm (required for secp256k1)
75
+ * - The returned signature is DER-encoded and needs to be parsed to extract r and s values
76
+ */
77
+ async sign(messageHash) {
78
+ try {
79
+ const command = new SignCommand({
80
+ KeyId: this.keyId,
81
+ Message: messageHash,
82
+ MessageType: MessageType.DIGEST,
83
+ SigningAlgorithm: SigningAlgorithmSpec.ECDSA_SHA_256,
84
+ });
85
+ const response = await this.client.send(command);
86
+ if (!response.Signature) {
87
+ throw new KmsClientError('No signature returned from KMS');
88
+ }
89
+ return new Uint8Array(response.Signature);
90
+ }
91
+ catch (error) {
92
+ if (error instanceof KmsClientError)
93
+ throw error;
94
+ throw new KmsClientError(`Failed to sign with KMS: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
95
+ }
96
+ }
97
+ }
98
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/kms/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,SAAS,EACT,mBAAmB,EACnB,WAAW,EACX,WAAW,EACX,oBAAoB,GACrB,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAE1C;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,OAAO,SAAS;IAIpB;;;;;;;;;;OAUG;IACH,YAAY,MAAiB;QAC3B,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC;YAC1B,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,GAAG,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;SAC/D,CAAC,CAAA;QACF,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAA;IAC3B,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,YAAY;QAChB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,mBAAmB,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;YAC9D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAEhD,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;gBACxB,MAAM,IAAI,cAAc,CAAC,iCAAiC,CAAC,CAAA;YAC7D,CAAC;YAED,OAAO,IAAI,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,cAAc;gBAAE,MAAM,KAAK,CAAA;YAChD,MAAM,IAAI,cAAc,CACtB,sCAAsC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,EAChG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAC3C,CAAA;QACH,CAAC;IACH,CAAC;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,IAAI,CAAC,WAAuB;QAChC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC;gBAC9B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,OAAO,EAAE,WAAW;gBACpB,WAAW,EAAE,WAAW,CAAC,MAAM;gBAC/B,gBAAgB,EAAE,oBAAoB,CAAC,aAAa;aACrD,CAAC,CAAA;YACF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAEhD,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;gBACxB,MAAM,IAAI,cAAc,CAAC,gCAAgC,CAAC,CAAA;YAC5D,CAAC;YAED,OAAO,IAAI,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;QAC3C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,cAAc;gBAAE,MAAM,KAAK,CAAA;YAChD,MAAM,IAAI,cAAc,CACtB,4BAA4B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,EACtF,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAC3C,CAAA;QACH,CAAC;IACH,CAAC;CACF"}