# Grant Application - Lean proof of the GLV recoding and ladder in pasta\_curves (part 3 of pasta\_curves#174)

**URL:** <https://forum.zcashcommunity.com/t/grant-application-lean-proof-of-the-glv-recoding-and-ladder-in-pasta-curves-part-3-of-pasta-curves-174/58412>\
**Category:** Applications\
**Created:** [October 21, 2026, 1:08pm UTC](https://forum.zcashcommunity.com/t/grant-application-lean-proof-of-the-glv-recoding-and-ladder-in-pasta-curves-part-3-of-pasta-curves-174/58412 "2026-10-21T13:08:22Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![athexweb3](https://sea2.discourse-cdn.com/zcash/user_avatar/forum.zcashcommunity.com/athexweb3/32/49663_2.png) [@athexweb3](https://forum.zcashcommunity.com/u/athexweb3)\
**Post date:** [October 21, 2026, 1:08pm UTC](https://forum.zcashcommunity.com/t/grant-application-lean-proof-of-the-glv-recoding-and-ladder-in-pasta-curves-part-3-of-pasta-curves-174/58412/1 "2026-10-21T13:08:22Z")

</div>

Hi all, I’m Athex. Rifat Ahmed and I work at [Nirapod](https://github.com/nirapod-labs), and I’ve contributed to the [Jasmin compiler](https://github.com/jasmin-lang/jasmin/pulls?q=is%3Apr+is%3Amerged+author%3Aathexweb3), mostly its ARMv8-A back end.

We’ve finished a machine-checked Lean proof of part 3 of [pasta\_curves#174](https://github.com/zcash/pasta_curves/issues/174), the wNAF recoding and the Straus ladder in [`src/glv.rs`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/glv.rs). Orchard has used this code for batched trial decryption since [0.15.4](https://github.com/zcash/orchard/blob/main/CHANGELOG.md), and so far it has only been tested, on chosen and random scalars. The proof is up as a pull request to pasta\_curves. The recoding is proved against [Aeneas](https://github.com/AeneasVerif/aeneas)’ translation of the Rust, and the ladder against a Lean model of it.

We’re asking for $7,000. None of it is paid unless the pasta\_curves maintainers accept the proof: $5,500 on acceptance, then $1,500 for six months of upkeep after the merge.

Questions and criticism welcome, especially on whether the theorem statements say the right thing.

Application: [Grant Application - Lean proof of the GLV recoding and ladder in pasta\_curves (part 3 of pasta\_curves#174) · Issue #507 · ZcashCommunityGrants/zcashcommunitygrants · GitHub](https://github.com/ZcashCommunityGrants/zcashcommunitygrants/issues/507)

Pull request: [lean: prove the GLV wNAF recoding and Straus ladder by athexweb3 · Pull Request #180 · zcash/pasta\_curves · GitHub](https://github.com/zcash/pasta_curves/pull/180)

Proofs, tests and the differential test: [GitHub - nirapod-labs/pasta-glv-proofs: Machine-checked Lean 4 proof of the wNAF recoding and Straus ladder in pasta\_curves' GLV scalar multiplication · GitHub](https://github.com/nirapod-labs/pasta-glv-proofs)

The full application is below, the same text as on GitHub.

### Project Summary

We have completed a machine-checked Lean proof of the wNAF recoding and the Straus ladder in pasta\_curves’ GLV scalar multiplication (part 3 of [pasta\_curves#174](https://github.com/zcash/pasta_curves/issues/174)), and opened it as a pull request to pasta\_curves. This is the code that Orchard’s batched trial decryption calls. The recoding is proved against Aeneas’ translation of the Rust, and the ladder against a Lean model of it. We request $7,000, paid only if the pasta\_curves maintainers accept the proof: $5,500 on acceptance and $1,500 for six months of upkeep after the merge.

### Project Description

[pasta\_curves#174](https://github.com/zcash/pasta_curves/issues/174), opened by daira, asks for a formal proof of the crate’s GLV scalar multiplication in three parts: the endomorphism, the scalar decomposition, and the wNAF recoding with the Straus ladder. It asks for a proof of whichever algorithm the crate settles on as the most efficient, and [pasta\_curves#125](https://github.com/zcash/pasta_curves/pull/125), currently a draft, proposes an alternative recoding based on Eisenstein integers. Our proof covers part 3 for the algorithm in the crate today (`src/glv.rs` at commit `e3c0f02`).

For [`wnaf_digits`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/glv.rs#L399-L419), we prove that every magnitude below 2^127 is recoded without a panic into at most 128 digits, each zero or odd with absolute value at most 7, that encode the signed input. This is proved about a Lean model and also about Aeneas’ machine translation of the Rust function.

For the four entry points ([`mul_glv`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/glv.rs#L82-L89), [`Table::mul`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/glv.rs#L326-L345), `mul_decomposed` on a decomposed scalar, and [`batch_mul_same_scalar_vartime`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/curves.rs#L33)), we prove that they return valid points that represent `[k]P`. We also prove what the ladder and the table builders compute. These results are about a Lean model that mirrors the Rust line by line and was checked against it by reading. They take four hypotheses, about the group order, the endomorphism (part 1), the scalar decomposition (part 2) and the curve primitives.

The proofs use only Lean’s three standard axioms. Run locally on the pull request branch, pasta’s own checks and the nanoda kernel checker pass.

### Proposed Problem

The GLV code that Orchard’s wallet scanning runs has no proof: its constants are checked exactly, but its behaviour only by tests on sampled inputs. Since Orchard 0.15.4, batched trial decryption uses `pasta_curves::glv` ([`Table`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/glv.rs#L252), [`Decomposed`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/glv.rs#L367), [`Table::mul_decomposed`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/glv.rs#L333)): each incoming viewing key is decomposed and recoded once per batch, and the result is reused against every ephemeral key. [pasta’s tests](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/glv.rs#L421) compare this code with native scalar multiplication on fixed sets of 64 deterministic scalars, on edge cases and boundary witnesses, and on randomized property tests. The digits depend only on the scalar, which here is the incoming viewing key. An error in the recoding or the ladder that affected only some scalars could therefore pass every sampled test while causing the affected wallets to miss their incoming notes in every batch.

The proof covers every scalar, under the stated hypotheses. It found no error in the path that callers use. It also establishes two boundary facts above the contract’s 2^127 bound: the least magnitude that needs a 129th digit is 0x88888888888888888888888888888889, and in a build without debug assertions or overflow checks, the u128 addition wraps on exactly four inputs (2^128 − 7, −5, −3 and −1). Neither affects a correct caller.

pasta’s CI regenerates the Aeneas translation and compares it with the committed copy, so a change to `wnaf_digits` fails CI until the translation is regenerated, and the proofs are then checked against the new translation.

### Proposed Solution

Milestone 1 takes the proof to acceptance in pasta\_curves (merged, or confirmed ready to merge): the changes that upstream review requires in the statements, proofs and layout, the branch kept current with `main` and its toolchain pins, and a theorem-to-source map that gives, for each theorem, the functions of `src/glv.rs` and `src/curves.rs` it covers, whether it is proved about the model or about the translation, and its hypotheses.

Milestone 2 keeps the proofs working for six months after the merge: a fix within three working days of any GLV failure in pasta’s Lean CI, and an update to the model within 14 days of any merged change to the Rust it mirrors.

The natural next step is to prove the ladder and tables against Aeneas’ translation as well, the route that [pasta\_curves#174](https://github.com/zcash/pasta_curves/issues/174) itself suggests. The pinned Charon and Aeneas cannot translate that code as written, because it is generic over the curve traits. We have built and checked a refactor of `src/glv.rs` that makes the ladder and most of the table code translatable: generic `_with` functions over private traits, as pasta’s [`invert_with`](https://github.com/zcash/pasta_curves/blob/e3c0f0246f56d351f1a94ff0a74c8d201dbf87d9/src/inversion.rs#L71) is written, with blanket impls of the traits for every curve. The public API and tests are unchanged, and the release machine code on aarch64 is the same apart from references to panic-location records. The refactor is [published in the proof package](https://github.com/nirapod-labs/pasta-glv-proofs/tree/main/refactor). pasta’s contribution rules ask that such a change to the Rust come at the maintainers’ request, so the pull request only offers it. If the maintainers want it, we will propose it to ZCG as a change of scope; it is not funded here. Composing this proof with parts 1 and 2, and any proof of the Eisenstein recoding, are also outside this application.

If it helps the committee, we are glad for it to consult the pasta\_curves maintainers, or to wait for their first response on the pull request, before voting.

### Solution Format

One pull request to pasta\_curves’ `lean/` package: the Lean files, one line in `lean/scripts/gen_aeneas.sh`, two paths in the CI translation filter, and no change to the Rust. The proofs in their original layout, with the Lean tests and the differential test, are published at [GitHub - nirapod-labs/pasta-glv-proofs: Machine-checked Lean 4 proof of the wNAF recoding and Straus ladder in pasta\_curves' GLV scalar multiplication · GitHub](https://github.com/nirapod-labs/pasta-glv-proofs).

### Dependencies

pasta\_curves’ pinned toolchain: Lean v4.31.0, Mathlib, and Charon and Aeneas at `b86120d`. Maintainer review for milestone 1. Milestone 2 starts when the pull request is merged. Nothing from parts 1 and 2 is needed: their results enter as hypotheses, so this work stands on its own.

### Technical Approach

A Lean model mirrors the Rust of the GLV path line by line, and the specifications are stated about it. The recoding is also proved about Aeneas’ machine translation of `wnaf_digits`, so for the recoding the result holds of the translated Rust, with Charon and Aeneas trusted. The ladder and table model was checked against the Rust by reading.

The evidence is public:

- The Lean package builds with `lake build`, and `#print axioms` on the final theorems lists only `propext`, `Classical.choice` and `Quot.sound`. Its CI repeats the build, the axiom check and the differential test on every push.
- [Ten deliberately broken models](https://github.com/nirapod-labs/pasta-glv-proofs/blob/main/Tests/Mutants.lean) each fail the statements they should (twelve refutations, checked by the kernel). [One instance](https://github.com/nirapod-labs/pasta-glv-proofs/blob/main/Tests/NonVacuity.lean) satisfies all four hypotheses at once, so the end-to-end theorems are not vacuous.
- A [differential test](https://github.com/nirapod-labs/pasta-glv-proofs/tree/main/difftest) runs a verbatim copy of `wnaf_digits` against the Lean model: identical digits or an identical panic on 75,223 magnitudes with both signs, in all four combinations of debug assertions and overflow checks.
- [nanoda](https://github.com/ammkrn/nanoda_lib), an independent implementation of Lean’s kernel, accepts the whole library on the pull request branch (308,862 declarations, dependencies included).

Tools: [Lean 4](https://lean-lang.org) and [Mathlib](https://github.com/leanprover-community/mathlib4), [Charon](https://github.com/AeneasVerif/charon) and [Aeneas](https://github.com/AeneasVerif/aeneas), nanoda, and Claude (Anthropic).

Scope: variable-time code only, with no constant-time claims. All four hypotheses are arguments of the theorems, not axioms. The one on the curve primitives is trusted, the one on the group order is not proved here, and those on the endomorphism and the decomposition stand until parts 1 and 2 are proved.

License: the work is released under MIT OR Apache-2.0, pasta\_curves’ own license, which includes MIT.

### Upstream Merge Opportunities

The proof is proposed to zcash/pasta\_curves as [pasta\_curves#180](https://github.com/zcash/pasta_curves/pull/180); the Lean tests, the differential test and the refactor trial stay in the public proof package. [pasta\_curves#174](https://github.com/zcash/pasta_curves/issues/174), opened by daira, is the tracking issue that pasta’s contribution rules ask for. Review will take place on the pull request. The change adds Lean files and touches no Rust, so it asks for review time but changes no shipped code. The refactor described under Proposed Solution would be a separate pull request, sent only if the maintainers ask for it.

### Open Source License

MIT

### Hardware/Software Costs (USD)

$0

### Hardware/Software Justification

None. The builds run on our own machines.

### Service Costs (USD)

$0

### Service Costs Justification

None.

### Compensation Costs (USD)

$7000

### Compensation Costs Justification

Priced per accepted deliverable. Milestone 1, $5,500: $3,000 for the proof delivered with this application (the model, the statements, their proofs, including the proof against Aeneas’ translation of `wnaf_digits`, and the tests), and $2,500 for about 50 hours at $50 an hour to bring it to merge: the changes requested in upstream review, rebasing onto `main` and its pins, and the theorem-to-source map. Nothing in milestone 1 is paid until a maintainer accepts the proof. Milestone 2, $1,500: about 30 hours at $50 an hour over six months of upkeep after the merge.

### Total Budget (USD)

$7000

### Previous Funding

No

### Previous Funding Details

None.

### Other Funding Sources

No

### Other Funding Sources Details

None. We will not apply to any other programme for this work.

### Implementation Risks

The main risk is in review: a maintainer may find that a statement does not say what the Rust does, or may want it stated differently. Either way some proofs have to be reworked. That rework is what milestone 1 is priced for, and the differential test, the broken-model tests and the theorem-to-source map are there to bring such a problem to light early.

Review may also take longer than planned, since maintainer time on pasta\_curves is limited. We will report any delay on the forum thread, and an open pull request does not count as delivered.

The code may move. `src/glv.rs` or the Lean, Mathlib and Aeneas pins can change while the pull request is open; we keep the branch current with `main`, and after the merge milestone 2 covers both. If pasta adopts the Eisenstein recoding of [pasta\_curves#125](https://github.com/zcash/pasta_curves/pull/125) in place of the current one, the recoding and ladder proofs would have to be redone, and we would propose that work separately.

### Potential Side Effects

The main risk is false confidence: reading the result as “all of GLV is verified”. The application, the README and the pull request state which results are about the model and which are about the translated Rust, which hypotheses are assumed, and what is out of scope. The GLV modules add build time to pasta’s Lean CI, and the proofs add upkeep, which milestone 2 covers for the first six months. Nothing changes in consensus, funds or shipped code.

### Success Metrics

- The pull request is merged, or a maintainer confirms that it is ready to merge.
- pasta’s Lean CI, nanoda included, passes on `main` with the GLV modules, using only the standard axioms.
- For six months after the merge, every GLV failure in pasta’s Lean CI is fixed within three working days, and every merged change to the mirrored Rust is reflected in the model within 14 days, with links in the monthly updates.

### Startup Funding (USD)

$0

### Startup Funding Justification

None needed.

### Milestone Details

```auto
- Milestone: 1
  Amount (USD): 5500
  Expected Completion Date: Eight weeks after the grant decision.
  User Stories:
    - "As a pasta_curves maintainer, I want a machine-checked proof of the GLV recoding and ladder in the crate's own lean/ package and CI, so that part 3 of pasta_curves#174 rests on a proof that pasta's CI rebuilds, not on sampled tests alone."
    - "As a reviewer of that proof, I want each theorem tied to the Rust functions it covers, with every assumption listed in one place, so that I can judge the statements without reading the proofs."
  Deliverables:
    - The pull request https://github.com/zcash/pasta_curves/pull/180 merged into pasta_curves' lean/ package, or confirmed ready to merge, with the changes requested in review incorporated: the Lean model, the statements and their proofs, using only Lean's standard axioms and passing pasta's Lean CI, nanoda included.
    - A theorem-to-source map in lean/README.md: for each theorem, the Rust functions it covers, whether it is proved about the model or about Aeneas' translation, and the hypotheses it takes; with a one-page summary in plain language.
  Acceptance Criteria: A pasta_curves maintainer (for example daira, str4d or nuttycom) merges the pull request, or confirms on it that the statements match part 3 of pasta_curves#174 and that it is ready to merge. $3,000 of this milestone pays for the proof as delivered with this application; nothing is paid without this acceptance. If no maintainer has reviewed by the expected date, we report that on the forum thread and ZCG decides whether to extend or close the milestone.

- Milestone: 2
  Amount (USD): 1500
  Expected Completion Date: Six months after the pull request is merged.
  User Stories:
    - "As a pasta_curves maintainer, I want the GLV proofs kept building and kept in step with src/glv.rs for six months after the merge, so that a toolchain update or an edit to the GLV code does not leave me a failing required check or a stale model."
  Deliverables:
    - A fix pull request within 3 working days of any GLV proof failure in pasta's Lean CI.
    - For every merged change to the Rust functions the model mirrors, an update to the model and proofs, or a note on that pull request that none is needed, within 14 days.
    - Monthly updates on the forum thread, and a closing report that links every event and its fix.
  Acceptance Criteria: The closing report is published, and a pasta_curves maintainer (for example daira, str4d or nuttycom), or a technical reviewer agreed with ZCG, confirms that no GLV proof failure stayed open beyond the times above. This milestone starts when the pull request is merged.

```

### Supporting Documents

- The request, [pasta\_curves#174](https://github.com/zcash/pasta_curves/issues/174) (daira; parts 1 to 3): [https://github.com/zcash/pasta\_curves/issues/174](https://github.com/zcash/pasta_curves/issues/174)
- The pull request: [https://github.com/zcash/pasta\_curves/pull/180](https://github.com/zcash/pasta_curves/pull/180)
- The proof package, with its tests: [https://github.com/nirapod-labs/pasta-glv-proofs](https://github.com/nirapod-labs/pasta-glv-proofs)
- The alternative recoding: [https://github.com/zcash/pasta\_curves/pull/125](https://github.com/zcash/pasta_curves/pull/125) (draft)
- Orchard’s use of pasta\_curves::glv, in its 0.15.4 changelog entry: [https://github.com/zcash/orchard/blob/main/CHANGELOG.md](https://github.com/zcash/orchard/blob/main/CHANGELOG.md)
- Part 2 of [pasta\_curves#174](https://github.com/zcash/pasta_curves/issues/174) (the decomposition), proposed by another team: [https://github.com/ZcashCommunityGrants/zcashcommunitygrants/issues/504](https://github.com/ZcashCommunityGrants/zcashcommunitygrants/issues/504). This work takes part 2 as a hypothesis and does not overlap with it.
- Part 1 of [pasta\_curves#174](https://github.com/zcash/pasta_curves/issues/174) (the endomorphism), a draft: [https://github.com/daira/CompElliptic/pull/5](https://github.com/daira/CompElliptic/pull/5)
