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.
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.13is 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.gosboxP:i3 = i2*input(cube)crates/stark/src/kb31_poseidon2.rs:Perm = Poseidon2KoalaBear<16>koala-bear/src/poseidon2.rs:KOALABEAR_S_BOX_DEGREE = 32. 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 = 13crates/recursion/core/src/chips/poseidon2_skinny/mod.rs(L16):NUM_INTERNAL_ROUNDS = 13crates/recursion/core/src/chips/poseidon2_wide/mod.rs(L22):NUM_INTERNAL_ROUNDS = 13crates/recursion/gnark-ffi/go/zkm/poseidon2/poseidon2_koalabear.go(L12):koalabearNumInternalRounds = 133. Required round count for 128-bit security (ProjectZKM Plonky3 fork,
poseidon2/src/round_numbers.rs)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 2043185de:fix(go-runtime): koalabearNumInternalRounds = 13->20e0f4236: updated the failing gnark test vectors0eeddf6(Jul 2026): Revert of898264812605c8: Revert of43185decb07d19: Revert ofe0f4236All 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 valuesUnder-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.