OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / scripts / test-blind-client-browser-bundle.mjs

257 lines · javascript ref:
#!/usr/bin/env node
import fs from 'node:fs/promises'
import os from 'node:os'
import path from 'node:path'
import { gzipSync } from 'node:zlib'
import { fileURLToPath, pathToFileURL } from 'node:url'
import b4a from 'b4a'
import { build } from 'esbuild'

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'blind-client-browser-'))
const output = path.join(directory, 'bundle.mjs')
const controlOutput = path.join(directory, 'control.mjs')

try {
  const built = await build({
    stdin: {
      contents: "export * from './packages/blind-client/index.js'\nexport * from './packages/blind-client/runtime/browser.js'\nexport { default as __browserSodiumForTest } from './packages/blind-client/crypto-browser.js'\n",
      resolveDir: root,
      sourcefile: 'blind-client-browser-entry.mjs',
      loader: 'js'
    },
    bundle: true,
    platform: 'browser',
    format: 'esm',
    minify: true,
    metafile: true,
    outfile: output,
    logLevel: 'silent'
  })
  const forbidden = Object.keys(built.metafile.inputs).filter(input =>
    input.includes('sodium-native') || input.includes('bare-crypto') ||
    input.includes('node:crypto') || input.endsWith('.node'))
  if (forbidden.length > 0) throw new Error(`browser bundle contains native/runtime-only inputs: ${forbidden.join(', ')}`)
  const serverOnlyProtocolInputs = Object.keys(built.metafile.inputs).filter(input =>
    input.endsWith('packages/blind-protocol/index.js') ||
    input.endsWith('packages/blind-protocol/evidence-schemas.js') ||
    input.endsWith('packages/blind-protocol/durability-schemas.js') ||
    input.endsWith('packages/blind-protocol/client-internal-schemas.js') ||
    input.endsWith('packages/blind-protocol/client-composition-authority.js') ||
    input.endsWith('packages/blind-protocol/client-composition-runtime-vectors.js') ||
    input.endsWith('packages/blind-protocol/extended-schema-metadata.js') ||
    input.endsWith('packages/blind-protocol/master-schema-inventory.js') ||
    input.endsWith('packages/blind-protocol/registry.js') ||
    input.endsWith('packages/blind-protocol/schema-codecs.js') ||
    input.endsWith('packages/blind-protocol/schema-meta.js') ||
    input.endsWith('packages/blind-protocol/abi-registry.js'))
  if (serverOnlyProtocolInputs.length > 0) {
    throw new Error(`browser bundle contains server/evidence protocol inputs: ${serverOnlyProtocolInputs.join(', ')}`)
  }
  if (!Object.keys(built.metafile.inputs).some(input => input.includes('sodium-javascript'))) {
    throw new Error('browser bundle did not resolve sodium-universal to sodium-javascript')
  }
  const unusedSodiumInputs = Object.keys(built.metafile.inputs).filter(input =>
    input.endsWith('node_modules/sodium-javascript/index.js') ||
    /node_modules\/sodium-javascript\/crypto_(?:aead|auth|box|hash_sha256|kdf|kx|onetimeauth|secretbox|secretstream|shorthash|stream(?:_chacha20)?)\.js$/.test(input) ||
    /node_modules\/(?:chacha20-universal|sha256-universal|sha256-wasm|siphash24|xsalsa20)\//.test(input))
  if (unusedSodiumInputs.length > 0) {
    throw new Error(`browser bundle contains unused sodium algorithm inputs: ${unusedSodiumInputs.join(', ')}`)
  }

  const bundleBytes = await fs.readFile(output)
  const gzipBytes = gzipSync(bundleBytes).byteLength
  if (bundleBytes.byteLength > 300 * 1024 || gzipBytes > 80 * 1024) {
    throw new Error(`blind-client browser bundle exceeds its draft budget (${bundleBytes.byteLength} raw, ${gzipBytes} gzip)`)
  }

  const client = await import(`${pathToFileURL(output).href}?test=${Date.now()}`)
  const sodium = client.__browserSodiumForTest
  const expectedSodiumMethods = [
    'crypto_generichash',
    'crypto_sign_detached',
    'crypto_sign_seed_keypair',
    'crypto_sign_verify_detached',
    'randombytes_buf'
  ]
  if (Object.keys(sodium).sort().join(',') !== expectedSodiumMethods.join(',')) {
    throw new Error('browser sodium facade is not closed over its five required primitives')
  }
  const cryptoMessage = b4a.from('HIVERELAY_BROWSER_CRYPTO_FACADE_V1')
  const cryptoHash = b4a.alloc(32)
  sodium.crypto_generichash(cryptoHash, cryptoMessage)
  if (b4a.toString(cryptoHash, 'hex') !== '33de3b524927256cb40acca4cc13e34824a4a785e3c43d2325364ec917563991') {
    throw new Error('browser sodium facade changed the frozen BLAKE2b-256 vector')
  }
  const cryptoPublicKey = b4a.alloc(32)
  const cryptoSecretKey = b4a.alloc(64)
  const cryptoSignature = b4a.alloc(64)
  sodium.crypto_sign_seed_keypair(cryptoPublicKey, cryptoSecretKey, b4a.alloc(32, 0x42))
  sodium.crypto_sign_detached(cryptoSignature, cryptoMessage, cryptoSecretKey)
  if (b4a.toString(cryptoPublicKey, 'hex') !== '2152f8d19b791d24453242e15f2eab6cb7cffa7b6a5ed30097960e069881db12' ||
      b4a.toString(cryptoSignature, 'hex') !== 'df7a0da34e3abd3d459938940688eff60e7b870049ff5f8c3b16e3eab6b6c329ebe9f319b882e20e2e903f20a47bc4462eb7ab712b2020f6d86b65c2131e3201' ||
      !sodium.crypto_sign_verify_detached(cryptoSignature, cryptoMessage, cryptoPublicKey)) {
    throw new Error('browser sodium facade changed the frozen Ed25519 vector')
  }
  const randomProbe = b4a.alloc(32)
  sodium.randombytes_buf(randomProbe)
  if (randomProbe.every(byte => byte === 0)) throw new Error('browser sodium facade RNG did not fill its output')
  const runtime = client.createBrowserCryptoRuntime(globalThis.crypto)
  const sentinel = b4a.from('BROWSER_BUNDLE_PRIVATE_SENTINEL_f8e1762c')
  const relayPublicKey = b4a.alloc(32, 0x31)
  const created = await client.createCellReplica({
    runtime,
    relayPublicKey,
    allocationEpoch: 300,
    sizeClass: 1,
    leaseClass: 1,
    structuredContent: sentinel,
    admission: {
      profileId: 1,
      schemeId: 1,
      parameterHash: b4a.alloc(32, 0x32),
      token: b4a.from([0x33])
    }
  })
  const opened = await client.openCell({ runtime, ...created.readCap, cellBlob: created.request.cellBlob })
  if (!b4a.equals(opened, sentinel)) throw new Error('browser bundle cell round-trip mismatch')
  if (b4a.toString(created.requestBytes, 'hex').includes(b4a.toString(sentinel, 'hex'))) {
    throw new Error('browser bundle leaked application sentinel into relay request bytes')
  }
  const inbox = await client.createInboxReplica({
    runtime,
    relayPublicKey,
    allocationEpoch: 301,
    frameClassBits: 1,
    retentionClass: 1,
    leaseClass: 1,
    admission: {
      profileId: 1,
      schemeId: 1,
      parameterHash: b4a.alloc(32, 0x34),
      token: b4a.from([0x35])
    }
  })
  const appended = await client.createAppendInboxRequest({
    runtime,
    writeCap: inbox.writeCap,
    frameClass: 1,
    frame: runtime.randomBytes(4096),
    admission: {
      profileId: 1,
      schemeId: 1,
      parameterHash: b4a.alloc(32, 0x36),
      token: b4a.from([0x37])
    }
  })
  if (appended.request.frame.byteLength !== 4096 || appended.request.appendSignature.byteLength !== 64) {
    throw new Error('browser bundle signed-inbox composition mismatch')
  }
  const coreMirror = await client.createCoreMirrorRequest({
    runtime,
    relayPublicKey,
    corePublicKey: b4a.alloc(32, 0x38),
    fork: 0n,
    length: 1n,
    signedHeadHash: b4a.alloc(32, 0x39),
    leaseClass: 1,
    admission: {
      profileId: 1,
      schemeId: 1,
      parameterHash: b4a.alloc(32, 0x3a),
      token: b4a.from([0x3b])
    }
  })
  const forwardOpen = await client.createForwardOpenRequest({
    runtime,
    previousRelayKey: relayPublicKey,
    routeId: b4a.alloc(16, 0x3c),
    nextDescriptorSequence: 1n,
    nextDescriptorHash: b4a.alloc(32, 0x3d),
    requestedWireClass: 1,
    circuitClass: 1,
    innerHandshake: b4a.alloc(32, 0x3e),
    expectedInnerHandshakeBytes: 32,
    admission: {
      profileId: 1,
      schemeId: 1,
      parameterHash: b4a.alloc(32, 0x3f),
      token: b4a.from([0x40])
    }
  })
  const circuit = new client.ForwardClientCircuit({
    streamId: 1n,
    circuitNonce: forwardOpen.circuitNonce,
    grantedWireClass: 1,
    circuitClass: 1
  })
  const forwardFrame = circuit.encodeData(b4a.alloc(64, 0x41))
  if (coreMirror.requestBytes.byteLength === 0 || forwardFrame.byteLength === 0) {
    throw new Error('browser bundle Core/Forward composition mismatch')
  }
  const controlBuilt = await build({
    entryPoints: [path.join(root, 'packages/blind-client/control.js')],
    bundle: true,
    platform: 'browser',
    format: 'esm',
    minify: true,
    metafile: true,
    outfile: controlOutput,
    logLevel: 'silent'
  })
  const controlForbidden = Object.keys(controlBuilt.metafile.inputs).filter(input =>
    input.includes('sodium-native') || input.includes('bare-crypto') ||
    input.includes('node:crypto') || input.endsWith('.node') ||
    input.endsWith('packages/blind-protocol/index.js') ||
    input.endsWith('packages/blind-protocol/evidence-schemas.js') ||
    input.endsWith('packages/blind-protocol/durability-schemas.js') ||
    input.endsWith('packages/blind-protocol/client-internal-schemas.js') ||
    input.endsWith('packages/blind-protocol/client-composition-authority.js') ||
    input.endsWith('packages/blind-protocol/client-composition-runtime-vectors.js') ||
    input.endsWith('packages/blind-protocol/extended-schema-metadata.js') ||
    input.endsWith('packages/blind-protocol/master-schema-inventory.js') ||
    input.endsWith('packages/blind-protocol/registry.js') ||
    input.endsWith('packages/blind-protocol/schema-codecs.js') ||
    input.endsWith('packages/blind-protocol/schema-meta.js') ||
    input.endsWith('packages/blind-protocol/abi-registry.js'))
  if (controlForbidden.length > 0) {
    throw new Error(`browser control bundle contains forbidden inputs: ${controlForbidden.join(', ')}`)
  }
  const controlBytes = await fs.readFile(controlOutput)
  const controlGzipBytes = gzipSync(controlBytes).byteLength
  if (controlBytes.byteLength > 320 * 1024 || controlGzipBytes > 90 * 1024) {
    throw new Error(`blind-client browser control bundle exceeds its lazy budget (${controlBytes.byteLength} raw, ${controlGzipBytes} gzip)`)
  }
  const control = await import(`${pathToFileURL(controlOutput).href}?test=${Date.now()}`)
  if (typeof control.DescriptorTrustStore !== 'function' || typeof control.DurableAttempt !== 'function' ||
      typeof control.decodeBlindExternalProfileValueV1 !== 'function') {
    throw new Error('lazy browser control bundle is missing authenticated control-plane exports')
  }
  const readCap = b4a.concat([
    b4a.from([1]),
    b4a.alloc(32, 0x51),
    b4a.alloc(32, 0x52),
    b4a.alloc(32, 0x53),
    b4a.from([1, 1]),
    b4a.alloc(32, 0x54)
  ])
  if (control.decodeBlindExternalProfileValueV1('ReadCellCapV1', readCap).sizeClass !== 1) {
    throw new Error('lazy browser control bundle external-profile decoder failed')
  }
  process.stdout.write(`${JSON.stringify({
    schema: 'HiveRelayBlindClientBrowserBundleTestV1',
    rawBytes: bundleBytes.byteLength,
    gzipBytes,
    cellBytes: created.request.cellBlob.byteLength,
    inboxFrameBytes: appended.request.frame.byteLength,
    coreMirrorBytes: coreMirror.requestBytes.byteLength,
    forwardFlightBytes: forwardFrame.byteLength,
    controlRawBytes: controlBytes.byteLength,
    controlGzipBytes,
    nativeInputs: 0,
    ok: true
  })}\n`)
} finally {
  await fs.rm(directory, { recursive: true, force: true })
}