OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-client / test / browser-artifact.test.js

388 lines · javascript ref:
import test from 'brittle'
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { spawnSync } from 'node:child_process'
import { fileURLToPath } from 'node:url'
import { gzipSync } from 'node:zlib'
import b4a from 'b4a'
import {
  hashAbi,
  hashClientCompositionFormat,
  hashClientCompositionVectorSet,
  hashSpec,
  hashVectorSet
} from '@hiverelay/blind-protocol'
import {
  BLIND_CLIENT_BROWSER_ARTIFACT_STATUS,
  decodeBlindClientBrowserArtifactManifestV1,
  encodeBlindClientBrowserArtifactManifestV1,
  hashBlindClientBrowserArtifactManifest,
  verifyBlindClientBrowserArtifactReleaseEvidenceV1,
  verifyBlindClientBrowserArtifactV1
} from '../browser-artifact.js'

const artifactUrl = new URL('../browser-artifacts/blind-client-control-v1.mjs', import.meta.url)
const manifestUrl = new URL('../browser-artifacts/blind-client-control-v1.manifest.cenc', import.meta.url)
const chromiumEvidenceUrl = new URL(
  '../browser-artifacts/blind-client-control-v1.chromium-evidence.json', import.meta.url)
const crossHostEvidenceUrl = new URL(
  '../browser-artifacts/blind-client-control-v1.cross-host-evidence.json', import.meta.url)
const artifactBytes = fs.readFileSync(artifactUrl)
const manifestBytes = fs.readFileSync(manifestUrl)
const packageUrl = new URL('../package.json', import.meta.url)
const rootPackageUrl = new URL('../../../package.json', import.meta.url)
const eslintIgnoreUrl = new URL('../../../.eslintignore', import.meta.url)
const generatorUrl = new URL('../../../scripts/generate-blind-client-browser-artifacts.mjs', import.meta.url)
const expectedManifestHash = hashBlindClientBrowserArtifactManifest(manifestBytes)
const expectedTuple = Object.freeze({
  specHash: hashSpec(fs.readFileSync(new URL('../../../docs/protocol/HIVERELAY-BLIND-WIRE-V1.md', import.meta.url))),
  abiHash: hashAbi(fs.readFileSync(new URL('../../blind-protocol/hiverelay-blind-abi-v1.cenc', import.meta.url))),
  vectorSetHash: hashVectorSet(fs.readFileSync(new URL('../../blind-protocol/vector-manifest-v1.cenc', import.meta.url))),
  clientCompositionFormatHash: hashClientCompositionFormat(fs.readFileSync(
    new URL('../../blind-protocol/hiverelay-blind-client-composition-format-v1.cenc', import.meta.url))),
  clientCompositionVectorSetHash: hashClientCompositionVectorSet(fs.readFileSync(
    new URL('../../blind-protocol/hiverelay-blind-client-composition-vector-manifest-v1.cenc', import.meta.url)))
})

function changed (value, offset) {
  const output = b4a.from(value)
  output[offset] ^= 1
  return output
}

function throwsCode (t, operation, code) {
  try {
    operation()
    t.fail(`expected ${code}`)
  } catch (error) {
    t.is(error.code, code)
  }
}

function decodedManifest () {
  return decodeBlindClientBrowserArtifactManifestV1(manifestBytes)
}

function manifestInput (overrides = {}) {
  const value = decodedManifest()
  return {
    ...value,
    specHash: b4a.from(value.specHash),
    abiHash: b4a.from(value.abiHash),
    vectorSetHash: b4a.from(value.vectorSetHash),
    clientCompositionFormatHash: b4a.from(value.clientCompositionFormatHash),
    clientCompositionVectorSetHash: b4a.from(value.clientCompositionVectorSetHash),
    sourceClosureHash: b4a.from(value.sourceClosureHash),
    artifactHash: b4a.from(value.artifactHash),
    ...overrides
  }
}

test('checked browser control artifact is deterministic, tuple-bound, and importable', async t => {
  const verified = verifyBlindClientBrowserArtifactV1({
    artifactBytes,
    manifestBytes,
    expectedManifestHash,
    expectedTuple
  })
  t.is(verified.manifest.draft, false)
  t.is(verified.manifest.artifactLength, BigInt(artifactBytes.byteLength))
  t.is(verified.manifest.artifactPath, BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.artifactPath)
  t.ok(b4a.equals(encodeBlindClientBrowserArtifactManifestV1(verified.manifest), manifestBytes))
  t.ok(artifactBytes.byteLength <= BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.maxArtifactBytes)
  t.ok(gzipSync(artifactBytes).byteLength <= BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.maxArtifactGzipBytes)
  const exposedManifest = verified.manifest
  const exposedArtifact = verified.artifactBytes
  exposedManifest.specHash[0] ^= 1
  exposedManifest.clientCompositionFormatHash[0] ^= 1
  exposedArtifact[0] ^= 1
  t.ok(b4a.equals(verified.manifest.specHash, expectedTuple.specHash), 'verified manifest has no mutable alias')
  t.ok(b4a.equals(verified.manifest.clientCompositionFormatHash,
    expectedTuple.clientCompositionFormatHash), 'verified client-composition tuple has no mutable alias')
  t.ok(b4a.equals(verified.artifactBytes, artifactBytes), 'verified artifact has no mutable alias')
  const artifactText = b4a.toString(artifactBytes)
  t.absent(artifactText.includes(fileURLToPath(new URL('../../../', import.meta.url))), 'workspace path is absent')
  t.absent(artifactText.includes('blind-client-control-browser-entry.mjs'), 'virtual build path is absent')
  for (const forbidden of [
    'INTERNAL_STORE', 'CLIENT_EXAMPLE', 'BlindStoreManifestV1', 'BlindBackupManifestV1',
    'Peerit', 'OutboxLog', 'BlindShard', 'EXECUTABLE_SCHEMA_CODECS'
  ]) {
    t.absent(artifactText.toLowerCase().includes(forbidden.toLowerCase()),
      `${forbidden} is absent from the standalone browser artifact`)
  }
  const browser = await import(`${artifactUrl.href}?test=${Date.now()}`)
  for (const name of [
    'BlindDescriptorBootstrapHttpClient',
    'BlindDirectHttpClient',
    'BlindRelayQualifier',
    'DescriptorTrustStore',
    'createBrowserCryptoRuntime',
    'createCellReplica',
    'createGetCellRequest',
    'decodeBlindExternalProfileValueV1',
    'openVerifiedCellGetResult',
    'trustedAdmissionProfile',
    'trustedDescriptorValidity',
    'verifiedAdmissionParametersValidity',
    'verifiedEndpointContext',
    'verifiedHealthValidity',
    'verifyAdmissionParametersBytes',
    'verifyOperationResult'
  ]) t.is(typeof browser[name], 'function', name)
  t.alike(Object.keys(browser).filter(name => name.includes('ExternalProfile')),
    ['decodeBlindExternalProfileValueV1'], 'the external-profile schema selector is one closed API')
  for (const hidden of [
    'decodeCanonical', 'encodeCanonical', 'readCellCapV1', 'blindReceiptV1',
    'EXECUTABLE_SCHEMA_CODECS'
  ]) t.is(browser[hidden], undefined, `${hidden} is not exported`)

  const readCap = b4a.concat([
    b4a.from([1]),
    b4a.alloc(32, 0x41),
    b4a.alloc(32, 0x42),
    b4a.alloc(32, 0x43),
    b4a.from([1, 1]),
    b4a.alloc(32, 0x44)
  ])
  const decoded = browser.decodeBlindExternalProfileValueV1('ReadCellCapV1', readCap)
  t.is(decoded.sizeClass, 1)
  t.ok(Object.isFrozen(decoded))
  const relayPublicKey = b4a.from(decoded.relayPublicKey)
  readCap.fill(0)
  t.ok(b4a.equals(decoded.relayPublicKey, relayPublicKey), 'browser decoder snapshots caller bytes')
  throwsCode(t, () => browser.decodeBlindExternalProfileValueV1(
    'WriteCellCapV1', b4a.alloc(131)), 'BAD_ENCODING')
})

test('artifact, manifest, tuple, and expected-manifest substitution fail closed', t => {
  for (const offset of [0, Math.floor(artifactBytes.byteLength / 2), artifactBytes.byteLength - 1]) {
    throwsCode(t, () => verifyBlindClientBrowserArtifactV1({
      artifactBytes: changed(artifactBytes, offset),
      manifestBytes,
      expectedManifestHash,
      expectedTuple
    }), 'BLIND_CLIENT_BROWSER_ARTIFACT_DRIFT')
  }
  for (let offset = 0; offset < manifestBytes.byteLength; offset++) {
    throwsCode(t, () => verifyBlindClientBrowserArtifactV1({
      artifactBytes,
      manifestBytes: changed(manifestBytes, offset),
      expectedManifestHash,
      expectedTuple
    }), 'BLIND_CLIENT_BROWSER_MANIFEST_DRIFT')
  }
  throwsCode(t, () => verifyBlindClientBrowserArtifactV1({
    artifactBytes,
    manifestBytes,
    expectedManifestHash: changed(expectedManifestHash, 0),
    expectedTuple
  }), 'BLIND_CLIENT_BROWSER_MANIFEST_DRIFT')
  throwsCode(t, () => verifyBlindClientBrowserArtifactV1({
    artifactBytes,
    manifestBytes,
    expectedManifestHash,
    expectedTuple: { ...expectedTuple, abiHash: changed(expectedTuple.abiHash, 0) }
  }), 'BLIND_CLIENT_BROWSER_TUPLE_MISMATCH')
  throwsCode(t, () => verifyBlindClientBrowserArtifactV1({
    artifactBytes,
    manifestBytes,
    expectedManifestHash,
    expectedTuple: {
      ...expectedTuple,
      clientCompositionFormatHash: changed(expectedTuple.clientCompositionFormatHash, 0)
    }
  }), 'BLIND_CLIENT_BROWSER_TUPLE_MISMATCH')
})

test('browser artifact manifest and verification inputs are exact and canonical', t => {
  for (let length = 0; length < manifestBytes.byteLength; length++) {
    throwsCode(t, () => decodeBlindClientBrowserArtifactManifestV1(
      manifestBytes.subarray(0, length)), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  }
  throwsCode(t, () => decodeBlindClientBrowserArtifactManifestV1(
    b4a.concat([manifestBytes, b4a.from([0])])), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  const magic = b4a.from('HIVERELAY-BLIND-CLIENT-BROWSER-V1', 'ascii')
  for (const offset of [0, magic.byteLength, magic.byteLength + 1]) {
    throwsCode(t, () => decodeBlindClientBrowserArtifactManifestV1(
      changed(manifestBytes, offset)), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  }
  const toolchainOffset = manifestBytes.indexOf(b4a.from(decodedManifest().toolchain))
  const invalidUtf8 = b4a.from(manifestBytes)
  invalidUtf8[toolchainOffset] = 0xff
  throwsCode(t, () => decodeBlindClientBrowserArtifactManifestV1(
    invalidUtf8), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  const zeroLength = b4a.from(manifestBytes)
  zeroLength.fill(0, zeroLength.byteLength - 40, zeroLength.byteLength - 32)
  throwsCode(t, () => decodeBlindClientBrowserArtifactManifestV1(
    zeroLength), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')

  for (const artifactLength of ['1', 1.5, NaN, {}, 0n, 327681n]) {
    throwsCode(t, () => encodeBlindClientBrowserArtifactManifestV1(
      manifestInput({ artifactLength })), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  }
  throwsCode(t, () => encodeBlindClientBrowserArtifactManifestV1(manifestInput({
    artifactPath: 'browser-artifacts/other.mjs'
  })), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  const inherited = manifestInput()
  Object.setPrototypeOf(inherited, { injected: true })
  throwsCode(t, () => encodeBlindClientBrowserArtifactManifestV1(
    inherited), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  const symbol = manifestInput()
  symbol[Symbol('hidden')] = true
  throwsCode(t, () => encodeBlindClientBrowserArtifactManifestV1(
    symbol), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  const accessor = manifestInput()
  Object.defineProperty(accessor, 'toolchain', {
    enumerable: true,
    get () { return decodedManifest().toolchain }
  })
  throwsCode(t, () => encodeBlindClientBrowserArtifactManifestV1(
    accessor), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  t.ok(b4a.equals(encodeBlindClientBrowserArtifactManifestV1(manifestInput({
    artifactLength: artifactBytes.byteLength
  })), manifestBytes), 'safe integer length canonicalizes to the exact u64 wire form')

  throwsCode(t, () => verifyBlindClientBrowserArtifactV1({
    artifactBytes,
    manifestBytes,
    expectedManifestHash,
    expectedTuple,
    extra: true
  }), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
  throwsCode(t, () => verifyBlindClientBrowserArtifactV1({
    artifactBytes,
    manifestBytes,
    expectedManifestHash,
    expectedTuple: { ...expectedTuple, extra: true }
  }), 'BAD_BLIND_CLIENT_BROWSER_ARTIFACT')
})

test('browser artifact stays package-addressable and regeneration check is current', t => {
  const generated = spawnSync(process.execPath, [fileURLToPath(generatorUrl), '--check'], {
    cwd: os.tmpdir(),
    encoding: 'utf8'
  })
  t.is(generated.status, 0)
  const generationReport = JSON.parse(generated.stdout)
  t.is(generationReport.schema, 'HiveRelayBlindClientBrowserArtifactGenerationV1')
  t.is(generationReport.releaseReady, true)
  const resolvedArtifact = fileURLToPath(import.meta.resolve(
    '@hiverelay/blind-client/browser-artifacts/blind-client-control-v1.mjs'))
  const resolvedManifest = fileURLToPath(import.meta.resolve(
    '@hiverelay/blind-client/browser-artifacts/blind-client-control-v1.manifest.cenc'))
  const resolvedChromiumEvidence = fileURLToPath(import.meta.resolve(
    '@hiverelay/blind-client/browser-artifacts/blind-client-control-v1.chromium-evidence.json'))
  const resolvedCrossHostEvidence = fileURLToPath(import.meta.resolve(
    '@hiverelay/blind-client/browser-artifacts/blind-client-control-v1.cross-host-evidence.json'))
  t.is(fs.realpathSync(resolvedArtifact), fs.realpathSync(fileURLToPath(artifactUrl)))
  t.is(fs.realpathSync(resolvedManifest), fs.realpathSync(fileURLToPath(manifestUrl)))
  t.is(fs.realpathSync(resolvedChromiumEvidence), fs.realpathSync(fileURLToPath(chromiumEvidenceUrl)))
  t.is(fs.realpathSync(resolvedCrossHostEvidence), fs.realpathSync(fileURLToPath(crossHostEvidenceUrl)))
  const packageJson = JSON.parse(fs.readFileSync(packageUrl, 'utf8'))
  t.is(packageJson.exports['./browser-artifacts/blind-client-control-v1.mjs'],
    './browser-artifacts/blind-client-control-v1.mjs')
  t.is(packageJson.exports['./browser-artifacts/blind-client-control-v1.manifest.cenc'],
    './browser-artifacts/blind-client-control-v1.manifest.cenc')
  t.is(packageJson.exports['./browser-artifacts/blind-client-control-v1.chromium-evidence.json'],
    './browser-artifacts/blind-client-control-v1.chromium-evidence.json')
  t.is(packageJson.exports['./browser-artifacts/blind-client-control-v1.cross-host-evidence.json'],
    './browser-artifacts/blind-client-control-v1.cross-host-evidence.json')
  t.ok(packageJson.files.includes('browser-artifacts/'))
  t.is(path.basename(resolvedArtifact), 'blind-client-control-v1.mjs')
})

test('ordinary style lint excludes only the generated bundle and retains dedicated byte verification', t => {
  const ignored = fs.readFileSync(eslintIgnoreUrl, 'utf8').split(/\r?\n/).filter(Boolean)
  t.alike(ignored, ['packages/blind-client/browser-artifacts/blind-client-control-v1.mjs'])
  const rootPackage = JSON.parse(fs.readFileSync(rootPackageUrl, 'utf8'))
  t.is(rootPackage.scripts['generate:blind-client:browser-artifact'],
    'node scripts/generate-blind-client-browser-artifacts.mjs')
  t.is(rootPackage.scripts['verify:blind-client:browser-artifact'],
    'node scripts/generate-blind-client-browser-artifacts.mjs --check')
})

test('browser production closure has no broad registry edge', t => {
  const clientRoot = fileURLToPath(new URL('..', import.meta.url))
  const protocolRoot = fileURLToPath(new URL('../../blind-protocol', import.meta.url))
  const sources = [
    ...fs.readdirSync(clientRoot).filter(name => name.endsWith('.js'))
      .map(name => path.join(clientRoot, name)),
    ...[
      'client-composition-external-codecs.js', 'codec.js', 'dispatch.js', 'errors.js',
      'external-profile-decoder.js', 'hashes.js', 'outer-envelope.js',
      'result-binding.js', 'schema-catalog-runtime-authority.js', 'schemas.js',
      'wire-runtime-authority.js'
    ].map(name => path.join(protocolRoot, name))
  ]
  for (const source of sources) {
    const text = fs.readFileSync(source, 'utf8')
    t.absent(text.includes("blind-protocol/registry'"), `${path.basename(source)} avoids package registry`)
    t.absent(text.includes("from './registry.js'"), `${path.basename(source)} avoids local registry`)
  }
})

test('browser artifact static status is fail closed until exact external evidence is verified', t => {
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.tupleBound, true)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.sourceClosureBound, true)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.deterministicGeneratorReady, true)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.sameHostByteEqualityProven, true)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.crossHostByteEqualityProven, false)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.realBrowserImportProven, false)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.packageSubpathsExported, true)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.maxArtifactBytes, 320 * 1024)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.maxArtifactGzipBytes, 90 * 1024)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.finalTupleBound, true)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.clientCompositionTupleBound, true)
  t.is(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.releaseReady, false)
  t.alike(BLIND_CLIENT_BROWSER_ARTIFACT_STATUS.releaseBlockers, [
    'REAL_CHROMIUM_HASH_BOUND_EVIDENCE_NOT_VERIFIED',
    'CROSS_HOST_HASH_BOUND_EVIDENCE_NOT_VERIFIED'
  ])
})

test('checked Chromium and clean-Linux records release the exact artifact only', t => {
  const chromiumEvidenceBytes = fs.readFileSync(chromiumEvidenceUrl)
  const crossHostEvidenceBytes = fs.readFileSync(crossHostEvidenceUrl)
  const status = verifyBlindClientBrowserArtifactReleaseEvidenceV1({
    artifactBytes,
    manifestBytes,
    expectedManifestHash,
    expectedTuple,
    chromiumEvidenceBytes,
    crossHostEvidenceBytes
  })
  t.is(status.releaseReady, true)
  t.is(status.realBrowserImportProven, true)
  t.is(status.crossHostByteEqualityProven, true)
  t.alike(status.releaseBlockers, [])
  t.is(status.artifactHash, b4a.toString(decodedManifest().artifactHash, 'hex'))
  t.is(status.manifestHash, b4a.toString(expectedManifestHash, 'hex'))

  for (const [field, value] of [
    ['chromiumEvidenceBytes', chromiumEvidenceBytes],
    ['crossHostEvidenceBytes', crossHostEvidenceBytes]
  ]) {
    const changedValue = JSON.parse(b4a.toString(value, 'utf8'))
    changedValue.artifactHash = `${changedValue.artifactHash[0] === '0' ? '1' : '0'}${changedValue.artifactHash.slice(1)}`
    const changedEvidence = b4a.from(`${JSON.stringify(changedValue, null, 2)}\n`, 'utf8')
    throwsCode(t, () => verifyBlindClientBrowserArtifactReleaseEvidenceV1({
      artifactBytes,
      manifestBytes,
      expectedManifestHash,
      expectedTuple,
      chromiumEvidenceBytes,
      crossHostEvidenceBytes,
      [field]: changedEvidence
    }), 'BLIND_CLIENT_BROWSER_RELEASE_EVIDENCE_MISMATCH')
  }
  throwsCode(t, () => verifyBlindClientBrowserArtifactReleaseEvidenceV1({
    artifactBytes: changed(artifactBytes, 0),
    manifestBytes,
    expectedManifestHash,
    expectedTuple,
    chromiumEvidenceBytes,
    crossHostEvidenceBytes
  }), 'BLIND_CLIENT_BROWSER_ARTIFACT_DRIFT')
})