Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
field_utils.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Sergei], commit: 777717f6af324188ecd6bb68c3c86ee7befef94d}
3// external_1: { status: Complete, auditors: [@ed25519 (Spearbit)], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
7#include "./field_utils.hpp"
8#include "./field.hpp"
10
11namespace bb::stdlib {
12
13template <typename Builder>
15 const field_t<Builder>& hi,
16 const size_t lo_bits,
17 const uint256_t& field_modulus)
18{
19 const size_t hi_bits = static_cast<size_t>(field_modulus.get_msb()) + 1 - lo_bits;
20
21 // Split the field modulus at the same position
22 const uint256_t r_lo = field_modulus.slice(0, lo_bits);
23 const uint256_t r_hi = field_modulus.slice(lo_bits, field_modulus.get_msb() + 1);
24
25 // Algorithm: Validate lo + hi * 2^lo_bits < field_modulus using borrow logic
26 //
27 // We want: value < modulus, i.e., value <= modulus - 1
28 // We compute: hi_diff = r_hi - hi - borrow, lo_diff = (r_lo - 1) - lo + borrow * 2^lo_bits
29 // Both must be in range [0, 2^{*_bits}) for the check to pass.
30 //
31 // - If lo <= r_lo - 1: no borrow needed
32 // - If lo > r_lo - 1 (i.e., lo >= r_lo): set borrow=1, which reduces the allowed hi value by 1
33 bool need_borrow = uint256_t(lo.get_value()) > r_lo - 1;
34
35 // If both lo and hi are constant, the validation is straightforward
36 const bool both_constant = lo.is_constant() && hi.is_constant();
38
39 field_t<Builder> borrow = both_constant
40 ? need_borrow
41 : field_t<Builder>::from_witness(ctx, typename field_t<Builder>::native(need_borrow));
42
43 // Constrain borrow to be boolean (0 or 1) unless both inputs are constant.
44 if (!both_constant) {
45 // We need to manually propagate the origin tag
47 ctx->create_small_range_constraint(borrow.get_witness_index(), 1, "borrow");
48 }
49
50 // Hi range check = r_hi - hi - borrow
51 // Lo range check = (r_lo - 1) - lo + borrow * 2^lo_bits
52 field_t<Builder> hi_diff = (-hi + r_hi) - borrow;
53 field_t<Builder> lo_diff = (-lo + (r_lo - fr(1))) + (borrow * (uint256_t(1) << lo_bits));
54
55 hi_diff.create_range_constraint(hi_bits);
56 lo_diff.create_range_constraint(lo_bits);
57}
58
59template <typename Builder>
61 const size_t lo_bits,
62 const bool skip_range_constraints)
63{
64 using native = typename field_t<Builder>::native;
65 static constexpr size_t max_bits = native::modulus.get_msb() + 1;
66 BB_ASSERT(lo_bits < max_bits);
67
68 const uint256_t value(field.get_value());
69 const uint256_t lo_val = value.slice(0, lo_bits);
70 const uint256_t hi_val = value.slice(lo_bits, max_bits);
71
72 Builder* ctx = field.get_context();
73
74 // If `field` is constant, return constants based on the native hi/lo values
75 if (field.is_constant()) {
76 return { field_t<Builder>(lo_val), field_t<Builder>(hi_val) };
77 }
78
79 // Create hi/lo witnesses
80 auto lo = field_t<Builder>::from_witness(ctx, typename field_t<Builder>::native(lo_val));
81 auto hi = field_t<Builder>::from_witness(ctx, typename field_t<Builder>::native(hi_val));
82
83 // Component 1: Reconstruction constraint lo + hi * 2^lo_bits - field == 0
84 const uint256_t shift = uint256_t(1) << lo_bits;
85 auto zero = field_t<Builder>::from_witness_index(ctx, ctx->zero_idx());
87
88 // Set the origin tag for the limbs
89 lo.set_origin_tag(field.get_origin_tag());
90 hi.set_origin_tag(field.get_origin_tag());
91
92 // Component 2: Field validation against bn254 scalar field modulus
93 // Note: We use _unsafe variant because Component 3 applies range constraints (unless explicitly skipped). When
94 // range constraints are skipped, caller must ensure they are applied elsewhere.
95 validate_split_in_field_unsafe(lo, hi, lo_bits, native::modulus);
96
97 // Component 3: Range constraints (unless skipped)
98 if (!skip_range_constraints) {
99 lo.create_range_constraint(lo_bits);
100 const size_t hi_bits = max_bits - lo_bits;
101 hi.create_range_constraint(hi_bits);
102 }
103
104 return { lo, hi };
105}
106
107template <typename Builder> void mark_witness_as_used(const field_t<Builder>& field)
108{
109 if (!field.is_constant()) {
110 // Use raw witness_index to avoid normalization overhead
111 field.get_context()->update_used_witnesses(field.witness_index);
112 }
113}
114
115// Explicit instantiations for split_unique
117 const field_t<bb::UltraCircuitBuilder>& field, const size_t lo_bits, const bool skip_range_constraints);
119 const field_t<bb::MegaCircuitBuilder>& field, const size_t lo_bits, const bool skip_range_constraints);
120
121// Explicit instantiations for validate_split_in_field_unsafe
124 const size_t lo_bits,
125 const uint256_t& field_modulus);
128 const size_t lo_bits,
129 const uint256_t& field_modulus);
130
131// Explicit instantiations for mark_witness_as_used
134
135} // namespace bb::stdlib
#define BB_ASSERT(expression,...)
Definition assert.hpp:70
constexpr uint256_t slice(uint64_t start, uint64_t end) const
constexpr uint64_t get_msb() const
static field_t from_witness_index(Builder *ctx, uint32_t witness_index)
Definition field.cpp:67
void create_range_constraint(size_t num_bits, std::string const &msg="field_t::range_constraint") const
Let x = *this.normalize(), constrain x.v < 2^{num_bits}.
Definition field.cpp:919
Builder * get_context() const
Definition field.hpp:431
OriginTag get_origin_tag() const
Definition field.hpp:358
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:836
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:466
static void evaluate_linear_identity(const field_t &a, const field_t &b, const field_t &c, const field_t &d, const std::string &msg="field_t::evaluate_linear_identity")
Constrain a + b + c + d to be equal to 0.
Definition field.cpp:1099
bool is_constant() const
Definition field.hpp:441
void set_origin_tag(const OriginTag &new_tag) const
Definition field.hpp:357
uint32_t get_witness_index() const
Get the witness index of the current field element.
Definition field.hpp:518
std::pair< field_t< Builder >, field_t< Builder > > split_unique(const field_t< Builder > &field, const size_t lo_bits, const bool skip_range_constraints)
Split a bn254 scalar field element into unique lo and hi limbs.
void validate_split_in_field_unsafe(const field_t< Builder > &lo, const field_t< Builder > &hi, const size_t lo_bits, const uint256_t &field_modulus)
Validates that lo + hi * 2^lo_bits < field_modulus (assuming range constraints on lo and hi)
T * validate_context(T *ptr)
Definition field.hpp:17
void mark_witness_as_used(const field_t< Builder > &field)
Mark a field_t witness as used (for UltraBuilder only).
field< Bn254FrParams > fr
Definition fr.hpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13