23#include <unordered_map>
34 std::unique_ptr<purify_bulletproof_backend_resources, BulletproofBackendResourceDeleter>;
37 std::unordered_map<std::size_t, BulletproofBackendResourcePtr>
resources;
51 if (impl_ !=
nullptr) {
52 impl_->resources.clear();
57 return impl_ !=
nullptr ? impl_->resources.size() : 0;
61 std::size_t n_gates)
const {
63 if (impl_ ==
nullptr) {
67 auto it = impl_->resources.find(n_gates);
68 if (it == impl_->resources.end()) {
74 if (cloned ==
nullptr) {
77 "ExperimentalBulletproofBackendCache::clone_for_thread:backend_resources");
86 if (impl_ ==
nullptr || secp_context ==
nullptr) {
90 auto it = impl_->resources.find(n_gates);
91 if (it != impl_->resources.end()) {
92 return it->second.get();
97 if (created ==
nullptr) {
102 return inserted.first->second.get();
134static_assert(std::is_trivially_copyable_v<purify::NativeBulletproofCircuitTerm>,
135 "Packed circuit storage requires trivially copyable terms");
136static_assert(std::is_trivially_copyable_v<purify::FieldElement>,
137 "Packed circuit storage requires trivially copyable field elements");
139std::uint32_t narrow_symbol_index(std::size_t index) {
140 assert(index <=
static_cast<std::size_t
>(std::numeric_limits<std::uint32_t>::max())
141 &&
"symbol index must fit in uint32_t");
142 return static_cast<std::uint32_t
>(index);
145struct ResolvedValues {
147 std::vector<std::optional<FieldElement>> left;
148 std::vector<std::optional<FieldElement>> right;
149 std::vector<std::optional<FieldElement>> output;
150 std::vector<std::optional<FieldElement>> commitment;
152 ResolvedValues(std::size_t witness_count, std::size_t gate_count, std::size_t commitment_count)
153 : witness(witness_count), left(gate_count), right(gate_count), output(gate_count), commitment(commitment_count) {}
155 std::optional<FieldElement> get(Symbol symbol)
const {
156 std::size_t index = symbol.index;
157 switch (symbol.kind) {
158 case SymbolKind::Witness:
159 if (index >= witness.size()) {
162 return witness[index];
163 case SymbolKind::Left:
164 if (index >= left.size()) {
168 case SymbolKind::Right:
169 if (index >= right.size()) {
173 case SymbolKind::Output:
174 if (index >= output.size()) {
177 return output[index];
178 case SymbolKind::Commitment:
179 if (index >= commitment.size()) {
182 return commitment[index];
187 bool set(Symbol symbol,
const FieldElement& value) {
188 std::size_t index = symbol.index;
189 switch (symbol.kind) {
190 case SymbolKind::Witness:
191 if (index >= witness.size()) {
194 witness[index] = value;
196 case SymbolKind::Left:
197 if (index >= left.size()) {
202 case SymbolKind::Right:
203 if (index >= right.size()) {
206 right[index] = value;
208 case SymbolKind::Output:
209 if (index >= output.size()) {
212 output[index] = value;
214 case SymbolKind::Commitment:
215 if (index >= commitment.size()) {
218 commitment[index] = value;
225Result<FieldElement> evaluate_known(
const Expr& expr,
const ResolvedValues& values) {
226 FieldElement out = expr.constant();
227 for (
const auto& term : expr.linear()) {
228 std::optional<FieldElement> value = values.get(term.first);
229 if (!value.has_value()) {
232 out = out + *value * term.second;
237bool checked_align_up(std::size_t value, std::size_t alignment, std::size_t& out) {
238 assert(alignment != 0 &&
"alignment must be non-zero");
239 const std::size_t remainder = value % alignment;
240 if (remainder == 0) {
247bool packed_row_count(std::size_t n_gates, std::size_t n_commitments, std::size_t& out) {
248 std::size_t gate_rows = 0;
253 for (
int i = 0; i < 4; ++i) {
254 out.push_back(
static_cast<unsigned char>((value >> (8 * i)) & 0xffU));
258void append_u64_le(Bytes& out, std::uint64_t value) {
259 for (
int i = 0; i < 8; ++i) {
260 out.push_back(
static_cast<unsigned char>((value >> (8 * i)) & 0xffU));
265 using SymbolRank = std::underlying_type_t<purify::SymbolKind>;
266 append_u64_le(out,
static_cast<std::uint64_t
>(
static_cast<SymbolRank
>(symbol.
kind)));
268 "append_symbol_digest:index");
269 append_u64_le(out, symbol_index);
275 out.insert(out.end(), constant.begin(), constant.end());
277 "append_expr_digest:term_count");
278 append_u64_le(out, term_count);
279 for (
const auto& term : expr.linear()) {
281 const auto coeff = term.second.to_bytes_be();
282 out.insert(out.end(), coeff.begin(), coeff.end());
287std::optional<std::uint32_t>
read_u32_le(std::span<const unsigned char> bytes, std::size_t offset) {
288 std::uint32_t value = 0;
289 if (offset > bytes.size() || bytes.size() - offset < 4) {
292 for (
int i = 0; i < 4; ++i) {
293 value |=
static_cast<std::uint32_t
>(bytes[offset + i]) << (8 * i);
298Bytes flatten_scalars32(
const std::vector<FieldElement>& values) {
301 for (
const FieldElement& value : values) {
302 auto bytes = value.to_bytes_be();
303 out.insert(out.end(), bytes.begin(), bytes.end());
308template <
typename RowEntriesFn>
309Status append_row_family_digest_generic(Bytes& out, std::size_t row_count,
const RowEntriesFn& row_entries,
310 const char* context) {
312 append_u64_le(out, encoded_row_count);
313 for (std::size_t i = 0; i < row_count; ++i) {
314 std::span<const purify::NativeBulletproofCircuitTerm> entries = row_entries(i);
316 append_u64_le(out, encoded_entry_count);
317 for (
const auto& entry : entries) {
319 append_u64_le(out, encoded_entry_index);
320 auto scalar = entry.scalar.to_bytes_be();
328 std::span<const unsigned char> statement_binding) {
329 static const purify::TaggedHash kCircuitBindingTag(
"Purify/ExperimentalBulletproof/CircuitV1");
330 std::size_t reserve_size = 0;
333 return unexpected_error(ErrorCode::Overflow,
"circuit_binding_digest:native_reserve");
337 serialized.reserve(reserve_size);
339 "circuit_binding_digest:native_n_gates");
342 "circuit_binding_digest:native_n_commitments");
344 "circuit_binding_digest:native_n_bits");
347 "circuit_binding_digest:native_constant_count");
348 append_u64_le(serialized, encoded_n_gates);
349 append_u64_le(serialized, encoded_n_commitments);
350 append_u64_le(serialized, encoded_n_bits);
351 append_u64_le(serialized, encoded_constant_count);
353 serialized, circuit.
wl.size(),
354 [&](std::size_t i) { return std::span<const purify::NativeBulletproofCircuitTerm>(circuit.wl[i].entries); },
355 "circuit_binding_digest:native_wl"),
356 "circuit_binding_digest:native_wl");
358 serialized, circuit.
wr.size(),
359 [&](std::size_t i) { return std::span<const purify::NativeBulletproofCircuitTerm>(circuit.wr[i].entries); },
360 "circuit_binding_digest:native_wr"),
361 "circuit_binding_digest:native_wr");
363 serialized, circuit.
wo.size(),
364 [&](std::size_t i) { return std::span<const purify::NativeBulletproofCircuitTerm>(circuit.wo[i].entries); },
365 "circuit_binding_digest:native_wo"),
366 "circuit_binding_digest:native_wo");
368 serialized, circuit.
wv.size(),
369 [&](std::size_t i) { return std::span<const purify::NativeBulletproofCircuitTerm>(circuit.wv[i].entries); },
370 "circuit_binding_digest:native_wv"),
371 "circuit_binding_digest:native_wv");
372 for (
const FieldElement& constant : circuit.c) {
373 auto bytes = constant.to_bytes_be();
374 serialized.insert(serialized.end(), bytes.begin(), bytes.end());
377 narrow_size_to_u64(statement_binding.size(),
"circuit_binding_digest:native_statement_binding"),
378 "circuit_binding_digest:native_statement_binding");
379 append_u64_le(serialized, encoded_binding_size);
380 serialized.insert(serialized.end(), statement_binding.begin(), statement_binding.end());
381 std::array<unsigned char, 32> digest =
382 kCircuitBindingTag.digest(std::span<const unsigned char>(serialized.data(), serialized.size()));
383 return Bytes(digest.begin(), digest.end());
386Result<Bytes> circuit_binding_digest(
const PackedCircuit& circuit, std::span<const unsigned char> statement_binding) {
387 static const purify::TaggedHash kCircuitBindingTag(
"Purify/ExperimentalBulletproof/CircuitV1");
388 std::size_t reserve_size = 0;
391 return unexpected_error(ErrorCode::Overflow,
"circuit_binding_digest:packed_reserve");
395 serialized.reserve(reserve_size);
397 "circuit_binding_digest:packed_n_gates");
399 narrow_size_to_u64(circuit.n_commitments(),
"circuit_binding_digest:packed_n_commitments"),
400 "circuit_binding_digest:packed_n_commitments");
402 "circuit_binding_digest:packed_n_bits");
404 narrow_size_to_u64(circuit.constraint_count(),
"circuit_binding_digest:packed_constraint_count"),
405 "circuit_binding_digest:packed_constraint_count");
406 append_u64_le(serialized, encoded_n_gates);
407 append_u64_le(serialized, encoded_n_commitments);
408 append_u64_le(serialized, encoded_n_bits);
409 append_u64_le(serialized, encoded_constraint_count);
411 [&](std::size_t i) { return circuit.left_row(i).entries_view(); },
412 "circuit_binding_digest:packed_left"),
413 "circuit_binding_digest:packed_left");
415 [&](std::size_t i) { return circuit.right_row(i).entries_view(); },
416 "circuit_binding_digest:packed_right"),
417 "circuit_binding_digest:packed_right");
419 [&](std::size_t i) { return circuit.output_row(i).entries_view(); },
420 "circuit_binding_digest:packed_output"),
421 "circuit_binding_digest:packed_output");
423 [&](std::size_t i) { return circuit.commitment_row(i).entries_view(); },
424 "circuit_binding_digest:packed_commitment"),
425 "circuit_binding_digest:packed_commitment");
426 for (
const FieldElement& constant : circuit.constants()) {
427 auto bytes = constant.to_bytes_be();
428 serialized.insert(serialized.end(), bytes.begin(), bytes.end());
431 narrow_size_to_u64(statement_binding.size(),
"circuit_binding_digest:packed_statement_binding"),
432 "circuit_binding_digest:packed_statement_binding");
433 append_u64_le(serialized, encoded_binding_size);
434 serialized.insert(serialized.end(), statement_binding.begin(), statement_binding.end());
435 std::array<unsigned char, 32> digest =
436 kCircuitBindingTag.digest(std::span<const unsigned char>(serialized.data(), serialized.size()));
437 return Bytes(digest.begin(), digest.end());
440template <
typename CircuitLike>
441void append_constraint_to_circuit(CircuitLike& circuit,
const Expr& lhs,
const Expr& rhs) {
442 Expr combined = lhs - rhs;
443 std::size_t constraint_idx = circuit.add_constraint(combined.constant().negate());
444 for (
const auto& term : combined.linear()) {
445 std::size_t index = term.first.index;
446 switch (term.first.kind) {
447 case SymbolKind::Left:
448 circuit.add_left_term(index, constraint_idx, term.second);
450 case SymbolKind::Right:
451 circuit.add_right_term(index, constraint_idx, term.second);
453 case SymbolKind::Output:
454 circuit.add_output_term(index, constraint_idx, term.second);
456 case SymbolKind::Commitment:
457 circuit.add_commitment_term(index, constraint_idx, term.second);
459 case SymbolKind::Witness:
460 assert(
false &&
"append_constraint_to_circuit() encountered an unmapped witness symbol");
466struct FlattenedRowFamily {
467 std::vector<purify_bulletproof_row_view> views;
468 std::vector<std::size_t> indices;
472template <
typename RowEntriesFn>
473FlattenedRowFamily flatten_row_family_generic(std::size_t row_count,
const RowEntriesFn& row_entries,
474 std::size_t constraint_offset) {
475 FlattenedRowFamily flat;
476 std::vector<std::size_t> offsets;
477 std::vector<std::size_t> counts;
478 offsets.reserve(row_count);
479 counts.reserve(row_count);
480 flat.views.resize(row_count);
482 std::size_t total_entries = 0;
483 for (std::size_t i = 0; i < row_count; ++i) {
484 std::span<const purify::NativeBulletproofCircuitTerm> entries = row_entries(i);
485 total_entries += std::count_if(entries.begin(), entries.end(),
486 [&](
const auto& entry) { return entry.idx >= constraint_offset; });
488 flat.indices.reserve(total_entries);
491 for (std::size_t i = 0; i < row_count; ++i) {
492 std::span<const purify::NativeBulletproofCircuitTerm> entries = row_entries(i);
493 std::size_t kept = 0;
494 offsets.push_back(flat.indices.size());
495 for (
const auto& entry : entries) {
496 if (entry.idx < constraint_offset) {
500 flat.indices.push_back(entry.idx - constraint_offset);
501 auto bytes = entry.scalar.to_bytes_be();
502 flat.scalars32.insert(flat.scalars32.end(), bytes.begin(), bytes.end());
504 counts.push_back(kept);
507 const std::size_t* indices_base = flat.indices.empty() ? nullptr : flat.indices.data();
508 const unsigned char* scalars_base = flat.scalars32.empty() ? nullptr : flat.scalars32.data();
509 for (std::size_t i = 0; i < row_count; ++i) {
510 const std::size_t count = counts[i];
511 const std::size_t start = offsets[i];
512 flat.views[i].size = count;
513 flat.views[i].indices = count == 0 ? nullptr : indices_base + start;
514 flat.views[i].scalars32 = count == 0 ? nullptr : scalars_base + start * 32;
519struct FlattenedCircuitView {
520 FlattenedRowFamily wl;
521 FlattenedRowFamily wr;
522 FlattenedRowFamily wo;
523 FlattenedRowFamily wv;
529 FlattenedCircuitView flat;
530 const std::size_t implicit_bit_constraints = 2 * circuit.
n_bits;
531 assert(circuit.
c.size() >= implicit_bit_constraints &&
"native circuit must contain all implicit bit constraints");
532 flat.wl = flatten_row_family_generic(circuit.
wl.size(),
533 [&](std::size_t i) { return std::span<const purify::NativeBulletproofCircuitTerm>(circuit.wl[i].entries); },
534 implicit_bit_constraints);
535 flat.wr = flatten_row_family_generic(circuit.
wr.size(),
536 [&](std::size_t i) { return std::span<const purify::NativeBulletproofCircuitTerm>(circuit.wr[i].entries); },
537 implicit_bit_constraints);
538 flat.wo = flatten_row_family_generic(circuit.
wo.size(),
539 [&](std::size_t i) { return std::span<const purify::NativeBulletproofCircuitTerm>(circuit.wo[i].entries); },
540 implicit_bit_constraints);
541 flat.wv = flatten_row_family_generic(circuit.
wv.size(),
542 [&](std::size_t i) { return std::span<const purify::NativeBulletproofCircuitTerm>(circuit.wv[i].entries); },
543 implicit_bit_constraints);
544 std::vector<FieldElement> explicit_constants(circuit.
c.begin() +
static_cast<std::ptrdiff_t
>(implicit_bit_constraints), circuit.
c.end());
545 flat.constants32 = flatten_scalars32(explicit_constants);
546 flat.view.n_gates = circuit.
n_gates;
548 flat.view.n_bits = circuit.
n_bits;
549 flat.view.n_constraints = explicit_constants.size();
550 flat.view.wl = flat.wl.views.data();
551 flat.view.wr = flat.wr.views.data();
552 flat.view.wo = flat.wo.views.data();
553 flat.view.wv = flat.wv.views.data();
554 flat.view.c32 = flat.constants32.empty() ? nullptr : flat.constants32.data();
558FlattenedCircuitView flatten_circuit_view(
const PackedCircuit& circuit) {
559 FlattenedCircuitView flat;
560 const std::size_t implicit_bit_constraints = 2 * circuit.n_bits();
561 assert(circuit.constraint_count() >= implicit_bit_constraints
562 &&
"packed circuit must contain all implicit bit constraints");
563 flat.wl = flatten_row_family_generic(circuit.n_gates(),
564 [&](std::size_t i) { return circuit.left_row(i).entries_view(); },
565 implicit_bit_constraints);
566 flat.wr = flatten_row_family_generic(circuit.n_gates(),
567 [&](std::size_t i) { return circuit.right_row(i).entries_view(); },
568 implicit_bit_constraints);
569 flat.wo = flatten_row_family_generic(circuit.n_gates(),
570 [&](std::size_t i) { return circuit.output_row(i).entries_view(); },
571 implicit_bit_constraints);
572 flat.wv = flatten_row_family_generic(circuit.n_commitments(),
573 [&](std::size_t i) { return circuit.commitment_row(i).entries_view(); },
574 implicit_bit_constraints);
575 std::vector<FieldElement> explicit_constants;
576 std::span<const FieldElement> constants = circuit.constants();
577 if (constants.size() > implicit_bit_constraints) {
578 const FieldElement* constants_begin = constants.data();
579 explicit_constants.assign(constants_begin +
static_cast<std::ptrdiff_t
>(implicit_bit_constraints),
580 constants_begin +
static_cast<std::ptrdiff_t
>(constants.size()));
582 flat.constants32 = flatten_scalars32(explicit_constants);
583 flat.view.n_gates = circuit.n_gates();
584 flat.view.n_commits = circuit.n_commitments();
585 flat.view.n_bits = circuit.n_bits();
586 flat.view.n_constraints = explicit_constants.size();
587 flat.view.wl = flat.wl.views.data();
588 flat.view.wr = flat.wr.views.data();
589 flat.view.wo = flat.wo.views.data();
590 flat.view.wv = flat.wv.views.data();
591 flat.view.c32 = flat.constants32.empty() ? nullptr : flat.constants32.data();
595struct FlattenedAssignmentView {
604 FlattenedAssignmentView flat;
605 flat.left32 = flatten_scalars32(assignment.
left);
606 flat.right32 = flatten_scalars32(assignment.
right);
607 flat.output32 = flatten_scalars32(assignment.
output);
608 flat.commitments32 = flatten_scalars32(assignment.
commitments);
609 flat.view.n_gates = assignment.
left.size();
610 flat.view.n_commits = assignment.
commitments.size();
611 flat.view.al32 = flat.left32.empty() ? nullptr : flat.left32.data();
612 flat.view.ar32 = flat.right32.empty() ? nullptr : flat.right32.data();
613 flat.view.ao32 = flat.output32.empty() ? nullptr : flat.output32.data();
614 flat.view.v32 = flat.commitments32.empty() ? nullptr : flat.commitments32.data();
623 return circuit.n_gates();
631 return circuit.n_commitments();
634struct ResolvedBulletproofBackendResources {
638ResolvedBulletproofBackendResources resolve_bulletproof_backend_resources(
642 ResolvedBulletproofBackendResources out;
644 if (cache !=
nullptr) {
650template <
typename CircuitLike>
651Result<ExperimentalBulletproofProof> prove_experimental_circuit_impl(
652 const CircuitLike& circuit,
653 const BulletproofAssignmentData& assignment,
654 const BulletproofScalarBytes&
nonce,
655 const BulletproofGeneratorBytes& value_generator,
657 std::span<const unsigned char> statement_binding,
658 std::optional<BulletproofScalarBytes> blind,
659 ExperimentalBulletproofBackendCache* backend_cache,
660 bool require_assignment_validation,
661 const char* shape_context,
662 const char* gates_context,
663 const char* commitments_context,
664 const char* assignment_shape_context,
665 const char* assignment_invalid_context,
666 const char* proof_size_context,
667 const char* bridge_context) {
669 "prove_experimental_circuit:secp_context");
670 ExperimentalBulletproofProof out;
671 if (!circuit.has_valid_shape()) {
686 if (require_assignment_validation && !circuit.evaluate(assignment)) {
687 return unexpected_error(ErrorCode::EquationMismatch, assignment_invalid_context);
690 FlattenedCircuitView flat_circuit = flatten_circuit_view(circuit);
691 FlattenedAssignmentView flat_assignment = flatten_assignment_view(assignment);
693 "prove_experimental_circuit_impl:binding_digest");
695 std::size_t proof_len = proof_bytes.size();
697 const unsigned char* blind_ptr = blind.has_value() ? blind->data() :
nullptr;
698 ResolvedBulletproofBackendResources resolved =
699 resolve_bulletproof_backend_resources(
circuit_n_gates(circuit), secp_context, backend_cache);
702 if (proof_len == 0) {
707 ? (require_assignment_validation
709 resources, &flat_circuit.view, &flat_assignment.view, blind_ptr,
710 value_generator.data(),
nonce.data(),
712 commitment.data(), proof_bytes.data(), &proof_len)
714 resources, &flat_circuit.view, &flat_assignment.view, blind_ptr,
715 value_generator.data(),
nonce.data(),
717 commitment.data(), proof_bytes.data(), &proof_len))
718 : (require_assignment_validation
720 secp_context, &flat_circuit.view, &flat_assignment.view, blind_ptr,
721 value_generator.data(),
nonce.data(),
723 commitment.data(), proof_bytes.data(), &proof_len)
725 secp_context, &flat_circuit.view, &flat_assignment.view, blind_ptr,
726 value_generator.data(),
nonce.data(),
728 commitment.data(), proof_bytes.data(), &proof_len));
733 proof_bytes.resize(proof_len);
734 out.commitment = commitment;
735 out.proof = std::move(proof_bytes);
739Result<FieldElement> evaluate_expr_with_assignment(
const Expr& expr,
741 FieldElement out = expr.constant();
742 for (
const auto& term : expr.linear()) {
743 std::size_t index = term.first.index;
744 switch (term.first.kind) {
745 case SymbolKind::Left:
746 if (index >= assignment.
left.size()) {
748 "evaluate_expr_with_assignment:left_index");
750 out = out + (assignment.
left[index] * term.second);
752 case SymbolKind::Right:
753 if (index >= assignment.
right.size()) {
755 "evaluate_expr_with_assignment:right_index");
757 out = out + (assignment.
right[index] * term.second);
759 case SymbolKind::Output:
760 if (index >= assignment.
output.size()) {
762 "evaluate_expr_with_assignment:output_index");
764 out = out + (assignment.
output[index] * term.second);
766 case SymbolKind::Commitment:
769 "evaluate_expr_with_assignment:commitment_index");
771 out = out + (assignment.
commitments[index] * term.second);
773 case SymbolKind::Witness:
775 "evaluate_expr_with_assignment:witness_symbol");
796 std::size_t scalar_count = 0;
797 std::size_t serialized_size = 0;
806 out.reserve(serialized_size);
807 auto append_u32_le = [&](std::uint32_t value) {
808 for (
int i = 0; i < 4; ++i) {
809 out.push_back(
static_cast<unsigned char>((value >> (8 * i)) & 0xffU));
812 auto append_u64_le = [&](std::uint64_t value) {
813 for (
int i = 0; i < 8; ++i) {
814 out.push_back(
static_cast<unsigned char>((value >> (8 * i)) & 0xffU));
817 auto write_column = [&](
const std::vector<FieldElement>& column) {
819 out.push_back(
static_cast<unsigned char>(0x20));
820 std::array<unsigned char, 32> bytes = value.to_bytes_le();
821 out.insert(out.end(), bytes.begin(), bytes.end());
826 append_u32_le(
static_cast<std::uint32_t
>(
commitments.size()));
827 append_u64_le(
static_cast<std::uint64_t
>(
left.size()));
843 : n_gates(gates), n_commitments(commitments), n_bits(bits), wl(gates), wr(gates), wo(gates), wv(commitments) {}
849 wl.assign(gates, {});
850 wr.assign(gates, {});
851 wo.assign(gates, {});
852 wv.assign(commitments, {});
864 c.push_back(constant);
895 for (std::size_t i = 0; i <
n_gates; ++i) {
896 if (assignment.
left[i] * assignment.
right[i] != assignment.
output[i]) {
902 auto accumulate = [&](
const std::vector<NativeBulletproofCircuitRow>& rows,
903 const std::vector<FieldElement>& values,
904 bool negate_values =
false) {
905 if (rows.size() != values.size()) {
908 for (std::size_t i = 0; i < rows.size(); ++i) {
912 if (entry.idx >= acc.size()) {
915 acc[entry.idx] = acc[entry.idx] + entry.scalar * negated;
920 if (entry.idx >= acc.size()) {
923 acc[entry.idx] = acc[entry.idx] + entry.scalar * values[i];
928 if (!accumulate(
wl, assignment.
left) || !accumulate(
wr, assignment.
right) || !accumulate(
wo, assignment.
output)) {
935 for (std::size_t i = 0; i <
c.size(); ++i) {
936 if (acc[i] !=
c[i]) {
943void NativeBulletproofCircuit::add_row_term(std::vector<NativeBulletproofCircuitRow>& rows, std::size_t expected_size,
946 assert(rows.size() == expected_size &&
"NativeBulletproofCircuit rows must be initialized before adding terms");
947 assert(row_idx < rows.size() &&
"NativeBulletproofCircuit row index out of range");
948 rows[row_idx].add(constraint_idx,
scalar);
951void NativeBulletproofCircuit::PackedWithSlack::PackedStorageDeleter::operator()(std::byte* storage)
const noexcept {
952 if (storage !=
nullptr) {
953 ::operator
delete(storage, std::align_val_t(NativeBulletproofCircuit::PackedWithSlack::kPackedStorageAlignment));
957std::byte* NativeBulletproofCircuit::PackedWithSlack::allocate_storage(std::size_t bytes) {
961 return static_cast<std::byte*
>(::operator
new(bytes, std::align_val_t(kPackedStorageAlignment)));
965 : n_gates_(other.n_gates_), n_commitments_(other.n_commitments_), n_bits_(other.n_bits_),
966 constraint_size_(other.constraint_size_), constraint_base_size_(other.constraint_base_size_),
967 constraint_capacity_(other.constraint_capacity_), term_capacity_(other.term_capacity_),
968 term_bytes_offset_(other.term_bytes_offset_), constant_bytes_offset_(other.constant_bytes_offset_),
969 storage_bytes_(other.storage_bytes_) {
970 assert((other.storage_ !=
nullptr) == (other.storage_bytes_ != 0)
971 &&
"PackedWithSlack source storage must match its byte count");
972 if (storage_bytes_ != 0) {
973 storage_.reset(allocate_storage(storage_bytes_));
974 start_storage_lifetimes();
975 std::memcpy(storage_.get(), other.storage_.get(), storage_bytes_);
980 : n_gates_(other.n_gates_), n_commitments_(other.n_commitments_), n_bits_(other.n_bits_),
981 constraint_size_(other.constraint_size_), constraint_base_size_(other.constraint_base_size_),
982 constraint_capacity_(other.constraint_capacity_), term_capacity_(other.term_capacity_),
983 term_bytes_offset_(other.term_bytes_offset_), constant_bytes_offset_(other.constant_bytes_offset_),
984 storage_bytes_(other.storage_bytes_), storage_(std::move(other.storage_)) {
985 other.reset_to_empty();
990 if (
this == &other) {
994 *
this = std::move(copy);
1000 if (
this == &other) {
1003 n_gates_ = other.n_gates_;
1004 n_commitments_ = other.n_commitments_;
1005 n_bits_ = other.n_bits_;
1006 constraint_size_ = other.constraint_size_;
1007 constraint_base_size_ = other.constraint_base_size_;
1008 constraint_capacity_ = other.constraint_capacity_;
1009 term_capacity_ = other.term_capacity_;
1010 term_bytes_offset_ = other.term_bytes_offset_;
1011 constant_bytes_offset_ = other.constant_bytes_offset_;
1012 storage_bytes_ = other.storage_bytes_;
1013 storage_ = std::move(other.storage_);
1014 other.reset_to_empty();
1018bool NativeBulletproofCircuit::PackedWithSlack::compute_storage_layout(std::size_t row_count, std::size_t term_capacity,
1019 std::size_t constraint_capacity,
1020 std::size_t& term_bytes_offset,
1021 std::size_t& constant_bytes_offset,
1022 std::size_t& storage_bytes)
noexcept {
1023 std::size_t row_headers_bytes = 0;
1024 if (!
checked_mul_size(row_count,
sizeof(PackedRowHeader), row_headers_bytes)
1028 std::size_t terms_bytes = 0;
1032 std::size_t term_region_end = 0;
1033 if (!checked_add_size(term_bytes_offset, terms_bytes, term_region_end)
1034 || !checked_align_up(term_region_end,
alignof(FieldElement), constant_bytes_offset)) {
1037 std::size_t constants_bytes = 0;
1038 if (!
checked_mul_size(constraint_capacity,
sizeof(FieldElement), constants_bytes)) {
1041 return checked_add_size(constant_bytes_offset, constants_bytes, storage_bytes);
1044void NativeBulletproofCircuit::PackedWithSlack::reset_to_empty() noexcept {
1049 constraint_size_ = 0;
1050 constraint_base_size_ = 0;
1051 constraint_capacity_ = 0;
1053 term_bytes_offset_ = 0;
1054 constant_bytes_offset_ = 0;
1059 if (constraint_base_size_ > constraint_size_ || constraint_size_ > constraint_capacity_) {
1062 std::size_t row_count = 0;
1063 if (!packed_row_count(n_gates_, n_commitments_, row_count)) {
1066 std::size_t expected_term_offset = 0;
1067 std::size_t expected_constant_offset = 0;
1068 std::size_t expected_storage_bytes = 0;
1069 if (!compute_storage_layout(row_count, term_capacity_, constraint_capacity_,
1070 expected_term_offset, expected_constant_offset, expected_storage_bytes)) {
1073 if (term_bytes_offset_ != expected_term_offset || constant_bytes_offset_ != expected_constant_offset
1074 || storage_bytes_ != expected_storage_bytes) {
1077 if ((storage_ ==
nullptr) != (storage_bytes_ == 0)) {
1080 if (storage_bytes_ == 0) {
1081 return row_count == 0 && term_capacity_ == 0 && constraint_capacity_ == 0;
1084 const PackedRowHeader* headers =
1085 std::launder(
reinterpret_cast<const PackedRowHeader*
>(raw_storage_bytes()));
1086 std::size_t term_cursor = 0;
1087 for (std::size_t i = 0; i < row_count; ++i) {
1088 const PackedRowHeader& header = headers[i];
1089 if (header.base_size > header.size || header.size > header.capacity) {
1092 if (header.offset != term_cursor) {
1095 if (term_cursor > term_capacity_ || header.capacity > term_capacity_ - term_cursor) {
1098 term_cursor += header.capacity;
1100 return term_cursor == term_capacity_;
1104 constraint_size_ = constraint_base_size_;
1105 std::size_t row_count = 0;
1106 [[maybe_unused]]
const bool ok = packed_row_count(n_gates_, n_commitments_, row_count);
1107 assert(ok &&
"PackedWithSlack row count should fit in size_t");
1108 PackedRowHeader* headers = row_count == 0 ? nullptr
1109 : std::launder(
reinterpret_cast<PackedRowHeader*
>(raw_storage_bytes()));
1110 for (std::size_t i = 0; i < row_count; ++i) {
1111 PackedRowHeader& header = headers[i];
1112 header.size = header.base_size;
1116NativeBulletproofCircuit::PackedWithSlack::PackedRowHeader&
1117NativeBulletproofCircuit::PackedWithSlack::row_header(RowFamily family, std::size_t idx)
noexcept {
1118 PackedRowHeader* headers = std::launder(
reinterpret_cast<PackedRowHeader*
>(raw_storage_bytes()));
1119 std::size_t base = 0;
1121 case RowFamily::Left:
1124 case RowFamily::Right:
1127 case RowFamily::Output:
1128 base = 2 * n_gates_;
1130 case RowFamily::Commitment:
1131 base = 3 * n_gates_;
1134 return headers[base + idx];
1137const NativeBulletproofCircuit::PackedWithSlack::PackedRowHeader&
1138NativeBulletproofCircuit::PackedWithSlack::row_header(RowFamily family, std::size_t idx)
const noexcept {
1139 const PackedRowHeader* headers = std::launder(
reinterpret_cast<const PackedRowHeader*
>(raw_storage_bytes()));
1140 std::size_t base = 0;
1142 case RowFamily::Left:
1145 case RowFamily::Right:
1148 case RowFamily::Output:
1149 base = 2 * n_gates_;
1151 case RowFamily::Commitment:
1152 base = 3 * n_gates_;
1155 return headers[base + idx];
1158NativeBulletproofCircuitTerm* NativeBulletproofCircuit::PackedWithSlack::term_data() noexcept {
1159 if (term_capacity_ == 0) {
1162 return std::launder(
reinterpret_cast<NativeBulletproofCircuitTerm*
>(raw_storage_bytes() + term_bytes_offset_));
1165const NativeBulletproofCircuitTerm* NativeBulletproofCircuit::PackedWithSlack::term_data() const noexcept {
1166 if (term_capacity_ == 0) {
1169 return std::launder(
reinterpret_cast<const NativeBulletproofCircuitTerm*
>(raw_storage_bytes() + term_bytes_offset_));
1172FieldElement* NativeBulletproofCircuit::PackedWithSlack::constant_data() noexcept {
1173 if (constraint_capacity_ == 0) {
1176 return std::launder(
reinterpret_cast<FieldElement*
>(raw_storage_bytes() + constant_bytes_offset_));
1179const FieldElement* NativeBulletproofCircuit::PackedWithSlack::constant_data() const noexcept {
1180 if (constraint_capacity_ == 0) {
1183 return std::launder(
reinterpret_cast<const FieldElement*
>(raw_storage_bytes() + constant_bytes_offset_));
1186unsigned char* NativeBulletproofCircuit::PackedWithSlack::raw_storage_bytes() noexcept {
1187 return reinterpret_cast<unsigned char*
>(storage_.get());
1190const unsigned char* NativeBulletproofCircuit::PackedWithSlack::raw_storage_bytes() const noexcept {
1191 return reinterpret_cast<const unsigned char*
>(storage_.get());
1194void NativeBulletproofCircuit::PackedWithSlack::start_storage_lifetimes() noexcept {
1195 if (storage_ ==
nullptr) {
1198 std::size_t row_count = 0;
1199 [[maybe_unused]]
const bool ok = packed_row_count(n_gates_, n_commitments_, row_count);
1200 assert(ok &&
"PackedWithSlack row count should fit in size_t");
1201 if (row_count != 0) {
1202 PackedRowHeader* headers =
reinterpret_cast<PackedRowHeader*
>(raw_storage_bytes());
1203 std::uninitialized_value_construct_n(headers, row_count);
1205 if (term_capacity_ != 0) {
1206 NativeBulletproofCircuitTerm* terms =
1207 reinterpret_cast<NativeBulletproofCircuitTerm*
>(raw_storage_bytes() + term_bytes_offset_);
1208 std::uninitialized_value_construct_n(terms, term_capacity_);
1210 if (constraint_capacity_ != 0) {
1211 FieldElement* constants =
reinterpret_cast<FieldElement*
>(raw_storage_bytes() + constant_bytes_offset_);
1212 std::uninitialized_value_construct_n(constants, constraint_capacity_);
1216NativeBulletproofCircuitRow::PackedWithSlack
1217NativeBulletproofCircuit::PackedWithSlack::row_view(
const PackedRowHeader& header)
const noexcept {
1218 const NativeBulletproofCircuitTerm* terms = term_data();
1219 return {header.capacity == 0 ? nullptr : terms + header.offset, header.size, header.capacity};
1223 assert(constraint_size_ < constraint_capacity_ &&
"PackedWithSlack constraint capacity exceeded");
1225 constants[constraint_size_] = constant;
1227 return constraint_size_ - 1;
1230void NativeBulletproofCircuit::PackedWithSlack::add_row_term(RowFamily family, std::size_t expected_size,
1231 std::size_t row_idx, std::size_t constraint_idx,
1233 (void)expected_size;
1237 assert(constraint_idx < constraint_size_ &&
"PackedWithSlack constraint index out of range");
1238 assert(((family == RowFamily::Commitment) ? n_commitments_ : n_gates_) == expected_size
1239 &&
"PackedWithSlack expected row family size mismatch");
1240 assert(row_idx < expected_size &&
"PackedWithSlack row index out of range");
1241 PackedRowHeader& header = row_header(family, row_idx);
1242 assert(header.size < header.capacity &&
"PackedWithSlack row capacity exceeded");
1243 term_data()[header.offset + header.size] = {constraint_idx,
scalar};
1249 add_row_term(RowFamily::Left, n_gates_, gate_idx, constraint_idx,
scalar);
1254 add_row_term(RowFamily::Right, n_gates_, gate_idx, constraint_idx,
scalar);
1259 add_row_term(RowFamily::Output, n_gates_, gate_idx, constraint_idx,
scalar);
1264 add_row_term(RowFamily::Commitment, n_commitments_, commitment_idx, constraint_idx,
scalar.negate());
1269 return row_view(row_header(RowFamily::Left, gate_idx));
1274 return row_view(row_header(RowFamily::Right, gate_idx));
1279 return row_view(row_header(RowFamily::Output, gate_idx));
1284 return row_view(row_header(RowFamily::Commitment, commitment_idx));
1288 return std::span<const FieldElement>(constant_data(), constraint_size_);
1295 if (assignment.
left.size() != n_gates_ || assignment.
right.size() != n_gates_ || assignment.
output.size() != n_gates_) {
1298 if (assignment.
commitments.size() != n_commitments_) {
1301 for (std::size_t i = 0; i < n_gates_; ++i) {
1302 if (assignment.
left[i] * assignment.
right[i] != assignment.
output[i]) {
1309 auto accumulate = [&](RowFamily family, std::size_t row_count,
const std::vector<FieldElement>& values,
1310 bool negate_values =
false) {
1311 if (values.size() != row_count) {
1314 for (std::size_t i = 0; i < row_count; ++i) {
1315 const PackedRowHeader& header = row_header(family, i);
1317 if (header.size == 0) {
1321 for (std::size_t j = 0; j < header.size; ++j) {
1323 if (entry.
idx >= acc.size()) {
1326 acc[entry.
idx] = acc[entry.
idx] + (entry.
scalar * factor);
1331 if (!accumulate(RowFamily::Left, n_gates_, assignment.
left)
1332 || !accumulate(RowFamily::Right, n_gates_, assignment.
right)
1333 || !accumulate(RowFamily::Output, n_gates_, assignment.
output)
1334 || !accumulate(RowFamily::Commitment, n_commitments_, assignment.
commitments,
true)) {
1337 for (std::size_t i = 0; i < constraint_size_; ++i) {
1338 if (acc[i] != constant_data()[i]) {
1350 if (constraint_size_ != 0) {
1352 out.
c.assign(constants, constants +
static_cast<std::ptrdiff_t
>(constraint_size_));
1355 auto unpack_family = [&](RowFamily family, std::vector<NativeBulletproofCircuitRow>& rows, std::size_t row_count) {
1356 for (std::size_t i = 0; i < row_count; ++i) {
1357 const PackedRowHeader& header = row_header(family, i);
1358 if (header.size == 0) {
1359 rows[i].entries.clear();
1362 rows[i].entries.assign(all_terms + header.offset,
1363 all_terms + header.offset +
static_cast<std::ptrdiff_t
>(header.size));
1366 unpack_family(RowFamily::Left, out.
wl, n_gates_);
1367 unpack_family(RowFamily::Right, out.
wr, n_gates_);
1368 unpack_family(RowFamily::Output, out.
wo, n_gates_);
1369 unpack_family(RowFamily::Commitment, out.
wv, n_commitments_);
1379 auto validate_family_slack = [](
const std::vector<std::size_t>& family_slack, std::size_t expected,
1380 const char* context) ->
Status {
1381 if (!family_slack.empty() && family_slack.size() != expected) {
1387 "NativeBulletproofCircuit::PackedWithSlack::from_circuit:wl");
1389 return unexpected_error(wl_status.
error(),
"NativeBulletproofCircuit::PackedWithSlack::from_circuit:wl");
1392 "NativeBulletproofCircuit::PackedWithSlack::from_circuit:wr");
1394 return unexpected_error(wr_status.
error(),
"NativeBulletproofCircuit::PackedWithSlack::from_circuit:wr");
1397 "NativeBulletproofCircuit::PackedWithSlack::from_circuit:wo");
1399 return unexpected_error(wo_status.
error(),
"NativeBulletproofCircuit::PackedWithSlack::from_circuit:wo");
1402 "NativeBulletproofCircuit::PackedWithSlack::from_circuit:wv");
1404 return unexpected_error(wv_status.
error(),
"NativeBulletproofCircuit::PackedWithSlack::from_circuit:wv");
1408 packed.n_gates_ = circuit.
n_gates;
1410 packed.n_bits_ = circuit.
n_bits;
1411 packed.constraint_size_ = circuit.
c.size();
1412 packed.constraint_base_size_ = circuit.
c.size();
1417 std::size_t row_count = 0;
1421 std::size_t total_term_capacity = 0;
1422 auto sum_capacity = [&](
const std::vector<NativeBulletproofCircuitRow>& rows,
1423 const std::vector<std::size_t>& family_slack) ->
bool {
1424 for (std::size_t i = 0; i < rows.size(); ++i) {
1425 const std::size_t extra = family_slack.empty() ? 0 : family_slack[i];
1426 std::size_t row_capacity = 0;
1428 || !
checked_add_size(total_term_capacity, row_capacity, total_term_capacity)) {
1434 if (!sum_capacity(circuit.
wl, slack.
wl)
1435 || !sum_capacity(circuit.
wr, slack.
wr)
1436 || !sum_capacity(circuit.
wo, slack.
wo)
1437 || !sum_capacity(circuit.
wv, slack.
wv)) {
1440 packed.term_capacity_ = total_term_capacity;
1441 if (!compute_storage_layout(row_count, packed.term_capacity_, packed.constraint_capacity_,
1442 packed.term_bytes_offset_, packed.constant_bytes_offset_, packed.storage_bytes_)) {
1445 packed.storage_.reset(allocate_storage(packed.storage_bytes_));
1446 packed.start_storage_lifetimes();
1448 PackedWithSlack::PackedRowHeader* headers =
1449 row_count == 0 ? nullptr : std::launder(
reinterpret_cast<PackedWithSlack::PackedRowHeader*
>(packed.raw_storage_bytes()));
1451 if (packed.constraint_capacity_ != 0) {
1453 std::copy(circuit.
c.begin(), circuit.
c.end(), constants);
1456 std::size_t row_cursor = 0;
1457 std::size_t term_cursor = 0;
1458 auto fill_family = [&](
const std::vector<NativeBulletproofCircuitRow>& rows,
1459 const std::vector<std::size_t>& family_slack) {
1460 for (std::size_t i = 0; i < rows.size(); ++i, ++row_cursor) {
1461 const std::size_t extra = family_slack.empty() ? 0 : family_slack[i];
1462 PackedWithSlack::PackedRowHeader& header = headers[row_cursor];
1463 header.offset = term_cursor;
1464 header.size = rows[i].entries.size();
1465 header.base_size = rows[i].entries.size();
1466 header.capacity = rows[i].entries.size() + extra;
1467 if (!rows[i].entries.empty()) {
1468 std::copy(rows[i].entries.begin(), rows[i].entries.end(),
1469 packed.term_data() +
static_cast<std::ptrdiff_t
>(term_cursor));
1471 term_cursor += header.capacity;
1474 fill_family(circuit.
wl, slack.
wl);
1475 fill_family(circuit.
wr, slack.
wr);
1476 fill_family(circuit.
wo, slack.
wo);
1477 fill_family(circuit.
wv, slack.
wv);
1478 assert(term_cursor == packed.term_capacity_ &&
"packed term cursor should consume the entire term buffer");
1492 if (proof.size() >
static_cast<std::size_t
>(std::numeric_limits<std::uint32_t>::max())) {
1495 std::size_t serialized_size = 0;
1501 out.reserve(serialized_size);
1502 out.push_back(kSerializationVersion);
1503 append_u32_le(out,
static_cast<std::uint32_t
>(proof.size()));
1504 out.insert(out.end(), commitment.begin(), commitment.end());
1505 out.insert(out.end(), proof.begin(), proof.end());
1511 if (bytes.size() < 38) {
1514 if (bytes[0] != kSerializationVersion) {
1518 std::optional<std::uint32_t> proof_size = read_u32_le(bytes, 1);
1519 assert(proof_size.has_value() &&
"header length check should guarantee a proof length");
1520 std::size_t offset = 5;
1521 if (bytes.size() - offset < 33) {
1524 std::copy_n(bytes.begin() +
static_cast<std::ptrdiff_t
>(offset), 33, out.
commitment.begin());
1526 if (*proof_size != bytes.size() - offset) {
1529 out.
proof.assign(bytes.begin() +
static_cast<std::ptrdiff_t
>(offset), bytes.end());
1539 std::span<const unsigned char> statement_binding,
1540 std::optional<BulletproofScalarBytes> blind,
1542 return prove_experimental_circuit_impl(circuit, assignment,
nonce, value_generator, secp_context, statement_binding,
1543 blind, backend_cache,
true,
1544 "prove_experimental_circuit:circuit_shape",
1545 "prove_experimental_circuit:n_gates_power_of_two",
1546 "prove_experimental_circuit:n_commitments",
1547 "prove_experimental_circuit:assignment_shape",
1548 "prove_experimental_circuit:assignment_invalid",
1549 "prove_experimental_circuit:proof_size",
1550 "prove_experimental_circuit:bridge");
1559 std::span<const unsigned char> statement_binding,
1560 std::optional<BulletproofScalarBytes> blind,
1562 return prove_experimental_circuit_impl(circuit, assignment,
nonce, value_generator, secp_context, statement_binding,
1563 blind, backend_cache,
true,
1564 "prove_experimental_circuit:packed_circuit_shape",
1565 "prove_experimental_circuit:packed_n_gates_power_of_two",
1566 "prove_experimental_circuit:packed_n_commitments",
1567 "prove_experimental_circuit:packed_assignment_shape",
1568 "prove_experimental_circuit:packed_assignment_invalid",
1569 "prove_experimental_circuit:packed_proof_size",
1570 "prove_experimental_circuit:packed_bridge");
1579 std::span<const unsigned char> statement_binding,
1580 std::optional<BulletproofScalarBytes> blind,
1582 return prove_experimental_circuit_impl(circuit, assignment,
nonce, value_generator, secp_context, statement_binding,
1583 blind, backend_cache,
false,
1584 "prove_experimental_circuit_assume_valid:packed_circuit_shape",
1585 "prove_experimental_circuit_assume_valid:packed_n_gates_power_of_two",
1586 "prove_experimental_circuit_assume_valid:packed_n_commitments",
1587 "prove_experimental_circuit_assume_valid:packed_assignment_shape",
1588 "prove_experimental_circuit_assume_valid:packed_assignment_invalid",
1589 "prove_experimental_circuit_assume_valid:packed_proof_size",
1590 "prove_experimental_circuit_assume_valid:packed_bridge");
1598 std::span<const unsigned char> statement_binding,
1601 "verify_experimental_circuit:secp_context");
1611 if (proof.
proof.empty()) {
1615 FlattenedCircuitView flat_circuit = flatten_circuit_view(circuit);
1617 "verify_experimental_circuit:binding_digest");
1618 ResolvedBulletproofBackendResources resolved =
1619 resolve_bulletproof_backend_resources(circuit.
n_gates, secp_context, backend_cache);
1622 resources !=
nullptr
1626 proof.
proof.size()) != 0
1630 proof.
proof.size()) != 0;
1639 std::span<const unsigned char> statement_binding,
1642 "verify_experimental_circuit:packed_secp_context");
1652 if (proof.
proof.empty()) {
1656 FlattenedCircuitView flat_circuit = flatten_circuit_view(circuit);
1658 "verify_experimental_circuit:packed_binding_digest");
1659 ResolvedBulletproofBackendResources resolved =
1660 resolve_bulletproof_backend_resources(circuit.
n_gates(), secp_context, backend_cache);
1663 resources !=
nullptr
1667 proof.
proof.size()) != 0
1671 proof.
proof.size()) != 0;
1676 for (
auto& term : expr.
linear()) {
1678 && term.first.index < witness_to_a_.size()
1679 && witness_to_a_[term.first.index].has_value()) {
1680 term.first = *witness_to_a_[term.first.index];
1686 if (!expr.
linear().empty()) {
1687 replace_expr_v_with_bp_var(expr);
1689 && expr.
linear().size() == 1
1692 std::uint32_t witness_index = expr.
linear()[0].first.index;
1693 if (witness_index < witness_to_a_.size() && !witness_to_a_[witness_index].has_value()) {
1694 witness_to_a_[witness_index] = symbol;
1695 witness_to_a_order_.push_back({witness_index, symbol});
1704 bool is_v = replace_and_insert(expr, symbol);
1705 assignments_.push_back({symbol, std::move(expr), is_v});
1709 assignments_.clear();
1710 constraints_.clear();
1711 witness_to_a_.assign(transcript.
varmap().size(), std::nullopt);
1712 witness_to_a_order_.clear();
1713 n_witnesses_ = transcript.
varmap().size();
1715 std::size_t source_muls = transcript.
muls().size();
1716 for (std::size_t i = 0; i < source_muls; ++i) {
1717 const auto& mul = transcript.
muls()[i];
1718 add_assignment(
Symbol::left(narrow_symbol_index(i)), mul.lhs);
1719 add_assignment(
Symbol::right(narrow_symbol_index(i)), mul.rhs);
1723 std::vector<std::uint32_t> unmapped_transcript_vars;
1724 std::vector<bool> seen_transcript_vars(n_witnesses_,
false);
1725 for (
const Expr& eq : transcript.
eqs()) {
1726 for (
const auto& term : eq.linear()) {
1727 if (!is_transcript_var(term.first)) {
1730 std::uint32_t witness_index = term.first.index;
1731 if (witness_index >= witness_to_a_.size() || witness_to_a_[witness_index].has_value()) {
1734 if (!seen_transcript_vars[witness_index]) {
1735 unmapped_transcript_vars.push_back(witness_index);
1736 seen_transcript_vars[witness_index] =
true;
1741 std::size_t total_gates = source_muls + unmapped_transcript_vars.size();
1743 while (n_muls_ < std::max<std::size_t>(1, total_gates)) {
1747 for (std::size_t i = 0; i < unmapped_transcript_vars.size(); ++i) {
1748 std::size_t gate_idx = source_muls + i;
1749 std::uint32_t gate_symbol = narrow_symbol_index(gate_idx);
1755 for (std::size_t i = total_gates; i < n_muls_; ++i) {
1756 std::uint32_t gate_symbol = narrow_symbol_index(i);
1762 for (
const Expr& eq : transcript.
eqs()) {
1764 replace_expr_v_with_bp_var(lowered);
1765 if (contains_transcript_var(lowered)) {
1768 constraints_.push_back({lowered,
Expr(0)});
1776 return unexpected_error(unpacked.
error(),
"BulletproofTranscript::add_pubkey_and_out:unpack_public");
1779 replace_expr_v_with_bp_var(expr);
1780 auto parts = expr.split();
1783 return unexpected_error(constant.
error(),
"BulletproofTranscript::add_pubkey_and_out:field_constant");
1785 constraints_.push_back({parts.second,
Expr(*constant) - parts.first});
1796 replace_expr_v_with_bp_var(out);
1802 std::size_t n_constraints = 0;
1803 for (
const auto& assignment : assignments_) {
1804 if (!assignment.is_v) {
1808 n_constraints += constraints_.size();
1809 std::ostringstream out;
1810 out << n_muls_ <<
"," << n_commitments_ <<
"," << n_bits_ <<
"," << (n_constraints - 2 * n_bits_) <<
";";
1812 for (
const auto& assignment : assignments_) {
1813 if (!assignment.is_v) {
1814 if (i < 2 * n_bits_) {
1818 auto parts = assignment.expr.split();
1819 out << assignment.symbol.to_string();
1820 if (!parts.second.linear().empty()) {
1821 out <<
" + " << (-parts.second).
to_string();
1823 out <<
" = " << parts.first.to_string() <<
";";
1826 for (
const auto& constraint : constraints_) {
1827 out << constraint.first.to_string() <<
" = " << constraint.second.to_string() <<
";";
1833 ResolvedValues values(n_witnesses_, n_muls_, n_commitments_);
1834 for (std::size_t i = 0; i < std::min(vars.size(), values.witness.size()); ++i) {
1835 values.witness[i] = vars[i];
1840 for (
const auto& item : witness_to_a_order_) {
1841 if (item.first >= values.witness.size() || !values.witness[item.first].has_value()) {
1844 if (!values.set(item.second, *values.witness[item.first])) {
1848 for (
const auto& assignment : assignments_) {
1853 if (!values.set(assignment.symbol, *evaluated)) {
1857 for (std::size_t i = 0; i < n_muls_; ++i) {
1858 if (!values.left[i].has_value() || !values.right[i].has_value() || !values.output[i].has_value()) {
1861 if (*values.left[i] * *values.right[i] != *values.output[i]) {
1865 for (
const auto& constraint : constraints_) {
1879 circuit.
n_bits = n_bits_;
1880 circuit.
wl.resize(n_muls_);
1881 circuit.
wr.resize(n_muls_);
1882 circuit.
wo.resize(n_muls_);
1883 circuit.
wv.resize(n_commitments_);
1885 std::count_if(assignments_.begin(), assignments_.end(),
1886 [](
const auto& assignment) { return !assignment.is_v; })
1887 + constraints_.size());
1889 for (
const auto& assignment : assignments_) {
1890 if (!assignment.is_v) {
1891 append_constraint_to_circuit(circuit,
Expr::variable(assignment.symbol), assignment.expr);
1894 for (
const auto& constraint : constraints_) {
1895 append_constraint_to_circuit(circuit, constraint.first, constraint.second);
1906 template_circuit.base_packed_ = std::move(base_packed);
1907 template_circuit.p1x_ = std::move(p1x);
1908 template_circuit.p2x_ = std::move(p2x);
1909 template_circuit.out_ = std::move(out);
1910 return template_circuit;
1914 if (!base_packed_.has_valid_shape()) {
1917 if (assignment.
left.size() != base_packed_.n_gates()
1918 || assignment.
right.size() != base_packed_.n_gates()
1919 || assignment.
output.size() != base_packed_.n_gates()
1920 || assignment.
commitments.size() != base_packed_.n_commitments()) {
1923 return base_packed_.evaluate(assignment);
1927 const UInt512& pubkey)
const {
1928 if (!base_packed_.has_valid_shape()) {
1931 if (assignment.
left.size() != base_packed_.n_gates()
1932 || assignment.
right.size() != base_packed_.n_gates()
1933 || assignment.
output.size() != base_packed_.n_gates()
1934 || assignment.
commitments.size() != base_packed_.n_commitments()) {
1939 return unexpected_error(unpacked.
error(),
"NativeBulletproofCircuitTemplate::final_evaluate:unpack_public");
1943 return unexpected_error(expected_p1.
error(),
"NativeBulletproofCircuitTemplate::final_evaluate:expected_p1");
1947 return unexpected_error(expected_p2.
error(),
"NativeBulletproofCircuitTemplate::final_evaluate:expected_p2");
1951 return unexpected_error(actual_p1.
error(),
"NativeBulletproofCircuitTemplate::final_evaluate:actual_p1");
1955 return unexpected_error(actual_p2.
error(),
"NativeBulletproofCircuitTemplate::final_evaluate:actual_p2");
1959 return unexpected_error(actual_out.
error(),
"NativeBulletproofCircuitTemplate::final_evaluate:actual_out");
1961 return *actual_p1 == *expected_p1
1962 && *actual_p2 == *expected_p2
1967 if (!base_packed_.has_valid_shape()) {
1971 static const TaggedHash kTemplateDigestTag(
"Purify/VerifierCircuitTemplate/V1");
1973 "NativeBulletproofCircuitTemplate::integrity_digest:circuit_binding_digest");
1975 "NativeBulletproofCircuitTemplate::integrity_digest:p1x");
1977 "NativeBulletproofCircuitTemplate::integrity_digest:p2x");
1979 "NativeBulletproofCircuitTemplate::integrity_digest:out");
1980 const std::array<unsigned char, 32> digest =
1981 kTemplateDigestTag.
digest(std::span<const unsigned char>(serialized.data(), serialized.size()));
1982 return Bytes(digest.begin(), digest.end());
1988 return unexpected_error(unpacked.
error(),
"NativeBulletproofCircuitTemplate::instantiate_packed:unpack_public");
1993 auto append_pubkey_constraint = [&](
const UInt256& packed,
const Expr& expr) ->
Status {
1994 auto parts = expr.split();
1997 return unexpected_error(constant.
error(),
"NativeBulletproofCircuitTemplate::instantiate_packed:field_constant");
1999 append_constraint_to_circuit(circuit, parts.second,
Expr(*constant) - parts.first);
2003 Status p1_status = append_pubkey_constraint(unpacked->first, p1x_);
2005 return unexpected_error(p1_status.
error(),
"NativeBulletproofCircuitTemplate::instantiate_packed:p1x");
2007 Status p2_status = append_pubkey_constraint(unpacked->second, p2x_);
2009 return unexpected_error(p2_status.
error(),
"NativeBulletproofCircuitTemplate::instantiate_packed:p2x");
2018 return unexpected_error(packed.
error(),
"NativeBulletproofCircuitTemplate::instantiate:instantiate_packed");
2020 return packed->unpack();
2024 return assignment_data_impl(vars,
nullptr);
2029 return assignment_data_impl(vars, &commitment);
2034 ResolvedValues values(n_witnesses_, n_muls_, n_commitments_);
2035 for (std::size_t i = 0; i < std::min(vars.size(), values.witness.size()); ++i) {
2036 values.witness[i] = vars[i];
2041 for (
const auto& item : witness_to_a_order_) {
2042 if (item.first >= values.witness.size() || !values.witness[item.first].has_value()) {
2045 if (!values.set(item.second, *values.witness[item.first])) {
2049 for (
const auto& assignment : assignments_) {
2050 Result<FieldElement> evaluated = ::evaluate_known(assignment.expr, values);
2051 if (!evaluated.has_value()) {
2052 return unexpected_error(evaluated.error(),
"BulletproofTranscript::assignment_data:evaluate_assignment");
2054 if (!values.set(assignment.symbol, *evaluated)) {
2059 BulletproofAssignmentData assignment;
2060 assignment.
left.reserve(n_muls_);
2061 assignment.right.reserve(n_muls_);
2062 assignment.output.reserve(n_muls_);
2063 assignment.commitments.reserve(n_commitments_);
2064 auto read_column = [&](
const std::vector<std::optional<FieldElement>>& source,
2065 std::vector<FieldElement>& column) ->
Status {
2066 for (
const auto& value : source) {
2067 if (!value.has_value()) {
2070 column.push_back(*value);
2074 Status left_status = read_column(values.left, assignment.left);
2075 if (!left_status.has_value()) {
2076 return unexpected_error(left_status.error(),
"BulletproofTranscript::assignment_data:left_column");
2078 Status right_status = read_column(values.right, assignment.right);
2079 if (!right_status.has_value()) {
2080 return unexpected_error(right_status.error(),
"BulletproofTranscript::assignment_data:right_column");
2082 Status output_status = read_column(values.output, assignment.output);
2083 if (!output_status.has_value()) {
2084 return unexpected_error(output_status.error(),
"BulletproofTranscript::assignment_data:output_column");
2086 for (
const auto& value : values.commitment) {
2087 if (!value.has_value()) {
2090 assignment.commitments.push_back(*value);
2098 return unexpected_error(assignment.
error(),
"BulletproofTranscript::serialize_assignment:assignment_data");
2100 return assignment->serialize();
2103bool BulletproofTranscript::is_transcript_var(
Symbol symbol) {
2107bool BulletproofTranscript::contains_transcript_var(
const Expr& expr) {
2108 return std::any_of(expr.linear().begin(), expr.linear().end(),
2109 [](
const auto& term) { return is_transcript_var(term.first); });
2120 Expr xy = transcript.
mul(x, y);
2125 .
add_scaled(xy, values[0] + values[3] - values[1] - values[2])
2130 Expr xy = transcript.
mul(x, y);
2131 Expr yz = transcript.
mul(y, z);
2132 Expr zx = transcript.
mul(z, x);
2133 Expr xyz = transcript.
mul(xy, z);
2140 .
add_scaled(xy, values[0] + values[3] - values[1] - values[2])
2141 .
add_scaled(zx, values[0] + values[5] - values[1] - values[4])
2142 .
add_scaled(yz, values[0] + values[6] - values[2] - values[4])
2143 .
add_scaled(xyz, values[1] + values[2] + values[4] + values[7] - values[0] - values[3] - values[5] - values[6])
2149 std::array<AffinePoint, 2> affine_points{curve.
affine(points[0]), curve.
affine(points[1])};
2151 circuit_1bit({affine_points[0].x, affine_points[1].x}, transcript, b0),
2152 circuit_1bit({affine_points[0].y, affine_points[1].y}, transcript, b0)
2158 std::array<AffinePoint, 4> affine_points{curve.
affine(points[0]), curve.
affine(points[1]), curve.
affine(points[2]), curve.
affine(points[3])};
2160 circuit_2bit({affine_points[0].x, affine_points[1].x, affine_points[2].x, affine_points[3].x}, transcript, b0, b1),
2161 circuit_2bit({affine_points[0].y, affine_points[1].y, affine_points[2].y, affine_points[3].y}, transcript, b0, b1)
2167 std::array<AffinePoint, 8> affine_points{
2172 circuit_3bit({affine_points[0].x, affine_points[1].x, affine_points[2].x, affine_points[3].x,
2173 affine_points[4].x, affine_points[5].x, affine_points[6].x, affine_points[7].x},
2174 transcript, b0, b1, b2),
2175 circuit_3bit({affine_points[0].y, affine_points[1].y, affine_points[2].y, affine_points[3].y,
2176 affine_points[4].y, affine_points[5].y, affine_points[6].y, affine_points[7].y},
2177 transcript, b0, b1, b2)
2182 return {point.first, transcript.
mul(
Expr(1) - 2 * negate_bit, point.second)};
2186 Expr lambda = transcript.
div(p2.second - p1.second, p2.first - p1.first);
2187 Expr lambda_sq = transcript.
mul(lambda, lambda);
2197 Expr lambda_delta = transcript.
mul(lambda, delta);
2206 Expr lambda = transcript.
div(p2.second - p1.second, p2.first - p1.first);
2207 Expr lambda_sq = transcript.
mul(lambda, lambda);
2216 const JacobianPoint& point,
const std::vector<Expr>& bits) {
2217 std::vector<JacobianPoint> powers;
2218 powers.reserve(bits.size());
2219 powers.push_back(point);
2220 for (std::size_t i = 1; i < bits.size(); ++i) {
2224 std::vector<ExprPoint> lookups;
2225 for (std::size_t i = 0; i < (bits.size() - 1) / 3; ++i) {
2231 circuit_2bit_point(curve, {p1, p3, p5, p7}, transcript, bits[i * 3 + 1], bits[i * 3 + 2]),
2236 if (bits.size() % 3 == 0) {
2245 lookups.push_back(
circuit_3bit_point(curve, {pn, pn1, p3n, p3n1, p5n, p5n1, p7n, p7n1},
2246 transcript, bits[0], bits[bits.size() - 2], bits[bits.size() - 1]));
2247 }
else if (bits.size() % 3 == 1) {
2256 lookups.push_back(
circuit_2bit_point(curve, {pn, pn1, p3n, p3n1}, transcript, bits[0], bits.back()));
2260 for (std::size_t i = 1; i + 1 < lookups.size(); ++i) {
2274 Expr uv = transcript.
mul(x1, v);
2279 Expr numerator_mul = transcript.
mul(u_plus_v, uv_plus_a);
2288 Expr denominator = transcript.
mul(u_minus_v, u_minus_v);
2289 return transcript.
div(numerator, denominator);
2293 const std::optional<UInt256>& z1,
const std::optional<UInt256>& z2) {
2296 std::vector<int> z1_values(
static_cast<std::size_t
>(z1_bits_len), -1);
2297 std::vector<int> z2_values(
static_cast<std::size_t
>(z2_bits_len), -1);
2298 if (z1.has_value() && z2.has_value()) {
2307 z1_values = *z1_result;
2308 z2_values = *z2_result;
2310 std::vector<Expr> z1_bits;
2311 std::vector<Expr> z2_bits;
2312 z1_bits.reserve(z1_values.size());
2313 z2_bits.reserve(z2_values.size());
2314 for (
int bit : z1_values) {
2317 for (
int bit : z2_values) {
2320 std::size_t
n_bits = z1_bits.size() + z2_bits.size();
2334 std::span<const unsigned char> statement_binding) {
2335 return ::circuit_binding_digest(circuit, statement_binding);
2340 std::span<const unsigned char> statement_binding) {
2341 return ::circuit_binding_digest(circuit, statement_binding);
int purify_bulletproof_prove_circuit(purify_secp_context *context, const purify_bulletproof_circuit_view *circuit, const purify_bulletproof_assignment_view *assignment, const unsigned char *blind32, const unsigned char value_gen33[33], const unsigned char nonce32[32], const unsigned char *extra_commit, size_t extra_commit_len, unsigned char commitment_out33[33], unsigned char *proof_out, size_t *proof_len)
int purify_bulletproof_prove_circuit_with_resources(purify_bulletproof_backend_resources *resources, const purify_bulletproof_circuit_view *circuit, const purify_bulletproof_assignment_view *assignment, const unsigned char *blind32, const unsigned char value_gen33[33], const unsigned char nonce32[32], const unsigned char *extra_commit, size_t extra_commit_len, unsigned char commitment_out33[33], unsigned char *proof_out, size_t *proof_len)
int purify_bulletproof_verify_circuit(purify_secp_context *context, const purify_bulletproof_circuit_view *circuit, const unsigned char commitment33[33], const unsigned char value_gen33[33], const unsigned char *extra_commit, size_t extra_commit_len, const unsigned char *proof, size_t proof_len)
int purify_bulletproof_prove_circuit_assume_valid(purify_secp_context *context, const purify_bulletproof_circuit_view *circuit, const purify_bulletproof_assignment_view *assignment, const unsigned char *blind32, const unsigned char value_gen33[33], const unsigned char nonce32[32], const unsigned char *extra_commit, size_t extra_commit_len, unsigned char commitment_out33[33], unsigned char *proof_out, size_t *proof_len)
int purify_bulletproof_verify_circuit_with_resources(purify_bulletproof_backend_resources *resources, const purify_bulletproof_circuit_view *circuit, const unsigned char commitment33[33], const unsigned char value_gen33[33], const unsigned char *extra_commit, size_t extra_commit_len, const unsigned char *proof, size_t proof_len)
int purify_bulletproof_prove_circuit_assume_valid_with_resources(purify_bulletproof_backend_resources *resources, const purify_bulletproof_circuit_view *circuit, const purify_bulletproof_assignment_view *assignment, const unsigned char *blind32, const unsigned char value_gen33[33], const unsigned char nonce32[32], const unsigned char *extra_commit, size_t extra_commit_len, unsigned char commitment_out33[33], unsigned char *proof_out, size_t *proof_len)
void purify_bulletproof_backend_resources_destroy(purify_bulletproof_backend_resources *resources)
purify_bulletproof_backend_resources * purify_bulletproof_backend_resources_create(purify_secp_context *context, size_t n_gates)
purify_bulletproof_backend_resources * purify_bulletproof_backend_resources_clone(const purify_bulletproof_backend_resources *resources)
size_t purify_bulletproof_required_proof_size(size_t n_gates)
C ABI bridging Purify C++ code to secp256k1-zkp BPPP functionality.
Native Bulletproof-style circuit types and witness serialization helpers.
bool replace_and_insert(Expr &expr, Symbol symbol)
Detects simple witness aliases and records them for later assignment lowering.
bool evaluate(const WitnessAssignments &vars, const FieldElement &commitment) const
Evaluates the lowered symbolic constraints with a concrete commitment value.
std::string to_string() const
Renders the lowered circuit in the legacy textual verifier format.
void add_assignment(Symbol symbol, Expr expr)
Adds one symbolic wire assignment to the lowering state.
Status from_transcript(const Transcript &transcript, std::size_t n_bits)
Imports a symbolic transcript and pads it to a power-of-two multiplication count.
void replace_expr_v_with_bp_var(Expr &expr)
Rewrites transcript variable names to their eventual Bulletproof wire aliases.
Status add_pubkey_and_out(const UInt512 &pubkey, Expr p1x, Expr p2x, Expr out)
Binds packed public-key coordinates and the output commitment into explicit constraints.
NativeBulletproofCircuit native_circuit() const
Builds the native sparse circuit object from the lowered assignments and constraints.
Result< BulletproofAssignmentData > assignment_data(const WitnessAssignments &vars) const
Materializes the witness columns expected by the native circuit representation.
Result< Bytes > serialize_assignment(const WitnessAssignments &vars) const
Serializes the derived witness assignment using the legacy blob format.
Minimal elliptic-curve arithmetic over the Purify base field.
AffinePoint affine(const JacobianPoint &point) const
Converts a Jacobian point to affine coordinates.
JacobianPoint add(const JacobianPoint &lhs, const JacobianPoint &rhs) const
Adds two Jacobian points.
JacobianPoint double_point(const JacobianPoint &point) const
Doubles a Jacobian point.
Purify result carrier that either holds a value or an error.
bool has_value() const noexcept
Caller-owned cache for reusable legacy Bulletproof backend resources keyed by gate count.
ExperimentalBulletproofBackendCache()
ExperimentalBulletproofBackendCache & operator=(const ExperimentalBulletproofBackendCache &)=delete
~ExperimentalBulletproofBackendCache()
purify_bulletproof_backend_resources * get_or_create(std::size_t n_gates, purify_secp_context *secp_context)
Returns cached backend resources for this gate count, creating them on first use.
Result< ExperimentalBulletproofBackendCache > clone_for_thread(std::size_t n_gates) const
Clones one warmed backend-resource entry for independent use on another thread.
std::size_t size() const noexcept
ExprBuilder & add(const FieldElement &value)
Adds a constant field term to the pending affine expression.
static ExprBuilder reserved(std::size_t terms)
Returns a builder with storage reserved for approximately terms linear slots.
Expr build()
Materializes the flattened affine expression.
ExprBuilder & add_scaled(const Expr &expr, const FieldElement &scale)
Adds an existing expression scaled by a field element.
ExprBuilder & subtract(const Expr &expr)
Subtracts an existing expression with implicit coefficient minus one.
Symbolic affine expression over indexed variables and field coefficients.
const FieldElement & constant() const
Returns the constant term of the affine expression.
std::vector< Term > & linear()
Returns mutable access to the sorted linear term list.
static Expr variable(Symbol symbol)
Returns a single-variable expression with coefficient one.
Field element modulo the backend scalar field used by this implementation.
static Result< FieldElement > try_from_uint256(const UInt256 &value)
Converts a canonical 256-bit unsigned integer into the scalar field representation.
static FieldElement one()
Returns the multiplicative identity of the scalar field.
FieldElement negate() const
Returns the additive inverse modulo the field prime.
static FieldElement from_int(std::int64_t value)
Constructs a field element from a signed integer, reducing negatives modulo the field.
bool is_zero() const
Returns true when the element is zero.
static FieldElement zero()
Returns the additive identity of the scalar field.
std::array< unsigned char, 32 > to_bytes_be() const
Serializes the field element in big-endian form.
Public-key-agnostic native verifier-circuit template.
Result< NativeBulletproofCircuit::PackedWithSlack > instantiate_packed(const UInt512 &pubkey) const
Result< NativeBulletproofCircuit > instantiate(const UInt512 &pubkey) const
Result< bool > partial_evaluate(const BulletproofAssignmentData &assignment) const
Evaluates only the public-key-agnostic base circuit cached inside the template.
Result< Bytes > integrity_digest() const
Returns a stable digest of the packed base circuit plus late-bound expressions.
static NativeBulletproofCircuitTemplate from_parts(NativeBulletproofCircuit::PackedWithSlack base_packed, Expr p1x, Expr p2x, Expr out)
Builds a template from a lowered base circuit plus late-bound public expressions.
Result< bool > final_evaluate(const BulletproofAssignmentData &assignment, const UInt512 &pubkey) const
Evaluates only the late-bound public-key/output constraints against one assignment.
Resettable packed circuit representation backed by one aligned owning slab.
void add_right_term(std::size_t gate_idx, std::size_t constraint_idx, const FieldElement &scalar)
Adds a coefficient to the right-wire matrix.
PackedWithSlack & operator=(const PackedWithSlack &other)
std::size_t n_commitments() const noexcept
Result< NativeBulletproofCircuit > unpack() const
Materializes the packed circuit back into the ergonomic row-vector representation.
void add_commitment_term(std::size_t commitment_idx, std::size_t constraint_idx, const FieldElement &scalar)
Adds a coefficient to the commitment matrix using the Bulletproof sign convention.
bool has_valid_shape() const noexcept
std::size_t n_gates() const noexcept
void add_output_term(std::size_t gate_idx, std::size_t constraint_idx, const FieldElement &scalar)
Adds a coefficient to the output-wire matrix.
void reset() noexcept
Restores the packed circuit to its original base row sizes and constraint count.
NativeBulletproofCircuitRow::PackedWithSlack right_row(std::size_t gate_idx) const noexcept
Returns a read-only packed right-wire row view.
void add_left_term(std::size_t gate_idx, std::size_t constraint_idx, const FieldElement &scalar)
Adds a coefficient to the left-wire matrix.
PackedWithSlack()=default
NativeBulletproofCircuitRow::PackedWithSlack left_row(std::size_t gate_idx) const noexcept
Returns a read-only packed left-wire row view.
NativeBulletproofCircuitRow::PackedWithSlack output_row(std::size_t gate_idx) const noexcept
Returns a read-only packed output-wire row view.
std::span< const FieldElement > constants() const noexcept
Returns the live constraint constants stored in the slab.
NativeBulletproofCircuitRow::PackedWithSlack commitment_row(std::size_t commitment_idx) const noexcept
Returns a read-only packed commitment row view.
std::size_t add_constraint(const FieldElement &constant=FieldElement::zero())
Appends a new linear constraint constant term and returns its index.
static Result< PackedWithSlack > from_circuit(const NativeBulletproofCircuit &circuit, const PackedSlackPlan &slack)
Packs a native circuit into one aligned slab with caller-supplied row and constraint slack.
bool evaluate(const BulletproofAssignmentData &assignment) const
Evaluates the packed circuit directly against a witness assignment.
Reusable BIP340-style tagged SHA-256 helper.
std::array< unsigned char, 32 > digest(std::span< const unsigned char > data) const
Mutable transcript used to record symbolic multiplication, division, and boolean constraints.
const std::vector< Expr > & eqs() const
Returns the linear equality constraints accumulated so far.
Expr secret(const std::optional< FieldElement > &value)
Allocates a new secret witness variable, optionally with a known concrete value.
Expr boolean(const Expr &expr)
Constrains an expression to be boolean by adding x * (x - 1) = 0.
const std::vector< MulConstraint > & muls() const
Returns the multiplication and division constraints accumulated so far.
Expr mul(const Expr &lhs, const Expr &rhs)
Allocates or reuses a multiplication witness enforcing lhs * rhs = out.
const WitnessAssignments & varmap() const
Returns the underlying witness assignment vector.
Expr div(const Expr &lhs, const Expr &rhs)
Allocates or reuses a division witness enforcing out * rhs = lhs.
#define PURIFY_RETURN_IF_ERROR(expr, context)
Evaluates an expected-like expression and returns the wrapped error on failure.
#define PURIFY_ASSIGN_OR_RETURN(lhs, expr, context)
Evaluates an expected-like expression, binds the value to lhs, and propagates errors.
std::optional< std::uint32_t > read_u32_le(std::span< const unsigned char > bytes, std::size_t offset)
void append_u32_le(Bytes &out, std::uint32_t value)
std::size_t circuit_n_gates(const NativeBulletproofCircuit &circuit)
std::size_t circuit_n_commitments(const NativeBulletproofCircuit &circuit)
Status require_secp_context(const purify_secp_context *context, const char *error_context)
FieldElement field_di()
Returns the inverse of the twist factor in the field.
const UInt256 & half_n1()
Returns floor(order_n1 / 2).
Result< Bytes > experimental_circuit_binding_digest(const NativeBulletproofCircuit &circuit, std::span< const unsigned char > statement_binding)
ExprPoint circuit_3bit_point(const EllipticCurve &curve, const std::array< JacobianPoint, 8 > &points, Transcript &transcript, const Expr &b0, const Expr &b1, const Expr &b2)
Selects between eight affine point constants using three boolean expression bits.
constexpr Unexpected< Error > unexpected_error(ErrorCode code, const char *context=nullptr)
Constructs an unexpected Error value from a machine-readable code.
Result< std::vector< int > > key_to_bits(UInt256 n, const UInt256 &max_value)
Encodes a scalar into the signed 3-bit window bit schedule used by the circuit.
bool size_fits_u64(std::size_t value) noexcept
bool is_power_of_two_size(std::size_t value) noexcept
Result< std::uint64_t > narrow_size_to_u64(std::size_t value, const char *context)
Result< std::pair< UInt256, UInt256 > > unpack_public(const UInt512 &packed)
Splits a packed public key into its two x-coordinates.
ErrorCode
Machine-readable error codes shared across the library.
const UInt256 & half_n2()
Returns floor(order_n2 / 2).
std::pair< Expr, Expr > ExprPoint
Symbolic affine point represented as independent x and y expressions.
bool checked_mul_size(std::size_t lhs, std::size_t rhs, std::size_t &out) noexcept
Expr circuit_combine(Transcript &transcript, const Expr &x1, const Expr &x2)
Builds the symbolic Purify output combiner over two x-coordinates.
Result< bool > verify_experimental_circuit(const NativeBulletproofCircuit &circuit, const ExperimentalBulletproofProof &proof, const BulletproofGeneratorBytes &value_generator, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalBulletproofBackendCache *backend_cache=nullptr)
Verifies a proof produced by prove_experimental_circuit against the same one-commitment native circui...
ExprPoint circuit_ec_add(Transcript &transcript, const ExprPoint &p1, const ExprPoint &p2)
Symbolically adds two affine elliptic-curve points.
std::unique_ptr< purify_bulletproof_backend_resources, BulletproofBackendResourceDeleter > BulletproofBackendResourcePtr
FieldElement field_a()
Returns the shared Weierstrass a coefficient used by Purify.
Expr circuit_ec_add_x(Transcript &transcript, const ExprPoint &p1, const ExprPoint &p2)
Symbolically adds two affine points and returns only the resulting x-coordinate.
const EllipticCurve & curve1()
Returns the first Purify curve instance.
Expr circuit_ec_multiply_x(const EllipticCurve &curve, Transcript &transcript, const JacobianPoint &point, const std::vector< Expr > &bits)
Builds the symbolic x-coordinate multiplication gadget for one curve point.
Result< CircuitMainResult > circuit_main(Transcript &transcript, const JacobianPoint &m1, const JacobianPoint &m2, const std::optional< UInt256 > &z1=std::nullopt, const std::optional< UInt256 > &z2=std::nullopt)
Builds the full symbolic Purify circuit from message points and optional witness scalars.
void best_effort_reserve_mul(std::vector< T > &out, std::size_t lhs, std::size_t rhs)
std::vector< unsigned char > Bytes
Dynamically sized byte string used for messages, serialized witnesses, and proofs.
std::array< unsigned char, 33 > BulletproofGeneratorBytes
const JacobianPoint & generator1()
Returns the fixed generator for the first curve.
SymbolKind
Symbol classes used while deriving witness and Bulletproof wire relations.
constexpr std::string_view to_string(ErrorCategory category) noexcept
Returns a stable programmatic name for an error category.
Result< ExperimentalBulletproofProof > prove_experimental_circuit(const NativeBulletproofCircuit &circuit, const BulletproofAssignmentData &assignment, const BulletproofScalarBytes &nonce, const BulletproofGeneratorBytes &value_generator, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, std::optional< BulletproofScalarBytes > blind=std::nullopt, ExperimentalBulletproofBackendCache *backend_cache=nullptr)
Proves a native circuit with the experimental imported Bulletproof circuit backend.
Expr circuit_1bit(const std::array< FieldElement, 2 > &values, Transcript &transcript, const Expr &x)
Selects one of two field constants using a single boolean expression bit.
FieldElement field_b()
Returns the shared Weierstrass b coefficient used by Purify.
std::array< unsigned char, 33 > BulletproofPointBytes
bool size_fits_u32(std::size_t value) noexcept
const EllipticCurve & curve2()
Returns the second Purify curve instance.
const JacobianPoint & generator2()
Returns the fixed generator for the second curve.
Expr circuit_3bit(const std::array< FieldElement, 8 > &values, Transcript &transcript, const Expr &x, const Expr &y, const Expr &z)
Selects one of eight field constants using three boolean expression bits.
std::array< unsigned char, 32 > BulletproofScalarBytes
Result< ExperimentalBulletproofProof > prove_experimental_circuit_assume_valid(const NativeBulletproofCircuit::PackedWithSlack &circuit, const BulletproofAssignmentData &assignment, const BulletproofScalarBytes &nonce, const BulletproofGeneratorBytes &value_generator, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding, std::optional< BulletproofScalarBytes > blind, ExperimentalBulletproofBackendCache *backend_cache)
ExprPoint circuit_1bit_point(const EllipticCurve &curve, const std::array< JacobianPoint, 2 > &points, Transcript &transcript, const Expr &b0)
Selects between two affine point constants using one boolean expression bit.
ExprPoint circuit_optionally_negate_ec(const ExprPoint &point, Transcript &transcript, const Expr &negate_bit)
Conditionally negates an elliptic-curve point encoded as symbolic affine expressions.
bool checked_add_size(std::size_t lhs, std::size_t rhs, std::size_t &out) noexcept
std::vector< std::optional< FieldElement > > WitnessAssignments
Partial witness assignment vector indexed by transcript witness id.
Expected< void, Error > Status
Expected-returning convenience alias for Purify status-only APIs.
ExprPoint circuit_2bit_point(const EllipticCurve &curve, const std::array< JacobianPoint, 4 > &points, Transcript &transcript, const Expr &b0, const Expr &b1)
Selects between four affine point constants using two boolean expression bits.
Expr circuit_2bit(const std::array< FieldElement, 4 > &values, Transcript &transcript, const Expr &x, const Expr &y)
Selects one of four field constants using two boolean expression bits.
std::size_t bit_length() const
Returns the index of the highest set bit plus one.
Columnar witness assignment compatible with the native Bulletproof circuit layout.
std::vector< FieldElement > output
std::vector< FieldElement > commitments
std::vector< FieldElement > right
Result< Bytes > serialize() const
Serializes the witness columns in the legacy assignment blob format.
std::vector< FieldElement > left
void operator()(purify_bulletproof_backend_resources *resources) const noexcept
Result bundle returned by the main symbolic Purify circuit construction.
std::unordered_map< std::size_t, BulletproofBackendResourcePtr > resources
Experimental single-proof wrapper over the imported legacy Bulletproof circuit backend.
BulletproofPointBytes commitment
Result< Bytes > serialize() const
static Result< ExperimentalBulletproofProof > deserialize(std::span< const unsigned char > bytes)
Jacobian point representation used for curve arithmetic.
Non-owning packed row view used by NativeBulletproofCircuit::PackedWithSlack.
void add(std::size_t idx, const FieldElement &scalar)
Appends a sparse coefficient to the row, skipping zero entries.
std::vector< NativeBulletproofCircuitTerm > entries
One sparse matrix entry in a native circuit row.
std::vector< std::size_t > wl
std::vector< std::size_t > wr
std::vector< std::size_t > wv
std::size_t constraint_slack
std::vector< std::size_t > wo
Native in-memory representation of a Bulletproof-style arithmetic circuit.
std::size_t add_constraint(const FieldElement &constant=FieldElement::zero())
Appends a new linear constraint constant term and returns its index.
std::vector< NativeBulletproofCircuitRow > wv
void resize(std::size_t gates, std::size_t commitments, std::size_t bits=0)
Reinitializes the circuit shape and clears all accumulated constraints.
bool evaluate(const BulletproofAssignmentData &assignment) const
Evaluates the circuit against a witness assignment and checks all gate and row equations.
std::vector< NativeBulletproofCircuitRow > wr
bool has_valid_shape() const
Returns true when the sparse matrix vectors match the declared circuit dimensions.
std::vector< NativeBulletproofCircuitRow > wl
void add_left_term(std::size_t gate_idx, std::size_t constraint_idx, const FieldElement &scalar)
Adds a coefficient to the left-wire matrix.
std::vector< NativeBulletproofCircuitRow > wo
Result< PackedWithSlack > pack_with_slack() const
Packs the circuit into one aligned slab with no additional row or constraint slack.
NativeBulletproofCircuit()=default
void add_right_term(std::size_t gate_idx, std::size_t constraint_idx, const FieldElement &scalar)
Adds a coefficient to the right-wire matrix.
void add_output_term(std::size_t gate_idx, std::size_t constraint_idx, const FieldElement &scalar)
Adds a coefficient to the output-wire matrix.
std::vector< FieldElement > c
std::size_t n_commitments
void add_commitment_term(std::size_t commitment_idx, std::size_t constraint_idx, const FieldElement &scalar)
Adds a coefficient to the commitment matrix using the Bulletproof sign convention.
Compact symbolic variable identifier used inside expressions and transcripts.
static Symbol witness(std::uint32_t index)
static Symbol left(std::uint32_t index)
static Symbol output(std::uint32_t index)
static Symbol commitment(std::uint32_t index)
static Symbol right(std::uint32_t index)
int PURIFY_UINT_FN() bit(const uint64_t value[PURIFY_UINT_WORDS], size_t index)