OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-protocol / generate-client-composition-v2.mjs

177 lines · javascript ref:
import fs from 'node:fs/promises'
import path from 'node:path'
import process from 'node:process'
import { createHash } from 'node:crypto'
import b4a from 'b4a'
import {
  encodeVectorManifest,
  hashAbi,
  hashClientCompositionFormat,
  hashClientCompositionVectorSet,
  hashSpec
} from './hashes.js'
import {
  CLIENT_COMPOSITION_V2_SCHEMA_DECLARATIONS,
  createClientCompositionV2Value,
  encodeClientCompositionV2,
  encodeClientCompositionV2SchemaCatalog
} from './client-composition-v2.js'

const check = process.argv.includes('--check')
const root = path.dirname(new URL(import.meta.url).pathname)
const repoRoot = path.resolve(root, '../..')
const vectorRoot = path.join(root, 'vectors-v2/client-composition')
const legacyVectorRoot = path.join(root, 'vectors/client-composition-v2')
const hex = value => b4a.toString(value, 'hex')
const json = value => b4a.from(`${JSON.stringify(value, null, 2)}\n`)

const compatibilityFloor = Object.freeze({
  'hiverelay-blind-client-composition-authority-v1.json': '4b1cd5835a2952b5e06914e59890ad2ebb4fa085cbc294a79e24b91ff20ed160',
  'hiverelay-blind-client-composition-format-v1.cenc': 'a525dc297bf8771ecb7a9204b5b3c031bd292991b210421f3fa713619e296b60',
  'hiverelay-blind-client-composition-schema-catalog-v1.cenc': '8ff920aece109f681f94ee655c9f273f78c66013fcff818bc40df86b867bb97f',
  'hiverelay-blind-client-composition-vector-manifest-v1.cenc': 'b26ab9a86ccd665255ee17dd742ffc39acceeb670bc01a7f7633460cb9d7cee9',
  'client-composition-authority-generated.js': 'a66d7211bdedd3b0d0e580c7d6c504584001ee1bf5747ad9b27d8ee5e2b566aa'
})

function sha256 (bytes) {
  return createHash('sha256').update(bytes).digest('hex')
}

async function assertCompatibilityFloor () {
  for (const [relative, expected] of Object.entries(compatibilityFloor)) {
    const actual = sha256(await fs.readFile(path.join(root, relative)))
    if (actual !== expected) throw new Error(`frozen client composition v1 artifact changed: ${relative} ${actual}`)
  }
}

async function writeOrCheck (file, bytes) {
  if (!b4a.isBuffer(bytes)) bytes = b4a.from(bytes)
  if (check) {
    let current
    try {
      current = await fs.readFile(file)
    } catch {
      throw new Error(`missing generated client composition v2 artifact: ${path.relative(repoRoot, file)}`)
    }
    if (!b4a.equals(current, bytes)) throw new Error(`stale generated client composition v2 artifact: ${path.relative(repoRoot, file)}`)
    return
  }
  await fs.mkdir(path.dirname(file), { recursive: true })
  await fs.writeFile(file, bytes)
}

await assertCompatibilityFloor()
if (!check) await fs.rm(legacyVectorRoot, { recursive: true, force: true })

const v1FormatBytes = await fs.readFile(path.join(root, 'hiverelay-blind-client-composition-format-v1.cenc'))
const baseFormatHash = hashClientCompositionFormat(v1FormatBytes)
const wireV2AbiBytes = await fs.readFile(path.join(root, 'hiverelay-blind-abi-v2.cenc'))
const wireV2AbiHash = hashAbi(wireV2AbiBytes)
const value = createClientCompositionV2Value(baseFormatHash, wireV2AbiHash)
const formatBytes = encodeClientCompositionV2(value)
const catalogBytes = encodeClientCompositionV2SchemaCatalog()
const specBytes = await fs.readFile(path.join(repoRoot, 'docs/protocol/HIVERELAY-BLIND-CLIENT-COMPOSITION-V2.md'))

const endpointFixture = {
  version: 2,
  releaseProfileId: 2,
  routeKind: 7,
  wireV2AbiHash: hex(wireV2AbiHash),
  verifiedEndpointHandleHash: '11'.repeat(32),
  targetCatalogEntryId: '22'.repeat(32),
  targetRelayPublicKey: '33'.repeat(32),
  targetDescriptorSequence: '22',
  targetDescriptorHash: '44'.repeat(32),
  signedDescriptorHash: '55'.repeat(32),
  signedHealthHash: '66'.repeat(32),
  descriptorFresh: true,
  signedHealthFresh: true,
  credentialFreeHttps: true,
  cookies: false,
  authorization: false,
  referrer: false,
  redirect: false,
  exactRequestBytes: 65_536,
  exactResultBytes: 65_536,
  continuityBackend: 'INDEXEDDB_PERSISTENT'
}
const vectorFiles = [
  { path: 'registry/client-composition-format-v2.cenc', bytes: formatBytes },
  { path: 'registry/client-composition-schema-catalog-v2.cenc', bytes: catalogBytes },
  { path: 'registry/compatibility-floor.json', bytes: json(compatibilityFloor) },
  { path: 'positive/forward-https-verified-endpoint-v2.json', bytes: json(endpointFixture) },
  {
    path: 'negative/expectations.json',
    bytes: json({
      'caller-url-host-ip-dial-fields': 'unrepresentable-and-rejected',
      'non-persistent-continuity': 'reject',
      'cookies-authorization-referrer-or-redirect': 'reject',
      'forward-readiness-before-independent-acceptance': 'zero'
    })
  }
]
const manifestBytes = encodeVectorManifest(vectorFiles)
const authority = {
  profile: 'client-composition-authority-v2',
  authorityVersion: 2,
  formatMajor: 2,
  formatMinor: 0,
  specificationArtifact: 'docs/protocol/HIVERELAY-BLIND-CLIENT-COMPOSITION-V2.md',
  formatAuthorityArtifact: 'packages/blind-protocol/hiverelay-blind-client-composition-format-v2.cenc',
  schemaCatalogArtifact: 'packages/blind-protocol/hiverelay-blind-client-composition-schema-catalog-v2.cenc',
  vectorManifestArtifact: 'packages/blind-protocol/hiverelay-blind-client-composition-vector-manifest-v2.cenc',
  vectorRoot: 'packages/blind-protocol/vectors-v2/client-composition',
  baseCompositionV1FormatHash: hex(baseFormatHash),
  wireV2AbiHash: hex(wireV2AbiHash),
  formatHash: hex(hashClientCompositionFormat(formatBytes)),
  specificationHash: hex(hashSpec(specBytes)),
  vectorSetHash: hex(hashClientCompositionVectorSet(manifestBytes)),
  baseSchemaCount: 6,
  additionalSchemas: CLIENT_COMPOSITION_V2_SCHEMA_DECLARATIONS,
  schemaCount: 8,
  vectorCount: vectorFiles.length,
  forwardDescriptorOperationBits: 0,
  forwardAdvertisedOperationBits: 0,
  forwardReadinessOperationBits: 0,
  browserRuntimeReady: false,
  compatibilityFloor
}
const generatedSource = '/* eslint-disable */\n// Generated by generate-client-composition-v2.mjs. Do not edit.\n' +
  `export const CLIENT_COMPOSITION_AUTHORITY_V2 = Object.freeze(${JSON.stringify(authority, null, 2)})\n` +
  'export function assertClientCompositionAuthorityV2 (actual) {\n' +
  '  if (!actual || actual.formatHash !== CLIENT_COMPOSITION_AUTHORITY_V2.formatHash ||\n' +
  '      actual.wireV2AbiHash !== CLIENT_COMPOSITION_AUTHORITY_V2.wireV2AbiHash ||\n' +
  '      actual.schemaCount !== 8 || actual.forwardReadinessOperationBits !== 0 || actual.browserRuntimeReady !== false) {\n' +
  '    throw new Error(\'client composition v2 authority mismatch\')\n' +
  '  }\n' +
  '  return true\n' +
  '}\n'

const outputs = [
  [path.join(root, 'hiverelay-blind-client-composition-format-v2.cenc'), formatBytes],
  [path.join(root, 'hiverelay-blind-client-composition-schema-catalog-v2.cenc'), catalogBytes],
  [path.join(root, 'hiverelay-blind-client-composition-vector-manifest-v2.cenc'), manifestBytes],
  [path.join(root, 'hiverelay-blind-client-composition-authority-v2.json'), json(authority)],
  [path.join(root, 'client-composition-authority-generated-v2.js'), b4a.from(generatedSource)]
]
for (const vector of vectorFiles) outputs.push([path.join(vectorRoot, vector.path), vector.bytes])
for (const [file, bytes] of outputs) await writeOrCheck(file, bytes)

if (check) {
  const expected = new Set(vectorFiles.map(vector => vector.path))
  async function walk (directory, prefix = '') {
    const found = []
    for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
      const relative = prefix ? `${prefix}/${entry.name}` : entry.name
      if (entry.isDirectory()) found.push(...await walk(path.join(directory, entry.name), relative))
      else found.push(relative)
    }
    return found
  }
  for (const found of await walk(vectorRoot)) {
    if (!expected.has(found)) throw new Error(`unexpected client composition v2 vector: ${found}`)
  }
}

console.log(check ? 'client composition v2 authority verified' : 'client composition v2 authority generated')