OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-protocol / client-internal-schemas.js

1271 lines · javascript ref:
import b4a from 'b4a'
import {
  arrayOf,
  boundedBytes,
  canonicalAsciiBytes,
  constant,
  constantBytes,
  exactBytesByClass,
  fixedBytes,
  optional,
  ranged,
  struct,
  u8,
  u16be,
  u32be,
  u64be
} from './codec.js'
import { protocolError } from './errors.js'
import { CELL_SIZE_CLASS, CORE_SESSION_CLASS, INBOX_FRAME_CLASS, OPERATION } from './registry.js'
import {
  blindCoreReadCapV1,
  readCellCapV1
} from './client-composition-external-codecs.js'

export { blindCoreReadCapV1, readCellCapV1 }

const bytes12 = fixedBytes(12)
const bytes32 = fixedBytes(32)
const bytes64 = fixedBytes(64)
const version1 = constant(u8, 1, 'version')
const version2 = constant(u8, 2, 'version')
const booleanU8 = ranged(u8, 0, 1, 'boolean')

function fail (message) {
  protocolError('BAD_ENCODING', message)
}

function isZero (value) {
  for (const byte of value) if (byte !== 0) return false
  return true
}

function nonzero (value, field) {
  if (isZero(value)) fail(`${field} must be nonzero`)
}

function asU64 (value, field) {
  if (typeof value === 'number') value = BigInt(value)
  if (typeof value !== 'bigint' || value < 0n || value > ((1n << 64n) - 1n)) fail(`${field} is outside u64`)
  return value
}

function compareBytes (left, right) {
  return b4a.compare(left, right)
}

function assertSorted (values, compare, name) {
  for (let index = 1; index < values.length; index++) {
    if (compare(values[index - 1], values[index]) >= 0) fail(`${name} must be strictly sorted and duplicate-free`)
  }
}

function capped (encoding, maximum, name) {
  return {
    preencode (state, value) {
      const start = state.end
      encoding.preencode(state, value)
      if (state.end - start > maximum) fail(`${name} exceeds ${maximum} bytes`)
    },
    encode (state, value) { encoding.encode(state, value) },
    decode (state) {
      const start = state.start
      const value = encoding.decode(state)
      if (state.start - start > maximum) fail(`${name} exceeds ${maximum} bytes`)
      return value
    }
  }
}

export const writeCellCapV1 = struct([
  ['readCap', readCellCapV1],
  ['allocationEpoch', u32be],
  ['createPrivateKey', bytes32],
  ['renewPrivateKey', bytes32],
  ['dropPrivateKey', bytes32]
], {
  name: 'WriteCellCapV1',
  validate (value) {
    for (const field of ['createPrivateKey', 'renewPrivateKey', 'dropPrivateKey']) nonzero(value[field], field)
    const keys = [value.createPrivateKey, value.renewPrivateKey, value.dropPrivateKey]
    if (new Set(keys.map(key => b4a.toString(key, 'hex'))).size !== keys.length) fail('write capability private keys must be distinct')
  }
})

// CellBlobV1 is intentionally contextual: sizeClass is carried by the enclosing
// capability/record, not serialized into the blob. Calling the factory freezes
// the only byte length a decoder may accept.
export function cellBlobV1 (sizeClass) {
  const totalLength = CELL_SIZE_CLASS[sizeClass]
  if (!totalLength) throw new RangeError('CellBlobV1 sizeClass must be 1..5')
  return struct([
    ['formatVersion', version1],
    ['nonce', bytes12],
    ['sealed', fixedBytes(totalLength - 13, `sealed class ${sizeClass}`)]
  ], { name: `CellBlobV1(class=${sizeClass})` })
}

export const CELL_BLOB_V1_BY_SIZE_CLASS = Object.freeze(Object.fromEntries(
  Object.keys(CELL_SIZE_CLASS).map(sizeClass => [sizeClass, cellBlobV1(Number(sizeClass))])
))

const frontierEntryV1 = struct([
  ['chainId', bytes32],
  ['sequence', u64be],
  ['frameHash', bytes32]
], {
  name: 'OpaqueChainFrontierEntryV1',
  validate (value) {
    nonzero(value.chainId, 'chainId')
    nonzero(value.frameHash, 'frameHash')
  }
})

export const opaqueChainCheckpointV1 = struct([
  ['version', version1],
  ['coveredFrontier', arrayOf(frontierEntryV1, 1, 1024, 'coveredFrontier')],
  ['opaqueStateCommitment', bytes32],
  ['snapshotPayloadHash', bytes32],
  ['snapshotReadCaps', arrayOf(readCellCapV1, 1, 16, 'snapshotReadCaps')]
], {
  name: 'OpaqueChainCheckpointV1',
  validate (value) {
    assertSorted(value.coveredFrontier, (left, right) => compareBytes(left.chainId, right.chainId), 'coveredFrontier')
    assertSorted(value.snapshotReadCaps, (left, right) => {
      const relay = compareBytes(left.relayPublicKey, right.relayPublicKey)
      return relay || compareBytes(left.storageSlot, right.storageSlot)
    }, 'snapshotReadCaps')
    nonzero(value.opaqueStateCommitment, 'opaqueStateCommitment')
    nonzero(value.snapshotPayloadHash, 'snapshotPayloadHash')
  }
})

export const opaqueChainFrameV1 = struct([
  ['version', version1],
  ['chainId', bytes32],
  ['sequence', u64be],
  ['previousFrameHash', optional(bytes32, 'previousFrameHash')],
  ['transportVerifyKey', bytes32],
  ['opaquePayloads', arrayOf(boundedBytes(0, 256 * 1024, 'opaquePayload'), 0, 256, 'opaquePayloads')],
  ['nextReadCellCaps', arrayOf(readCellCapV1, 0, 16, 'nextReadCellCaps')],
  ['checkpoint', optional(opaqueChainCheckpointV1, 'checkpoint')],
  ['transportSignature', bytes64]
], {
  name: 'OpaqueChainFrameV1',
  validate (value) {
    nonzero(value.chainId, 'chainId')
    nonzero(value.transportVerifyKey, 'transportVerifyKey')
    const sequence = asU64(value.sequence, 'sequence')
    if ((sequence === 0n) !== (value.previousFrameHash == null)) {
      fail('previousFrameHash is absent exactly at sequence zero')
    }
    assertSorted(value.nextReadCellCaps, (left, right) => {
      const relay = compareBytes(left.relayPublicKey, right.relayPublicKey)
      return relay || compareBytes(left.storageSlot, right.storageSlot)
    }, 'nextReadCellCaps')
    if (value.opaquePayloads.length === 0 && value.checkpoint == null) fail('opaque chain frame needs a payload or checkpoint')
  }
})

const cellBlob = exactBytesByClass('sizeClass', CELL_SIZE_CLASS, 'cellBlob')

export const cellRecordV1 = struct([
  ['version', version1],
  ['slot', bytes32],
  ['allocationEpoch', u32be],
  ['sizeClass', ranged(u8, 1, 5, 'sizeClass')],
  ['leaseClass', ranged(u8, 1, 4, 'leaseClass')],
  ['leaseEpoch', u32be],
  ['stateRevision', u64be],
  ['policyRevision', u64be],
  ['cellBlobHash', bytes32],
  ['cellBlob', cellBlob],
  ['createPublicKey', bytes32],
  ['renewPublicKey', bytes32],
  ['dropPublicKey', bytes32],
  ['allocationCommitment', bytes32]
], {
  name: 'CellRecordV1',
  validate (value) {
    for (const field of [
      'slot', 'cellBlobHash', 'createPublicKey', 'renewPublicKey', 'dropPublicKey', 'allocationCommitment'
    ]) nonzero(value[field], field)
  }
})

export const chargedUnaryRetryV1 = capped(struct([
  ['version', version1],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['familyId', ranged(u8, 1, 5, 'familyId')],
  ['operationId', ranged(u8, 1, 255, 'operationId')],
  ['locatorCommitment', bytes32],
  ['sourceRevision', u64be],
  ['sourceCommitment', bytes32],
  ['resultCommitment', bytes32],
  ['reconstruction', boundedBytes(0, 96, 'reconstruction')],
  ['retryExpiresMinute', u64be],
  ['retryState', ranged(u8, 1, 3, 'retryState')]
], {
  name: 'ChargedUnaryRetryV1',
  validate (value) {
    for (const field of ['spendTag', 'requestCommitment', 'locatorCommitment', 'sourceCommitment', 'resultCommitment']) {
      nonzero(value[field], field)
    }
  }
}), 256, 'ChargedUnaryRetryV1')

const controlStateEntryV1 = struct([
  ['entryKind', ranged(u8, 1, 8, 'entryKind')],
  ['key', boundedBytes(1, 256, 'key')],
  ['value', boundedBytes(0, 0xffff, 'value')]
], { name: 'BlindControlStateSnapshotEntryV1' })

export const blindPreparedAdmissionStoreV1 = struct([
  ['version', version1],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['profileId', ranged(u16be, 1, 0xffff, 'profileId')],
  ['schemeId', ranged(u16be, 1, 0xffff, 'schemeId')],
  ['parameterHash', bytes32],
  ['resourceClass', u16be],
  ['leaseClass', u8],
  ['costUnits', u64be],
  ['walCommitRecord', boundedBytes(1, 16384, 'walCommitRecord')]
], {
  name: 'BlindPreparedAdmissionStoreV1',
  validate (value) {
    for (const field of ['spendTag', 'requestCommitment', 'parameterHash']) nonzero(value[field], field)
  }
})

const cellIngressSnapshotFields = remainingAttempts => [
  ['version', version1],
  ['transactionId', bytes32],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['requestFingerprint', bytes32],
  ['storageSlot', bytes32],
  ['allocationEpoch', u32be],
  ['sizeClass', ranged(u8, 1, 5, 'sizeClass')],
  ['leaseClass', ranged(u8, 1, 4, 'leaseClass')],
  ['declaredBlobHash', bytes32],
  ['createPublicKey', bytes32],
  ['renewPublicKey', bytes32],
  ['dropPublicKey', bytes32],
  ['allocationCommitment', bytes32],
  ['profileId', ranged(u16be, 1, 0xffff, 'profileId')],
  ['preparedAdmissionBytes', boundedBytes(1, 17408, 'preparedAdmissionBytes')],
  ['resultBindingBytes', optional(boundedBytes(1, 1024, 'resultBindingBytes'), 'resultBindingBytes')],
  ['declaredBytes', u32be],
  ['deadlineUnixMillis', u64be],
  ['remainingAttempts', remainingAttempts],
  ['reservedEpoch', u32be]
]

function validateCellIngressSnapshot (value, extra = []) {
  for (const field of [
    'transactionId', 'spendTag', 'requestCommitment', 'requestFingerprint', 'storageSlot',
    'declaredBlobHash', 'createPublicKey', 'renewPublicKey', 'dropPublicKey', 'allocationCommitment',
    ...extra
  ]) nonzero(value[field], field)
  if (asU64(value.deadlineUnixMillis, 'deadlineUnixMillis') === 0n) fail('deadlineUnixMillis must be nonzero')
}

export const blindCellHistoricalResultSnapshotV1 = struct([
  ['storageSlot', bytes32],
  ['allocationEpoch', u32be],
  ['sizeClass', ranged(u8, 1, 5, 'sizeClass')],
  ['leaseClass', ranged(u8, 1, 4, 'leaseClass')],
  ['leaseEpoch', u32be],
  ['stateRevision', u64be],
  ['policyRevision', u64be],
  ['cellBlobHash', bytes32],
  ['allocationCommitment', bytes32],
  ['objectState', ranged(u8, 1, 2, 'objectState')],
  ['policyState', ranged(u8, 1, 2, 'policyState')]
], {
  name: 'BlindCellHistoricalResultSnapshotV1',
  validate (value) {
    for (const field of ['storageSlot', 'cellBlobHash', 'allocationCommitment']) nonzero(value[field], field)
  }
})

export const blindCellReservedSpendSnapshotV1 = struct([
  ...cellIngressSnapshotFields(ranged(u8, 0, 2, 'remainingAttempts'))
], {
  name: 'BlindCellReservedSpendSnapshotV1',
  validate (value) { validateCellIngressSnapshot(value) }
})

export const blindCellCommittedPutSpendSnapshotV1 = struct([
  ...cellIngressSnapshotFields(ranged(u8, 0, 1, 'remainingAttempts')),
  ['resultIdentity', bytes32],
  ['committedEpoch', u32be],
  ['resultCell', blindCellHistoricalResultSnapshotV1]
], {
  name: 'BlindCellCommittedPutSpendSnapshotV1',
  validate (value) { validateCellIngressSnapshot(value, ['resultIdentity']) }
})

const cellAtomicCommittedPutFields = [
  ['version', version1],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['requestFingerprint', bytes32],
  ['storageSlot', bytes32],
  ['allocationEpoch', u32be],
  ['sizeClass', ranged(u8, 1, 5, 'sizeClass')],
  ['leaseClass', ranged(u8, 1, 4, 'leaseClass')],
  ['declaredBlobHash', bytes32],
  ['createPublicKey', bytes32],
  ['renewPublicKey', bytes32],
  ['dropPublicKey', bytes32],
  ['allocationCommitment', bytes32],
  ['profileId', ranged(u16be, 1, 0xffff, 'profileId')],
  ['preparedAdmissionBytes', boundedBytes(1, 17408, 'preparedAdmissionBytes')],
  ['resultBindingBytes', boundedBytes(1, 1024, 'resultBindingBytes')],
  ['declaredBytes', u32be]
]

function validateCellAtomicCommittedPut (value, extra = []) {
  for (const field of [
    'spendTag', 'requestCommitment', 'requestFingerprint', 'storageSlot', 'declaredBlobHash',
    'createPublicKey', 'renewPublicKey', 'dropPublicKey', 'allocationCommitment', ...extra
  ]) nonzero(value[field], field)
}

export const blindPutAtomicCommittedStoreV1 = struct([
  ...cellAtomicCommittedPutFields,
  ['blobObjectId', bytes32],
  ['leaseEpoch', u32be],
  ['stateRevision', constant(u64be, 0n, 'stateRevision')],
  ['policyRevision', constant(u64be, 0n, 'policyRevision')],
  ['resultIdentity', bytes32],
  ['committedEpoch', u32be]
], {
  name: 'BlindPutAtomicCommittedStoreV1',
  validate (value) { validateCellAtomicCommittedPut(value, ['blobObjectId', 'resultIdentity']) }
})

export const blindCellAtomicCommittedPutSpendSnapshotV1 = struct([
  ['version', version1],
  ['transactionId', bytes32],
  ...cellAtomicCommittedPutFields.slice(1),
  ['resultIdentity', bytes32],
  ['committedEpoch', u32be],
  ['resultCell', blindCellHistoricalResultSnapshotV1]
], {
  name: 'BlindCellAtomicCommittedPutSpendSnapshotV1',
  validate (value) {
    validateCellAtomicCommittedPut(value, ['transactionId', 'resultIdentity'])
  }
})

export const blindCellTerminalSpendSnapshotV1 = struct([
  ...cellIngressSnapshotFields(ranged(u8, 0, 2, 'remainingAttempts')),
  ['terminalReason', ranged(u8, 1, 3, 'terminalReason')],
  ['terminalEpoch', u32be]
], {
  name: 'BlindCellTerminalSpendSnapshotV1',
  validate (value) { validateCellIngressSnapshot(value) }
})

export const BLIND_CELL_CHARGED_READ_LIFECYCLE_STATE = Object.freeze({
  PINNED: 1,
  FINALIZED: 2,
  EXPIRED: 3
})

export const blindCellChargedReadPinEntrySnapshotV1 = struct([
  ['storageSlot', bytes32],
  ['present', booleanU8],
  ['sizeClass', ranged(u8, 0, 5, 'sizeClass')],
  ['allocationEpoch', u32be],
  ['leaseClass', ranged(u8, 0, 4, 'leaseClass')],
  ['leaseEpoch', u32be],
  ['stateRevision', u64be],
  ['policyRevision', u64be],
  ['cellBlobHash', bytes32],
  ['allocationCommitment', bytes32]
], {
  name: 'BlindCellChargedReadPinEntrySnapshotV1',
  validate (value) {
    nonzero(value.storageSlot, 'storageSlot')
    if (value.present === 0) {
      if (value.sizeClass !== 0 || value.allocationEpoch !== 0 || value.leaseClass !== 0 ||
          value.leaseEpoch !== 0 || asU64(value.stateRevision, 'stateRevision') !== 0n ||
          asU64(value.policyRevision, 'policyRevision') !== 0n || !isZero(value.cellBlobHash) ||
          !isZero(value.allocationCommitment)) {
        fail('absent charged-read pin entry must use its exact zero state')
      }
      return
    }
    if (value.sizeClass === 0 || value.leaseClass === 0 || isZero(value.cellBlobHash) ||
        isZero(value.allocationCommitment)) {
      fail('present charged-read pin entry is incomplete')
    }
  }
})

const blindCellChargedReadPinEntriesSnapshotV1 = arrayOf(
  blindCellChargedReadPinEntrySnapshotV1,
  1,
  64,
  'charged read pin entries'
)

export const blindCellChargedReadRetrySnapshotV1 = struct([
  ['version', version1],
  ['lifecycleState', ranged(u8, 1, 3, 'lifecycleState')],
  ['operationId', ranged(u8, OPERATION.CELL.GET, OPERATION.CELL.BATCH_GET, 'operationId')],
  ['transactionId', bytes32],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['requestFingerprint', bytes32],
  ['preparedAdmissionBytes', optional(boundedBytes(1, 17408, 'preparedAdmissionBytes'), 'preparedAdmissionBytes')],
  ['resultBindingBytes', optional(boundedBytes(1, 1024, 'resultBindingBytes'), 'resultBindingBytes')],
  ['receiptEpoch', optional(u32be, 'receiptEpoch')],
  ['retryExpiresUnixMillis', u64be],
  ['entries', optional(blindCellChargedReadPinEntriesSnapshotV1, 'entries')],
  ['resultCommitment', optional(bytes32, 'resultCommitment')],
  ['committedEpoch', u32be],
  ['terminalEpoch', optional(u32be, 'terminalEpoch')]
], {
  name: 'BlindCellChargedReadRetrySnapshotV1',
  validate (value) {
    if (value.operationId !== OPERATION.CELL.GET && value.operationId !== OPERATION.CELL.PROVE &&
        value.operationId !== OPERATION.CELL.BATCH_GET) fail('unknown charged CELL read operation')
    for (const field of ['transactionId', 'spendTag', 'requestCommitment', 'requestFingerprint']) {
      nonzero(value[field], field)
    }
    if (asU64(value.retryExpiresUnixMillis, 'retryExpiresUnixMillis') === 0n) {
      fail('retryExpiresUnixMillis must be nonzero')
    }
    const active = value.lifecycleState !== BLIND_CELL_CHARGED_READ_LIFECYCLE_STATE.EXPIRED
    if (active !== (value.preparedAdmissionBytes != null) ||
        active !== (value.resultBindingBytes != null) ||
        active !== (value.receiptEpoch != null) ||
        active !== (value.entries != null)) {
      fail('charged-read retry active fields do not match lifecycleState')
    }
    if (active && value.operationId !== OPERATION.CELL.BATCH_GET && value.entries.length !== 1) {
      fail('charged unary CELL read must pin exactly one entry')
    }
    if (active && value.operationId !== OPERATION.CELL.BATCH_GET && value.entries[0].present !== 1) {
      fail('charged unary CELL read must pin a present cell')
    }
    const finalized = value.lifecycleState === BLIND_CELL_CHARGED_READ_LIFECYCLE_STATE.FINALIZED
    if (finalized !== (value.resultCommitment != null)) {
      fail('charged-read result commitment does not match lifecycleState')
    }
    if (value.resultCommitment != null) nonzero(value.resultCommitment, 'resultCommitment')
    const expired = value.lifecycleState === BLIND_CELL_CHARGED_READ_LIFECYCLE_STATE.EXPIRED
    if (expired !== (value.terminalEpoch != null)) {
      fail('charged-read terminal epoch does not match lifecycleState')
    }
  }
})

export const blindCellCommittedRenewSpendSnapshotV1 = struct([
  ['version', version1],
  ['transactionId', bytes32],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['requestFingerprint', bytes32],
  ['storageSlot', bytes32],
  ['expectedStateRevision', u64be],
  ['expectedLeaseEpoch', u32be],
  ['requestedLeaseClass', ranged(u8, 1, 4, 'requestedLeaseClass')],
  ['profileId', ranged(u16be, 1, 0xffff, 'profileId')],
  ['preparedAdmissionBytes', boundedBytes(1, 17408, 'preparedAdmissionBytes')],
  ['resultBindingBytes', optional(boundedBytes(1, 1024, 'resultBindingBytes'), 'resultBindingBytes')],
  ['resultIdentity', bytes32],
  ['committedEpoch', u32be],
  ['resultCell', blindCellHistoricalResultSnapshotV1]
], {
  name: 'BlindCellCommittedRenewSpendSnapshotV1',
  validate (value) {
    for (const field of [
      'transactionId', 'spendTag', 'requestCommitment', 'requestFingerprint', 'storageSlot', 'resultIdentity'
    ]) nonzero(value[field], field)
  }
})

export const blindCellRequestResultSnapshotV1 = struct([
  ['version', version1],
  ['transactionId', bytes32],
  ['requestCommitment', bytes32],
  ['requestFingerprint', bytes32],
  ['storageSlot', bytes32],
  ['resultBindingBytes', optional(boundedBytes(1, 1024, 'resultBindingBytes'), 'resultBindingBytes')],
  ['resultIdentity', bytes32],
  ['committedEpoch', u32be],
  ['resultCell', blindCellHistoricalResultSnapshotV1]
], {
  name: 'BlindCellRequestResultSnapshotV1',
  validate (value) {
    for (const field of [
      'transactionId', 'requestCommitment', 'requestFingerprint', 'storageSlot', 'resultIdentity'
    ]) nonzero(value[field], field)
  }
})

export const blindCellRecordSnapshotV1 = struct([
  ['version', version1],
  ['storageSlot', bytes32],
  ['allocationEpoch', u32be],
  // leaseClass is mutable on renew; allocationLeaseClass is the immutable
  // allocation-commitment input needed to revalidate a recovered record.
  ['allocationLeaseClass', ranged(u8, 1, 4, 'allocationLeaseClass')],
  ['sizeClass', ranged(u8, 1, 5, 'sizeClass')],
  ['leaseClass', ranged(u8, 1, 4, 'leaseClass')],
  ['leaseEpoch', u32be],
  ['stateRevision', u64be],
  ['policyRevision', u64be],
  ['cellBlobHash', bytes32],
  ['blobVirtualBucket', u16be],
  ['blobObjectId', bytes32],
  ['createPublicKey', bytes32],
  ['renewPublicKey', bytes32],
  ['dropPublicKey', bytes32],
  ['allocationCommitment', bytes32],
  ['objectState', ranged(u8, 1, 2, 'objectState')],
  ['policyState', ranged(u8, 1, 2, 'policyState')],
  ['tombstoneReason', optional(ranged(u8, 1, 2, 'tombstoneReason'), 'tombstoneReason')],
  ['terminalEpoch', optional(u32be, 'terminalEpoch')],
  ['createSpendTag', bytes32],
  ['resultIdentity', bytes32],
  ['createdEpoch', u32be]
], {
  name: 'BlindCellRecordSnapshotV1',
  validate (value) {
    for (const field of [
      'storageSlot', 'cellBlobHash', 'blobObjectId', 'createPublicKey', 'renewPublicKey',
      'dropPublicKey', 'allocationCommitment', 'createSpendTag', 'resultIdentity'
    ]) nonzero(value[field], field)
    const tombstone = value.objectState === 2
    if (tombstone !== (value.tombstoneReason != null) || tombstone !== (value.terminalEpoch != null)) {
      fail('cell snapshot tombstone fields do not match objectState')
    }
  }
})

export const blindCellControlGlobalSnapshotV1 = struct([
  ['version', version1],
  ['epochFloor', u32be],
  ['clockUnsafe', booleanU8],
  ['recoveryGap', booleanU8],
  ['storedBytes', u64be],
  ['stagingBytes', u64be],
  ['controlBytes', u64be],
  ['tombstoneBytes', u64be],
  ['reservedCells', u64be],
  ['cellCount', u64be],
  ['spendCount', u64be],
  ['commitmentCount', u64be],
  ['requestResultCount', u64be],
  ['chargedReadPinnedCount', u64be],
  ['chargedReadFinalizedCount', u64be],
  ['chargedReadExpiredCount', u64be],
  ['chargedReadPinnedEntryCount', u64be],
  ['profileStagingCount', u32be],
  ['integrityEvidenceCount', u32be],
  ['controlRecordAccountingBytes', constant(u16be, 512, 'controlRecordAccountingBytes')],
  ['tombstoneRecordAccountingBytes', constant(u16be, 512, 'tombstoneRecordAccountingBytes')]
], { name: 'BlindCellControlGlobalSnapshotV1' })

export const blindCellProfileStagingSnapshotV1 = struct([
  ['version', version1],
  ['profileId', ranged(u16be, 1, 0xffff, 'profileId')],
  ['stagingBytes', u64be]
], {
  name: 'BlindCellProfileStagingSnapshotV1',
  validate (value) {
    if (asU64(value.stagingBytes, 'stagingBytes') === 0n) fail('profile staging bytes must be nonzero')
  }
})

export const blindCellIntegrityEvidenceSnapshotV1 = struct([
  ['version', version1],
  ['reason', ranged(u8, 1, 3, 'reason')],
  ['detectedEpoch', u32be],
  ['evidenceHash', bytes32]
], {
  name: 'BlindCellIntegrityEvidenceSnapshotV1',
  validate (value) { nonzero(value.evidenceHash, 'evidenceHash') }
})

export const BLIND_CORE_OPEN_REPLICATION_LIFECYCLE_STATE = Object.freeze({
  RESERVED: 1,
  LIVE: 2,
  TERMINAL: 3
})

export const blindCoreOpenReplicationRetrySnapshotV1 = struct([
  ['version', version1],
  ['lifecycleState', ranged(u8, 1, 3, 'lifecycleState')],
  ['logicalRetryKey', bytes32],
  ['spendTag', boundedBytes(1, 128, 'spendTag')],
  ['requestCommitment', bytes32],
  ['wireProfileHash', bytes32],
  ['sessionClass', ranged(u8, 1, 3, 'sessionClass')],
  ['clientNonce', bytes32],
  ['parentSessionId', boundedBytes(1, 256, 'parentSessionId')],
  ['controlChannelId', u64be],
  ['parentChannelBinding', bytes32],
  ['streamId', u64be],
  ['maxSessionBytes', u64be],
  ['idleMillis', u32be],
  ['lifetimeMillis', u32be],
  ['openedAtEpoch', u32be],
  ['recordVirtualBucket', u16be],
  ['resultBytes', optional(boundedBytes(1, 16384, 'resultBytes'), 'resultBytes')],
  ['terminalReason', optional(canonicalAsciiBytes(1, 64, 'terminalReason'), 'terminalReason')]
], {
  name: 'BlindCoreOpenReplicationRetrySnapshotV1',
  validate (value) {
    for (const field of [
      'logicalRetryKey', 'spendTag', 'requestCommitment', 'wireProfileHash',
      'parentSessionId', 'parentChannelBinding'
    ]) nonzero(value[field], field)
    if (asU64(value.controlChannelId, 'controlChannelId') === 0n ||
        asU64(value.streamId, 'streamId') === 0n) {
      fail('Core retry controlChannelId and streamId must be nonzero')
    }
    const limits = CORE_SESSION_CLASS[value.sessionClass]
    if (!limits || asU64(value.maxSessionBytes, 'maxSessionBytes') !== BigInt(limits.maxSessionBytes) ||
        value.idleMillis !== limits.idleMillis || value.lifetimeMillis !== limits.lifetimeMillis) {
      fail('Core retry limits do not match sessionClass')
    }
    const terminal = value.lifecycleState === BLIND_CORE_OPEN_REPLICATION_LIFECYCLE_STATE.TERMINAL
    if (terminal !== (value.terminalReason != null)) {
      fail('Core retry terminalReason presence does not match lifecycleState')
    }
    if (!terminal && value.resultBytes == null) {
      fail('Core reserved/live retry records require their signed result')
    }
  }
})

export const blindCoreControlGlobalSnapshotV1 = struct([
  ['version', version1],
  ['epochFloor', u32be],
  ['clockUnsafe', booleanU8],
  ['recordCount', u64be],
  ['reservedCount', u64be],
  ['liveCount', u64be],
  ['terminalCount', u64be],
  ['spendIndexCount', u64be],
  ['logicalIndexCount', u64be],
  ['channelIndexCount', u64be],
  ['resultCount', u64be],
  ['snapshotRecordBytes', u64be]
], { name: 'BlindCoreControlGlobalSnapshotV1' })

const inboxSnapshotOperation = ranged(u8, 1, 6, 'operation')
const inboxSnapshotFrameClass = ranged(u8, 0, 3, 'frameClass')
const inboxSnapshotLeaseClass = ranged(u8, 0, 4, 'requestedLeaseClass')

function validateInboxSpendSnapshot (value, extra = []) {
  for (const field of [
    'transactionId', 'spendTag', 'requestCommitment', 'requestFingerprint', 'physicalTopic',
    ...extra
  ]) nonzero(value[field], field)
  if (asU64(value.deadlineUnixMillis, 'deadlineUnixMillis') === 0n) fail('deadlineUnixMillis must be nonzero')
  const operation = value.operation
  if (![OPERATION.INBOX.CREATE, OPERATION.INBOX.RENEW, OPERATION.INBOX.APPEND,
    OPERATION.INBOX.READ, OPERATION.INBOX.WATCH].includes(operation)) {
    fail('Inbox spend snapshot operation is not charged by the Inbox family')
  }
  const append = operation === OPERATION.INBOX.APPEND
  if (append !== (value.frameHash != null) || append !== (value.frameClass !== 0)) {
    fail('Inbox spend frame fields do not match APPEND')
  }
  if (append) {
    nonzero(value.frameHash, 'frameHash')
    if (value.declaredBytes !== INBOX_FRAME_CLASS[value.frameClass]) {
      fail('Inbox APPEND declaredBytes does not match frameClass')
    }
  } else if (value.declaredBytes !== 0) {
    fail('non-APPEND Inbox spend must declare zero staged frame bytes')
  }
  const leased = operation === OPERATION.INBOX.CREATE || operation === OPERATION.INBOX.RENEW
  if (leased !== (value.requestedLeaseClass !== 0)) {
    fail('Inbox spend requestedLeaseClass does not match CREATE/RENEW')
  }
}

const inboxSpendSnapshotFields = remainingAttempts => [
  ['version', version1],
  ['transactionId', bytes32],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['requestFingerprint', bytes32],
  ['physicalTopic', bytes32],
  ['operation', inboxSnapshotOperation],
  ['profileId', ranged(u16be, 1, 0xffff, 'profileId')],
  ['frameClass', inboxSnapshotFrameClass],
  ['frameHash', optional(bytes32, 'frameHash')],
  ['requestedLeaseClass', inboxSnapshotLeaseClass],
  ['declaredBytes', u32be],
  ['deadlineUnixMillis', u64be],
  ['remainingAttempts', remainingAttempts],
  ['reservedEpoch', u32be]
]

export const blindInboxReservedSpendSnapshotV1 = struct([
  ...inboxSpendSnapshotFields(ranged(u8, 0, 2, 'remainingAttempts'))
], {
  name: 'BlindInboxReservedSpendSnapshotV1',
  validate (value) { validateInboxSpendSnapshot(value) }
})

export const blindInboxCommittedSpendSnapshotV1 = struct([
  ...inboxSpendSnapshotFields(ranged(u8, 0, 1, 'remainingAttempts')),
  ['resultIdentity', bytes32],
  ['resultRevision', u64be],
  ['committedEpoch', u32be],
  ['resultLeaseClass', optional(ranged(u8, 1, 4, 'resultLeaseClass'), 'resultLeaseClass')],
  ['resultLeaseEpoch', optional(u32be, 'resultLeaseEpoch')],
  ['resultBindingBytes', boundedBytes(1, 1024, 'resultBindingBytes')],
  ['clientNonce', bytes32],
  ['retentionClassAtAppend', optional(ranged(u8, 1, 4, 'retentionClassAtAppend'), 'retentionClassAtAppend')],
  ['appendLeaseEpoch', optional(u32be, 'appendLeaseEpoch')],
  ['expiresAtEpoch', optional(u32be, 'expiresAtEpoch')],
  ['ackSignature', optional(bytes64, 'ackSignature')],
  ['resultCommitment', optional(bytes32, 'resultCommitment')],
  ['retryState', ranged(u8, 0, 3, 'retryState')]
], {
  name: 'BlindInboxCommittedSpendSnapshotV1',
  validate (value) {
    validateInboxSpendSnapshot(value, ['resultIdentity'])
    const retryOperation = value.operation === OPERATION.INBOX.READ || value.operation === OPERATION.INBOX.WATCH
    const append = value.operation === OPERATION.INBOX.APPEND
    const leaseMutation = value.operation === OPERATION.INBOX.CREATE || value.operation === OPERATION.INBOX.RENEW
    if (retryOperation !== (value.retryState !== 0)) {
      fail('Inbox committed retryState does not match READ/WATCH')
    }
    if ((retryOperation || append) !== (value.resultCommitment != null)) {
      fail('Inbox committed resultCommitment does not match READ/WATCH/APPEND')
    }
    if (value.resultCommitment != null) nonzero(value.resultCommitment, 'resultCommitment')
    nonzero(value.clientNonce, 'clientNonce')
    if (leaseMutation !== (value.resultLeaseClass != null) ||
        leaseMutation !== (value.resultLeaseEpoch != null)) {
      fail('Inbox committed lease result presence does not match CREATE/RENEW')
    }
    if (leaseMutation && (value.resultLeaseClass !== value.requestedLeaseClass ||
        value.resultLeaseEpoch <= value.committedEpoch)) {
      fail('Inbox committed lease result does not match its request and commit epoch')
    }
    for (const field of [
      'retentionClassAtAppend', 'appendLeaseEpoch', 'expiresAtEpoch', 'ackSignature'
    ]) {
      if (append !== (value[field] != null)) fail(`Inbox committed ${field} presence does not match APPEND`)
    }
    if (append) {
      nonzero(value.clientNonce, 'clientNonce')
      nonzero(value.ackSignature, 'ackSignature')
      const expiresAtEpoch = Math.min(value.appendLeaseEpoch,
        value.committedEpoch + [0, 4, 28, 120, 360][value.retentionClassAtAppend])
      if (value.expiresAtEpoch !== expiresAtEpoch || value.expiresAtEpoch <= value.committedEpoch) {
        fail('Inbox committed APPEND expiry does not match retention and append lease')
      }
    }
  }
})

export const blindInboxExpiredAppendSpendSnapshotV1 = struct([
  ['version', version1],
  ['transactionId', bytes32],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['requestFingerprint', bytes32],
  ['physicalTopic', bytes32],
  ['profileId', ranged(u16be, 1, 0xffff, 'profileId')],
  ['frameClass', ranged(u8, 1, 3, 'frameClass')],
  ['frameHash', bytes32],
  ['declaredBytes', u32be],
  ['deadlineUnixMillis', u64be],
  ['remainingAttempts', ranged(u8, 0, 1, 'remainingAttempts')],
  ['reservedEpoch', u32be],
  ['resultIdentity', bytes32],
  ['appendRevision', u64be],
  ['storedAtEpoch', u32be],
  ['retentionClassAtAppend', ranged(u8, 1, 4, 'retentionClassAtAppend')],
  ['appendLeaseEpoch', u32be],
  ['expiresAtEpoch', u32be],
  ['expiredEpoch', u32be],
  ['clientNonce', bytes32],
  ['resultBindingBytes', boundedBytes(1, 1024, 'resultBindingBytes')],
  ['ackSignature', bytes64],
  ['resultCommitment', bytes32]
], {
  name: 'BlindInboxExpiredAppendSpendSnapshotV1',
  validate (value) {
    for (const field of [
      'transactionId', 'spendTag', 'requestCommitment', 'requestFingerprint', 'physicalTopic',
      'frameHash', 'resultIdentity', 'clientNonce', 'ackSignature', 'resultCommitment'
    ]) nonzero(value[field], field)
    if (value.declaredBytes !== INBOX_FRAME_CLASS[value.frameClass]) {
      fail('expired Inbox APPEND declaredBytes does not match frameClass')
    }
    if (asU64(value.deadlineUnixMillis, 'deadlineUnixMillis') === 0n ||
        asU64(value.appendRevision, 'appendRevision') === 0n) {
      fail('expired Inbox APPEND deadline and revision must be nonzero')
    }
    const expiresAtEpoch = Math.min(value.appendLeaseEpoch,
      value.storedAtEpoch + [0, 4, 28, 120, 360][value.retentionClassAtAppend])
    if (value.expiresAtEpoch !== expiresAtEpoch || value.expiresAtEpoch <= value.storedAtEpoch ||
        value.expiredEpoch < value.expiresAtEpoch) {
      fail('expired Inbox APPEND epoch facts are inconsistent')
    }
  }
})

export const blindInboxTerminalSpendSnapshotV1 = struct([
  ...inboxSpendSnapshotFields(ranged(u8, 0, 2, 'remainingAttempts')),
  ['terminalReason', ranged(u8, 1, 3, 'terminalReason')],
  ['terminalEpoch', u32be]
], {
  name: 'BlindInboxTerminalSpendSnapshotV1',
  validate (value) { validateInboxSpendSnapshot(value) }
})

export const blindInboxRequestResultSnapshotV1 = struct([
  ['version', version1],
  ['transactionId', bytes32],
  ['requestCommitment', bytes32],
  ['physicalTopic', bytes32],
  ['resultIdentity', bytes32],
  ['resultRevision', u64be],
  ['committedEpoch', u32be],
  ['resultBindingBytes', boundedBytes(1, 1024, 'resultBindingBytes')],
  ['clientNonce', bytes32],
  ['resultLeaseClass', constant(u8, 0, 'resultLeaseClass')],
  ['resultLeaseEpoch', u32be]
], {
  name: 'BlindInboxRequestResultSnapshotV1',
  validate (value) {
    for (const field of ['transactionId', 'requestCommitment', 'physicalTopic', 'resultIdentity']) {
      nonzero(value[field], field)
    }
    nonzero(value.clientNonce, 'clientNonce')
    if (asU64(value.resultRevision, 'resultRevision') === 0n) fail('Inbox close resultRevision must be nonzero')
  }
})

export const blindInboxRecordSnapshotV1 = struct([
  ['version', version1],
  ['physicalTopic', bytes32],
  ['metadataVirtualBucket', u16be],
  ['allocationEpoch', u32be],
  ['allocationLeaseClass', ranged(u8, 1, 4, 'allocationLeaseClass')],
  ['frameClassBits', ranged(u8, 1, 7, 'frameClassBits')],
  ['appendAuthMode', booleanU8],
  ['appendPublicKey', optional(bytes32, 'appendPublicKey')],
  ['createPublicKey', bytes32],
  ['renewPublicKey', bytes32],
  ['closePublicKey', bytes32],
  ['retentionClass', ranged(u8, 1, 4, 'retentionClass')],
  ['leaseClass', ranged(u8, 1, 4, 'leaseClass')],
  ['leaseEpoch', u32be],
  ['stateRevision', u64be],
  ['policyRevision', u64be],
  ['appendRevision', u64be],
  ['createCommitment', bytes32],
  ['objectState', ranged(u8, 1, 2, 'objectState')],
  ['policyState', ranged(u8, 1, 2, 'policyState')],
  ['tombstoneReason', optional(ranged(u8, 1, 2, 'tombstoneReason'), 'tombstoneReason')],
  ['terminalEpoch', optional(u32be, 'terminalEpoch')],
  ['createSpendTag', bytes32],
  ['createRequestCommitment', bytes32],
  ['resultIdentity', bytes32],
  ['createdEpoch', u32be]
], {
  name: 'BlindInboxRecordSnapshotV1',
  validate (value) {
    for (const field of [
      'physicalTopic', 'createPublicKey', 'renewPublicKey', 'closePublicKey', 'createCommitment',
      'createSpendTag', 'createRequestCommitment', 'resultIdentity'
    ]) nonzero(value[field], field)
    if ((value.appendAuthMode === 1) !== (value.appendPublicKey != null)) {
      fail('Inbox snapshot appendPublicKey presence does not match appendAuthMode')
    }
    if (value.appendPublicKey != null) nonzero(value.appendPublicKey, 'appendPublicKey')
    const keys = [value.createPublicKey, value.renewPublicKey, value.closePublicKey]
    if (value.appendPublicKey != null) keys.push(value.appendPublicKey)
    if (new Set(keys.map(key => b4a.toString(key, 'hex'))).size !== keys.length) {
      fail('Inbox snapshot management keys must be distinct')
    }
    const tombstone = value.objectState === 2
    if (tombstone !== (value.tombstoneReason != null) || tombstone !== (value.terminalEpoch != null)) {
      fail('Inbox snapshot tombstone fields do not match objectState')
    }
  }
})

export const blindInboxFrameSnapshotV1 = struct([
  ['version', version1],
  ['physicalTopic', bytes32],
  ['appendRevision', u64be],
  ['frameHash', bytes32],
  ['frameClass', ranged(u8, 1, 3, 'frameClass')],
  ['frameVirtualBucket', u16be],
  ['frameObjectId', bytes32],
  ['appendLeaseEpoch', u32be],
  ['storedAtEpoch', u32be],
  ['expiresAtEpoch', u32be],
  ['spendTag', bytes32],
  ['requestCommitment', bytes32],
  ['resultIdentity', bytes32]
], {
  name: 'BlindInboxFrameSnapshotV1',
  validate (value) {
    for (const field of [
      'physicalTopic', 'frameHash', 'frameObjectId', 'spendTag', 'requestCommitment', 'resultIdentity'
    ]) nonzero(value[field], field)
    if (asU64(value.appendRevision, 'appendRevision') === 0n) fail('Inbox appendRevision must be nonzero')
    if (value.expiresAtEpoch <= value.storedAtEpoch) fail('Inbox frame expiresAtEpoch must follow storedAtEpoch')
  }
})

export const blindInboxRetryReconstructionV1 = struct([
  ['version', version1],
  ['firstAppendRevision', u64be],
  ['lastAppendRevision', u64be],
  ['entryCount', ranged(u8, 0, 64, 'entryCount')],
  ['nextCursorHash', bytes32]
], {
  name: 'BlindInboxRetryReconstructionV1',
  validate (value) {
    const first = asU64(value.firstAppendRevision, 'firstAppendRevision')
    const last = asU64(value.lastAppendRevision, 'lastAppendRevision')
    const empty = value.entryCount === 0
    if (empty !== (first === 0n && last === 0n)) {
      fail('Inbox retry empty range does not match entryCount')
    }
    if (!empty && (first === 0n || last < first)) {
      fail('Inbox retry append range is invalid')
    }
  }
})

export const blindInboxRetryFramePinSnapshotV1 = struct([
  ['version', version1],
  ['spendTag', bytes32],
  ['physicalTopic', bytes32],
  ['appendRevision', u64be],
  ['frameHash', bytes32]
], {
  name: 'BlindInboxRetryFramePinSnapshotV1',
  validate (value) {
    for (const field of ['spendTag', 'physicalTopic', 'frameHash']) nonzero(value[field], field)
    if (asU64(value.appendRevision, 'appendRevision') === 0n) fail('Inbox retry pin appendRevision must be nonzero')
  }
})

export const blindInboxRetryMaterialSnapshotV1 = struct([
  ['version', version1],
  ['spendTag', bytes32],
  ['entriesCommitment', bytes32],
  ['nextCursor', optional(boundedBytes(1, 128, 'nextCursor'), 'nextCursor')]
], {
  name: 'BlindInboxRetryMaterialSnapshotV1',
  validate (value) {
    nonzero(value.spendTag, 'spendTag')
    nonzero(value.entriesCommitment, 'entriesCommitment')
  }
})

export const blindInboxControlGlobalSnapshotV1 = struct([
  ['version', version1],
  ['epochFloor', u32be],
  ['clockUnsafe', booleanU8],
  ['recoveryGap', booleanU8],
  ['storedFrameBytes', u64be],
  ['stagingFrameBytes', u64be],
  ['controlBytes', u64be],
  ['tombstoneBytes', u64be],
  ['frameIndexBytes', u64be],
  ['reservedFrames', u64be],
  ['inboxCount', u64be],
  ['frameCount', u64be],
  ['spendCount', u64be],
  ['commitmentCount', u64be],
  ['requestResultCount', u64be],
  ['retryRecordCount', u64be],
  ['retryFramePinCount', u64be],
  ['profileStagingCount', u32be],
  ['integrityEvidenceCount', u32be],
  ['controlRecordAccountingBytes', constant(u16be, 512, 'controlRecordAccountingBytes')],
  ['tombstoneRecordAccountingBytes', constant(u16be, 512, 'tombstoneRecordAccountingBytes')],
  ['frameIndexAccountingBytes', constant(u16be, 256, 'frameIndexAccountingBytes')],
  ['retryRecordAccountingBytes', constant(u16be, 256, 'retryRecordAccountingBytes')]
], { name: 'BlindInboxControlGlobalSnapshotV1' })

export const blindInboxProfileStagingSnapshotV1 = struct([
  ['version', version1],
  ['profileId', ranged(u16be, 1, 0xffff, 'profileId')],
  ['stagingFrameBytes', u64be]
], {
  name: 'BlindInboxProfileStagingSnapshotV1',
  validate (value) {
    if (asU64(value.stagingFrameBytes, 'stagingFrameBytes') === 0n) fail('Inbox profile staging bytes must be nonzero')
  }
})

export const blindInboxIntegrityEvidenceSnapshotV1 = struct([
  ['version', version1],
  ['reason', ranged(u8, 1, 3, 'reason')],
  ['detectedEpoch', u32be],
  ['evidenceHash', bytes32]
], {
  name: 'BlindInboxIntegrityEvidenceSnapshotV1',
  validate (value) { nonzero(value.evidenceHash, 'evidenceHash') }
})

export const blindControlStateSnapshotV1 = struct([
  ['version', version1],
  ['relayPublicKey', bytes32],
  ['storeId', bytes32],
  ['durabilityContinuityHash', bytes32],
  ['walSequence', u64be],
  ['walHash', bytes32],
  ['entries', arrayOf(controlStateEntryV1, 0, 0x1000000, 'entries')]
], {
  name: 'BlindControlStateSnapshotV1',
  validate (value) {
    for (const field of ['relayPublicKey', 'storeId', 'durabilityContinuityHash', 'walHash']) nonzero(value[field], field)
    assertSorted(value.entries, (left, right) => left.entryKind - right.entryKind || compareBytes(left.key, right.key), 'control state entries')
  }
})

export const blindExternalAckFloorV1 = struct([
  ['version', version1],
  ['relayPublicKey', bytes32],
  ['storeId', bytes32],
  ['externalJournalId', bytes32],
  ['durabilityContinuityHash', bytes32],
  ['floorRevision', u64be],
  ['previousFloorHash', optional(bytes32, 'previousFloorHash')],
  ['writerEpoch', u64be],
  ['writerFenceTokenHash', bytes32],
  ['externalLeaseRevision', u64be],
  ['walSequence', u64be],
  ['walHash', bytes32],
  ['descriptorSequence', u64be],
  ['descriptorHash', bytes32],
  ['witnessedUnixMillis', u64be],
  ['witnessPublicKey', bytes32],
  ['signature', bytes64]
], {
  name: 'BlindExternalAckFloorV1',
  validate (value) {
    for (const field of [
      'relayPublicKey', 'storeId', 'externalJournalId', 'durabilityContinuityHash',
      'writerFenceTokenHash', 'walHash', 'descriptorHash', 'witnessPublicKey'
    ]) nonzero(value[field], field)
    const revision = asU64(value.floorRevision, 'floorRevision')
    if (revision === 0n || (revision === 1n) !== (value.previousFloorHash == null)) {
      fail('floor predecessor presence does not match floorRevision')
    }
  }
})

export const blindExternalControlCheckpointV1 = struct([
  ['version', version1],
  ['relayPublicKey', bytes32],
  ['storeId', bytes32],
  ['externalJournalId', bytes32],
  ['durabilityContinuityHash', bytes32],
  ['checkpointRevision', u64be],
  ['previousCheckpointHash', optional(bytes32, 'previousCheckpointHash')],
  ['baseFloorRevision', u64be],
  ['baseFloorHash', bytes32],
  ['writerEpoch', u64be],
  ['writerFenceTokenHash', bytes32],
  ['externalLeaseRevision', u64be],
  ['walSequence', u64be],
  ['walHash', bytes32],
  ['descriptorSequence', u64be],
  ['descriptorHash', bytes32],
  ['snapshotByteLength', u64be],
  ['snapshotHash', bytes32],
  ['oldestRetainedFloorRevision', u64be],
  ['createdUnixMillis', u64be],
  ['witnessPublicKey', bytes32],
  ['signature', bytes64]
], {
  name: 'BlindExternalControlCheckpointV1',
  validate (value) {
    for (const field of [
      'relayPublicKey', 'storeId', 'externalJournalId', 'durabilityContinuityHash', 'baseFloorHash',
      'writerFenceTokenHash', 'walHash', 'descriptorHash', 'snapshotHash', 'witnessPublicKey'
    ]) nonzero(value[field], field)
    const revision = asU64(value.checkpointRevision, 'checkpointRevision')
    if (revision === 0n || (revision === 1n) !== (value.previousCheckpointHash == null)) {
      fail('checkpoint predecessor presence does not match checkpointRevision')
    }
  }
})

// Exact fixed prefix of every version-2 local WAL frame. The variable payload
// and trailing checksum follow this 192-byte canonical header; the store-format
// registry therefore changes whenever this crash-recovery authority changes.
export const blindWalHeaderV2 = struct([
  ['magic', constantBytes(b4a.from('HRWL', 'ascii'), 'magic')],
  ['walVersion', version2],
  ['recordType', ranged(u8, 1, 0xff, 'recordType')],
  ['totalLength', u32be],
  ['walSequence', u64be],
  ['transactionId', bytes32],
  ['virtualBucket', u16be],
  ['mapGeneration', u64be],
  ['writerFenceTokenHash', bytes32],
  ['payloadLength', u32be],
  ['previousWalHash', bytes32],
  ['durabilityContinuityHash', bytes32],
  ['payloadHash', bytes32]
], {
  name: 'BlindWalHeaderV2',
  validate (value) {
    for (const field of [
      'transactionId', 'writerFenceTokenHash', 'durabilityContinuityHash', 'payloadHash'
    ]) nonzero(value[field], field)
    const sequence = asU64(value.walSequence, 'walSequence')
    const generation = asU64(value.mapGeneration, 'mapGeneration')
    if (sequence === 0n || generation === 0n) fail('WAL sequence and map generation must be nonzero')
    if (value.totalLength !== 224 + value.payloadLength) fail('WAL total and payload lengths disagree')
    if ((sequence === 1n) !== isZero(value.previousWalHash)) {
      fail('WAL predecessor hash zero state does not match its sequence')
    }
  }
})

export const blindLocalCheckpointV1 = struct([
  ['magic', constantBytes(b4a.from('HRBCKP01', 'ascii'), 'magic')],
  ['checkpointVersion', constant(u16be, 1, 'checkpointVersion')],
  ['relayPublicKey', bytes32],
  ['storeId', bytes32],
  ['durabilityProfileId', ranged(u8, 1, 2, 'durabilityProfileId')],
  ['durabilityContinuityHash', bytes32],
  ['durabilityProfileHash', bytes32],
  ['formatMajor', u16be],
  ['formatMinor', u16be],
  ['storeFormatHash', bytes32],
  ['specHash', bytes32],
  ['abiHash', bytes32],
  ['mapGeneration', u64be],
  ['bucketMapHash', bytes32],
  ['writerEpoch', u64be],
  ['writerFenceTokenHash', bytes32],
  ['checkpointRevision', u64be],
  ['previousCheckpointHash', optional(bytes32, 'previousCheckpointHash')],
  ['coveredWalSequence', u64be],
  ['coveredWalHash', bytes32],
  ['epochFloor', u32be],
  ['descriptorSequenceFloor', u64be],
  ['descriptorHashFloor', bytes32],
  ['snapshotByteLength', u64be],
  ['snapshotHash', bytes32]
], {
  name: 'BlindLocalCheckpointV1',
  validate (value) {
    for (const field of [
      'relayPublicKey', 'storeId', 'durabilityContinuityHash', 'durabilityProfileHash',
      'storeFormatHash', 'specHash', 'abiHash', 'bucketMapHash', 'writerFenceTokenHash',
      'coveredWalHash', 'descriptorHashFloor', 'snapshotHash'
    ]) nonzero(value[field], field)
    const revision = asU64(value.checkpointRevision, 'checkpointRevision')
    if (revision === 0n || (revision === 1n) !== (value.previousCheckpointHash == null)) {
      fail('local checkpoint predecessor presence does not match checkpointRevision')
    }
    if (asU64(value.mapGeneration, 'mapGeneration') === 0n ||
        asU64(value.writerEpoch, 'writerEpoch') === 0n ||
        asU64(value.coveredWalSequence, 'coveredWalSequence') === 0n ||
        asU64(value.snapshotByteLength, 'snapshotByteLength') === 0n) {
      fail('local checkpoint generation, writer, WAL, and snapshot values must be nonzero')
    }
  }
})

export const blindStoreManifestV1 = struct([
  ['magic', constantBytes(b4a.from('HRBLIND1', 'ascii'), 'magic')],
  ['manifestVersion', constant(u16be, 1, 'manifestVersion')],
  ['storeId', bytes32],
  ['relayPublicKey', bytes32],
  ['durabilityProfileId', ranged(u8, 1, 2, 'durabilityProfileId')],
  ['durabilityContinuityHash', bytes32],
  ['durabilityProfileHash', bytes32],
  ['formatMajor', u16be],
  ['formatMinor', u16be],
  ['storeFormatHash', bytes32],
  ['specHash', bytes32],
  ['abiHash', bytes32],
  ['mapGeneration', u64be],
  ['bucketMapHash', bytes32],
  ['checkpointWalSequence', u64be],
  ['checkpointHash', bytes32],
  ['epochFloor', u32be],
  ['writerEpoch', u64be],
  ['writerFenceTokenHash', bytes32],
  ['externalLeaseRevision', u64be],
  ['externalJournalId', bytes32],
  ['externalWitnessPublicKey', bytes32],
  ['restoreEvidenceFeedId', bytes32],
  ['lastAckWalSequence', u64be],
  ['lastAckWalHash', bytes32],
  ['externalCheckpointRevision', u64be],
  ['externalCheckpointHash', bytes32],
  ['descriptorSequenceFloor', u64be],
  ['descriptorHashFloor', bytes32],
  ['migrationState', ranged(u8, 0, 4, 'migrationState')],
  ['sourceFormatMajor', u16be],
  ['targetFormatMajor', u16be],
  ['migrationCursorHash', bytes32],
  ['previousManifestHash', optional(bytes32, 'previousManifestHash')],
  ['manifestRevision', u64be],
  ['mac', bytes32]
], {
  name: 'BlindStoreManifestV1',
  validate (value) {
    for (const field of [
      'storeId', 'relayPublicKey', 'durabilityContinuityHash', 'durabilityProfileHash', 'storeFormatHash',
      'specHash', 'abiHash', 'bucketMapHash', 'checkpointHash', 'writerFenceTokenHash',
      'descriptorHashFloor', 'mac'
    ]) nonzero(value[field], field)
    const revision = asU64(value.manifestRevision, 'manifestRevision')
    if ((revision === 0n) !== (value.previousManifestHash == null)) {
      fail('manifest predecessor presence does not match manifestRevision')
    }
    const profile1 = value.durabilityProfileId === 1
    const profileExternal = [
      value.externalJournalId, value.externalWitnessPublicKey, value.restoreEvidenceFeedId,
      value.lastAckWalHash, value.externalCheckpointHash
    ]
    if (profile1 && (profileExternal.some(value => !isZero(value)) ||
      asU64(value.externalLeaseRevision, 'externalLeaseRevision') !== 0n ||
      asU64(value.lastAckWalSequence, 'lastAckWalSequence') !== 0n ||
      asU64(value.externalCheckpointRevision, 'externalCheckpointRevision') !== 0n)) {
      fail('profile 1 manifest must contain zero external continuity state')
    }
    if (!profile1 && (isZero(value.externalJournalId) || isZero(value.externalWitnessPublicKey))) {
      fail('profile 2 manifest requires external journal and witness identities')
    }
    if (value.migrationState === 0) {
      if (value.sourceFormatMajor !== 0 || value.targetFormatMajor !== 0 || !isZero(value.migrationCursorHash)) {
        fail('stable manifest must contain zero migration fields')
      }
    } else {
      if (value.sourceFormatMajor === 0 || value.targetFormatMajor <= value.sourceFormatMajor) {
        fail('migration source and target format majors are invalid')
      }
      if (value.migrationState === 1 ? !isZero(value.migrationCursorHash) : isZero(value.migrationCursorHash)) {
        fail('migration cursor presence does not match migration phase')
      }
    }
  }
})