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
|
@@ -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 @@
|
|
|
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"}
|