// PLC ceremony dry-run harness. Mints a FRESH THROWAWAY identity (never a real account),
// validates the genesis + rotation ops locally (§6), and — only when PLC_SUBMIT=1 — fires the
// full ceremony against the REAL plc.directory (the authoritative oracle) and confirms on-chain.
//
// node scripts/plc-dryrun.mjs # OFFLINE: mint + local-validate + print the ops
// PLC_SUBMIT=1 node scripts/plc-dryrun.mjs # ALSO submit the throwaway to plc.directory (PERMANENT, junk DID)
//
// The submit is a permanent append to the global PLC op-log for a throwaway DID — harmless, but
// irreversible. It is the authoritative cross-check that our op bytes are accepted by the real
// directory before we touch a real account (wj62/s6cyl).
import { generateMnemonic } from '@scure/bip39'
import { wordlist } from '@scure/bip39/wordlists/english'
import { selfCustodyIdentity, deriveAtprotoKey } from '../lib/atproto-identity.js'
import { checkGenesisOp, checkUpdateOp, PlcDirectory } from '../lib/plc-directory.js'
import { didForCreateOp, opCid } from '../lib/did-plc.js'
const rootMnemonic = generateMnemonic(wordlist, 256) // fresh throwaway root (recovery authority)
const deviceMnemonic = generateMnemonic(wordlist, 256) // fresh throwaway device signing key
const handle = 'plc-dryrun-' + Date.now() + '.example.com' // plc.directory does NOT verify handles
const pdsUrl = 'https://pds.7rnx.net'
const id = selfCustodyIdentity({ rootMnemonic, handle, pdsUrl, deviceKey: deriveAtprotoKey(deviceMnemonic).privateKey })
console.log('== throwaway identity ==')
console.log('did ', id.did)
console.log('handle ', handle)
console.log('rootDidKey ', id.rootDidKey, '(genesis vm + rotationKey)')
console.log('deviceDidKey', id.deviceKey.didKey, '(rotation vm)')
console.log('\n== offline validation (§6 mirror) ==')
const g = checkGenesisOp(id.genesisOp, id.did)
const r = checkUpdateOp(id.rotationOp, id.genesisOp)
console.log('genesis :', g.ok ? 'OK' : 'FAIL — ' + g.error)
console.log('rotation :', r.ok ? 'OK' : 'FAIL — ' + r.error)
console.log('DID = hash(genesis)? ', didForCreateOp(id.genesisOp) === id.did)
console.log('rotation.prev = cid(gen)? ', id.rotationOp.prev === opCid(id.genesisOp), '(' + id.rotationOp.prev + ')')
console.log('\n== genesis op (would POST /:did --genesis) ==')
console.log(JSON.stringify(id.genesisOp, null, 2))
console.log('\n== rotation op (would POST /:did) ==')
console.log(JSON.stringify(id.rotationOp, null, 2))
if (process.env.PLC_SUBMIT !== '1') {
console.log('\n[offline only] set PLC_SUBMIT=1 to submit this throwaway to the REAL plc.directory.')
process.exit(0)
}
console.log('\n== SUBMITTING throwaway to https://plc.directory (PERMANENT, junk DID) ==')
const plc = new PlcDirectory({})
const afterGenesis = await plc.submitGenesis({ did: id.did, signedOp: id.genesisOp })
console.log('genesis accepted. getData vm =', afterGenesis.verificationMethods.atproto,
afterGenesis.verificationMethods.atproto === id.rootDidKey ? '(= root)' : '(UNEXPECTED)')
const afterRotate = await plc.submitUpdate({ did: id.did, signedOp: id.rotationOp })
console.log('rotation accepted. getData vm =', afterRotate.verificationMethods.atproto,
afterRotate.verificationMethods.atproto === id.deviceKey.didKey ? '(= device ✓)' : '(UNEXPECTED)')
const doc = await plc.resolve(id.did)
const vm = (doc.verificationMethod || []).find((m) => (m.id || '').endsWith('#atproto'))
console.log('resolved DID doc #atproto verificationMethod:', vm && (vm.publicKeyMultibase || vm.id))
console.log('\nDID unchanged across genesis→rotation:', id.did)
console.log('✓ ceremony validated end-to-end against the real plc.directory.')