feat(bundle-source): Add hash to Endo Zip Base64 bundle format
ADD
packages/bundle-source/NEWS.md
+
+# Next release
+
+- Bundles generated with the module format `endoZipBase64` (the current
+ default) will now also contain an `endoZipBase64Sha512` property with a
+ consistent hash suitable for verifying the integrity of a bundle.
+ The hash is that of the `compartment-map.json` in the bundle zip archive,
+ which in turn contains the hashes of every other file in the archive.
+ A new package, `@endo/check-bundle` will provide a mechanism for
+ verifying bundle integrity.

ADD
packages/bundle-source/demo/integrity.js
+/* eslint-disable no-empty */
+// a c'thuloop runs
+for (;;) {} // ever

MODIFY
packages/bundle-source/src/index.js
packages/bundle-source/src/index.js
@@ -8,7 +8,7 @@ import commonjs0 from '@rollup/plugin-commonjs';
import * as babelParser from '@babel/parser';
import babelGenerate from '@agoric/babel-generator';
import babelTraverse from '@babel/traverse';
-import { makeArchive } from '@endo/compartment-mapper/archive.js';
+import { makeAndHashArchive } from '@endo/compartment-mapper/archive.js';
import { makeReadPowers } from '@endo/compartment-mapper/node-powers.js';
import { encodeBase64 } from '@endo/base64';
import SourceMaps from 'source-map';
@@ -140,7 +140,10 @@ async function transformSource(
async function bundleZipBase64(startFilename, dev, powers = {}) {
const entry = url.pathToFileURL(path.resolve(startFilename));
- const bytes = await makeArchive({ ...readPowers, ...powers }, entry, {
+ const { bytes, sha512 } = await makeAndHashArchive(
+ { ...readPowers, ...powers },
+ entry,
+ {
dev,
moduleTransforms: {
async mjs(sourceBytes) {
@@ -152,9 +155,14 @@ async function bundleZipBase64(startFilename, dev, powers = {}) {
return { bytes: objectBytes, parser: 'mjs' };
},
},
- });
+ },
+ );
const endoZipBase64 = encodeBase64(bytes);
- return harden({ endoZipBase64, moduleFormat: 'endoZipBase64' });
+ return harden({
+ endoZipBase64,
+ endoZipBase64Sha512: sha512,
+ moduleFormat: 'endoZipBase64',
+ });
}
async function bundleNestedEvaluateAndGetExports(

ADD
packages/bundle-source/test/snapshots/test-integrity.js.md
+# Snapshot report for `test/test-integrity.js`
+
+The actual snapshot is saved in `test-integrity.js.snap`.
+
+Generated by [AVA](https://avajs.dev).
+
+## integrity check
+
+> Snapshot 1
+
+ '7e9800caf92690f95caf1d514023504bbf4bb875a94b32c2810f5eb3a27ed7e69d49ff703b59319da74c2fabd12a9c1062aa52f859c878b86bebf7f3963d1276'

ADD
packages/bundle-source/test/snapshots/test-integrity.js.snap
packages/bundle-source/test/snapshots/test-integrity.js.snap
Binary files /dev/null and b/packages/bundle-source/test/snapshots/test-integrity.js.snap differ

ADD
packages/bundle-source/test/test-integrity.js
+import url from 'url';
+import { test } from './prepare-test-env-ava.js';
+import bundleSource from '../src/index.js';
+
+test('integrity check', async t => {
+ const {
+ moduleFormat,
+ endoZipBase64,
+ endoZipBase64Sha512,
+ } = await bundleSource(
+ url.fileURLToPath(new URL(`../demo/integrity.js`, import.meta.url)),
+ 'endoZipBase64',
+ );
+
+ const { endoZipBase64Sha512: secondHash } = await bundleSource(
+ url.fileURLToPath(new URL(`../demo/integrity.js`, import.meta.url)),
+ 'endoZipBase64',
+ );
+
+ t.is(moduleFormat, 'endoZipBase64');
+ t.is(typeof endoZipBase64, 'string');
+ t.is(typeof endoZipBase64Sha512, 'string');
+ t.is(secondHash, endoZipBase64Sha512);
+ t.assert(/^[0-9a-f]{128}$/.test(endoZipBase64Sha512));
+ t.snapshot(endoZipBase64Sha512);
+});

feat(bundle-source): Add hash to Endo Zip Base64 bundle format