Step-by-step — PQC · Hybrid Signing
Lab 5 enables a strict verifier policy locally. In a deployed federation, a hub can pin a peer’s advertised PQ key and reject a later downgrade for that peer. Requiring PQ from every peer is a separate opt-in setting,
AIMARKET_PQC_REQUIRE=1. This policy covers off-chain signatures. Base escrow transactions still use the EVM signature scheme.
Audience: engineers migrating signing to post-quantum without breaking today's federation. Languages:
COURSE_LANG=en|ru|es|fr|zh· catalogs ini18n/· this guide in English. Other languages: RU · ES · FR · ZH
Why this course
Signatures are statements about the past. A receipt disputed years from now must already resist a quantum adversary. Replacing Ed25519 with ML-DSA alone breaks interoperability today (fail-closed verifiers, young libraries). The AIMarket hub ships hybrid: Ed25519 + ML-DSA-65 over the same canonical — see the LIVE envelope at https://modelmarket.dev/.well-known/ai-market.json (pq_algorithm=ml-dsa-65).
This course teaches: why hybrid, dual-orbit identity, sign a receipt, verify offline, and refuse single-orbit (phase 3). It never presents SIM crypto as LIVE.
Setup (10 minutes)
git clone https://github.com/alexar76/aimarket-courses.git
cd aimarket-courses/pqc-hybrid-signing-course
pip install -e ".[dev]" # cryptography>=42 required
pip install -e ".[pqc,dev]" # optional: dilithium-py for local ML-DSA
pytest -q
Without dilithium-py you can still verify Ed25519 and inspect LIVE hybrid fields — PQ sign is labeled skipped, never faked.
export COURSE_LANG=ru # or es|fr|zh
python labs/lab01_why_hybrid.py
PQC_COURSE_FETCH_LIVE=1 python labs/lab01_why_hybrid.py # optional LIVE
Module M1 — Why hybrid
Lab: labs/lab01_why_hybrid.py · ~15 min
- Read the four bullets: classical alone, PQ alone, hybrid, downgrade.
- Confirm
envelope_is_hybridis false for classical-only shapes. - Optional:
PQC_COURSE_FETCH_LIVE=1— inspect LIVE hub signature; classical verified offline; PQ verified only if dilithium is installed.
Exercise: assert why_hybrid_brief keys; classical shape is not hybrid.
Module M2 — Key identity
Lab: labs/lab02_key_identity.py · ~15 min
HybridSigner(enable_pq=True)prints dual public keys (or labeled PQ skip).- Remember: pin both keys out-of-band. A self-asserted PQ key is worthless against a broken classical key.
Exercise: assert classical key present; PQ key or pq_skipped.
Module M3 — Sign a receipt
Lab: labs/lab03_sign_receipt.py · ~20 min
- Sign a capability receipt over
receipt_canonical. - Inspect
algorithm/valueand optionalpq_*. - If dilithium missing: message says PQ skipped — classical signature is still real.
Exercise: sign_receipt; assert Ed25519 fields; hybrid if PQ available.
Module M4 — Verify offline
Lab: labs/lab04_verify_offline.py · ~20 min
- Verify your signed receipt with pinned keys — no hub callback.
- Wrong message → classical fails.
- Optional LIVE fetch verifies classical on the hub envelope offline.
Exercise: offline verify; never claim PQ success without dilithium.
Module M5 — Capstone: hybrid gate
Lab: labs/lab05_hybrid_gate_capstone.py · ~25 min
strip_pqsimulates a downgrade attack.refuse_single_orbitmust refuse classical-only even if Ed25519 verifies.require_pq=Falsestill accepts classical (migration phase).- Full hybrid accepted only when ML-DSA is real locally.
Exercise: implement the refuse path; accept hybrid only when PQ is real.
Exercises & certificate
# fill # YOUR CODE HERE in courselib/exercises.py (see exercise_solutions.py)
python labs/run_exercises.py
python labs/run_exercises.py --certificate "Your Name"
Browser checkboxes do not issue certificates.
Self-check
- [ ] Explained why hybrid beats classical-only and PQ-only
- [ ] Generated dual-orbit identity (or labeled skip)
- [ ] Signed and verified a receipt offline
- [ ] Proved refuse_single_orbit blocks downgrade
- [ ] Inspected LIVE hub hybrid fields without calling them SIM
Troubleshooting
| Symptom | Fix |
|---|---|
cryptography ImportError |
pip install -e ".[dev]" |
| PQ always skipped | pip install -e ".[pqc]" |
| Live fetch fails | offline OK — tests skip; or set PQC_COURSE_WELL_KNOWN |
| Exercises all fail | fill stubs; CI checks exercise_solutions |