Skip to content

feat: add Poseidon transcript#1173

Closed
akoidefi wants to merge 8 commits intoa16z:mainfrom
defi-wonderland:feat/poseidon-hash
Closed

feat: add Poseidon transcript#1173
akoidefi wants to merge 8 commits intoa16z:mainfrom
defi-wonderland:feat/poseidon-hash

Conversation

@akoidefi
Copy link
Copy Markdown
Contributor

Adds PoseidonTranscript using light_poseidon over BN254.

Uses width-3 Poseidon to include n_rounds in every hash call for domain separation, same as Blake2b/Keccak. Chunks large inputs into 32-byte pieces since Poseidon has fixed-width inputs.

Gated behind transcript-poseidon feature flag.

@akoidefi akoidefi marked this pull request as draft December 18, 2025 14:21
@akoidefi akoidefi marked this pull request as ready for review December 18, 2025 14:24
@markosg04 markosg04 self-requested a review December 18, 2025 15:07
Copy link
Copy Markdown
Collaborator

@markosg04 markosg04 left a comment

Choose a reason for hiding this comment

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

Excellent job! Just have one question about the usage of Fr.

Comment thread jolt-core/src/zkvm/mod.rs
Comment thread jolt-core/src/transcripts/poseidon.rs Outdated
Comment on lines +47 to +49
fn hasher() -> Poseidon<Fr> {
Poseidon::<Fr>::new_circom(POSEIDON_WIDTH).expect("Failed to initialize Poseidon")
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

light-poseidon seems to rely on trait PrimeField from arkworks.

I think it is fine to couple to arkworks for now, especially if this is the best poseidon lib. Ideal world is that this would be generic over F: JoltField, which I don't think we can achieve. Maybe if it was over generic like F: PrimeField then it's easier to swap to Fq or BLS fields later.

Do you foresee any problems with fixing Fr in a snark composition context? In snark composition, sum-checks are over Fq (BN254 base field), and in this implementation we interpret all bytes as arkworks Fr. My hunch is that it's fine, but I wonder if there could be some weird attacks since Fq modulus is larger than Fr (a tiny number of collisions). Probably this fact is not really exploitable, but maybe worth a bit of thought.

if we make generic over F: PrimeField we could probably avoid this by just using Fq when the sum-check is working with Fq.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a good catch. I'll make it generic on F: PrimeField.

@akoidefi akoidefi force-pushed the feat/poseidon-hash branch 2 times, most recently from b46cfbb to 34e0e30 Compare January 8, 2026 00:53
@0xParti 0xParti force-pushed the feat/poseidon-hash branch from 854b65f to 6338e06 Compare February 2, 2026 14:27
Comment thread jolt-core/src/transcripts/poseidon.rs Outdated
Comment on lines +195 to +201
self.n_rounds as usize <= expected_state_history.len(),
"Fiat-Shamir transcript mismatch: n_rounds {} exceeds expected history length {}",
self.n_rounds,
expected_state_history.len()
);
assert!(
new_state == expected_state_history[self.n_rounds as usize],
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.

Off-by-one error causing array index out of bounds panic in test mode. After incrementing n_rounds at line 190, the code checks self.n_rounds as usize <= expected_state_history.len() at line 195, then accesses expected_state_history[self.n_rounds as usize] at line 201. When n_rounds equals len(), this accesses an invalid index (arrays are 0-indexed, so valid indices are 0 to len-1).

// Fix: Change <= to <
assert!(
    self.n_rounds as usize < expected_state_history.len(),
    "Fiat-Shamir transcript mismatch: n_rounds {} exceeds expected history length {}",
    self.n_rounds,
    expected_state_history.len()
);
assert!(
    new_state == expected_state_history[self.n_rounds as usize - 1],
    "Fiat-Shamir transcript mismatch at round {}",
    self.n_rounds
);

Or alternatively access at [self.n_rounds as usize - 1] since n_rounds was just incremented.

Spotted by Graphite Agent

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

akoidefi and others added 6 commits February 3, 2026 12:59
Adds PoseidonTranscript using light_poseidon over BN254.

Uses width-3 Poseidon to include n_rounds in every hash call for domain
separation, same as Blake2b/Keccak. Chunks large inputs into 32-byte
pieces since Poseidon has fixed-width inputs.

Gated behind transcript-poseidon feature flag.
Co-authored-by: Markos <53157953+markosg04@users.noreply.github.com>
- Add PoseidonParams<F> trait for type-level parameter configuration
- Implement FrParams (uses new_circom) and FqParams (generated params)
- Add poseidon_fq_params.rs with BN254 Fq parameters (128-bit security)
- Create type aliases PoseidonTranscriptFr and PoseidonTranscriptFq
- Fq params generated with poseidon-paramgen v0.4.0 (audited by NCC Group)

This enables SNARK composition where sumchecks operate over Fq.
Integrates the poseidon-paramgen library (arkworks 0.5 compatible fork) to enable
verification that hardcoded BN254 Fq Poseidon parameters match the audited output
from poseidon-paramgen v0.4.0 (NCC Group, Summer 2022).

This adds transparency and verifiability to the Fq parameters needed for SNARK
recursion, where the verifier operates in the base field rather than the scalar field.

Changes:
- Add workspace deps for defi-wonderland/poseidon377 fork (arkworks-0.5 branch)
- Add poseidon-paramgen and poseidon-parameters as optional deps in jolt-core
- Create poseidon_param_gen.rs with generate_fq_params() and verification tests
- Update poseidon_fq_params.rs header with verification instructions

Tests verify:
- Round counts (8 full, 56 partial)
- Alpha/S-box exponent (5)
- MDS matrix (4x4, 16 elements)
- Round constants (256 values)
- Integration with light-poseidon hasher
- Transcript determinism
- Update poseidon.rs to implement raw_append_* methods with labels
- Remove unused AppendToTranscript export from mod.rs
Comment thread jolt-core/src/transcripts/poseidon.rs Outdated
}
}

/// Type alias for the original Fr-based transcript (backwards compatible)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Type alias for the original Fr-based transcript (backwards compatible)

Comment thread jolt-core/src/transcripts/poseidon.rs Outdated
/// Type alias for the original Fr-based transcript (backwards compatible)
pub type PoseidonTranscriptFr = PoseidonTranscript<Fr, FrParams>;

/// Type alias for Fq-based transcript (for SNARK composition / Grumpkin Fr)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
/// Type alias for Fq-based transcript (for SNARK composition / Grumpkin Fr)

Comment on lines +420 to +425
// This is still different from challenge_scalar_powers as inside the for loop
// we use an optimised multiplication every time we compute the powers.
let q: JF::Challenge = self.challenge_scalar_optimized::<JF>();
let mut q_powers = vec![<JF as ark_std::One>::one(); len];
for i in 1..len {
q_powers[i] = q * q_powers[i - 1]; // this is optimised
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI may have got confused with the naming of the functions which tbh they are confusing. Optimized refers to fact they are 128 bits, which I guess you can't get by taking powers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

are you speaking about the comment "this is optimized"? it was taken from the blake2b implementation, or is there something deeper I'm missing?

Copy link
Copy Markdown
Collaborator

@markosg04 markosg04 Feb 6, 2026

Choose a reason for hiding this comment

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

Oh I see. The blake2b comments were changed at some point and are also a little confusing. I think then it's ok to leave it and I can fix it later

@akoidefi akoidefi marked this pull request as draft February 19, 2026 14:29
@akoidefi akoidefi closed this Apr 7, 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.

3 participants