OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-daemon / cell-inbox-control-snapshot.js

313 lines · javascript ref:
import b4a from 'b4a'
import { FAMILY } from '@hiverelay/blind-protocol'
import {
  verifyBlindCellControlSnapshotSemanticResult,
  verifyBlindCellControlSnapshotSemanticVerifier
} from './cell-control-snapshot.js'
import {
  verifyBlindInboxControlSnapshotSemanticResult,
  verifyBlindInboxControlSnapshotSemanticVerifier
} from './inbox-control-snapshot.js'

const MAX_U64 = (1n << 64n) - 1n
const DEFAULT_MAXIMUM_BUFFERED_BYTES = 64 * 1024 * 1024
const MAXIMUM_BUFFERED_BYTES = 256 * 1024 * 1024
// Arrays, object headers and allocator metadata cost substantially more than
// the key/value payload alone. Charge a conservative fixed amount so millions
// of tiny entries cannot bypass the byte bound and exhaust the process heap.
const BUFFERED_ENTRY_OVERHEAD = 128
const AUTHORITIES = new WeakMap()
const VERIFIERS = new WeakMap()
const VERIFIED_RESULTS = new WeakMap()

export const BLIND_CELL_INBOX_CONTROL_SNAPSHOT_STATUS = Object.freeze({
  cellInboxCompositionImplemented: true,
  singlePassFamilyPartitionImplemented: true,
  exactSharedCheckpointTupleVerified: true,
  allFamilyCompositionImplemented: false,
  publicationAuthorized: false,
  productionComplete: false,
  exclusions: Object.freeze([
    'CORE_CONTROL_SNAPSHOT_UNIMPLEMENTED',
    'DESCRIPTOR_IDENTITY_FLOOR_SNAPSHOT_UNIMPLEMENTED',
    'CROSS_SERVICE_GLOBAL_SNAPSHOT_COMPOSITION_UNIMPLEMENTED',
    'INBOX_FRAME_BODY_AVAILABILITY_AND_HASH_VERIFICATION_UNIMPLEMENTED',
    'INBOX_WAL_STATE_MACHINE_AND_ENGINE_RESTORE_UNIMPLEMENTED',
    'SCALABLE_EXTERNAL_SORTED_CANDIDATE_STREAM_UNIMPLEMENTED',
    'ENGINE_INSTANCE_WAL_BARRIER_PUBLICATION_AUTHORITY_UNIMPLEMENTED'
  ])
})

export class BlindCellInboxControlSnapshotIntegrityError extends Error {
  constructor (message) {
    super(message)
    this.name = 'BlindCellInboxControlSnapshotIntegrityError'
    this.code = 'RECOVERY_GAP_READ_ONLY'
  }
}

function fail (message) {
  throw new BlindCellInboxControlSnapshotIntegrityError(message)
}

function bytes (value, length, field, nonzero = false) {
  if (!value || typeof value.byteLength !== 'number') fail(`${field} must be bytes`)
  value = b4a.isBuffer(value)
    ? value
    : ArrayBuffer.isView(value)
      ? b4a.from(value.buffer, value.byteOffset, value.byteLength)
      : b4a.from(value)
  if (length != null && value.byteLength !== length) fail(`${field} must be exactly ${length} bytes`)
  if (nonzero && value.every(byte => byte === 0)) fail(`${field} must be nonzero`)
  return value
}

function u64 (value, field) {
  if (typeof value === 'number') {
    if (!Number.isSafeInteger(value) || value < 0) fail(`${field} must be an unsigned safe integer or bigint`)
    value = BigInt(value)
  }
  if (typeof value !== 'bigint' || value < 0n || value > MAX_U64) fail(`${field} is outside u64`)
  return value
}

function integer (value, minimum, maximum, field) {
  if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
    fail(`${field} is outside ${minimum}..${maximum}`)
  }
  return value
}

function tuple (header) {
  if (!header || typeof header !== 'object' || Array.isArray(header)) fail('snapshot semantic header is required')
  return Object.freeze({
    relayPublicKey: b4a.from(bytes(header.relayPublicKey, 32, 'header relayPublicKey', true)),
    storeId: b4a.from(bytes(header.storeId, 32, 'header storeId', true)),
    durabilityContinuityHash: b4a.from(bytes(
      header.durabilityContinuityHash, 32, 'header durabilityContinuityHash', true)),
    walSequence: u64(header.walSequence, 'header walSequence'),
    walHash: b4a.from(bytes(header.walHash, 32, 'header walHash', true))
  })
}

function checkpointTuple (header, expected) {
  if (!header || typeof header !== 'object' || Array.isArray(header)) {
    fail('checkpointHeader is required for Cell+Inbox reconstruction')
  }
  const output = Object.freeze({
    relayPublicKey: b4a.from(bytes(header.relayPublicKey, 32, 'checkpoint relayPublicKey', true)),
    storeId: b4a.from(bytes(header.storeId, 32, 'checkpoint storeId', true)),
    durabilityContinuityHash: b4a.from(bytes(
      header.durabilityContinuityHash, 32, 'checkpoint durabilityContinuityHash', true)),
    coveredWalSequence: u64(header.coveredWalSequence, 'checkpoint coveredWalSequence'),
    coveredWalHash: b4a.from(bytes(header.coveredWalHash, 32, 'checkpoint coveredWalHash', true)),
    epochFloor: integer(header.epochFloor, 0, 0xffffffff, 'checkpoint epochFloor')
  })
  if (!b4a.equals(output.relayPublicKey, expected.relayPublicKey) ||
      !b4a.equals(output.storeId, expected.storeId) ||
      !b4a.equals(output.durabilityContinuityHash, expected.durabilityContinuityHash) ||
      output.coveredWalSequence !== expected.walSequence ||
      !b4a.equals(output.coveredWalHash, expected.walHash)) {
    fail('Cell+Inbox semantic snapshot tuple does not match its checkpoint header')
  }
  return output
}

function copyEntry (input) {
  if (!input || typeof input !== 'object' || Array.isArray(input)) fail('snapshot entry must be an object')
  const entryKind = integer(input.entryKind, 1, 8, 'entryKind')
  const key = b4a.from(bytes(input.key, null, 'entry key'))
  const value = b4a.from(bytes(input.value, null, 'entry value'))
  if (key.byteLength < 2 || key.byteLength > 256 || value.byteLength > 0xffff) {
    fail('snapshot entry is outside its byte bounds')
  }
  if (key[0] !== FAMILY.CELL && key[0] !== FAMILY.INBOX) {
    fail('Cell+Inbox composition rejects an uncovered control snapshot family')
  }
  return Object.freeze({ entryKind, key, value })
}

function compareEntries (left, right) {
  if (left.entryKind !== right.entryKind) return left.entryKind - right.entryKind
  return b4a.compare(left.key, right.key)
}

async function partitionEntries (input, maximumEntries, maximumBufferedBytes) {
  if (!input || (typeof input[Symbol.iterator] !== 'function' &&
      typeof input[Symbol.asyncIterator] !== 'function')) {
    fail('snapshot entries must be iterable')
  }
  const output = { cell: [], inbox: [], count: 0, bufferedBytes: 0 }
  let previous = null
  for await (const raw of input) {
    if (++output.count > maximumEntries) fail('Cell+Inbox snapshot exceeds its configured entry bound')
    const entry = copyEntry(raw)
    output.bufferedBytes += BUFFERED_ENTRY_OVERHEAD + entry.key.byteLength + entry.value.byteLength
    if (!Number.isSafeInteger(output.bufferedBytes) || output.bufferedBytes > maximumBufferedBytes) {
      fail('Cell+Inbox snapshot exceeds its configured buffered-byte bound')
    }
    if (previous && compareEntries(previous, entry) >= 0) {
      fail('Cell+Inbox snapshot entries are not strictly sorted and duplicate-free')
    }
    previous = entry
    if (entry.key[0] === FAMILY.CELL) output.cell.push(entry)
    else output.inbox.push(entry)
  }
  if (output.cell.length < 1 || output.inbox.length < 1) {
    fail('Cell+Inbox composition requires both complete family snapshots')
  }
  return output
}

function authorityState (authority) {
  const state = AUTHORITIES.get(authority)
  if (!state) throw new TypeError('a branded Cell+Inbox control snapshot semantic authority is required')
  return state
}

export function createBlindCellInboxControlSnapshotSemanticAuthority (options = {}) {
  if (!options || typeof options !== 'object' || Array.isArray(options)) {
    throw new TypeError('Cell+Inbox semantic authority options must be an object')
  }
  const cellVerifier = verifyBlindCellControlSnapshotSemanticVerifier(options.cellVerifier)
  const inboxVerifier = verifyBlindInboxControlSnapshotSemanticVerifier(options.inboxVerifier)
  const maximumEntries = options.maximumEntries == null
    ? 0x1000000
    : integer(options.maximumEntries, 2, 0x1000000, 'maximumEntries')
  const maximumBufferedBytes = options.maximumBufferedBytes == null
    ? DEFAULT_MAXIMUM_BUFFERED_BYTES
    : integer(options.maximumBufferedBytes, 256, MAXIMUM_BUFFERED_BYTES, 'maximumBufferedBytes')
  const authority = Object.freeze({
    kind: 'BLIND_CELL_INBOX_CONTROL_SNAPSHOT_RECOVERY_SEMANTIC_AUTHORITY_V1',
    allFamilyComposition: false,
    publicationAuthorized: false,
    productionComplete: false
  })
  AUTHORITIES.set(authority, Object.freeze({
    cellVerifier,
    inboxVerifier,
    maximumEntries,
    maximumBufferedBytes
  }))
  return authority
}

export async function reconstructBlindCellInboxControlSnapshot (authority, input = {}) {
  const state = authorityState(authority)
  const ownedTuple = tuple(input.header)
  // Snapshot the complete checkpoint header before awaiting an attacker-
  // controlled iterable. In particular, epochFloor must be identical for both
  // family reconstructions even when the supplied object has mutable getters.
  const ownedCheckpointTuple = checkpointTuple(input.checkpointHeader, ownedTuple)
  const declaredEntryCount = integer(input.declaredEntryCount, 2, state.maximumEntries, 'declaredEntryCount')
  const partitioned = await partitionEntries(
    input.entries,
    state.maximumEntries,
    state.maximumBufferedBytes
  )
  if (partitioned.count !== declaredEntryCount) {
    fail('Cell+Inbox semantic entry count does not match the declared snapshot count')
  }
  const childInput = entries => Object.freeze({
    header: ownedTuple,
    checkpointHeader: ownedCheckpointTuple,
    declaredEntryCount: entries.length,
    entries
  })
  // Avoid doubling the peak reconstruction workspace while the implementation
  // still buffers both family partitions in memory.
  const cellResult = await state.cellVerifier(childInput(partitioned.cell))
  const inboxResult = await state.inboxVerifier(childInput(partitioned.inbox))
  const expected = { ...ownedTuple }
  verifyBlindCellControlSnapshotSemanticResult(cellResult, {
    ...expected,
    entryCount: partitioned.cell.length
  })
  verifyBlindInboxControlSnapshotSemanticResult(inboxResult, {
    ...expected,
    entryCount: partitioned.inbox.length
  })
  if (cellResult.cellComplete !== true || inboxResult.inboxComplete !== true ||
      cellResult.recoveryVerified !== true || inboxResult.recoveryVerified !== true ||
      cellResult.publicationAuthorized !== false || inboxResult.publicationAuthorized !== false ||
      cellResult.productionComplete !== false || inboxResult.productionComplete !== false) {
    fail('family semantic results are not recovery-only complete results')
  }

  const verified = Object.freeze({
    ...ownedTuple,
    entryCount: partitioned.count,
    cellEntryCount: partitioned.cell.length,
    inboxEntryCount: partitioned.inbox.length,
    cellResult,
    inboxResult
  })
  const result = {}
  for (const field of ['relayPublicKey', 'storeId', 'durabilityContinuityHash', 'walHash']) {
    Object.defineProperty(result, field, { enumerable: true, get: () => b4a.from(verified[field]) })
  }
  Object.defineProperty(result, 'cellState', { enumerable: true, get: () => verified.cellResult.cellState })
  Object.defineProperty(result, 'inboxState', { enumerable: true, get: () => verified.inboxResult.inboxState })
  for (const [field, value] of Object.entries({
    walSequence: verified.walSequence,
    entryCount: verified.entryCount,
    cellEntryCount: verified.cellEntryCount,
    inboxEntryCount: verified.inboxEntryCount,
    cellComplete: true,
    inboxComplete: true,
    cellInboxComplete: true,
    allFamilyComplete: false,
    recoveryVerified: true,
    publicationAuthorized: false,
    productionComplete: false,
    exclusions: BLIND_CELL_INBOX_CONTROL_SNAPSHOT_STATUS.exclusions
  })) Object.defineProperty(result, field, { enumerable: true, value })
  Object.freeze(result)
  VERIFIED_RESULTS.set(result, verified)
  return result
}

export function createBlindCellInboxControlSnapshotSemanticVerifier (authority) {
  const state = authorityState(authority)
  const verifier = input => reconstructBlindCellInboxControlSnapshot(authority, input)
  VERIFIERS.set(verifier, state)
  return verifier
}

export function verifyBlindCellInboxControlSnapshotSemanticVerifier (verifier) {
  if (!VERIFIERS.has(verifier)) throw new TypeError('a branded Cell+Inbox control snapshot semantic verifier is required')
  return verifier
}

export function verifyBlindCellInboxControlSnapshotSemanticResult (result, expected = {}) {
  const verified = VERIFIED_RESULTS.get(result)
  if (!verified) throw new TypeError('a branded Cell+Inbox control snapshot semantic result is required')
  for (const field of ['entryCount', 'cellEntryCount', 'inboxEntryCount']) {
    if (expected[field] != null && integer(expected[field], 1, 0x1000000, `expected ${field}`) !== verified[field]) {
      fail(`Cell+Inbox semantic result ${field} does not match`)
    }
  }
  if (expected.walSequence != null && u64(expected.walSequence, 'expected walSequence') !== verified.walSequence) {
    fail('Cell+Inbox semantic result walSequence does not match')
  }
  for (const field of ['relayPublicKey', 'storeId', 'durabilityContinuityHash', 'walHash']) {
    if (expected[field] != null && !b4a.equals(bytes(expected[field], 32, `expected ${field}`), verified[field])) {
      fail(`Cell+Inbox semantic result ${field} does not match`)
    }
  }
  verifyBlindCellControlSnapshotSemanticResult(verified.cellResult, {
    ...verified,
    entryCount: verified.cellEntryCount
  })
  verifyBlindInboxControlSnapshotSemanticResult(verified.inboxResult, {
    ...verified,
    entryCount: verified.inboxEntryCount
  })
  if (result.cellInboxComplete !== true || result.allFamilyComplete !== false ||
      result.recoveryVerified !== true || result.publicationAuthorized !== false ||
      result.productionComplete !== false) {
    fail('Cell+Inbox semantic result must remain partial and recovery-only')
  }
  return result
}