Skip to content

Commit

Permalink
Testing service account
Browse files Browse the repository at this point in the history
  • Loading branch information
Inkvi committed May 8, 2024
0 parents commit 35011d9
Show file tree
Hide file tree
Showing 6 changed files with 2,683 additions and 0 deletions.
59 changes: 59 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: docker-publish

on:
push:
tags:
- "v*.*.*"

env:
IMAGE_NAME: ghcr.io/polymerdao/signer-service

jobs:
docker-build-signer-service:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
ref: ${{ github.event.inputs.build-ref }}

- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
${{ env.IMAGE_NAME }}
tags: |
type=sha
type=semver,pattern={{raw}}
labels: |
org.opencontainers.image.source=https://github.com/polymerdao/signer-service
org.opencontainers.image.title=signer-service
org.opencontainers.image.url=https://github.com/polymerdao/signer-service
- name: Authenticate Docker
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v2

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64
push: true
file: Dockerfile
provenance: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache,mode=max
19 changes: 19 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Use the official Node.js 14 image as a base
FROM node:18-alpine

# Set the working directory in the container
WORKDIR /app

# Copy package.json and package-lock.json (if available)
COPY package*.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code
COPY main.ts .

RUN npm install -g ts-node

CMD ["ts-node", "main.ts"]

131 changes: 131 additions & 0 deletions KMSProviderGCP.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { KMSProvider } from "@web3-kms-signer/core";
import { KeyManagementServiceClient } from "@google-cloud/kms";
import crypto from 'crypto';
import crc32c from 'fast-crc32c';
import { v4 as uuidv4 } from 'uuid';

interface KeyManagementServiceClientPath {
projectId: string;
locationId: string;
keyRingId: string;
}

/**
* A class who extends the abstract KMSProvider to implement key management and cryptographic
* operations using Google Cloud Platform's Key Management Service (KMS). It provides methods
* to retrieve DER-encoded public keys, sign digests with private keys managed by GCP KMS,
* and manage cryptographic keys and key rings within GCP KMS.
*
*/
export class KMSProviderGCP extends KMSProvider {
private kms: KeyManagementServiceClient;
private path?: KeyManagementServiceClientPath

constructor(config: {keyFilename?: string}) {
super();
this.kms = new KeyManagementServiceClient({keyFilename: config.keyFilename});
}

public setPath(path: KeyManagementServiceClientPath) {
this.path = path;
}

/**
* Retrieves the DER-encoded object as defined by ANS X9.62–2005.
* @param keyId The gcp identifier of the key.
* @returns Promise resolving to the DER-encoded public key.
*/
async getDerPublickey(keyId: string) : Promise<Buffer> {
if (!this.path) {
throw "this.path is undefined";
}
const pubKey = await this.kms.getPublicKey({name: this.kms.cryptoKeyVersionPath(this.path.projectId, this.path.locationId, this.path.keyRingId, keyId, '1')})
if (!pubKey[0].pem) {
throw new Error("GCPKMS: pubKey[0].pem is undefined.");
}
const p2 = crypto.createPublicKey(pubKey[0].pem);
return p2.export({format:"der", type:"spki"});
}

/**
* Signs a digest using a private key stored in GCP KMS, returning the signature.
* @param keyId The gcp identifier of the key.
* @param digest The digest to sign, as a Buffer.
* @returns Promise resolving to the signature, as a Buffer.
*/
async signDigest(keyId: string, digest: Buffer) : Promise<Buffer> {
if (!this.path) {
throw "this.path is undefined";
}
const [signResponse] = await this.kms.asymmetricSign({
name: this.kms.cryptoKeyVersionPath(this.path.projectId, this.path.locationId, this.path.keyRingId, keyId, '1'),
digest: {
sha256: digest
},
digestCrc32c: {
value: crc32c.calculate(digest),
}
});
if (!signResponse.signature || !signResponse.signatureCrc32c) {
throw new Error("GCPKMS: signResponse is undefined.");
}
if (!signResponse.verifiedDigestCrc32c) {
throw new Error('GCPKMS: request corrupted in-transit');
}
if (crc32c.calculate(Buffer.from(signResponse.signature)) !== Number(signResponse.signatureCrc32c.value)) {
throw new Error('GCPKMS: response corrupted in-transit');
}
return Buffer.from(signResponse.signature);
}

/**
* Asynchronously creates a new cryptographic key within a specified key ring in Google Cloud Platform's Key Management Service (GCP KMS).
* This method allows for the specification of the protection level for the key, with options for hardware security module (HSM)
* or software-based protection. It generates a unique identifier for the key, constructs the cryptographic key with the specified
* protection level, and associates it with a key ring defined by the `path` property of the class instance.
*
* @param {'HSM' | 'SOFTWARE'} [protectionLevel='SOFTWARE'] - The desired protection level for the new cryptographic key.
* @returns {Promise<string>} A promise that resolves to the unique identifier (UUID) of the newly created cryptographic key.
* @throws {Error} Throws an error if the path to the key ring is undefined or if the key creation fails in GCP KMS.
*/
async createKey(protectionLevel: 'HSM' | 'SOFTWARE' = 'SOFTWARE') : Promise<string> {
if (!this.path) {
throw "this.path is undefined";
}
const cryptoKeyId = uuidv4();
const [key] = await this.kms.createCryptoKey({
parent: this.kms.keyRingPath(this.path.projectId, this.path.locationId, this.path.keyRingId),
cryptoKeyId: cryptoKeyId,
cryptoKey: {
purpose: 'ASYMMETRIC_SIGN',
versionTemplate: {
algorithm: 'EC_SIGN_SECP256K1_SHA256',
protectionLevel: protectionLevel
}
}
});
if (!key.name) {
throw new Error("GCPKMS: key.name not exist.");
}
return cryptoKeyId;
}

/**
* Creates a new key ring in GCP KMS to organize crypto keys.
* @param keyRingId The identifier for the new key ring.
* @returns The ID of the created key ring.
*/
async createKeyRing(keyRingId: string) {
if (!this.path) {
throw "this.path is undefined";
}
const [keyRing] = await this.kms.createKeyRing({
parent: this.kms.locationPath(this.path.projectId, this.path.locationId),
keyRingId: keyRingId,
});
if (!keyRing.name) {
throw new Error("GCPKMS: keyRing.name not exist.");
}
return keyRingId;
}
}
58 changes: 58 additions & 0 deletions main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Signer } from "@web3-kms-signer/core";
import Web3 from "web3";
import { ethers, formatUnits } from "ethers";
import { KMSWallets } from "@web3-kms-signer/kms-wallets";
import { KMSProviderGCP } from "./KMSProviderGCP";

const rpc = process.env.RPC;
const provider = new ethers.JsonRpcProvider(rpc);
const web3 = new Web3(rpc);
let to = process.env.TO!
let PROJECT_ID = process.env.PROJECT_ID!
let LOCATION_ID = process.env.LOCATION_ID!
let keyRingId = process.env.KEY_RING_ID!
let keyId = process.env.KEY_ID!


if (require.main === module) {
(async () => {
const kmsProvider = new KMSProviderGCP({});
const wallets = new KMSWallets(kmsProvider);

kmsProvider.setPath({
projectId: PROJECT_ID,
locationId: LOCATION_ID,
keyRingId: keyRingId
});

let kmsAddress = await wallets.getAddressHex(keyId)
console.log("address", kmsAddress)

let kmsNonce = await provider.getTransactionCount(kmsAddress);
const chainId = await web3.eth.getChainId();

const signer = new Signer(wallets, Number(chainId));

let gasPrice = await web3.eth.getGasPrice()
console.log("gas price (gwei)", formatUnits(gasPrice, "gwei"))

const txData = {
nonce: kmsNonce,
gasPrice: gasPrice,
gasLimit: 21000,
to: to,
value: ethers.parseEther("0.0001"),
data: '0x',
chainId: chainId,
};

const signedTx = await signer.signTransaction({keyId: keyId}, txData);
console.log("signedTx", signedTx)

const tx = await provider.broadcastTransaction(signedTx)
console.log(`https://sepolia.etherscan.io/tx/${tx.hash}`);

const receipt = await tx.wait(1)
console.log("receipt", receipt)
})();
}
Loading

0 comments on commit 35011d9

Please sign in to comment.