OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-client / core.js

178 lines · javascript ref:
import b4a from 'b4a'
import { FAMILY, OPERATION } from '@hiverelay/blind-protocol/wire-runtime-authority'
import {
  coreMirrorRequestCommitment,
  coreOpenReplicationRequestCommitment,
  coreServeRequestCommitment
} from '@hiverelay/blind-protocol/hashes'
import {
  coreMirrorRequestV1,
  coreOpenReplicationV1,
  coreServeChallengeV1
} from '@hiverelay/blind-protocol/schemas'
import { encodeCanonical } from '@hiverelay/blind-protocol/codec'
import { asBytes, randomBytes } from './bytes.js'
import { fail } from './errors.js'
import { resolveAdmission } from './provider.js'
import { selectedOperationProfile } from './selected-operation-profile.js'

const MAX_U64 = (1n << 64n) - 1n

function integer (value, minimum, maximum, field) {
  if (!Number.isSafeInteger(value) || value < minimum || value > maximum) {
    fail('BAD_CLIENT_INPUT', `${field} must be within ${minimum}..${maximum}`)
  }
  return value
}

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

function fixed (value, length, field, nonzero = false) {
  const bytes = b4a.from(asBytes(value, field, length))
  if (nonzero && bytes.every(byte => byte === 0)) fail('BAD_CLIENT_INPUT', `${field} must be nonzero`)
  return bytes
}

function nonce (runtime, value) {
  return value == null ? randomBytes(runtime, 32, 'client nonce') : fixed(value, 32, 'clientNonce')
}

async function admission (options, context, required) {
  return resolveAdmission(options, context, required,
    'this operation requires an admission provider or admission value')
}

function result (encoding, request, requestCommitment, operationId, extra = {}) {
  const profile = selectedOperationProfile(FAMILY.CORE, operationId)
  return {
    request,
    requestBytes: encodeCanonical(encoding, request),
    requestCommitment,
    wire: Object.freeze({
      familyId: FAMILY.CORE,
      operationId,
      expectedResultBodyBytes: profile.maxResultBodyBytes,
      ...extra
    })
  }
}

function coreHead (options) {
  const relayPublicKey = fixed(options.relayPublicKey, 32, 'relayPublicKey', true)
  const corePublicKey = fixed(options.corePublicKey, 32, 'corePublicKey', true)
  const fork = u64(options.fork, 'fork')
  const length = u64(options.length, 'length', true)
  const signedHeadHash = fixed(options.signedHeadHash, 32, 'signedHeadHash', true)
  return { relayPublicKey, corePublicKey, fork, length, signedHeadHash }
}

export async function createCoreMirrorRequest (options) {
  if (!options || typeof options !== 'object') fail('BAD_CLIENT_INPUT', 'core mirror options are required')
  const head = coreHead(options)
  const leaseClass = integer(options.leaseClass, 1, 4, 'leaseClass')
  const clientNonce = nonce(options.runtime, options.clientNonce)
  const requestCommitment = coreMirrorRequestCommitment({ ...head, leaseClass, clientNonce })
  const admissionValue = await admission(options, {
    familyId: FAMILY.CORE,
    operationId: OPERATION.CORE.MIRROR,
    requestCommitment,
    relayPublicKey: head.relayPublicKey,
    length: head.length,
    leaseClass
  }, true)
  const request = {
    version: 1,
    corePublicKey: head.corePublicKey,
    fork: head.fork,
    length: head.length,
    signedHeadHash: head.signedHeadHash,
    leaseClass,
    clientNonce,
    admission: admissionValue
  }
  return result(coreMirrorRequestV1, request, requestCommitment, OPERATION.CORE.MIRROR)
}

export async function createCoreProveRequest (options) {
  if (!options || typeof options !== 'object') fail('BAD_CLIENT_INPUT', 'core prove options are required')
  const head = coreHead(options)
  if (!Array.isArray(options.blockIndices) || options.blockIndices.length < 1 || options.blockIndices.length > 16) {
    fail('BAD_CLIENT_INPUT', 'blockIndices must contain 1..16 entries')
  }
  const blockIndices = options.blockIndices.map((value, index) => u64(value, `blockIndices[${index}]`))
  for (let index = 0; index < blockIndices.length; index++) {
    if (blockIndices[index] >= head.length || (index > 0 && blockIndices[index] <= blockIndices[index - 1])) {
      fail('BAD_CLIENT_INPUT', 'blockIndices must be strictly sorted, distinct, and below length')
    }
  }
  const clientNonce = nonce(options.runtime, options.clientNonce)
  const requestCommitment = coreServeRequestCommitment({ ...head, blockIndices, clientNonce })
  const admissionValue = await admission(options, {
    familyId: FAMILY.CORE,
    operationId: OPERATION.CORE.PROVE,
    requestCommitment,
    relayPublicKey: head.relayPublicKey,
    blockCount: blockIndices.length
  }, false)
  const request = {
    version: 1,
    corePublicKey: head.corePublicKey,
    fork: head.fork,
    length: head.length,
    signedHeadHash: head.signedHeadHash,
    blockIndices,
    clientNonce,
    admission: admissionValue
  }
  return result(coreServeChallengeV1, request, requestCommitment, OPERATION.CORE.PROVE)
}

export async function createCoreOpenReplicationRequest (options) {
  if (!options || typeof options !== 'object') fail('BAD_CLIENT_INPUT', 'core open options are required')
  selectedOperationProfile(FAMILY.CORE, OPERATION.CORE.OPEN_REPLICATION)
  const relayPublicKey = fixed(options.relayPublicKey, 32, 'relayPublicKey', true)
  const wireProfileHash = fixed(options.wireProfileHash, 32, 'wireProfileHash', true)
  const sessionClass = integer(options.sessionClass, 1, 3, 'sessionClass')
  const controlChannelId = u64(options.controlChannelId, 'controlChannelId', true)
  const parentChannelBinding = fixed(options.parentChannelBinding, 32, 'parentChannelBinding', true)
  const clientNonce = nonce(options.runtime, options.clientNonce)
  const requestCommitment = coreOpenReplicationRequestCommitment({
    relayPublicKey,
    wireProfileHash,
    sessionClass,
    controlChannelId,
    parentChannelBinding,
    clientNonce
  })
  const admissionValue = await admission(options, {
    familyId: FAMILY.CORE,
    operationId: OPERATION.CORE.OPEN_REPLICATION,
    requestCommitment,
    relayPublicKey,
    sessionClass,
    controlChannelId
  }, true)
  const request = {
    version: 1,
    wireProfileHash,
    sessionClass,
    controlChannelId,
    parentChannelBinding,
    clientNonce,
    admission: admissionValue
  }
  return result(coreOpenReplicationV1, request, requestCommitment, OPERATION.CORE.OPEN_REPLICATION, {
    requiresAuthenticatedStream: true,
    controlChannelId
  })
}