Tip5: The Algebraic Hash
Most cybersecurity professionals will tell you that a cryptographic hash involves the squeezing of data to form a fixed-length output. We can, of course, have a variable-length output with an XOF function, but it still involves a squeezing process for several rounds. But this process does not follow an algebraic approach and thus lacks a mathematical basis. In a zero-knowledge proof world — such as with STARKs — our normal hashing methods, such as SHA-256 and Blake2, are complex to implement and often slow to run, especially since we cannot compute them within a finite field.
MiMC
To integrate algebraic hashes, we need a hashing method that can be implemented over a finite field. Three typical methods are the Pedersen hash [here], the Posiden hash [here] and the MiMC (Minimal Multiplicative Complexity) hash [1][here]:
MiMC has an important feature of having a low multiplicative
complexity, and which is used in areas of multi-party computation (MPC), fully homomorphic encryption (FHE), and ZKPs. Dan Bohen et al [2] also propose its uses for VDF (Verifiable Delay Functions), as MiMC has only 1.6% of the complexity of AES.
In the original paper, there is a mapping of:
F(x) = x³
but MiMC-7 uses:
F(x) = x⁷
Overall, we have a finite field (F_q) of q elements and iterate with a round function of r times — and where q is a prime number. Within each round, we add a key (k) value and a round constant (c_i) — apart from the first round — along with the non-linear function of F(x)=x⁷. A demo of this is here:
Tip5
Tip5 is an Algebraic-oriented (AO) hash function [here], and uses a finite field (F_𝑝) with 𝑝 = 2⁶⁴ − 2³² + 1 — a “small” Goldilocks prime field. This has 128 bits of security. A key feature is that it has a low multiplicative depth, as multiplication has a high overhead within zero-knowledge proofs. Overall, Tip5 has three main elements:
- An S-Box layer. This operates as a basic look-up table.
- An MDS layer. This involves a matrix-vector multiplication of the internal state using a 16 × 16 MDS matrix.
- An ARK (Additive Round Constants) layer. This involves adding a sampled constant to each state element.
We can use the code [here][Demo]:
/**
*
* Copyright (c) 2025 Maxim [maxirmx] Samsonov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* This file is a part of tip5xx library
*
*/
use clap::{Parser, ValueEnum};
use std::error::Error;
use twenty_first::{math::tip5::Tip5, prelude::{Digest, BFieldElement}};
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
enum Mode {
Pair,
Varlen,
}
#[derive(Parser)]
#[command(author, version, about = "TIP5 Hash Calculator")]
struct Args {
/// Hash mode: 'pair' or 'varlen'
#[arg(short, long, value_enum, default_value_t = Mode::Pair)]
mode: Mode,
/// Input digests in format (n1,n2,n3,n4,n5) for each digest
#[arg(required = true, help = "Input digests in format (n1,n2,n3,n4,n5).\nFor pair mode: provide exactly 2 digests\nFor varlen mode: provide 2 or more digests\nEach number can be in formats:\n- Hexadecimal: 0x01020304 (must use 0x prefix)\n- Decimal: 16909060 (numbers starting with 0 like 0123 are treated as decimal)")]
inputs: Vec,
}
fn parse_number(input: &str) -> Result<BFieldElement, Box<dyn Error>> {
let trimmed = input.trim();
let value = if trimmed.starts_with("0x") || trimmed.starts_with("0X") {
// Handle hex format
let hex_str = &trimmed[2..];
u64::from_str_radix(hex_str, 16)?
} else {
// Handle decimal
trimmed.parse::()?
};
Ok(BFieldElement::new(value))
}
fn parse_digest(input: &str) -> Result<Digest, Box<dyn Error>> {
// Remove outer parentheses and split by comma
let content = input.trim()
.strip_prefix('(')
.ok_or("Missing opening parenthesis")?
.strip_suffix(')')
.ok_or("Missing closing parenthesis")?;
let numbers: Vec<&str> = content.split(',').collect();
if numbers.len() != 5 {
return Err("Each digest must contain exactly 5 numbers".into());
}
let elements: Result, _> = numbers
.iter()
.map(|n| parse_number(n))
.collect();
let elements = elements?;
Ok(Digest::new([
elements[0],
elements[1],
elements[2],
elements[3],
elements[4],
]))
}
fn main() -> Result<(), Box<dyn Error>> {
let args = Args::parse();
match args.mode {
Mode::Pair => {
if args.inputs.len() != 2 {
return Err("pair mode requires exactly 2 digests".into());
}
let digest1 = parse_digest(&args.inputs[0])?;
let digest2 = parse_digest(&args.inputs[1])?;
println!("Hash pair mode Digest{}, Digest{}", args.inputs[0], args.inputs[1]);
let result = Tip5::hash_pair(digest1, digest2);
print!("Result: ");
println!("Digest({})", result.to_string());
}
Mode::Varlen => {
if args.inputs.len() < 2 {
return Err("varlen mode requires at least 2 digests".into());
}
let mut digests = Vec::new();
for input in &args.inputs {
let digest = parse_digest(input)?;
digests.extend_from_slice(&digest.values());
}
print!("Hash varlen mode Digest");
for (i, input) in args.inputs.iter().enumerate() {
if i > 0 {
print!(", ");
}
print!("{}", input);
}
println!("");
let result = Tip5::hash_varlen(&digests);
print!("Result: ");
println!("Digest({})", result.to_string());
}
}
Ok(())
}A sample run is [Demo]:
Hash pair mode Digest(06635923243246478901,08947438058482825410,01316460179998954120,11985665471998192029,14569756628708636130), Digest(06635923243246478902,08947438058482825410,01316460179998954127,11985665471998192023,14569756628708636139)
Result: Digest(16064912558447642910,09028968064102432957,17557561715502144571,18410287472055724668,16895367727029851698)You can find other Low MC methods here:

