Crates.io | dusk-safe |
lib.rs | dusk-safe |
version | 0.2.1 |
source | src |
created_at | 2024-03-06 11:01:12.761751 |
updated_at | 2024-05-08 13:55:11.934679 |
description | Sponge API for Field Elements |
homepage | |
repository | https://github.com/dusk-network/safe |
max_upload_size | |
id | 1164646 |
size | 67,376 |
A generic API for sponge functions.
This is a minimal, no_std
, pure Rust implementation of a sponge function, based on SAFE (Sponge API for Field Elements), to be used in permutation-based symmetric primitives' design, such as hash functions, MACs, authenticated encryption schemes, PRNGs, and other.
The sponge is designed to be usable in zero-knowledge proving systems (ZKPs) as well as natively, operating on any type implementing the Default
and Copy
trait with a size of minimal 32 bytes.
Sponge functions are the basis of permutation-based symmetric primitives’ design. They can be seen as a stateful object that can ingest input (“absorb”) and produce output (“squeeze”) at any time and in arbitrary order.
As its main features, this sponge API:
This sponge construction in itself does not support variable-length hashing, i.e. hashing where the length of data hashed is unknown in advance.
However, this behavior can be achieved by wrapping the sponge in a hasher, that only starts the sponge upon finalizing the hash, thus at a time when the length of the input is known (example implementation of this wrapper can be found in dusk-poseidon
).
The sponge constructed in this library is defined by:
[T; W]
with type T: Default + Copy
and width W
1
R
with R = W - 1
len
items of input (absorb(len)
) and pruduce output (squeeze(len)
) (eg. [absorb(4), absorb(1), squeeze(3)]
)Note: With the capacity beeing one element of type T
we need to restrict T
to be at least 256 bits. It is the responsibility of the user to properly serialize input of different sizes into a type with at least 256 bits.
start
absorb
.squeeze
.len == 0
.absorb
and to 0 for squeeze
, and the length is added to the lower bits. Any contiguous calls to absorb
and squeeze
will be aggregated, e.g. the above example of an IO pattern of [absorb(4), absorb(1), squeeze(3)]
will have the same encoding as [absorb(5), squeeze(3)]
: [0x8000_0005, 0x0000_0001]
.0x4142
, then the example above would yield the string (with big-endian convention): 0x80000005000000014142
.T
.T
.finish
absorb(len, input)
len
elements of input
:
pos_absorb == rate
and set pos_absorb = 0
.pos_absort + 1
(we skip the first element which is the capacity).pos_absorb
by one.pos_squeeze
to the rate to force a call to the permutation function at the start of the next call to squeeze
.squeeze(len)
len
times:
pos_squeeze == rate
and set pos_squeeze = 0
pos_squeeze + 1
(also here we skip the first element due to the capacity) to the output vector.Note that we do not set the pos_absorb
to the rate as we do with the pos_squeeze
in the call to absorb
, this is because we may want the state to absorb at the same positions that have been squeezed.
use dusk_bls12_381::BlsScalar;
use ff::Field;
use rand::rngs::StdRng;
use rand::SeedableRng;
use dusk_safe::{Error, Call, Safe, Sponge};
const W: usize = 7;
#[derive(Default, Debug, Clone, Copy, PartialEq)]
struct Rotate();
impl Safe<BlsScalar, W> for Rotate {
// Rotate every item one item to the left, first item becomes last.
// Note: This permutation is just an example and *should not* be used for a
// sponge construction for cryptographically safe hash functions.
fn permute(&mut self, state: &mut [BlsScalar; W]) {
let tmp = state[0];
for i in 1..W {
state[i - 1] = state[i];
}
state[W - 1] = tmp;
}
// Define the hasher used to generate the tag from the encoding of the
// io-pattern and domain-separator.
fn tag(&mut self, input: &[u8]) -> BlsScalar {
BlsScalar::hash_to_scalar(input)
}
fn add(&mut self, right: &BlsScalar, left: &BlsScalar) -> BlsScalar {
right + left
}
}
impl Rotate {
pub fn new() -> Self {
Self()
}
}
// pick a domain-separator
let domain_sep = 0;
// generate random input
let mut rng = StdRng::seed_from_u64(0x42424242);
let mut input = [BlsScalar::zero(); 8];
input.iter_mut().for_each(|s| *s = BlsScalar::random(&mut rng));
// build the io-pattern
let iopattern = vec![
Call::Absorb(6),
Call::Absorb(2),
Call::Squeeze(1),
Call::Squeeze(2),
];
// start the sponge
let mut sponge = Sponge::start(
Rotate::new(),
iopattern,
domain_sep,
)
.expect("io-pattern should be valid");
// absorb 6 elements
sponge.absorb(6, &input).expect("absorbing should not fail");
// absorb 2 elements
sponge.absorb(2, &input[6..]).expect("absorbing should not fail");
// squeeze 1 element
sponge.squeeze(1).expect("squeezing should not fail");
// squeeze 2 elements
sponge.squeeze(2).expect("squeezing should not fail");
// generate the hash output
let output1 = sponge.finish().expect("Finishing should not fail");
// Generate another hash output from the same input and aggregated IO pattern:
// build the io-pattern
let iopattern = vec![
Call::Absorb(8),
Call::Squeeze(3),
];
// start the sponge
let mut sponge = Sponge::start(
Rotate::new(),
iopattern,
domain_sep,
)
.expect("io-pattern should be valid");
// absorb 8 elements
sponge.absorb(8, &input).expect("absorbing should not fail");
// squeeze 3 elements
sponge.squeeze(3).expect("squeezing should not fail");
// generate the hash output
let output2 = sponge.finish().expect("Finishing should not fail");
assert_eq!(output1, output2);