Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
sha256.cpp
Go to the documentation of this file.
1// === AUDIT STATUS ===
2// internal: { status: Complete, auditors: [Luke], commit: }
3// external_1: { status: not started, auditors: [], commit: }
4// external_2: { status: not started, auditors: [], commit: }
5// =====================
6
24#include "sha256.hpp"
25
30
31using namespace bb;
32
33namespace bb::stdlib {
34using namespace bb::plookup;
35
44template <typename Builder>
46{
48
49 sparse_witness_limbs result(input);
50 const auto lookup = plookup_read<Builder>::get_lookup_accumulators(MultiTableId::SHA256_WITNESS_INPUT, input);
51
53 lookup[ColumnIdx::C2][0],
54 lookup[ColumnIdx::C2][1],
55 lookup[ColumnIdx::C2][2],
56 lookup[ColumnIdx::C2][3],
57 };
59 lookup[ColumnIdx::C3][0],
60 lookup[ColumnIdx::C3][1],
61 lookup[ColumnIdx::C3][2],
62 lookup[ColumnIdx::C3][3],
63 };
64 result.has_sparse_limbs = true;
65
66 return result;
67}
68
81template <typename Builder>
83{
85
86 Builder* ctx = w_in[0].get_context();
87
89
90 // Populate initial 16 words from input (sparse form computed lazily as needed)
91 for (size_t i = 0; i < 16; ++i) {
92 w_sparse[i] = SHA256<Builder>::sparse_witness_limbs(w_in[i]);
93 // Extract builder context from inputs
94 if ((ctx == nullptr) && w_in[i].get_context()) {
95 ctx = w_in[i].get_context();
96 }
97 }
98
99 // Compute extended words W[16..63]
100 for (size_t i = 16; i < 64; ++i) {
101 auto& w_left = w_sparse[i - 15];
102 auto& w_right = w_sparse[i - 2];
103
104 if (!w_left.has_sparse_limbs) {
105 w_left = convert_witness(w_left.normal);
106 }
107 if (!w_right.has_sparse_limbs) {
108 w_right = convert_witness(w_right.normal);
109 }
110
111 // Compute the (partially) rotated sparse limbs for σ₀
112 // Note: remaining contributions accounted for via w_left.rotated_limb_corrections
114 w_left.sparse_limbs[0] * left_multipliers[0],
115 w_left.sparse_limbs[1] * left_multipliers[1],
116 w_left.sparse_limbs[2] * left_multipliers[2],
117 w_left.sparse_limbs[3] * left_multipliers[3],
118 };
119
120 // Compute the (partially) rotated sparse limbs for σ₁
121 // Note: remaining contributions accounted for via w_right.rotated_limb_corrections
123 w_right.sparse_limbs[0] * right_multipliers[0],
124 w_right.sparse_limbs[1] * right_multipliers[1],
125 w_right.sparse_limbs[2] * right_multipliers[2],
126 w_right.sparse_limbs[3] * right_multipliers[3],
127 };
128
129 // Compute σ₀(w[i-15]) in sparse form where σ₀(x) = (x >>> 7) ⊕ (x >>> 18) ⊕ (x >> 3).
130 // Each sparse digit holds the sum of contributions from the three rotation/shift operations (digit value in
131 // {0,1,2,3}). The fr(4) scaling positions σ₀'s contribution in the upper 2 bits of each 4-bit digit slot: when
132 // combined with σ₁ (unscaled, in lower 2 bits), each digit becomes 4*σ₀_digit + σ₁_digit ∈ [0,15].
133 const field_pt left_xor_sparse =
134 left[0].add_two(left[1], left[2]).add_two(left[3], w_left.rotated_limb_corrections[1]) * fr(4);
135
136 // Compute σ₀(w[i-15]) + σ₁(w[i-2]) in sparse form where σ₁(x) = (x >>> 17) ⊕ (x >>> 19) ⊕ (x >> 10).
137 const field_pt xor_result_sparse = right[0]
138 .add_two(right[1], right[2])
139 .add_two(right[3], w_right.rotated_limb_corrections[2])
140 .add_two(w_right.rotated_limb_corrections[3], left_xor_sparse);
141
142 // Normalize the sparse representation via a lookup to obtain the genuine result σ₀ + σ₁
144
145 // Compute W[i] = σ₁(W[i-2]) + W[i-7] + σ₀(W[i-15]) + W[i-16]
146 field_pt w_out_raw = xor_result.add_two(w_sparse[i - 16].normal, w_sparse[i - 7].normal);
147
148 // Natively compute value reduced to 32 bits per SHA-256 spec
149 const uint64_t w_out_modded = w_out_raw.get_value().from_montgomery_form().data[0] & 0xffffffffULL;
150
151 field_pt w_out;
152 if (w_out_raw.is_constant()) {
153 w_out = field_pt(ctx, fr(w_out_modded));
154 } else {
155 // Establish w_out as the 32-bit reduction of w_out_raw via w_out_raw = w_out + divisor*2^32
156 w_out = witness_t<Builder>(ctx, fr(w_out_modded));
157 static constexpr fr inv_pow_two = fr(2).pow(32).invert();
158
159 field_pt w_out_raw_inv_pow_two = w_out_raw * inv_pow_two;
160 field_pt w_out_inv_pow_two = w_out * inv_pow_two;
161 field_pt divisor = w_out_raw_inv_pow_two - w_out_inv_pow_two;
162 // AUDITTODO: The 3-bit constraint is currently necessary due to unconstrained inputs.
163 //
164 // w_out_raw = xor_result + w[i-16] + w[i-7], where:
165 // - xor_result: 32-bit (from SHA256_WITNESS_OUTPUT lookup)
166 // - w[i-16]: At i=16, this is input[0] which is NEVER lookup-constrained
167 // - w[i-7]: At i=16..20, this is input[9..13] which are used BEFORE being converted
168 //
169 // If all three inputs were 32-bit constrained, max sum = 3*(2^32-1), so divisor <= 2
170 // and a 2-bit constraint would suffice. However, with unconstrained inputs (~35 bits
171 // per the add_normalize overflow slack), divisor could exceed 7 and reject the proof.
172 //
173 // This constraint implicitly enforces input bounds - if we add explicit 32-bit input
174 // constraints (see AUDITTODO in sha256_block), this could be tightened to 2 bits.
175 divisor.create_range_constraint(3);
176 }
177
178 w_sparse[i] = sparse_witness_limbs(w_out);
179 }
180
181 std::array<field_pt, 64> w_extended;
182 for (size_t i = 0; i < 64; ++i) {
183 w_extended[i] = w_sparse[i].normal;
184 }
185 return w_extended;
186}
187
198template <typename Builder>
207
218template <typename Builder>
227
245template <typename Builder>
247{
249 // Separates rotation contributions (0-3) from Choose encoding (0-6) in each base-28 digit
250 constexpr fr SPARSE_MULT = fr(7);
251
253 const auto rotation_coefficients = sha256_tables::get_choose_rotation_multipliers();
254
255 field_pt rotation_result = lookup[ColumnIdx::C3][0];
256 e.sparse = lookup[ColumnIdx::C2][0];
257 field_pt sparse_L2 = lookup[ColumnIdx::C2][2];
258
259 // Compute e + 7*Σ₁(e) in sparse form
260 field_pt xor_result = (rotation_result * SPARSE_MULT)
261 .add_two(e.sparse * (rotation_coefficients[0] * SPARSE_MULT + fr(1)),
262 sparse_L2 * (rotation_coefficients[2] * SPARSE_MULT));
263
264 // Add 2f + 3g to get e + 7*Σ₁(e) + 2f + 3g (each digit in 0..27)
265 field_pt choose_result_sparse = xor_result.add_two(f.sparse + f.sparse, g.sparse + g.sparse + g.sparse);
266
267 // Normalize via lookup: each digit maps to Σ₁(e)_i + Ch(e,f,g)_i
268 field_pt choose_result = plookup_read<Builder>::read_from_1_to_2_table(SHA256_CH_OUTPUT, choose_result_sparse);
269
270 return choose_result;
271}
272
290template <typename Builder>
292{
294 // Separates rotation contributions (0-3) from Majority encoding (0-3) in each base-16 digit
295 constexpr fr SPARSE_MULT = fr(4);
296
298 const auto rotation_coefficients = sha256_tables::get_majority_rotation_multipliers();
299
300 // first row of 3rd column gives accumulating sum of "non-trivial" wraps
301 field_pt rotation_result = lookup[ColumnIdx::C3][0];
302 a.sparse = lookup[ColumnIdx::C2][0];
303 field_pt sparse_L1_acc = lookup[ColumnIdx::C2][1];
304
305 // Compute a + 4*Σ₀(a) in sparse form
306 field_pt xor_result = (rotation_result * SPARSE_MULT)
307 .add_two(a.sparse * (rotation_coefficients[0] * SPARSE_MULT + fr(1)),
308 sparse_L1_acc * (rotation_coefficients[1] * SPARSE_MULT));
309
310 // Add b + c to get a + 4*Σ₀(a) + b + c (each digit in 0..15)
311 field_pt majority_result_sparse = xor_result.add_two(b.sparse, c.sparse);
312
313 // Normalize via lookup: each digit maps to Σ₀(a)_i + Maj(a,b,c)_i
314 field_pt majority_result = plookup_read<Builder>::read_from_1_to_2_table(SHA256_MAJ_OUTPUT, majority_result_sparse);
315
316 return majority_result;
317}
318
328template <typename Builder>
330{
333
334 Builder* ctx = a.get_context() ? a.get_context() : b.get_context();
335
336 uint256_t sum = a.get_value() + b.get_value();
337 uint256_t normalized_sum = static_cast<uint32_t>(sum.data[0]); // lower 32 bits
338
339 if (a.is_constant() && b.is_constant()) {
340 return field_pt(ctx, normalized_sum);
341 }
342
343 fr overflow_value = fr((sum - normalized_sum) >> 32);
344 field_pt overflow = witness_pt(ctx, overflow_value);
345
346 field_pt result = a.add_two(b, overflow * field_pt(ctx, -fr(1ULL << 32ULL)));
347 // AUDITTODO: The 3-bit constraint is necessary. Analysis of call sites:
348 //
349 // Compression loop (lines ~439-450):
350 // ch, maj outputs: max = 2(2^32-1) each (lookup output digits are 0-2, see sha256.hpp:79)
351 // temp1 = ch + h.normal + (w[i] + K[i]) (max = 2(2^32-1) + (2^32-1) + 2(2^32-1) = 5(2^32-1))
352 // add_normalize(d.normal, temp1): max sum = (2^32-1) + 5(2^32-1) = 6(2^32-1), overflow <= 5
353 // add_normalize(temp1, maj): max sum = 5(2^32-1) + 2(2^32-1) = 7(2^32-1), overflow <= 6
354 // => Requires 3 bits (to represent overflow values 0-7)
355 //
356 // Final output (lines ~456-463):
357 // add_normalize(X.normal, h_init[i]): both 32-bit, max sum = 2(2^32-1), overflow <= 1
358 // => Could use 1 bit, but we use 3 for uniformity
359 //
360 // The 3-bit constraint is correct and necessary for the compression loop.
361 // Consider adding argument overflow_bits to customize constraint size and make it more explicit.
362 overflow.create_range_constraint(3);
363 return result;
364}
365
378template <typename Builder>
380 const std::array<field_t<Builder>, 16>& input)
381{
383
384 // AUDITTODO: Input range constraints are not explicitly enforced here. Analysis shows:
385 //
386 // - h_init[1,2,5,6] are immediately lookup-constrained (32-bit) via map_into_*_sparse_form
387 // - h_init[0,4] are lookup-constrained in round 0 via choose/majority functions
388 // - h_init[3,7] are used in round 0 arithmetic BEFORE being lookup-constrained (they cycle
389 // through working variables and get constrained in later rounds)
390 // - input[0] is NEVER lookup-constrained (only used as w[i-16] and in round 0, both additions)
391 // - input[1..8] are lookup-constrained during extend_witness as w[i-15] (at i=16..23)
392 // - input[9..13] are used as w[i-7] at i=16..20 BEFORE being constrained (converted later
393 // as w[i-15] at i=24..28)
394 // - input[14..15] are lookup-constrained during extend_witness as w[i-2] (at i=16..17)
395 //
396 // The overflow constraints in extend_witness (3-bit divisor) and add_normalize (3-bit overflow)
397 // provide weak implicit bounds. If unconstrained inputs exceed ~35 bits, these constraints
398 // will reject the proof. This is safe (rejects invalid proofs) but not ideal.
399 //
400 // This is not practically exploitable (finding inputs that produce a specific hash still
401 // requires ~2^208 work), but deviates from the SHA-256 spec which assumes 32-bit words.
402 //
403 // Potential fix: Use lookups (cheaper than create_range_constraint, ~1 gate vs multiple):
404 // - For h_init[3], h_init[7]: convert immediately via map_into_*_sparse_form instead of
405 // wrapping in sparse_value(). The lookup constrains the input as a side effect.
406 // - For input[0]: add a lookup in extend_witness via convert_witness() or SHA256_WITNESS_INPUT.
407 // - For input[9..13]: reorder extend_witness to convert these before use, or add explicit lookups.
408 //
409 // After fixing, the extend_witness divisor constraint could be tightened to 2 bits.
410
416 sparse_value a = sparse_value(h_init[0]); // delay conversion to maj sparse form
417 auto b = map_into_maj_sparse_form(h_init[1]);
418 auto c = map_into_maj_sparse_form(h_init[2]);
419 sparse_value d = sparse_value(h_init[3]);
420 sparse_value e = sparse_value(h_init[4]); // delay conversion to choose sparse form
421 auto f = map_into_choose_sparse_form(h_init[5]);
422 auto g = map_into_choose_sparse_form(h_init[6]);
423 sparse_value h = sparse_value(h_init[7]);
424
425 // Extend the 16-word message block to 64 words per SHA-256 specification
426 const std::array<field_t<Builder>, 64> w = extend_witness(input);
427
439 for (size_t i = 0; i < 64; ++i) {
440 auto ch = choose_with_sigma1(e, f, g);
441 auto maj = majority_with_sigma0(a, b, c);
442 auto temp1 = ch.add_two(h.normal, w[i] + fr(round_constants[i]));
443
444 h = g;
445 g = f;
446 f = e;
447 e.normal = add_normalize(d.normal, temp1);
448 d = c;
449 c = b;
450 b = a;
451 a.normal = add_normalize(temp1, maj);
452 }
453
454 // Add into previous block output and return
456 output[0] = add_normalize(a.normal, h_init[0]);
457 output[1] = add_normalize(b.normal, h_init[1]);
458 output[2] = add_normalize(c.normal, h_init[2]);
459 output[3] = add_normalize(d.normal, h_init[3]);
460 output[4] = add_normalize(e.normal, h_init[4]);
461 output[5] = add_normalize(f.normal, h_init[5]);
462 output[6] = add_normalize(g.normal, h_init[6]);
463 output[7] = add_normalize(h.normal, h_init[7]);
464
465 // The final add_normalize outputs are not consumed by lookup tables, so they must be
466 // explicitly range-constrained. (Within the compression loop, lookup tables provide
467 // implicit 32-bit constraints on add_normalize outputs.)
468 for (size_t i = 0; i < 8; i++) {
469 output[i].create_range_constraint(32);
470 }
471
472 return output;
473}
474
476template class SHA256<bb::MegaCircuitBuilder>;
477
478} // namespace bb::stdlib
static sparse_value map_into_maj_sparse_form(const field_ct &input)
Convert a field element to sparse form for use in the Majority function.
Definition sha256.cpp:219
static field_ct add_normalize(const field_ct &a, const field_ct &b)
Compute (a + b) mod 2^32 with circuit constraints.
Definition sha256.cpp:329
static std::array< field_ct, 64 > extend_witness(const std::array< field_ct, 16 > &w_in)
Extend the 16-word message block to 64 words per SHA-256 specification.
Definition sha256.cpp:82
static field_ct choose_with_sigma1(sparse_value &e, const sparse_value &f, const sparse_value &g)
Compute Σ₁(e) + Ch(e,f,g) for SHA-256 compression rounds.
Definition sha256.cpp:246
static sparse_witness_limbs convert_witness(const field_ct &input)
Convert a 32-bit value to sparse limbs form for message schedule extension.
Definition sha256.cpp:45
static field_ct majority_with_sigma0(sparse_value &a, const sparse_value &b, const sparse_value &c)
Compute Σ₀(a) + Maj(a,b,c) for SHA-256 compression rounds.
Definition sha256.cpp:291
static sparse_value map_into_choose_sparse_form(const field_ct &input)
Convert a field element to sparse form for use in the Choose function.
Definition sha256.cpp:199
static std::array< field_ct, 8 > sha256_block(const std::array< field_ct, 8 > &h_init, const std::array< field_ct, 16 > &input)
Apply the SHA-256 compression function to a single 512-bit message block.
Definition sha256.cpp:379
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:909
bb::fr get_value() const
Given a := *this, compute its value given by a.v * a.mul + a.add.
Definition field.cpp:828
bool is_constant() const
Definition field.hpp:429
field_t add_two(const field_t &add_b, const field_t &add_c) const
Efficiently compute (this + a + b) using big_mul gate.
Definition field.cpp:575
static plookup::ReadData< field_pt > get_lookup_accumulators(const plookup::MultiTableId id, const field_pt &key_a, const field_pt &key_b=0, const bool is_2_to_1_lookup=false)
Definition plookup.cpp:19
static field_pt read_from_1_to_2_table(const plookup::MultiTableId id, const field_pt &key_a)
Definition plookup.cpp:89
FF a
FF b
stdlib::witness_t< bb::UltraCircuitBuilder > witness_pt
stdlib::field_t< UltraCircuitBuilder > field_pt
std::array< bb::fr, 3 > get_choose_rotation_multipliers()
Returns multipliers for computing Σ₁(e) rotations in choose_with_sigma1.
Definition sha256.hpp:409
std::array< bb::fr, 3 > get_majority_rotation_multipliers()
Returns multipliers for computing Σ₀(a) rotations in majority_with_sigma0.
Definition sha256.hpp:392
@ SHA256_CH_INPUT
Definition types.hpp:92
@ SHA256_MAJ_OUTPUT
Definition types.hpp:95
@ SHA256_CH_OUTPUT
Definition types.hpp:93
@ SHA256_WITNESS_OUTPUT
Definition types.hpp:97
@ SHA256_MAJ_INPUT
Definition types.hpp:94
field_t< Builder > add_normalize(const field_t< Builder > &a, const field_t< Builder > &b)
void g(field_t< Builder > state[BLAKE_STATE_SIZE], size_t a, size_t b, size_t c, size_t d, field_t< Builder > x, field_t< Builder > y)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
field< Bn254FrParams > fr
Definition fr.hpp:174
Inner sum(Cont< Inner, Args... > const &in)
Definition container.hpp:70
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Plookup tables for SHA-256 using sparse form representation.
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
constexpr field invert() const noexcept
BB_INLINE constexpr field from_montgomery_form() const noexcept
std::array< field_ct, 4 > rotated_limb_corrections
Definition sha256.hpp:115
std::array< field_ct, 4 > sparse_limbs
Definition sha256.hpp:113