Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ void FuzzerContext::set_existing_note_hashes(std::span<const std::pair<FF, uint6
existing_note_hashes_.assign(note_hashes.begin(), note_hashes.end());
}

void FuzzerContext::set_existing_contract_addresses(std::span<const AztecAddress> contract_addresses)
{
contract_addresses_.assign(contract_addresses.begin(), contract_addresses.end());
}

void FuzzerContext::reset()
{
contract_addresses_.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class FuzzerContext {

void set_existing_note_hashes(std::span<const std::pair<FF, uint64_t>> note_hashes);

void set_existing_contract_addresses(std::span<const FF> contract_addresses);

private:
std::vector<FF> contract_addresses_;
std::unique_ptr<FuzzerContractDB> contract_db_;
Expand Down
25 changes: 10 additions & 15 deletions barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,12 +322,6 @@ size_t mutate_tx_data(FuzzerContext& context,

populate_context_from_tx_data(context, tx_data);

// Mutate the fuzzer data multiple times for better bytecode variety
auto num_mutations = std::uniform_int_distribution<uint8_t>(1, 5)(rng);
for (uint8_t i = 0; i < num_mutations; i++) {
mutate_fuzzer_data_vec(context, tx_data.input_programs, rng, 64);
}

// Build up bytecodes, contract classes and instances from the fuzzer data
tx_data.contract_classes.clear();
tx_data.contract_instances.clear();
Expand Down Expand Up @@ -357,8 +351,8 @@ size_t mutate_tx_data(FuzzerContext& context,

// Ensure all enqueued calls have valid contract addresses (not placeholders)
// We may add more advanced mutation to change contract addresses later, right now we just ensure they are valid
auto idx_dist = std::uniform_int_distribution<size_t>(0, contract_addresses.size() - 1);
if (!contract_addresses.empty()) {
auto idx_dist = std::uniform_int_distribution<size_t>(0, contract_addresses.size() - 1);
for (auto& call : tx_data.tx.setup_enqueued_calls) {
call.request.contract_address = contract_addresses[idx_dist(rng)];
}
Expand All @@ -371,23 +365,22 @@ size_t mutate_tx_data(FuzzerContext& context,
FuzzerTxDataMutationType mutation_choice = FUZZER_TX_DATA_MUTATION_CONFIGURATION.select(rng);

switch (mutation_choice) {
case FuzzerTxDataMutationType::TxFuzzerDataMutation:
mutate_fuzzer_data_vec(context, tx_data.input_programs, rng, 64);
break;
case FuzzerTxDataMutationType::TxMutation:
mutate_tx(tx_data.tx, contract_addresses, rng);
break;
case FuzzerTxDataMutationType::BytecodeMutation: {
// Mutate bytecode and append public data writes for world state setup
case FuzzerTxDataMutationType::BytecodeMutation:
mutate_bytecode(tx_data.contract_classes,
tx_data.contract_instances,
tx_data.contract_addresses,
tx_data.public_data_writes,
rng);
break;
}

case FuzzerTxDataMutationType::ContractClassMutation:
mutate_contract_classes(tx_data.contract_classes, tx_data.contract_instances, tx_data.contract_addresses, rng);
// The fuzzer (like the AVM) assumes that all triplets of contract classes, instances and addresses are in sync
// So when we mutate contract classes, we also need to update the corresponding artifacts

break;
case FuzzerTxDataMutationType::ContractInstanceMutation:
mutate_contract_instances(tx_data.contract_instances, tx_data.contract_addresses, rng);
Expand All @@ -413,8 +406,8 @@ size_t mutate_tx_data(FuzzerContext& context,
}
}

// todo: do we need to ensure this or are should we able to process 0 enqueued calls?
// Ensure at least 1 app_logic enqueued call exists (mutations may have deleted all)
// Ensure at least 1 app_logic enqueued call exists (mutations may have deleted all), the public base kernel circuit
// guarantees that there is at least 1 enqueued call in an public tx (so this is a valid assumption)
if (tx_data.tx.app_logic_enqueued_calls.empty() && !contract_addresses.empty()) {
auto idx = std::uniform_int_distribution<size_t>(0, contract_addresses.size() - 1)(rng);
std::vector<FF> calldata = {};
Expand Down Expand Up @@ -473,4 +466,6 @@ void populate_context_from_tx_data(FuzzerContext& context, const FuzzerTxData& t
}

context.set_existing_note_hashes(note_hash_leaf_index_pairs);

context.set_existing_contract_addresses(tx_data.contract_addresses);
}
4 changes: 3 additions & 1 deletion barretenberg/cpp/src/barretenberg/avm_fuzzer/fuzzer_lib.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ using FuzzerContext = bb::avm2::fuzzer::FuzzerContext;

// Mutation configuration
enum class FuzzerTxDataMutationType : uint8_t {
TxFuzzerDataMutation,
TxMutation,
BytecodeMutation,
ContractClassMutation,
Expand All @@ -69,9 +70,10 @@ enum class FuzzerTxDataMutationType : uint8_t {
ProtocolContractsMutation
};

using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 6>;
using FuzzerTxDataMutationConfig = WeightedSelectionConfig<FuzzerTxDataMutationType, 7>;

constexpr FuzzerTxDataMutationConfig FUZZER_TX_DATA_MUTATION_CONFIGURATION = FuzzerTxDataMutationConfig({
{ FuzzerTxDataMutationType::TxFuzzerDataMutation, 20 },
{ FuzzerTxDataMutationType::TxMutation, 10 },
{ FuzzerTxDataMutationType::BytecodeMutation, 1 },
{ FuzzerTxDataMutationType::ContractClassMutation, 1 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/// 2. Increment by 1
/// 3. Decrement by 1
/// 4. Add a random value
/// 5. Boundary selection (pick from curated edge-case values)

#include "barretenberg/avm_fuzzer/mutations/basic_types/field.hpp"

Expand All @@ -17,6 +18,7 @@
#include "barretenberg/avm_fuzzer/mutations/basic_types/uint128_t.hpp"
#include "barretenberg/avm_fuzzer/mutations/configuration.hpp"
#include "barretenberg/numeric/random/engine.hpp"
#include "barretenberg/numeric/uint256/uint256.hpp"
#include "barretenberg/vm2/common/field.hpp"

using FF = bb::field<bb::Bn254FrParams>;
Expand Down Expand Up @@ -51,6 +53,49 @@ struct AddRandomValue {
static void mutate(bb::avm2::FF& value, std::mt19937_64& rng) { value = value + generate_random_field(rng); }
};

// BN254 scalar field (bn254::fr) boundary values
// Modulus p = 21888242871839275222246405745257275088548364400416034343698204186575808495617
// 4-limb Montgomery representation (64 bits per limb)
// These values exercise:
// - Zero/small values
// - Values near modulus p (exercise modular reduction)
// - Limb boundaries (4 x 64-bit limbs)
// - Values that cause reduction when doubled
// clang-format off
static const std::vector<FF> FIELD_BOUNDARY_VALUES = {
// === Zero/small values ===
FF::zero(), // 0
FF::one(), // 1
FF(2), // 2

// === Values near modulus p (exercise modular reduction) ===
FF::neg_one(), // p - 1 (max field element)
FF::neg_one() - FF::one(), // p - 2
FF::neg_one() - FF(2), // p - 3
FF::neg_one() / FF(2), // (p - 1) / 2 (midpoint)
FF::neg_one() / FF(2) + FF::one(), // (p + 1) / 2

// === Limb boundaries (4 x 64-bit limbs) ===
FF(uint256_t(1) << 64), // 2^64 (limb 0/1 boundary)
FF(uint256_t(1) << 128), // 2^128 (limb 1/2 boundary)
FF(uint256_t(1) << 192), // 2^192 (limb 2/3 boundary)
FF((uint256_t(1) << 64) - 1), // 2^64 - 1 (limb 0 full)
FF((uint256_t(1) << 128) - 1), // 2^128 - 1 (limbs 0-1 full)
FF((uint256_t(1) << 192) - 1), // 2^192 - 1 (limbs 0-2 full)

// === Montgomery form edge cases ===
FF(uint256_t(1) << 253), // 2^253 (near R boundary, < p)
};
// clang-format on

/// @brief Select from curated boundary values
struct BoundarySelection {
static void mutate(std::mt19937_64& rng, FF& value)
{
value = FIELD_BOUNDARY_VALUES[std::uniform_int_distribution<size_t>(0, FIELD_BOUNDARY_VALUES.size() - 1)(rng)];
}
};

void mutate_field(bb::avm2::FF& value, std::mt19937_64& rng, const FieldMutationConfig& config)
{
FieldMutationOptions option = config.select(rng);
Expand All @@ -68,5 +113,8 @@ void mutate_field(bb::avm2::FF& value, std::mt19937_64& rng, const FieldMutation
case FieldMutationOptions::AddRandomValue:
AddRandomValue::mutate(value, rng);
break;
case FieldMutationOptions::BoundarySelection:
BoundarySelection::mutate(rng, value);
break;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/// 2. Increment by 1
/// 3. Decrement by 1
/// 4. Add a random value
/// 5. Boundary selection (pick from curated edge-case values)

#pragma once

Expand Down Expand Up @@ -47,6 +48,94 @@ template <> struct UintTraits<uint128_t> {
static constexpr bool has_mask = false;
};

// BoundaryValues: curated sets of edge-case values for each uint type
// These values exercise:
// - Zero/one edge cases
// - Power-of-2 midpoints (which trigger different code paths in multi-limb arithmetic)
// - Maximum value overflow detection
// - Cross-type boundaries (e.g., U8 max within U16)
template <typename T> struct BoundaryValues;

template <> struct BoundaryValues<uint8_t> {
static constexpr std::array<uint8_t, 8> values = {
0, // Zero
1, // One
2, // Small value
127, // 2^7 - 1 (max value with high bit clear)
128, // 2^7 (midpoint, high bit set)
254, // Max - 1
255, // Max (2^8 - 1)
0x55, // Alternating bits (01010101)
};
};

template <> struct BoundaryValues<uint16_t> {
static constexpr std::array<uint16_t, 10> values = {
0, 1, 2,
255, // U8 max (cross-type boundary)
256, // U8 max + 1
32767, // 2^15 - 1 (max with high bit clear)
32768, // 2^15 (midpoint, high bit set)
65534, // Max - 1
65535, // Max (2^16 - 1)
0x5555, // Alternating bits
};
};

template <> struct BoundaryValues<uint32_t> {
static constexpr std::array<uint32_t, 12> values = {
0, 1, 2, 255,
256, // U8 boundaries
65535,
65536, // U16 boundaries
0x7FFFFFFF, // 2^31 - 1 (max with high bit clear)
0x80000000, // 2^31 (midpoint, high bit set)
0xFFFFFFFE, // Max - 1
0xFFFFFFFF, // Max (2^32 - 1)
0x55555555, // Alternating bits
};
};

template <> struct BoundaryValues<uint64_t> {
static constexpr std::array<uint64_t, 14> values = {
0,
1,
2,
0xFF,
0x100, // U8 boundaries
0xFFFF,
0x10000, // U16 boundaries
0xFFFFFFFF, // U32 max
0x100000000, // U32 max + 1 (exercises carry into high word)
0x7FFFFFFFFFFFFFFF, // 2^63 - 1 (max with high bit clear)
0x8000000000000000, // 2^63 (midpoint, high bit set)
0xFFFFFFFFFFFFFFFE, // Max - 1
0xFFFFFFFFFFFFFFFF, // Max (2^64 - 1)
0x5555555555555555, // Alternating bits
};
};

template <> struct BoundaryValues<uint128_t> {
// Critical for multi-limb arithmetic: values at limb boundaries
// U128 is stored as two 64-bit limbs, so 2^64 boundary is crucial
static inline const std::array<uint128_t, 14> values = {
uint128_t(0),
uint128_t(1),
uint128_t(2),
uint128_t(0xFFFFFFFFFFFFFFFFULL), // U64 max (low limb full)
uint128_t(0xFFFFFFFFFFFFFFFFULL) + 1, // 2^64 (carry into high limb)
uint128_t(1) << 64, // 2^64 (high limb = 1)
(uint128_t(0x7FFFFFFFFFFFFFFFULL) << 64) | 0xFFFFFFFFFFFFFFFFULL, // 2^127 - 1
uint128_t(1) << 127, // 2^127 (midpoint)
(uint128_t(0xFFFFFFFFFFFFFFFFULL) << 64) | 0xFFFFFFFFFFFFFFFEULL, // Max - 1
(uint128_t(0xFFFFFFFFFFFFFFFFULL) << 64) | 0xFFFFFFFFFFFFFFFFULL, // Max (2^128 - 1)
uint128_t(1) << 96, // 2^96 (3/4 point)
(uint128_t(1) << 96) - 1, // 2^96 - 1
uint128_t(1) << 63, // 2^63 (quarter point)
(uint128_t(0x5555555555555555ULL) << 64) | 0x5555555555555555ULL, // Alternating bits
};
};

template <typename T>
typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value, T>::type generate_random_uint(
std::mt19937_64& rng)
Expand Down Expand Up @@ -99,6 +188,14 @@ template <typename T> struct AddRandomValue {
}
}
};

template <typename T> struct BoundarySelection {
static void mutate(std::mt19937_64& rng, T& value)
{
const auto& bounds = BoundaryValues<T>::values;
value = bounds[std::uniform_int_distribution<size_t>(0, bounds.size() - 1)(rng)];
}
};
} // namespace uint_mutation

// Generic mutation function using WeightedSelectionConfig
Expand All @@ -119,6 +216,9 @@ template <typename T, typename ConfigType> void mutate_uint(T& value, std::mt199
case UintMutationOptions::AddRandomValue:
uint_mutation::AddRandomValue<T>::mutate(value, rng);
break;
case UintMutationOptions::BoundarySelection:
uint_mutation::BoundarySelection<T>::mutate(rng, value);
break;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ constexpr VecMutationConfig BASIC_VEC_MUTATION_CONFIGURATION = VecMutationConfig
});

// Generic uint mutation options (used by all uint types)
enum class UintMutationOptions { RandomSelection, IncrementBy1, DecrementBy1, AddRandomValue };
// BoundarySelection picks from a curated set of edge-case values (0, 1, max, midpoint, etc.)
// to dramatically improve coverage of boundary conditions in arithmetic operations
enum class UintMutationOptions { RandomSelection, IncrementBy1, DecrementBy1, AddRandomValue, BoundarySelection };

// Type aliases for backward compatibility
using Uint8MutationOptions = UintMutationOptions;
Expand All @@ -38,37 +40,44 @@ constexpr Uint8MutationConfig BASIC_UINT8_T_MUTATION_CONFIGURATION = Uint8Mutati
{ UintMutationOptions::IncrementBy1, 22 },
{ UintMutationOptions::DecrementBy1, 20 },
{ UintMutationOptions::AddRandomValue, 10 },
{ UintMutationOptions::BoundarySelection, 25 }, // ~30% - picks boundary values (0, 1, max, midpoint, etc.)
});

constexpr Uint16MutationConfig BASIC_UINT16_T_MUTATION_CONFIGURATION = Uint16MutationConfig({
{ UintMutationOptions::RandomSelection, 7 },
{ UintMutationOptions::IncrementBy1, 22 },
{ UintMutationOptions::DecrementBy1, 20 },
{ UintMutationOptions::AddRandomValue, 10 },
{ UintMutationOptions::BoundarySelection, 25 }, // ~30% - picks boundary values (0, 1, max, midpoint, etc.)
});

constexpr Uint32MutationConfig BASIC_UINT32_T_MUTATION_CONFIGURATION = Uint32MutationConfig({
{ UintMutationOptions::RandomSelection, 7 },
{ UintMutationOptions::IncrementBy1, 22 },
{ UintMutationOptions::DecrementBy1, 20 },
{ UintMutationOptions::AddRandomValue, 10 },
{ UintMutationOptions::BoundarySelection, 25 }, // ~30% - picks boundary values (0, 1, max, midpoint, etc.)
});

constexpr Uint64MutationConfig BASIC_UINT64_T_MUTATION_CONFIGURATION = Uint64MutationConfig({
{ UintMutationOptions::RandomSelection, 7 },
{ UintMutationOptions::IncrementBy1, 22 },
{ UintMutationOptions::DecrementBy1, 20 },
{ UintMutationOptions::AddRandomValue, 10 },
{ UintMutationOptions::BoundarySelection, 25 }, // ~30% - picks boundary values (0, 1, max, midpoint, etc.)
});

constexpr Uint128MutationConfig BASIC_UINT128_T_MUTATION_CONFIGURATION = Uint128MutationConfig({
{ UintMutationOptions::RandomSelection, 7 },
{ UintMutationOptions::IncrementBy1, 22 },
{ UintMutationOptions::DecrementBy1, 20 },
{ UintMutationOptions::AddRandomValue, 10 },
{ UintMutationOptions::BoundarySelection, 25 }, // ~30% - picks boundary values (0, 1, max, midpoint, etc.)
});

enum class FieldMutationOptions { RandomSelection, IncrementBy1, DecrementBy1, AddRandomValue };
// BoundarySelection picks from a curated set of edge-case values (0, 1, p-1, limb boundaries, etc.)
// to dramatically improve coverage of boundary conditions in field arithmetic
enum class FieldMutationOptions { RandomSelection, IncrementBy1, DecrementBy1, AddRandomValue, BoundarySelection };

using FieldMutationConfig = WeightedSelectionConfig<FieldMutationOptions, 5>;

Expand All @@ -77,6 +86,7 @@ constexpr FieldMutationConfig BASIC_FIELD_MUTATION_CONFIGURATION = FieldMutation
{ FieldMutationOptions::IncrementBy1, 22 },
{ FieldMutationOptions::DecrementBy1, 20 },
{ FieldMutationOptions::AddRandomValue, 10 },
{ FieldMutationOptions::BoundarySelection, 25 }, // ~30% - picks boundary values (0, 1, p-1, limb boundaries)
});

enum class MemoryTagOptions { U1, U8, U16, U32, U64, U128, FF };
Expand Down
Loading
Loading