Skip to content
Merged
21 changes: 11 additions & 10 deletions barretenberg/cpp/scripts/audit/audit_scopes/numeric_audit_scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,23 @@ Note: Paths relative to `aztec-packages/barretenberg/cpp/src/barretenberg`
3. `numeric/bitop/keep_n_lsb.hpp`
4. `numeric/bitop/pow.hpp`
5. `numeric/bitop/rotate.hpp`
6. `numeric/bitop/sparse_form.hpp`

### Random Number Generation
6. `numeric/random/engine.cpp`
7. `numeric/random/engine.hpp`
7. `numeric/random/engine.cpp`
8. `numeric/random/engine.hpp`

### Unsigned Integer Types
8. `numeric/uint128/uint128.hpp`
9. `numeric/uint128/uint128_impl.hpp`
10. `numeric/uint256/uint256.hpp`
11. `numeric/uint256/uint256_impl.hpp`
12. `numeric/uintx/uintx.cpp`
13. `numeric/uintx/uintx.hpp`
14. `numeric/uintx/uintx_impl.hpp`
9. `numeric/uint128/uint128.hpp`
10. `numeric/uint128/uint128_impl.hpp`
11. `numeric/uint256/uint256.hpp`
12. `numeric/uint256/uint256_impl.hpp`
13. `numeric/uintx/uintx.cpp`
14. `numeric/uintx/uintx.hpp`
15. `numeric/uintx/uintx_impl.hpp`

### General Utilities
15. `numeric/general/general.hpp`
16. `numeric/general/general.hpp`

## Summary of Module

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down Expand Up @@ -35,7 +35,10 @@ template <> constexpr inline size_t count_leading_zeros<uint128_t>(uint128_t con
return static_cast<size_t>(__builtin_clzll(hi));
}
auto lo = static_cast<uint64_t>(u);
return static_cast<size_t>(__builtin_clzll(lo)) + 64;
if (lo != 0U) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its technically undefined behavior to call __builtin_clzll on 0 so adding this extra protection (matches what we do for u256 below)

return static_cast<size_t>(__builtin_clzll(lo)) + 64;
}
return 128;
}

template <> constexpr inline size_t count_leading_zeros<uint256_t>(uint256_t const& u)
Expand Down
15 changes: 13 additions & 2 deletions barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================

#pragma once
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
namespace bb::numeric {

// from http://supertech.csail.mit.edu/papers/debruijn.pdf
Expand Down Expand Up @@ -72,8 +74,17 @@ template <typename T> constexpr inline T get_lsb(const T in)

template <typename T> constexpr inline T round_up_power_2(const T in)
{
if (in == 0) {
return 0;
}
auto lower_bound = T(1) << get_msb(in);
return (lower_bound == in || lower_bound == 1) ? in : lower_bound * 2;
if (lower_bound == in) {
return in;
}
// Overflow check: lower_bound is the highest power of 2 <= in,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably overly cautious but avoids wrap in lower_bound * 2 if input is > numeric_limits/2

// so lower_bound * 2 would overflow if lower_bound is already the top bit.
assert(lower_bound <= (std::numeric_limits<T>::max() >> 1));
return lower_bound * 2;
}

} // namespace bb::numeric
94 changes: 94 additions & 0 deletions barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,97 @@ TEST(bitop, GetMsbSizeT7)
auto r = numeric::get_msb(a);
EXPECT_EQ(r, 7U);
}

// Verify De Bruijn lookup tables by testing every bit position with multiple input patterns
TEST(bitop, GetMsbUint32AllPositions)
{
for (uint32_t i = 0; i < 32; i++) {
// Power of 2: exactly one bit set
EXPECT_EQ(numeric::get_msb(uint32_t(1U << i)), i);
// All bits set up to position i (exercises the post-smearing pattern)
uint32_t all_ones = (i == 31) ? 0xFFFFFFFF : ((1U << (i + 1)) - 1);
EXPECT_EQ(numeric::get_msb(all_ones), i);
// MSB set plus random low bit
if (i > 0) {
EXPECT_EQ(numeric::get_msb(uint32_t((1U << i) | 1U)), i);
}
}
}

TEST(bitop, GetMsbUint64AllPositions)
{
for (uint64_t i = 0; i < 64; i++) {
// Power of 2
EXPECT_EQ(numeric::get_msb(uint64_t(1ULL << i)), i);
// All bits set up to position i
uint64_t all_ones = (i == 63) ? 0xFFFFFFFFFFFFFFFFULL : ((1ULL << (i + 1)) - 1);
EXPECT_EQ(numeric::get_msb(all_ones), i);
// MSB set plus low bit
if (i > 0) {
EXPECT_EQ(numeric::get_msb(uint64_t((1ULL << i) | 1ULL)), i);
}
}
}

// get_lsb tests
TEST(bitop, GetLsbZero)
{
EXPECT_EQ(numeric::get_lsb(uint32_t(0)), 0U);
EXPECT_EQ(numeric::get_lsb(uint64_t(0)), 0U);
}

TEST(bitop, GetLsbOne)
{
EXPECT_EQ(numeric::get_lsb(uint32_t(1)), 0U);
EXPECT_EQ(numeric::get_lsb(uint64_t(1)), 0U);
}

TEST(bitop, GetLsbPowersOfTwo)
{
for (uint32_t i = 0; i < 32; i++) {
EXPECT_EQ(numeric::get_lsb(uint32_t(1U << i)), i);
}
for (uint64_t i = 0; i < 64; i++) {
EXPECT_EQ(numeric::get_lsb(uint64_t(1ULL << i)), i);
}
}

TEST(bitop, GetLsbComposite)
{
// LSB of 0b1100 is bit 2
EXPECT_EQ(numeric::get_lsb(uint32_t(0b1100)), 2U);
// LSB of 0xFF00 is bit 8
EXPECT_EQ(numeric::get_lsb(uint64_t(0xFF00)), 8U);
}

// round_up_power_2 tests
TEST(bitop, RoundUpPower2Zero)
{
EXPECT_EQ(numeric::round_up_power_2(uint32_t(0)), 0U);
EXPECT_EQ(numeric::round_up_power_2(uint64_t(0)), 0ULL);
}

TEST(bitop, RoundUpPower2PowersOfTwo)
{
// Powers of two should be returned unchanged
for (uint32_t i = 0; i < 31; i++) {
uint32_t val = 1U << i;
EXPECT_EQ(numeric::round_up_power_2(val), val);
}
}

TEST(bitop, RoundUpPower2NonPowers)
{
EXPECT_EQ(numeric::round_up_power_2(uint32_t(3)), 4U);
EXPECT_EQ(numeric::round_up_power_2(uint32_t(5)), 8U);
EXPECT_EQ(numeric::round_up_power_2(uint32_t(7)), 8U);
EXPECT_EQ(numeric::round_up_power_2(uint32_t(9)), 16U);
EXPECT_EQ(numeric::round_up_power_2(uint32_t(100)), 128U);
EXPECT_EQ(numeric::round_up_power_2(uint64_t(1000)), 1024ULL);
}

TEST(bitop, RoundUpPower2LargestValid)
{
// Largest non-power-of-2 that doesn't overflow: 2^30 + 1 -> 2^31
EXPECT_EQ(numeric::round_up_power_2(uint32_t((1U << 30) + 1)), 1U << 31);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down
2 changes: 1 addition & 1 deletion barretenberg/cpp/src/barretenberg/numeric/bitop/pow.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down
2 changes: 1 addition & 1 deletion barretenberg/cpp/src/barretenberg/numeric/bitop/rotate.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down
35 changes: 35 additions & 0 deletions barretenberg/cpp/src/barretenberg/numeric/bitop/sparse_form.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// =====================

#pragma once
#include "barretenberg/common/assert.hpp"
#include "barretenberg/common/throw_or_abort.hpp"
#include <array>
#include <cstddef>
Expand All @@ -15,8 +16,13 @@

namespace bb::numeric {

/**
* @brief Decompose a uint256_t into digits in the given base (least-significant digit first).
* If num_slices > 0, returns exactly that many digits. If num_slices == 0, returns as many as needed.
*/
inline std::vector<uint64_t> slice_input(const uint256_t& input, const uint64_t base, const size_t num_slices)
{
BB_ASSERT(base > 0);
uint256_t target = input;
std::vector<uint64_t> slices;
if (num_slices > 0) {
Expand All @@ -33,12 +39,17 @@ inline std::vector<uint64_t> slice_input(const uint256_t& input, const uint64_t
return slices;
}

/**
* @brief Decompose a uint256_t using a different base for each digit position (least-significant first).
* Throws if the input is too large to be fully represented by the given bases.
*/
inline std::vector<uint64_t> slice_input_using_variable_bases(const uint256_t& input,
const std::vector<uint64_t>& bases)
{
uint256_t target = input;
std::vector<uint64_t> slices;
for (size_t i = 0; i < bases.size(); ++i) {
BB_ASSERT(bases[i] > 0);
if (target >= bases[i] && i == bases.size() - 1) {
throw_or_abort(format("Last key slice greater than ", bases[i]));
}
Expand All @@ -48,6 +59,9 @@ inline std::vector<uint64_t> slice_input_using_variable_bases(const uint256_t& i
return slices;
}

/**
* @brief Compute [1, base, base^2, ..., base^(num_slices-1)] as uint256_t values.
*/
template <uint64_t base, uint64_t num_slices> constexpr std::array<uint256_t, num_slices> get_base_powers()
{
std::array<uint256_t, num_slices> output{};
Expand All @@ -58,6 +72,11 @@ template <uint64_t base, uint64_t num_slices> constexpr std::array<uint256_t, nu
return output;
}

/**
* @brief Encode a 32-bit value into sparse form: each binary bit of input becomes a digit in the given base.
* E.g. with base=3, binary 0b101 becomes 1*3^2 + 0*3^1 + 1*3^0 = 10.
* Used by plookup tables (SHA256, Keccak, AES, Blake2s) to encode XOR/AND via lookup-friendly representations.
*/
template <uint64_t base> constexpr uint256_t map_into_sparse_form(const uint64_t input)
{
uint256_t out = 0UL;
Expand All @@ -73,6 +92,11 @@ template <uint64_t base> constexpr uint256_t map_into_sparse_form(const uint64_t
return out;
}

/**
* @brief Decode a sparse-form uint256_t back to a 32-bit value.
* Extracts the base-adic digits from most-significant to least-significant, and recovers the original
* binary value by reading the low bit of each digit.
*/
template <uint64_t base> constexpr uint64_t map_from_sparse_form(const uint256_t& input)
{
uint256_t target = input;
Expand Down Expand Up @@ -102,6 +126,12 @@ template <uint64_t base> constexpr uint64_t map_from_sparse_form(const uint256_t
return output;
}

/**
* @brief Integer type that stores each bit as a separate digit in the given base.
* Supports addition with single-pass carry propagation. Used to build plookup tables
* for bitwise operations (XOR, AND) where two sparse_ints are added and the resulting
* per-digit values encode the operation's truth table.
*/
template <uint64_t base, size_t num_bits> class sparse_int {
public:
sparse_int(const uint64_t input = 0)
Expand All @@ -118,6 +148,8 @@ template <uint64_t base, size_t num_bits> class sparse_int {
sparse_int& operator=(sparse_int&& other) noexcept = default;
~sparse_int() noexcept = default;

// Single-pass carry propagation: correct when all input limbs are < base, which is guaranteed
// by the constructor (limbs are 0 or 1) and maintained by this operator (carry produces values < base).
sparse_int operator+(const sparse_int& other) const
{
sparse_int result(*this);
Expand All @@ -126,6 +158,9 @@ template <uint64_t base, size_t num_bits> class sparse_int {
if (result.limbs[i] >= base) {
result.limbs[i] -= base;
++result.limbs[i + 1];
// After carry: result.limbs[i] < base (since both inputs were < base, sum < 2*base,
// so subtracting base gives a value < base). The carry of 1 into limbs[i+1] cannot
// cascade because limbs[i+1] hasn't been added to other.limbs[i+1] yet.
}
}
result.limbs[num_bits - 1] += other.limbs[num_bits - 1];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down
9 changes: 6 additions & 3 deletions barretenberg/cpp/src/barretenberg/numeric/uint128/uint128.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down Expand Up @@ -37,12 +37,15 @@ class alignas(32) uint128_t {
return { static_cast<uint32_t>(a), static_cast<uint32_t>(a >> 32), 0, 0 };
}

constexpr explicit operator uint64_t() { return (static_cast<uint64_t>(data[1]) << 32) + data[0]; }
constexpr explicit operator uint64_t() const { return (static_cast<uint64_t>(data[1]) << 32) + data[0]; }

constexpr uint128_t& operator=(const uint128_t& other) = default;
constexpr uint128_t& operator=(uint128_t&& other) = default;
constexpr ~uint128_t() = default;
explicit constexpr operator bool() const { return static_cast<bool>(data[0]); };
explicit constexpr operator bool() const
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug: was only checking lowest limb. wasn't ever showing up anywhere because we don't have instances where we directly treat a u128 as a bool

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe worth documenting that it converts any non-zero value to 1?

{
return (data[0] != 0) || (data[1] != 0) || (data[2] != 0) || (data[3] != 0);
};

template <std::integral T> explicit constexpr operator T() const { return static_cast<T>(data[0]); };

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down
9 changes: 6 additions & 3 deletions barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// === AUDIT STATUS ===
// internal: { status: Planned, auditors: [], commit: }
// internal: { status: Complete, auditors: [Luke], commit: }
// external_1: { status: not started, auditors: [], commit: }
// external_2: { status: not started, auditors: [], commit: }
// =====================
Expand Down Expand Up @@ -109,9 +109,12 @@ class alignas(32) uint256_t {
constexpr uint256_t& operator=(uint256_t&& other) noexcept = default;
constexpr ~uint256_t() noexcept = default;

explicit constexpr operator bool() const { return static_cast<bool>(data[0]); };
explicit constexpr operator bool() const
{
return (data[0] != 0) || (data[1] != 0) || (data[2] != 0) || (data[3] != 0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also here

};

constexpr explicit operator uint128_t() { return (static_cast<uint128_t>(data[1]) << 64) + data[0]; }
constexpr explicit operator uint128_t() const { return (static_cast<uint128_t>(data[1]) << 64) + data[0]; }
template <std::integral T> explicit constexpr operator T() const { return static_cast<T>(data[0]); };

[[nodiscard]] constexpr bool get_bit(uint64_t bit_index) const;
Expand Down
Loading
Loading