1. Run your node
A throwaway node generates its own signing key and accepts commits from anyone — fine for a first boot, wrong for anything real. A practical node runs on a key you control and admits only your identity. That's two keypairs and one admission hook.
Two keypairs, two roles
ENC keeps the sequencer (who orders events) separate from the owner (who authors them), so you generate two identities:
| Key | Role |
|---|---|
| Node key | The node's sequencer key — signs every receipt and Signed Tree Head to prove ordering. It never signs for you, and thanks to host-delegated signing it never enters the kernel's WASM memory. A raw keypair is fine; nothing else needs to read it. |
| Owner seed | Your identity — mints and owns your enclave, and signs every write. It's a 12-word BIP-39 seed so you can import it into the ENC wallet extension in step 4; the admission hook lets only this identity through. |
The owner is a seed (not a raw key) because the wallet extension custodies your identity from a
seed phrase. A tiny helper derives the ENC identity from the seed exactly the way the extension
does (BIP-44 path m/44'/60'/0'/0/0 → x-only Schnorr pubkey), so the seed you import and the key
your scripts sign with are one identity:
// derive the OWNER identity from a 12-word seed, the extension's way
import * as bip39 from '@scure/bip39'
import { wordlist } from '@scure/bip39/wordlists/english'
import { HDKey } from '@scure/bip32'
import { derivePublicKey, bytesToHex } from '@enc-protocol/client'
export function generateMnemonic() {
return bip39.generateMnemonic(wordlist)
}
/** mnemonic → { privateKey, publicKeyHex } — the extension's account 0 */
export function ownerFromMnemonic(mnemonic, index = 0) {
const seed = bip39.mnemonicToSeedSync(mnemonic.trim())
const hd = HDKey.fromMasterSeed(seed).derive(`m/44'/60'/0'/0/${index}`)
return { privateKey: hd.privateKey, publicKeyHex: bytesToHex(derivePublicKey(hd.privateKey)) }
}Now generate both and write them where each belongs — run once:
import { generateKeypair, bytesToHex } from '@enc-protocol/client'
import { generateMnemonic, ownerFromMnemonic } from './owner.mjs'
import { writeFileSync } from 'node:fs'
const node = generateKeypair() // the node's sequencer key
const seed = generateMnemonic() // YOUR identity — a 12-word seed
const owner = ownerFromMnemonic(seed)
writeFileSync('node.key', bytesToHex(node.privateKey)) // the node signs with this
writeFileSync('owner.seed', seed) // import THIS into the wallet (step 4)
console.log('NODE_PRIVATE_KEY =', bytesToHex(node.privateKey))
console.log('NODE_PUBKEY =', bytesToHex(node.publicKey)) // the sequencer (seq_pub) in receipts
console.log('OWNER_PUBKEY =', owner.publicKeyHex) // the hook admits this
console.log('\nYour 12-word seed — import into the ENC wallet extension:\n ', seed)npm config set @enc-protocol:registry https://npm-registry.ocrybit.workers.dev/
npm install @enc-protocol/client @scure/bip39 @scure/bip32
node keygen.mjs
echo 'owner.seed' >> .gitignore # your identity — keep it out of gitGet the node + point it at your keys
git clone https://github.com/enc-protocol/impl-node
cd impl-node && yarn installSet the key the node signs with (NODE_PRIVATE_KEY) and the one identity it will admit
(OWNER_PUBKEY):
[vars]
NODE_PRIVATE_KEY = "<your NODE_PRIVATE_KEY>"
OWNER_PUBKEY = "<your OWNER_PUBKEY>"The hook — admit only your pubkey
A node admits commits through an
admission hook chain: each hook either
passes a commit or rejects it — it can never alter one. A Lean theorem
(replay_invariant_under_hook_swap, spec Enc.Core.Hooks) proves hooks are orthogonal to the
protocol, so this gate changes who may write without changing a single emitted byte. (It's the
modern replacement for the old provisioner allowlist.)
Every commit carries a from — the pubkey that signed it — so the gate is one comparison: admit
a commit only when its from equals your owner pubkey. In js/node/worker/enclave.js, where the
enclave handles a commit (handleCommitSimple), add a validateContent hook:
const OWNER_PUBKEY = this.env.OWNER_PUBKEY // …or hardcode your owner pubkey hex right here
return handleCommitSimple(this.sql, this.node, body, {
// admit only commits signed by the owner; reject everyone else
validateContent: (commit) =>
commit.from === OWNER_PUBKEY
? null // pass
: 'unauthorized: only the owner may write to this node', // reject
onEvent: (e) => { /* …existing archive / broadcast / push… */ },
})Gating on the claimed from is a cheap early reject — and it's safe, because the node still
verifies the signature on every commit it admits: a forger who stamps your pubkey onto a commit
without your private key fails that check. Claimed-from filter + signature verification = only
your key writes to your node. (Reads stay governed by the enclave's RBAC, which you set when you
mint it in step 3.)
Boot it
yarn wrangler dev --config test/wrangler.toml --local # → http://127.0.0.1:8787curl -s http://localhost:8787/ | head -c 60 # the protocol bannerLeave it running in its own terminal. In step 3 you'll mint your enclave — owned by the very owner identity this node now admits — and the wallet in step 4 will sign with it.
Next: Run a dataview server →