synor/crates/synor-mining/src/matrix.rs
Gulshan Yadav 5c643af64c fix: resolve all clippy warnings for CI
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
2026-01-08 05:58:22 +05:30

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));
}
}
}
}