generated from MetaMask/metamask-module-template
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add script for signing, verifying, and deploying registry (#18)
* Add script for signing and verifying registry * Remove unused import * Run lint:fix * Deploy registry and signature to gh-pages * Don't use exit code for checking if file was updated * Run workflow as part of main workflow * Added public key creation and refactored scripts * Changes after previous commit * Add tests * Fix lint * Fix environment name --------- Co-authored-by: Olaf Tomalka <[email protected]>
- Loading branch information
Showing
14 changed files
with
451 additions
and
4 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,4 @@ | ||
PRIVATE_KEY_PATH=./secp256k1-key | ||
PUBLIC_KEY_PATH=./secp256k1-key.pub | ||
REGISTRY_PATH=./src/registry.json | ||
SIGNATURE_PATH=./src/signature.json |
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
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
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 @@ | ||
name: Publish Registry | ||
|
||
on: | ||
workflow_call: | ||
secrets: | ||
REGISTRY_PRIVATE_KEY: | ||
required: true | ||
METAMASKBOT_TOKEN: | ||
required: true | ||
|
||
jobs: | ||
check-updated: | ||
name: Check if registry file was updated | ||
runs-on: ubuntu-latest | ||
outputs: | ||
UPDATED: ${{ steps.updated.outputs.UPDATED }} | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Check if registry file was updated | ||
id: updated | ||
run: | | ||
git fetch --prune --unshallow | ||
if git diff --name-only HEAD^ HEAD | grep src/registry.json | ||
then | ||
echo "UPDATED=true" >> "$GITHUB_OUTPUT" | ||
else | ||
echo "UPDATED=false" >> "$GITHUB_OUTPUT" | ||
fi | ||
publish-registry: | ||
name: Deploy registry to `gh-pages` branch | ||
environment: registry-publish | ||
needs: check-updated | ||
if: ${{ needs.check-updated.outputs.UPDATED == 'true' }} | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
- name: Use Node.js | ||
uses: actions/setup-node@v3 | ||
with: | ||
node-version-file: '.nvmrc' | ||
cache: 'yarn' | ||
- name: Install Yarn dependencies | ||
run: yarn --immutable | ||
- name: Sign registry | ||
run: yarn sign | ||
env: | ||
PRIVATE_KEY: ${{ secrets.REGISTRY_PRIVATE_KEY }} | ||
- run: | | ||
mkdir -p dist | ||
cp src/registry.json dist/registry.json | ||
cp src/signature.json dist/signature.json | ||
- name: Deploy registry | ||
uses: peaceiris/actions-gh-pages@de7ea6f8efb354206b205ef54722213d99067935 | ||
with: | ||
personal_token: ${{ secrets.METAMASKBOT_TOKEN }} | ||
publish_dir: ./dist | ||
destination_dir: latest |
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 |
---|---|---|
|
@@ -76,3 +76,8 @@ node_modules/ | |
!.yarn/releases | ||
!.yarn/sdks | ||
!.yarn/versions | ||
|
||
# Signature file | ||
src/signature.json | ||
|
||
secp256k1-key |
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
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,65 @@ | ||
import { bytesToHex, hasProperty } from '@metamask/utils'; | ||
import * as secp256k1 from '@noble/secp256k1'; | ||
import assert from 'assert'; | ||
import * as dotenv from 'dotenv'; | ||
import fs from 'fs/promises'; | ||
import path from 'path'; | ||
|
||
dotenv.config(); | ||
|
||
/** | ||
* Create a private key to be used for signing of the registry.json. | ||
*/ | ||
async function main() { | ||
const force = process.argv.includes('-f') || process.argv.includes('--force'); | ||
const privateKeyPath = process.env.PRIVATE_KEY_PATH; | ||
const publicKeyPath = process.env.PUBLIC_KEY_PATH; | ||
|
||
assert(privateKeyPath !== undefined, 'PRIVATE_KEY_PATH must be set.'); | ||
assert(publicKeyPath !== undefined, 'PUBLIC_KEY_PATH must be set.'); | ||
|
||
const privateKeyBytes = secp256k1.utils.randomPrivateKey(); | ||
const publicKey = bytesToHex(secp256k1.getPublicKey(privateKeyBytes, true)); | ||
|
||
console.log(`Key "${publicKey}" created.`); | ||
|
||
const privateKeyHex = bytesToHex(privateKeyBytes); | ||
|
||
try { | ||
// Write to file only if it doesn't exist. | ||
const flag = force ? 'w' : 'wx'; | ||
|
||
await Promise.all([ | ||
fs.writeFile(privateKeyPath, `${privateKeyHex}\n`, { | ||
flag, | ||
}), | ||
fs.writeFile(publicKeyPath, `${publicKey}\n`, { | ||
flag, | ||
}), | ||
]); | ||
} catch (error) { | ||
if ( | ||
typeof error === 'object' && | ||
error !== null && | ||
hasProperty(error, 'code') && | ||
error.code === 'EEXIST' | ||
) { | ||
console.error( | ||
`File ${ | ||
hasProperty(error, 'path') ? String(error.path) : '(unknown path)' | ||
} already exists. Use --force to overwrite.`, | ||
); | ||
|
||
// eslint-disable-next-line node/no-process-exit | ||
process.exit(1); | ||
} | ||
throw error; | ||
} | ||
|
||
console.log(`Successfully wrote to files. | ||
-> Private key: ${path.resolve(privateKeyPath)} | ||
-> Public key: ${path.resolve(publicKeyPath)}`); | ||
} | ||
main().catch((error) => { | ||
throw error; | ||
}); |
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,90 @@ | ||
import { | ||
add0x, | ||
assert, | ||
bytesToHex, | ||
hexToBytes, | ||
isHexString, | ||
} from '@metamask/utils'; | ||
import { secp256k1 } from '@noble/curves/secp256k1'; | ||
import { sha256 } from '@noble/hashes/sha256'; | ||
import * as dotenv from 'dotenv'; | ||
import { promises as fs } from 'fs'; | ||
import path from 'path'; | ||
import { format } from 'prettier'; | ||
|
||
dotenv.config(); | ||
|
||
/** | ||
* Get the private key from an environment variable. Either PRIVATE_KEY_PATH or | ||
* REGISTRY_PRIVATE_KEY must be set. | ||
* | ||
* @returns The private key. | ||
* @throws If neither environment variable is set, or if the private key cannot | ||
* be read from the file. | ||
*/ | ||
async function getPrivateKey() { | ||
const privateKeyPath = process.env.PRIVATE_KEY_PATH; | ||
const privateKeyEnv = process.env.REGISTRY_PRIVATE_KEY; | ||
|
||
if (privateKeyPath) { | ||
console.log('Using key from PRIVATE_KEY_PATH file.'); | ||
return await fs.readFile(privateKeyPath, 'utf-8').then((key) => key.trim()); | ||
} | ||
|
||
if (privateKeyEnv) { | ||
console.log('Using key from REGISTRY_PRIVATE_KEY variable.'); | ||
return privateKeyEnv; | ||
} | ||
|
||
throw new Error( | ||
'Either PRIVATE_KEY_PATH or REGISTRY_PRIVATE_KEY environment variable must be set.', | ||
); | ||
} | ||
|
||
/** | ||
* Signs the registry with the given private key. | ||
*/ | ||
async function main() { | ||
const registryPath = process.env.REGISTRY_PATH; | ||
const signaturePath = process.env.SIGNATURE_PATH; | ||
|
||
assert( | ||
registryPath !== undefined, | ||
'REGISTRY_PATH environment variable must be set.', | ||
); | ||
assert( | ||
signaturePath !== undefined, | ||
'SIGNATURE_PATH environment variable must be set.', | ||
); | ||
|
||
const privateKey = await getPrivateKey(); | ||
|
||
assert(isHexString(privateKey), 'Private key must be a hex string.'); | ||
const privateKeyBytes = hexToBytes(privateKey); | ||
assert(privateKeyBytes.length === 32, 'Private key must be 32 bytes'); | ||
const publicKey = bytesToHex(secp256k1.getPublicKey(privateKeyBytes)); | ||
|
||
const registry = await fs.readFile(registryPath); | ||
|
||
const signature = add0x( | ||
secp256k1.sign(sha256(registry), privateKeyBytes).toDERHex(), | ||
); | ||
|
||
const signatureObject = format( | ||
JSON.stringify({ | ||
signature, | ||
curve: 'secp256k1', | ||
format: 'DER', | ||
}), | ||
{ filepath: path.resolve(process.cwd(), signaturePath) }, | ||
); | ||
|
||
await fs.writeFile(signaturePath, signatureObject, 'utf-8'); | ||
console.log( | ||
`Signature signed using "${publicKey}" and written to "${signaturePath}".`, | ||
); | ||
} | ||
|
||
main().catch((error) => { | ||
throw error; | ||
}); |
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,70 @@ | ||
import { assert, assertStruct, isHexString } from '@metamask/utils'; | ||
import * as dotenv from 'dotenv'; | ||
import { promises as fs } from 'fs'; | ||
|
||
import { SignatureStruct, verify } from '../src'; | ||
|
||
dotenv.config(); | ||
|
||
/** | ||
* Get the private key from an environment variable. Either PRIVATE_KEY_PATH or | ||
* REGISTRY_PRIVATE_KEY must be set. | ||
* | ||
* @returns The private key. | ||
* @throws If neither environment variable is set, or if the private key cannot | ||
* be read from the file. | ||
*/ | ||
async function getPublicKey() { | ||
const publicKeyPath = process.env.PUBLIC_KEY_PATH; | ||
const registryPublicKey = process.env.REGISTRY_PUBLIC_KEY; | ||
|
||
if (publicKeyPath) { | ||
console.log('Using key from PUBLIC_KEY_PATH file.'); | ||
return await fs.readFile(publicKeyPath, 'utf-8').then((key) => key.trim()); | ||
} | ||
|
||
if (registryPublicKey) { | ||
console.log('Using key from REGISTRY_PUBLIC_KEY variable.'); | ||
return registryPublicKey; | ||
} | ||
|
||
throw new Error( | ||
'Either PRIVATE_KEY_PATH or REGISTRY_PRIVATE_KEY environment variable must be set.', | ||
); | ||
} | ||
|
||
/** | ||
* Verify the signature of the registry. | ||
*/ | ||
async function main() { | ||
const registryPath = process.env.REGISTRY_PATH; | ||
const signaturePath = process.env.SIGNATURE_PATH; | ||
|
||
assert( | ||
registryPath !== undefined, | ||
'REGISTRY_PATH environment variable must be set.', | ||
); | ||
assert( | ||
signaturePath !== undefined, | ||
'SIGNATURE_PATH environment variable must be set.', | ||
); | ||
|
||
const publicKey = await getPublicKey(); | ||
assert(isHexString(publicKey), 'Public key must be a hex string.'); | ||
|
||
const signature = JSON.parse(await fs.readFile(signaturePath, 'utf-8')); | ||
assertStruct(signature, SignatureStruct); | ||
const registry = await fs.readFile(registryPath, 'utf-8'); | ||
|
||
const isValid = await verify({ registry, signature, publicKey }); | ||
if (!isValid) { | ||
console.error('Signature is invalid.'); | ||
// eslint-disable-next-line node/no-process-exit | ||
process.exit(1); | ||
} | ||
console.log('Signature is valid.'); | ||
} | ||
|
||
main().catch((error) => { | ||
throw error; | ||
}); |
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 @@ | ||
0x025b65308f0f0fb8bc7f7ff87bfc296e0330eee5d3c1d1ee4a048b2fd6a86fa0a6 |
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
Oops, something went wrong.