OverviewFilesCommitsIssuesDocsReleasesPeers

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

349 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 {
  blindForwardHttpsOriginForwardTurnRequestV1,
  blindForwardHttpsOriginForwardTurnResultV1,
  forwardHttpsForwardedRequestCommitmentV1,
  forwardHttpsOriginRequestCommitmentV1
} from '@hiverelay/blind-protocol'
import { hashImportedWireAbi } from './private-hashes.js'
import {
  LOCAL_FORWARD_HTTPS_DIRECTION_V4,
  PRIVATE_IPC_V4_ADDITIONAL_SCHEMAS,
  PRIVATE_IPC_V4_LIMITS,
  createLocalForwardHttpsOriginAuthorityV4,
  createLocalForwardHttpsTargetIngressV4,
  createPrivateIpcV4RegistryValue,
  encodeLocalForwardHttpsOriginAuthorityV4,
  encodeLocalForwardHttpsSourceOriginTranscriptV4,
  encodeLocalForwardHttpsTargetIngressV4,
  encodeLocalForwardHttpsTurnV4,
  encodePrivateIpcV4Registry,
  forwardHttpsTargetTlsExporterBindingHashV4,
  forwardHttpsTargetTlsExporterContextV4,
  hashPrivateIpcV4Registry,
  hashPrivateIpcV4VectorManifest,
  localForwardHttpsSourceReplayTupleV4,
  localForwardHttpsTargetReplayTupleV4
} from './private-ipc-v4-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-v4')
const hex = value => b4a.toString(value, 'hex')
const fixed = (length, value) => b4a.alloc(length, value)
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',
  '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',
  'hiverelay-blind-private-ipc-v3.cenc': 'c3e109c36d4252399a1dab655cbc94291963fedfff9826630c82c7c33492b639',
  'vector-manifest-v3.cenc': '13ab3659deb5442189f52fb326dfb6b72e4050c0ce2de1fca39de94a20c7489f',
  'hiverelay-blind-private-ipc-authority-v3.json': '253d0c015c12cc67ab2b599992c2b23ea2256ebd5a669d0799f8c21181b3abf3',
  'private-ipc-v3-contract.js': 'f0baeb69c787d461f052468a31c490274b39854a97927583047691c58285f730',
  'private-ipc-v3-status.js': 'f8fbd4c28c955755ca2a42f5c5bd714bd9ecc3b87fbb685d1410553ec1d1369c'
})

const sha256 = bytes => 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(packageRoot, relative)))
    if (actual !== expected) throw new Error(`frozen private IPC v1/v2/v3 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 v4 artifact: ${path.relative(repoRoot, file)}`)
    }
    if (!b4a.equals(current, bytes)) throw new Error(`stale generated private IPC v4 artifact: ${path.relative(repoRoot, file)}`)
    return
  }
  await fs.mkdir(path.dirname(file), { recursive: true })
  await fs.writeFile(file, bytes)
}

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

function statusSource (authority) {
  const status = {
    profile: 'private-ipc-authority-v4',
    authorityArtifactPath: 'packages/blind-ipc/hiverelay-blind-private-ipc-v4.cenc',
    vectorManifestPath: 'packages/blind-ipc/vector-manifest-v4.cenc',
    authorityMetadataPath: 'packages/blind-ipc/hiverelay-blind-private-ipc-authority-v4.json',
    importedWireV3AbiHash: authority.importedWireV3AbiHash,
    basePrivateIpcV3FormatHash: authority.basePrivateIpcV3FormatHash,
    privateIpcFormatHash: authority.privateIpcFormatHash,
    privateIpcVectorSetHash: authority.privateIpcVectorSetHash,
    schemaCount: authority.schemaCount,
    additionalSchemaIds: [16, 17, 18],
    contractReady: true,
    forwardDescriptorOperationBits: 0,
    forwardAdvertisedOperationBits: 0,
    forwardReadinessOperationBits: 0,
    runtimeReady: false,
    releaseReady: false,
    authorizesRelease: false
  }
  return '/* eslint-disable */\n// Generated by generate-private-ipc-v4.mjs. Do not edit.\n' +
    `export const PRIVATE_IPC_V4_STATUS = Object.freeze(${JSON.stringify(status, null, 2)})\n` +
    'export function assertPrivateIpcV4Status (actual) {\n' +
    '  if (!actual || actual.importedWireV3AbiHash !== PRIVATE_IPC_V4_STATUS.importedWireV3AbiHash ||\n' +
    '      actual.basePrivateIpcV3FormatHash !== PRIVATE_IPC_V4_STATUS.basePrivateIpcV3FormatHash ||\n' +
    '      actual.privateIpcFormatHash !== PRIVATE_IPC_V4_STATUS.privateIpcFormatHash ||\n' +
    '      actual.privateIpcVectorSetHash !== PRIVATE_IPC_V4_STATUS.privateIpcVectorSetHash ||\n' +
    '      actual.schemaCount !== 18 || actual.forwardReadinessOperationBits !== 0 ||\n' +
    '      actual.runtimeReleaseReady !== false || actual.authorizesRelease !== false) {\n' +
    '    const error = new Error(\'private IPC v4 authority status mismatch\')\n' +
    '    error.code = \'PRIVATE_IPC_V4_STATUS_MISMATCH\'\n' +
    '    throw error\n' +
    '  }\n' +
    '  return true\n' +
    '}\n'
}

await assertCompatibilityFloor()

const wireAbiBytes = await fs.readFile(path.join(protocolRoot, 'hiverelay-blind-abi-v3.cenc'))
const wireV3AbiHash = hashImportedWireAbi(wireAbiBytes)
const wireAuthority = JSON.parse(await fs.readFile(path.join(protocolRoot, 'hiverelay-blind-wire-authority-v3.json'), 'utf8'))
if (hex(wireV3AbiHash) !== wireAuthority.abiHash) throw new Error('WIRE v3 ABI hash does not match generated authority')
const ipcV3Authority = JSON.parse(await fs.readFile(path.join(packageRoot, 'hiverelay-blind-private-ipc-authority-v3.json'), 'utf8'))
if (ipcV3Authority.privateIpcFormatHash !== 'efb4fd8eae1a2338722deced991fdc907b465d7580acfe2bde8ad692dc1c8200') {
  throw new Error('frozen private IPC v3 format hash mismatch')
}
const basePrivateIpcV3FormatHash = b4a.from(ipcV3Authority.privateIpcFormatHash, 'hex')
const registryBytes = encodePrivateIpcV4Registry(createPrivateIpcV4RegistryValue(wireV3AbiHash, basePrivateIpcV3FormatHash))

const openOriginBytes = await fs.readFile(path.join(protocolRoot, 'vectors-v3/wire/positive/open-origin.bin'))
const openForwardedBytes = await fs.readFile(path.join(protocolRoot, 'vectors-v3/wire/positive/open-forwarded.bin'))
const targetResultBytes = await fs.readFile(path.join(protocolRoot, 'vectors-v3/wire/positive/open-target-result.bin'))
const sourceErrorBytes = await fs.readFile(path.join(protocolRoot, 'vectors-v3/wire/positive/data-target-source-error.bin'))
const sourceAmbiguousBytes = await fs.readFile(path.join(protocolRoot, 'vectors-v3/wire/positive/data-target-source-ambiguous.bin'))
const origin = decodeCanonical(blindForwardHttpsOriginForwardTurnRequestV1, openOriginBytes, { copyBytes: true })
const forwarded = decodeCanonical(blindForwardHttpsOriginForwardTurnRequestV1, openForwardedBytes, { copyBytes: true })

const originAuthority = createLocalForwardHttpsOriginAuthorityV4({
  version: 4,
  authorityKind: 1,
  transportId: 1,
  endpointId: 1,
  flags: 0,
  wireV3AbiHash,
  signedLaunchTopologyHash: fixed(32, 71),
  edgeProcessNonce: fixed(32, 72),
  localChannelNonce: fixed(32, 73),
  tlsExporterBindingHash: fixed(32, 74),
  originRequestCommitment: forwardHttpsOriginRequestCommitmentV1(openOriginBytes),
  stableSessionId: origin.stableSessionId,
  sequence: origin.sequence,
  acceptedMonotonicMillis: 1_000_000n,
  absoluteDeadlineMonotonicMillis: 1_015_000n
})
const originAuthorityBytes = encodeLocalForwardHttpsOriginAuthorityV4(originAuthority)
const originTurn = {
  version: 4,
  direction: LOCAL_FORWARD_HTTPS_DIRECTION_V4.ORIGIN_REQUEST,
  wireRole: 0,
  flags: 0,
  wireV3AbiHash,
  localExchangeId: originAuthority.localExchangeId,
  originRequestCommitment: originAuthority.originRequestCommitment,
  stableSessionId: origin.stableSessionId,
  sequence: origin.sequence,
  body: openOriginBytes
}
const originTurnBytes = encodeLocalForwardHttpsTurnV4(originTurn)
const sourceTranscriptBytes = encodeLocalForwardHttpsSourceOriginTranscriptV4(originAuthority, originTurn)

const targetContext = forwardHttpsTargetTlsExporterContextV4(
  forwarded.stableSessionId,
  forwarded.sequence,
  forwardHttpsForwardedRequestCommitmentV1(openForwardedBytes)
)
const targetBinding = forwardHttpsTargetTlsExporterBindingHashV4(fixed(32, 75), targetContext)
const targetIngress = createLocalForwardHttpsTargetIngressV4({
  endpointId: 2,
  wireV3AbiHash,
  signedLaunchTopologyHash: fixed(32, 71),
  edgeProcessNonce: fixed(32, 76),
  localChannelNonce: fixed(32, 77),
  targetTlsExporterBindingHash: targetBinding,
  acceptedMonotonicMillis: 2_000_000n,
  absoluteDeadlineMonotonicMillis: 2_015_000n,
  body: openForwardedBytes
})
const targetIngressBytes = encodeLocalForwardHttpsTargetIngressV4(targetIngress)

function resultTurn (body, localExchangeId) {
  const result = decodeCanonical(blindForwardHttpsOriginForwardTurnResultV1, body, { copyBytes: true })
  return encodeLocalForwardHttpsTurnV4({
    version: 4,
    direction: LOCAL_FORWARD_HTTPS_DIRECTION_V4.RESULT,
    wireRole: result.resultRole,
    flags: 0,
    wireV3AbiHash,
    localExchangeId,
    originRequestCommitment: result.originRequestCommitment,
    stableSessionId: result.stableSessionId,
    sequence: result.sequence,
    body
  })
}

const targetResultTurnBytes = resultTurn(targetResultBytes, targetIngress.targetLocalExchangeId)
const sourceErrorTurnBytes = resultTurn(sourceErrorBytes, originAuthority.localExchangeId)
const sourceAmbiguousTurnBytes = resultTurn(sourceAmbiguousBytes, originAuthority.localExchangeId)
const replacementTargetContext = forwardHttpsTargetTlsExporterContextV4(
  forwarded.stableSessionId,
  forwarded.sequence,
  targetIngress.forwardedRequestCommitment
)
const replacementTargetIngress = createLocalForwardHttpsTargetIngressV4({
  ...targetIngress,
  localChannelNonce: fixed(32, 78),
  targetTlsExporterBindingHash: forwardHttpsTargetTlsExporterBindingHashV4(fixed(32, 79), replacementTargetContext),
  acceptedMonotonicMillis: 2_001_000n,
  absoluteDeadlineMonotonicMillis: 2_016_000n,
  targetLocalExchangeId: undefined
})
const replacementOriginAuthority = createLocalForwardHttpsOriginAuthorityV4({
  ...originAuthority,
  localChannelNonce: fixed(32, 80),
  tlsExporterBindingHash: fixed(32, 81),
  acceptedMonotonicMillis: 1_001_000n,
  absoluteDeadlineMonotonicMillis: 1_016_000n,
  localExchangeId: undefined
})

const vectorFiles = [
  { path: 'registry/private-ipc-v4.cenc', bytes: registryBytes },
  { path: 'registry/additional-schemas.json', bytes: json(PRIVATE_IPC_V4_ADDITIONAL_SCHEMAS) },
  { path: 'registry/compatibility-floor.json', bytes: json(compatibilityFloor) },
  { path: 'positive/source-origin-authority-v4.bin', bytes: originAuthorityBytes },
  { path: 'positive/source-origin-turn-v4.bin', bytes: originTurnBytes },
  { path: 'positive/source-origin-transcript-v4.bin', bytes: sourceTranscriptBytes },
  { path: 'positive/source-result-target-v4.bin', bytes: resultTurn(targetResultBytes, originAuthority.localExchangeId) },
  { path: 'positive/source-result-error-v4.bin', bytes: sourceErrorTurnBytes },
  { path: 'positive/source-result-ambiguous-v4.bin', bytes: sourceAmbiguousTurnBytes },
  { path: 'positive/target-ingress-v4.bin', bytes: targetIngressBytes },
  { path: 'positive/target-result-turn-v4.bin', bytes: targetResultTurnBytes },
  { path: 'positive/target-tls-context-v4.bin', bytes: targetContext },
  { path: 'positive/target-tls-binding-v4.bin', bytes: targetBinding },
  { path: 'positive/source-replay-tuple-v4.bin', bytes: localForwardHttpsSourceReplayTupleV4(originAuthority) },
  { path: 'positive/target-replay-tuple-v4.bin', bytes: localForwardHttpsTargetReplayTupleV4(targetIngress) },
  { path: 'positive/replacement-source-authority-v4.bin', bytes: encodeLocalForwardHttpsOriginAuthorityV4(replacementOriginAuthority) },
  { path: 'positive/replacement-target-ingress-v4.bin', bytes: encodeLocalForwardHttpsTargetIngressV4(replacementTargetIngress) },
  { path: 'negative/source-origin-bad-magic.bin', bytes: mutate(sourceTranscriptBytes, 0) },
  { path: 'negative/source-origin-extra-record.bin', bytes: b4a.concat([sourceTranscriptBytes, fixed(1, 1)]) },
  { path: 'negative/source-origin-role-forwarded.bin', bytes: mutate(sourceTranscriptBytes, 292 + 6) },
  { path: 'negative/source-origin-exchange-mismatch.bin', bytes: mutate(sourceTranscriptBytes, 292 + 40) },
  { path: 'negative/target-ingress-origin-role.bin', bytes: b4a.concat([targetIngressBytes.subarray(0, 292), openOriginBytes]) },
  { path: 'negative/target-ingress-bad-binding.bin', bytes: mutate(targetIngressBytes, 140) },
  { path: 'negative/target-ingress-bad-exchange.bin', bytes: mutate(targetIngressBytes, 260) },
  { path: 'negative/target-result-source-role.bin', bytes: mutate(targetResultTurnBytes, 6) },
  {
    path: 'negative/expectations.json',
    bytes: json({
      'source-origin-bad-magic.bin': 'reject-ID16-magic',
      'source-origin-extra-record.bin': 'reject-exact-transcript-length',
      'source-origin-role-forwarded.bin': 'reject-source-socket-role-confusion',
      'source-origin-exchange-mismatch.bin': 'reject-ID16-ID17-binding',
      'target-ingress-origin-role.bin': 'reject-target-role-origin-template',
      'target-ingress-bad-binding.bin': 'reject-target-exchange-recomputation',
      'target-ingress-bad-exchange.bin': 'reject-target-exchange-recomputation',
      'target-result-source-role.bin': 'reject-target-result-role-confusion',
      'missing-eof': 'reject-before-claim-sign-spend-dial-or-response',
      'ID16-on-target-socket': 'reject-record-family',
      'ID18-on-source-socket': 'reject-record-family',
      'outer-envelope-or-native-stream-fallback': 'unrepresentable',
      'raw-exporter-source-address-credentials': 'unrepresentable',
      'socket-or-journal-alias': 'reject'
    })
  }
]
const manifestBytes = encodeVectorManifest(vectorFiles)
const authority = {
  authorityVersion: 4,
  formatVersion: 4,
  authorizesRelease: false,
  contractReady: true,
  runtimeReleaseReady: false,
  importedWireV3AbiHash: hex(wireV3AbiHash),
  basePrivateIpcV3FormatHash: hex(basePrivateIpcV3FormatHash),
  privateIpcFormatHash: hex(hashPrivateIpcV4Registry(registryBytes)),
  privateIpcRegistrySha256: sha256(registryBytes),
  privateIpcVectorSetHash: hex(hashPrivateIpcV4VectorManifest(manifestBytes)),
  privateIpcVectorManifestSha256: sha256(manifestBytes),
  baseSchemaCount: 15,
  additionalSchemas: PRIVATE_IPC_V4_ADDITIONAL_SCHEMAS,
  schemaCount: 18,
  vectorCount: vectorFiles.length,
  exactByteLimits: {
    sourceOriginRequest: PRIVATE_IPC_V4_LIMITS.SOURCE_ORIGIN_TRANSCRIPT_BYTES,
    targetIngressRequest: PRIVATE_IPC_V4_LIMITS.TARGET_INGRESS_BYTES,
    result: PRIVATE_IPC_V4_LIMITS.RESULT_TRANSCRIPT_BYTES,
    targetLocalRequestPlusResult: PRIVATE_IPC_V4_LIMITS.TARGET_LOCAL_REQUEST_PLUS_RESULT_BYTES
  },
  replayPayloadBytes: PRIVATE_IPC_V4_LIMITS.REPLAY_PAYLOAD_BYTES,
  replayCapacity: PRIVATE_IPC_V4_LIMITS.REPLAY_CAPACITY,
  maxDeadlineMillis: PRIVATE_IPC_V4_LIMITS.MAX_DEADLINE_MILLIS,
  forwardDescriptorOperationBits: 0,
  forwardAdvertisedOperationBits: 0,
  forwardReadinessOperationBits: 0,
  compatibilityFloor,
  modelOnly: true,
  releaseBlockers: [
    'filesystem journals, fsync, dedicated socket enforcement and tamper recovery remain runtime/storage work',
    'native peercred enforcement, real TLS exporter extraction and crash injection remain runtime work',
    'FORWARD descriptor, advertised and readiness bits remain zero pending independent acceptance'
  ]
}

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

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