OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-ipc / test / private-ipc-v4-contract.test.js

595 lines · javascript ref:
import test from 'brittle'
import b4a from 'b4a'
import fs from 'node:fs'
import path from 'node:path'
import {
  FORWARD_HTTPS_RESULT_ROLE_V1,
  blindForwardHttpsOriginForwardTurnRequestV1,
  forwardHttpsForwardedRequestCommitmentV1,
  forwardHttpsSourceTransformSignaturePayloadV1,
  forwardHttpsTargetResultChainHashV1
} from '@hiverelay/blind-protocol'
import { decodeCanonical, encodeCanonical } from '@hiverelay/blind-protocol/codec'
import sodium from '../../blind-protocol/crypto.js'
import { privateBlake2b256 } from '../private-hashes.js'
import {
  FORWARD_HTTPS_TARGET_TLS_EXPORTER_LABEL_V4,
  LOCAL_FORWARD_HTTPS_DIRECTION_V4,
  PRIVATE_IPC_V4_ADDITIONAL_SCHEMAS,
  PRIVATE_IPC_V4_LIMITS,
  PRIVATE_IPC_V4_SCHEMA,
  LocalForwardHttpsReplayJournalModelV4,
  LocalForwardHttpsTargetClaimModelV4,
  LocalForwardHttpsTranscriptAccumulatorV4,
  assertLocalForwardHttpsResultTranscriptV4,
  assertLocalForwardHttpsSocketSeparationV4,
  decodeLocalForwardHttpsOriginAuthorityV4,
  decodeLocalForwardHttpsSourceOriginTranscriptV4,
  decodeLocalForwardHttpsTargetIngressTranscriptV4,
  decodeLocalForwardHttpsTargetIngressV4,
  decodeLocalForwardHttpsTurnV4,
  decodePrivateIpcV4Registry,
  encodeLocalForwardHttpsOriginAuthorityV4,
  encodeLocalForwardHttpsTargetIngressV4,
  forwardHttpsTargetTlsExporterBindingHashV4,
  forwardHttpsTargetTlsExporterContextV4,
  hashPrivateIpcV4Registry,
  hashPrivateIpcV4VectorManifest,
  localForwardHttpsExchangeIdV4,
  localForwardHttpsSourceReplayTupleV4,
  localForwardHttpsTargetReplayTupleV4,
  targetLocalForwardHttpsExchangeIdV4
} from '../private-ipc-v4-contract.js'
import { PRIVATE_IPC_V4_STATUS, assertPrivateIpcV4Status } from '../private-ipc-v4-status.js'

const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..')
const read = relative => fs.readFileSync(path.join(root, relative))
const vector = name => read(`vectors-v4/${name}`)
const wireVector = name => read(`../blind-protocol/vectors-v3/wire/${name}`)

const sourcePublicKey = b4a.alloc(32)
const sourceSecretKey = b4a.alloc(64)
sodium.crypto_sign_seed_keypair(sourcePublicKey, sourceSecretKey, b4a.alloc(32, 41))

function resignForwarded (input, mutate) {
  const request = decodeCanonical(blindForwardHttpsOriginForwardTurnRequestV1, input, { copyBytes: true })
  mutate(request)
  request.sourceTransformSignature = b4a.alloc(64, 1)
  const provisional = encodeCanonical(blindForwardHttpsOriginForwardTurnRequestV1, request)
  const signature = b4a.alloc(64)
  sodium.crypto_sign_detached(signature, forwardHttpsSourceTransformSignaturePayloadV1(provisional), sourceSecretKey)
  request.sourceTransformSignature = signature
  return encodeCanonical(blindForwardHttpsOriginForwardTurnRequestV1, request)
}

function openedTargetClaims () {
  const claims = new LocalForwardHttpsTargetClaimModelV4()
  const open = wireVector('positive/open-forwarded-role.bin')
  const result = wireVector('positive/open-target-open-accept.bin')
  claims.claim(open)
  claims.persistResult(open, result)
  return claims
}

function length64 (length) {
  let value = BigInt(length)
  const output = b4a.alloc(8)
  for (let index = 7; index >= 0; index--) {
    output[index] = Number(value & 0xffn)
    value >>= 8n
  }
  return output
}

function u64 (value) {
  const output = b4a.alloc(8)
  for (let index = 7; index >= 0; index--) {
    output[index] = Number(value & 0xffn)
    value >>= 8n
  }
  return output
}

function errorFrom (fn) {
  try {
    fn()
  } catch (error) {
    return error
  }
  throw new Error('expected function to throw')
}

function journalSnapshot (value) {
  const payload = b4a.from(typeof value === 'string' ? value : JSON.stringify(value), 'utf8')
  const domain = b4a.from('hiverelay.blind.private-ipc-forward-journal-model.v4', 'ascii')
  return { payload, checksum: privateBlake2b256(b4a.concat([domain, length64(payload.byteLength), payload])) }
}

const restartOptions = (expectedNamespace, expectedCapacity, nowMonotonicMillis) => ({
  expectedNamespace,
  expectedCapacity,
  nowMonotonicMillis
})

test('private IPC v4 imports frozen IPC v3 and WIRE v3 and appends only IDs 16/17/18', t => {
  const registryBytes = read('hiverelay-blind-private-ipc-v4.cenc')
  const manifestBytes = read('vector-manifest-v4.cenc')
  const registry = decodePrivateIpcV4Registry(registryBytes)
  const authority = JSON.parse(read('hiverelay-blind-private-ipc-authority-v4.json'))
  t.alike(PRIVATE_IPC_V4_SCHEMA, {
    LocalForwardHttpsOriginAuthorityV4: 16,
    LocalForwardHttpsTurnV4: 17,
    LocalForwardHttpsTargetIngressV4: 18
  })
  t.is(registry.baseSchemaCount, 15)
  t.alike(registry.additionalSchemas.map(value => value.schemaId), [16, 17, 18])
  t.is(b4a.toString(registry.basePrivateIpcV3FormatHash, 'hex'), 'efb4fd8eae1a2338722deced991fdc907b465d7580acfe2bde8ad692dc1c8200')
  t.is(b4a.toString(registry.importedWireV3AbiHash, 'hex'), PRIVATE_IPC_V4_STATUS.importedWireV3AbiHash)
  t.is(b4a.toString(hashPrivateIpcV4Registry(registryBytes), 'hex'), authority.privateIpcFormatHash)
  t.is(b4a.toString(hashPrivateIpcV4VectorManifest(manifestBytes), 'hex'), authority.privateIpcVectorSetHash)
  t.is(registry.forwardReadinessOperationBits, 0)
  t.ok(assertPrivateIpcV4Status(authority))
  t.is(PRIVATE_IPC_V4_STATUS.runtimeReady, false)
  t.is(PRIVATE_IPC_V4_STATUS.releaseReady, false)
  t.is(PRIVATE_IPC_V4_STATUS.authorizesRelease, false)
  t.exception(() => decodePrivateIpcV4Registry(b4a.concat([registryBytes, b4a.from([0])])), /canonical/)
})

test('private IPC v4 declaration bytes preserve exact name/fields order and ID18 semantic tail', t => {
  const registry = decodePrivateIpcV4Registry(read('hiverelay-blind-private-ipc-v4.cenc'))
  for (const [index, declaration] of PRIVATE_IPC_V4_ADDITIONAL_SCHEMAS.entries()) {
    const expected = b4a.from(JSON.stringify({ name: declaration.schemaName, fields: declaration.fields }), 'utf8')
    t.alike(registry.additionalSchemas[index].canonicalDeclarationBytes, expected)
  }
  t.alike(PRIVATE_IPC_V4_ADDITIONAL_SCHEMAS[2].fields.slice(-6), [
    'targetTlsExporterLabel:ASCII(EXPORTER-HiveRelay-Blind-Forward-Target-v1)',
    'targetTlsExporterContext:BLAKE2b256(ASCII(hiverelay.blind.private-ipc-forward-target-tls-context.v4)||stableSessionId32||u64be(sequence)||forwardedRequestCommitment32)',
    'targetTlsExporterBindingHash:BLAKE2b256(ASCII(hiverelay.blind.private-ipc-forward-target-tls-binding.v4)||u64be(64)||targetTlsExporterSecret32||targetTlsExporterContext32)',
    'targetLocalExchangeId:BLAKE2b256(ASCII(hiverelay.blind.private-ipc-forward-target-exchange.v4)||u64be(260)||bytes[0:260])',
    'body:exact canonical WIRE ID76 requestRole FORWARDED; commitment/session/sequence match header; no origin/outer-envelope/native-stream/fallback form',
    'raw target TLS exporter, source address, URL, host, IP, cookies, authorization, credentials, and app metadata are unrepresentable'
  ])
})

test('source-origin transcript is exact ID16||ID17, requires EOF, and binds every duplicated field', t => {
  const transcript = vector('positive/source-origin-transcript-v4.bin')
  t.is(transcript.byteLength, 65_976)
  const decoded = decodeLocalForwardHttpsSourceOriginTranscriptV4(transcript, { eof: true })
  t.is(decoded.turn.direction, LOCAL_FORWARD_HTTPS_DIRECTION_V4.ORIGIN_REQUEST)
  t.is(decoded.turn.wireRole, 0)
  t.alike(decoded.authority.localExchangeId, localForwardHttpsExchangeIdV4(transcript.subarray(0, 260)))
  t.alike(encodeLocalForwardHttpsOriginAuthorityV4(decoded.authority), transcript.subarray(0, 292))
  t.exception(() => decodeLocalForwardHttpsSourceOriginTranscriptV4(transcript), /EOF/)
  t.exception(() => decodeLocalForwardHttpsSourceOriginTranscriptV4(vector('negative/source-origin-extra-record.bin'), { eof: true }), /exactly 65976/)
  t.exception(() => decodeLocalForwardHttpsSourceOriginTranscriptV4(vector('negative/source-origin-role-forwarded.bin'), { eof: true }), /role|body|canonical/i)
  t.exception(() => decodeLocalForwardHttpsSourceOriginTranscriptV4(vector('negative/source-origin-exchange-mismatch.bin'), { eof: true }), /bind/)
})

test('target ingress is exact composite ID18 with unchanged role-FORWARDED ID76 and target-only result', t => {
  const input = vector('positive/target-ingress-v4.bin')
  t.is(input.byteLength, 65_828)
  const ingress = decodeLocalForwardHttpsTargetIngressTranscriptV4(input, { eof: true })
  const request = decodeCanonical(blindForwardHttpsOriginForwardTurnRequestV1, ingress.body, { copyBytes: true })
  t.is(request.requestRole, 1)
  t.alike(ingress.forwardedRequestCommitment, forwardHttpsForwardedRequestCommitmentV1(ingress.body))
  t.alike(ingress.targetLocalExchangeId, targetLocalForwardHttpsExchangeIdV4(input))
  t.alike(encodeLocalForwardHttpsTargetIngressV4(ingress), input)
  const result = assertLocalForwardHttpsResultTranscriptV4(
    vector('positive/target-result-turn-v4.bin'), ingress, FORWARD_HTTPS_RESULT_ROLE_V1.TARGET_RESULT)
  t.is(result.wireRole, FORWARD_HTTPS_RESULT_ROLE_V1.TARGET_RESULT)
  t.exception(() => decodeLocalForwardHttpsTargetIngressTranscriptV4(input), /EOF/)
  t.exception(() => decodeLocalForwardHttpsTargetIngressV4(vector('negative/target-ingress-origin-role.bin')), /FORWARDED|commitment|canonical/)
  t.exception(() => decodeLocalForwardHttpsTargetIngressV4(vector('negative/target-ingress-bad-binding.bin')), /ExchangeId|canonical/)
  t.exception(() => decodeLocalForwardHttpsTargetIngressV4(vector('negative/target-ingress-bad-exchange.bin')), /ExchangeId|canonical/)
  t.exception(() => assertLocalForwardHttpsResultTranscriptV4(
    vector('positive/source-result-error-v4.bin'), ingress, FORWARD_HTTPS_RESULT_ROLE_V1.TARGET_RESULT), /result transcript/)
})

test('target TLS context, binding, exchange and replay use exact independent domains', t => {
  const ingress = decodeLocalForwardHttpsTargetIngressV4(vector('positive/target-ingress-v4.bin'))
  const context = forwardHttpsTargetTlsExporterContextV4(
    ingress.stableSessionId, ingress.sequence, ingress.forwardedRequestCommitment)
  t.is(FORWARD_HTTPS_TARGET_TLS_EXPORTER_LABEL_V4, 'EXPORTER-HiveRelay-Blind-Forward-Target-v1')
  t.alike(context, vector('positive/target-tls-context-v4.bin'))
  t.alike(context, privateBlake2b256(b4a.concat([
    b4a.from('hiverelay.blind.private-ipc-forward-target-tls-context.v4', 'ascii'),
    ingress.stableSessionId, u64(ingress.sequence), ingress.forwardedRequestCommitment
  ])))
  const secret = b4a.alloc(32, 75)
  t.alike(forwardHttpsTargetTlsExporterBindingHashV4(secret, context), privateBlake2b256(b4a.concat([
    b4a.from('hiverelay.blind.private-ipc-forward-target-tls-binding.v4', 'ascii'),
    length64(64), secret, context
  ])))
  t.alike(targetLocalForwardHttpsExchangeIdV4(vector('positive/target-ingress-v4.bin')), privateBlake2b256(b4a.concat([
    b4a.from('hiverelay.blind.private-ipc-forward-target-exchange.v4', 'ascii'),
    length64(260), vector('positive/target-ingress-v4.bin').subarray(0, 260)
  ])))
})

test('source and target replay tuples have exact 232-byte role-separated preimages', t => {
  const source = decodeLocalForwardHttpsOriginAuthorityV4(vector('positive/source-origin-authority-v4.bin'))
  const target = decodeLocalForwardHttpsTargetIngressV4(vector('positive/target-ingress-v4.bin'))
  const sourcePayload = b4a.concat([
    source.wireV3AbiHash, source.signedLaunchTopologyHash, source.edgeProcessNonce, source.localChannelNonce,
    source.originRequestCommitment, source.stableSessionId, u64(source.sequence), source.localExchangeId
  ])
  const targetPayload = b4a.concat([
    target.wireV3AbiHash, target.signedLaunchTopologyHash, target.edgeProcessNonce, target.localChannelNonce,
    target.forwardedRequestCommitment, target.stableSessionId, u64(target.sequence), target.targetLocalExchangeId
  ])
  t.is(sourcePayload.byteLength, 232)
  t.is(targetPayload.byteLength, 232)
  const sourceTuple = localForwardHttpsSourceReplayTupleV4(source)
  const targetTuple = localForwardHttpsTargetReplayTupleV4(target)
  t.alike(sourceTuple, privateBlake2b256(b4a.concat([
    b4a.from('hiverelay.blind.private-ipc-forward-replay.v4', 'ascii'), length64(232), sourcePayload
  ])))
  t.alike(targetTuple, privateBlake2b256(b4a.concat([
    b4a.from('hiverelay.blind.private-ipc-forward-target-replay.v4', 'ascii'), length64(232), targetPayload
  ])))
  t.not(sourceTuple, targetTuple)
  t.alike(sourceTuple, vector('positive/source-replay-tuple-v4.bin'))
  t.alike(targetTuple, vector('positive/target-replay-tuple-v4.bin'))
})

test('transcript accumulator accepts arbitrary split/coalesce but no short, extra, second, or pre-EOF response', t => {
  for (const [role, input] of [
    ['SOURCE_ORIGIN', vector('positive/source-origin-transcript-v4.bin')],
    ['TARGET_INGRESS', vector('positive/target-ingress-v4.bin')]
  ]) {
    const split = new LocalForwardHttpsTranscriptAccumulatorV4(role)
    t.ok(errorFrom(() => Object.assign(split, { role: 'TARGET_INGRESS', limit: 0, chunks: [input], length: input.byteLength, ended: true })) instanceof TypeError)
    t.ok(errorFrom(() => Object.defineProperty(split, 'responseAllowed', { value: true })) instanceof TypeError)
    const forgedPrototype = Object.create(Object.getPrototypeOf(split), {
      responseAllowed: { value: true, configurable: true }
    })
    t.ok(errorFrom(() => Object.setPrototypeOf(split, forgedPrototype)) instanceof TypeError)
    t.is(split.responseAllowed, false)
    for (let offset = 0; offset < input.byteLength; offset++) split.write(input.subarray(offset, offset + 1))
    t.is(split.responseAllowed, false)
    split.end()
    t.is(split.responseAllowed, true)
    t.exception(() => split.write(b4a.from([0])), /after EOF/)
    t.exception(() => split.end(), /only once/)

    const short = new LocalForwardHttpsTranscriptAccumulatorV4(role)
    short.write(input.subarray(0, input.byteLength - 1))
    t.exception(() => short.end(), /before its exact byte limit/)
    t.is(short.responseAllowed, false)

    const extra = new LocalForwardHttpsTranscriptAccumulatorV4(role)
    t.exception(() => extra.write(b4a.concat([input, b4a.from([0])])), /exceeds/)

    const malformed = new LocalForwardHttpsTranscriptAccumulatorV4(role)
    malformed.write(b4a.alloc(input.byteLength))
    t.exception(() => malformed.end())
    t.is(malformed.responseAllowed, false)
  }
})

test('source and target socket identities are non-aliasing and record families cannot cross sockets', t => {
  t.ok(assertLocalForwardHttpsSocketSeparationV4({
    sourceOrigin: 'inode:1',
    targetIngress: 'inode:2',
    genericUnary: 'inode:3',
    nativeV2Stream: 'inode:4'
  }))
  t.exception(() => assertLocalForwardHttpsSocketSeparationV4({
    sourceOrigin: 'inode:1',
    targetIngress: 'inode:1',
    genericUnary: 'inode:3',
    nativeV2Stream: 'inode:4'
  }), /must not alias/)
  t.exception(() => decodeLocalForwardHttpsSourceOriginTranscriptV4(vector('positive/target-ingress-v4.bin'), { eof: true }), /exactly 65976/)
  t.exception(() => decodeLocalForwardHttpsTargetIngressTranscriptV4(vector('positive/source-origin-authority-v4.bin'), { eof: true }), /exactly 65828/)
})

test('role-separated journal model covers replay, capacity, crash ambiguity, tamper, restart, and clock regression', t => {
  const tuple = vector('positive/source-replay-tuple-v4.bin')
  const journal = new LocalForwardHttpsReplayJournalModelV4('SOURCE_ORIGIN', { capacity: 1 })
  const key = journal.reserve(tuple, 1000n, 16_000n, 1000n)
  journal.commit(key)
  t.exception(() => journal.reserve(tuple, 1000n, 16_000n, 1001n), /already exists/)
  t.exception(() => journal.reserve(b4a.alloc(32, 99), 1000n, 16_000n, 1001n), /capacity/)
  const restarted = LocalForwardHttpsReplayJournalModelV4.restart(
    journal.crashSnapshot(),
    restartOptions('SOURCE_ORIGIN', 1, 1001n)
  )
  t.is(restarted.namespace, 'SOURCE_ORIGIN')
  t.is(restarted.quarantined, false)

  const pending = new LocalForwardHttpsReplayJournalModelV4('TARGET_INGRESS')
  pending.reserve(vector('positive/target-replay-tuple-v4.bin'), 2000n, 17_000n, 2000n)
  t.is(LocalForwardHttpsReplayJournalModelV4.restart(
    pending.crashSnapshot(),
    restartOptions('TARGET_INGRESS', 4096, 2000n)
  ).quarantined, true)

  const snapshot = journal.crashSnapshot()
  const tampered = { payload: b4a.from(snapshot.payload), checksum: snapshot.checksum }
  tampered.payload[0] ^= 1
  t.is(LocalForwardHttpsReplayJournalModelV4.restart(tampered, restartOptions('SOURCE_ORIGIN', 1, 1001n)).quarantined, true)

  const clock = new LocalForwardHttpsReplayJournalModelV4('SOURCE_ORIGIN')
  const first = clock.reserve(b4a.alloc(32, 1), 1000n, 16_000n, 2000n)
  t.is(errorFrom(() => clock.reserve(b4a.alloc(32, 2), 1000n, 16_000n, 1999n)).code, 'PRIVATE_IPC_V4_CLOCK_REGRESSION')
  t.is(clock.quarantined, true)
  const before = clock.inspectEntries()
  t.is(errorFrom(() => clock.commit(first)).code, 'PRIVATE_IPC_V4_JOURNAL_QUARANTINED')
  t.alike(clock.inspectEntries(), before)
  const persisted = LocalForwardHttpsReplayJournalModelV4.restart(
    clock.crashSnapshot(),
    restartOptions('SOURCE_ORIGIN', 4096, 2000n)
  )
  t.is(persisted.quarantined, true)
})

test('replay restart is total, role-bound, canonical, deadline-safe and defensive', t => {
  const base = {
    version: 1,
    namespace: 'SOURCE_ORIGIN',
    capacity: 2,
    lastMonotonicMillis: '2000',
    quarantined: false,
    records: [
      { key: '01'.repeat(32), accepted: '1000', deadline: '16000', state: 'CONSUMED' },
      { key: '02'.repeat(32), accepted: '1001', deadline: '16001', state: 'CONSUMED' }
    ]
  }
  const options = restartOptions('SOURCE_ORIGIN', 2, 2000n)
  const valid = LocalForwardHttpsReplayJournalModelV4.restart(journalSnapshot(base), options)
  t.is(valid.quarantined, false)
  t.is(valid.size, 2)
  const inspected = valid.inspectEntries()
  t.ok(Object.isFrozen(inspected))
  t.ok(Object.isFrozen(inspected[0]))
  t.is(valid.inspectEntries()[0].key, '01'.repeat(32))

  const malformed = [
    null,
    journalSnapshot('{'),
    journalSnapshot({ ...base, version: 2 }),
    journalSnapshot({ namespace: base.namespace, version: 1, capacity: 2, lastMonotonicMillis: '2000', quarantined: false, records: [] }),
    journalSnapshot({ ...base, namespace: 'TARGET_INGRESS' }),
    journalSnapshot({ ...base, capacity: 1 }),
    journalSnapshot({ ...base, lastMonotonicMillis: 'not-u64' }),
    journalSnapshot({ ...base, lastMonotonicMillis: '-1' }),
    journalSnapshot({ ...base, lastMonotonicMillis: ((1n << 64n) + 1n).toString() }),
    journalSnapshot({ ...base, lastMonotonicMillis: '2001' }),
    journalSnapshot({ ...base, quarantined: true }),
    journalSnapshot({ ...base, records: {} }),
    journalSnapshot({ ...base, records: [base.records[0], base.records[0]] }),
    journalSnapshot({ ...base, records: [...base.records].reverse() }),
    journalSnapshot({ ...base, records: [{ ...base.records[0], key: 'GG'.repeat(32) }] }),
    journalSnapshot({ ...base, records: [{ ...base.records[0], state: 'UNKNOWN' }] }),
    journalSnapshot({ ...base, records: [{ ...base.records[0], state: 'PENDING' }] }),
    journalSnapshot({ ...base, records: [{ ...base.records[0], deadline: '1000' }] }),
    journalSnapshot({ ...base, records: [{ ...base.records[0], deadline: '16001' }] }),
    journalSnapshot({ ...base, records: [{ ...base.records[0], accepted: '2001' }] }),
    journalSnapshot({ ...base, records: [{ accepted: '1000', key: base.records[0].key, deadline: '16000', state: 'CONSUMED' }] })
  ]
  for (const snapshot of malformed) {
    const restarted = LocalForwardHttpsReplayJournalModelV4.restart(snapshot, options)
    t.is(restarted.namespace, 'SOURCE_ORIGIN')
    t.is(restarted.capacity, 2)
    t.is(restarted.quarantined, true)
  }
  const roleForged = LocalForwardHttpsReplayJournalModelV4.restart(
    journalSnapshot(base),
    restartOptions('TARGET_INGRESS', 2, 2000n)
  )
  t.is(roleForged.namespace, 'TARGET_INGRESS')
  t.is(roleForged.quarantined, true)
  const expired = LocalForwardHttpsReplayJournalModelV4.restart(
    journalSnapshot(base),
    restartOptions('SOURCE_ORIGIN', 2, 20_000n)
  )
  t.is(expired.quarantined, false)
  t.is(expired.size, 0)
})

test('replacement target TLS gets a new local tuple but reuses exact durable target claim and result', t => {
  const first = decodeLocalForwardHttpsTargetIngressV4(vector('positive/target-ingress-v4.bin'))
  const replacement = decodeLocalForwardHttpsTargetIngressV4(vector('positive/replacement-target-ingress-v4.bin'))
  t.alike(first.body, replacement.body)
  t.not(first.targetTlsExporterBindingHash, replacement.targetTlsExporterBindingHash)
  t.not(first.targetLocalExchangeId, replacement.targetLocalExchangeId)
  t.not(localForwardHttpsTargetReplayTupleV4(first), localForwardHttpsTargetReplayTupleV4(replacement))
  const claims = new LocalForwardHttpsTargetClaimModelV4()
  t.is(claims.claim(first.body).disposition, 'CLAIMED')
  const result = vector('positive/target-result-turn-v4.bin').subarray(148)
  claims.persistResult(first.body, result)
  const retry = claims.claim(replacement.body)
  t.is(retry.disposition, 'EXACT_RETRY')
  t.alike(retry.resultBytes, result)
})

test('target claim fail-closes DATA-before-OPEN and invalid capability or source signatures before mutation', t => {
  const open = wireVector('positive/open-forwarded-role.bin')
  const badCapability = b4a.from(open)
  badCapability[472] ^= 1
  const badSource = b4a.from(open)
  badSource[600] ^= 1
  for (const input of [
    wireVector('positive/data-forwarded-role.bin'),
    badCapability,
    badSource
  ]) {
    const claims = new LocalForwardHttpsTargetClaimModelV4()
    const error = errorFrom(() => claims.claim(input))
    t.is(error.code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
    t.is(claims.terminal, true)
    t.is(claims.terminalReason, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
    t.is(claims.claimCount, 0)
    t.is(claims.stableSessionId, null)
    t.is(errorFrom(() => claims.claim(open)).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
    t.is(claims.claimCount, 0)
  }
})

test('target claim rejects second OPEN, sequence gaps, bad chains, forks and unresolved outstanding requests', t => {
  const open = wireVector('positive/open-forwarded-role.bin')
  const data = wireVector('positive/data-forwarded-role.bin')

  const secondOpen = resignForwarded(open, request => {
    request.requestNonce = b4a.from(request.requestNonce)
    request.requestNonce[0] ^= 1
  })
  const secondOpenClaims = openedTargetClaims()
  const originalOpen = secondOpenClaims.inspectClaim(open)
  t.is(errorFrom(() => secondOpenClaims.claim(secondOpen)).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
  t.alike(secondOpenClaims.inspectClaim(open), originalOpen)

  const gap = resignForwarded(data, request => { request.sequence = 2n })
  const gapClaims = openedTargetClaims()
  t.is(errorFrom(() => gapClaims.claim(gap)).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
  t.is(gapClaims.claimCount, 1)
  t.is(gapClaims.nextSequence, 1n)

  const badChain = resignForwarded(data, request => {
    request.previousTargetResultHash = b4a.from(request.previousTargetResultHash)
    request.previousTargetResultHash[0] ^= 1
  })
  const chainClaims = openedTargetClaims()
  t.is(errorFrom(() => chainClaims.claim(badChain)).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
  t.is(chainClaims.claimCount, 1)
  t.is(chainClaims.nextSequence, 1n)

  const forkClaims = openedTargetClaims()
  t.is(forkClaims.claim(data).disposition, 'CLAIMED')
  const originalData = forkClaims.inspectClaim(data)
  t.is(errorFrom(() => forkClaims.claim(wireVector('positive/window-forwarded-role.bin'))).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
  t.alike(forkClaims.inspectClaim(data), originalData)
  t.is(forkClaims.claimCount, 2)

  const outstandingClaims = new LocalForwardHttpsTargetClaimModelV4()
  t.is(outstandingClaims.claim(open).disposition, 'CLAIMED')
  const outstandingOpen = outstandingClaims.inspectClaim(open)
  t.is(errorFrom(() => outstandingClaims.claim(data)).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
  t.alike(outstandingClaims.inspectClaim(open), outstandingOpen)
  t.is(outstandingClaims.claimCount, 1)
  t.is(outstandingClaims.nextSequence, 0n)
})

test('target result persistence rejects mismatch and invalid signatures without overwriting the original claim', t => {
  const open = wireVector('positive/open-forwarded-role.bin')
  const result = wireVector('positive/open-target-open-accept.bin')

  const mismatch = new LocalForwardHttpsTargetClaimModelV4()
  mismatch.claim(open)
  const beforeMismatch = mismatch.inspectClaim(open)
  t.is(errorFrom(() => mismatch.persistResult(open, wireVector('positive/data-target-ack.bin'))).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
  t.alike(mismatch.inspectClaim(open), beforeMismatch)
  t.is(mismatch.nextSequence, 0n)
  t.is(errorFrom(() => mismatch.persistResult(open, result)).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')

  const badSignature = b4a.from(result)
  badSignature[705] ^= 1
  const invalid = new LocalForwardHttpsTargetClaimModelV4()
  invalid.claim(open)
  const beforeInvalid = invalid.inspectClaim(open)
  t.is(errorFrom(() => invalid.persistResult(open, badSignature)).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
  t.alike(invalid.inspectClaim(open), beforeInvalid)
  t.is(invalid.nextSequence, 0n)

  const exact = new LocalForwardHttpsTargetClaimModelV4()
  exact.claim(open)
  t.alike(exact.persistResult(open, result), result)
  t.alike(exact.persistResult(open, result), result)
  const retry = exact.claim(open)
  t.is(retry.disposition, 'EXACT_RETRY')
  t.alike(retry.resultBytes, result)
  t.is(exact.claimCount, 1)
  t.is(exact.nextSequence, 1n)
})

test('target FIN is not normal close, while a CLOSE request closes only after its definitive result', t => {
  const poll = wireVector('positive/poll-forwarded-role.bin')
  const pollClose = wireVector('positive/poll-target-close.bin')
  const targetFin = openedTargetClaims()
  t.is(targetFin.claim(poll).disposition, 'CLAIMED')
  targetFin.persistResult(poll, pollClose)
  t.is(targetFin.targetFin, true)
  t.is(targetFin.closed, false)
  t.is(targetFin.nextSequence, 2n)
  const afterTargetFin = resignForwarded(poll, request => {
    request.sequence = 2n
    request.previousTargetResultHash = forwardHttpsTargetResultChainHashV1(pollClose)
  })
  t.is(targetFin.claim(afterTargetFin).disposition, 'CLAIMED')

  const close = wireVector('positive/close-forwarded-role.bin')
  const closeAck = wireVector('positive/close-target-ack.bin')
  const normalClose = openedTargetClaims()
  normalClose.claim(close)
  normalClose.persistResult(close, closeAck)
  t.is(normalClose.closed, true)
  t.is(normalClose.targetFin, false)
  t.is(normalClose.nextSequence, 2n)
  t.is(normalClose.claim(close).disposition, 'EXACT_RETRY')
  const postClose = resignForwarded(wireVector('positive/data-forwarded-role.bin'), request => {
    request.sequence = 2n
    request.previousTargetResultHash = forwardHttpsTargetResultChainHashV1(closeAck)
  })
  t.is(errorFrom(() => normalClose.claim(postClose)).code, 'TERMINAL_FORWARD_HTTPS_TARGET_CLAIM')
  t.is(normalClose.closed, true)
  t.is(normalClose.claimCount, 2)
})

test('target claim getters, inspections and completed retry results are defensive', t => {
  const open = wireVector('positive/open-forwarded-role.bin')
  const result = wireVector('positive/open-target-open-accept.bin')
  const claims = openedTargetClaims()
  const stable = claims.stableSessionId
  const stableExpected = b4a.from(stable)
  stable[0] ^= 1
  t.alike(claims.stableSessionId, stableExpected)
  const previous = claims.previousTargetResultHash
  const previousExpected = b4a.from(previous)
  previous[0] ^= 1
  t.alike(claims.previousTargetResultHash, previousExpected)

  const inspected = claims.inspectClaim(open)
  inspected.requestBytes[0] ^= 1
  inspected.commitment[0] ^= 1
  inspected.resultBytes[0] ^= 1
  const retained = claims.inspectClaim(open)
  t.alike(retained.requestBytes, open)
  t.alike(retained.resultBytes, result)

  const retry = claims.claim(open)
  retry.resultBytes[0] ^= 1
  t.alike(claims.claim(open).resultBytes, result)
  t.is(claims.claimCount, 1)
})

test('replacement source TLS authority cannot alter exact origin bytes or public commitment', t => {
  const first = decodeLocalForwardHttpsOriginAuthorityV4(vector('positive/source-origin-authority-v4.bin'))
  const replacement = decodeLocalForwardHttpsOriginAuthorityV4(vector('positive/replacement-source-authority-v4.bin'))
  t.not(first.tlsExporterBindingHash, replacement.tlsExporterBindingHash)
  t.not(first.localExchangeId, replacement.localExchangeId)
  t.alike(first.originRequestCommitment, replacement.originRequestCommitment)
  t.alike(first.stableSessionId, replacement.stableSessionId)
  t.is(first.sequence, replacement.sequence)
  t.not(localForwardHttpsSourceReplayTupleV4(first), localForwardHttpsSourceReplayTupleV4(replacement))
})

test('private IPC v4 exposes model-only authority and never claims runtime durability', t => {
  const authority = JSON.parse(read('hiverelay-blind-private-ipc-authority-v4.json'))
  t.is(authority.modelOnly, true)
  t.is(authority.runtimeReleaseReady, false)
  t.is(authority.authorizesRelease, false)
  t.is(authority.forwardDescriptorOperationBits, 0)
  t.is(authority.forwardAdvertisedOperationBits, 0)
  t.is(authority.forwardReadinessOperationBits, 0)
  t.is(PRIVATE_IPC_V4_LIMITS.REPLAY_CAPACITY, 4096)
  t.is(PRIVATE_IPC_V4_LIMITS.MAX_DEADLINE_MILLIS, 15_000)
  const target = decodeLocalForwardHttpsTargetIngressV4(vector('positive/target-ingress-v4.bin'))
  for (const forbidden of ['tlsExporter', 'sourceAddress', 'url', 'host', 'ip', 'cookies', 'authorization', 'credentials', 'appMetadata']) {
    t.absent(target[forbidden])
  }
  const turn = decodeLocalForwardHttpsTurnV4(vector('positive/source-result-error-v4.bin'))
  t.is(turn.direction, LOCAL_FORWARD_HTTPS_DIRECTION_V4.RESULT)
  t.is(turn.wireRole, FORWARD_HTTPS_RESULT_ROLE_V1.SOURCE_PRE_FORWARD_ERROR)
})