World-class educational lab for the Midnight auth trap:
ownPublicKey() is a prover-supplied witness — not bound to the transaction signer.
Any assert(ownPublicKey() == owner) is theater. Prefer witness-derived keys
(example-bboard
/ create-mn-app --template bboard).
By @kshot9000
· nightdream.xyz.
Honest scope: LOCAL-TRUE browser persistence (schema v2 localStorage, multi-tab sync, JSON export/import).
Does not run Compact, a proof server, or Lace makeTransfer.
Does not claim GitHub Pages is live until Actions are enabled.
SHA-256 “derive” here is a teaching stand-in — verify against official Compact crypto before shipping.
LOCAL-TRUE ≠ on-chain.
Attack journey
Labeled phases for the unsafe forge path. Error / reject is a first-class state — never silent.
1
IdleNo unsafe contract deployed
2
Deployedowner ← claimed ownPublicKey
3
ForgedMallory bypasses admin gate
4
Witness-boundSafe path rejects forger
Phase: idle — deploy the unsafe contract to begin.
The rule in one screen
Safe uses of ownPublicKey() are token routing after a real auth gate — never the gate itself.
Unsafe
Auth via ownPublicKey()
Prover picks any 32-byte value. Assertions that compare it to a stored owner are theater.
assert(ownPublicKey() == admin)
Blacklists keyed only by ownPublicKey()
Role maps filled from ownPublicKey() at register
Safe
Witness-derived identity
Caller proves knowledge of a secret. Derive a public commitment in-circuit (bboard / zkloan pattern).
witness localSecretKey(): Bytes<32>
publicKey(sk) / persistentHash with domain sep
ownPublicKey() only to route shielded coins
Public claim rail
Anyone can assert “I am Alice” by stuffing Alice’s pk into the witness.
0% forge pressure
Secret-bound rail
Without Alice’s secret, Mallory’s derived pk never matches owner.
100% integrity (until safe deploy)
Forge theater simulated
Local model of a naive “owner = ownPublicKey” contract. No Compact runtime — educational only.
1. Deploy (honest Alice)
Alice deploys and stores her claimed public key as owner.
64 hex chars · 32 bytes
Not deployed.
2. Attack (Mallory)
Mallory calls adminOnly() while supplying Alice’s public key as her “ownPublicKey”.
Waiting…
3. Same call with witness binding
With a secret-bound owner, Mallory cannot reproduce Alice’s derived key without Alice’s secret.
Safe contract not deployed.
Auth scorecard
Interactive checklist — tick what your Compact design actually does. Local only; nothing leaves the browser.
—health
Check items to grade your design.
Safe local bulletin board
bboard-inspired LOCAL-TRUE board: posts bind to publicKey(localSecret). Schema v2 localStorage · BroadcastChannel sync · JSON export/import. Not on-chain.
Donate + @kshot9000 · sensitive secrets may be in exports
Your local secret
Generated in-browser. Never sent anywhere. Mimics localSecretKey witness.
—pk: —
Post message
0 / 280
Board (0)
Wallet discovery
Enumerate Object.entries(window.midnight). Do not hardcode mnLace — keys are often UUIDs. Read-only — no connect / transfer.
Not scanned yet.
Compact snippets
Illustrative — verify against current Compact docs / example-bboard before shipping.
✗ Forgeable
export ledger owner: ZswapCoinPublicKey;
constructor() {
owner = ownPublicKey(); // anyone can later claim this
}
export circuit adminOnly(): [] {
assert(ownPublicKey() == owner, "not owner"); // bypassable
}