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 // Precondition: r_lo must be nonzero. If r_lo == 0, the borrow logic below breaks.
26 // The fields of our concern (bn254 Fr/Fq, secp256k1 Fq) have nonzero low bits, so this
27 // precondition holds for all current uses.
28 BB_ASSERT(r_lo != 0, "validate_split_in_field_unsafe: low bits of modulus are zero, borrow logic is unsound");
29
30 // Algorithm: Validate lo + hi * 2^lo_bits < field_modulus using borrow logic
31 //
32 // We want: value < modulus, i.e., value <= modulus - 1
33 // We compute: hi_diff = r_hi - hi - borrow, lo_diff = (r_lo - 1) - lo + borrow * 2^lo_bits
34 // Both must be in range [0, 2^{*_bits}) for the check to pass.
35 //
36 // - If lo <= r_lo - 1: no borrow needed
37 // - If lo > r_lo - 1 (i.e., lo >= r_lo): set borrow=1, which reduces the allowed hi value by 1
38 bool need_borrow = uint256_t(lo.get_value()) > r_lo - 1;
39
40 // If both lo and hi are constant, the validation is straightforward
41 const bool both_constant = lo.is_constant() && hi.is_constant();
43
44 field_t<Builder> borrow = both_constant
45 ? need_borrow
46 : field_t<Builder>::from_witness(ctx, typename field_t<Builder>::native(need_borrow));
47
48 // Constrain borrow to be boolean (0 or 1) unless both inputs are constant.
49 if (!both_constant) {
50 // We need to manually propagate the origin tag
52 ctx->create_small_range_constraint(borrow.get_witness_index(), 1, "borrow");
53 }
54
55 // Hi range check = r_hi - hi - borrow
56 // Lo range check = (r_lo - 1) - lo + borrow * 2^lo_bits
57 field_t<Builder> hi_diff = (-hi + r_hi) - borrow;
58 field_t<Builder> lo_diff = (-lo + (r_lo - fr(1))) + (borrow * (uint256_t(1) << lo_bits));
59
60 hi_diff.create_range_constraint(hi_bits);
61 lo_diff.create_range_constraint(lo_bits);
62}
63
64template <typename Builder>
66 const size_t lo_bits,
67 const bool skip_range_constraints)
68{
69 using native = typename field_t<Builder>::native;
70 static constexpr size_t max_bits = native::modulus.get_msb() + 1;
71 BB_ASSERT(lo_bits < max_bits);
72
73 const uint256_t value(field.get_value());
74 const uint256_t lo_val = value.slice(0, lo_bits);
75 const uint256_t hi_val = value.slice(lo_bits, max_bits);
76
77 Builder* ctx = field.get_context();
78
79 // If `field` is constant, return constants based on the native hi/lo values
80 if (field.is_constant()) {
81 return { field_t<Builder>(lo_val), field_t<Builder>(hi_val) };
82 }
83
84 // Create hi/lo witnesses
85 auto lo = field_t<Builder>::from_witness(ctx, typename field_t<Builder>::native(lo_val));
86 auto hi = field_t<Builder>::from_witness(ctx, typename field_t<Builder>::native(hi_val));
87
88 // Component 1: Reconstruction constraint lo + hi * 2^lo_bits - field == 0
89 const uint256_t shift = uint256_t(1) << lo_bits;
90 auto zero = field_t<Builder>::from_witness_index(ctx, ctx->zero_idx());
92
93 // Set the origin tag for the limbs
94 lo.set_origin_tag(field.get_origin_tag());
95 hi.set_origin_tag(field.get_origin_tag());
96
97 // Component 2: Field validation against bn254 scalar field modulus
98 // Note: We use _unsafe variant because Component 3 applies range constraints (unless explicitly skipped). When
99 // range constraints are skipped, caller must ensure they are applied elsewhere.
100 validate_split_in_field_unsafe(lo, hi, lo_bits, native::modulus);
101
102 // Component 3: Range constraints (unless skipped)
103 if (!skip_range_constraints) {
104 lo.create_range_constraint(lo_bits);
105 const size_t hi_bits = max_bits - lo_bits;
106 hi.create_range_constraint(hi_bits);
107 }
108
109 return { lo, hi };
110}
111
112template <typename Builder> void mark_witness_as_used(const field_t<Builder>& field)
113{
114 if (!field.is_constant()) {
115 // Use raw witness_index to avoid normalization overhead
116 field.get_context()->update_used_witnesses(field.witness_index);
117 }
118}
119
120// Explicit instantiations for split_unique
122 const field_t<bb::UltraCircuitBuilder>& field, const size_t lo_bits, const bool skip_range_constraints);
124 const field_t<bb::MegaCircuitBuilder>& field, const size_t lo_bits, const bool skip_range_constraints);
125
126// Explicit instantiations for validate_split_in_field_unsafe
129 const size_t lo_bits,
130 const uint256_t& field_modulus);
133 const size_t lo_bits,
134 const uint256_t& field_modulus);
135
136// Explicit instantiations for mark_witness_as_used
139
140} // 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:921
Builder * get_context() const
Definition field.hpp:432
OriginTag get_origin_tag() const
Definition field.hpp:359
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:838
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:467
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:1101
bool is_constant() const
Definition field.hpp:442
void set_origin_tag(const OriginTag &new_tag) const
Definition field.hpp:358
uint32_t get_witness_index() const
Get the witness index of the current field element.
Definition field.hpp:519
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