clock-hash 1.0.0

ClockHash-256: Consensus hash function for ClockinChain
Documentation
//! Padding implementation for ClockHash-256
//!
//! Implements the padding scheme: message || 0x80 || zeros || length_bits
//! where length_bits is the message length in bits as a 64-bit little-endian value.

#[cfg(any(feature = "alloc", feature = "std"))]
extern crate alloc;

/// Block size in bytes (128 bytes = 1024 bits)
pub const BLOCK_SIZE: usize = 128;

/// Maximum input size for constant-time operation (16384 bytes)
/// This ensures all inputs process the same number of blocks
pub const MAX_INPUT_SIZE: usize = 16384;

/// Number of blocks to always process (129 blocks = 16512 bytes)
pub const MAX_BLOCKS: usize = (MAX_INPUT_SIZE + BLOCK_SIZE - 1) / BLOCK_SIZE + 1;

/// Total padded size for constant-time operation
pub const MAX_PADDED_SIZE: usize = MAX_BLOCKS * BLOCK_SIZE;

/// Calculate the padded length for a message.
///
/// For constant-time operation, all messages are padded to the maximum size
/// to ensure uniform processing time regardless of input length.
///
/// # Arguments
///
/// * `_message_len` - Length of the message in bytes (unused for constant-time)
///
/// # Returns
///
/// The total length after padding (always MAX_PADDED_SIZE)
#[inline]
pub fn padded_length(_message_len: usize) -> usize {
    MAX_PADDED_SIZE
}

/// Pad a message block in-place.
///
/// This function pads the buffer to the maximum size for constant-time operation.
/// The buffer must be large enough to hold MAX_PADDED_SIZE bytes.
///
/// # Arguments
///
/// * `buffer` - Mutable buffer containing the message (must be at least MAX_PADDED_SIZE)
/// * `message_len_in_buffer` - Length of the message in the buffer in bytes
/// * `offset` - Offset in buffer where message starts (must be 0 for constant-time)
/// * `total_message_len` - Total length of the entire message in bytes
///
/// # Panics
///
/// Panics if the buffer is not large enough for MAX_PADDED_SIZE bytes.
#[inline]
pub fn pad_message_in_place(
    buffer: &mut [u8],
    message_len_in_buffer: usize,
    offset: usize,
    total_message_len: usize,
) {
    let bit_len = (total_message_len as u64).wrapping_mul(8);
    let pad_start = offset + message_len_in_buffer;
    let pad_end = offset + MAX_PADDED_SIZE;

    // Write 0x80 byte
    buffer[pad_start] = 0x80;

    // Zero out all remaining padding bytes (including where length will go)
    for i in (pad_start + 1)..pad_end {
        buffer[i] = 0;
    }

    // Write length in bits (64-bit little-endian) at the very end
    let len_bytes = bit_len.to_le_bytes();
    for (i, &byte) in len_bytes.iter().enumerate() {
        buffer[pad_end - 8 + i] = byte;
    }
}

/// Create a padded message block.
///
/// # Arguments
///
/// * `message` - The message to pad
///
/// # Returns
///
/// A vector containing the padded message (multiple of BLOCK_SIZE)
#[cfg(any(feature = "alloc", feature = "std"))]
pub fn pad_message(message: &[u8]) -> alloc::vec::Vec<u8> {
    let padded_len = padded_length(message.len());
    let mut result = alloc::vec::Vec::with_capacity(padded_len);
    result.extend_from_slice(message);
    result.resize(padded_len, 0);

    pad_message_in_place(&mut result, message.len(), 0, message.len());

    result
}

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

    #[test]
    fn test_padded_length() {
        // For constant-time operation, all messages pad to the maximum size
        assert_eq!(padded_length(0), MAX_PADDED_SIZE);
        assert_eq!(padded_length(1), MAX_PADDED_SIZE);
        assert_eq!(padded_length(120), MAX_PADDED_SIZE);
        assert_eq!(padded_length(119), MAX_PADDED_SIZE);
        assert_eq!(padded_length(128), MAX_PADDED_SIZE);
        assert_eq!(padded_length(16384), MAX_PADDED_SIZE);
    }

    #[test]
    #[cfg(any(feature = "alloc", feature = "std"))]
    fn test_pad_message_empty() {
        let padded = pad_message(b"");
        assert_eq!(padded.len(), MAX_PADDED_SIZE);
        assert_eq!(padded[0], 0x80);
        // Last 8 bytes should be length in bits (0)
        let len_start = MAX_PADDED_SIZE - 8;
        let len_bytes = u64::from_le_bytes([
            padded[len_start],
            padded[len_start + 1],
            padded[len_start + 2],
            padded[len_start + 3],
            padded[len_start + 4],
            padded[len_start + 5],
            padded[len_start + 6],
            padded[len_start + 7],
        ]);
        assert_eq!(len_bytes, 0);
    }

    #[test]
    #[cfg(any(feature = "alloc", feature = "std"))]
    fn test_pad_message_small() {
        let message = b"abc";
        let padded = pad_message(message);
        assert_eq!(padded.len(), MAX_PADDED_SIZE);
        assert_eq!(&padded[0..3], message);
        assert_eq!(padded[3], 0x80);
        // Check length in bits (3 * 8 = 24)
        let len_start = MAX_PADDED_SIZE - 8;
        let len_bytes = u64::from_le_bytes([
            padded[len_start],
            padded[len_start + 1],
            padded[len_start + 2],
            padded[len_start + 3],
            padded[len_start + 4],
            padded[len_start + 5],
            padded[len_start + 6],
            padded[len_start + 7],
        ]);
        assert_eq!(len_bytes, 24);
    }
}