Skip to content

Use abstract rings for Zip and linear code - #2

Merged
frozenspider merged 20 commits into
mainfrom
feature/zip-rings
Sep 29, 2025
Merged

Use abstract rings for Zip and linear code#2
frozenspider merged 20 commits into
mainfrom
feature/zip-rings

Conversation

@frozenspider

@frozenspider frozenspider commented Sep 19, 2025

Copy link
Copy Markdown
Collaborator

Instead of N, L, K, M usize constant bounds for Ints, both Zip and linear code now operates on 4 abstract rings:

  • N => EvaluationRing
  • L (previously: element width of the encoding matrix of a linear code) is no longer a generic, instead it's made into a property of linear code. PrimeField elements must be obtainable from it, but otherwise code is free to place any constraints on the rings that it needs to make it work.
  • K => CodewordRing
  • Added new ring ChallengeRing, used to generate challenges for random linear combination of codewords. Previously it was the same as evaluation ring.
  • M => LinearCombinationRing

These ring aliases are implemented by Int<_> rings, so not much changes from a user perspective, except you have to specify ChallengeRing separately.

Other major changes

  • Added a new trait MulByScalar to represent an ability to multiply a given ring element by ChallengeRing (and field elements with each other, as they are being combined too)
  • LinearCode no longer has generic encode_wide, instead 3 encode methods are now defined as follows:
    • encode: EvaluationRing => CodewordRing
    • encode_wide: LinearCombinationRing => LinearCombinationRing
    • encode_f: PrimeField => PrimeField
  • Tweaked RaaCode to have a configurable check_for_overflows. This is needed to keep encoding times on par with original Zinc encoding times - using checked_add adds ~20% overhead.
  • All to- and from-bytes encoding is now done through a new trait Transcribable. It can use whatever byte order as long as it allows serialization and deserialization cross-platform. (Actually uses little-endian order as being more efficient and more natural when applied to underlying crypto-bigint types)
    • This allows to make transcription reads/writes much more generalized, removing a bunch of type-specific methods
    • This also made AsWords trait unnecessary, removed it
    • Cross-platform deserialization wasn't actually tested yet, left a TODO

Additional changes

  • ConstRing is now auto-implemented
  • dense multilinear polynomial module moved from poly to poly::mle to free up module name
  • num_proximity_testing no longer depends on log2_q. It was unused previously anyway, and if we need it in future, we'd need to determine what log2_q is for an arbitrary ring.

Performance

(single-threaded)

  • Encoding and MT construction is on par with original Zinc.
  • Commitment overall is ~35% faster.
  • Open and verify is on par with Refactor Field and RandomField: zinc#142 (which is much faster than original Zinc)

@frozenspider frozenspider self-assigned this Sep 19, 2025
@github-actions

Copy link
Copy Markdown

Coverage after merging feature/zip-rings into main will be

91.92%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
crypto-primitives/src
   matrix.rs44.07%100%46.15%43.48%36–38, 42–44, 46–48, 50–53, 60, 62–69, 71
crypto-primitives/src/ring
   crypto_bigint_int.rs96.06%100%92.59%96.59%256–258, 385–387, 73–75, 79–81
zip-plus/src
   code.rs100%100%100%100%
   field.rs63.64%100%60%64.71%28–30, 34–36
   pcs_transcript.rs86.96%100%77.14%88.95%180–184, 190–194
   poly.rs0%100%0%0%18–20
   transcript.rs61.76%100%69.23%60.67%108–109, 111–113, 115–117, 122–123, 125–127, 136–147, 149–150, 79–82, 84–87
   utils.rs100%100%100%100%
zip-plus/src/code
   raa.rs96.21%100%100%95.79%223, 273, 277, 281, 285, 325
zip-plus/src/field
   arithmetic.rs85.54%100%79.59%86.59%105–110, 116–118, 124–126, 132–137, 143–148, 192–194, 241–243, 45–47, 53–55
   comparison.rs100%100%100%100%
   conversion.rs94.32%100%93.75%94.44%79–81, 91
   numeric.rs100%100%100%100%
   utils.rs100%100%100%100%
zip-plus/src/pcs
   commit.rs96.46%100%97.50%96.35%117–121, 123–124, 126, 128–129, 379, 399, 401
   open_z.rs94.84%100%93.33%94.96%121, 50–52, 529, 53–56, 566, 57–59, 61–62, 64–65, 95
   structs.rs79.07%100%75%80%118–120, 154, 86–88
   tests.rs17.86%100%33.33%16%20–23, 30–44, 46–47
   utils.rs93.88%100%92.59%94.01%245–249, 301, 307, 414, 61–64, 83–86
   verify_z.rs93.16%100%96%92.99%131, 171, 192, 195, 275, 387, 400–401, 483, 497–498, 52–58, 586, 59–64, 66–67, 69–70, 91
zip-plus/src/poly/mle
   dense.rs98.39%100%98.39%98.39%123, 207–209, 39–40

@github-actions

Copy link
Copy Markdown

Coverage after merging feature/zip-rings into main will be

93.38%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
crypto-primitives/src
   matrix.rs44.07%100%46.15%43.48%36–38, 42–44, 46–48, 50–53, 60, 62–69, 71
crypto-primitives/src/ring
   crypto_bigint_int.rs96.06%100%92.59%96.59%256–258, 385–387, 73–75, 79–81
zip-plus/src
   code.rs100%100%100%100%
   field.rs63.64%100%60%64.71%28–30, 34–36
   pcs_transcript.rs87.74%100%83.33%88.55%146–150, 156–160
   poly.rs0%100%0%0%18–20
   traits.rs100%100%100%100%
   transcript.rs85.25%100%85.71%85.19%78–81, 83–86
   utils.rs100%100%100%100%
zip-plus/src/code
   raa.rs96.21%100%100%95.79%223, 273, 277, 281, 285, 325
zip-plus/src/field
   arithmetic.rs85.54%100%79.59%86.59%105–110, 116–118, 124–126, 132–137, 143–148, 192–194, 241–243, 45–47, 53–55
   comparison.rs100%100%100%100%
   conversion.rs94.32%100%93.75%94.44%79–81, 91
   numeric.rs100%100%100%100%
   utils.rs100%100%100%100%
zip-plus/src/pcs
   commit.rs96.46%100%97.50%96.35%117–121, 123–124, 126, 128–129, 379, 399, 401
   open_z.rs94.84%100%93.33%94.96%121, 50–52, 529, 53–56, 566, 57–59, 61–62, 64–65, 95
   structs.rs79.07%100%75%80%118–120, 183, 86–88
   tests.rs100%100%100%100%
   utils.rs94.08%100%93.10%94.18%259–263, 315, 321, 428, 61–64, 83–86
   verify_z.rs93.16%100%96%92.99%131, 171, 192, 195, 275, 387, 400–401, 483, 497–498, 52–58, 586, 59–64, 66–67, 69–70, 91
zip-plus/src/poly/mle
   dense.rs98.39%100%98.39%98.39%123, 207–209, 39–40

@github-actions

Copy link
Copy Markdown

Coverage after merging feature/zip-rings into main will be

93.58%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
crypto-primitives/src
   matrix.rs44.07%100%46.15%43.48%36–38, 42–44, 46–48, 50–53, 60, 62–69, 71
crypto-primitives/src/ring
   crypto_bigint_int.rs97.06%100%94.55%97.45%403–405, 73–75, 79–81
zip-plus/src
   code.rs100%100%100%100%
   field.rs63.64%100%60%64.71%28–30, 34–36
   pcs_transcript.rs87.74%100%83.33%88.55%146–150, 156–160
   poly.rs0%100%0%0%18–20
   traits.rs100%100%100%100%
   transcript.rs85.25%100%85.71%85.19%78–81, 83–86
   utils.rs100%100%100%100%
zip-plus/src/code
   raa.rs96.92%100%100%96.58%274, 328, 332, 336, 340, 375
zip-plus/src/field
   arithmetic.rs85.54%100%79.59%86.59%105–110, 116–118, 124–126, 132–137, 143–148, 192–194, 241–243, 45–47, 53–55
   comparison.rs100%100%100%100%
   conversion.rs94.32%100%93.75%94.44%79–81, 91
   numeric.rs100%100%100%100%
   utils.rs100%100%100%100%
zip-plus/src/pcs
   commit.rs96.49%100%97.50%96.38%117–121, 123–124, 126, 128–129, 379, 399, 401
   open_z.rs94.84%100%93.33%94.96%121, 50–52, 529, 53–56, 566, 57–59, 61–62, 64–65, 95
   structs.rs79.07%100%75%80%118–120, 183, 86–88
   tests.rs100%100%100%100%
   utils.rs94.08%100%93.10%94.18%259–263, 315, 321, 428, 61–64, 83–86
   verify_z.rs93.25%100%96%93.09%131, 171, 192, 195, 275, 387, 400–401, 488, 502–503, 52–59, 591, 60–64, 66–67, 69–70, 91
zip-plus/src/poly/mle
   dense.rs98.39%100%98.39%98.39%123, 207–209, 39–40

@frozenspider
frozenspider marked this pull request as ready for review September 20, 2025 13:57
@frozenspider frozenspider changed the title [WIP] Use abstract rings for Zip and linear code Use abstract rings for Zip and linear code Sep 20, 2025
@github-actions

Copy link
Copy Markdown

Coverage after merging feature/zip-rings into main will be

93.58%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
crypto-primitives/src
   matrix.rs44.07%100%46.15%43.48%36–38, 42–44, 46–48, 50–53, 60, 62–69, 71
crypto-primitives/src/ring
   crypto_bigint_int.rs97.06%100%94.55%97.45%403–405, 73–75, 79–81
zip-plus/src
   code.rs100%100%100%100%
   field.rs63.64%100%60%64.71%28–30, 34–36
   pcs_transcript.rs87.74%100%83.33%88.55%146–150, 156–160
   poly.rs0%100%0%0%18–20
   traits.rs100%100%100%100%
   transcript.rs85.25%100%85.71%85.19%78–81, 83–86
   utils.rs100%100%100%100%
zip-plus/src/code
   raa.rs96.92%100%100%96.58%274, 328, 332, 336, 340, 375
zip-plus/src/field
   arithmetic.rs85.54%100%79.59%86.59%105–110, 116–118, 124–126, 132–137, 143–148, 192–194, 241–243, 45–47, 53–55
   comparison.rs100%100%100%100%
   conversion.rs94.32%100%93.75%94.44%79–81, 91
   numeric.rs100%100%100%100%
   utils.rs100%100%100%100%
zip-plus/src/pcs
   commit.rs96.49%100%97.50%96.38%117–121, 123–124, 126, 128–129, 379, 399, 401
   open_z.rs94.84%100%93.33%94.96%121, 50–52, 529, 53–56, 566, 57–59, 61–62, 64–65, 95
   structs.rs79.07%100%75%80%118–120, 183, 86–88
   tests.rs100%100%100%100%
   utils.rs94.08%100%93.10%94.18%259–263, 315, 321, 428, 61–64, 83–86
   verify_z.rs93.25%100%96%93.09%131, 171, 192, 195, 275, 387, 400–401, 488, 502–503, 52–59, 591, 60–64, 66–67, 69–70, 91
zip-plus/src/poly/mle
   dense.rs98.39%100%98.39%98.39%123, 207–209, 39–40

@cupicmarko cupicmarko left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't finish the review yet, but I'll mark the PR approved once I finish, I don't see any changes to request or any questions. If performance are the same or better, this is a nicer, cleaner code.

I would reconsider #[inline(always)] flags, because they can increase the resulting code size, which is usually fine, but it can affect performance on CPUs with small instruction cache (Cortex-M can have 8-16KB for example), so I would suggest using #[inline] and letting the compiler decide on inlining. I don't know how real this scenario is, just mentioning it.

Comment thread zip-plus/src/code/raa.rs
}

#[allow(clippy::arithmetic_side_effects)]
fn accumulate_unchecked<I>(input: &mut [I])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the name, I would suggest marking the function unsafe and removing unsafe block inside.

@frozenspider frozenspider Sep 22, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is not unsafe in the usual Rust sense (no undefined behaviour). I agree that it makes sense to mark function unsafe for internal reasons too, but in this case this intention is hard to propagate. Making it unsafe will just make encode_inner to use unsafe call wrapper twice - however, both it and accumulate_unchecked are internal functions not visible to the outside world, so it doesn't feel like we're accomplishing much.
On the other hand, unsafe block inside this function is unsafe in memory manipulation sense, so it makes sense to keep unsafe {} block wrapper there.
Overall, I think in this case I'd prefer to keep it the way it is now.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, can we add debug_asserts that check for the UB?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asked a follow-up question on Slack, let's do it in the next PR

Comment thread zip-plus/src/traits.rs Outdated
@github-actions

Copy link
Copy Markdown

Coverage after merging feature/zip-rings into main will be

93.58%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
crypto-primitives/src
   matrix.rs44.07%100%46.15%43.48%36–38, 42–44, 46–48, 50–53, 60, 62–69, 71
crypto-primitives/src/ring
   crypto_bigint_int.rs97.06%100%94.55%97.45%403–405, 73–75, 79–81
zip-plus/src
   code.rs100%100%100%100%
   field.rs63.64%100%60%64.71%28–30, 34–36
   pcs_transcript.rs87.74%100%83.33%88.55%146–150, 156–160
   poly.rs0%100%0%0%18–20
   traits.rs100%100%100%100%
   transcript.rs85.25%100%85.71%85.19%78–81, 83–86
   utils.rs100%100%100%100%
zip-plus/src/code
   raa.rs96.92%100%100%96.58%274, 328, 332, 336, 340, 375
zip-plus/src/field
   arithmetic.rs85.54%100%79.59%86.59%105–110, 116–118, 124–126, 132–137, 143–148, 192–194, 241–243, 45–47, 53–55
   comparison.rs100%100%100%100%
   conversion.rs94.32%100%93.75%94.44%79–81, 91
   numeric.rs100%100%100%100%
   utils.rs100%100%100%100%
zip-plus/src/pcs
   commit.rs96.49%100%97.50%96.38%117–121, 123–124, 126, 128–129, 379, 399, 401
   open_z.rs94.84%100%93.33%94.96%121, 50–52, 529, 53–56, 566, 57–59, 61–62, 64–65, 95
   structs.rs79.07%100%75%80%118–120, 183, 86–88
   tests.rs100%100%100%100%
   utils.rs94.08%100%93.10%94.18%259–263, 315, 321, 428, 61–64, 83–86
   verify_z.rs93.25%100%96%93.09%131, 171, 192, 195, 275, 387, 400–401, 488, 502–503, 52–59, 591, 60–64, 66–67, 69–70, 91
zip-plus/src/poly/mle
   dense.rs98.39%100%98.39%98.39%123, 207–209, 39–40

@frozenspider

Copy link
Copy Markdown
Collaborator Author

Thanks @cupicmarko!
As for #[inline(always)], I wanted to use it coupled with #[repr(transparent)] to give compiler more a strong hint to remove the wrapper altogether - calls should just be forwarded to the underlying repr. This might not be needed though.

@maksimryndin maksimryndin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work, @frozenspider ! 👍

Probably, in future optimizations it is worth also to try an alternative to crypto-bigint

https://github.com/recmo/uint by Remco et al

Comment thread zip-plus/src/code/raa.rs Outdated
Comment thread zip-plus/src/code/raa.rs Outdated
Comment thread zip-plus/src/pcs/structs.rs
Comment thread zip-plus/src/pcs/tests.rs Outdated
@cupicmarko

Copy link
Copy Markdown

Thanks @cupicmarko! As for #[inline(always)], I wanted to use it coupled with #[repr(transparent)] to give compiler more a strong hint to remove the wrapper altogether - calls should just be forwarded to the underlying repr. This might not be needed though.

That seems good, although, I would assume that the compiler is already doing it on its own. It's not something that is needed to be changed, just NIT.

@cupicmarko cupicmarko left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Just some NIT comments, nothing else

@frozenspider
frozenspider merged commit 35911fe into main Sep 29, 2025
5 checks passed
@frozenspider
frozenspider deleted the feature/zip-rings branch September 29, 2025 09:15
osdnk added a commit that referenced this pull request Jan 23, 2026
albert-garreta added a commit that referenced this pull request Mar 4, 2026
…aryPoly)

Optimization #2 (eq-weighted higher-degree path):
- New evaluate_combined_polynomials_at_point: shift-spec support, last-row
  Lagrange correction, replaces evaluate_combined_polynomials_linear
- New compute_combined_polynomial_evals_with_eq: eq-weight accumulation for
  higher-degree constraints, avoids O(K×D×N) intermediate MLE storage
- prove_at_point dispatches: degree≤1 → at_point, degree>1 → eq-weighted
- scale_dynamic_poly_f helper, backward-compat linear alias

Optimization #3 (fused BinaryPoly → constraint evaluation):
- New prove_from_binary_poly / prove_from_binary_poly_at_point entry points
  operating directly on BinaryPoly<D> traces for any constraint degree
- New compute_combined_evals_from_binary_poly_with_eq: fuses projection +
  constraint evaluation + eq-weighted accumulation into a single pass,
  eliminating the full projected trace allocation (num_cols × N × D)
albert-garreta added a commit that referenced this pull request Apr 25, 2026
Combines optimizations #2 and #3 from the perf analysis. Replaces the
hashmap-based multiplicity index AND drops the intermediate
`chunks_psi: Vec<Vec<Vec<F>>>` materialization in `prove_group`.

Before:
  Step 1: chunks_psi[ell][k][i] = ψ_a-projection of K·W bit-polys, each
          evaluated as `Σ_p bit_p · a^p` — L·K·W·cw field adds.
  Step 2: build_table_index → HashMap<Vec<u8>, usize>.
  Step 3: per (ell, chunk) call compute_multiplicities_with_index →
          W hash lookups per chunk × L·K calls = L·K·W hashmap lookups
          (~2M at L=8, K=4, W=2^16).
  Step 5: leaf_q[…] = β − chunks_psi[ell][k][i]; reads through
          the L·K·W F intermediates a second time.

After:
  Step 1: chunks_idx[ell][k][i]: u32 — extract cw bits from the parent
          BitPoly via OR (no field arithmetic).
  Step 2: subtable only; no hashmap.
  Step 3: agg_mults[ell][n] = histogram of chunks_idx[ell] flattened
          into Vec<u64>, then converted to F. Pure integer work.
  Step 5: precompute β_minus_subtable: Vec<F> of length 2^cw, then
          leaf_q[…] = β_minus_subtable[chunks_idx[ell][k][i]] — one
          F clone per leaf, no per-leaf field op.

Net effect:
  - L·K·W hashmap lookups → L·K·W array indexes (~50ns → ~1ns each)
  - L·K·W field adds (chunks_psi build) → L·K·W·cw bit ORs
  - L·K·W F clones for chunks_psi → none (chunks_psi gone)
  - β_minus_subtable amortizes the β subtraction over 2^cw entries
    instead of L·K·W leaves

Pure-internal optimization: no FS transcript or proof byte changes.
17 protocol + 60 piop tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

3 participants