Sitemap
ASecuritySite: When Bob Met Alice

This publication brings together interesting articles related to cyber security.

Press enter or click to view image in full size

Tip5: The Algebraic Hash

5 min readSep 28, 2025

--

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]:

Press enter or click to view image in full size

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:

--

--

ASecuritySite: When Bob Met Alice
ASecuritySite: When Bob Met Alice

Published in ASecuritySite: When Bob Met Alice

This publication brings together interesting articles related to cyber security.

Prof Bill Buchanan OBE FRSE
Prof Bill Buchanan OBE FRSE

Written by Prof Bill Buchanan OBE FRSE

Professor of Cryptography. Serial innovator. Believer in fairness, justice & freedom. Based in Edinburgh. Old World Breaker. New World Creator. Building trust.

No responses yet