Skip to content

Commit

Permalink
Verify snaps in the registry (#67)
Browse files Browse the repository at this point in the history
* Verify snaps in the registry

* Run allow-scripts auto

* Move verification to separate step

* Fix typo

* Only validate updated snaps in CI

* Use stable version of snaps-controllers

* Update snaps-controllers import

* Use assertIsSemVerVersion instead of assertIsSemVerRange

* Fix yarn.lock

* lint:fix
  • Loading branch information
Mrtenz authored Sep 4, 2023
1 parent e5f0835 commit 54d1bbe
Show file tree
Hide file tree
Showing 5 changed files with 1,652 additions and 216 deletions.
16 changes: 16 additions & 0 deletions .github/workflows/build-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,19 @@ jobs:
echo "Working tree dirty at end of job"
exit 1
fi
verify-snaps:
name: Verify Snaps
runs-on: ubuntu-latest
needs:
- prepare
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn --immutable --immutable-cache
- run: yarn build
- run: yarn verify-snaps --diff
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16
v18
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"sign": "ts-node scripts/sign-registry.ts",
"test": "jest && jest-it-up",
"test:watch": "jest --watch",
"verify": "ts-node scripts/verify-registry.ts"
"verify": "ts-node scripts/verify-registry.ts",
"verify-snaps": "ts-node scripts/verify-snaps.ts"
},
"dependencies": {
"@metamask/utils": "^8.1.0",
Expand All @@ -38,6 +39,7 @@
"@metamask/eslint-config-jest": "^12.1.0",
"@metamask/eslint-config-nodejs": "^12.1.0",
"@metamask/eslint-config-typescript": "^12.1.0",
"@metamask/snaps-controllers": "^1.0.2",
"@noble/curves": "^1.2.0",
"@noble/hashes": "^1.3.2",
"@types/jest": "^28.1.6",
Expand All @@ -53,11 +55,13 @@
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"fast-deep-equal": "^3.1.3",
"jest": "^28.1.3",
"jest-it-up": "^2.0.2",
"prettier": "^2.7.1",
"prettier-plugin-packagejson": "^2.3.0",
"rimraf": "^3.0.2",
"semver": "^7.5.4",
"ts-jest": "^28.0.7",
"ts-node": "^10.7.0",
"typescript": "~4.8.4"
Expand All @@ -72,7 +76,9 @@
},
"lavamoat": {
"allowScripts": {
"@lavamoat/preinstall-always-fail": false
"@lavamoat/preinstall-always-fail": false,
"@metamask/snaps-controllers>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>keccak": false,
"@metamask/snaps-controllers>@metamask/permission-controller>@metamask/controller-utils>ethereumjs-util>ethereum-cryptography>secp256k1": false
}
}
}
112 changes: 112 additions & 0 deletions scripts/verify-snaps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { detectSnapLocation } from '@metamask/snaps-controllers/dist/snaps/location';
import { assertIsSemVerVersion } from '@metamask/utils';
import deepEqual from 'fast-deep-equal';
import semver from 'semver/preload';
import type { Infer } from 'superstruct';

import type { VerifiedSnapStruct } from '../src';
import registry from '../src/registry.json';

type VerifiedSnap = Infer<typeof VerifiedSnapStruct>;

/**
* Verify a snap version. This checks that the snap exists and that the
* checksum matches the checksum in the registry.
*
* @param snap - The snap object.
* @param version - The version.
* @param checksum - The checksum.
* @param latest - Whether the version is the latest version.
*/
async function verifySnapVersion(
snap: VerifiedSnap,
version: string,
checksum: string,
latest?: boolean,
) {
assertIsSemVerVersion(version);

// TODO: The version of `@metamask/utils` does not match with the version used
// by `@metamask/snaps-controllers`, so the `versionRange` property cannot be
// validated.
const location = detectSnapLocation(snap.id, {
versionRange: version as any,
});
const { result: manifest } = await location.manifest();

if (latest && snap.metadata.name !== manifest.proposedName) {
throw new Error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Proposed name for "${snap.id}@${version}" does not match the metadata name in the registry. Expected "${manifest.proposedName}" (proposed name), got "${snap.metadata.name}" (registry metadata name).`,
);
}

if (checksum !== manifest.source.shasum) {
throw new Error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Checksum for "${snap.id}@${version}" does not match the checksum in the registry. Expected "${manifest.source.shasum}" (manifest checksum), got "${checksum}" (registry checksum).`,
);
}
}

/**
* Verify a snap.
*
* @param snap - The snap.
* @returns A list of validation errors, if any.
*/
async function verifySnap(snap: VerifiedSnap) {
const latestVersion = Object.keys(snap.versions).reduce((result, version) => {
if (result === null || semver.gt(version, result)) {
return version;
}

return result;
});

for (const [version, { checksum }] of Object.entries(snap.versions)) {
await verifySnapVersion(
snap,
version,
checksum,
latestVersion === version,
).catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
}
}

/**
* Verify all snaps that are different from the main registry.
*/
async function diff() {
const mainRegistry = await fetch(
'https://raw.githubusercontent.com/MetaMask/snaps-registry/main/src/registry.json',
).then(async (response) => response.json());

for (const snap of Object.values(registry.verifiedSnaps)) {
if (!deepEqual(mainRegistry.verifiedSnaps[snap.id], snap)) {
await verifySnap(snap);
}
}
}

/**
* Verify all snaps.
*/
async function main() {
if (process.argv.includes('--diff')) {
await diff();
return;
}

for (const snap of Object.values(registry.verifiedSnaps)) {
await verifySnap(snap);
}
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Loading

0 comments on commit 54d1bbe

Please sign in to comment.