Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions crypto/muhash/src/u3072.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ impl U3072 {

#[inline(always)]
fn full_reduce(&mut self) {
debug_assert!(self.is_overflow());
let mut low = PRIME_DIFF;
let mut high: Limb = 0;
for limb in &mut self.limbs {
Expand Down Expand Up @@ -249,6 +250,7 @@ fn mul_wide(a: Limb, b: Limb) -> (Limb, Limb) {
#[inline(always)]
#[must_use]
fn mulnadd3(c0: Limb, c1: Limb, d0: Limb, d1: Limb, d2: Limb, n: Limb) -> (Limb, Limb, Limb) {
debug_assert!(d2 < LIMBS as Limb, "{d2} >= {LIMBS}"); // d2 is the highest limb of the number, so it must be smaller than the number of limbs (otherwise it would have been reduced already)
let mut t = d0 as DoubleLimb * n as DoubleLimb + c0 as DoubleLimb;
let c0 = t as Limb;
t >>= LIMB_SIZE;
Expand Down Expand Up @@ -277,6 +279,7 @@ fn muladd3(a: Limb, b: Limb, low: Limb, high: Limb, mut carry: Limb) -> (Limb, L
#[inline(always)]
#[must_use]
fn muln2(low: Limb, high: Limb, n: Limb) -> (Limb, Limb) {
debug_assert!(high <= LIMBS as Limb, "high({high}) carry must be bounded by accumulation count");
let mut tmp = low as DoubleLimb * n as DoubleLimb;
let low = tmp as Limb;

Expand Down Expand Up @@ -338,22 +341,22 @@ mod tests {
c1: Limb::MAX - 75,
d0: Limb::MAX - 30,
d1: Limb::MAX - 3452,
d2: 829429,
d2: 10,
n: 48569320,
expected_c0: 18446744072203902596,
expected_c1: 18446743906048258900,
expected_c2: 40284851087600,
expected_c2: 534262520,
},
TestVector {
c0: 0,
c1: Limb::MAX - 32432432,
d0: Limb::MAX - 534532431432423,
d1: 1,
d2: 342356341,
d2: 47,
n: 878998734,
expected_c0: 3687790413486659920,
expected_c1: 1725539564,
expected_c2: 300930790315872295,
expected_c2: 41312940499,
},
];
for test in tests {
Expand All @@ -374,14 +377,8 @@ mod tests {
expected_high: Limb,
}
let tests = [
TestVector { low: Limb::MAX - 99, high: Limb::MAX - 75, n: Limb::MAX - 543, expected_low: 54400, expected_high: 40700 },
TestVector {
low: 0,
high: Limb::MAX - 32432432,
n: Limb::MAX - 546546456543,
expected_low: 0,
expected_high: 17725831333250691552,
},
TestVector { low: Limb::MAX - 99, high: 47, n: Limb::MAX - 543, expected_low: 54400, expected_high: 18446744073709525404 },
TestVector { low: 0, high: 3, n: Limb::MAX - 546546456543, expected_low: 0, expected_high: 18446742434070181984 },
];
for test in tests {
let (low, high) = u3072::muln2(test.low, test.high, test.n);
Expand Down
6 changes: 6 additions & 0 deletions math/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ path = ".."
[workspace]
members = ["."]

[profile.release]
debug = true
strip = false
debug-assertions = true
overflow-checks = true

[[bin]]
name = "u128"
path = "fuzz_targets/u128.rs"
Expand Down
57 changes: 47 additions & 10 deletions math/fuzz/fuzz_targets/u128.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![no_main]
mod utils;

use core::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem};
use core::ops::{BitAnd, BitOr, BitXor, Div, Rem};
use kaspa_math::construct_uint;
use libfuzzer_sys::fuzz_target;
use std::convert::TryInto;
Expand All @@ -22,6 +22,7 @@ fn generate_ints_top_bit_cleared(data: &mut &[u8]) -> Option<(Uint128, u128)> {
Some((Uint128::from_le_bytes(buf), u128::from_le_bytes(buf)))
}

#[track_caller]
fn assert_op<T, U>(data: &mut &[u8], op_lib: T, op_native: U, ok_by_zero: bool) -> Option<()>
where
T: Fn(Uint128, Uint128) -> Uint128,
Expand All @@ -38,6 +39,26 @@ where
Some(())
}

#[track_caller]
fn assert_op_with_overflow<T, U>(data: &mut &[u8], op_lib: T, op_native: U, ok_by_zero: bool) -> Option<()>
where
T: Fn(Uint128, Uint128) -> (Uint128, bool),
U: Fn(u128, u128) -> (u128, bool),
{
let (lib, native) = generate_ints(data)?;
let (lib2, native2) = loop {
let (lib2, native2) = generate_ints(data)?;
if ok_by_zero || native2 != 0 {
break (lib2, native2);
}
};
let (lib_result, lib_overflow) = op_lib(lib, lib2);
let (native_result, native_overflow) = op_native(native, native2);
assert_eq!(lib_result, native_result, "native: {native}, native2: {native2}");
assert_eq!(lib_overflow, native_overflow, "native: {native}, native2: {native2}");
Some(())
}

fuzz_target!(|data: &[u8]| {
let mut data = data;
// from_le_bytes
Expand All @@ -46,10 +67,12 @@ fuzz_target!(|data: &[u8]| {
assert_eq!(lib, native);
}

// Full addition
assert_op(&mut data, Add::add, Add::add, true);
// Full multiplication
assert_op(&mut data, Mul::mul, Mul::mul, true);
// Full Overflow addition
assert_op_with_overflow(&mut data, Uint128::overflowing_add, u128::overflowing_add, true);
// Full Overflow multiplication
assert_op_with_overflow(&mut data, Uint128::overflowing_mul, u128::overflowing_mul, true);
// Full Overflow subtraction
assert_op_with_overflow(&mut data, Uint128::overflowing_sub, u128::overflowing_sub, true);
// Full division
assert_op(&mut data, Div::div, Div::div, false);
// Full remainder
Expand All @@ -71,25 +94,39 @@ fuzz_target!(|data: &[u8]| {
{
let (lib, native) = try_opt!(generate_ints(&mut data));
let word = u64::from_le_bytes(try_opt!(consume(&mut data)));
assert_eq!(lib + word, native + (word as u128), "native: {native}, word: {word}");
let (lib_result, lib_overflow) = lib.overflowing_add_u64(word);
let (native_result, native_overflow) = u128::overflowing_add(native, word as u128);
assert_eq!(lib_result, native_result, "native: {native}, word: {word}");
assert_eq!(lib_overflow, native_overflow, "native: {native}, word: {word}");
}
// U64 multiplication
{
let (lib, native) = try_opt!(generate_ints(&mut data));
let word = u64::from_le_bytes(try_opt!(consume(&mut data)));
assert_eq!(lib * word, native * (word as u128), "native: {native}, word: {word}");
let (lib_result, lib_overflow) = lib.overflowing_mul_u64(word);
let (native_result, native_overflow) = u128::overflowing_mul(native, word as u128);
assert_eq!(lib_result, native_result, "native: {native}, word: {word}");
assert_eq!(lib_overflow, native_overflow, "native: {native}, word: {word}");
}
// Left shift
{
let (lib, native) = try_opt!(generate_ints(&mut data));
let lshift = try_opt!(consume::<1>(&mut data))[0] as u32;
assert_eq!(lib << lshift, native << lshift, "native: {native}, lshift: {lshift}");

let (native_res, native_overflow) = native.overflowing_shl(lshift);
let (lib_res, lib_overflow) = lib.overflowing_shl(lshift);
assert_eq!(lib_res, native_res, "native: {native}, lshift: {lshift}");
assert_eq!(lib_overflow, native_overflow, "native: {native}, lshift: {lshift}");
}
// Right shift
{
let (lib, native) = try_opt!(generate_ints(&mut data));
let rshift = try_opt!(consume::<1>(&mut data))[0] as u32;
assert_eq!(lib >> rshift, native >> rshift, "native: {native}, rshift: {rshift}");

let (native_res, native_overflow) = native.overflowing_shr(rshift);
let (lib_res, lib_overflow) = lib.overflowing_shr(rshift);
assert_eq!(lib_res, native_res, "native: {native}, rshift: {rshift}");
assert_eq!(lib_overflow, native_overflow, "native: {native}, rshift: {rshift}");
}
// bits
{
Expand All @@ -104,7 +141,7 @@ fuzz_target!(|data: &[u8]| {
// as u128
{
let (lib, native) = try_opt!(generate_ints(&mut data));
assert_eq!(lib.as_u128(), native as u128, "native: {native}");
assert_eq!(lib.as_u128(), native, "native: {native}");
}
// as f64
{
Expand Down
99 changes: 86 additions & 13 deletions math/fuzz/fuzz_targets/u192.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use libfuzzer_sys::fuzz_target;
use num_bigint::{BigInt, BigUint};
use num_integer::Integer;
use num_traits::{Signed, Zero};
use std::convert::TryInto;
use std::{any::type_name, convert::TryInto};
use utils::{assert_same, consume, try_opt};

// This is important as it's a non power of two.
Expand All @@ -32,27 +32,85 @@ where
break (lib2, native2);
}
};
assert_same!(op_lib(lib, lib2), op_native(native, native2), "lib: {lib}, lib2: {lib2}");
assert_same!(op_lib(lib, lib2), op_native(native, native2), "lib: {lib}, lib2: {lib2}, op: {}", type_name::<T>());
Some(())
}

#[track_caller]
fn assert_op_with_overflow<T, U>(data: &mut &[u8], op_lib: T, op_native: U, ok_by_zero: bool) -> Option<()>
where
T: Fn(Uint192, Uint192) -> (Uint192, bool),
U: Fn(&BigUint, &BigUint) -> (BigUint, bool),
{
let (lib, native) = generate_ints(data)?;
let (lib2, native2) = loop {
let (lib2, native2) = generate_ints(data)?;
if ok_by_zero || !native2.is_zero() {
break (lib2, native2);
}
};
let (lib_result, lib_overflow) = op_lib(lib, lib2);
let (native_result, native_overflow) = op_native(&native, &native2);
assert_same!(lib_result, native_result, "native: {native}, native2: {native2}, op: {}", type_name::<T>());
assert_eq!(lib_overflow, native_overflow, "native: {native}, native2: {native2}, op: {}", type_name::<T>());
Some(())
}

fn biguint_overflowing_op<F, A, B>(a: A, b: B, mask: &BigUint, op: F) -> (BigUint, bool)
where
F: Fn(A, B) -> BigUint,
{
let result = op(a, b);
let masked = &result & mask;
let overflow = result != masked;
(masked, overflow)
}

fn overflowing_sub_bigint(a: &BigUint, b: &BigUint, mask: &BigUint) -> (BigUint, bool) {
if a >= b {
(a - b, false)
} else {
(mask - b + a + 1u8, true)
}
}
/// Shifts self left by rhs bits.
/// Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits.
/// If the shift value is too large, then value is reduced modulo the number of bits, and this value is then used to perform the shift. (for powers of 2 this is equivalent to masking the shift value by (N-1) where N is the number of bits).
fn overflowing_shl_bigint(a: &BigUint, b: u32, mask: &BigUint) -> (BigUint, bool) {
let overflow = b >= 192;
let b = b % 192; // mask shift to be within bounds
((a << b) & mask, overflow)
}

/// Shifts self right by rhs bits.
/// Returns a tuple of the shifted version of self along with a boolean indicating whether the shift value was larger than or equal to the number of bits.
/// If the shift value is too large, then value is reduced modulo the number of bits, and this value is then used to perform the shift. (for powers of 2 this is equivalent to masking the shift value by (N-1) where N is the number of bits, but for non-powers of two we need to do an actual modulo).
fn overflowing_shr_bigint(a: &BigUint, b: u32) -> (BigUint, bool) {
let overflow = b >= 192;
let b = b % 192; // mask shift to be within bounds
(a >> b, overflow)
}

fuzz_target!(|data: &[u8]| {
let mut data = data;
// from_le_bytes
{
let (lib, native) = try_opt!(generate_ints(&mut data));
assert_same!(lib, native, "lib: {lib}");
}
let modulo = &BigUint::from_bytes_le(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]);

// Full addition
assert_op(&mut data, Add::add, |a, b| (a + b) % modulo, true);
// Full multiplication
assert_op(&mut data, Mul::mul, |a, b| (a * b) % modulo, true);
let mask = &BigUint::from_bytes_le(&[u8::MAX; 24]);

// Full Overflow addition
assert_op_with_overflow(&mut data, Uint192::overflowing_add, |a, b| biguint_overflowing_op(a, b, mask, Add::add), true);
// Full Overflow multiplication
assert_op_with_overflow(&mut data, Uint192::overflowing_mul, |a, b| biguint_overflowing_op(a, b, mask, Mul::mul), true);
// Full Overflow subtraction
assert_op_with_overflow(&mut data, Uint192::overflowing_sub, |a, b| overflowing_sub_bigint(a, b, mask), true);
// Full division
assert_op(&mut data, Div::div, |a, b| (a / b) % modulo, false);
assert_op(&mut data, Div::div, |a, b| (a / b) & mask, false);
// Full remainder
assert_op(&mut data, Rem::rem, |a, b| (a % b) % modulo, false);
assert_op(&mut data, Rem::rem, |a, b| (a % b) & mask, false);
// Full bitwise And
assert_op(&mut data, BitAnd::bitand, BitAnd::bitand, true);
// Full bitwise Or
Expand All @@ -64,25 +122,40 @@ fuzz_target!(|data: &[u8]| {
{
let (lib, native) = try_opt!(generate_ints(&mut data));
let word = u64::from_le_bytes(try_opt!(consume(&mut data)));
assert_same!(lib + word, (native + word) % modulo, "lib: {lib}, word: {word}");
let (lib_result, lib_overflow) = lib.overflowing_add_u64(word);
let (native_result, native_overflow) = biguint_overflowing_op(native, word, mask, Add::add);
assert_same!(lib_result, native_result, "lib: {lib}, word: {word}");
assert_eq!(lib_overflow, native_overflow, "lib: {lib}, word: {word}");
}
// U64 multiplication
{
let (lib, native) = try_opt!(generate_ints(&mut data));
let word = u64::from_le_bytes(try_opt!(consume(&mut data)));
assert_same!(lib * word, (native * word) % modulo, "lib: {lib}, word: {word}");
let (lib_result, lib_overflow) = lib.overflowing_mul_u64(word);
let (native_result, native_overflow) = biguint_overflowing_op(native, word, mask, Mul::mul);
assert_same!(lib_result, native_result, "lib: {lib}, word: {word}");
assert_eq!(lib_overflow, native_overflow, "lib: {lib}, word: {word}");
}
// Left shift
{
let (lib, native) = try_opt!(generate_ints(&mut data));
let lshift = u32::from(u16::from_le_bytes(try_opt!(consume(&mut data))) % 192);
assert_same!(lib << lshift, (native << lshift) % modulo, "lib: {lib}, lshift: {lshift}");

let (native_res, native_overflow) = overflowing_shl_bigint(&native, lshift, mask);
let (lib_res, lib_overflow) = lib.overflowing_shl(lshift);
assert_same!(lib_res, native_res, "native: {native}, lshift: {lshift}");
assert_eq!(lib_overflow, native_overflow, "native: {native}, lshift: {lshift}");
}
// Right shift
{
let (lib, native) = try_opt!(generate_ints(&mut data));
let rshift = u32::from(u16::from_le_bytes(try_opt!(consume(&mut data))) % 192);
assert_same!(lib >> rshift, (native >> rshift) % modulo, "lib: {lib}, rshift: {rshift}");
assert_same!(lib >> rshift, (&native >> rshift) & mask, "lib: {lib}, rshift: {rshift}");

let (native_res, native_overflow) = overflowing_shr_bigint(&native, rshift);
let (lib_res, lib_overflow) = lib.overflowing_shr(rshift);
assert_same!(lib_res, native_res, "native: {native}, rshift: {rshift}");
assert_eq!(lib_overflow, native_overflow, "native: {native}, rshift: {rshift}");
}
// bits
{
Expand Down
Loading