Skip to content

Poseidon2 KoalaBear (width 16, alpha=3) uses only 13 partial rounds instead of required 20 — undermines Fiat-Shamir & Merkle commitments #524

Description

@mhmdrizzzki

Poseidon2 (KoalaBear, width 16, alpha=3) uses only 13 partial rounds instead of the required 20 — undermines Fiat-Shamir & Merkle commitments

Summary

Ziren's Poseidon2 KoalaBear permutation is configured with S-box degree alpha=3 but only 13 partial (internal) rounds. The Plonky3 reference (which ProjectZKM themselves fork) requires 20 partial rounds for (width=16, alpha=3) to reach the intended 128-bit security level. 13 is the correct value for alpha=7, not alpha=3. This weakens the algebraic security of every component that uses this permutation: the Fiat-Shamir challenger, the Merkle-tree commitment scheme (MMCS), and public-value hashing — the foundation of STARK/Groth16 proof soundness used by GOAT BitVM3.

Evidence

1. S-box degree is alpha=3 (cube) in all implementations

  • crates/recursion/core/src/chips/poseidon2_skinny/air.rs (~L109): sbox_deg_3[i] = add_rc[i]*add_rc[i]*add_rc[i]
  • crates/recursion/gnark-ffi/go/zkm/poseidon2/poseidon2_koalabear.go sboxP: i3 = i2*input (cube)
  • crates/stark/src/kb31_poseidon2.rs: Perm = Poseidon2KoalaBear<16>
  • ProjectZKM Plonky3 fork koala-bear/src/poseidon2.rs: KOALABEAR_S_BOX_DEGREE = 3

2. But partial rounds are hardcoded to 13 everywhere

  • crates/primitives/src/lib.rs (~L1109): const ROUNDS_P: usize = 13;
  • crates/core/machine/src/operations/poseidon2/mod.rs (L18): NUM_INTERNAL_ROUNDS = 13
  • crates/recursion/core/src/chips/poseidon2_skinny/mod.rs (L16): NUM_INTERNAL_ROUNDS = 13
  • crates/recursion/core/src/chips/poseidon2_wide/mod.rs (L22): NUM_INTERNAL_ROUNDS = 13
  • crates/recursion/gnark-ffi/go/zkm/poseidon2/poseidon2_koalabear.go (L12): koalabearNumInternalRounds = 13

3. Required round count for 128-bit security (ProjectZKM Plonky3 fork, poseidon2/src/round_numbers.rs)

(16, 3) => (8, 20),   // width=16, alpha=3  -> 20 partial rounds
(16, 7) => (8, 13),   // width=16, alpha=7  -> 13 partial rounds

Ziren ships the alpha=3 permutation with the alpha=7 round count.

4. The permutation is otherwise standard — the internal diagonal matrix matches the Plonky3 fork exactly ([-2,1,2,1/2,3,4,-1/2,-3,-4,-1/2^8,-1/2^3,-127,1/2^8,1/2^3,1/2^4,127]), so there is no custom construction that would justify fewer rounds.

5. A security fix was authored, then reverted without explanation

  • 8982648 (Jun 2026): fix(poseidon2): raise KoalaBear/alpha=3 partial rounds to 20
  • 43185de: fix(go-runtime): koalabearNumInternalRounds = 13->20
  • e0f4236: updated the failing gnark test vectors
  • 0eeddf6 (Jul 2026): Revert of 8982648
  • 12605c8: Revert of 43185de
  • cb07d19: Revert of e0f4236
    All releases (v1.2.5, v1.2.6, v1.2.7 current) ship R_P=13.

Security impact

Poseidon2 with R_P=13 is used by:

  • DuplexChallenger<Val, Perm, 16, 8> — Fiat-Shamir transform (proof soundness)
  • MerkleTreeMmcs<...> — Merkle commitments (STARK oracle binding)
  • PaddingFreeSponge<Perm, 16, 8, 8> / TruncatedPermutation<Perm, 2, 8, 16> — hashing public values

Under-provisioning partial rounds reduces the algebraic security below the intended 128-bit level, which can make interpolation/Gröbner-basis attacks on the Fiat-Shamir transform or commitments feasible. Recent research (GSR, "From Round Skipping to S-Box Skipping; Attacking Poseidon's Partial Layer via Subspace Restriction", IACR 2026/1692) demonstrates practical partial-round attacks on Poseidon with KoalaBear alpha=3 (CICO-1 solved at 28/31 rounds). For a proof system like Ziren, this can undermine soundness — for the GOAT BitVM3 bridge this could allow forging a proof accepted by the verifier (unauthorized withdrawal / incorrect state transition).

Suggested fix

Restore R_P=20 for the alpha=3 KoalaBear width-16 Poseidon2 in all components (Rust primitives, machine, skinny/wide chips, Go gnark runtime) and regenerate the expected test vectors. Do not revert.

Affected versions

v1.2.5, v1.2.6, v1.2.7 (current, 2026-08-14) and all tags in between.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions