@solana/keychain-gcp-kms 0.0.0 → 0.4.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 +103 -0
- package/dist/__tests__/gcp-kms-signer.integration.test.d.ts +2 -0
- package/dist/__tests__/gcp-kms-signer.integration.test.d.ts.map +1 -0
- package/dist/__tests__/gcp-kms-signer.integration.test.js +49 -0
- package/dist/__tests__/gcp-kms-signer.integration.test.js.map +1 -0
- package/dist/__tests__/gcp-kms-signer.test.d.ts +2 -0
- package/dist/__tests__/gcp-kms-signer.test.d.ts.map +1 -0
- package/dist/__tests__/gcp-kms-signer.test.js +286 -0
- package/dist/__tests__/gcp-kms-signer.test.js.map +1 -0
- package/dist/gcp-kms-signer.d.ts +53 -0
- package/dist/gcp-kms-signer.d.ts.map +1 -0
- package/dist/gcp-kms-signer.js +161 -0
- package/dist/gcp-kms-signer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -8
- package/src/__tests__/gcp-kms-signer.integration.test.ts +82 -0
- package/src/__tests__/gcp-kms-signer.test.ts +361 -0
- package/src/gcp-kms-signer.ts +191 -0
- package/src/index.ts +2 -0
- package/src/types.ts +11 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { v1 } from '@google-cloud/kms';
|
|
2
|
+
import { assertIsAddress } from '@solana/addresses';
|
|
3
|
+
import { createSignatureDictionary, SignerErrorCode, throwSignerError } from '@solana/keychain-core';
|
|
4
|
+
/**
|
|
5
|
+
* Google Cloud KMS-based signer using EdDSA (Ed25519) signing
|
|
6
|
+
*
|
|
7
|
+
* The GCP KMS key must be created with:
|
|
8
|
+
* - Algorithm: EC_SIGN_ED25519
|
|
9
|
+
* - Purpose: ASYMMETRIC_SIGN
|
|
10
|
+
*
|
|
11
|
+
* Example gcloud CLI command to create a key:
|
|
12
|
+
* ```bash
|
|
13
|
+
* gcloud kms keys create my-key \
|
|
14
|
+
* --keyring=my-keyring \
|
|
15
|
+
* --location=us-east1 \
|
|
16
|
+
* --purpose=asymmetric-signing \
|
|
17
|
+
* --default-algorithm=ec-sign-ed25519
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export class GcpKmsSigner {
|
|
21
|
+
constructor(config) {
|
|
22
|
+
if (!config.keyName) {
|
|
23
|
+
throwSignerError(SignerErrorCode.CONFIG_ERROR, {
|
|
24
|
+
message: 'Missing required keyName field',
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
if (!config.publicKey) {
|
|
28
|
+
throwSignerError(SignerErrorCode.CONFIG_ERROR, {
|
|
29
|
+
message: 'Missing required publicKey field',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
assertIsAddress(config.publicKey);
|
|
34
|
+
this.address = config.publicKey;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throwSignerError(SignerErrorCode.CONFIG_ERROR, {
|
|
38
|
+
cause: error,
|
|
39
|
+
message: 'Invalid Solana public key format',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
this.keyName = config.keyName;
|
|
43
|
+
this.requestDelayMs = config.requestDelayMs || 0;
|
|
44
|
+
this.validateRequestDelayMs(this.requestDelayMs);
|
|
45
|
+
this.client = new v1.KeyManagementServiceClient();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Validate request delay ms
|
|
49
|
+
*/
|
|
50
|
+
validateRequestDelayMs(requestDelayMs) {
|
|
51
|
+
if (requestDelayMs < 0) {
|
|
52
|
+
throwSignerError(SignerErrorCode.CONFIG_ERROR, {
|
|
53
|
+
message: 'requestDelayMs must not be negative',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (requestDelayMs > 3000) {
|
|
57
|
+
console.warn('requestDelayMs is greater than 3000ms, this may result in blockhash expiration errors for signing messages/transactions');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Add delay between concurrent requests
|
|
62
|
+
*/
|
|
63
|
+
async delay(index) {
|
|
64
|
+
if (this.requestDelayMs > 0 && index > 0) {
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, index * this.requestDelayMs));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Sign message bytes using GCP KMS EdDSA signing
|
|
70
|
+
*/
|
|
71
|
+
async signBytes(messageBytes) {
|
|
72
|
+
try {
|
|
73
|
+
// GCP KMS AsymmetricSign expects the raw message for Ed25519 (PureEdDSA)
|
|
74
|
+
const [response] = await this.client.asymmetricSign({
|
|
75
|
+
data: messageBytes,
|
|
76
|
+
name: this.keyName,
|
|
77
|
+
});
|
|
78
|
+
if (!response.signature) {
|
|
79
|
+
throwSignerError(SignerErrorCode.REMOTE_API_ERROR, {
|
|
80
|
+
message: 'No signature in GCP KMS response',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Ed25519 signatures are 64 bytes
|
|
84
|
+
const signature = response.signature;
|
|
85
|
+
if (signature.length !== 64) {
|
|
86
|
+
throwSignerError(SignerErrorCode.SIGNING_FAILED, {
|
|
87
|
+
message: `Invalid signature length: expected 64 bytes, got ${signature.length}`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return signature;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
// Re-throw SignerError as-is
|
|
94
|
+
if (error instanceof Error && error.name === 'SignerError') {
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
if (error instanceof Error) {
|
|
98
|
+
// GCP SDK errors
|
|
99
|
+
const gcpError = error;
|
|
100
|
+
throwSignerError(SignerErrorCode.REMOTE_API_ERROR, {
|
|
101
|
+
cause: error,
|
|
102
|
+
message: `GCP KMS Sign operation failed: ${gcpError.message || error.message}`,
|
|
103
|
+
status: gcpError.code,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
throwSignerError(SignerErrorCode.REMOTE_API_ERROR, {
|
|
107
|
+
cause: error,
|
|
108
|
+
message: 'GCP KMS Sign operation failed',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Sign multiple messages using GCP KMS
|
|
114
|
+
*/
|
|
115
|
+
async signMessages(messages) {
|
|
116
|
+
return await Promise.all(messages.map(async (message, index) => {
|
|
117
|
+
await this.delay(index);
|
|
118
|
+
const messageBytes = message.content instanceof Uint8Array
|
|
119
|
+
? message.content
|
|
120
|
+
: new Uint8Array(Array.from(message.content));
|
|
121
|
+
const signatureBytes = await this.signBytes(messageBytes);
|
|
122
|
+
return createSignatureDictionary({
|
|
123
|
+
signature: signatureBytes,
|
|
124
|
+
signerAddress: this.address,
|
|
125
|
+
});
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Sign multiple transactions using GCP KMS
|
|
130
|
+
*/
|
|
131
|
+
async signTransactions(transactions) {
|
|
132
|
+
return await Promise.all(transactions.map(async (transaction, index) => {
|
|
133
|
+
await this.delay(index);
|
|
134
|
+
// Sign the transaction message bytes
|
|
135
|
+
const signatureBytes = await this.signBytes(new Uint8Array(transaction.messageBytes));
|
|
136
|
+
return createSignatureDictionary({
|
|
137
|
+
signature: signatureBytes,
|
|
138
|
+
signerAddress: this.address,
|
|
139
|
+
});
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if GCP KMS is available and the key is accessible
|
|
144
|
+
*/
|
|
145
|
+
async isAvailable() {
|
|
146
|
+
try {
|
|
147
|
+
const [publicKey] = await this.client.getPublicKey({
|
|
148
|
+
name: this.keyName,
|
|
149
|
+
});
|
|
150
|
+
if (!publicKey) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
// Verify the algorithm is EC_SIGN_ED25519
|
|
154
|
+
return publicKey.algorithm === 'EC_SIGN_ED25519';
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=gcp-kms-signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gcp-kms-signer.js","sourceRoot":"","sources":["../src/gcp-kms-signer.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for creating a GcpKmsSigner
|
|
3
|
+
*/
|
|
4
|
+
export interface GcpKmsSignerConfig {
|
|
5
|
+
/** Full resource name of the crypto key version */
|
|
6
|
+
keyName: string;
|
|
7
|
+
/** Solana public key (base58-encoded) */
|
|
8
|
+
publicKey: string;
|
|
9
|
+
/** Optional delay in ms between concurrent signing requests to avoid rate limits (default: 0) */
|
|
10
|
+
requestDelayMs?: number;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAC/B,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,12 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solana/keychain-gcp-kms",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
3
|
+
"author": "Solana Foundation",
|
|
4
|
+
"version": "0.4.0",
|
|
5
|
+
"description": "Google Cloud KMS-based signer for Solana transactions using EdDSA (Ed25519)",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": "https://github.com/solana-foundation/solana-keychain",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"solana",
|
|
10
|
+
"signing",
|
|
11
|
+
"wallet",
|
|
12
|
+
"gcp",
|
|
13
|
+
"kms",
|
|
14
|
+
"ed25519",
|
|
15
|
+
"eddsa"
|
|
16
|
+
],
|
|
17
|
+
"type": "module",
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"src"
|
|
30
|
+
],
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@google-cloud/kms": "^5.2.1",
|
|
33
|
+
"@solana/addresses": "^6.0.1",
|
|
34
|
+
"@solana/keys": "^6.0.1",
|
|
35
|
+
"@solana/signers": "^6.0.1",
|
|
36
|
+
"@solana/transactions": "^6.0.1",
|
|
37
|
+
"@solana/keychain-core": "0.4.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@solana-program/memo": "^0.11.0",
|
|
41
|
+
"@solana/kit": "^6.0.1",
|
|
42
|
+
"dotenv": "^17.2.3",
|
|
43
|
+
"@solana/keychain-test-utils": "0.4.0"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
9
48
|
"scripts": {
|
|
10
|
-
"
|
|
49
|
+
"build": "tsc --build",
|
|
50
|
+
"clean": "rm -rf dist *.tsbuildinfo",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:unit": "vitest run --config ../../vitest.config.unit.ts",
|
|
53
|
+
"test:integration": "vitest run --config ../../vitest.config.integration.ts",
|
|
54
|
+
"test:watch": "vitest",
|
|
55
|
+
"test:watch:unit": "vitest --config ../../vitest.config.unit.ts",
|
|
56
|
+
"test:watch:integration": "vitest --config ../../vitest.config.integration.ts",
|
|
57
|
+
"typecheck": "tsc --noEmit"
|
|
11
58
|
}
|
|
12
|
-
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendTransactionMessageInstructions,
|
|
3
|
+
createSolanaRpc,
|
|
4
|
+
createTransactionMessage,
|
|
5
|
+
pipe,
|
|
6
|
+
setTransactionMessageFeePayerSigner,
|
|
7
|
+
setTransactionMessageLifetimeUsingBlockhash,
|
|
8
|
+
signTransactionMessageWithSigners,
|
|
9
|
+
} from '@solana/kit';
|
|
10
|
+
import { getAddMemoInstruction } from '@solana-program/memo';
|
|
11
|
+
import { config } from 'dotenv';
|
|
12
|
+
import { describe, expect, it } from 'vitest';
|
|
13
|
+
|
|
14
|
+
import { GcpKmsSigner } from '../gcp-kms-signer.js';
|
|
15
|
+
|
|
16
|
+
config();
|
|
17
|
+
|
|
18
|
+
const REQUIRED_ENV_VARS = ['GCP_KMS_KEY_NAME', 'GCP_KMS_SIGNER_PUBKEY'];
|
|
19
|
+
|
|
20
|
+
function hasRequiredEnvVars(): boolean {
|
|
21
|
+
return REQUIRED_ENV_VARS.every(v => process.env[v]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createGcpKmsSigner(): GcpKmsSigner {
|
|
25
|
+
return new GcpKmsSigner({
|
|
26
|
+
keyName: process.env.GCP_KMS_KEY_NAME!,
|
|
27
|
+
publicKey: process.env.GCP_KMS_SIGNER_PUBKEY!,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe('GcpKmsSigner Integration', () => {
|
|
32
|
+
it.skipIf(!hasRequiredEnvVars())(
|
|
33
|
+
'signs transactions with GCP KMS',
|
|
34
|
+
async () => {
|
|
35
|
+
const signer = createGcpKmsSigner();
|
|
36
|
+
const rpcUrl = process.env.SOLANA_RPC_URL ?? 'https://api.devnet.solana.com';
|
|
37
|
+
|
|
38
|
+
// Get real blockhash from devnet
|
|
39
|
+
const rpc = createSolanaRpc(rpcUrl);
|
|
40
|
+
const {
|
|
41
|
+
value: { blockhash, lastValidBlockHeight },
|
|
42
|
+
} = await rpc.getLatestBlockhash().send();
|
|
43
|
+
|
|
44
|
+
// Create memo transaction (doesn't need funds)
|
|
45
|
+
const transaction = pipe(
|
|
46
|
+
createTransactionMessage({ version: 0 }),
|
|
47
|
+
tx => setTransactionMessageFeePayerSigner(signer, tx),
|
|
48
|
+
tx => appendTransactionMessageInstructions([getAddMemoInstruction({ memo: 'GCP KMS test' })], tx),
|
|
49
|
+
tx => setTransactionMessageLifetimeUsingBlockhash({ blockhash, lastValidBlockHeight }, tx),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Sign via GCP KMS
|
|
53
|
+
const signed = await signTransactionMessageWithSigners(transaction);
|
|
54
|
+
|
|
55
|
+
// Verify signature returned
|
|
56
|
+
expect(signed.signatures[signer.address]).toBeDefined();
|
|
57
|
+
expect(signed.signatures[signer.address]?.length).toBe(64);
|
|
58
|
+
},
|
|
59
|
+
60_000,
|
|
60
|
+
); // 1 minute timeout
|
|
61
|
+
|
|
62
|
+
it.skipIf(!hasRequiredEnvVars())('signs messages', async () => {
|
|
63
|
+
const signer = createGcpKmsSigner();
|
|
64
|
+
|
|
65
|
+
const message = {
|
|
66
|
+
content: new Uint8Array([1, 2, 3, 4, 5]),
|
|
67
|
+
signatures: {},
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const result = await signer.signMessages([message]);
|
|
71
|
+
|
|
72
|
+
expect(result).toHaveLength(1);
|
|
73
|
+
expect(result[0]?.[signer.address]).toBeDefined();
|
|
74
|
+
expect(result[0]?.[signer.address]?.length).toBe(64);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it.skipIf(!hasRequiredEnvVars())('checks availability', async () => {
|
|
78
|
+
const signer = createGcpKmsSigner();
|
|
79
|
+
const available = await signer.isAvailable();
|
|
80
|
+
expect(available).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|