Prove two small computations with Plonky2, then verify the resulting proofs as a byte stream — either locally on a host, or on a Ledger Flex, which checks the ~298-310 KB proof on a device with 36 KB of RAM without ever holding the whole thing in memory.
Two fixed circuits are supported, each with its own example, artifacts, and verifier key:
display— a 32-step xorshift image generator. Verifying it yields a 32x32 picture.sha256— proof of knowledge of a SHA-256 preimage. Verifying it yields the digest.
This repository is a fork of 0xPolygonZero/plonky2. The Plonky2 library itself is kept
(the prover and verifier both need it); a handful of internal functions were made public so
the streaming verifier can reuse Plonky2's exact verification logic. See
Changes to Plonky2.
- Prove the good computation of
displayandsha256(the Plonky2 examples). - Verify and interpret proofs locally — on the host, both by reconstructing the proof and by true streaming, rendering the image or printing the digest.
- Build, install and manage the Ledger app — stream proofs to it, run its test suite,
measure its RAM/timing. See
ledger-app/README.md.
| Path | What it is |
|---|---|
plonky2/ |
Fork of the Plonky2 prover/verifier library. The examples examples/display.rs and examples/sha256.rs build the two circuits, prove them, and write artifacts in the "Solution-B" streaming layout; examples/shared/streaming_artifacts.rs is the shared serializer. |
field/, util/, maybe_rayon/ |
Plonky2's supporting crates (dependencies of plonky2), upstream. |
stream_verifier/ |
no_std core that verifies a Solution-B proof in one forward-only pass, with a working set that does not grow with the proof size. Shared verbatim by the host CLI and the Ledger app. |
verifier/ |
Host CLI. Two binaries: verify_b (reconstructs the proof, then runs the stock verifier) and stream_verifier (true bounded-memory streaming). Both interpret the verified outputs — render the image / print the digest. |
ledger-app/ |
The Ledger Flex app. Verifies a streamed proof on-device and shows the result. Its own Cargo workspace (BOLOS cross-compile). |
verify.sh |
Convenience wrapper that builds and runs the host verifier (verify_b). |
Generated files — target/, display_artifacts/, sha_artifacts/, and bitmaps/ — are
git-ignored; the commands below (re)create them.
Plonky2 needs a nightly toolchain; rust-toolchain already pins
nightly, so cargo picks it up automatically. Building/loading the Ledger app has its own
prerequisites (Docker, ledgerblue) documented in ledger-app/README.md.
Each example builds its circuit, proves it, and writes three files into its artifacts
directory (display_artifacts/ or sha_artifacts/): the verifier key
(verifier_circuit_data.bin), the proof in the streaming layout
(proof_with_public_inputs.bin), and a convenience copy of the public outputs
(output.bin).
# display: optional seed as decimal or 0x-hex (defaults to 0x12345678)
cargo run --release -p plonky2 --example display
cargo run --release -p plonky2 --example display -- 0xdeadbeef
# sha256: the preimage whose digest is proven
cargo run --release -p plonky2 --example sha256 -- "hello world"The quickest path is verify.sh, which builds verify_b and runs it against a program's
artifacts. For display it renders the verified image into ./bitmaps/; for sha256 it
prints the verified digest.
./verify.sh # display (default): display_artifacts/ -> bitmaps/
./verify.sh sha256 # sha256: verify sha_artifacts/ and print the digestTo exercise the bounded-memory streaming verifier — the host twin of what the Ledger
app runs, delivering the proof in small packets drained through a tiny fixed buffer — run
the stream_verifier binary:
cargo run --release -p verifier --bin stream_verifier # display
cargo run --release -p verifier --bin stream_verifier -- --program sha256Both verifiers cross-check output.bin against the proof's public inputs, but that check is
a convenience only: the outputs are part of the proof and are covered by the Fiat-Shamir
transcript.
Everything about building, sideloading, streaming proofs to, testing, and measuring the
Ledger Flex app lives in ledger-app/README.md. In short:
# Both verifier keys are compiled into the app, so both artifact sets must exist first.
cargo run --release -p plonky2 --example display
cargo run --release -p plonky2 --example sha256 -- "hello world"
ledger-app/build.sh # build the ELF + installable .apdu (needs Docker)
ledger-app/run_speculos.sh # run under the emulator
python3 ledger-app/tools/send_proof.py # stream a proof and show the result
python3 ledger-app/tools/run_tests.py # the acceptance/rejection test suiteThe proof is stored in the "Solution-B" layout: a small header (~10% of the bytes) followed by the FRI query rounds (~90%). Every Fiat-Shamir challenge can be derived from the header alone, so a verifier reads the header, fixes the challenges, and then checks the query rounds one at a time — each read, verified against the already-fixed challenges, and dropped before the next arrives. Peak memory is reached while processing the header and does not grow with the number of query rounds.
Only the byte order of the file differs from Plonky2's canonical format; the transcript
order — and therefore the proof's validity — is unchanged, so this is exactly as sound as
verifying a whole proof. The crate-level docs of stream_verifier
explain the verification order, the memory model, and the soundness argument in detail.
The custom crates reuse Plonky2's own verification code rather than reimplementing any cryptography. To make that possible, the fork only widens visibility and splits one function (no logic changes):
plonk::get_challenges::get_challengesis madepub— derives every challenge from the header, before any query round.plonk::verifier: the PLONK-identity check is split out into a publicverify_plonk_identity(the non-FRI half ofverify_with_challenges), so it can run as soon as the openings arrive.fri::verifier:fri_verifier_query_round(pluscompute_evaluation,fri_combine_initial,fri_verify_proof_of_work,PrecomputedReducedOpenings) are madepub, and query-round verification takes the commit-phase caps and final polynomial directly instead of a wholeFriProof, so rounds can be checked as they stream in.plonk::proof::OpeningSet::to_fri_openingsandplonk::circuit_data::CommonCircuitData::get_fri_instanceare madepub.plonky2/Cargo.toml: addsplonky2_u32,sha2, andhexas dev-dependencies, used only by thesha256example.Cargo.toml: adds the custom crates as workspace members and patchescrates-io'splonky2to this workspace's local crate (so a single copy is used everywhere, including transitively viaplonky2_u32).
As a fork of Plonky2, this repository is licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.