-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Inkvi
committed
May 8, 2024
0 parents
commit 35011d9
Showing
6 changed files
with
2,683 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
})(); | ||
} |
Oops, something went wrong.