Skip to content

feat: add SpendAuthG short-scalar and base-field fixed-base support#8

Closed
czarcas7ic wants to merge 3 commits into
adam/pub-visibility-and-assign-constantfrom
adam/spend-auth-g-short-base
Closed

feat: add SpendAuthG short-scalar and base-field fixed-base support#8
czarcas7ic wants to merge 3 commits into
adam/pub-visibility-and-assign-constantfrom
adam/spend-auth-g-short-base

Conversation

@czarcas7ic
Copy link
Copy Markdown

@czarcas7ic czarcas7ic commented Apr 11, 2026

Summary

Generalizes OrchardFixedBases to support multiplying the spend authorization generator G^Orchard by (1) full-width base-field scalars via a new FixedPointBaseField path and (2) 64-bit signed short scalars via a new 22-window precomputed table. No other generator is added — only G^Orchard gains these new scalar-multiplication paths.

The main Orchard action circuit VK is unchanged. src/circuit_description/ and src/circuit_proof_test_case.bin are byte-identical to v0.11.0.

Motivation

voting-circuits needs [k]*G^Orchard in its El Gamal encryption gadget for two scalar kinds:

  • A base-field element (pallas::Base, 255-bit), where k is 16 per-share ephemeral randomness values. Without a FixedPointBaseField path for G^Orchard, this requires witnessing the generator as a NonIdentityPoint and using variable-base multiplication — an extra 254-row variable-base mul per share plus an equality constraint pinning the witnessed point to the fixed generator.
  • A range-checked small scalar (≤64 bits signed), where k is 16 per-share plaintext values bounded to 30 bits. Without a FixedPointShort path for G^Orchard, this requires the 85-window full-scalar path, wasting 63 windows per mul call. Orchard's ValueCommitV base already gets the short-scalar treatment via FixedPointShort; this PR extends the same capability to G^Orchard for the same reason.

This is a targeted optimization for the El Gamal gadget, not a general-purpose change. The only voting-circuits call site is voting-circuits/src/circuit/elgamal.rs (lines 175 and 183).

Concrete use case: voting-circuits El Gamal encryption

The vote_proof circuit (ZKP #2, Condition 11) encrypts 16 vote shares per voter under the election authority's public key:

C1_i = [r_i] * G                    (ephemeral public key, 16 shares)
C2_i = [v_i] * G + [r_i] * ea_pk    (masked plaintext, 16 shares)

where:

  • G = SpendAuthG (voting spec locks this; ZKP Convert BLAKE2s circuit to BLAKE2b #3 Share Reveal must use the same generator)
  • r_i = 255-bit base-field randomness (requires OrchardBaseFieldBases::SpendAuthGBase)
  • v_i = 30-bit range-checked share value (requires OrchardShortScalarBases::SpendAuthGShort)

Measured savings from using the short-scalar path for [v_i]*G: 315 circuit rows (3,827 → 3,512 high-water mark, K=13 budget 8,192 rows). Documentation at voting-circuits/src/vote_proof/circuit.rs:118-121.

What changed

New enums in src/constants/fixed_bases.rs

pub enum OrchardBaseFieldBases {
    NullifierK,
    SpendAuthGBase,  // new: G^Orchard with 85-window base-field tables
}

pub enum OrchardShortScalarBases {
    ValueCommitV,
    SpendAuthGShort, // new: G^Orchard with 22-window short-scalar tables
}

Both plugged into FixedPoints<OrchardFixedBases>:

impl FixedPoints<pallas::Affine> for OrchardFixedBases {
    type FullScalar = OrchardFixedBasesFull;
    type Base = OrchardBaseFieldBases;          // was: NullifierK
    type ShortScalar = OrchardShortScalarBases; // was: ValueCommitV
}

Each implements FixedPoint<pallas::Affine> dispatching through a match on the variant, in exactly the same shape as OrchardFixedBasesFull.

SpendAuthGBase reuses the existing spend_auth_g::{U, Z} full-scalar tables without change — pallas::Base and pallas::Scalar are both 255-bit and the 85-window tables depend only on the generator and window layout, not the scalar kind.

SpendAuthGShort uses new tables spend_auth_g::{Z_SHORT, U_SHORT} (22 windows), added as ~270 lines of hex constants reproducible via halo2_gadgets::ecc::chip::constants::find_zs_and_us.

Breaking change to OrchardFixedBases

This is a SemVer-breaking change to the enum shape:

  • Before: {Full(OrchardFixedBasesFull), NullifierK, ValueCommitV}
  • After: {Full(OrchardFixedBasesFull), Base(OrchardBaseFieldBases), Short(OrchardShortScalarBases)}

External callers pattern-matching on the old NullifierK/ValueCommitV variants (if any exist outside of orchard itself) will need to update to Base(OrchardBaseFieldBases::NullifierK) / Short(OrchardShortScalarBases::ValueCommitV).

The NullifierK and ValueCommitV unit structs are retained with unchanged FixedPoint<pallas::Affine> impls and unchanged From coercions into OrchardFixedBases, so code that uses them as marker types continues to compile. They are simply no longer the FixedPoints::Base / FixedPoints::ShortScalar associated types.

Internal rewiring

circuit::gadget::value_commit_orchard and circuit::gadget::derive_nullifier now use OrchardShortScalarBases::ValueCommitV and OrchardBaseFieldBases::NullifierK respectively, because those are the types FixedPointShort::from_inner and FixedPointBaseField::from_inner now expect. This is mechanical — the generator, U/Z tables, and window layout are all unchanged.

Verification

  • cargo test --all-features --workspace passes — 59 tests (up from 53, new routing tests added) + 1 integration test.
  • The Z_SHORT/U_SHORT tables are verified by the existing test_zs_and_us helper.
  • circuit::tests::round_trip (regenerates a fresh proof and verifies it against circuit_proof_test_case.bin) passes unchanged.
  • circuit::tests::serialized_proof_test_case passes unchanged.
  • cargo clippy --all-features -- -D warnings passes clean.

Binary-identical action circuit — the main Orchard consensus circuit is unaffected:

diff -q src/circuit_description/ /path/to/orchard-0.11.0-pristine/src/circuit_description/
diff -q src/circuit_proof_test_case.bin /path/to/orchard-0.11.0-pristine/src/circuit_proof_test_case.bin
# Both return no output (identical)

Alternatives considered

Sibling crate

Discussed but rejected. FixedPoints::Base and FixedPoints::ShortScalar are single types per FixedPoints impl. A sibling crate can't extend the set of Base/Short variants usable with orchard::constants::OrchardFixedBases; it would need to define its own parallel FixedPoints impl with a separate enum, forking the ECC chip instance. That in turn means re-implementing derive_nullifier, value_commit_orchard, CommitIvkChip, NoteCommitChip, etc. — all parameterized by FixedPoints = OrchardFixedBases. ~500 lines of duplication that defeats the purpose.

The new variants are named after the underlying math, not after voting-specific use cases. Any protocol needing a short scalar on G^Orchard benefits, not just voting.

Full 85-window path for [v_i]*G (fallback if this PR is rejected)

If this enum refactor is not acceptable upstream, voting-circuits can fall back to using the full 85-window path for [v_i]*G. This stays within the K=13 budget but reduces the headroom from ~57% to ~53% (3,512 → 3,827 high-water mark). The savings is "comfort margin" rather than strictly necessary. Voting-circuits would need no orchard changes for this fallback — just a two-line Cargo.toml switch to use the existing FixedPointBaseField/NullifierK routing plus variable-base mul for G.

Scope

Part of a 2-PR series targeting voting-circuits/v0.11.0:

Depends on PR A merging first (conflicts on src/circuit/gadget.rs). Draft — not for merge until both PRs are reviewed as a series.

Generalizes OrchardFixedBases to support multiplying the spend
authorization generator G^Orchard by (1) full-width base-field scalars
via a new FixedPointBaseField path and (2) 64-bit signed short scalars
via a new 22-window precomputed table.

Motivation: voting-circuits implements El Gamal encryption of 16 vote
shares per voter in its vote_proof circuit, computing:

  C1_i = [r_i] * G              (ephemeral public key, 255-bit base-field r_i)
  C2_i = [v_i] * G + [r_i]*ea_pk (masked plaintext, 30-bit range-checked v_i)

where G = SpendAuthG. The voting spec locks G to SpendAuthG because
ZKP#3 (Share Reveal) and the tally phase must use the same generator
as the encrypter.

Without these enum variants:
- [r_i]*G would need to witness G as a NonIdentityPoint and use
  variable-base mul (expensive)
- [v_i]*G would need to use the 85-window full-scalar path instead of
  the 22-window short-scalar path (~315 rows wasted × 16 shares)

New types:
- OrchardBaseFieldBases { NullifierK, SpendAuthGBase } — the new
  FixedPoints::Base associated type for OrchardFixedBases, allowing
  downstream circuits to multiply G^Orchard by a base-field scalar
  via FixedPointBaseField::mul.
- OrchardShortScalarBases { ValueCommitV, SpendAuthGShort } — the new
  FixedPoints::ShortScalar associated type, enabling multiplication of
  G^Orchard by a 64-bit signed short scalar via FixedPointShort::mul
  using 22-window precomputed tables (saves 63 window rows per
  multiplication vs. the 85-window full-scalar path for 64-bit scalars).
- New Z_SHORT / U_SHORT precomputed tables in spend_auth_g.rs, verified
  by the existing test_zs_and_us and test_lagrange_coeffs helpers.

Breaking change: OrchardFixedBases is now a 3-variant enum
{Full(OrchardFixedBasesFull), Base(OrchardBaseFieldBases),
Short(OrchardShortScalarBases)} instead of the previous flat form
{Full(...), NullifierK, ValueCommitV}. The NullifierK and ValueCommitV
unit structs are retained with unchanged FixedPoint impls for backward
compat but are no longer plugged into FixedPoints. Internal callers
circuit::gadget::{value_commit_orchard, derive_nullifier} are rewired
to the new variants.

Verification:
- All 59 orchard tests pass (up from 53, new routing tests added).
- circuit::tests::round_trip and circuit::tests::serialized_proof_test_case
  pass unchanged — the main Orchard action circuit VK is unchanged.
- src/circuit_description/ and src/circuit_proof_test_case.bin are
  byte-identical to v0.11.0.
- cargo clippy --all-features -- -D warnings passes clean.

Co-Authored-By: Claude <noreply@anthropic.com>
@czarcas7ic czarcas7ic changed the base branch from voting-circuits/v0.11.0 to adam/pub-visibility-and-assign-constant April 11, 2026 00:52
- cargo fmt: wrap long line in derive_nullifier, reformat U_SHORT table,
  drop blank lines at start of test functions in fixed_bases.rs.
- Fix intra-doc link error: `[v_i]*G` in OrchardShortScalarBases doc
  comment was parsed as an unresolved link; wrap in backticks.

Co-Authored-By: Claude <noreply@anthropic.com>
czarcas7ic added a commit to valargroup/voting-circuits that referenced this pull request Apr 11, 2026
The upstreaming checklist it contained is superseded by the draft PRs
against valargroup/orchard:
- valargroup/orchard#7 (visibility widenings + assign_constant)
- valargroup/orchard#8 (SpendAuthG short-scalar + base-field enums)

The PR diffs and descriptions are now the source of truth for what
needs to land upstream. Keeping UPSTREAM.md risks drift.
Keep NullifierK and ValueCommitV usable alongside the new enum-based fixed-base API so existing call sites can retain the old marker-style flow with explicit conversions. This narrows the public and internal churn from the SpendAuthG generalization without changing circuit behavior.
@czarcas7ic czarcas7ic closed this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant