Fix all Rust clippy warnings that were causing CI failures when built with RUSTFLAGS=-Dwarnings. Changes include: - Replace derivable_impls with derive macros for BlockBody, Network, etc. - Use div_ceil() instead of manual implementation - Fix should_implement_trait by renaming from_str to parse - Add type aliases for type_complexity warnings - Use or_default(), is_some_and(), is_multiple_of() where appropriate - Remove needless borrows and redundant closures - Fix manual_strip with strip_prefix() - Add allow attributes for intentional patterns (too_many_arguments, needless_range_loop in cryptographic code, assertions_on_constants) - Remove unused imports, mut bindings, and dead code in tests
320 lines
8.2 KiB
Rust
320 lines
8.2 KiB
Rust
//! HeavyHash matrix operations in GF(2^8).
|
|
//!
|
|
//! The matrix is a 64x64 matrix where each element is in GF(2^8).
|
|
//! Matrix multiplication is performed using Galois field arithmetic.
|
|
|
|
/// The irreducible polynomial for GF(2^8): x^8 + x^4 + x^3 + x + 1
|
|
const GF_POLY: u16 = 0x11B;
|
|
|
|
/// 64x64 HeavyHash matrix.
|
|
/// This is deterministically generated from a seed.
|
|
#[derive(Clone)]
|
|
pub struct HeavyMatrix {
|
|
/// The 64x64 matrix elements.
|
|
data: [[u8; 64]; 64],
|
|
}
|
|
|
|
impl HeavyMatrix {
|
|
/// Creates the default HeavyMatrix (Kaspa-compatible).
|
|
pub fn new() -> Self {
|
|
Self::from_seed(&SYNOR_MATRIX_SEED)
|
|
}
|
|
|
|
/// Creates a matrix from a 32-byte seed.
|
|
pub fn from_seed(seed: &[u8; 32]) -> Self {
|
|
let mut matrix = [[0u8; 64]; 64];
|
|
let mut state = *seed;
|
|
|
|
for row in matrix.iter_mut() {
|
|
for elem in row.iter_mut() {
|
|
// Simple deterministic expansion using Blake3
|
|
state = blake3::hash(&state).into();
|
|
*elem = state[0];
|
|
}
|
|
}
|
|
|
|
HeavyMatrix { data: matrix }
|
|
}
|
|
|
|
/// Multiplies a 256-bit vector by the matrix.
|
|
/// Input: 32 bytes (256 bits) treated as 64 nibbles
|
|
/// Output: 32 bytes after matrix multiplication
|
|
#[allow(clippy::needless_range_loop)]
|
|
pub fn multiply(&self, input: &[u8; 32]) -> [u8; 32] {
|
|
// Expand 32 bytes into 64 nibbles (4 bits each)
|
|
let mut nibbles = [0u8; 64];
|
|
for i in 0..32 {
|
|
nibbles[i * 2] = (input[i] >> 4) & 0x0F;
|
|
nibbles[i * 2 + 1] = input[i] & 0x0F;
|
|
}
|
|
|
|
// Matrix multiplication in GF(2^8)
|
|
let mut result = [0u8; 64];
|
|
for i in 0..64 {
|
|
let mut acc = 0u8;
|
|
for j in 0..64 {
|
|
acc = gf_add(acc, gf_mul(self.data[i][j], nibbles[j]));
|
|
}
|
|
result[i] = acc;
|
|
}
|
|
|
|
// Compress 64 values back to 32 bytes
|
|
let mut output = [0u8; 32];
|
|
for i in 0..32 {
|
|
// XOR adjacent pairs
|
|
output[i] = result[i * 2] ^ result[i * 2 + 1];
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
/// Multiplies with full byte input (alternative method).
|
|
#[allow(clippy::needless_range_loop)]
|
|
pub fn multiply_bytes(&self, input: &[u8; 32]) -> [u8; 32] {
|
|
let mut output = [0u8; 32];
|
|
|
|
// Treat input as 32 bytes, output as 32 bytes
|
|
// Use a 32x32 view of the matrix
|
|
for i in 0..32 {
|
|
let mut acc: u16 = 0;
|
|
for j in 0..32 {
|
|
let m1 = self.data[i * 2][j * 2] as u16;
|
|
let m2 = self.data[i * 2 + 1][j * 2 + 1] as u16;
|
|
let v = input[j] as u16;
|
|
acc = acc.wrapping_add((m1.wrapping_mul(v)).wrapping_add(m2.wrapping_mul(v)));
|
|
}
|
|
output[i] = (acc & 0xFF) as u8;
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
/// Gets a matrix element.
|
|
#[inline]
|
|
pub fn get(&self, row: usize, col: usize) -> u8 {
|
|
self.data[row][col]
|
|
}
|
|
|
|
/// Returns the raw matrix data.
|
|
pub fn as_bytes(&self) -> &[[u8; 64]; 64] {
|
|
&self.data
|
|
}
|
|
}
|
|
|
|
impl Default for HeavyMatrix {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Addition in GF(2^8) is XOR.
|
|
#[inline]
|
|
pub fn gf_add(a: u8, b: u8) -> u8 {
|
|
a ^ b
|
|
}
|
|
|
|
/// Multiplication in GF(2^8) using the irreducible polynomial.
|
|
#[inline]
|
|
pub fn gf_mul(a: u8, b: u8) -> u8 {
|
|
let mut result: u16 = 0;
|
|
let mut aa = a as u16;
|
|
let mut bb = b as u16;
|
|
|
|
for _ in 0..8 {
|
|
if bb & 1 != 0 {
|
|
result ^= aa;
|
|
}
|
|
let hi_bit = aa & 0x80;
|
|
aa <<= 1;
|
|
if hi_bit != 0 {
|
|
aa ^= GF_POLY;
|
|
}
|
|
bb >>= 1;
|
|
}
|
|
|
|
(result & 0xFF) as u8
|
|
}
|
|
|
|
/// Precomputed multiplication tables for faster GF(2^8) operations.
|
|
pub struct GfTables {
|
|
/// Multiplication table: mul[a][b] = a * b in GF(2^8)
|
|
mul: [[u8; 256]; 256],
|
|
}
|
|
|
|
impl GfTables {
|
|
/// Creates precomputed tables.
|
|
#[allow(clippy::needless_range_loop)]
|
|
pub fn new() -> Self {
|
|
let mut mul = [[0u8; 256]; 256];
|
|
|
|
for a in 0..256 {
|
|
for b in 0..256 {
|
|
mul[a][b] = gf_mul(a as u8, b as u8);
|
|
}
|
|
}
|
|
|
|
GfTables { mul }
|
|
}
|
|
|
|
/// Fast multiplication using lookup table.
|
|
#[inline]
|
|
pub fn mul(&self, a: u8, b: u8) -> u8 {
|
|
self.mul[a as usize][b as usize]
|
|
}
|
|
}
|
|
|
|
impl Default for GfTables {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
/// Synor's matrix seed (unique to our chain).
|
|
/// This creates a different matrix than Kaspa, preventing merge-mining.
|
|
pub const SYNOR_MATRIX_SEED: [u8; 32] = [
|
|
0x53, 0x59, 0x4E, 0x4F, 0x52, 0x5F, 0x48, 0x45, // "SYNOR_HE"
|
|
0x41, 0x56, 0x59, 0x5F, 0x4D, 0x41, 0x54, 0x52, // "AVY_MATR"
|
|
0x49, 0x58, 0x5F, 0x53, 0x45, 0x45, 0x44, 0x5F, // "IX_SEED_"
|
|
0x56, 0x30, 0x30, 0x31, 0x00, 0x00, 0x00, 0x01, // "V001"...
|
|
];
|
|
|
|
/// Optimized matrix multiplication using precomputed tables.
|
|
pub struct OptimizedMatrix {
|
|
/// The 64x64 matrix.
|
|
matrix: HeavyMatrix,
|
|
/// Precomputed GF tables.
|
|
tables: GfTables,
|
|
}
|
|
|
|
impl OptimizedMatrix {
|
|
/// Creates an optimized matrix with lookup tables.
|
|
pub fn new() -> Self {
|
|
OptimizedMatrix {
|
|
matrix: HeavyMatrix::new(),
|
|
tables: GfTables::new(),
|
|
}
|
|
}
|
|
|
|
/// Creates from a specific seed.
|
|
pub fn from_seed(seed: &[u8; 32]) -> Self {
|
|
OptimizedMatrix {
|
|
matrix: HeavyMatrix::from_seed(seed),
|
|
tables: GfTables::new(),
|
|
}
|
|
}
|
|
|
|
/// Fast matrix multiplication.
|
|
#[allow(clippy::needless_range_loop)]
|
|
pub fn multiply(&self, input: &[u8; 32]) -> [u8; 32] {
|
|
// Expand to nibbles
|
|
let mut nibbles = [0u8; 64];
|
|
for i in 0..32 {
|
|
nibbles[i * 2] = (input[i] >> 4) & 0x0F;
|
|
nibbles[i * 2 + 1] = input[i] & 0x0F;
|
|
}
|
|
|
|
// Matrix multiplication with table lookup
|
|
let mut result = [0u8; 64];
|
|
for i in 0..64 {
|
|
let mut acc = 0u8;
|
|
for j in 0..64 {
|
|
acc ^= self.tables.mul(self.matrix.get(i, j), nibbles[j]);
|
|
}
|
|
result[i] = acc;
|
|
}
|
|
|
|
// Compress
|
|
let mut output = [0u8; 32];
|
|
for i in 0..32 {
|
|
output[i] = result[i * 2] ^ result[i * 2 + 1];
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
/// Returns the underlying matrix.
|
|
pub fn matrix(&self) -> &HeavyMatrix {
|
|
&self.matrix
|
|
}
|
|
}
|
|
|
|
impl Default for OptimizedMatrix {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_gf_add() {
|
|
assert_eq!(gf_add(0, 0), 0);
|
|
assert_eq!(gf_add(0xFF, 0xFF), 0);
|
|
assert_eq!(gf_add(0xAA, 0x55), 0xFF);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gf_mul() {
|
|
// Multiplication by 1
|
|
assert_eq!(gf_mul(0x53, 1), 0x53);
|
|
// Multiplication by 0
|
|
assert_eq!(gf_mul(0x53, 0), 0);
|
|
// Known values
|
|
assert_eq!(gf_mul(0x57, 0x83), 0xC1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_matrix_deterministic() {
|
|
let m1 = HeavyMatrix::new();
|
|
let m2 = HeavyMatrix::new();
|
|
|
|
// Same seed should produce same matrix
|
|
for i in 0..64 {
|
|
for j in 0..64 {
|
|
assert_eq!(m1.get(i, j), m2.get(i, j));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_matrix_multiply() {
|
|
let matrix = HeavyMatrix::new();
|
|
let input = [0x42u8; 32];
|
|
|
|
let output = matrix.multiply(&input);
|
|
|
|
// Output should be deterministic
|
|
let output2 = matrix.multiply(&input);
|
|
assert_eq!(output, output2);
|
|
|
|
// Output should be different from input
|
|
assert_ne!(output, input);
|
|
}
|
|
|
|
#[test]
|
|
fn test_optimized_matrix() {
|
|
let matrix = HeavyMatrix::new();
|
|
let optimized = OptimizedMatrix::new();
|
|
let input = [0x55u8; 32];
|
|
|
|
let output1 = matrix.multiply(&input);
|
|
let output2 = optimized.multiply(&input);
|
|
|
|
// Both should produce same result
|
|
assert_eq!(output1, output2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_gf_tables() {
|
|
let tables = GfTables::new();
|
|
|
|
// Verify table matches direct computation
|
|
for a in 0..=255u8 {
|
|
for b in 0..=255u8 {
|
|
assert_eq!(tables.mul(a, b), gf_mul(a, b));
|
|
}
|
|
}
|
|
}
|
|
}
|