use proofman_starks_lib_c::GOLDILOCKS_POSEIDON_MERKLE_TREE_ARITY;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum GateRole {
PoseidonSponge,
PoseidonCompression,
TreeSelector,
CMul,
Fft4,
EvPol4,
SelectVal1,
}
pub const FAMILIES: &[&str] = &["Poseidon1", "Poseidon2", "blake3"];
pub const DEFAULT_HASH_ID: &str = "Poseidon1";
pub fn merkle_tree_arity(family: &str) -> u64 {
match family {
"Poseidon1" | "Poseidon2" => GOLDILOCKS_POSEIDON_MERKLE_TREE_ARITY,
"blake3" => 2,
fam => panic!("Unknown hash family: {fam}"),
}
}
pub fn default_grinding_bits(family: &str) -> usize {
match family {
"Poseidon1" | "Poseidon2" => 16,
"blake3" => 24,
fam => panic!("Unknown hash family: {fam}"),
}
}
pub fn transcript_arity(family: &str) -> u64 {
merkle_tree_arity(family)
}
pub const DIGEST_SIZE: u64 = 4;
pub fn sponge_width(arity: u64) -> u64 {
arity * DIGEST_SIZE
}
pub fn sponge_rate(arity: u64) -> u64 {
sponge_width(arity) - DIGEST_SIZE
}
pub fn transcript_pending_size(arity: u64) -> u64 {
DIGEST_SIZE * (arity - 1)
}
pub fn transcript_out_size(arity: u64) -> u64 {
DIGEST_SIZE * arity
}
pub fn supports_stream_commit(family: &str) -> bool {
matches!(family, "Poseidon1" | "blake3")
}
pub fn has_forced_tree_geometry(family: &str) -> bool {
family == "blake3"
}
const GATES: &[(&str, GateRole, Option<&str>)] = &[
("Poseidon1_16", GateRole::PoseidonSponge, Some("Poseidon1")),
("CustPoseidon1_16", GateRole::PoseidonCompression, Some("Poseidon1")),
("TreeSelector8", GateRole::TreeSelector, Some("Poseidon1")),
("Poseidon2_16", GateRole::PoseidonSponge, Some("Poseidon2")),
("CustPoseidon2_16", GateRole::PoseidonCompression, Some("Poseidon2")),
("TreeSelector4", GateRole::TreeSelector, Some("Poseidon2")),
("CMul", GateRole::CMul, None),
("FFT4", GateRole::Fft4, None),
("EvPol4", GateRole::EvPol4, None),
("SelectValue1", GateRole::SelectVal1, None),
];
pub fn lookup_gate(name: &str) -> Option<(GateRole, Option<&'static str>)> {
GATES.iter().find(|(n, _, _)| *n == name).map(|(_, r, f)| (*r, *f))
}
pub fn is_known_family(id: &str) -> bool {
FAMILIES.contains(&id)
}
pub fn rust_hash_type(family: &str, arity: u64) -> &'static str {
match (family, arity * 4) {
("Poseidon1", 8) => "Poseidon1_8",
("Poseidon1", 12) => "Poseidon1_12",
("Poseidon1", 16) => "Poseidon1_16",
("Poseidon2", 4) => "Poseidon2_4",
("Poseidon2", 8) => "Poseidon2_8",
("Poseidon2", 12) => "Poseidon2_12",
("Poseidon2", 16) => "Poseidon2_16",
(fam, width) => panic!("Unsupported hash type: {fam}_{width} (arity {arity})"),
}
}
pub fn rust_grinding_type(family: &str) -> &'static str {
match family {
"Poseidon1" => "Poseidon1_8",
"Poseidon2" => "Poseidon2_8",
fam => panic!("Unsupported grinding hash family: {fam}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn discriminating_gate_names_are_unique_per_family() {
for (i, (name, role, fam)) in GATES.iter().enumerate() {
if fam.is_none() {
continue;
}
if !matches!(role, GateRole::PoseidonSponge | GateRole::PoseidonCompression | GateRole::TreeSelector) {
continue;
}
for (other_name, _, other_fam) in &GATES[i + 1..] {
if name == other_name {
panic!("gate {name:?} claimed by both {fam:?} and {other_fam:?}");
}
}
}
}
#[test]
fn rust_hash_type_tracks_arity_width() {
assert_eq!(rust_hash_type("Poseidon2", 4), "Poseidon2_16");
assert_eq!(rust_hash_type("Poseidon2", 2), "Poseidon2_8");
assert_eq!(rust_hash_type("Poseidon1", 4), "Poseidon1_16");
assert_eq!(rust_hash_type("Poseidon1", 3), "Poseidon1_12");
assert_eq!(rust_hash_type("Poseidon1", 2), "Poseidon1_8");
}
#[test]
fn every_family_has_grinding_bits_and_blake3_grinds_hardest() {
for family in FAMILIES {
assert!(default_grinding_bits(family) > 0, "{family} has no grinding default");
}
assert_eq!(default_grinding_bits("Poseidon1"), default_grinding_bits("Poseidon2"));
assert!(default_grinding_bits("blake3") > default_grinding_bits("Poseidon1"));
}
#[test]
fn the_sponge_width_fits_one_node_of_children() {
for arity in [2, 3, 4] {
assert_eq!(sponge_width(arity), arity * DIGEST_SIZE);
assert_eq!(sponge_rate(arity), sponge_width(arity) - DIGEST_SIZE);
}
assert_eq!((sponge_width(4), sponge_rate(4)), (16, 12));
}
#[test]
fn transcript_sizes_match_the_native_transcript() {
assert_eq!((transcript_pending_size(4), transcript_out_size(4)), (12, 16));
assert_eq!((transcript_pending_size(2), transcript_out_size(2)), (4, 8));
}
#[test]
fn default_hash_id_is_registered() {
assert!(is_known_family(DEFAULT_HASH_ID));
}
}