// Hypothesis test: DeviceLinker pre-computes a writer key that target
// daemons (post the keet-pattern revert 3b815e8) will never use.
//
// Concrete claim:
// 1. DeviceLinker.createInvite generates newDeviceKp + computes
// autobaseLocalKey = Hypercore.key({ signers: [{ publicKey: newDeviceKp.publicKey }] })
// Source addWriters that.
// 2. If target's Autobase is opened with `keyPair: newDeviceKp`,
// base.local.key matches. ✓
// 3. If target's Autobase is opened WITHOUT `keyPair` (corestore
// auto-derives `{name:'local'}` per the keet-pattern + post-revert
// IdentityRegistry construction), base.local.key does NOT match. ✗
//
// If predictions 2 and 3 both pass, the device-invite ↔ post-revert
// IdentityRegistry contract is broken: addWriter goes to a key the
// target will never write under.
import test from 'brittle'
import Autobase from 'autobase'
import Corestore from 'corestore'
import Hypercore from 'hypercore'
import crypto from 'hypercore-crypto'
import b4a from 'b4a'
import os from '#node:os'
import path from '#node:path'
import fs from '#node:fs'
const TMP = fs.mkdtempSync(path.join(os.tmpdir(), 'dvi-mismatch-'))
let n = 0
const tmp = () => path.join(TMP, 'store-' + (n++))
async function apply (nodes, view, host) {
for (const node of nodes) await view.append(node.value)
}
function predictedKey (publicKey) {
// Exact formula DeviceLinker.createInvite uses.
return Hypercore.key({
version: 1,
signers: [{ publicKey }]
})
}
test('PREDICTION 1: Autobase opened with keyPair: K → base.local.key matches Hypercore.key({signers:[{publicKey: K.publicKey}]})', async (t) => {
const newDeviceKp = crypto.keyPair()
const predicted = predictedKey(newDeviceKp.publicKey)
const store = new Corestore(tmp())
await store.ready()
const base = new Autobase(store, null, {
apply,
open: (s) => s.get('view', { valueEncoding: 'json' }),
valueEncoding: 'json',
keyPair: newDeviceKp, // ← target uses newDeviceKp as autobase keypair
optimistic: true,
ackInterval: 1000
})
await base.ready()
console.log(' newDeviceKp.publicKey:', b4a.toString(newDeviceKp.publicKey, 'hex').slice(0, 16) + '…')
console.log(' predicted: ', b4a.toString(predicted, 'hex').slice(0, 16) + '…')
console.log(' base.local.key: ', b4a.toString(base.local.key, 'hex').slice(0, 16) + '…')
t.ok(b4a.equals(base.local.key, predicted),
'with keyPair: newDeviceKp, base.local.key MATCHES the DeviceLinker-predicted autobaseLocalKey')
await base.close(); await store.close()
})
test('PREDICTION 2: Autobase opened WITHOUT keyPair → base.local.key does NOT match Hypercore.key({signers:[{publicKey: K.publicKey}]})', async (t) => {
// Same newDeviceKp the source generated and addWritered.
const newDeviceKp = crypto.keyPair()
const predicted = predictedKey(newDeviceKp.publicKey)
// Target opens Autobase the way IdentityRegistry does post-3b815e8 revert:
// no keyPair passed.
const store = new Corestore(tmp())
await store.ready()
const base = new Autobase(store, null, {
apply,
open: (s) => s.get('view', { valueEncoding: 'json' }),
valueEncoding: 'json',
// keyPair: undefined, ← exactly what post-revert IdentityRegistry passes
optimistic: true,
ackInterval: 1000
})
await base.ready()
console.log(' newDeviceKp.publicKey:', b4a.toString(newDeviceKp.publicKey, 'hex').slice(0, 16) + '…')
console.log(' predicted: ', b4a.toString(predicted, 'hex').slice(0, 16) + '…')
console.log(' base.local.key: ', b4a.toString(base.local.key, 'hex').slice(0, 16) + '…')
t.absent(b4a.equals(base.local.key, predicted),
'without keyPair, base.local.key does NOT match DeviceLinker-predicted autobaseLocalKey — ' +
'so the addWriter goes to a key target never writes under (BUG CONFIRMED)')
await base.close(); await store.close()
})
test('CROSS-CHECK: a SECOND fresh corestore (no keyPair) gets a DIFFERENT base.local.key — so the post-revert key is corestore-instance-specific, not derivable in advance', async (t) => {
const storeA = new Corestore(tmp()); await storeA.ready()
const baseA = new Autobase(storeA, null, { apply, open: (s) => s.get('view', { valueEncoding: 'json' }), valueEncoding: 'json', optimistic: true, ackInterval: 1000 })
await baseA.ready()
const storeB = new Corestore(tmp()); await storeB.ready()
const baseB = new Autobase(storeB, null, { apply, open: (s) => s.get('view', { valueEncoding: 'json' }), valueEncoding: 'json', optimistic: true, ackInterval: 1000 })
await baseB.ready()
console.log(' baseA.local.key:', b4a.toString(baseA.local.key, 'hex').slice(0, 16) + '…')
console.log(' baseB.local.key:', b4a.toString(baseB.local.key, 'hex').slice(0, 16) + '…')
t.absent(b4a.equals(baseA.local.key, baseB.local.key),
'two independent corestores without shared keyPair generate DIFFERENT local keys ' +
'— confirms post-revert key is per-corestore, source cannot pre-compute it')
await baseA.close(); await storeA.close()
await baseB.close(); await storeB.close()
})