Skip to content

fix(mamabear): derive the Poseidon2 round numbers for this field - #900

Merged
ivokub merged 1 commit into
masterfrom
mamabear/poseidon2
Oct 2, 2026
Merged

ivokub merged 1 commit into
masterfrom
mamabear/poseidon2

Conversation

@yelhousni

Copy link
Copy Markdown
Contributor

Description

The MamaBear Poseidon2 instance inherited its round numbers from KoalaBear: number of full rounds R_F = 6, and partial rounds R_P = 21. The diagonal and the S-box degree d carry over between those two fields safely; the round numbers do not.

Eq. (1) of the Poseidon2 paper bounds the partial rounds by

R_P  >= ceil(1.075 * max(R_interp, R_GB)) 
with R_GB the lower bound from the Gröbner-basis attack
and R_interp the lower bound from the interpolation attack:
  R_interp = ceil(min{κ, n}/log2(d)) + ceil(log_d(t)) - 5  
and:   
  n = ceil(log2(p)) 
  κ = target security level
  t = state width

The bound grows with min{κ, log2(p)}, so a wider field needs more rounds. KoalaBear saturates at n = 31 and needs R_P = 20; MamaBear at n = 49 needs R_P = 32, with R_interp = 29 binding. The inherited 21 satisfies Eq. (1) only up to roughly κ = 33, i.e. the parameters were sized for a security level far below the intended 128 bits.

This PR sets R_F = 8, R_P = 32 for both the width-16 compression and width-24 sponge parameters. R_F = 8 is the paper's value throughout, and restores the +2 margin over the statistical minimum of 6 that the inherited value had dropped.

The S-box degree is unchanged: d = 3 is the smallest d >= 3 with gcd(d, p-1) = 1, since p - 1 = 2^34 * 7 * 31 * 151.

Round constants are derived from the round counts by the Grain-style LFSR in initRC, so they regenerate with the new schedule. The known-answer vectors are recomputed accordingly.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

The permutation output changes, so any digest previously produced by MamaBear Poseidon2 will not reproduce. MamaBear was merged recently (#887) and has no released consumers that we are aware of, so the blast radius should be zero.

How has this been tested?

  • go test ./field/mamabear/... — all seven packages pass.
  • go test ./field/... ./hash/... — pass, confirming no other field is affected.
  • TestDefaultRoundNumbers (new) pins R_F = 8, R_P = 32, width, d = 3 and the round-key count to the derivation, with the reasoning in the comment, so the numbers cannot be changed silently again.
  • TestPoseidon2Width16 / TestPoseidon2Width24 known-answer vectors recomputed for the new schedule, and the test now takes its round counts from the generated parameters rather than hardcoding them.
  • TestMulMulInternalInPlaceWidth16 / Width24 unchanged and passing — the diagonal is untouched.
  • go generate ./... leaves the tree clean, and BabyBear, KoalaBear and Goldilocks regenerate byte for byte unchanged.

How has this been benchmarked?

  • BenchmarkPoseidon2Width16 / Width24, benchstat over 10 runs, on MacBook Pro M5, darwin/arm64
                     old (6/21)   new (8/32)
Poseidon2Width16      1.040µ   ->  1.492µ    +43.41%  (p=0.000, n=10)
Poseidon2Width24      1.565µ   ->  2.247µ    +43.58%  (p=0.000, n=10)
geomean               1.276µ   ->  1.831µ    +43.50%

The permutation gets ~43% slower. S-box count rises 117 → 160 (+36.8%) at width 16 and 165 → 224 (+35.8%) at width 24; the remainder is the two extra full rounds' external layers. This is the cost of the parameters being correct rather than inherited, so the regression is the point of the PR, not a side effect of it.

Not benchmarked on x86 with AVX512-IFMA.

Checklist:

  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works
  • I did not modify files generated from templates
  • golangci-lint does not output errors locally
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

The mamabear Poseidon2 parameters were inherited from koalabear: 6 full
rounds and 21 partial rounds. Only the diagonal and the S-box degree carry
over safely. The round numbers do not.

Eq. (1) of the Poseidon2 paper (https://eprint.iacr.org/2023/323.pdf)
bounds the partial rounds by

    R_P >= ceil(1.075 * max(R_interp, R_GB))
    R_interp = ceil(min{kappa, n}/log2(d)) + ceil(log_d(t)) - 5

with n = ceil(log2(p)). The bound grows with min{kappa, log2(p)}, so a
wider field needs MORE rounds, not fewer. koalabear saturates at n = 31 and
needs 20; mamabear at n = 49 needs 32, with R_interp = 29 binding. The
inherited 21 satisfies Eq. (1) only up to roughly kappa = 33.

Set R_F = 8 and R_P = 32 for both the width-16 compression and width-24
sponge parameters. R_F = 8 is the paper's value throughout, and is also the
+2 margin over the statistical minimum of 6 that the inherited value had
dropped.

The S-box degree is unchanged and was already right: d = 3 is the smallest
d >= 3 with gcd(d, p-1) = 1, since p-1 = 2^34 * 7 * 31 * 151.

The round constants are derived from the round counts by the Grain-style
LFSR in initRC, so they regenerate with the new schedule; the known-answer
vectors are recomputed accordingly. Those vectors come from this
implementation, as the existing ones did, so they guard against regression
rather than validating against an independent source. The KAT now builds
its permutation from the generated parameters instead of hardcoding the
counts, so it cannot drift from the shipped configuration again.

The derivation was validated before being applied: the same formula
reproduces all six instances of Table 1 of the paper, and all six of
Plonky3's shipped constants for babybear (13/21/30 at t = 16/24/32) and
koalabear (20/23/31).

Only mamabear changes here. Regenerating leaves babybear, koalabear and
goldilocks byte for byte unchanged. Note that koalabear ships R_F = 6
against Plonky3's 8, and goldilocks 6/17 against a derived 8/22; both are
left alone and are worth a separate look.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@yelhousni
yelhousni requested a review from ivokub October 1, 2026 16:23
@yelhousni yelhousni self-assigned this Oct 1, 2026
@yelhousni
yelhousni requested a review from a team as a code owner October 1, 2026 16:23

@ivokub ivokub 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.

LGTM!

@ivokub
ivokub merged commit 4c054e7 into master Oct 2, 2026
14 checks passed
@ivokub
ivokub deleted the mamabear/poseidon2 branch October 2, 2026 10:33
@ivokub ivokub mentioned this pull request Oct 2, 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.

2 participants