evm-kms-signer 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,38 +1,41 @@
1
- # kms-signer-evm
1
+ # evm-kms-signer
2
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.
3
+ A TypeScript library that integrates AWS/GCP 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 or GCP KMS, providing enterprise-grade security for your Ethereum operations.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **AWS KMS Integration**: Sign Ethereum transactions using keys securely stored in AWS KMS
8
+ - **GCP KMS Support**: Also supports Google Cloud Platform KMS for multi-cloud deployments
8
9
  - **Full EIP Compliance**: Supports EIP-191 (personal messages), EIP-712 (typed data), EIP-155 (replay protection), EIP-2 (signature normalization)
9
10
  - **Type-Safe**: Built with TypeScript in strict mode with comprehensive type definitions
10
11
  - **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
+ - **DER Signature Parsing**: Automatically converts AWS/GCP KMS DER-encoded signatures to Ethereum format
12
13
  - **Comprehensive Error Handling**: Custom error classes for better debugging
13
- - **Well-Tested**: 51 tests covering all functionality with 100% type safety
14
+ - **Well-Tested**: 169 tests covering all functionality with 100% type safety
14
15
 
15
16
  ## Installation
16
17
 
17
18
  ```bash
18
- pnpm add kms-signer-evm
19
+ pnpm add evm-kms-signer
19
20
  ```
20
21
 
21
22
  Or with npm:
22
23
 
23
24
  ```bash
24
- npm install kms-signer-evm
25
+ npm install evm-kms-signer
25
26
  ```
26
27
 
27
28
  Or with yarn:
28
29
 
29
30
  ```bash
30
- yarn add kms-signer-evm
31
+ yarn add evm-kms-signer
31
32
  ```
32
33
 
33
- ## Prerequisites
34
+ ## Usage
34
35
 
35
- ### AWS KMS Key Setup
36
+ ### AWS KMS
37
+
38
+ #### Prerequisites
36
39
 
37
40
  1. **Create an ECC Key in AWS KMS**:
38
41
  - Go to AWS KMS Console
@@ -50,7 +53,7 @@ yarn add kms-signer-evm
50
53
  3. **Note Your Key ID**:
51
54
  Copy the Key ARN or Key ID for use in your application.
52
55
 
53
- ### Environment Variables
56
+ #### Environment Variables
54
57
 
55
58
  Create a `.env` file in your project root:
56
59
 
@@ -63,13 +66,11 @@ AWS_ACCESS_KEY_ID=your_access_key
63
66
  AWS_SECRET_ACCESS_KEY=your_secret_key
64
67
  ```
65
68
 
66
- ## Quick Start
67
-
68
- ### Signing a Message
69
+ #### Basic Usage
69
70
 
70
71
  ```typescript
71
72
  import 'dotenv/config'
72
- import { KmsSigner, toKmsAccount } from 'kms-signer-evm'
73
+ import { KmsSigner, toKmsAccount } from 'evm-kms-signer'
73
74
 
74
75
  async function main() {
75
76
  // Initialize the KMS signer
@@ -93,13 +94,13 @@ async function main() {
93
94
  main().catch(console.error)
94
95
  ```
95
96
 
96
- ### Sending a Transaction
97
+ #### Use with viem
97
98
 
98
99
  ```typescript
99
100
  import 'dotenv/config'
100
101
  import { createWalletClient, http } from 'viem'
101
102
  import { sepolia } from 'viem/chains'
102
- import { KmsSigner, toKmsAccount } from 'kms-signer-evm'
103
+ import { KmsSigner, toKmsAccount } from 'evm-kms-signer'
103
104
 
104
105
  async function main() {
105
106
  // Initialize the KMS signer
@@ -130,6 +131,63 @@ async function main() {
130
131
  main().catch(console.error)
131
132
  ```
132
133
 
134
+ ### GCP KMS
135
+
136
+ #### Prerequisites
137
+
138
+ 1. **Create a KMS key ring and crypto key in GCP Console**:
139
+ - Go to Google Cloud Console → Security → Key Management
140
+ - Create a new key ring in your desired location
141
+ - Create a crypto key with purpose "Asymmetric sign"
142
+ - Choose **Elliptic Curve P-256 - SHA256 Digest** algorithm (secp256k1 for Ethereum)
143
+
144
+ 2. **Grant Permissions**:
145
+ Grant `roles/cloudkms.cryptoKeySignerVerifier` permission to your service account:
146
+ ```bash
147
+ gcloud kms keys add-iam-policy-binding KEY_ID \
148
+ --location=LOCATION \
149
+ --keyring=KEYRING_ID \
150
+ --member=serviceAccount:SERVICE_ACCOUNT_EMAIL \
151
+ --role=roles/cloudkms.cryptoKeySignerVerifier
152
+ ```
153
+
154
+ 3. **Set up authentication**:
155
+ - Set `GOOGLE_APPLICATION_CREDENTIALS` environment variable pointing to your service account key file, or
156
+ - Pass `keyFilename` in config
157
+
158
+ #### Basic Usage
159
+
160
+ ```typescript
161
+ import { GcpSigner } from 'evm-kms-signer';
162
+
163
+ const signer = new GcpSigner({
164
+ projectId: 'your-project-id',
165
+ locationId: 'global',
166
+ keyRingId: 'your-keyring-id',
167
+ keyId: 'your-key-id',
168
+ keyVersion: '1',
169
+ keyFilename: '/path/to/service-account-key.json', // optional
170
+ });
171
+
172
+ const address = await signer.getAddress();
173
+ const signature = await signer.signMessage({ message: 'Hello!' });
174
+ ```
175
+
176
+ #### Use with viem
177
+
178
+ ```typescript
179
+ import { createWalletClient, http } from 'viem';
180
+ import { mainnet } from 'viem/chains';
181
+ import { toGcpKmsAccount } from 'evm-kms-signer';
182
+
183
+ const account = await toGcpKmsAccount(signer);
184
+ const client = createWalletClient({
185
+ account,
186
+ chain: mainnet,
187
+ transport: http(),
188
+ });
189
+ ```
190
+
133
191
  ## API Documentation
134
192
 
135
193
  ### `KmsSigner`
@@ -302,4 +360,5 @@ Contributions are welcome! Please feel free to submit a Pull Request.
302
360
 
303
361
  - Built with [viem](https://viem.sh) - Modern TypeScript Ethereum library
304
362
  - Uses [AWS SDK for JavaScript v3](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/)
363
+ - Uses [Google Cloud KMS Client Library](https://cloud.google.com/nodejs/docs/reference/kms/latest)
305
364
  - Inspired by the need for secure key management in Ethereum applications
package/dist/account.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { LocalAccount } from 'viem';
2
- import { KmsSigner } from './kms/signer';
2
+ import type { GcpSigner } from './gcp/signer';
3
+ import type { KmsSigner } from './kms/signer';
3
4
  /**
4
5
  * Create a viem Account from KmsSigner.
5
6
  *
@@ -22,4 +23,32 @@ import { KmsSigner } from './kms/signer';
22
23
  * ```
23
24
  */
24
25
  export declare function toKmsAccount(signer: KmsSigner): Promise<LocalAccount>;
26
+ /**
27
+ * Create a viem Account from GcpSigner.
28
+ *
29
+ * Wraps GcpSigner methods to match viem's Account interface,
30
+ * enabling use with viem clients (walletClient, etc.)
31
+ *
32
+ * @param signer - GcpSigner instance
33
+ * @returns viem Account with GCP KMS signing capabilities
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const signer = new GcpSigner({
38
+ * projectId: 'my-project',
39
+ * locationId: 'global',
40
+ * keyRingId: 'my-keyring',
41
+ * keyId: 'my-key',
42
+ * keyVersion: '1'
43
+ * })
44
+ * const account = await toGcpKmsAccount(signer)
45
+ *
46
+ * const client = createWalletClient({
47
+ * account,
48
+ * chain: mainnet,
49
+ * transport: http()
50
+ * })
51
+ * ```
52
+ */
53
+ export declare function toGcpKmsAccount(signer: GcpSigner): Promise<LocalAccount>;
25
54
  //# sourceMappingURL=account.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"account.d.ts","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAe3E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,eAAe,CACpC,MAAM,EAAE,SAAS,GACf,OAAO,CAAC,YAAY,CAAC,CAevB"}
package/dist/account.js CHANGED
@@ -30,7 +30,47 @@ export async function toKmsAccount(signer) {
30
30
  return signer.signMessage({ message: messageStr });
31
31
  },
32
32
  signTransaction: async (transaction, options) => signer.signTransaction(transaction, options),
33
- signTypedData: async (typedData) => signer.signTypedData(typedData)
33
+ signTypedData: async (typedData) => signer.signTypedData(typedData),
34
+ });
35
+ }
36
+ /**
37
+ * Create a viem Account from GcpSigner.
38
+ *
39
+ * Wraps GcpSigner methods to match viem's Account interface,
40
+ * enabling use with viem clients (walletClient, etc.)
41
+ *
42
+ * @param signer - GcpSigner instance
43
+ * @returns viem Account with GCP KMS signing capabilities
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const signer = new GcpSigner({
48
+ * projectId: 'my-project',
49
+ * locationId: 'global',
50
+ * keyRingId: 'my-keyring',
51
+ * keyId: 'my-key',
52
+ * keyVersion: '1'
53
+ * })
54
+ * const account = await toGcpKmsAccount(signer)
55
+ *
56
+ * const client = createWalletClient({
57
+ * account,
58
+ * chain: mainnet,
59
+ * transport: http()
60
+ * })
61
+ * ```
62
+ */
63
+ export async function toGcpKmsAccount(signer) {
64
+ const address = await signer.getAddress();
65
+ return toAccount({
66
+ address,
67
+ signMessage: async ({ message }) => {
68
+ // Convert SignableMessage to string for GcpSigner
69
+ const messageStr = typeof message === 'string' ? message : message.raw.toString();
70
+ return signer.signMessage({ message: messageStr });
71
+ },
72
+ signTransaction: async (transaction, options) => signer.signTransaction(transaction, options),
73
+ signTypedData: async (typedData) => signer.signTypedData(typedData),
34
74
  });
35
75
  }
36
76
  //# sourceMappingURL=account.js.map
@@ -1 +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"}
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../src/account.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI1C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAiB;IACnD,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;IAE1C,OAAO,SAAS,CAAC;QAChB,OAAO;QACP,WAAW,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YAClC,kDAAkD;YAClD,MAAM,UAAU,GACf,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAChE,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,CAC/C,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,OAAO,CAAC;QAC7C,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC;KACnE,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACpC,MAAiB;IAEjB,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC;IAE1C,OAAO,SAAS,CAAC;QAChB,OAAO;QACP,WAAW,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;YAClC,kDAAkD;YAClD,MAAM,UAAU,GACf,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;YAChE,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;QACpD,CAAC;QACD,eAAe,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,CAC/C,MAAM,CAAC,eAAe,CAAC,WAAW,EAAE,OAAO,CAAC;QAC7C,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC;KACnE,CAAC,CAAC;AACJ,CAAC"}
@@ -1 +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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI3B;AAED,qBAAa,eAAgB,SAAQ,cAAc;gBACtC,OAAO,EAAE,MAAM;CAI3B;AAED,qBAAa,cAAe,SAAQ,cAAc;aAGhC,KAAK,CAAC,EAAE,KAAK;gBAD7B,OAAO,EAAE,MAAM,EACC,KAAK,CAAC,EAAE,KAAK,YAAA;CAK9B;AAED,qBAAa,2BAA4B,SAAQ,cAAc;gBAClD,OAAO,EAAE,MAAM;CAI3B;AAED,qBAAa,0BAA2B,SAAQ,cAAc;gBACjD,OAAO,EAAE,MAAM;CAI3B"}
@@ -1 +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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACxC,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC9B,CAAC;CACD;AAED,MAAM,OAAO,eAAgB,SAAQ,cAAc;IAClD,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAC/B,CAAC;CACD;AAED,MAAM,OAAO,cAAe,SAAQ,cAAc;IACjD,YACC,OAAe,EACC,KAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,UAAK,GAAL,KAAK,CAAQ;QAG7B,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC9B,CAAC;CACD;AAED,MAAM,OAAO,2BAA4B,SAAQ,cAAc;IAC9D,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,6BAA6B,CAAC;IAC3C,CAAC;CACD;AAED,MAAM,OAAO,0BAA2B,SAAQ,cAAc;IAC7D,YAAY,OAAe;QAC1B,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;IAC1C,CAAC;CACD"}
@@ -0,0 +1,91 @@
1
+ import type { GcpKmsConfig } from '../types';
2
+ /**
3
+ * GcpClient wraps GCP KMS SDK operations for key management and signing.
4
+ *
5
+ * This class provides a simplified interface to GCP 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 GcpClient({
12
+ * projectId: 'my-project',
13
+ * locationId: 'global',
14
+ * keyRingId: 'my-keyring',
15
+ * keyId: 'my-key',
16
+ * keyVersion: '1'
17
+ * })
18
+ *
19
+ * const publicKey = await client.getPublicKey()
20
+ * const signature = await client.sign(messageHash)
21
+ * ```
22
+ */
23
+ export declare class GcpClient {
24
+ private client;
25
+ private keyName;
26
+ /**
27
+ * Creates a new GCP KMS client instance.
28
+ *
29
+ * @param config - GCP KMS configuration including project, location, key ring, key, and version
30
+ *
31
+ * @remarks
32
+ * If keyFilename is not provided, the GCP SDK will use the default credential provider chain:
33
+ * - GOOGLE_APPLICATION_CREDENTIALS environment variable
34
+ * - Application Default Credentials (ADC)
35
+ * - Service account attached to the compute resource
36
+ */
37
+ constructor(config: GcpKmsConfig);
38
+ /**
39
+ * Retrieves the public key from GCP KMS.
40
+ *
41
+ * @returns The public key in DER-encoded SubjectPublicKeyInfo (SPKI) format
42
+ * @throws {KmsClientError} If the KMS API call fails or returns no public key
43
+ *
44
+ * @remarks
45
+ * GCP KMS returns the public key in PEM format, which is converted to DER format.
46
+ * The returned public key is in SPKI format with a variable-length DER header.
47
+ * The last 65 bytes contain the uncompressed secp256k1 public key (0x04 + x + y).
48
+ */
49
+ getPublicKey(): Promise<Uint8Array>;
50
+ /**
51
+ * Signs a message hash using the GCP KMS-stored private key.
52
+ *
53
+ * @param messageHash - The pre-hashed message to sign (32 bytes for keccak256)
54
+ * @returns The signature in DER-encoded format (SEQUENCE { INTEGER r, INTEGER s })
55
+ * @throws {KmsClientError} If the KMS API call fails or returns no signature
56
+ *
57
+ * @remarks
58
+ * - The messageHash should already be hashed (e.g., with keccak256)
59
+ * - GCP KMS uses EC_SIGN_SECP256K1_SHA256 algorithm for signing
60
+ * - The returned signature is DER-encoded and needs to be parsed to extract r and s values
61
+ */
62
+ sign(messageHash: Uint8Array): Promise<Uint8Array>;
63
+ /**
64
+ * Converts PEM-encoded data to DER-encoded format.
65
+ *
66
+ * @param pem - PEM-encoded public key string
67
+ * @returns DER-encoded public key as Uint8Array
68
+ * @throws {KmsClientError} If PEM format is invalid
69
+ *
70
+ * @remarks
71
+ * PEM format consists of:
72
+ * - Header line: -----BEGIN PUBLIC KEY-----
73
+ * - Base64-encoded DER data
74
+ * - Footer line: -----END PUBLIC KEY-----
75
+ *
76
+ * This method extracts the Base64 data and decodes it to DER format.
77
+ */
78
+ private pemToDer;
79
+ /**
80
+ * Calculates CRC32C checksum for data integrity verification.
81
+ *
82
+ * @param data - Data to calculate checksum for
83
+ * @returns CRC32C checksum as number
84
+ *
85
+ * @remarks
86
+ * GCP KMS requires CRC32C checksums for request/response integrity verification.
87
+ * This is a simple implementation using a lookup table.
88
+ */
89
+ private calculateCrc32c;
90
+ }
91
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/gcp/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,SAAS;IACrB,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,OAAO,CAAS;IAExB;;;;;;;;;;OAUG;gBACS,MAAM,EAAE,YAAY;IAUhC;;;;;;;;;;OAUG;IACG,YAAY,IAAI,OAAO,CAAC,UAAU,CAAC;IAqBzC;;;;;;;;;;;OAWG;IACG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAqDxD;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,QAAQ;IAkBhB;;;;;;;;;OASG;IACH,OAAO,CAAC,eAAe;CAkBvB"}
@@ -0,0 +1,179 @@
1
+ import { KeyManagementServiceClient } from '@google-cloud/kms';
2
+ import { KmsClientError } from '../errors';
3
+ /**
4
+ * GcpClient wraps GCP KMS SDK operations for key management and signing.
5
+ *
6
+ * This class provides a simplified interface to GCP 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 GcpClient({
13
+ * projectId: 'my-project',
14
+ * locationId: 'global',
15
+ * keyRingId: 'my-keyring',
16
+ * keyId: 'my-key',
17
+ * keyVersion: '1'
18
+ * })
19
+ *
20
+ * const publicKey = await client.getPublicKey()
21
+ * const signature = await client.sign(messageHash)
22
+ * ```
23
+ */
24
+ export class GcpClient {
25
+ /**
26
+ * Creates a new GCP KMS client instance.
27
+ *
28
+ * @param config - GCP KMS configuration including project, location, key ring, key, and version
29
+ *
30
+ * @remarks
31
+ * If keyFilename is not provided, the GCP SDK will use the default credential provider chain:
32
+ * - GOOGLE_APPLICATION_CREDENTIALS environment variable
33
+ * - Application Default Credentials (ADC)
34
+ * - Service account attached to the compute resource
35
+ */
36
+ constructor(config) {
37
+ this.client = new KeyManagementServiceClient(config.keyFilename ? { keyFilename: config.keyFilename } : {});
38
+ // Construct the full key resource name
39
+ // Format: projects/{project}/locations/{location}/keyRings/{keyRing}/cryptoKeys/{key}/cryptoKeyVersions/{version}
40
+ this.keyName = `projects/${config.projectId}/locations/${config.locationId}/keyRings/${config.keyRingId}/cryptoKeys/${config.keyId}/cryptoKeyVersions/${config.keyVersion}`;
41
+ }
42
+ /**
43
+ * Retrieves the public key from GCP KMS.
44
+ *
45
+ * @returns The public key in DER-encoded SubjectPublicKeyInfo (SPKI) format
46
+ * @throws {KmsClientError} If the KMS API call fails or returns no public key
47
+ *
48
+ * @remarks
49
+ * GCP KMS returns the public key in PEM format, which is converted to DER format.
50
+ * The returned public key is in SPKI format with a variable-length DER header.
51
+ * The last 65 bytes contain the uncompressed secp256k1 public key (0x04 + x + y).
52
+ */
53
+ async getPublicKey() {
54
+ try {
55
+ const [publicKey] = await this.client.getPublicKey({
56
+ name: this.keyName,
57
+ });
58
+ if (!publicKey.pem) {
59
+ throw new KmsClientError('No public key returned from GCP KMS');
60
+ }
61
+ // Convert PEM to DER format
62
+ return this.pemToDer(publicKey.pem);
63
+ }
64
+ catch (error) {
65
+ if (error instanceof KmsClientError)
66
+ throw error;
67
+ throw new KmsClientError(`Failed to get public key from GCP KMS: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
68
+ }
69
+ }
70
+ /**
71
+ * Signs a message hash using the GCP KMS-stored private key.
72
+ *
73
+ * @param messageHash - The pre-hashed message to sign (32 bytes for keccak256)
74
+ * @returns The signature in DER-encoded format (SEQUENCE { INTEGER r, INTEGER s })
75
+ * @throws {KmsClientError} If the KMS API call fails or returns no signature
76
+ *
77
+ * @remarks
78
+ * - The messageHash should already be hashed (e.g., with keccak256)
79
+ * - GCP KMS uses EC_SIGN_SECP256K1_SHA256 algorithm for signing
80
+ * - The returned signature is DER-encoded and needs to be parsed to extract r and s values
81
+ */
82
+ async sign(messageHash) {
83
+ try {
84
+ // Create CRC32C checksum for message integrity
85
+ const crc32c = this.calculateCrc32c(messageHash);
86
+ const [response] = await this.client.asymmetricSign({
87
+ name: this.keyName,
88
+ digest: {
89
+ sha256: messageHash,
90
+ },
91
+ digestCrc32c: {
92
+ value: crc32c,
93
+ },
94
+ });
95
+ if (!response.signature) {
96
+ throw new KmsClientError('No signature returned from GCP KMS');
97
+ }
98
+ // Convert signature to Uint8Array
99
+ const signatureBytes = typeof response.signature === 'string'
100
+ ? Buffer.from(response.signature, 'base64')
101
+ : response.signature instanceof Buffer
102
+ ? response.signature
103
+ : new Uint8Array(response.signature);
104
+ // Verify the signature CRC32C if provided
105
+ if (response.signatureCrc32c && response.verifiedDigestCrc32c) {
106
+ const signatureCrc32c = this.calculateCrc32c(signatureBytes instanceof Buffer
107
+ ? new Uint8Array(signatureBytes)
108
+ : signatureBytes);
109
+ if (signatureCrc32c !== Number(response.signatureCrc32c.value)) {
110
+ throw new KmsClientError('Signature CRC32C verification failed - data may be corrupted');
111
+ }
112
+ }
113
+ return signatureBytes instanceof Buffer
114
+ ? new Uint8Array(signatureBytes)
115
+ : signatureBytes;
116
+ }
117
+ catch (error) {
118
+ if (error instanceof KmsClientError)
119
+ throw error;
120
+ throw new KmsClientError(`Failed to sign with GCP KMS: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
121
+ }
122
+ }
123
+ /**
124
+ * Converts PEM-encoded data to DER-encoded format.
125
+ *
126
+ * @param pem - PEM-encoded public key string
127
+ * @returns DER-encoded public key as Uint8Array
128
+ * @throws {KmsClientError} If PEM format is invalid
129
+ *
130
+ * @remarks
131
+ * PEM format consists of:
132
+ * - Header line: -----BEGIN PUBLIC KEY-----
133
+ * - Base64-encoded DER data
134
+ * - Footer line: -----END PUBLIC KEY-----
135
+ *
136
+ * This method extracts the Base64 data and decodes it to DER format.
137
+ */
138
+ pemToDer(pem) {
139
+ try {
140
+ // Remove PEM header, footer, and whitespace
141
+ const base64 = pem
142
+ .replace(/-----BEGIN PUBLIC KEY-----/, '')
143
+ .replace(/-----END PUBLIC KEY-----/, '')
144
+ .replace(/\s/g, '');
145
+ // Decode Base64 to DER
146
+ return Uint8Array.from(Buffer.from(base64, 'base64'));
147
+ }
148
+ catch (error) {
149
+ throw new KmsClientError(`Failed to convert PEM to DER: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined);
150
+ }
151
+ }
152
+ /**
153
+ * Calculates CRC32C checksum for data integrity verification.
154
+ *
155
+ * @param data - Data to calculate checksum for
156
+ * @returns CRC32C checksum as number
157
+ *
158
+ * @remarks
159
+ * GCP KMS requires CRC32C checksums for request/response integrity verification.
160
+ * This is a simple implementation using a lookup table.
161
+ */
162
+ calculateCrc32c(data) {
163
+ // CRC32C polynomial lookup table
164
+ const crc32cTable = new Int32Array(256);
165
+ for (let i = 0; i < 256; i++) {
166
+ let c = i;
167
+ for (let j = 0; j < 8; j++) {
168
+ c = c & 1 ? 0x82f63b78 ^ (c >>> 1) : c >>> 1;
169
+ }
170
+ crc32cTable[i] = c;
171
+ }
172
+ let crc = 0xffffffff;
173
+ for (let i = 0; i < data.length; i++) {
174
+ crc = crc32cTable[(crc ^ data[i]) & 0xff] ^ (crc >>> 8);
175
+ }
176
+ return (crc ^ 0xffffffff) >>> 0;
177
+ }
178
+ }
179
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/gcp/client.ts"],"names":[],"mappings":"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"}