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.
@@ -0,0 +1,192 @@
1
+ import type { Address, Hex, TransactionSerializable, SerializeTransactionFn, TypedDataDefinition } from 'viem';
2
+ import type { TypedData } from 'abitype';
3
+ import type { KmsConfig } from '../types';
4
+ /**
5
+ * KmsSigner provides Ethereum signing capabilities using AWS KMS.
6
+ *
7
+ * This class manages the interaction with AWS KMS for cryptographic operations
8
+ * required by Ethereum accounts:
9
+ * - Public key retrieval and caching
10
+ * - Ethereum address derivation from KMS public key
11
+ * - Message and transaction signing (to be implemented in Part 2)
12
+ *
13
+ * The signer caches expensive operations (public key retrieval, address derivation)
14
+ * to avoid unnecessary KMS API calls.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const signer = new KmsSigner({
19
+ * region: 'us-east-1',
20
+ * keyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
21
+ * })
22
+ *
23
+ * const address = await signer.getAddress()
24
+ * console.log('Ethereum address:', address)
25
+ * ```
26
+ */
27
+ export declare class KmsSigner {
28
+ private kmsClient;
29
+ private keyId;
30
+ private cachedAddress?;
31
+ private cachedPublicKey?;
32
+ /**
33
+ * Creates a new KMS signer instance.
34
+ *
35
+ * @param config - KMS configuration including region, keyId, and optional credentials
36
+ *
37
+ * @remarks
38
+ * The constructor initializes the KMS client but does not make any API calls.
39
+ * Public key retrieval and address derivation happen lazily on first use.
40
+ */
41
+ constructor(config: KmsConfig);
42
+ /**
43
+ * Retrieves the uncompressed secp256k1 public key from AWS KMS.
44
+ *
45
+ * The public key is retrieved from KMS and extracted from the DER-encoded
46
+ * SubjectPublicKeyInfo format. The result is cached to avoid redundant KMS calls.
47
+ *
48
+ * @returns 65-byte uncompressed public key (0x04 + x coordinate + y coordinate)
49
+ * @throws {KmsClientError} If KMS API call fails
50
+ * @throws {DerParsingError} If public key format is invalid
51
+ *
52
+ * @remarks
53
+ * The public key format is:
54
+ * - Byte 0: 0x04 (uncompressed point indicator)
55
+ * - Bytes 1-32: x coordinate of the public key
56
+ * - Bytes 33-64: y coordinate of the public key
57
+ */
58
+ getPublicKey(): Promise<Uint8Array>;
59
+ /**
60
+ * Derives the Ethereum address from the KMS public key.
61
+ *
62
+ * The address is calculated by:
63
+ * 1. Retrieving the public key from KMS (cached if available)
64
+ * 2. Hashing the public key coordinates with keccak256
65
+ * 3. Taking the last 20 bytes as the address
66
+ *
67
+ * The result is cached to avoid redundant derivation.
68
+ *
69
+ * @returns Ethereum address (0x-prefixed, 40 hex characters)
70
+ * @throws {KmsClientError} If KMS API call fails
71
+ * @throws {DerParsingError} If public key format is invalid
72
+ *
73
+ * @remarks
74
+ * The returned address follows EIP-55 checksum encoding.
75
+ */
76
+ getAddress(): Promise<Address>;
77
+ /**
78
+ * Signs a hash using the KMS private key (internal helper method).
79
+ *
80
+ * This method is used internally by signMessage, signTransaction, and signTypedData.
81
+ * It converts the hash to bytes, signs with KMS, parses the DER signature,
82
+ * and normalizes the s value according to EIP-2.
83
+ *
84
+ * @param hash - The hash to sign (32 bytes, hex-encoded)
85
+ * @returns Object containing r and s as bigints
86
+ * @throws {KmsClientError} If KMS API call fails
87
+ * @throws {DerParsingError} If signature format is invalid
88
+ * @throws {SignatureNormalizationError} If s value is out of valid range
89
+ *
90
+ * @remarks
91
+ * The s value is automatically normalized to the lower half of the curve order (EIP-2)
92
+ * to prevent signature malleability attacks.
93
+ */
94
+ private signHash;
95
+ /**
96
+ * Signs a message using EIP-191 personal_sign standard.
97
+ *
98
+ * This method:
99
+ * 1. Hashes the message with EIP-191 prefix: "\x19Ethereum Signed Message:\n" + len(message) + message
100
+ * 2. Signs the hash with KMS
101
+ * 3. Calculates the recovery ID to enable public key recovery
102
+ * 4. Returns the signature in the standard format: r (32 bytes) + s (32 bytes) + v (1 byte)
103
+ *
104
+ * @param params - Object containing the message string
105
+ * @returns The signature as a hex string (0x-prefixed, 130 characters)
106
+ * @throws {KmsClientError} If KMS API call fails
107
+ * @throws {DerParsingError} If signature format is invalid
108
+ * @throws {RecoveryIdCalculationError} If recovery ID calculation fails
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * const signer = new KmsSigner({ region: 'us-east-1', keyId: 'arn:...' })
113
+ * const signature = await signer.signMessage({ message: 'Hello, world!' })
114
+ * // signature: '0x...' (130 characters: 0x + 64 hex chars for r + 64 for s + 2 for v)
115
+ * ```
116
+ */
117
+ signMessage({ message }: {
118
+ message: string;
119
+ }): Promise<Hex>;
120
+ /**
121
+ * Signs an Ethereum transaction.
122
+ *
123
+ * This method:
124
+ * 1. Serializes the transaction without signature fields (r, s, v)
125
+ * 2. Hashes the serialized transaction with keccak256
126
+ * 3. Signs the hash with KMS
127
+ * 4. Calculates the recovery ID
128
+ * 5. Computes the v value (EIP-155 if chainId present, legacy otherwise)
129
+ * 6. Returns the fully serialized transaction with signature
130
+ *
131
+ * @param transaction - The transaction to sign
132
+ * @param options - Optional serializer function (defaults to viem's serializeTransaction)
133
+ * @returns The serialized signed transaction as a hex string
134
+ * @throws {KmsClientError} If KMS API call fails
135
+ * @throws {DerParsingError} If signature format is invalid
136
+ * @throws {RecoveryIdCalculationError} If recovery ID calculation fails
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * const signer = new KmsSigner({ region: 'us-east-1', keyId: 'arn:...' })
141
+ * const signedTx = await signer.signTransaction({
142
+ * to: '0x...',
143
+ * value: parseEther('1'),
144
+ * chainId: 1
145
+ * })
146
+ * ```
147
+ */
148
+ signTransaction(transaction: TransactionSerializable, { serializer }?: {
149
+ serializer?: SerializeTransactionFn;
150
+ }): Promise<Hex>;
151
+ /**
152
+ * Signs typed data according to EIP-712.
153
+ *
154
+ * This method:
155
+ * 1. Hashes the typed data using EIP-712 (domain separator + type hash)
156
+ * 2. Signs the hash with KMS
157
+ * 3. Calculates the recovery ID
158
+ * 4. Returns the signature in the standard format: r (32 bytes) + s (32 bytes) + v (1 byte)
159
+ *
160
+ * @param typedData - The EIP-712 typed data to sign
161
+ * @returns The signature as a hex string (0x-prefixed, 130 characters)
162
+ * @throws {KmsClientError} If KMS API call fails
163
+ * @throws {DerParsingError} If signature format is invalid
164
+ * @throws {RecoveryIdCalculationError} If recovery ID calculation fails
165
+ *
166
+ * @example
167
+ * ```typescript
168
+ * const signer = new KmsSigner({ region: 'us-east-1', keyId: 'arn:...' })
169
+ * const signature = await signer.signTypedData({
170
+ * domain: {
171
+ * name: 'MyApp',
172
+ * version: '1',
173
+ * chainId: 1,
174
+ * verifyingContract: '0x...'
175
+ * },
176
+ * types: {
177
+ * Person: [
178
+ * { name: 'name', type: 'string' },
179
+ * { name: 'wallet', type: 'address' }
180
+ * ]
181
+ * },
182
+ * primaryType: 'Person',
183
+ * message: {
184
+ * name: 'Alice',
185
+ * wallet: '0x...'
186
+ * }
187
+ * })
188
+ * ```
189
+ */
190
+ signTypedData<const TTypedData extends TypedData | Record<string, unknown>, TPrimaryType extends keyof TTypedData | 'EIP712Domain' = keyof TTypedData>(typedData: TypedDataDefinition<TTypedData, TPrimaryType>): Promise<Hex>;
191
+ }
192
+ //# sourceMappingURL=signer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer.d.ts","sourceRoot":"","sources":["../../src/kms/signer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,MAAM,CAAA;AAC9G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAMxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAEzC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,SAAS,CAAW;IAC5B,OAAO,CAAC,KAAK,CAAQ;IACrB,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,eAAe,CAAC,CAAY;IAEpC;;;;;;;;OAQG;gBACS,MAAM,EAAE,SAAS;IAK7B;;;;;;;;;;;;;;;OAeG;IACG,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;IAWzC;;;;;;;;;;;;;;;;OAgBG;IACG,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAWpC;;;;;;;;;;;;;;;;OAgBG;YACW,QAAQ;IAoBtB;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,CAAC;IA2BjE;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACG,eAAe,CACnB,WAAW,EAAE,uBAAuB,EACpC,EAAE,UAAiC,EAAE,GAAE;QAAE,UAAU,CAAC,EAAE,sBAAsB,CAAA;KAAO,GAClF,OAAO,CAAC,GAAG,CAAC;IAgCf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsCG;IACG,aAAa,CACjB,KAAK,CAAC,UAAU,SAAS,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,YAAY,SAAS,MAAM,UAAU,GAAG,cAAc,GAAG,MAAM,UAAU,EACzE,SAAS,EAAE,mBAAmB,CAAC,UAAU,EAAE,YAAY,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;CA0B1E"}
@@ -0,0 +1,271 @@
1
+ import { fromHex, toHex, hashMessage, concat, keccak256, serializeTransaction, hashTypedData } from 'viem';
2
+ import { KmsClient } from './client';
3
+ import { extractPublicKeyFromDer, publicKeyToAddress } from '../utils/address';
4
+ import { parseDerSignature } from '../utils/der';
5
+ import { normalizeS, calculateRecoveryId, uint8ArrayToBigInt } from '../utils/signature';
6
+ /**
7
+ * KmsSigner provides Ethereum signing capabilities using AWS KMS.
8
+ *
9
+ * This class manages the interaction with AWS KMS for cryptographic operations
10
+ * required by Ethereum accounts:
11
+ * - Public key retrieval and caching
12
+ * - Ethereum address derivation from KMS public key
13
+ * - Message and transaction signing (to be implemented in Part 2)
14
+ *
15
+ * The signer caches expensive operations (public key retrieval, address derivation)
16
+ * to avoid unnecessary KMS API calls.
17
+ *
18
+ * @example
19
+ * ```typescript
20
+ * const signer = new KmsSigner({
21
+ * region: 'us-east-1',
22
+ * keyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012'
23
+ * })
24
+ *
25
+ * const address = await signer.getAddress()
26
+ * console.log('Ethereum address:', address)
27
+ * ```
28
+ */
29
+ export class KmsSigner {
30
+ /**
31
+ * Creates a new KMS signer instance.
32
+ *
33
+ * @param config - KMS configuration including region, keyId, and optional credentials
34
+ *
35
+ * @remarks
36
+ * The constructor initializes the KMS client but does not make any API calls.
37
+ * Public key retrieval and address derivation happen lazily on first use.
38
+ */
39
+ constructor(config) {
40
+ this.kmsClient = new KmsClient(config);
41
+ this.keyId = config.keyId;
42
+ }
43
+ /**
44
+ * Retrieves the uncompressed secp256k1 public key from AWS KMS.
45
+ *
46
+ * The public key is retrieved from KMS and extracted from the DER-encoded
47
+ * SubjectPublicKeyInfo format. The result is cached to avoid redundant KMS calls.
48
+ *
49
+ * @returns 65-byte uncompressed public key (0x04 + x coordinate + y coordinate)
50
+ * @throws {KmsClientError} If KMS API call fails
51
+ * @throws {DerParsingError} If public key format is invalid
52
+ *
53
+ * @remarks
54
+ * The public key format is:
55
+ * - Byte 0: 0x04 (uncompressed point indicator)
56
+ * - Bytes 1-32: x coordinate of the public key
57
+ * - Bytes 33-64: y coordinate of the public key
58
+ */
59
+ async getPublicKey() {
60
+ if (this.cachedPublicKey) {
61
+ return this.cachedPublicKey;
62
+ }
63
+ const derPublicKey = await this.kmsClient.getPublicKey();
64
+ const publicKey = extractPublicKeyFromDer(derPublicKey);
65
+ this.cachedPublicKey = publicKey;
66
+ return publicKey;
67
+ }
68
+ /**
69
+ * Derives the Ethereum address from the KMS public key.
70
+ *
71
+ * The address is calculated by:
72
+ * 1. Retrieving the public key from KMS (cached if available)
73
+ * 2. Hashing the public key coordinates with keccak256
74
+ * 3. Taking the last 20 bytes as the address
75
+ *
76
+ * The result is cached to avoid redundant derivation.
77
+ *
78
+ * @returns Ethereum address (0x-prefixed, 40 hex characters)
79
+ * @throws {KmsClientError} If KMS API call fails
80
+ * @throws {DerParsingError} If public key format is invalid
81
+ *
82
+ * @remarks
83
+ * The returned address follows EIP-55 checksum encoding.
84
+ */
85
+ async getAddress() {
86
+ if (this.cachedAddress) {
87
+ return this.cachedAddress;
88
+ }
89
+ const publicKey = await this.getPublicKey();
90
+ const address = publicKeyToAddress(publicKey);
91
+ this.cachedAddress = address;
92
+ return address;
93
+ }
94
+ /**
95
+ * Signs a hash using the KMS private key (internal helper method).
96
+ *
97
+ * This method is used internally by signMessage, signTransaction, and signTypedData.
98
+ * It converts the hash to bytes, signs with KMS, parses the DER signature,
99
+ * and normalizes the s value according to EIP-2.
100
+ *
101
+ * @param hash - The hash to sign (32 bytes, hex-encoded)
102
+ * @returns Object containing r and s as bigints
103
+ * @throws {KmsClientError} If KMS API call fails
104
+ * @throws {DerParsingError} If signature format is invalid
105
+ * @throws {SignatureNormalizationError} If s value is out of valid range
106
+ *
107
+ * @remarks
108
+ * The s value is automatically normalized to the lower half of the curve order (EIP-2)
109
+ * to prevent signature malleability attacks.
110
+ */
111
+ async signHash(hash) {
112
+ // Convert Hex to Uint8Array
113
+ const hashBytes = fromHex(hash, 'bytes');
114
+ // Sign with KMS
115
+ const derSignature = await this.kmsClient.sign(hashBytes);
116
+ // Parse DER signature
117
+ const { r: rBytes, s: sBytes } = parseDerSignature(derSignature);
118
+ // Convert to bigint
119
+ let r = uint8ArrayToBigInt(rBytes);
120
+ let s = uint8ArrayToBigInt(sBytes);
121
+ // EIP-2 normalization
122
+ s = normalizeS(s);
123
+ return { r, s };
124
+ }
125
+ /**
126
+ * Signs a message using EIP-191 personal_sign standard.
127
+ *
128
+ * This method:
129
+ * 1. Hashes the message with EIP-191 prefix: "\x19Ethereum Signed Message:\n" + len(message) + message
130
+ * 2. Signs the hash with KMS
131
+ * 3. Calculates the recovery ID to enable public key recovery
132
+ * 4. Returns the signature in the standard format: r (32 bytes) + s (32 bytes) + v (1 byte)
133
+ *
134
+ * @param params - Object containing the message string
135
+ * @returns The signature as a hex string (0x-prefixed, 130 characters)
136
+ * @throws {KmsClientError} If KMS API call fails
137
+ * @throws {DerParsingError} If signature format is invalid
138
+ * @throws {RecoveryIdCalculationError} If recovery ID calculation fails
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const signer = new KmsSigner({ region: 'us-east-1', keyId: 'arn:...' })
143
+ * const signature = await signer.signMessage({ message: 'Hello, world!' })
144
+ * // signature: '0x...' (130 characters: 0x + 64 hex chars for r + 64 for s + 2 for v)
145
+ * ```
146
+ */
147
+ async signMessage({ message }) {
148
+ // EIP-191 hashing (viem handles automatically)
149
+ const messageHash = hashMessage(message);
150
+ // Sign with KMS
151
+ const { r, s } = await this.signHash(messageHash);
152
+ // Calculate recovery ID
153
+ const address = await this.getAddress();
154
+ const recoveryId = await calculateRecoveryId(messageHash, toHex(r, { size: 32 }), toHex(s, { size: 32 }), address);
155
+ // Calculate v value (Legacy, no chain)
156
+ const v = 27 + recoveryId;
157
+ // Serialize signature
158
+ return concat([
159
+ toHex(r, { size: 32 }),
160
+ toHex(s, { size: 32 }),
161
+ toHex(v, { size: 1 })
162
+ ]);
163
+ }
164
+ /**
165
+ * Signs an Ethereum transaction.
166
+ *
167
+ * This method:
168
+ * 1. Serializes the transaction without signature fields (r, s, v)
169
+ * 2. Hashes the serialized transaction with keccak256
170
+ * 3. Signs the hash with KMS
171
+ * 4. Calculates the recovery ID
172
+ * 5. Computes the v value (EIP-155 if chainId present, legacy otherwise)
173
+ * 6. Returns the fully serialized transaction with signature
174
+ *
175
+ * @param transaction - The transaction to sign
176
+ * @param options - Optional serializer function (defaults to viem's serializeTransaction)
177
+ * @returns The serialized signed transaction as a hex string
178
+ * @throws {KmsClientError} If KMS API call fails
179
+ * @throws {DerParsingError} If signature format is invalid
180
+ * @throws {RecoveryIdCalculationError} If recovery ID calculation fails
181
+ *
182
+ * @example
183
+ * ```typescript
184
+ * const signer = new KmsSigner({ region: 'us-east-1', keyId: 'arn:...' })
185
+ * const signedTx = await signer.signTransaction({
186
+ * to: '0x...',
187
+ * value: parseEther('1'),
188
+ * chainId: 1
189
+ * })
190
+ * ```
191
+ */
192
+ async signTransaction(transaction, { serializer = serializeTransaction } = {}) {
193
+ // Serialize transaction for signing (without r, s, v)
194
+ const serializedTx = serializeTransaction({ ...transaction, r: undefined, s: undefined, v: undefined });
195
+ const hash = keccak256(serializedTx);
196
+ // Sign with KMS
197
+ const { r, s } = await this.signHash(hash);
198
+ // Calculate recovery ID
199
+ const address = await this.getAddress();
200
+ const recoveryId = await calculateRecoveryId(hash, toHex(r, { size: 32 }), toHex(s, { size: 32 }), address);
201
+ // Calculate v value
202
+ const chainId = transaction.chainId;
203
+ const v = chainId
204
+ ? BigInt(chainId * 2 + 35 + recoveryId) // EIP-155
205
+ : BigInt(27 + recoveryId); // Legacy
206
+ // Final serialization with signature
207
+ return serializer({
208
+ ...transaction,
209
+ r: toHex(r, { size: 32 }),
210
+ s: toHex(s, { size: 32 }),
211
+ v
212
+ });
213
+ }
214
+ /**
215
+ * Signs typed data according to EIP-712.
216
+ *
217
+ * This method:
218
+ * 1. Hashes the typed data using EIP-712 (domain separator + type hash)
219
+ * 2. Signs the hash with KMS
220
+ * 3. Calculates the recovery ID
221
+ * 4. Returns the signature in the standard format: r (32 bytes) + s (32 bytes) + v (1 byte)
222
+ *
223
+ * @param typedData - The EIP-712 typed data to sign
224
+ * @returns The signature as a hex string (0x-prefixed, 130 characters)
225
+ * @throws {KmsClientError} If KMS API call fails
226
+ * @throws {DerParsingError} If signature format is invalid
227
+ * @throws {RecoveryIdCalculationError} If recovery ID calculation fails
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const signer = new KmsSigner({ region: 'us-east-1', keyId: 'arn:...' })
232
+ * const signature = await signer.signTypedData({
233
+ * domain: {
234
+ * name: 'MyApp',
235
+ * version: '1',
236
+ * chainId: 1,
237
+ * verifyingContract: '0x...'
238
+ * },
239
+ * types: {
240
+ * Person: [
241
+ * { name: 'name', type: 'string' },
242
+ * { name: 'wallet', type: 'address' }
243
+ * ]
244
+ * },
245
+ * primaryType: 'Person',
246
+ * message: {
247
+ * name: 'Alice',
248
+ * wallet: '0x...'
249
+ * }
250
+ * })
251
+ * ```
252
+ */
253
+ async signTypedData(typedData) {
254
+ // EIP-712 hashing (viem handles domain separator and type hash)
255
+ const hash = hashTypedData(typedData);
256
+ // Sign with KMS
257
+ const { r, s } = await this.signHash(hash);
258
+ // Calculate recovery ID
259
+ const address = await this.getAddress();
260
+ const recoveryId = await calculateRecoveryId(hash, toHex(r, { size: 32 }), toHex(s, { size: 32 }), address);
261
+ // Calculate v value (Legacy, no chain for typed data)
262
+ const v = 27 + recoveryId;
263
+ // Serialize signature
264
+ return concat([
265
+ toHex(r, { size: 32 }),
266
+ toHex(s, { size: 32 }),
267
+ toHex(v, { size: 1 })
268
+ ]);
269
+ }
270
+ }
271
+ //# sourceMappingURL=signer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/kms/signer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * AWS KMS configuration for signing operations
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
11
+ */
12
+ keyId: string;
13
+ /**
14
+ * Optional AWS credentials. If not provided, AWS SDK will use the default credential chain
15
+ * (environment variables, IAM roles, etc.)
16
+ */
17
+ credentials?: {
18
+ accessKeyId: string;
19
+ secretAccessKey: string;
20
+ };
21
+ }
22
+ /**
23
+ * DER-parsed signature as raw byte arrays
24
+ * Used internally by DER parsing utilities
25
+ */
26
+ export interface DerSignature {
27
+ /**
28
+ * r component of ECDSA signature (32 bytes)
29
+ */
30
+ r: Uint8Array;
31
+ /**
32
+ * s component of ECDSA signature (32 bytes)
33
+ */
34
+ s: Uint8Array;
35
+ }
36
+ /**
37
+ * Ethereum signature with recovery ID
38
+ * Used for final signature serialization
39
+ */
40
+ export interface SignatureData {
41
+ /**
42
+ * r component of ECDSA signature as bigint
43
+ */
44
+ r: bigint;
45
+ /**
46
+ * s component of ECDSA signature as bigint (after EIP-2 normalization)
47
+ */
48
+ s: bigint;
49
+ /**
50
+ * Recovery ID as bigint
51
+ * - 27-28 for legacy signatures
52
+ * - 35+ for EIP-155 signatures (includes chain ID)
53
+ */
54
+ v: bigint;
55
+ }
56
+ //# 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,CAAA;IAEd;;OAEG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb;;;OAGG;IACH,WAAW,CAAC,EAAE;QACZ,WAAW,EAAE,MAAM,CAAA;QACnB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,CAAC,EAAE,UAAU,CAAA;IAEb;;OAEG;IACH,CAAC,EAAE,UAAU,CAAA;CACd;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,CAAC,EAAE,MAAM,CAAA;IAET;;OAEG;IACH,CAAC,EAAE,MAAM,CAAA;IAET;;;;OAIG;IACH,CAAC,EAAE,MAAM,CAAA;CACV"}
@@ -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,29 @@
1
+ import { type Address } from 'viem';
2
+ /**
3
+ * Extract uncompressed public key from DER-encoded SubjectPublicKeyInfo
4
+ *
5
+ * AWS KMS returns public keys in SubjectPublicKeyInfo (SPKI) format:
6
+ * SEQUENCE (algorithm identifier + public key bit string)
7
+ *
8
+ * For secp256k1, the last 65 bytes are the uncompressed public key:
9
+ * 0x04 (uncompressed marker) + 32-byte x coordinate + 32-byte y coordinate
10
+ *
11
+ * @param der - DER-encoded public key from AWS KMS GetPublicKeyCommand
12
+ * @returns 65-byte uncompressed public key (0x04 + x + y)
13
+ * @throws DerParsingError if public key format is invalid
14
+ */
15
+ export declare function extractPublicKeyFromDer(der: Uint8Array): Uint8Array;
16
+ /**
17
+ * Convert uncompressed public key to Ethereum address
18
+ *
19
+ * Ethereum address derivation:
20
+ * 1. Remove 0x04 prefix from public key (keep only x and y coordinates)
21
+ * 2. Hash the 64-byte coordinate data with keccak256
22
+ * 3. Take the last 20 bytes of the hash
23
+ * 4. Format as 0x-prefixed hex string (checksummed)
24
+ *
25
+ * @param publicKey - 65-byte uncompressed public key (0x04 + x + y)
26
+ * @returns Ethereum address (0x-prefixed, 40 hex chars)
27
+ */
28
+ export declare function publicKeyToAddress(publicKey: Uint8Array): Address;
29
+ //# sourceMappingURL=address.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../../src/utils/address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,OAAO,EAAE,MAAM,MAAM,CAAA;AAGrD;;;;;;;;;;;;GAYG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,UAAU,GAAG,UAAU,CAuBnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,UAAU,GAAG,OAAO,CAsBjE"}
@@ -0,0 +1,60 @@
1
+ import { keccak256, toHex } from 'viem';
2
+ import { DerParsingError } from '../errors';
3
+ /**
4
+ * Extract uncompressed public key from DER-encoded SubjectPublicKeyInfo
5
+ *
6
+ * AWS KMS returns public keys in SubjectPublicKeyInfo (SPKI) format:
7
+ * SEQUENCE (algorithm identifier + public key bit string)
8
+ *
9
+ * For secp256k1, the last 65 bytes are the uncompressed public key:
10
+ * 0x04 (uncompressed marker) + 32-byte x coordinate + 32-byte y coordinate
11
+ *
12
+ * @param der - DER-encoded public key from AWS KMS GetPublicKeyCommand
13
+ * @returns 65-byte uncompressed public key (0x04 + x + y)
14
+ * @throws DerParsingError if public key format is invalid
15
+ */
16
+ export function extractPublicKeyFromDer(der) {
17
+ // Validate minimum length
18
+ if (der.length === 0) {
19
+ throw new DerParsingError('Invalid DER: empty buffer');
20
+ }
21
+ if (der.length < 65) {
22
+ throw new DerParsingError(`Invalid DER: buffer too short (expected at least 65 bytes, got ${der.length})`);
23
+ }
24
+ // Simple implementation: last 65 bytes are the public key
25
+ // (0x04 + x coordinate 32 bytes + y coordinate 32 bytes)
26
+ const publicKey = der.slice(-65);
27
+ if (publicKey[0] !== 0x04) {
28
+ throw new DerParsingError(`Invalid public key format: expected uncompressed (0x04), got 0x${publicKey[0].toString(16).padStart(2, '0')}`);
29
+ }
30
+ return publicKey;
31
+ }
32
+ /**
33
+ * Convert uncompressed public key to Ethereum address
34
+ *
35
+ * Ethereum address derivation:
36
+ * 1. Remove 0x04 prefix from public key (keep only x and y coordinates)
37
+ * 2. Hash the 64-byte coordinate data with keccak256
38
+ * 3. Take the last 20 bytes of the hash
39
+ * 4. Format as 0x-prefixed hex string (checksummed)
40
+ *
41
+ * @param publicKey - 65-byte uncompressed public key (0x04 + x + y)
42
+ * @returns Ethereum address (0x-prefixed, 40 hex chars)
43
+ */
44
+ export function publicKeyToAddress(publicKey) {
45
+ // Validate public key length
46
+ if (publicKey.length === 0) {
47
+ throw new DerParsingError('Invalid public key: empty buffer');
48
+ }
49
+ if (publicKey.length < 65) {
50
+ throw new DerParsingError(`Invalid public key: expected at least 65 bytes, got ${publicKey.length}`);
51
+ }
52
+ // Remove 0x04 prefix, hash only x and y coordinates
53
+ const publicKeyWithoutPrefix = publicKey.slice(1);
54
+ // Calculate keccak256 hash
55
+ const hash = keccak256(toHex(publicKeyWithoutPrefix));
56
+ // Last 20 bytes = Ethereum address
57
+ const address = `0x${hash.slice(-40)}`;
58
+ return address;
59
+ }
60
+ //# sourceMappingURL=address.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.js","sourceRoot":"","sources":["../../src/utils/address.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAgB,MAAM,MAAM,CAAA;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAA;AAE3C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAe;IACrD,0BAA0B;IAC1B,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,MAAM,IAAI,eAAe,CAAC,2BAA2B,CAAC,CAAA;IACxD,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACpB,MAAM,IAAI,eAAe,CACvB,kEAAkE,GAAG,CAAC,MAAM,GAAG,CAChF,CAAA;IACH,CAAC;IAED,0DAA0D;IAC1D,yDAAyD;IACzD,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAA;IAEhC,IAAI,SAAS,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1B,MAAM,IAAI,eAAe,CACvB,kEAAkE,SAAS,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAC/G,CAAA;IACH,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAqB;IACtD,6BAA6B;IAC7B,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,eAAe,CAAC,kCAAkC,CAAC,CAAA;IAC/D,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QAC1B,MAAM,IAAI,eAAe,CACvB,uDAAuD,SAAS,CAAC,MAAM,EAAE,CAC1E,CAAA;IACH,CAAC;IAED,oDAAoD;IACpD,MAAM,sBAAsB,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAEjD,2BAA2B;IAC3B,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAA;IAErD,mCAAmC;IACnC,MAAM,OAAO,GAAG,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAa,CAAA;IAEjD,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { DerSignature } from '../types';
2
+ /**
3
+ * Parse DER-encoded ECDSA signature into r and s components
4
+ *
5
+ * DER structure:
6
+ * SEQUENCE (0x30) [total_length]
7
+ * INTEGER (0x02) [r_length] [r_bytes]
8
+ * INTEGER (0x02) [s_length] [s_bytes]
9
+ *
10
+ * @param der - DER-encoded signature from AWS KMS
11
+ * @returns Object with r and s as 32-byte Uint8Arrays
12
+ * @throws DerParsingError if signature format is invalid
13
+ */
14
+ export declare function parseDerSignature(der: Uint8Array): DerSignature;
15
+ //# sourceMappingURL=der.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"der.d.ts","sourceRoot":"","sources":["../../src/utils/der.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAG5C;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,GAAG,YAAY,CA0G/D"}