Skip to content

Commit

Permalink
Merge pull request #8 from LayerZero-Labs/solana_change_config_scripts
Browse files Browse the repository at this point in the history
Add Solana support for configChangePayload scripts
  • Loading branch information
MohammadChavosh authored Jan 22, 2025
2 parents f7f3254 + 99be0c9 commit d28e2ee
Show file tree
Hide file tree
Showing 9 changed files with 4,212 additions and 91 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.17.0
17 changes: 11 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@layerzerolabs/gasolina-gcp",
"private": true,
"version": "0.0.1",
"private": true,
"bin": {
"cdk": "bin/cdk.js"
},
Expand All @@ -14,22 +14,27 @@
"watch": "tsc -w"
},
"dependencies": {
"@layerzerolabs/lz-definitions": "^3.0.47",
"@layerzerolabs/lz-solana-sdk-v2": "3.0.47",
"args": "^5.0.3",
"aws-cdk-lib": "^2.62.1",
"axios": "^1.3.1",
"bs58": "^5.0.0",
"command-line-args": "^5.2.1",
"constructs": "^10.0.0",
"axios": "^1.3.1",
"ts-node": "^10.9.1",
"ts-command-line-args": "^2.4.2"
"ethers": "^5.7.2",
"ethers-gcp-kms-signer": "^1.1.6",
"ts-command-line-args": "^2.4.2",
"ts-node": "^10.9.1"
},
"devDependencies": {
"@types/node": "10.17.27",
"ts-node": "^9.0.0",
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
"@types/node": "^20.16.10",
"@vue/compiler-sfc": "^3.x",
"prettier": "2.8.3",
"prettier-plugin-packagejson": "^2.4.2",
"rimraf": "^3.0.2",
"ts-node": "^9.0.0",
"typescript": "^4.9.4"
}
}
32 changes: 17 additions & 15 deletions scripts/configChangePayloads/createAddOrRemoveSignerSignatures.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ethers } from 'ethers'
import fs from 'fs'
import path from 'path'
import { parse } from 'ts-command-line-args'

import { getGcpKmsSigners } from './kms'
import { GcpKmsKey } from './kms'
import {
getAddOrRemoveSignerCallData,
getSignatures,
getSignaturesPayload,
getVId,
Expand Down Expand Up @@ -48,12 +48,6 @@ const args = parse({
},
})

const setSignerFunctionSig = 'function setSigner(address _signer, bool _active)'
const iface = new ethers.utils.Interface([setSignerFunctionSig])
const getCallData = (signerAddress: string, active: boolean) => {
return iface.encodeFunctionData('setSigner', [signerAddress, active])
}

const main = async () => {
const { environment, chainNames, quorum, signerAddress, shouldRevoke } =
args
Expand All @@ -63,8 +57,7 @@ const main = async () => {

const dvnAddresses = require(`./data/dvn-addresses-${environment}.json`)

const keyIds = require(`./data/kms-keyids-${environment}.json`)
const signers = await getGcpKmsSigners(keyIds)
const keyIds: GcpKmsKey[] = require(`./data/kms-keyids-${environment}.json`)

const availableChainNames = chainNames.split(',')

Expand All @@ -73,20 +66,29 @@ const main = async () => {
availableChainNames.map(async (chainName) => {
results[chainName] = results[chainName] || {}
const vId = getVId(chainName, environment)
const callData = getCallData(
const callData = await getAddOrRemoveSignerCallData(
dvnAddresses[chainName],
signerAddress,
shouldRevoke === 1 ? false : true,
chainName,
environment,
)

const hash = hashCallData(
const hash = await hashCallData(
dvnAddresses[chainName],
vId,
EXPIRATION,
callData,
chainName,
environment,
)

const signatures = await getSignatures(signers, hash)
const signaturesPayload = getSignaturesPayload(signatures, quorum)
const signatures = await getSignatures(keyIds, hash, chainName)
const signaturesPayload = getSignaturesPayload(
signatures,
quorum,
chainName,
)

results[chainName] = {
args: {
Expand All @@ -106,7 +108,7 @@ const main = async () => {
}
}),
)
fs.writeFileSync(FILE_PATH, JSON.stringify(results))
fs.writeFileSync(FILE_PATH, JSON.stringify(results, null, 4))
console.log(`Results written to: ${FILE_PATH}`)
}

Expand Down
27 changes: 14 additions & 13 deletions scripts/configChangePayloads/createSetQuorumSignatures.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ethers } from 'ethers'
import fs from 'fs'
import path from 'path'
import { parse } from 'ts-command-line-args'

import { GcpKmsKey, getGcpKmsSigners } from './kms'
import { GcpKmsKey } from './kms'
import {
getSetQuorumCallData,
getSignatures,
getSignaturesPayload,
getVId,
Expand Down Expand Up @@ -43,19 +43,12 @@ const args = parse({
},
})

const setQuorumFunctionSig = 'function setQuorum(uint64 _quorum)'
const iface = new ethers.utils.Interface([setQuorumFunctionSig])
const getCallData = (newQuorum: number) => {
return iface.encodeFunctionData('setQuorum', [newQuorum])
}

const main = async () => {
const { environment, chainNames, oldQuorum, newQuorum } = args

const dvnAddresses = require(`./data/dvn-addresses-${environment}.json`)

const keyIds: GcpKmsKey[] = require(`./data/kms-keyids-${environment}.json`)
const signers = await getGcpKmsSigners(keyIds)

const availableChainNames = chainNames.split(',')

Expand All @@ -64,19 +57,27 @@ const main = async () => {
availableChainNames.map(async (chainName) => {
results[chainName] = results[chainName] || {}
const vId = getVId(chainName, environment)
const callData = getCallData(newQuorum)
const callData = await getSetQuorumCallData(
dvnAddresses[chainName],
newQuorum,
chainName,
environment,
)

const hash = hashCallData(
const hash = await hashCallData(
dvnAddresses[chainName],
vId,
EXPIRATION,
callData,
chainName,
environment,
)

const signatures = await getSignatures(signers, hash)
const signatures = await getSignatures(keyIds, hash, chainName)
const signaturesPayload = getSignaturesPayload(
signatures,
oldQuorum,
chainName,
)

results[chainName] = {
Expand All @@ -96,7 +97,7 @@ const main = async () => {
}
}),
)
fs.writeFileSync(FILE_PATH, JSON.stringify(results))
fs.writeFileSync(FILE_PATH, JSON.stringify(results, null, 4))
console.log(`Results written to: ${FILE_PATH}`)
}

Expand Down
3 changes: 2 additions & 1 deletion scripts/configChangePayloads/data/dvn-addresses-testnet.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"bsc": "0xfb5257b22111ed53df225bd71bce8a6e27311170",
"avalanche": "0xd28fa1395a45bf2d30af5afded0c7487b156f705",
"fantom": "0xFffc92A6AbE6480AdC574901ebFDe108A7077Eb8"
"fantom": "0xFffc92A6AbE6480AdC574901ebFDe108A7077Eb8",
"solana": "4VDjp6XQaxoZf5RGwiPU9NR1EXSZn2TP4ATMmiSzLfhb"
}
153 changes: 153 additions & 0 deletions scripts/configChangePayloads/kms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { KeyManagementServiceClient } from '@google-cloud/kms'
import { BN } from 'bn.js'
import crypto from 'crypto'
import { ec as EC } from 'elliptic'
import { GcpKmsSigner } from 'ethers-gcp-kms-signer'

import { bytesToHexPrefixed, hexToUint8Array } from './utils'

const asn1 = require('asn1.js')

/**
* Defines a GCP KMS Key.
*/
Expand All @@ -20,3 +28,148 @@ export async function getGcpKmsSigners(
}),
)
}

const calculateRecoveryId = (
ec: EC,
r: string,
s: string,
digest: string,
expectedKey: string,
): number => {
// Recover the public key
// Is R.y even and R.x less than the curve order n: recovery_id := 0
// Is R.y odd and R.x less than the curve order n: recovery_id := 1
// Is R.y even and R.x more than the curve order n: recovery_id := 2
// Is R.y odd and R.x more than the curve order n: recovery_id := 3
for (let i = 0; i <= 3; i++) {
const recoveredKey = ec.recoverPubKey(
Buffer.from(hexToUint8Array(digest)),
{
r: Buffer.from(hexToUint8Array(r)),
s: Buffer.from(hexToUint8Array(s)),
},
i,
)
// Raw ECDSA public key - remove first byte (0x04) which signifies that it is uncompressed
const publicKeyHex = `0x${recoveredKey.encode('hex', false).slice(2)}`
if (publicKeyHex === expectedKey) {
return i
}
}
throw new Error('Could not find recoveryId')
}

const EcdsaSigAsnParse = asn1.define('EcdsaSig', function (this: any) {
this.seq().obj(this.key('r').int(), this.key('s').int())
})

const getRSFromDER = (signature: Uint8Array): { r: string; s: string } => {
if (signature == undefined) {
throw new Error('Signature is undefined.')
}

const decoded = EcdsaSigAsnParse.decode(signature, 'der')

const r = new BN(decoded.r)
let s = new BN(decoded.s)

// The group order n in secp256k1 (number of points on the curve)
const secp256k1N = new BN(
hexToUint8Array(
'0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141',
),
)
const secp256k1halfN = secp256k1N.div(new BN(2))

if (s.gt(secp256k1halfN)) {
s = secp256k1N.sub(s)
}

return {
r: bytesToHexPrefixed(r.toBuffer('be', 32)),
s: bytesToHexPrefixed(s.toBuffer('be', 32)),
}
}

const getRSVFromDERSignature = (
ec: EC,
derSignature: Uint8Array,
digest: string,
expectedPublicKey: string,
): { r: string; s: string; v: number } => {
const { r, s } = getRSFromDER(derSignature as Uint8Array)
const v = calculateRecoveryId(ec, r, s, digest, expectedPublicKey)
return {
r,
s,
v,
}
}

const appendRecoveryIdToSignature = (
ec: EC,
derSignature: Uint8Array,
digest: string,
expectedPublicKey: string,
) => {
const { r, s, v } = getRSVFromDERSignature(
ec,
derSignature,
digest,
expectedPublicKey,
)
// join r, s and v
const signature = bytesToHexPrefixed(
Buffer.concat([
Buffer.from(hexToUint8Array(r)),
Buffer.from(hexToUint8Array(s)),
new BN(v).toBuffer('be', 1),
]),
)
return signature
}

export async function signUsingGcpKmsClinet(keyId: GcpKmsKey, data: string) {
const client = new KeyManagementServiceClient({})
const ec = new EC('secp256k1')

const kmsVersionName = client.cryptoKeyVersionPath(
keyId.projectId,
keyId.locationId,
keyId.keyRingId,
keyId.keyId,
keyId.keyVersion,
)

const [publicKey] = await client.getPublicKey({
name: kmsVersionName,
})
if (!publicKey || !publicKey.pem)
throw new Error(`Can not find key: ${keyId.keyId}`)

const x509der = crypto
.createPublicKey(publicKey.pem)
.export({ format: 'der', type: 'spki' })

const address = bytesToHexPrefixed(Uint8Array.from(x509der.subarray(-64)))

const [response] = await client.asymmetricSign({
name: kmsVersionName,
digest: {
sha256: Buffer.from(hexToUint8Array(data)),
},
})
if (!response || !response.signature) {
throw new Error(`GCP KMS: asymmetricSign() failed`)
}

return {
signature: appendRecoveryIdToSignature(
ec,
response.signature as Uint8Array,
data,
address,
),
address,
}
}
17 changes: 0 additions & 17 deletions scripts/configChangePayloads/package.json

This file was deleted.

Loading

0 comments on commit d28e2ee

Please sign in to comment.