Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
pippenger_constantine.fuzzer.cpp
Go to the documentation of this file.
1// libFuzzer target for the Constantine signed-Booth window recoder.
2//
3// Two-pronged differential check on each input:
4// 1. Scalar path vs textbook reference oracle — catches encoder algebra bugs.
5// 2. SIMD x4 path vs scalar path (lane-by-lane) — catches lane-mux / mask /
6// vector-shift bugs in the three slice-path specialisations.
7//
8// Input layout: 1 byte window_bits ∈ [2, 18], 1 byte bit_offset ∈ [0, 254],
9// followed by 32 bytes × 4 = 128 bytes of scalar limb material. Total minimum
10// input = 130 bytes; smaller inputs are zero-padded so libFuzzer's empty-seed
11// kickoff still drives the encoder.
12//
13// Run:
14// cmake --preset fuzzing && cmake --build --preset fuzzing --target ecc_pippenger_constantine_fuzzer
15// ./build-fuzzing/bin/ecc_pippenger_constantine_fuzzer -max_total_time=60
16
18
20
21#include <array>
22#include <cstdint>
23#include <cstring>
24
25namespace {
26
28
29constexpr size_t LIMB_BITS_U64 = 64;
30constexpr size_t NUM_LIMBS_U64 = 4;
31constexpr size_t NUM_LIMBS_U32 = 8;
32constexpr size_t MAX_BITS = 256;
33constexpr size_t SCALAR_BYTES = 32;
34
35uint32_t reference_packed_digit(const uint64_t* scalar_data, size_t bit_offset, size_t window_bits)
36{
37 auto bit_at = [&](int64_t i) -> uint64_t {
38 if (i < 0 || static_cast<size_t>(i) >= MAX_BITS) {
39 return 0;
40 }
41 return (scalar_data[static_cast<size_t>(i) / LIMB_BITS_U64] >> (static_cast<size_t>(i) % LIMB_BITS_U64)) &
42 uint64_t{ 1 };
43 };
44 uint32_t raw = 0;
45 for (size_t k = 0; k <= window_bits; ++k) {
46 const int64_t bit_idx = static_cast<int64_t>(bit_offset) + static_cast<int64_t>(k) - 1;
47 raw |= static_cast<uint32_t>(bit_at(bit_idx)) << k;
48 }
49 const uint32_t neg = (raw >> window_bits) & 1U;
50 const uint32_t val_mask = (uint32_t{ 1 } << window_bits) - 1;
51 const uint32_t encode = (raw + 1) >> 1;
52 const uint32_t bucket = ((encode - neg) ^ (uint32_t{ 0 } - neg)) & val_mask;
53 return (neg << 31) | bucket;
54}
55
56uint32_t production_scalar(const uint64_t* scalar_data, size_t bit_offset, size_t window_bits)
57{
58 const auto sp = cnst::compute_constantine_slice_params(bit_offset, window_bits, NUM_LIMBS_U64);
59 return cnst::get_constantine_packed_digit(scalar_data,
60 sp.lo_limb,
61 sp.hi_limb,
62 sp.lo_off,
63 sp.lo_bits,
64 sp.lo_mask,
65 sp.hi_mask,
66 sp.slice_localised_to_one_u64,
67 window_bits);
68}
69
70void production_simd(const std::array<std::array<uint64_t, NUM_LIMBS_U64>, 4>& scalars,
71 size_t bit_offset,
72 size_t window_bits,
73 std::array<uint32_t, 4>& out)
74{
75 const auto sp = cnst::compute_constantine_slice_params_u32(bit_offset, window_bits, NUM_LIMBS_U32);
76 const cnst::SimdU32x4 lo_mask_v{ sp.lo_mask, sp.lo_mask, sp.lo_mask, sp.lo_mask };
77 const cnst::SimdU32x4 hi_mask_v{ sp.hi_mask, sp.hi_mask, sp.hi_mask, sp.hi_mask };
78 const cnst::SimdU32x4 one_v{ 1, 1, 1, 1 };
79 const uint32_t val_mask_scalar = (uint32_t{ 1 } << window_bits) - 1;
80 const cnst::SimdU32x4 val_mask{ val_mask_scalar, val_mask_scalar, val_mask_scalar, val_mask_scalar };
81 const auto* s0 = reinterpret_cast<const uint32_t*>(scalars[0].data());
82 const auto* s1 = reinterpret_cast<const uint32_t*>(scalars[1].data());
83 const auto* s2 = reinterpret_cast<const uint32_t*>(scalars[2].data());
84 const auto* s3 = reinterpret_cast<const uint32_t*>(scalars[3].data());
85 const auto wb_u32 = static_cast<uint32_t>(window_bits);
86
88 case cnst::ConstantineSlicePath::Localised:
90 out.data(), s0, s1, s2, s3, sp.lo_limb, sp.lo_off, lo_mask_v, one_v, val_mask, wb_u32);
91 break;
92 case cnst::ConstantineSlicePath::Bottom:
94 out.data(), s0, s1, s2, s3, sp.hi_limb, sp.lo_bits, hi_mask_v, one_v, val_mask, wb_u32);
95 break;
96 case cnst::ConstantineSlicePath::Boundary:
98 s0,
99 s1,
100 s2,
101 s3,
102 sp.lo_limb,
103 sp.hi_limb,
104 sp.lo_off,
105 sp.lo_bits,
106 lo_mask_v,
107 hi_mask_v,
108 one_v,
109 val_mask,
110 wb_u32);
111 break;
112 }
113}
114
115} // namespace
116
117extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
118{
119 // Pad input to the minimum required length so empty / tiny seeds still
120 // exercise the encoder against zero-extended scalars.
121 constexpr size_t MIN_INPUT = 2 + (SCALAR_BYTES * 4);
123 std::memcpy(buf.data(), data, std::min(size, MIN_INPUT));
124
125 // window_bits ∈ [1, 19] — `choose_window_bits` returns [2,19]; the final
126 // window emitted by `build_var_window_schedule` can additionally be 1 bit
127 // (e.g. wb=3 over 256 bits = 85*3+1). Outside this range the encoder has
128 // no well-defined behavior in production.
129 const size_t window_bits = 1 + (buf[0] % 19);
130 // bit_offset ∈ [0, 255] — the live pipeline's range, including the top
131 // edge where bit_offset+wb extends past the scalar's 256 bits (production
132 // code clamps `hi_limb` and zeros `hi_mask`).
133 const size_t bit_offset = buf[1] & 0xff;
134
136 for (size_t lane = 0; lane < 4; ++lane) {
137 std::memcpy(scalars[lane].data(), buf.data() + 2 + (lane * SCALAR_BYTES), SCALAR_BYTES);
138 }
139
140 // Check 1: scalar path matches the textbook reference oracle.
141 for (size_t lane = 0; lane < 4; ++lane) {
142 const uint32_t got = production_scalar(scalars[lane].data(), bit_offset, window_bits);
143 const uint32_t want = reference_packed_digit(scalars[lane].data(), bit_offset, window_bits);
144 if (got != want) {
145 __builtin_trap();
146 }
147 }
148
149 // Check 2: SIMD x4 path agrees with scalar path lane-by-lane.
150 alignas(16) std::array<uint32_t, 4> simd_out{};
151 production_simd(scalars, bit_offset, window_bits, simd_out);
152 for (size_t lane = 0; lane < 4; ++lane) {
153 const uint32_t want = production_scalar(scalars[lane].data(), bit_offset, window_bits);
154 if (simd_out[lane] != want) {
155 __builtin_trap();
156 }
157 }
158
159 return 0;
160}
uint32_t get_constantine_packed_digit(const uint64_t *scalar_data, uint32_t lo_limb, uint32_t hi_limb, uint32_t lo_off, uint32_t lo_bits, uint32_t lo_mask, uint32_t hi_mask, bool slice_localised_to_one_u64, size_t window_bits) noexcept
Read (window_bits+1) bits from scalar_data (uint64 limbs) using precomputed slice params and apply Co...
ConstantineSlicePath classify_slice_path_u32(const ConstantineSliceParamsU32 &sp) noexcept
void store_constantine_packed_digits_x4_bottom(uint32_t *dst, const uint32_t *scalar_data_0, const uint32_t *scalar_data_1, const uint32_t *scalar_data_2, const uint32_t *scalar_data_3, uint32_t hi_limb, uint32_t lo_bits, SimdU32x4 hi_mask_v, SimdU32x4 one_v, SimdU32x4 val_mask, uint32_t window_bits) noexcept
void store_constantine_packed_digits_x4_boundary(uint32_t *dst, const uint32_t *scalar_data_0, const uint32_t *scalar_data_1, const uint32_t *scalar_data_2, const uint32_t *scalar_data_3, uint32_t lo_limb, uint32_t hi_limb, uint32_t lo_off, uint32_t lo_bits, SimdU32x4 lo_mask_v, SimdU32x4 hi_mask_v, SimdU32x4 one_v, SimdU32x4 val_mask, uint32_t window_bits) noexcept
uint32_t __attribute__((vector_size(16))) SimdU32x4
ConstantineSliceParams compute_constantine_slice_params(size_t bit_offset, size_t window_bits, size_t num_uint64_limbs) noexcept
void store_constantine_packed_digits_x4_localised(uint32_t *dst, const uint32_t *scalar_data_0, const uint32_t *scalar_data_1, const uint32_t *scalar_data_2, const uint32_t *scalar_data_3, uint32_t lo_limb, uint32_t lo_off, SimdU32x4 lo_mask_v, SimdU32x4 one_v, SimdU32x4 val_mask, uint32_t window_bits) noexcept
ConstantineSliceParamsU32 compute_constantine_slice_params_u32(size_t bit_offset, size_t window_bits, size_t num_u32_limbs) noexcept
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
std::byte * data