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 +21 -0
- package/README.md +305 -0
- package/dist/account.d.ts +25 -0
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +36 -0
- package/dist/account.js.map +1 -0
- package/dist/errors/index.d.ts +17 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +32 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -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 +98 -0
- package/dist/kms/client.js.map +1 -0
- package/dist/kms/signer.d.ts +192 -0
- package/dist/kms/signer.d.ts.map +1 -0
- package/dist/kms/signer.js +271 -0
- package/dist/kms/signer.js.map +1 -0
- package/dist/types/index.d.ts +56 -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/address.d.ts +29 -0
- package/dist/utils/address.d.ts.map +1 -0
- package/dist/utils/address.js +60 -0
- package/dist/utils/address.js.map +1 -0
- package/dist/utils/der.d.ts +15 -0
- package/dist/utils/der.d.ts.map +1 -0
- package/dist/utils/der.js +100 -0
- package/dist/utils/der.js.map +1 -0
- package/dist/utils/signature.d.ts +95 -0
- package/dist/utils/signature.d.ts.map +1 -0
- package/dist/utils/signature.js +153 -0
- package/dist/utils/signature.js.map +1 -0
- package/package.json +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,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"}
|
package/dist/account.js
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|