spongefish 0.2.0-alpha

A library for Fiat-Shamir transcripts.
//! This module defines the duplex sponge construction that can absorb and squeeze data.
//! Hashes in `spongefish` operate over some native elements satisfying the trait [`Unit`] which, roughly speaking, requires
//! the basic type to support cloning, size, read/write procedures, and secure deletion.
//!
//! Additionally, the module exports some utilities:
//! - [`DuplexSponge`] allows to implement a [`DuplexInterface`] using a secure permutation function, specifying the rate `R` and the width `N`.
//! This is done using the standard duplex sponge construction in overwrite mode (cf. [Wikipedia](https://en.wikipedia.org/wiki/Sponge_function#Duplex_construction)).
//! - [`legacy::DigestBridge`] takes as input any hash function implementing the NIST API via the standard [`digest::Digest`] trait and makes it suitable for usage in duplex mode for continuous absorb/squeeze.

/// Sponge functions.
mod interface;
/// Legacy hash functions support (e.g. [`sha2`](https://crates.io/crates/sha2), [`blake2`](https://crates.io/crates/blake2)).
pub mod legacy;

pub use interface::DuplexSpongeInterface;
use zeroize::{Zeroize, ZeroizeOnDrop};

/// Basic units over which a sponge operates.
///
/// We require the units to have a precise size in memory, to be cloneable,
/// and that we can zeroize them.
pub trait Unit: Clone + Sized + zeroize::Zeroize {
    /// Write a bunch of units in the wire.
    fn write(bunch: &[Self], w: &mut impl std::io::Write) -> Result<(), std::io::Error>;
    /// Read a bunch of units from the wire
    fn read(r: &mut impl std::io::Read, bunch: &mut [Self]) -> Result<(), std::io::Error>;
}

/// The basic state of a cryptographic sponge.
///
/// A cryptographic sponge operates over some domain [`Permutation::U`] units.
/// It has a width [`Permutation::N`] and can process elements at rate [`Permutation::R`],
/// using the permutation function [`Permutation::permute`].
///
/// For implementors:
///
/// - State is written in *the first* [`Permutation::R`] (rate) bytes of the state.
/// The last [`Permutation::N`]-[`Permutation::R`] bytes are never touched directly except during initialization.
/// - The duplex sponge is in *overwrite mode*.
/// This mode is not known to affect the security levels and removes assumptions on [`Permutation::U`]
/// as well as constraints in the final zero-knowledge proof implementing the hash function.
/// - The [`std::default::Default`] implementation *MUST* initialize the state to zero.
/// - The [`Permutation::new`] method should initialize the sponge writing the entropy provided in the `iv` in the last [`Permutation::N`]-[`Permutation::R`] elements of the state.
pub trait Permutation: Zeroize + Default + Clone + AsRef<[Self::U]> + AsMut<[Self::U]> {
    /// The basic unit over which the sponge operates.
    type U: Unit;

    /// The width of the sponge, equal to rate [`Permutation::R`] plus capacity.
    /// Cannot be less than 1. Cannot be less than [`Permutation::R`].
    const N: usize;

    /// The rate of the sponge.
    const R: usize;

    /// Initialize the state of the sponge using 32 bytes of seed.
    fn new(iv: [u8; 32]) -> Self;

    /// Permute the state of the sponge.
    fn permute(&mut self);
}

/// A cryptographic sponge.
#[derive(Clone, PartialEq, Eq, Default, Zeroize, ZeroizeOnDrop)]
pub struct DuplexSponge<C: Permutation> {
    permutation: C,
    absorb_pos: usize,
    squeeze_pos: usize,
}

impl<U: Unit, C: Permutation<U = U>> DuplexSpongeInterface<U> for DuplexSponge<C> {
    fn new(iv: [u8; 32]) -> Self {
        assert!(C::R > 0, "The rate segment must be non-trivial");
        assert!(C::N > C::R, "The capacity segment must be non-trivial");

        Self {
            permutation: C::new(iv),
            absorb_pos: 0,
            squeeze_pos: C::R,
        }
    }

    fn absorb_unchecked(&mut self, mut input: &[U]) -> &mut Self {
        self.squeeze_pos = C::R;

        while !input.is_empty() {
            if self.absorb_pos == C::R {
                self.permutation.permute();
                self.absorb_pos = 0;
            } else {
                assert!(self.absorb_pos < C::R);
                let chunk_len = usize::min(input.len(), C::R - self.absorb_pos);
                let (chunk, rest) = input.split_at(chunk_len);

                self.permutation.as_mut()[self.absorb_pos..self.absorb_pos + chunk_len]
                    .clone_from_slice(chunk);
                self.absorb_pos += chunk_len;
                input = rest;
            }
        }
        self
    }

    fn squeeze_unchecked(&mut self, output: &mut [U]) -> &mut Self {
        if output.is_empty() {
            return self;
        }
        self.absorb_pos = 0;

        if self.squeeze_pos == C::R {
            self.squeeze_pos = 0;
            self.permutation.permute();
        }

        assert!(self.squeeze_pos < C::R);
        let chunk_len = usize::min(output.len(), C::R - self.squeeze_pos);
        let (output, rest) = output.split_at_mut(chunk_len);
        output.clone_from_slice(
            &self.permutation.as_ref()[self.squeeze_pos..self.squeeze_pos + chunk_len],
        );
        self.squeeze_pos += chunk_len;
        self.squeeze_unchecked(rest)
    }

    // fn tag(self) -> &'static [Self::U] {
    //     &self.state[C::RATE..]
    // }

    fn ratchet_unchecked(&mut self) -> &mut Self {
        self.permutation.permute();
        // set to zero the state up to rate
        // XXX. is the compiler really going to do this?
        self.permutation.as_mut()[..C::R]
            .iter_mut()
            .for_each(Zeroize::zeroize);
        self.squeeze_pos = C::R;
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::keccak::Keccak;

    #[test]
    fn test_keccak_duplex_sponge() {
        let mut sponge = Keccak::new(*b"unit_tests_keccak_tag___________");
        let mut output = [0u8; 64];

        let input = b"Hello, World!";
        sponge.absorb_unchecked(input);
        sponge.squeeze_unchecked(&mut output);

        assert_eq!(output.to_vec(), hex::decode("73e4a040a956f57693fb2b2dde8a8ea2c14d39ff8830060cd0301d6de25b2097ba858efedeeb89368eaf7c94a68f62835f932b5f0dd0ba376c48a0fdb5e21f0c").unwrap());
    }

    #[test]
    fn test_absorb_empty_before_does_not_break() {
        let mut sponge = Keccak::new(*b"unit_tests_keccak_tag___________");
        let mut output = [0u8; 64];

        let input = b"Hello, World!";
        sponge.absorb_unchecked(input);
        sponge.absorb_unchecked(b"");
        sponge.squeeze_unchecked(&mut output);

        assert_eq!(output.to_vec(), hex::decode("73e4a040a956f57693fb2b2dde8a8ea2c14d39ff8830060cd0301d6de25b2097ba858efedeeb89368eaf7c94a68f62835f932b5f0dd0ba376c48a0fdb5e21f0c").unwrap());
    }

    #[test]
    fn test_absorb_empty_after_does_not_break() {
        let mut sponge = Keccak::new(*b"unit_tests_keccak_tag___________");
        let mut output = [0u8; 64];

        let input = b"Hello, World!";
        sponge.absorb_unchecked(b"");
        sponge.absorb_unchecked(input);
        sponge.squeeze_unchecked(&mut output);

        assert_eq!(output.to_vec(), hex::decode("73e4a040a956f57693fb2b2dde8a8ea2c14d39ff8830060cd0301d6de25b2097ba858efedeeb89368eaf7c94a68f62835f932b5f0dd0ba376c48a0fdb5e21f0c").unwrap());
    }

    #[test]
    fn test_squeeze_zero_behavior() {
        let mut sponge = Keccak::new(*b"unit_tests_keccak_tag___________");
        let mut output = [0u8; 64];

        let input = b"Hello, World!";
        sponge.squeeze_unchecked(&mut [0u8; 0]);
        sponge.absorb_unchecked(input);
        sponge.squeeze_unchecked(&mut [0u8; 0]);
        sponge.squeeze_unchecked(&mut output);

        assert_eq!(output.to_vec(), hex::decode("73e4a040a956f57693fb2b2dde8a8ea2c14d39ff8830060cd0301d6de25b2097ba858efedeeb89368eaf7c94a68f62835f932b5f0dd0ba376c48a0fdb5e21f0c").unwrap());
    }

    #[test]
    fn test_squeeze_zero_after_behavior() {
        let mut sponge = Keccak::new(*b"unit_tests_keccak_tag___________");
        let mut output = [0u8; 64];

        let input = b"Hello, World!";
        sponge.squeeze_unchecked(&mut [0u8; 0]);
        sponge.absorb_unchecked(input);
        sponge.squeeze_unchecked(&mut output);

        assert_eq!(output.to_vec(), hex::decode("73e4a040a956f57693fb2b2dde8a8ea2c14d39ff8830060cd0301d6de25b2097ba858efedeeb89368eaf7c94a68f62835f932b5f0dd0ba376c48a0fdb5e21f0c").unwrap());
    }

    #[test]
    fn test_absorb_squeeze_absorb_consistency() {
        let mut sponge = Keccak::new(*b"edge-case-test-domain-absorb0000");
        let mut output = [0u8; 32];

        sponge.absorb_unchecked(b"first");
        sponge.squeeze_unchecked(&mut output);
        sponge.absorb_unchecked(b"second");
        sponge.squeeze_unchecked(&mut output);

        assert_eq!(
            output.to_vec(),
            hex::decode("20ce6da64ffc09df8de254222c068358da39d23ec43e522ceaaa1b82b90c8b9a")
                .unwrap()
        );
    }

    #[test]
    fn test_associativity_of_absorb() {
        let expected_output =
            hex::decode("7dfada182d6191e106ce287c2262a443ce2fb695c7cc5037a46626e88889af58")
                .unwrap();
        let tag = *b"absorb-associativity-domain-----";

        let mut sponge1 = Keccak::new(tag);
        sponge1.absorb_unchecked(b"hello world");
        let mut out1 = [0u8; 32];
        sponge1.squeeze_unchecked(&mut out1);

        let mut sponge2 = Keccak::new(tag);
        sponge2.absorb_unchecked(b"hello");
        sponge2.absorb_unchecked(b" world");
        let mut out2 = [0u8; 32];
        sponge2.squeeze_unchecked(&mut out2);

        assert_eq!(out1.to_vec(), expected_output);
        assert_eq!(out2.to_vec(), expected_output);
    }

    #[test]
    fn test_tag_affects_output() {
        let mut sponge1 = Keccak::new(*b"domain-one-differs-here-00000000");
        let mut sponge2 = Keccak::new(*b"domain-two-differs-here-00000000");

        let mut output1 = [0u8; 32];
        sponge1.absorb_unchecked(b"input");
        sponge1.squeeze_unchecked(&mut output1);

        let mut output2 = [0u8; 32];
        sponge2.absorb_unchecked(b"input");
        sponge2.squeeze_unchecked(&mut output2);

        assert_eq!(
            output1.to_vec(),
            hex::decode("2ecad63584ec0ff7f31edb822530762e5cb4b7dc1a62b1ffe02c43f3073a61b8")
                .unwrap()
        );
        assert_eq!(
            output2.to_vec(),
            hex::decode("6310fa0356e1bab0442fa19958e1c4a6d1dcc565b2b139b6044d1a809f531825")
                .unwrap()
        );
    }

    #[test]
    fn test_multiple_blocks_absorb_squeeze() {
        let mut sponge = Keccak::new(*b"multi-block-absorb-test_________");
        let input = vec![0xABu8; 3 * 200];
        let mut output = vec![0u8; 3 * 200];

        sponge.absorb_unchecked(&input);
        sponge.squeeze_unchecked(&mut output);

        assert_eq!(output, hex::decode("606310f839e763f4f37ce4c9730da92d4d293109de06abee8a7b40577125bcbfca331b97aee104d03139247e801d8b1a5f6b028b8e51fd643de790416819780a1235357db153462f78c150e34f29a303288f07f854e229aed41c786313119a1cee87402006ab5102271576542e5580be1927af773b0f1b46ce5c78c15267d3729928909192ea0115fcb9475b38a1ff5004477bbbb1b1f5c6a5c90c29b245a83324cb108133efc82216d33da9866051d93baab3bdf0fe02b007d4eb94885a42fcd02a9acdd47b71b6eeac17f5946367d6c69c95cbb80ac91d75e22c9862cf5fe10c7e121368e8a8cd9ff8eebe21071ff014e053725bcc624cd9f31818c4d049e70c14a22e5d3062a553ceca6157315ef2bdb3619c970c9c3d60817ee68291dcd17a282ed1b33cb3afb79c8247cd46de13add88da4418278c8b6b919914be5379daa823b036da008718c1d2a4a0768ecdf032e2b93c344ff65768c8a383a8747a1dcc13b5569b4e15cab9cc8f233fb28b13168284c8a998be6f8fa05389ff9c1d90c5845060d2df3fe0a923be8603abbd2b6f6dd6a5c09c81afe7c06bec789db87185297d6f7261f1e5637f2d140ff3b306df77f42cceffe769545ea8b011022387cd9e3d4f2c97feff5099139715f72301799fcfd59aa30f997e26da9eb7d86ee934a3f9c116d4a9e1012d795db35e1c61d27cd74bb6002f463fc129c1f9c4f25bc8e79c051ac2f1686e393d670f8d1e4cea12acfbff5a135623615d69a88f390569f17a0fc65f5886e2df491615155d5c3eb871209a5c7b0439585ad1a0acbede2e1a8d5aad1d8f3a033267e12185c5f2bbab0f2f1769247").unwrap());
    }
}