OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / vdrive-deliberate-key.test.js

138 lines · javascript ref:
/**
 * The drive key must be a deliberate input, not a random seed (org #145).
 *
 * `publishTree` opened `new Corestore(new Storage(storeDir))` with no primaryKey,
 * so corestore minted a random seed and the drive key derived from it. Lose
 * `<releaseDir>/<repo>/.vdrive` and the pear:// link died permanently — the
 * secret existed only in the directory that was lost, and nothing anywhere
 * recorded how to regenerate it.
 *
 * Deriving the primaryKey from the daemon's long-lived corestore plus the repo
 * name makes the link a function of state that already survives. Losing the
 * drive store stops being data loss and becomes a rebuild.
 *
 * Note corestore requires `unsafe: true` to accept a primaryKey at all, and
 * refuses to open an existing store whose stored seed differs ("Another
 * corestore is stored here"). Both are load-bearing and asserted here.
 */
import test from 'brittle'
import fsp from '#node:fs/promises'
import path from '#node:path'
import os from '#node:os'
import crypto from 'hypercore-crypto'

import {
  publishVersionToDrive,
  vdriveStoreDir,
  deriveVdrivePrimaryKey
} from '../lib/vdrive-publish.js'

function tmpdir (name) {
  return fsp.mkdtemp(path.join(os.tmpdir(), 'pgv3-vdkey-' + name + '-'))
}

const PAYLOAD = Buffer.alloc(256 * 1024, 5)

async function publish (releaseDir, repo, version, primaryKey) {
  const versionDir = path.join(releaseDir, repo, 'versions', version)
  await fsp.mkdir(versionDir, { recursive: true })
  await fsp.writeFile(path.join(versionDir, 'app.bin'), PAYLOAD)
  await fsp.writeFile(path.join(versionDir, 'notes.txt'), 'same bytes every time')
  const { chunk } = await import('hyper-overlay/chunker')
  return publishVersionToDrive({
    releaseDir,
    repo,
    version,
    channel: 'production',
    vdrive: { enabled: true, channels: null },
    primaryKey,
    chunk
  })
}

test('derivation is deterministic and namespaced per repo', (t) => {
  const root = crypto.randomBytes(32)
  const other = crypto.randomBytes(32)

  const a = deriveVdrivePrimaryKey(root, 'demo')
  const b = deriveVdrivePrimaryKey(root, 'demo')
  t.alike(a, b, 'same inputs, same key')
  t.is(a.byteLength, 32, 'and it is a 32-byte primaryKey')

  t.unlike(deriveVdrivePrimaryKey(root, 'other'), a, 'a different repo differs')
  t.unlike(deriveVdrivePrimaryKey(other, 'demo'), a, 'a different root differs')
})

test('losing the drive store no longer loses the link', async (t) => {
  // The whole point. Publish, destroy the drive store, rebuild from the same
  // deliberate key, and the pear:// link must come back identical.
  const releaseDir = await tmpdir('recover')
  const repo = 'demo'
  const primaryKey = deriveVdrivePrimaryKey(crypto.randomBytes(32), repo)

  const first = await publish(releaseDir, repo, '1.0.0', primaryKey)
  t.is(first.ok, true, 'published')
  t.ok(first.link.startsWith('pear://'), 'has a link')

  // simulate losing the store (a temp dir this test created, not user state)
  await fsp.rm(vdriveStoreDir(releaseDir, repo), { recursive: true, force: true })

  const again = await publish(releaseDir, repo, '1.0.0', primaryKey)
  t.is(again.ok, true, 'republished after the store was gone')
  t.is(again.link, first.link, 'and the link is the SAME')

  await fsp.rm(releaseDir, { recursive: true, force: true })
})

test('the same key and content reproduce the link on another machine', async (t) => {
  // Two independent release dirs stand in for two machines holding the same
  // artifacts. Same deliberate key + same bytes => same link.
  const repo = 'demo'
  const primaryKey = deriveVdrivePrimaryKey(crypto.randomBytes(32), repo)

  const one = await tmpdir('m1')
  const two = await tmpdir('m2')

  const a = await publish(one, repo, '1.0.0', primaryKey)
  const b = await publish(two, repo, '1.0.0', primaryKey)

  t.is(a.ok && b.ok, true, 'both published')
  t.is(b.link, a.link, 'identical link from an independent build')

  await fsp.rm(one, { recursive: true, force: true })
  await fsp.rm(two, { recursive: true, force: true })
})

test('a different key yields a different drive', async (t) => {
  const repo = 'demo'
  const one = await tmpdir('k1')
  const two = await tmpdir('k2')

  const a = await publish(one, repo, '1.0.0', deriveVdrivePrimaryKey(crypto.randomBytes(32), repo))
  const b = await publish(two, repo, '1.0.0', deriveVdrivePrimaryKey(crypto.randomBytes(32), repo))

  t.is(a.ok && b.ok, true, 'both published')
  t.unlike(b.link, a.link, 'different keys, different links')

  await fsp.rm(one, { recursive: true, force: true })
  await fsp.rm(two, { recursive: true, force: true })
})

test('a store minted under a different key is refused, not silently rekeyed', async (t) => {
  // corestore throws "Another corestore is stored here" rather than adopting the
  // new key. Publishing must surface that as a clear, actionable skip instead of
  // taking the release down with it.
  const releaseDir = await tmpdir('mismatch')
  const repo = 'demo'

  const first = await publish(releaseDir, repo, '1.0.0', deriveVdrivePrimaryKey(crypto.randomBytes(32), repo))
  t.is(first.ok, true, 'first publish created the store')

  const second = await publish(releaseDir, repo, '2.0.0', deriveVdrivePrimaryKey(crypto.randomBytes(32), repo))
  t.is(second.ok, false, 'a mismatched key does not silently rekey the drive')
  t.ok(second.skipped, 'it is reported as skipped')
  t.ok(/another corestore|stored here|key/i.test(second.reason || ''), 'with a reason naming the cause: ' + second.reason)

  await fsp.rm(releaseDir, { recursive: true, force: true })
})