OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-ipc / generate-private-ipc-v3.mjs

247 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 { decodeCanonical } from '@hiverelay/blind-protocol/codec'
import { encodeVectorManifest } from '@hiverelay/blind-protocol/hashes'
import {
  blindForwardHttpsTurnRequestV1,
  blindForwardHttpsTurnResultV1,
  encodeForwardHttpsParentCapabilityV1
} from '@hiverelay/blind-protocol'
import { hashImportedWireAbi } from './private-hashes.js'
import {
  LOCAL_FORWARD_HTTPS_DIRECTION_V3,
  PRIVATE_IPC_V3_ADDITIONAL_SCHEMAS,
  createPrivateIpcV3RegistryValue,
  encodeLocalForwardHttpsExporterBindingV3,
  encodeLocalForwardHttpsParentCapabilityV3,
  encodeLocalForwardHttpsTurnV3,
  encodePrivateIpcV3Registry,
  hashPrivateIpcV3Registry,
  hashPrivateIpcV3VectorManifest
} from './private-ipc-v3-contract.js'

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

const compatibilityFloor = Object.freeze({
  'hiverelay-blind-private-ipc-v1.cenc': '116c78ad151543ff9973d4cb92089ddfbc2e3f861b1f502b9aa0c85a5a52e4f6',
  'hiverelay-blind-private-ipc-v1.draft.cenc': '116c78ad151543ff9973d4cb92089ddfbc2e3f861b1f502b9aa0c85a5a52e4f6',
  'vector-manifest-v1.cenc': 'd9949afeeb9fc06db9864d388a6917ca99bbec244395932a707cd8ab9736e29e',
  'vectors/draft/vector-manifest-v1.draft.cenc': 'd9949afeeb9fc06db9864d388a6917ca99bbec244395932a707cd8ab9736e29e',
  'hiverelay-blind-private-ipc-authority-v1.json': 'cba3f4be2f616a66730a90ce92ce78a963cf5389d18e41b9cdfe4ae1eeb5a59d',
  'hiverelay-blind-private-ipc-v2.cenc': '3475a93f8d6da4d5c516ec0017f4eb8337a2397f89dbb0000ea004f225461344',
  'vector-manifest-v2.cenc': '0abb90824d9e5388d538ae6a49cee67437056987601717bb9bdb003218deccff',
  'hiverelay-blind-private-ipc-authority-v2.json': '9e2abeed720afcea9165775bca0dff165901a1eab97251a7677d66e6292143a7'
})

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

async function assertCompatibilityFloor () {
  for (const [relative, expected] of Object.entries(compatibilityFloor)) {
    const actual = sha256Bytes(await fs.readFile(path.join(packageRoot, relative)))
    if (actual !== expected) throw new Error(`frozen private IPC v1/v2 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 private IPC v3 artifact: ${path.relative(repoRoot, file)}`)
    }
    if (!b4a.equals(current, bytes)) throw new Error(`stale generated private IPC v3 artifact: ${path.relative(repoRoot, file)}`)
    return
  }
  await fs.mkdir(path.dirname(file), { recursive: true })
  await fs.writeFile(file, bytes)
}

function statusSource (authority) {
  const status = {
    profile: 'private-ipc-authority-v3',
    authorityArtifactPath: 'packages/blind-ipc/hiverelay-blind-private-ipc-v3.cenc',
    vectorManifestPath: 'packages/blind-ipc/vector-manifest-v3.cenc',
    authorityMetadataPath: 'packages/blind-ipc/hiverelay-blind-private-ipc-authority-v3.json',
    importedWireV2AbiHash: authority.importedWireV2AbiHash,
    basePrivateIpcV2FormatHash: authority.basePrivateIpcV2FormatHash,
    privateIpcFormatHash: authority.privateIpcFormatHash,
    privateIpcVectorSetHash: authority.privateIpcVectorSetHash,
    schemaCount: authority.schemaCount,
    additionalSchemaIds: [13, 14, 15],
    contractReady: true,
    forwardReadinessOperationBits: 0,
    releaseReady: false
  }
  const legacy = {
    authoritative: false,
    reason: 'inherited v1 source mirror drift; use versioned generated authorities',
    observedImportedWireAbiHash: 'aaf29c8225ee33a59a02f1d27b898aa5b4f9aec005c6e509dee450ffc87b1b0d',
    authoritativeV1ImportedWireAbiHash: '199ba15d94d4d112cfac520a67055ce15ec870f0f6f7bd9adaaf47d552334567',
    observedPrivateIpcFormatHash: '02aa29d91367bbb9a6ade977017a148cd78e7c184a4234084b82498ef5cc2bed',
    authoritativeV1PrivateIpcFormatHash: 'dd895c7a849609d647134216499cbbf9344ba06fc181413db6f1e27dc26da561'
  }
  return '/* eslint-disable */\n// Generated by generate-private-ipc-v3.mjs. Do not edit.\n' +
    `export const PRIVATE_IPC_V3_STATUS = Object.freeze(${JSON.stringify(status, null, 2)})\n` +
    `export const PRIVATE_IPC_LEGACY_STATUS_MIRROR_NOTICE = Object.freeze(${JSON.stringify(legacy, null, 2)})\n` +
    'export function assertPrivateIpcV3Status (actual) {\n' +
    '  if (!actual || actual.importedWireV2AbiHash !== PRIVATE_IPC_V3_STATUS.importedWireV2AbiHash ||\n' +
    '      actual.basePrivateIpcV2FormatHash !== PRIVATE_IPC_V3_STATUS.basePrivateIpcV2FormatHash ||\n' +
    '      actual.privateIpcFormatHash !== PRIVATE_IPC_V3_STATUS.privateIpcFormatHash ||\n' +
    '      actual.privateIpcVectorSetHash !== PRIVATE_IPC_V3_STATUS.privateIpcVectorSetHash ||\n' +
    '      actual.schemaCount !== PRIVATE_IPC_V3_STATUS.schemaCount || actual.forwardReadinessOperationBits !== 0) {\n' +
    '    const error = new Error(\'private IPC v3 authority status mismatch\')\n' +
    '    error.code = \'PRIVATE_IPC_V3_STATUS_MISMATCH\'\n' +
    '    throw error\n' +
    '  }\n' +
    '  return true\n' +
    '}\n'
}

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

const wireV2AbiBytes = await fs.readFile(path.join(protocolRoot, 'hiverelay-blind-abi-v2.cenc'))
const wireV2Hash = hashImportedWireAbi(wireV2AbiBytes)
const wireAuthority = JSON.parse(await fs.readFile(path.join(protocolRoot, 'hiverelay-blind-wire-authority-v2.json'), 'utf8'))
if (hex(wireV2Hash) !== wireAuthority.abiHash) throw new Error('WIRE v2 ABI hash does not match generated authority')
const v2Authority = JSON.parse(await fs.readFile(path.join(packageRoot, 'hiverelay-blind-private-ipc-authority-v2.json'), 'utf8'))
const baseV2FormatHash = b4a.from(v2Authority.privateIpcFormatHash, 'hex')
const registryBytes = encodePrivateIpcV3Registry(createPrivateIpcV3RegistryValue(wireV2Hash, baseV2FormatHash))

const requestBody = await fs.readFile(path.join(protocolRoot, 'vectors-v2/wire/positive/data-request-v1.bin'))
const resultBody = await fs.readFile(path.join(protocolRoot, 'vectors-v2/wire/positive/data-result-success-v1.bin'))
const request = decodeCanonical(blindForwardHttpsTurnRequestV1, requestBody, { copyBytes: true })
const result = decodeCanonical(blindForwardHttpsTurnResultV1, resultBody, { copyBytes: true })
const parentCapabilityBytes = encodeForwardHttpsParentCapabilityV1(request.parentCapability)

const bindingValue = {
  version: 3,
  authorityKind: 1,
  releaseProfileId: 2,
  routeKind: 7,
  edgeProcessNonce: b4a.alloc(32, 21),
  localChannelNonce: b4a.alloc(32, 22),
  tlsExporterBindingHash: request.parentCapability.tlsExporterBindingHash,
  sourceRelayPublicKey: request.parentCapability.sourceRelayPublicKey,
  sourceDescriptorSequence: request.parentCapability.sourceDescriptorSequence,
  sourceDescriptorHash: request.parentCapability.sourceDescriptorHash,
  targetCatalogEntryId: request.parentCapability.targetCatalogEntryId,
  targetRelayPublicKey: request.parentCapability.targetRelayPublicKey,
  targetDescriptorSequence: request.parentCapability.targetDescriptorSequence,
  targetDescriptorHash: request.parentCapability.targetDescriptorHash,
  routeId: request.parentCapability.routeId,
  circuitNonce: request.parentCapability.circuitNonce,
  issuedAtEpoch: request.parentCapability.issuedAtEpoch,
  expiresAtEpoch: request.parentCapability.expiresAtEpoch
}
const bindingBytes = encodeLocalForwardHttpsExporterBindingV3(bindingValue)
const capabilityBytes = encodeLocalForwardHttpsParentCapabilityV3({
  version: 3,
  sessionId: request.sessionId,
  parentCapability: request.parentCapability
})
const requestTurnBytes = encodeLocalForwardHttpsTurnV3({
  version: 3,
  direction: LOCAL_FORWARD_HTTPS_DIRECTION_V3.REQUEST,
  flags: 0,
  sessionId: request.sessionId,
  sequence: request.sequence,
  body: requestBody
})
const resultTurnBytes = encodeLocalForwardHttpsTurnV3({
  version: 3,
  direction: LOCAL_FORWARD_HTTPS_DIRECTION_V3.RESULT,
  flags: 0,
  sessionId: result.sessionId,
  sequence: result.sequence,
  body: resultBody
})
const changedSequence = b4a.from(requestTurnBytes)
changedSequence[47] ^= 1

const vectorFiles = [
  { path: 'registry/private-ipc-v3.cenc', bytes: registryBytes },
  { path: 'registry/additional-schemas.json', bytes: json(PRIVATE_IPC_V3_ADDITIONAL_SCHEMAS) },
  { path: 'registry/compatibility-floor.json', bytes: json(compatibilityFloor) },
  { path: 'positive/exporter-binding-v3.bin', bytes: bindingBytes },
  { path: 'positive/parent-capability-v3.bin', bytes: capabilityBytes },
  { path: 'positive/parent-capability-wire-v1.bin', bytes: parentCapabilityBytes },
  { path: 'positive/request-turn-v3.bin', bytes: requestTurnBytes },
  { path: 'positive/result-turn-v3.bin', bytes: resultTurnBytes },
  { path: 'negative/request-turn-sequence-mismatch.bin', bytes: changedSequence },
  {
    path: 'negative/expectations.json',
    bytes: json({
      'request-turn-sequence-mismatch.bin': 'reject-local-header-does-not-match-wire-body',
      'raw-tls-exporter-field': 'unrepresentable-and-rejected-by-source-encoder'
    })
  }
]
const manifestBytes = encodeVectorManifest(vectorFiles)
const authority = {
  authorityVersion: 3,
  authorizesRelease: false,
  contractReady: true,
  runtimeReleaseReady: false,
  importedWireV2AbiHash: hex(wireV2Hash),
  basePrivateIpcV2FormatHash: hex(baseV2FormatHash),
  privateIpcFormatHash: hex(hashPrivateIpcV3Registry(registryBytes)),
  privateIpcRegistrySha256: sha256Bytes(registryBytes),
  privateIpcVectorSetHash: hex(hashPrivateIpcV3VectorManifest(manifestBytes)),
  privateIpcVectorManifestSha256: sha256Bytes(manifestBytes),
  baseSchemaCount: 12,
  additionalSchemas: PRIVATE_IPC_V3_ADDITIONAL_SCHEMAS,
  schemaCount: 15,
  vectorCount: vectorFiles.length,
  forwardDescriptorOperationBits: 0,
  forwardAdvertisedOperationBits: 0,
  forwardReadinessOperationBits: 0,
  compatibilityFloor,
  releaseBlockers: [
    'runtime peer-credential enforcement and TLS exporter derivation evidence remain external',
    'capability signing, atomic spend accounting, retry recovery and terminal replay evidence remain external',
    'FORWARD descriptor, advertised and readiness bits remain zero pending independent acceptance'
  ]
}

const outputs = [
  [path.join(packageRoot, 'hiverelay-blind-private-ipc-v3.cenc'), registryBytes],
  [path.join(packageRoot, 'vector-manifest-v3.cenc'), manifestBytes],
  [path.join(packageRoot, 'hiverelay-blind-private-ipc-authority-v3.json'), json(authority)],
  [path.join(packageRoot, 'private-ipc-v3-status.js'), b4a.from(statusSource(authority))]
]
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 private IPC v3 vector: ${found}`)
  }
}

console.log(check ? 'private IPC v3 authority verified' : 'private IPC v3 authority generated')