Skip to content

Commit 2349920

Browse files
Introduces GCP signer on @mysten/signers (MystenLabs#20473)
## Description Describe the changes or additions included in this PR. ## Test plan How did you test the new or updated feature? --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API:
1 parent e2d7ef0 commit 2349920

File tree

15 files changed

+792
-128
lines changed

15 files changed

+792
-128
lines changed

.changeset/polite-dryers-collect.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@mysten/signers': minor
3+
---
4+
5+
Introduces GCP KMS signer at `@mysten/signers/gcp`

.github/workflows/turborepo.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ jobs:
5858
uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa # [email protected]
5959
with:
6060
version: 'latest'
61+
- name: configure gcp/gke service user auth
62+
uses: google-github-actions/auth@v1
63+
with:
64+
credentials_json: ${{ secrets.GKE_TEST_KMS_SVCUSER_CREDENTIALS }}
6165
- name: Build
6266
run: pnpm turbo build
6367
- name: Test
@@ -67,6 +71,12 @@ jobs:
6771
AWS_REGION: ${{ vars.AWS_KMS_AWS_REGION }}
6872
AWS_KMS_KEY_ID: ${{ secrets.AWS_KMS_TEST_KMS_KEY_ID }}
6973
E2E_AWS_KMS_TEST_ENABLE: "true"
74+
GOOGLE_PROJECT_ID: ${{ secrets.GOOGLE_PROJECT_ID }}
75+
GOOGLE_LOCATION: ${{ secrets.GOOGLE_LOCATION }}
76+
GOOGLE_KEYRING: ${{ secrets.GOOGLE_KEYRING }}
77+
GOOGLE_KEY_NAME: ${{ secrets.GOOGLE_KEY_NAME }}
78+
GOOGLE_KEY_NAME_VERSION: ${{ secrets.GOOGLE_KEY_NAME_VERSION }}
79+
E2E_GCP_KMS_TEST_ENABLE: "true"
7080
run: pnpm turbo test
7181

7282
# Pack wallet extension and upload it as an artifact for easy developer use:

pnpm-lock.yaml

Lines changed: 389 additions & 61 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ packages:
2828
- '!sdk/typescript/graphql/schemas/2024.1'
2929
- '!sdk/typescript/graphql/schemas/2024.4'
3030
- '!sdk/signers/aws'
31+
- '!sdk/signers/gcp'

sdk/signers/.env.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,9 @@ export AWS_ACCESS_KEY_ID=""
22
export AWS_SECRET_ACCESS_KEY=""
33
export AWS_REGION=""
44
export AWS_KMS_KEY_ID=""
5+
6+
export GOOGLE_PROJECT_ID=""
7+
export GOOGLE_LOCATION=""
8+
export GOOGLE_KEYRING=""
9+
export GOOGLE_KEY_NAME=""
10+
export GOOGLE_KEY_NAME_VERSION="1"

sdk/signers/README.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Sui KMS Signers
22

33
The Sui KMS Signers package provides a set of tools for securely signing transactions using Key
4-
Management Services (KMS) like AWS KMS.
4+
Management Services (KMS) like AWS KMS and GCP KMS.
55

66
## Table of Contents
77

@@ -11,6 +11,12 @@ Management Services (KMS) like AWS KMS.
1111
- [fromKeyId](#fromkeyid)
1212
- [Parameters](#parameters)
1313
- [Examples](#examples)
14+
- [GCP KMS Signer](#gcp-kms-signer)
15+
- [Usage](#usage-1)
16+
- [API](#api-1)
17+
- [fromOptions](#fromoptions)
18+
- [Parameters](#parameters-1)
19+
- [Examples](#examples-1)
1420

1521
## AWS KMS Signer
1622

@@ -73,3 +79,64 @@ Returns
7379
An instance of AwsKmsSigner.
7480

7581
**Notice**: AWS Signer requires Node >=20 due to dependency on `crypto`
82+
83+
## GCP KMS Signer
84+
85+
The GCP KMS Signer allows you to leverage Google Cloud's Key Management Service to sign Sui
86+
transactions.
87+
88+
### Usage
89+
90+
#### fromOptions
91+
92+
Create a GCP KMS signer from the provided options. This method initializes the signer with the
93+
necessary GCP credentials and configuration, allowing it to interact with GCP KMS to perform
94+
cryptographic operations.
95+
96+
##### Parameters
97+
98+
- `options`
99+
**[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** An
100+
object containing GCP credentials and configuration.
101+
- `projectId`
102+
**[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
103+
The GCP project ID.
104+
- `location`
105+
**[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
106+
The GCP location.
107+
- `keyRing`
108+
**[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
109+
The GCP key ring.
110+
- `cryptoKey`
111+
**[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
112+
The GCP crypto key.
113+
- `cryptoKeyVersion`
114+
**[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
115+
The GCP crypto key version.
116+
117+
##### Examples
118+
119+
```typescript
120+
const signer = await GcpKmsSigner.fromOptions({
121+
projectId: 'your-google-project-id',
122+
location: 'your-google-location',
123+
keyRing: 'your-google-keyring',
124+
cryptoKey: 'your-google-key-name',
125+
cryptoKeyVersion: 'your-google-key-name-version',
126+
});
127+
128+
// Retrieve the public key and get the Sui address
129+
const publicKey = signer.getPublicKey();
130+
console.log(publicKey.toSuiAddress());
131+
132+
// Define a test message
133+
const testMessage = 'Hello, GCP KMS Signer!';
134+
const messageBytes = new TextEncoder().encode(testMessage);
135+
136+
// Sign the test message
137+
const { signature } = await signer.signPersonalMessage(messageBytes);
138+
139+
// Verify the signature against the public key
140+
const isValid = await publicKey.verifyPersonalMessage(messageBytes, signature);
141+
console.log(isValid); // Should print true if the signature is valid
142+
```

sdk/signers/gcp/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"private": true,
3+
"import": "../dist/esm/gcp/index.js",
4+
"main": "../dist/cjs/gcp/index.js",
5+
"sideEffects": false
6+
}

sdk/signers/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"./aws": {
1010
"import": "./dist/esm/aws/index.js",
1111
"require": "./dist/cjs/aws/index.js"
12+
},
13+
"./gcp": {
14+
"import": "./dist/esm/gcp/index.js",
15+
"require": "./dist/cjs/gcp/index.js"
1216
}
1317
},
1418
"sideEffects": false,
@@ -18,6 +22,7 @@
1822
"README.md",
1923
"aws",
2024
"dist",
25+
"gcp",
2126
"src"
2227
],
2328
"scripts": {
@@ -48,6 +53,7 @@
4853
"vitest": "^2.0.1"
4954
},
5055
"dependencies": {
56+
"@google-cloud/kms": "^4.5.0",
5157
"@mysten/sui": "workspace:*",
5258
"@noble/curves": "^1.4.2",
5359
"@noble/hashes": "^1.4.0",

sdk/signers/src/aws/aws-client.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44
import { Secp256k1PublicKey } from '@mysten/sui/keypairs/secp256k1';
55
import { Secp256r1PublicKey } from '@mysten/sui/keypairs/secp256r1';
66
import { fromBase64 } from '@mysten/sui/utils';
7-
import { ASN1Construction, ASN1TagClass, DERElement } from 'asn1-ts';
87

8+
import { publicKeyFromDER } from '../utils/utils.js';
99
import { AwsClient } from './aws4fetch.js';
10-
import { compressPublicKeyClamped } from './utils.js';
1110

1211
interface KmsCommands {
1312
Sign: {
@@ -66,30 +65,7 @@ export class AwsKmsClient extends AwsClient {
6665
throw new Error('Public Key not found for the supplied `keyId`');
6766
}
6867

69-
const publicKey = fromBase64(publicKeyResponse.PublicKey);
70-
71-
const encodedData: Uint8Array = publicKey;
72-
const derElement = new DERElement();
73-
derElement.fromBytes(encodedData);
74-
75-
// Validate the ASN.1 structure of the public key
76-
if (
77-
!(
78-
derElement.tagClass === ASN1TagClass.universal &&
79-
derElement.construction === ASN1Construction.constructed
80-
)
81-
) {
82-
throw new Error('Unexpected ASN.1 structure');
83-
}
84-
85-
const components = derElement.components;
86-
const publicKeyElement = components[1];
87-
88-
if (!publicKeyElement) {
89-
throw new Error('Public Key not found in the DER structure');
90-
}
91-
92-
const compressedKey = compressPublicKeyClamped(publicKeyElement.bitString);
68+
const compressedKey = publicKeyFromDER(fromBase64(publicKeyResponse.PublicKey));
9369

9470
switch (publicKeyResponse.KeySpec) {
9571
case 'ECC_NIST_P256':

sdk/signers/src/aws/aws-kms-signer.ts

Lines changed: 2 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
import type { PublicKey, SignatureFlag } from '@mysten/sui/cryptography';
44
import { SIGNATURE_FLAG_TO_SCHEME, Signer } from '@mysten/sui/cryptography';
55
import { fromBase64, toBase64 } from '@mysten/sui/utils';
6-
import { secp256r1 } from '@noble/curves/p256';
7-
import { secp256k1 } from '@noble/curves/secp256k1';
8-
import { DERElement } from 'asn1-ts';
96

7+
import { getConcatenatedSignature } from '../utils/utils.js';
108
import type { AwsClientOptions } from './aws-client.js';
119
import { AwsKmsClient } from './aws-client.js';
1210

@@ -82,7 +80,7 @@ export class AwsKmsSigner extends Signer {
8280
});
8381

8482
// Concatenate the signature components into a compact form
85-
return this.#getConcatenatedSignature(fromBase64(signResponse.Signature));
83+
return getConcatenatedSignature(fromBase64(signResponse.Signature), this.getKeyScheme());
8684
}
8785

8886
/**
@@ -93,41 +91,6 @@ export class AwsKmsSigner extends Signer {
9391
throw new Error('KMS Signer does not support sync signing');
9492
}
9593

96-
/**
97-
* Generates a concatenated signature from a DER-encoded signature.
98-
*
99-
* This signature format is consumable by Sui's `toSerializedSignature` method.
100-
*
101-
* @param signature - A `Uint8Array` representing the DER-encoded signature.
102-
* @returns A `Uint8Array` containing the concatenated signature in compact form.
103-
*
104-
* @throws {Error} If the input signature is invalid or cannot be processed.
105-
*/
106-
#getConcatenatedSignature(signature: Uint8Array): Uint8Array {
107-
if (!signature || signature.length === 0) {
108-
throw new Error('Invalid signature');
109-
}
110-
111-
// Initialize a DERElement to parse the DER-encoded signature
112-
const derElement = new DERElement();
113-
derElement.fromBytes(signature);
114-
115-
const [r, s] = derElement.toJSON() as [string, string];
116-
117-
switch (this.getKeyScheme()) {
118-
case 'Secp256k1':
119-
return new secp256k1.Signature(BigInt(r), BigInt(s)).normalizeS().toCompactRawBytes();
120-
case 'Secp256r1':
121-
return new secp256r1.Signature(BigInt(r), BigInt(s)).normalizeS().toCompactRawBytes();
122-
}
123-
124-
// Create a Secp256k1Signature using the extracted r and s values
125-
const secp256k1Signature = new secp256k1.Signature(BigInt(r), BigInt(s));
126-
127-
// Normalize the signature and convert it to compact raw bytes
128-
return secp256k1Signature.normalizeS().toCompactRawBytes();
129-
}
130-
13194
/**
13295
* Prepares the signer by fetching and setting the public key from AWS KMS.
13396
* It is recommended to initialize an `AwsKmsSigner` instance using this function.

0 commit comments

Comments
 (0)