OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / blind-client / test / cells.test.js

217 lines · javascript ref:
import b4a from 'b4a'
import { webcrypto } from 'node:crypto'
import test from 'brittle'
import {
  CELL_SIZE_CLASS,
  allocationCommitment,
  cellStorageSlot,
  decodeCanonical,
  putCellV1
} from '@hiverelay/blind-protocol'
import {
  BlindClientError,
  createCellReplica,
  createDropCellRequest,
  createGetCellRequest,
  createRenewCellRequest,
  destroyCellWriteCapability,
  generateDistinctCapabilityKeys,
  maximumCellContentBytes,
  openCell,
  publicKeyFromPrivateSeed,
  sealCell,
  verifyCapabilitySignature
} from '../index.js'
import { createBrowserCryptoRuntime } from '../runtime/browser.js'
import { createNodeCryptoRuntime } from '../runtime/node.js'

const runtime = createNodeCryptoRuntime()
const relayPublicKey = b4a.alloc(32, 7)
const admission = {
  profileId: 1,
  schemeId: 1,
  parameterHash: b4a.alloc(32, 8),
  token: b4a.from([9])
}

test('cell encryption is exact-sized, authenticated and randomized across every class', async t => {
  const storageSlot = b4a.alloc(32, 3)
  for (const [id, bytes] of Object.entries(CELL_SIZE_CLASS)) {
    const sizeClass = Number(id)
    const structuredContent = b4a.from(`opaque-app-record-${id}`)
    const first = await import('../cells.js').then(module => module.sealCell({ runtime, storageSlot, sizeClass, structuredContent }))
    const second = await import('../cells.js').then(module => module.sealCell({ runtime, storageSlot, sizeClass, structuredContent }))
    t.is(first.cellBlob.byteLength, bytes)
    t.is(second.cellBlob.byteLength, bytes)
    t.not(first.cellBlob, second.cellBlob)
    t.alike(await openCell({ runtime, storageSlot, sizeClass, cellKey: first.cellKey, cellBlob: first.cellBlob }), structuredContent)
  }
})

test('cell authentication binds key, slot, class and every sealed byte', async t => {
  const created = await createCellReplica({
    runtime,
    relayPublicKey,
    allocationEpoch: 100,
    sizeClass: 1,
    leaseClass: 2,
    structuredContent: b4a.from('peerit is only opaque application bytes here'),
    admission
  })
  const opened = await openCell({
    runtime,
    storageSlot: created.readCap.storageSlot,
    sizeClass: created.readCap.sizeClass,
    cellKey: created.readCap.cellKey,
    cellBlob: created.request.cellBlob
  })
  t.alike(opened, b4a.from('peerit is only opaque application bytes here'))

  const tampered = b4a.from(created.request.cellBlob)
  tampered[tampered.byteLength - 1] ^= 1
  await t.exception(openCell({ runtime, ...created.readCap, cellBlob: tampered }), BlindClientError)
  await t.exception(openCell({ runtime, ...created.readCap, storageSlot: b4a.alloc(32, 4), cellBlob: created.request.cellBlob }), BlindClientError)
  await t.exception(openCell({ runtime, ...created.readCap, cellKey: b4a.alloc(32, 5), cellBlob: created.request.cellBlob }), BlindClientError)
  await t.exception(openCell({
    runtime,
    ...created.readCap,
    expectedCellBlobHash: b4a.alloc(32, 6),
    cellBlob: created.request.cellBlob
  }), /read capability/)
})

test('Node and browser runtimes produce byte-identical mandatory cells', async t => {
  const browser = createBrowserCryptoRuntime(webcrypto)
  const storageSlot = b4a.alloc(32, 21)
  const cellKey = b4a.alloc(32, 22)
  const nonce = b4a.alloc(12, 23)
  const structuredContent = b4a.alloc(maximumCellContentBytes(1), 24)
  const nodeCell = await sealCell({ runtime, storageSlot, sizeClass: 1, structuredContent, cellKey, nonce })
  const browserCell = await sealCell({ runtime: browser, storageSlot, sizeClass: 1, structuredContent, cellKey, nonce })
  t.alike(nodeCell.cellBlob, browserCell.cellBlob)
  t.alike(await openCell({ runtime, storageSlot, sizeClass: 1, cellKey, cellBlob: browserCell.cellBlob }), structuredContent)
  t.alike(await openCell({ runtime: browser, storageSlot, sizeClass: 1, cellKey, cellBlob: nodeCell.cellBlob }), structuredContent)
})

test('cell replica creates three distinct relay-bound capabilities and canonical PUT bytes', async t => {
  const created = await createCellReplica({
    runtime,
    relayPublicKey,
    allocationEpoch: 321,
    sizeClass: 2,
    leaseClass: 3,
    structuredContent: b4a.from('field-notebook fixture record'),
    admission
  })
  const decoded = decodeCanonical(putCellV1, created.requestBytes, { copyBytes: true })
  t.alike(decoded.storageSlot, created.request.storageSlot)
  t.alike(cellStorageSlot(decoded), created.request.storageSlot)
  t.unlike(decoded.createPublicKey, decoded.renewPublicKey)
  t.unlike(decoded.createPublicKey, decoded.dropPublicKey)
  t.unlike(decoded.renewPublicKey, decoded.dropPublicKey)
  const expectedAllocation = allocationCommitment({
    relayPublicKey,
    ...decoded,
    declaredCellBlobHash: decoded.declaredBlobHash
  })
  t.alike(created.allocationCommitment, expectedAllocation)
  t.ok(verifyCapabilitySignature(decoded.createPublicKey, expectedAllocation, decoded.createSignature))
  t.is(created.readCap.expectedCellBlobHash.byteLength, 32)
  t.is(created.writeCap.createPrivateKey.byteLength, 32)
})

test('destroying cell write authority preserves independent read authority', async t => {
  const created = await createCellReplica({
    runtime,
    relayPublicKey,
    allocationEpoch: 322,
    sizeClass: 1,
    leaseClass: 1,
    structuredContent: b4a.from('keep reading after write-key destruction'),
    admission
  })
  const cellKey = b4a.from(created.readCap.cellKey)
  destroyCellWriteCapability(created.writeCap)
  t.alike(created.readCap.cellKey, cellKey)
  t.alike(created.writeCap.createPrivateKey, b4a.alloc(32))
  t.alike(await openCell({ runtime, ...created.readCap, cellBlob: created.request.cellBlob }),
    b4a.from('keep reading after write-key destruction'))
})

test('capability generation rejects a relay-key collision and regenerates', t => {
  const forbiddenSeed = b4a.alloc(32, 61)
  const forbiddenPublicKey = publicKeyFromPrivateSeed(forbiddenSeed)
  let call = 0
  const deterministic = {
    randomBytes (length) {
      t.is(length, 32)
      call++
      return call === 1 ? forbiddenSeed : b4a.alloc(32, 61 + call)
    }
  }
  const keys = generateDistinctCapabilityKeys(deterministic, ['create', 'renew', 'drop'], [forbiddenPublicKey])
  t.is(call, 4)
  t.unlike(keys.create.publicKey, forbiddenPublicKey)
  t.unlike(keys.renew.publicKey, forbiddenPublicKey)
  t.unlike(keys.drop.publicKey, forbiddenPublicKey)
})

test('cell content limit is exact and oversized content fails before admission', async t => {
  const limit = maximumCellContentBytes(1)
  let calls = 0
  const created = await createCellReplica({
    runtime,
    relayPublicKey,
    allocationEpoch: 50,
    sizeClass: 1,
    leaseClass: 1,
    structuredContent: b4a.alloc(limit, 1),
    admissionProvider: async () => { calls++; return admission }
  })
  t.is(created.request.cellBlob.byteLength, CELL_SIZE_CLASS[1])
  t.is(calls, 1)
  await t.exception(createCellReplica({
    runtime,
    relayPublicKey,
    allocationEpoch: 50,
    sizeClass: 1,
    leaseClass: 1,
    structuredContent: b4a.alloc(limit + 1),
    admissionProvider: async () => { calls++; return admission }
  }), /exceeds sizeClass/)
  t.is(calls, 1)
})

test('renew, drop and read requests bind commitments without application metadata', async t => {
  const created = await createCellReplica({
    runtime,
    relayPublicKey,
    allocationEpoch: 10,
    sizeClass: 1,
    leaseClass: 1,
    structuredContent: b4a.from([1, 2, 3]),
    admission
  })
  const renew = await createRenewCellRequest({
    runtime,
    writeCap: created.writeCap,
    expectedRevision: 0n,
    expectedLeaseEpoch: 14,
    leaseClass: 2,
    admission
  })
  const drop = createDropCellRequest({
    runtime,
    writeCap: created.writeCap,
    expectedRevision: 1n,
    expectedLeaseEpoch: 38
  })
  const get = await createGetCellRequest({ runtime, readCap: created.readCap })
  t.is(renew.requestCommitment.byteLength, 32)
  t.is(drop.requestCommitment.byteLength, 32)
  t.is(get.requestCommitment.byteLength, 32)
  t.absent(renew.request.app)
  t.absent(drop.request.namespace)
  t.absent(get.request.author)
})