Step-by-step course guide
Keep the lab proof and deployment evidence separate. The local ECVRF, VDF and lottery exercises require no payment. On the lottery showcase, identify the environment and inspect the draw evidence before interpreting it. UNI uses a simulated chain; a Base transaction must be checked on Base. The documented live entry is an unpaid work seat through
enterFromWork; paid tickets are disabled.
Audience: developers who need randomness a lottery player can verify — before wiring
lottery/on Base. Languages:COURSE_LANG=en|ru|es· UI strings ini18n/· this guide in English. Русский: step-by-step.ru.md · Español: step-by-step.es.md
Why this course
Most lotteries, raffles, and "random" drops trust an operator who can quietly re-roll an unfavourable result. This course shows you the cryptography that removes that trust: a verifiable chaos draw, a verifiable delay function (VDF) that proves elapsed work, an ECVRF proof you can verify offline from 80 bytes, and an RSW time-lock that hides a value until provable work unseals it. You then build a biased lottery, attack it, and fix it with a draw the operator cannot grind — exactly the pattern the AIMarket lottery/ contracts consume on Base.
Every lab talks to a real AIMarket oracle — Platon, Chronos, Sortes, Aestus — running locally, not a mock. Each prints a Trace block so the verifiable artifact (proof, beta, puzzle, VDF vector) is something you can inspect, not take on faith.
Table of contents
- Choose your track
- Setup (10 minutes)
- Module M1 — Platon chaos VRF
- Module M2 — Chronos VDF
- Module M3 — Sortes ECVRF
- Module M4 — Aestus time-lock
- Module M5 — Commit–reveal & bias attacks
- Module M6 — On-chain verification
- Reading the trace (every lab)
- Exercises & certificate
- Bridge: from course to the lottery on Base
- Self-check checklist
- Troubleshooting
Choose your track
| Track | Modules | Time | Oracles |
|---|---|---|---|
| Basic | M1 → M2 → M3 → M4 | ~2 h | Platon, Chronos, Sortes, Aestus (all local) |
| Economy | Basic + M5 | +30 min | Sortes (fair vs biased lottery) |
| Advanced | Economy + M6 | +30 min | Chronos + Sortes → Base testnet vector |
Every lab prints a Trace — treat it as the lab's audit log. See Reading the trace.
Setup (10 minutes)
Step 1 — Clone and install
The exact quickstart commands (note the [oracles,dev] extra — oracles pulls Platon/Chronos/Sortes/Aestus, dev pulls pytest):
git clone https://github.com/alexar76/aimarket-courses.git
cd aimarket-courses/verifiable-randomness-course
pip install -e ".[oracles,dev]"
From the aicom monorepo (recommended for contributors): the oracle packages resolve automatically via sibling paths under oracles/ — courselib/oracle_paths.py walks up to the monorepo root and adds oracles/oracles/{chronos,sortes,aestus} and oracles/oracles/platon/backend to sys.path. No extra install needed beyond numpy for Platon.
cd courses/verifiable-randomness-course
pip install -e ".[oracles,dev]"
Step 2 — Verify tests
pytest -q
Expected: all tests pass (i18n parity across en/ru/es, randomness helpers, exercise checks).
Step 3 — Pick a language (optional)
The lab UI strings (titles, proof/verify/trace labels, ticket names, audit notes) come from i18n/{en,ru,es,fr,zh}.json. Switch with COURSE_LANG:
export COURSE_LANG=ru # or es, default en
python labs/lab01_platon_chaos.py
Step 4 — Colab alternative
- Open the course site → Open in Colab on any lab.
- Run the setup cell (clone +
pip install -e ".[oracles,dev]"). - Set
os.environ["COURSE_LANG"] = "ru"in the setup cell if you want a localized run. - Run the lab cell.
Module M1 — Platon chaos VRF
Concept: unbiasable randomness from a verifiable chaos oracle — chaos state + client seed + OS entropy, bound by an Ed25519 signature.
Lab: labs/lab01_platon_chaos.py · Time: ~15 min · Prerequisites: setup done (numpy + Platon)
Steps
-
Read the docstring at the top of
labs/lab01_platon_chaos.py(industry map: lottery, gaming). -
Run it locally:
python labs/lab01_platon_chaos.py -
Watch the output: the
proofscheme, the first bytes of the draw, and theverifyresult. -
Open
courselib/randomness.py→chaos_draw(): it spins up a temp-dirSigner, builds a chaosstate, callsdraw_randomness(...), thenverify_randomness(result, public_key).
Expected output (EN)
- Header line
== Chaos VRF with Platon == proof: scheme=platon-chaos-vrf/v1- A signed 32-byte draw (
result: <hex>…) verify: True- A
traceblock with onechaos_drawevent (verified=True, bytes=32)
Self-check
- [ ] You can explain why a client seed + signature makes the draw unbiasable
- [ ] You see the draw is 32 bytes and
verified: Trueagainst the public key
Module M2 — Chronos VDF
Concept: a Wesolowski VDF proves elapsed sequential work on an RSA-2048 unknown-order group — proof-of-elapsed-work, not trust in a wall clock.
Lab: labs/lab02_chronos_vdf.py · Time: ~20 min · Prerequisites: M1
Steps
- Run
python labs/lab02_chronos_vdf.py. - Read the
proofline: the Wesolowski proofpiand primel;verify: True. - Note the tamper demo: the lab re-imports
chronos.vdfand callsvdf.verify(g, y+1, …)on a corrupted output — it printsFalse. - Open
courselib/randomness.py→vdf_eval():hash_to_group→evaluate→prove→verify. The lab usesdifficulty=700(~700 squarings) so it runs in seconds.
Expected output (EN)
proof: pi=<…>… l=<prime>verify: Truetampered VDF verify: Falsetracewithvdf_evalandtamper_checkevents
Self-check
- [ ] You can state what a VDF proves (sequential work) and what it does not (real time)
- [ ] You watched verification reject a tampered output
Module M3 — Sortes ECVRF
Concept: an RFC 9381 ECVRF proof. For a fixed (public key, alpha) there is exactly one valid beta — the operator cannot grind for a favourable output. The proof is 80 bytes and verifies offline, without calling the oracle again.
Lab: labs/lab03_sortes_ecvrf.py · Time: ~20 min · Prerequisites: M1
Steps
- Run
python labs/lab03_sortes_ecvrf.py. - Confirm the proof is exactly 80 bytes (
80 bytes = <hex>…). - Note
beta(the verifiable random output) andverify: True. - Open
courselib/randomness.py→ecvrf_draw():sk_to_pk→prove(sk, alpha)→verify(pk, alpha, pi).
Expected output (EN)
proof: 80 bytes = <hex>…result: beta=<hex>…verify: Truetracewith oneecvrfevent (proof_len=80, verified=True)
Self-check
- [ ] You can explain "one beta per (pk, alpha)" and why that blocks grinding
- [ ] You verified the proof is 80 bytes (this is the offline-verify number from the tagline)
Module M4 — Aestus time-lock
Concept: an RSW time-lock puzzle. Seal a value now; anyone can open it only after T sequential squarings — encrypt now, decrypt after a provable delay. The building block for sealed bids and commit-reveal where no one should peek early.
Lab: labs/lab04_aestus_timelock.py · Time: ~20 min · Prerequisites: M2
Steps
- Run
python labs/lab04_aestus_timelock.py. - Watch the seal → open roundtrip:
T=350squarings, then the opened payload andverify: True. - Open
courselib/randomness.py→timelock_seal():rsw.seal(message, T=...)→rsw.open_puzzle(puzzle);message_matchconfirms the plaintext came back intact.
Expected output (EN)
proof: T=350 squaringsresult: <opened payload>(the sealed-bid string)verify: Truetracewith onetimelockevent (T=350, opened=True)
Self-check
- [ ] You can explain why a time-lock hides a value without a trusted third party
- [ ] You saw the sealed message recovered after
Tsquarings
Module M5 — Commit–reveal & bias attacks
Concept: the payoff module. Build a lottery a dishonest operator can grind, demonstrate the bias, then replace the draw with a Sortes ECVRF the operator cannot grind.
Lab: labs/lab05_commit_reveal.py · Time: ~25 min · Track: economy · Prerequisites: M3
Steps
- Run
python labs/lab05_commit_reveal.py. - Compare the two tickets:
biased ticketvsfair VRF ticket. Theverify VRF: Trueline proves the fair draw carries a verifiable proof. - Read the audit note the lab prints: "Fair draw binds one beta per seed — operator cannot grind."
- Open
courselib/randomness.py→BiasedLottery(loops up to 200 attempts grindingoperator_secret|player_seed|attemptfor a jackpot) vsFairLottery(one ECVRF draw → one ticket, no grinding possible).
Expected output (EN)
biased ticket: <n>andfair VRF ticket: <n>verify VRF: True- The audit note line
tracewithcommitanddrawevents (vrf_ok=True)
Self-check
- [ ] You can describe the grinding attack the
BiasedLotteryperforms - [ ] You can explain why
FairLottery(one beta per seed) is immune
Module M6 — On-chain verification
Concept: package the proofs for an on-chain consumer — a Wesolowski VDF vector shaped for a Solidity verifier, plus a 32-byte lottery word derived from a Sortes ECVRF beta.
Lab: labs/lab06_onchain_consumer.py · Time: ~20 min · Track: advanced · Prerequisites: M2, M3
Steps
- Run
python labs/lab06_onchain_consumer.py. - Note the
protocol: wesolowski-vdfline,verify: True, and the 32-bytelottery word: 0x…. - Open
courselib/randomness.py→onchain_vdf_vector()(emitsscheme/seed/difficulty/y/pi/l/validas strings ready to feed a verifier) andlottery_word_from_vrf()(takes the first 32 bytes of the ECVRF beta). - Bridge: the lab prints a deploy note pointing at lottery/docs/deployments-base.md for the live
ChronosVDF.solverifier on Base.
Expected output (EN)
protocol: wesolowski-vdfverify: Truelottery word: 0x<hex>…- The deploy note line
tracewithonchain_vdfandlottery_wordevents
Self-check
- [ ] You can name the two artifacts an on-chain consumer needs (VDF vector + VRF word)
- [ ] You know where to find the Base deployment docs
Reading the trace (every lab)
Concept: every lab logs a small Trace (see courselib/trace.py) so agent/oracle behaviour is inspectable, not magic.
Steps (repeat after each lab)
- Scroll to the
trace (<n> events):block at the bottom of the lab output. - Read each event dict —
kindplus the fields the lab logged (e.g.chaos_draw→verified, bytes;vdf_eval→difficulty, valid;tamper_check→valid=False). - Map each event to a line you saw in stdout above it. The trace is the lab's "unit-test log" for verifiable randomness.
Self-check
- [ ] You can point to the one event that proves a draw verified
- [ ] In M2 you can find the
tamper_checkevent that showsvalid=False
Exercises & certificate
After the labs, run the DIY checks. Each module has a self-contained exercise in courselib/exercises.py (e.g. M2 tampers a VDF and asserts verify fails; M3 asserts the proof is exactly 80 bytes; M5 asserts the fair draw verifies):
python labs/run_exercises.py # run all six, prints ✓/✗ per module
python labs/run_exercises.py --module m3 # run just one module's check
When all six pass, mint your certificate:
python labs/run_exercises.py --certificate "Your Name" --lang en # or --lang ru / es
This writes certificate.html — open it in a browser and Print → Save as PDF. The credential ID is derived deterministically from name + issue date (courselib/certificate.py). The certificate is gated on the exercises passing unless you pass --skip-check.
Bridge: from course to the lottery on Base
Goal: the same proofs you produced locally are what the AIMarket lottery/ contracts verify on Base.
| Course | Lottery on Base |
|---|---|
| M2 Chronos VDF vector | ChronosVDF.sol Wesolowski verifier |
| M3 Sortes ECVRF beta | 32-byte lottery word (M6 lottery_word_from_vrf) |
| M5 fair vs biased draw | Why the consumer uses VRF, not a trusted beacon |
M6 onchain_vdf_vector() |
The exact {y, pi, l} vector the verifier consumes |
Deployment addresses and the Solidity verifier live in lottery/docs/deployments-base.md.
Self-check checklist
After the basic track you should be able to:
- [ ] Run lab01–lab04 locally (and in Colab) in EN, RU, ES
- [ ] Explain chaos VRF, VDF, ECVRF, and time-lock in one sentence each
- [ ] Show that a tampered VDF output fails verification (M2)
- [ ] State why an 80-byte ECVRF proof can be verified offline (M3)
After the economy + advanced tracks add:
- [ ] Run lab05 and describe the grinding attack + the VRF fix
- [ ] Run lab06 and produce a Base-ready VDF vector + lottery word
- [ ]
pytest -qgreen andpython labs/run_exercises.pyall ✓
Troubleshooting
| Issue | Fix |
|---|---|
No module named 'chronos' |
pip install -e ".[oracles]", or run from the aicom monorepo |
No module named 'oracle_core' |
ensure oracles/core is on the path — automatic in the monorepo; pulled by [oracles] for Sortes/Platon |
Oracle package '…' not installed |
not in a monorepo and extra missing — install .[oracles] or clone next to oracles/ |
| Platon import fails | install numpy; ensure oracles/oracles/platon/backend exists |
| Slow VDF lab | labs use ~700 squarings (M2) / T=350 (M4) — lower difficulty in your own exercise only, not the labs |
| Empty RU/ES strings | set COURSE_LANG=ru|es; check i18n/ru.json keys match i18n/en.json |
pytest i18n failure |
add the missing key to all three JSON catalogs (en/ru/es) |
| Colab old code | re-run the setup cell; pin the main branch |
Regenerate site & notebooks
After editing labs or i18n:
python3 scripts/build_course_assets.py
Guides in docs/ are hand-maintained — update EN first, then the RU/ES mirrors.