22#include <unordered_map>
38 std::unique_ptr<purify_bppp_backend_resources, BpppBackendResourcesDeleter>;
42template <
typename Digest>
44 static_assert(
sizeof(std::size_t) <= std::tuple_size_v<Digest>,
45 "digest prefix hash must fit within digest output");
47 std::memcpy(&out, digest.data(),
sizeof(std::size_t));
63 auto serialized_view = std::as_bytes(
generators);
64 const unsigned char *serialized =
65 serialized_view.empty()
67 :
reinterpret_cast<const unsigned char *
>(serialized_view.data());
68 purify_sha256(key.data(), serialized, serialized_view.size());
74std::shared_ptr<const void>
75detail::ExperimentalCircuitBackendAccess::find_public_data(
76 const ExperimentalCircuitBackend *backend,
77 const std::array<unsigned char, 32> &key) {
78 return backend !=
nullptr ? backend->find_public_data_impl(key)
79 : std::shared_ptr<const void>{};
82void detail::ExperimentalCircuitBackendAccess::insert_public_data(
83 ExperimentalCircuitBackend *backend, std::array<unsigned char, 32> key,
84 std::shared_ptr<const void> value) {
85 if (backend !=
nullptr) {
86 backend->insert_public_data_impl(std::move(key), std::move(value));
91detail::ExperimentalCircuitBackendAccess::get_or_create_backend_resources(
92 ExperimentalCircuitBackend *backend,
95 return backend !=
nullptr
96 ? backend->get_or_create_backend_resources_impl(
generators,
109 std::shared_ptr<const void>,
118 : impl_(std::make_unique<
Impl>()) {}
129 return impl_ ==
nullptr || impl_->backend_resources ==
nullptr;
132std::shared_ptr<const void> ExperimentalCircuitCacheLine::find_public_data_impl(
133 const std::array<unsigned char, 32> &key)
const {
138void ExperimentalCircuitCacheLine::insert_public_data_impl(
139 std::array<unsigned char, 32> key, std::shared_ptr<const void> value) {
145ExperimentalCircuitCacheLine::get_or_create_backend_resources_impl(
149 if (impl_ ==
nullptr || impl_->backend_resources ==
nullptr ||
150 generators.empty() || !impl_->has_backend_key) {
154 ? impl_->backend_resources.get()
159 : impl_(std::make_unique<
Impl>()) {}
170 if (impl_ !=
nullptr) {
171 impl_->public_data.clear();
172 impl_->backend_resources.clear();
177 return impl_ !=
nullptr
178 ? impl_->public_data.size() + impl_->backend_resources.size()
183 std::span<const PointBytes>
generators)
const {
190 auto it = impl_->backend_resources.find(key);
191 if (it == impl_->backend_resources.end()) {
197 if (cloned ==
nullptr) {
200 "ExperimentalCircuitCache::clone_line_for_thread:backend_resources");
202 clone.impl_->backend_key = key;
203 clone.impl_->has_backend_key =
true;
204 clone.impl_->backend_resources.reset(cloned);
209 const std::array<unsigned char, 32> &key)
const {
210 return find_public_data_impl(key);
214 std::array<unsigned char, 32> key,
215 std::shared_ptr<const void> value) {
216 insert_public_data_impl(std::move(key), std::move(value));
223 return get_or_create_backend_resources_impl(
generators, secp_context);
226std::shared_ptr<const void> ExperimentalCircuitCache::find_public_data_impl(
227 const std::array<unsigned char, 32> &key)
const {
228 if (impl_ ==
nullptr) {
231 auto it = impl_->public_data.find(key);
232 if (it == impl_->public_data.end()) {
238void ExperimentalCircuitCache::insert_public_data_impl(
239 std::array<unsigned char, 32> key,
240 std::shared_ptr<const void> value) {
241 if (impl_ ==
nullptr) {
244 impl_->public_data.emplace(std::move(key), std::move(value));
248ExperimentalCircuitCache::get_or_create_backend_resources_impl(
251 if (impl_ ==
nullptr || secp_context ==
nullptr ||
generators.empty()) {
255 auto serialized_view = std::as_bytes(
generators);
256 const unsigned char *serialized =
257 serialized_view.empty()
259 :
reinterpret_cast<const unsigned char *
>(serialized_view.data());
263 auto it = impl_->backend_resources.find(key);
264 if (it != impl_->backend_resources.end()) {
265 return it->second.get();
271 if (created ==
nullptr) {
277 return inserted.first->second.get();
282template <
typename ByteArray>
constexpr void assert_packed_byte_array_layout() {
284 std::is_same_v<typename ByteArray::value_type, unsigned char>,
285 "byte-span bridge helpers require unsigned-char array elements");
287 std::is_trivially_copyable_v<ByteArray>,
288 "byte-span bridge helpers require trivially copyable array wrappers");
290 std::is_standard_layout_v<ByteArray>,
291 "byte-span bridge helpers require standard-layout array wrappers");
292 static_assert(
alignof(ByteArray) ==
alignof(
unsigned char),
293 "byte-span bridge helpers require byte-aligned array wrappers");
294 static_assert(
sizeof(ByteArray) == std::tuple_size_v<ByteArray>,
295 "byte-span bridge helpers require tightly packed byte arrays "
299template <
typename ByteArray>
300std::span<const unsigned char> byte_span(
const std::vector<ByteArray> &values) {
301 assert_packed_byte_array_layout<ByteArray>();
303 std::as_bytes(std::span<const ByteArray>(values.data(), values.size()));
304 return {
reinterpret_cast<const unsigned char *
>(bytes.data()), bytes.size()};
307template <
typename ByteArray>
308std::span<unsigned char> writable_byte_span(std::vector<ByteArray> &values) {
309 assert_packed_byte_array_layout<ByteArray>();
310 auto bytes = std::as_writable_bytes(
311 std::span<ByteArray>(values.data(), values.size()));
312 return {
reinterpret_cast<unsigned char *
>(bytes.data()), bytes.size()};
316bool require_ok(
int ok) {
return ok != 0; }
318constexpr std::string_view kCircuitNormArgRhoTag =
319 "Purify/BPPP/CircuitNormArg/Rho";
320constexpr std::string_view kCircuitNormArgMulTag =
321 "Purify/BPPP/CircuitNormArg/Mul";
322constexpr std::string_view kCircuitNormArgConstraintTag =
323 "Purify/BPPP/CircuitNormArg/Constraint";
324constexpr std::string_view kCircuitNormArgPublicCommitmentTag =
325 "Purify/BPPP/CircuitNormArg/PublicCommitments";
326constexpr std::string_view kCircuitZkBlindTag =
327 "Purify/BPPP/CircuitNormArg/ZKBlind";
328constexpr std::string_view kCircuitZkMaskNTag =
329 "Purify/BPPP/CircuitNormArg/ZKMaskN";
330constexpr std::string_view kCircuitZkMaskLTag =
331 "Purify/BPPP/CircuitNormArg/ZKMaskL";
332constexpr std::string_view kCircuitZkChallengeTag =
333 "Purify/BPPP/CircuitNormArg/ZKChallenge";
334const TaggedHash kCircuitNormArgRhoTaggedHash(kCircuitNormArgRhoTag);
335const TaggedHash kCircuitNormArgMulTaggedHash(kCircuitNormArgMulTag);
336const TaggedHash kCircuitNormArgConstraintTaggedHash(kCircuitNormArgConstraintTag);
337const TaggedHash kCircuitZkBlindTaggedHash(kCircuitZkBlindTag);
338const TaggedHash kCircuitZkMaskNTaggedHash(kCircuitZkMaskNTag);
339const TaggedHash kCircuitZkMaskLTaggedHash(kCircuitZkMaskLTag);
340const TaggedHash kCircuitZkChallengeTaggedHash(kCircuitZkChallengeTag);
342struct ResolvedBpppGeneratorBackend {
348ResolvedBpppGeneratorBackend resolve_bppp_generator_backend(
349 ExperimentalCircuitBackend *cache,
350 const std::vector<PointBytes> &serialized_generators,
352 ResolvedBpppGeneratorBackend out;
353 out.serialized_bytes = byte_span(serialized_generators);
354 out.generator_count = serialized_generators.size();
356 if (cache ==
nullptr || serialized_generators.empty()) {
359 out.backend_resources =
360 detail::ExperimentalCircuitBackendAccess::get_or_create_backend_resources(
361 cache, serialized_generators, secp_context);
365Result<std::size_t> round_up_power_of_two(std::size_t value,
366 const char *context) {
371 while (out < value) {
372 if (out > std::numeric_limits<std::size_t>::max() / 2) {
382 bool externalize_commitments) {
384 const unsigned char mode = externalize_commitments ?
'\x01' :
'\x00';
385 const std::array<const unsigned char *, 2> items{
387 const std::array<size_t, 2> item_lens{{1,
binding_digest.size()}};
391 "circuit norm arg public data cache key generation must succeed");
396void append_u64_le(
Bytes &out, std::uint64_t value) {
397 for (
int i = 0; i < 8; ++i) {
398 out.push_back(
static_cast<unsigned char>((value >> (8 * i)) & 0xffU));
402Result<FieldElement> derive_nonzero_scalar(std::span<const unsigned char> seed,
403 const TaggedHash& tag,
405 Bytes prefix(seed.begin(), seed.end());
408 "derive_nonzero_scalar:index"),
409 "derive_nonzero_scalar:index");
410 append_u64_le(prefix, encoded_index);
411 for (std::uint64_t counter = 0; counter < 256; ++counter) {
412 Bytes input = prefix;
413 append_u64_le(input, counter);
416 tag.digest(std::span<const unsigned char>(input.data(), input.size()));
417 Result<FieldElement> candidate =
419 if (candidate.has_value() && !candidate->is_zero()) {
424 "derive_nonzero_scalar:exhausted");
427Result<FieldElement> derive_scalar(std::span<const unsigned char> seed,
428 const TaggedHash& tag, std::size_t index,
429 std::uint64_t attempt = 0) {
430 Bytes prefix(seed.begin(), seed.end());
433 "derive_scalar:index");
434 append_u64_le(prefix, encoded_index);
435 append_u64_le(prefix, attempt);
436 for (std::uint64_t counter = 0; counter < 256; ++counter) {
437 Bytes input = prefix;
438 append_u64_le(input, counter);
441 tag.digest(std::span<const unsigned char>(input.data(), input.size()));
442 Result<FieldElement> candidate =
444 if (candidate.has_value()) {
449 "derive_scalar:exhausted");
452FieldElement weighted_bppp_inner_product(std::span<const FieldElement> lhs,
453 std::span<const FieldElement> rhs,
454 const FieldElement &
rho) {
455 assert(lhs.size() == rhs.size() &&
456 "weighted_bppp_inner_product requires matching vector lengths");
457 FieldElement mu =
rho *
rho;
458 FieldElement weight = mu;
460 for (std::size_t i = 0; i < lhs.size(); ++i) {
461 total = total + (weight * lhs[i] * rhs[i]);
462 weight = weight * mu;
467struct CircuitNormArgPublicData {
481using CircuitNormArgPublicDataPtr =
482 std::shared_ptr<const CircuitNormArgPublicData>;
484struct CircuitNormArgReduction {
490Result<CircuitNormArgPublicDataPtr> build_circuit_norm_arg_public_data(
491 const NativeBulletproofCircuit &circuit,
492 std::span<const unsigned char> statement_binding,
494 bool externalize_commitments =
false,
495 ExperimentalCircuitBackend *cache =
nullptr) {
498 "build_circuit_norm_arg_public_data:secp_context");
499 if (!circuit.has_valid_shape()) {
501 "build_circuit_norm_arg_public_data:circuit_shape");
506 "build_circuit_norm_arg_public_data:n_gates_power_of_two");
512 "build_circuit_norm_arg_public_data:binding_digest");
515 derive_nonzero_scalar(
binding_digest, kCircuitNormArgRhoTaggedHash, 0),
516 "build_circuit_norm_arg_public_data:rho");
519 externalize_commitments);
520 if (std::shared_ptr<const void> cached =
521 detail::ExperimentalCircuitBackendAccess::find_public_data(cache,
523 return std::static_pointer_cast<const CircuitNormArgPublicData>(cached);
526 std::optional<FieldElement> sqrt_minus_one =
528 if (!sqrt_minus_one.has_value()) {
531 "build_circuit_norm_arg_public_data:sqrt_minus_one");
538 const FieldElement inv2 = two.inverse();
539 const FieldElement inv4 = four.inverse();
541 std::vector<FieldElement> mul_weights(circuit.n_gates, zero);
542 for (std::size_t i = 0; i < circuit.n_gates; ++i) {
544 const auto &challenge,
545 derive_nonzero_scalar(
binding_digest, kCircuitNormArgMulTaggedHash, i),
546 "build_circuit_norm_arg_public_data:mul_weight");
547 mul_weights[i] = challenge;
550 std::vector<FieldElement> row_weights(circuit.c.size(), zero);
551 for (std::size_t i = 0; i < circuit.c.size(); ++i) {
553 const auto &challenge,
554 derive_nonzero_scalar(
binding_digest, kCircuitNormArgConstraintTaggedHash,
556 "build_circuit_norm_arg_public_data:constraint_weight");
557 row_weights[i] = challenge;
560 std::vector<FieldElement> left_coeffs(circuit.n_gates, zero);
561 std::vector<FieldElement> right_coeffs(circuit.n_gates, zero);
562 std::vector<FieldElement> output_coeffs(circuit.n_gates, zero);
563 std::vector<FieldElement> commitment_coeffs(circuit.n_commitments, zero);
564 for (std::size_t i = 0; i < circuit.n_gates; ++i) {
565 output_coeffs[i] = mul_weights[i].negate();
568 FieldElement constant = zero;
569 for (std::size_t j = 0; j < circuit.c.size(); ++j) {
570 constant = constant - (row_weights[j] * circuit.c[j]);
573 auto accumulate_coeffs =
574 [&](
const std::vector<NativeBulletproofCircuitRow> &rows,
575 std::vector<FieldElement> &coeffs,
bool negate_entries) {
576 for (std::size_t i = 0; i < rows.size(); ++i) {
577 for (
const NativeBulletproofCircuitTerm &entry : rows[i].entries) {
578 const FieldElement
scalar =
579 negate_entries ? entry.scalar.negate() : entry.scalar;
580 coeffs[i] = coeffs[i] + (row_weights[entry.idx] *
scalar);
584 accumulate_coeffs(circuit.wl, left_coeffs,
false);
585 accumulate_coeffs(circuit.wr, right_coeffs,
false);
586 accumulate_coeffs(circuit.wo, output_coeffs,
false);
587 accumulate_coeffs(circuit.wv, commitment_coeffs,
true);
589 auto out = std::make_shared<CircuitNormArgPublicData>();
592 out->plus_terms.resize(circuit.n_gates);
593 out->minus_terms.resize(circuit.n_gates);
594 out->plus_shift.resize(circuit.n_gates, zero);
595 out->minus_shift.resize(circuit.n_gates, zero);
597 auto two_square_terms = [&](
const FieldElement &coefficient) {
598 const FieldElement first = (coefficient + one) * inv2;
599 const FieldElement second = *sqrt_minus_one * (coefficient - one) * inv2;
600 return std::array<FieldElement, 2>{first, second};
603 for (std::size_t i = 0; i < circuit.n_gates; ++i) {
604 const FieldElement d_plus = mul_weights[i] * inv4;
605 const FieldElement d_minus = d_plus.negate();
606 const FieldElement e_plus = (left_coeffs[i] + right_coeffs[i]) * inv2;
607 const FieldElement e_minus = (left_coeffs[i] - right_coeffs[i]) * inv2;
609 out->plus_shift[i] = e_plus * (two * d_plus).inverse();
610 out->minus_shift[i] = e_minus * (two * d_minus).inverse();
611 constant = constant - ((e_plus * e_plus) * (four * d_plus).inverse());
612 constant = constant - ((e_minus * e_minus) * (four * d_minus).inverse());
613 out->plus_terms[i] = two_square_terms(d_plus);
614 out->minus_terms[i] = two_square_terms(d_minus);
617 std::size_t l_value_count = 0;
619 externalize_commitments ? 0 : circuit.n_commitments,
624 "build_circuit_norm_arg_public_data:l_value_count");
627 const auto &l_vec_len,
628 round_up_power_of_two(std::max<std::size_t>(1, l_value_count),
629 "build_circuit_norm_arg_public_data:l_vec_len"),
630 "build_circuit_norm_arg_public_data:l_vec_len");
631 out->c_vec.assign(l_vec_len, zero);
632 for (std::size_t i = 0; i < circuit.n_gates; ++i) {
633 out->c_vec[i] = output_coeffs[i];
635 if (externalize_commitments) {
636 out->public_commitment_coeffs = std::move(commitment_coeffs);
638 for (std::size_t i = 0; i < circuit.n_commitments; ++i) {
639 out->c_vec[circuit.n_gates + i] = commitment_coeffs[i];
642 out->target = constant.negate();
645 std::size_t witness_generator_count = 0;
652 "build_circuit_norm_arg_public_data:generator_count");
656 "build_circuit_norm_arg_public_data:create_generators");
658 CircuitNormArgPublicDataPtr shared = out;
659 if (cache !=
nullptr) {
660 detail::ExperimentalCircuitBackendAccess::insert_public_data(cache,
667Result<CircuitNormArgReduction> reduce_experimental_circuit_to_norm_arg(
668 const NativeBulletproofCircuit &circuit,
669 const BulletproofAssignmentData &assignment,
671 std::span<const unsigned char> statement_binding,
672 bool externalize_commitments =
false,
673 ExperimentalCircuitBackend *cache =
nullptr) {
674 if (assignment.left.size() != circuit.n_gates ||
675 assignment.right.size() != circuit.n_gates ||
676 assignment.output.size() != circuit.n_gates ||
677 assignment.commitments.size() != circuit.n_commitments) {
680 "reduce_experimental_circuit_to_norm_arg:assignment_shape");
685 build_circuit_norm_arg_public_data(circuit, statement_binding, secp_context,
686 externalize_commitments, cache),
687 "reduce_experimental_circuit_to_norm_arg:public_data");
689 CircuitNormArgReduction out;
691 std::size_t n_vec_capacity = 0;
695 "reduce_experimental_circuit_to_norm_arg:n_vec_capacity");
697 out.n_vec.reserve(n_vec_capacity);
700 const FieldElement rho_inv = out.public_data->rho.inverse();
701 FieldElement rho_weight_inv = rho_inv;
702 for (std::size_t i = 0; i < circuit.n_gates; ++i) {
703 const FieldElement plus_value = assignment.left[i] + assignment.right[i] +
704 out.public_data->plus_shift[i];
706 out.n_vec.push_back(term * plus_value * rho_weight_inv);
707 rho_weight_inv = rho_weight_inv * rho_inv;
710 const FieldElement minus_value = assignment.left[i] - assignment.right[i] +
711 out.public_data->minus_shift[i];
713 out.n_vec.push_back(term * minus_value * rho_weight_inv);
714 rho_weight_inv = rho_weight_inv * rho_inv;
717 out.l_vec[i] = assignment.output[i];
719 if (!externalize_commitments) {
720 for (std::size_t i = 0; i < circuit.n_commitments; ++i) {
721 out.l_vec[circuit.n_gates + i] = assignment.commitments[i];
727NormArgInputs build_norm_arg_inputs(
const CircuitNormArgReduction &reduction) {
728 NormArgInputs inputs;
729 inputs.rho = reduction.public_data->rho_bytes;
730 inputs.generators = reduction.public_data->generators;
733 inputs.c_vec = reduction.public_data->c_vec_bytes;
738commit_norm_arg_witness_only(
const NormArgInputs &inputs,
740 ExperimentalCircuitBackend *cache =
nullptr) {
743 "commit_norm_arg_witness_only:secp_context");
744 if (inputs.n_vec.empty() || inputs.l_vec.empty()) {
746 "commit_norm_arg_witness_only:empty_vectors");
748 if (!inputs.generators.empty() &&
749 inputs.generators.size() != inputs.n_vec.size() + inputs.l_vec.size()) {
751 "commit_norm_arg_witness_only:generator_size");
754 const std::vector<PointBytes> *
generators = &inputs.generators;
755 std::vector<PointBytes> generated_generators;
758 auto generated,
create_generators(inputs.n_vec.size() + inputs.l_vec.size(), secp_context),
759 "commit_norm_arg_witness_only:create_generators");
760 generated_generators = std::move(generated);
764 std::span<const unsigned char>
n_vec = byte_span(inputs.n_vec);
765 std::span<const unsigned char>
l_vec = byte_span(inputs.l_vec);
766 ResolvedBpppGeneratorBackend resolved =
767 resolve_bppp_generator_backend(cache, *
generators, secp_context);
770 resolved.backend_resources !=
nullptr
772 resolved.backend_resources,
n_vec.data(), inputs.n_vec.size(),
773 l_vec.data(), inputs.l_vec.size(), commitment.data())
778 inputs.
l_vec.size(), commitment.data());
779 if (!require_ok(ok)) {
781 "commit_norm_arg_witness_only:backend");
786Result<PointBytes> offset_commitment(
const PointBytes &commitment,
787 const FieldElement &
scalar,
792 "offset_commitment:secp_context");
794 scalar32.data(), out.data()))) {
796 "offset_commitment:backend");
801Result<PointBytes> point_scale(
const PointBytes &point,
802 const FieldElement &
scalar,
807 "point_scale:secp_context");
812 "point_scale:backend");
817Result<PointBytes> point_add(
const PointBytes &lhs,
822 "point_add:secp_context");
826 "point_add:backend");
831Result<PointBytes> point_from_scalar_base(
const FieldElement &
scalar,
839Result<Bytes> bind_public_commitments(
840 std::span<const PointBytes> public_commitments,
841 std::span<const unsigned char> statement_binding) {
843 const auto &encoded_commitment_count,
845 "bind_public_commitments:count"),
846 "bind_public_commitments:count");
848 const auto &encoded_binding_size,
850 "bind_public_commitments:statement_binding"),
851 "bind_public_commitments:statement_binding");
853 std::size_t point_bytes = 0;
854 std::size_t total_size = 0;
863 "bind_public_commitments:reserve");
865 out.reserve(total_size);
866 append_u64_le(out, encoded_commitment_count);
867 for (
const PointBytes &point : public_commitments) {
868 out.insert(out.end(), point.begin(), point.end());
870 append_u64_le(out, encoded_binding_size);
871 out.insert(out.end(), statement_binding.begin(), statement_binding.end());
876validate_public_commitments(std::span<const PointBytes> public_commitments,
877 std::span<const FieldElement> commitments,
879 if (public_commitments.size() != commitments.size()) {
881 "validate_public_commitments:size");
883 for (std::size_t i = 0; i < commitments.size(); ++i) {
885 const auto &expected, point_from_scalar_base(commitments[i], secp_context),
886 "validate_public_commitments:point_from_scalar_base");
887 if (expected != public_commitments[i]) {
889 "validate_public_commitments:mismatch");
895Result<PointBytes> add_scaled_points(
const PointBytes &base_commitment,
896 std::span<const PointBytes> points,
897 std::span<const FieldElement> scalars,
899 if (points.size() != scalars.size()) {
901 "add_scaled_points:size_mismatch");
905 for (std::size_t i = 0; i < points.size(); ++i) {
910 "add_scaled_points:scale");
912 "add_scaled_points:add");
919anchor_zk_a_commitment(
const PointBytes &a_witness_commitment,
921 std::span<const PointBytes> public_commitments,
923 if (public_commitments.size() !=
926 "anchor_zk_a_commitment:public_commitment_size");
930 auto anchored, offset_commitment(a_witness_commitment,
public_data.target, secp_context),
931 "anchor_zk_a_commitment:target");
932 if (public_commitments.empty()) {
936 std::vector<FieldElement> negated_coeffs;
937 negated_coeffs.reserve(
public_data.public_commitment_coeffs.size());
939 negated_coeffs.push_back(coeff.negate());
941 return add_scaled_points(anchored, public_commitments, negated_coeffs, secp_context);
945commit_explicit_norm_arg(
const NormArgInputs &inputs,
const FieldElement &value,
947 ExperimentalCircuitBackend *cache =
nullptr) {
949 const auto &witness_commitment, commit_norm_arg_witness_only(inputs, secp_context, cache),
950 "commit_explicit_norm_arg:witness_commitment");
951 return offset_commitment(witness_commitment, value, secp_context);
954Result<PointBytes> combine_zk_commitments(
const PointBytes &a_commitment,
956 const FieldElement &challenge,
957 const FieldElement &t2,
960 "combine_zk_commitments:scale_s");
962 "combine_zk_commitments:add");
963 return offset_commitment(combined, challenge * challenge * t2, secp_context);
966template <
typename Inputs>
968prove_norm_arg_impl(Inputs &&inputs,
970 ExperimentalCircuitBackend *cache =
nullptr) {
972 "prove_norm_arg:secp_context");
973 if (inputs.n_vec.empty() || inputs.l_vec.empty() || inputs.c_vec.empty()) {
975 "prove_norm_arg:empty_vectors");
977 if (inputs.l_vec.size() != inputs.c_vec.size()) {
979 "prove_norm_arg:l_c_size_mismatch");
982 const std::vector<PointBytes> *
generators = &inputs.generators;
983 std::vector<PointBytes> generated_generators;
986 auto generated,
create_generators(inputs.n_vec.size() + inputs.l_vec.size(), secp_context),
987 "prove_norm_arg:create_generators");
988 generated_generators = std::move(generated);
991 std::span<const unsigned char>
n_vec = byte_span(inputs.n_vec);
992 std::span<const unsigned char>
l_vec = byte_span(inputs.l_vec);
993 std::span<const unsigned char>
c_vec = byte_span(inputs.c_vec);
994 ResolvedBpppGeneratorBackend resolved =
995 resolve_bppp_generator_backend(cache, *
generators, secp_context);
996 std::size_t proof_len =
999 Bytes proof(proof_len);
1001 if (proof_len == 0) {
1003 "prove_norm_arg:proof_len_zero");
1006 resolved.backend_resources !=
nullptr
1008 resolved.backend_resources, inputs.rho.data(),
n_vec.data(),
1009 inputs.n_vec.size(),
l_vec.data(), inputs.l_vec.size(),
1010 c_vec.data(), inputs.c_vec.size(), commitment.data(),
1011 proof.data(), &proof_len)
1016 inputs.
c_vec.size(), commitment.data(), proof.data(),
1018 if (!require_ok(ok)) {
1020 "prove_norm_arg:backend");
1022 proof.resize(proof_len);
1024 std::vector<PointBytes> proof_generators;
1025 if (!generated_generators.empty()) {
1026 proof_generators = std::move(generated_generators);
1027 }
else if constexpr (!std::is_const_v<std::remove_reference_t<Inputs>> &&
1028 std::is_rvalue_reference_v<Inputs &&>) {
1029 proof_generators = std::move(inputs.generators);
1031 proof_generators = inputs.generators;
1034 std::vector<ScalarBytes> proof_c_vec;
1035 if constexpr (!std::is_const_v<std::remove_reference_t<Inputs>> &&
1036 std::is_rvalue_reference_v<Inputs &&>) {
1037 proof_c_vec = std::move(inputs.c_vec);
1039 proof_c_vec = inputs.c_vec;
1042 return NormArgProof{inputs.rho,
1043 std::move(proof_generators),
1044 std::move(proof_c_vec),
1045 inputs.n_vec.size(),
1051derive_zk_challenge(std::span<const unsigned char>
binding_digest,
1053 const PointBytes &s_commitment,
const FieldElement &t2) {
1055 seed.insert(seed.end(), a_commitment.begin(), a_commitment.end());
1056 seed.insert(seed.end(), s_commitment.begin(), s_commitment.end());
1058 seed.insert(seed.end(), t2_bytes.begin(), t2_bytes.end());
1059 return derive_nonzero_scalar(seed, kCircuitZkChallengeTaggedHash, 0);
1066 bool ok = secp_context !=
nullptr
1068 assert(ok &&
"base_generator() requires a functioning backend");
1075 bool ok = secp_context !=
nullptr
1078 assert(ok &&
"value_generator_h() requires a functioning backend");
1085 std::vector<PointBytes> out(count);
1090 "create_generators:secp_context");
1091 std::span<unsigned char> serialized = writable_byte_span(out);
1092 std::size_t serialized_len = serialized.size();
1095 &serialized_len))) {
1097 "create_generators:backend");
1099 if (serialized_len != serialized.size()) {
1101 "create_generators:serialized_len");
1111 "commit_norm_arg:secp_context");
1112 if (inputs.
n_vec.empty() || inputs.
l_vec.empty() || inputs.
c_vec.empty()) {
1114 "commit_norm_arg:empty_vectors");
1116 if (inputs.
l_vec.size() != inputs.
c_vec.size()) {
1118 "commit_norm_arg:l_c_size_mismatch");
1122 std::vector<PointBytes> generated_generators;
1126 "commit_norm_arg:create_generators");
1127 generated_generators = std::move(generated);
1131 std::span<const unsigned char>
n_vec = byte_span(inputs.
n_vec);
1132 std::span<const unsigned char>
l_vec = byte_span(inputs.
l_vec);
1133 std::span<const unsigned char>
c_vec = byte_span(inputs.
c_vec);
1134 ResolvedBpppGeneratorBackend resolved =
1135 resolve_bppp_generator_backend(cache, *
generators, secp_context);
1138 resolved.backend_resources !=
nullptr
1140 resolved.backend_resources, inputs.
rho.data(),
n_vec.data(),
1142 c_vec.data(), inputs.
c_vec.size(), commitment.data())
1144 secp_context, inputs.
rho.data(), resolved.serialized_bytes.data(),
1145 resolved.generator_count,
n_vec.data(), inputs.
n_vec.size(),
1147 inputs.
c_vec.size(), commitment.data());
1148 if (!require_ok(ok)) {
1150 "commit_norm_arg:backend");
1170 "pedersen_commit_char:secp_context");
1172 secp_context, blind.data(), value.data(), value_gen.data(), blind_gen.data(),
1173 commitment.data()))) {
1175 "pedersen_commit_char:backend");
1182 return prove_norm_arg_impl(inputs, secp_context,
nullptr);
1187 return prove_norm_arg_impl(std::move(inputs), secp_context,
nullptr);
1196 "prove_norm_arg_to_commitment:secp_context");
1197 if (inputs.
n_vec.empty() || inputs.
l_vec.empty() || inputs.
c_vec.empty()) {
1199 "prove_norm_arg_to_commitment:empty_vectors");
1201 if (inputs.
l_vec.size() != inputs.
c_vec.size()) {
1203 "prove_norm_arg_to_commitment:l_c_size_mismatch");
1207 std::vector<PointBytes> generated_generators;
1211 "prove_norm_arg_to_commitment:create_generators");
1212 generated_generators = std::move(generated);
1216 std::span<const unsigned char>
n_vec = byte_span(inputs.
n_vec);
1217 std::span<const unsigned char>
l_vec = byte_span(inputs.
l_vec);
1218 std::span<const unsigned char>
c_vec = byte_span(inputs.
c_vec);
1219 ResolvedBpppGeneratorBackend resolved =
1220 resolve_bppp_generator_backend(cache, *
generators, secp_context);
1221 std::size_t proof_len =
1223 Bytes proof(proof_len);
1224 if (proof_len == 0) {
1226 "prove_norm_arg_to_commitment:proof_len_zero");
1229 resolved.backend_resources !=
nullptr
1231 resolved.backend_resources, inputs.
rho.data(),
n_vec.data(),
1233 c_vec.data(), inputs.
c_vec.size(), commitment.data(),
1234 proof.data(), &proof_len)
1236 secp_context, inputs.
rho.data(),
1237 resolved.serialized_bytes.data(),
1238 resolved.generator_count,
n_vec.data(), inputs.
n_vec.size(),
1240 inputs.
c_vec.size(), commitment.data(), proof.data(),
1242 if (!require_ok(ok)) {
1244 "prove_norm_arg_to_commitment:backend");
1246 proof.resize(proof_len);
1248 std::vector<PointBytes> proof_generators;
1249 if (!generated_generators.empty()) {
1250 proof_generators = std::move(generated_generators);
1256 commitment, std::move(proof)};
1262 if (secp_context ==
nullptr) {
1269 std::span<const unsigned char>
c_vec = byte_span(proof.
c_vec);
1270 ResolvedBpppGeneratorBackend resolved =
1271 resolve_bppp_generator_backend(cache, proof.
generators, secp_context);
1273 resolved.backend_resources !=
nullptr
1275 resolved.backend_resources, proof.
rho.data(),
c_vec.data(),
1279 secp_context, proof.
rho.data(), resolved.serialized_bytes.data(),
1280 resolved.generator_count,
c_vec.data(), proof.
c_vec.size(),
1282 proof.
proof.size());
1307 std::span<const unsigned char> statement_binding,
1310 const auto &reduction,
1311 reduce_experimental_circuit_to_norm_arg(circuit, assignment, secp_context,
1312 statement_binding,
false, cache),
1313 "commit_experimental_circuit_witness:reduce");
1314 return commit_norm_arg_witness_only(build_norm_arg_inputs(reduction), secp_context, cache);
1323 std::span<const unsigned char> statement_binding,
1328 "prove_experimental_circuit_norm_arg_to_commitment:circuit_shape");
1332 "prove_experimental_circuit_norm_arg_to_commitment:"
1333 "n_gates_power_of_two");
1341 "prove_experimental_circuit_norm_arg_to_commitment:assignment_shape");
1343 if (!circuit.
evaluate(assignment)) {
1346 "prove_experimental_circuit_norm_arg_to_commitment:assignment_invalid");
1350 const auto &reduction,
1351 reduce_experimental_circuit_to_norm_arg(circuit, assignment, secp_context,
1352 statement_binding,
false, cache),
1353 "prove_experimental_circuit_norm_arg_to_commitment:reduce");
1357 const auto &computed_witness_commitment,
1358 commit_norm_arg_witness_only(inputs, secp_context, cache),
1359 "prove_experimental_circuit_norm_arg_to_commitment:commit_witness");
1360 if (computed_witness_commitment != witness_commitment) {
1362 "prove_experimental_circuit_norm_arg_to_commitment:"
1363 "witness_commitment_mismatch");
1367 const auto &anchored_commitment,
1368 offset_commitment(witness_commitment, reduction.public_data->target, secp_context),
1369 "prove_experimental_circuit_norm_arg_to_commitment:anchor");
1374 "prove_experimental_circuit_norm_arg_to_commitment:prove");
1376 std::move(proof.proof)};
1383 std::span<const unsigned char> statement_binding,
1386 const auto &witness_commitment,
1389 "prove_experimental_circuit_norm_arg:commit_witness");
1391 circuit, assignment, witness_commitment, secp_context, statement_binding, cache);
1398 std::span<const unsigned char> statement_binding,
1403 "verify_experimental_circuit_norm_arg:circuit_shape");
1408 "verify_experimental_circuit_norm_arg:n_gates_power_of_two");
1410 if (proof.
proof.empty()) {
1412 "verify_experimental_circuit_norm_arg:proof_empty");
1417 build_circuit_norm_arg_public_data(circuit, statement_binding, secp_context,
false,
1419 "verify_experimental_circuit_norm_arg:public_data");
1421 const auto &anchored_commitment,
1423 "verify_experimental_circuit_norm_arg:anchor");
1432 "verify_experimental_circuit_norm_arg:n_vec_len");
1443 std::span<const PointBytes> public_commitments,
1445 std::span<const unsigned char> statement_binding,
1450 "prove_experimental_circuit_zk_norm_arg_impl:circuit_shape");
1455 "prove_experimental_circuit_zk_norm_arg_impl:n_gates_power_of_two");
1463 "prove_experimental_circuit_zk_norm_arg_impl:assignment_shape");
1465 if (!circuit.
evaluate(assignment)) {
1468 "prove_experimental_circuit_zk_norm_arg_impl:assignment_invalid");
1471 Bytes bound_statement_binding;
1472 if (externalize_commitments) {
1474 bound_statement_binding,
1475 bind_public_commitments(public_commitments, statement_binding),
1476 "prove_experimental_circuit_zk_norm_arg_impl:bound_statement_binding");
1478 bound_statement_binding =
1479 Bytes(statement_binding.begin(), statement_binding.end());
1481 if (externalize_commitments) {
1483 validate_public_commitments(public_commitments, assignment.
commitments, secp_context),
1484 "prove_experimental_circuit_zk_norm_arg_impl:"
1485 "validate_public_commitments");
1489 const auto &base_reduction,
1490 reduce_experimental_circuit_to_norm_arg(circuit, assignment, secp_context,
1491 bound_statement_binding,
1492 externalize_commitments, cache),
1493 "prove_experimental_circuit_zk_norm_arg_impl:reduce");
1498 "prove_experimental_circuit_zk_norm_arg_impl:binding_digest");
1500 seed.insert(seed.end(),
nonce.begin(),
nonce.end());
1501 std::size_t used_l = 0;
1507 "prove_experimental_circuit_zk_norm_arg_impl:used_l");
1509 std::optional<Error> masked_failure;
1511 for (std::uint64_t attempt = 0; attempt < 32; ++attempt) {
1512 CircuitNormArgReduction hidden = base_reduction;
1513 for (std::size_t i = used_l; i < hidden.l_vec.size(); ++i) {
1516 derive_scalar(seed, kCircuitZkBlindTaggedHash, i - used_l, attempt),
1517 "prove_experimental_circuit_zk_norm_arg_impl:blind");
1518 hidden.l_vec[i] = blind;
1522 for (std::size_t i = 0; i < mask_n.size(); ++i) {
1525 derive_scalar(seed, kCircuitZkMaskNTaggedHash, i, attempt),
1526 "prove_experimental_circuit_zk_norm_arg_impl:mask_n");
1531 for (std::size_t i = 0; i < mask_l.size(); ++i) {
1534 derive_scalar(seed, kCircuitZkMaskLTaggedHash, i, attempt),
1535 "prove_experimental_circuit_zk_norm_arg_impl:mask_l");
1540 weighted_bppp_inner_product(mask_n, mask_n, hidden.public_data->rho);
1545 weighted_bppp_inner_product(hidden.n_vec, mask_n,
1546 hidden.public_data->rho);
1547 for (std::size_t i = 0; i < mask_l.size(); ++i) {
1548 t1 = t1 + (mask_l[i] * hidden.public_data->c_vec[i]);
1551 NormArgInputs hidden_inputs = build_norm_arg_inputs(hidden);
1553 mask_inputs.
rho = hidden.public_data->rho_bytes;
1554 mask_inputs.
generators = hidden.public_data->generators;
1557 mask_inputs.
c_vec = hidden.public_data->c_vec_bytes;
1560 commit_norm_arg_witness_only(hidden_inputs, secp_context, cache);
1561 if (!a_witness_commitment.
has_value()) {
1562 if (!masked_failure.has_value()) {
1564 "prove_experimental_circuit_zk_norm_"
1565 "arg_impl:a_witness_commitment")
1571 *a_witness_commitment, *hidden.public_data, public_commitments, secp_context);
1573 if (!masked_failure.has_value()) {
1576 a_commitment.
error(),
1577 "prove_experimental_circuit_zk_norm_arg_impl:a_commitment")
1583 commit_explicit_norm_arg(mask_inputs, t1, secp_context, cache);
1585 if (!masked_failure.has_value()) {
1588 s_commitment.
error(),
1589 "prove_experimental_circuit_zk_norm_arg_impl:s_commitment")
1595 const auto &challenge,
1596 derive_zk_challenge(
binding_digest, *a_commitment, *s_commitment, t2),
1597 "prove_experimental_circuit_zk_norm_arg_impl:challenge");
1599 CircuitNormArgReduction masked = hidden;
1600 for (std::size_t i = 0; i < masked.n_vec.size(); ++i) {
1601 masked.n_vec[i] = masked.n_vec[i] + (challenge * mask_n[i]);
1603 for (std::size_t i = 0; i < masked.l_vec.size(); ++i) {
1604 masked.l_vec[i] = masked.l_vec[i] + (challenge * mask_l[i]);
1606 NormArgInputs masked_inputs = build_norm_arg_inputs(masked);
1609 combine_zk_commitments(*a_commitment, *s_commitment, challenge, t2, secp_context);
1611 if (!masked_failure.has_value()) {
1613 "prove_experimental_circuit_zk_norm_"
1614 "arg_impl:combined_commitment")
1622 if (!masked_failure.has_value()) {
1625 direct_commitment.
error(),
1626 "prove_experimental_circuit_zk_norm_arg_impl:direct_commitment")
1631 if (*combined_commitment != *direct_commitment) {
1634 "prove_experimental_circuit_zk_norm_arg_impl:commitment_mismatch");
1638 masked_inputs, *combined_commitment, secp_context, cache);
1640 if (!masked_failure.has_value()) {
1643 "prove_experimental_circuit_zk_norm_arg_impl:"
1644 "prove_norm_arg_to_commitment")
1651 std::move(proof->proof)};
1654 if (masked_failure.has_value()) {
1657 "prove_experimental_circuit_zk_norm_arg_impl:masking_attempts");
1661 "prove_experimental_circuit_zk_norm_arg_impl:masking_attempts");
1667 std::span<const PointBytes> public_commitments,
1669 std::span<const unsigned char> statement_binding,
1674 "verify_experimental_circuit_zk_norm_arg_impl:circuit_shape");
1679 "verify_experimental_circuit_zk_norm_arg_impl:n_gates_power_of_two");
1681 if (proof.
proof.empty()) {
1684 "verify_experimental_circuit_zk_norm_arg_impl:proof_empty");
1687 Bytes bound_statement_binding;
1688 if (externalize_commitments) {
1690 bound_statement_binding,
1691 bind_public_commitments(public_commitments, statement_binding),
1692 "verify_experimental_circuit_zk_norm_arg_impl:bound_statement_binding");
1694 bound_statement_binding =
1695 Bytes(statement_binding.begin(), statement_binding.end());
1699 build_circuit_norm_arg_public_data(circuit, bound_statement_binding, secp_context,
1700 externalize_commitments, cache),
1701 "verify_experimental_circuit_zk_norm_arg_impl:public_data");
1704 "verify_experimental_circuit_zk_norm_arg_impl:t2");
1706 const auto &a_commitment,
1708 "verify_experimental_circuit_zk_norm_arg_impl:a_commitment");
1712 "verify_experimental_circuit_zk_norm_arg_impl:binding_digest");
1714 const auto &challenge,
1716 "verify_experimental_circuit_zk_norm_arg_impl:challenge");
1718 const auto &commitment,
1719 combine_zk_commitments(a_commitment, proof.
s_commitment, challenge, t2, secp_context),
1720 "verify_experimental_circuit_zk_norm_arg_impl:commitment");
1729 "verify_experimental_circuit_zk_norm_arg_impl:n_vec_len");
1741 std::span<const unsigned char> statement_binding,
1744 circuit, assignment,
nonce, {}, secp_context, statement_binding,
false, cache);
1751 std::span<const unsigned char> statement_binding,
1754 circuit, proof, {}, secp_context, statement_binding,
false, cache);
1761 std::span<const PointBytes> public_commitments,
1763 std::span<const unsigned char> statement_binding,
1766 circuit, assignment,
nonce, public_commitments, secp_context, statement_binding,
true,
1773 std::span<const PointBytes> public_commitments,
1775 std::span<const unsigned char> statement_binding,
1778 circuit, proof, public_commitments, secp_context, statement_binding,
true, cache);
1795 "commit_output_witness:prove_assignment_data");
1797 const auto &commitment,
1800 "commit_output_witness:pedersen_commit_char");
1802 std::move(witness.assignment), commitment};
C++ wrappers for the BPPP functionality used by Purify.
int purify_bppp_prove_norm_arg_to_commitment_with_resources(purify_bppp_backend_resources *resources, const unsigned char rho32[32], const unsigned char *n_vec32, size_t n_vec_len, const unsigned char *l_vec32, size_t l_vec_len, const unsigned char *c_vec32, size_t c_vec_len, const unsigned char commitment33[33], unsigned char *proof_out, size_t *proof_len)
int purify_bppp_commit_witness_only_with_resources(purify_bppp_backend_resources *resources, const unsigned char *n_vec32, size_t n_vec_len, const unsigned char *l_vec32, size_t l_vec_len, unsigned char commitment_out33[33])
int purify_point_scale(purify_secp_context *context, const unsigned char point33[33], const unsigned char scalar32[32], unsigned char out33[33])
int purify_bppp_prove_norm_arg(purify_secp_context *context, const unsigned char rho32[32], const unsigned char *generators33, size_t generators_count, const unsigned char *n_vec32, size_t n_vec_len, const unsigned char *l_vec32, size_t l_vec_len, const unsigned char *c_vec32, size_t c_vec_len, unsigned char commitment_out33[33], unsigned char *proof_out, size_t *proof_len)
Produces a standalone BPPP norm argument.
int purify_bppp_commit_witness_only(purify_secp_context *context, const unsigned char *generators33, size_t generators_count, const unsigned char *n_vec32, size_t n_vec_len, const unsigned char *l_vec32, size_t l_vec_len, unsigned char commitment_out33[33])
int purify_bppp_commit_norm_arg(purify_secp_context *context, const unsigned char rho32[32], const unsigned char *generators33, size_t generators_count, const unsigned char *n_vec32, size_t n_vec_len, const unsigned char *l_vec32, size_t l_vec_len, const unsigned char *c_vec32, size_t c_vec_len, unsigned char commitment_out33[33])
void purify_bppp_backend_resources_destroy(purify_bppp_backend_resources *resources)
int purify_bppp_value_generator_h(purify_secp_context *context, unsigned char out33[33])
Serializes the alternate value generator used by Pedersen commitments.
int purify_bppp_create_generators(purify_secp_context *context, size_t count, unsigned char *out, size_t *out_len)
Expands the generator list required by the BPPP prover and verifier.
purify_bppp_backend_resources * purify_bppp_backend_resources_clone(const purify_bppp_backend_resources *resources)
int purify_point_add(purify_secp_context *context, const unsigned char lhs33[33], const unsigned char rhs33[33], unsigned char out33[33])
int purify_bppp_verify_norm_arg(purify_secp_context *context, const unsigned char rho32[32], const unsigned char *generators33, size_t generators_count, const unsigned char *c_vec32, size_t c_vec_len, size_t n_vec_len, const unsigned char commitment33[33], const unsigned char *proof, size_t proof_len)
Verifies a standalone BPPP norm argument.
int purify_pedersen_commit_char(purify_secp_context *context, const unsigned char blind32[32], const unsigned char value32[32], const unsigned char value_gen33[33], const unsigned char blind_gen33[33], unsigned char commitment_out33[33])
Computes a Pedersen commitment to an arbitrary 32-byte scalar value.
int purify_bppp_prove_norm_arg_with_resources(purify_bppp_backend_resources *resources, const unsigned char rho32[32], const unsigned char *n_vec32, size_t n_vec_len, const unsigned char *l_vec32, size_t l_vec_len, const unsigned char *c_vec32, size_t c_vec_len, unsigned char commitment_out33[33], unsigned char *proof_out, size_t *proof_len)
int purify_bppp_offset_commitment(purify_secp_context *context, const unsigned char commitment33[33], const unsigned char scalar32[32], unsigned char commitment_out33[33])
int purify_bppp_prove_norm_arg_to_commitment(purify_secp_context *context, const unsigned char rho32[32], const unsigned char *generators33, size_t generators_count, const unsigned char *n_vec32, size_t n_vec_len, const unsigned char *l_vec32, size_t l_vec_len, const unsigned char *c_vec32, size_t c_vec_len, const unsigned char commitment33[33], unsigned char *proof_out, size_t *proof_len)
int purify_bppp_base_generator(purify_secp_context *context, unsigned char out33[33])
Serializes the secp256k1 base generator into compressed form.
int purify_bppp_commit_norm_arg_with_resources(purify_bppp_backend_resources *resources, const unsigned char rho32[32], const unsigned char *n_vec32, size_t n_vec_len, const unsigned char *l_vec32, size_t l_vec_len, const unsigned char *c_vec32, size_t c_vec_len, unsigned char commitment_out33[33])
size_t purify_bppp_required_proof_size(size_t n_vec_len, size_t c_vec_len)
Computes the maximum serialized size of a BPPP norm proof.
purify_bppp_backend_resources * purify_bppp_backend_resources_create(purify_secp_context *context, const unsigned char *generators33, size_t generators_count)
int purify_bppp_verify_norm_arg_with_resources(purify_bppp_backend_resources *resources, const unsigned char rho32[32], const unsigned char *c_vec32, size_t c_vec_len, size_t n_vec_len, const unsigned char commitment33[33], const unsigned char *proof, size_t proof_len)
C ABI bridging Purify C++ code to secp256k1-zkp BPPP functionality.
Purify result carrier that either holds a value or an error.
bool has_value() const noexcept
Field element modulo the backend scalar field used by this implementation.
std::optional< FieldElement > sqrt() const
Computes a square root when one exists, otherwise returns std::nullopt.
static FieldElement one()
Returns the multiplicative identity of the scalar field.
static Result< FieldElement > try_from_bytes32(const std::array< unsigned char, 32 > &bytes)
Decodes a canonical 32-byte big-endian field element.
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.
Move-only packed Purify secret stored in dedicated heap memory.
Common interface for reusable experimental BPPP backend state.
virtual ~ExperimentalCircuitBackend()
Thread-local clone of one warmed experimental BPPP backend-resource line.
bool empty() const noexcept
ExperimentalCircuitCacheLine & operator=(const ExperimentalCircuitCacheLine &)=delete
~ExperimentalCircuitCacheLine() override
ExperimentalCircuitCacheLine()
Caller-owned cache for reusable experimental circuit reduction and BPPP backend data.
ExperimentalCircuitCache & operator=(const ExperimentalCircuitCache &)=delete
Result< ExperimentalCircuitCacheLine > clone_line_for_thread(std::span< const PointBytes > generators) const
Clones one warmed backend-resource line for thread-local use.
ExperimentalCircuitCache()
std::shared_ptr< const void > find_public_data(const std::array< unsigned char, 32 > &key) const
Looks up opaque cached reduction data by digest key.
void insert_public_data(std::array< unsigned char, 32 > key, std::shared_ptr< const void > value)
Stores opaque cached reduction data by digest key.
purify_bppp_backend_resources * get_or_create_backend_resources(std::span< const PointBytes > generators, purify_secp_context *secp_context)
Returns cached backend resources for this generator set, creating them on first use.
std::size_t size() const noexcept
~ExperimentalCircuitCache()
#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.
Result< ExperimentalCircuitZkNormArgProof > prove_experimental_circuit_zk_norm_arg_impl(const NativeBulletproofCircuit &circuit, const BulletproofAssignmentData &assignment, const ScalarBytes &nonce, std::span< const PointBytes > public_commitments, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding, bool externalize_commitments, ExperimentalCircuitBackend *cache)
Result< NormArgProof > prove_norm_arg_to_commitment(const NormArgInputs &inputs, const PointBytes &commitment, purify_secp_context *secp_context)
Produces a standalone BPPP norm argument anchored to a caller-supplied public commitment.
Result< ExperimentalCircuitNormArgProof > prove_experimental_circuit_norm_arg_to_commitment(const NativeBulletproofCircuit &circuit, const BulletproofAssignmentData &assignment, const PointBytes &witness_commitment, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalCircuitBackend *cache=nullptr)
Produces an anchored transparent circuit proof against a caller-supplied reduced witness commitment.
std::array< unsigned char, 32 > ScalarBytes
Big-endian 32-byte scalar encoding.
Result< PointBytes > commit_experimental_circuit_witness(const NativeBulletproofCircuit &circuit, const BulletproofAssignmentData &assignment, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalCircuitBackend *cache=nullptr)
Commits to the reduced witness coordinates used by the experimental circuit-to-BPPP reduction.
Result< PointBytes > commit_norm_arg(const NormArgInputs &inputs, purify_secp_context *secp_context)
Computes the public BPPP commitment for a standalone norm-argument input bundle.
bool verify_norm_arg_with_cache(const NormArgProof &proof, purify_secp_context *secp_context, ExperimentalCircuitBackend *cache=nullptr)
Result< bool > verify_experimental_circuit_zk_norm_arg_impl(const NativeBulletproofCircuit &circuit, const ExperimentalCircuitZkNormArgProof &proof, std::span< const PointBytes > public_commitments, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding, bool externalize_commitments, ExperimentalCircuitBackend *cache)
std::size_t digest_prefix_hash(const Digest &digest) noexcept
Result< bool > verify_experimental_circuit_norm_arg(const NativeBulletproofCircuit &circuit, const ExperimentalCircuitNormArgProof &proof, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalCircuitBackend *cache=nullptr)
Verifies an experimental transparent circuit proof produced by prove_experimental_circuit_norm_arg.
GeneratorBytes base_generator(purify_secp_context *secp_context)
Returns the serialized secp256k1 base generator used as the blind generator.
std::unique_ptr< purify_bppp_backend_resources, BpppBackendResourcesDeleter > OwnedBpppBackendResources
Result< bool > verify_experimental_circuit_zk_norm_arg_with_public_commitments(const NativeBulletproofCircuit &circuit, const ExperimentalCircuitZkNormArgProof &proof, std::span< const PointBytes > public_commitments, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalCircuitBackend *cache=nullptr)
Verifies an experimental masked circuit proof against explicit public commitment points.
std::array< unsigned char, 33 > PointBytes
Compressed 33-byte curve-point encoding.
Result< NormArgProof > prove_norm_arg_to_commitment_with_cache(const NormArgInputs &inputs, const PointBytes &commitment, purify_secp_context *secp_context, ExperimentalCircuitBackend *cache=nullptr)
bool verify_norm_arg(const NormArgProof &proof, purify_secp_context *secp_context)
Verifies a standalone BPPP norm argument.
Result< ExperimentalCircuitZkNormArgProof > prove_experimental_circuit_zk_norm_arg_with_public_commitments(const NativeBulletproofCircuit &circuit, const BulletproofAssignmentData &assignment, const ScalarBytes &nonce, std::span< const PointBytes > public_commitments, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalCircuitBackend *cache=nullptr)
Produces an experimental masked circuit proof bound to explicit public commitment points.
Result< PointBytes > commit_norm_arg_with_cache(const NormArgInputs &inputs, purify_secp_context *secp_context, ExperimentalCircuitBackend *cache=nullptr)
Result< ExperimentalCircuitNormArgProof > prove_experimental_circuit_norm_arg(const NativeBulletproofCircuit &circuit, const BulletproofAssignmentData &assignment, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalCircuitBackend *cache=nullptr)
Produces an anchored transparent circuit proof using the experimental circuit-to-BPPP reduction.
GeneratorBytes value_generator_h(purify_secp_context *secp_context)
Returns the serialized alternate generator used for committed values.
Result< CommittedPurifyWitness > commit_output_witness(const Bytes &message, const SecretKey &secret, const ScalarBytes &blind, purify_secp_context *secp_context)
Evaluates Purify, derives its witness, and commits to the output using Purify's default generators.
Result< NormArgProof > prove_norm_arg(const NormArgInputs &inputs, purify_secp_context *secp_context)
Produces a standalone BPPP norm argument.
GeneratorBackendCacheKey generator_backend_cache_key(std::span< const PointBytes > generators)
Result< std::vector< PointBytes > > create_generators(std::size_t count, purify_secp_context *secp_context)
Expands the BPPP generator list.
Result< bool > verify_experimental_circuit_zk_norm_arg(const NativeBulletproofCircuit &circuit, const ExperimentalCircuitZkNormArgProof &proof, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalCircuitBackend *cache=nullptr)
Verifies an experimental masked circuit proof produced by prove_experimental_circuit_zk_norm_arg.
std::array< unsigned char, 32 > CircuitNormArgPublicDataCacheKey
std::array< unsigned char, 33 > GeneratorBytes
Serialized generator encoding used by the BPPP bridge.
Result< PointBytes > pedersen_commit_char(const ScalarBytes &blind, const ScalarBytes &value, purify_secp_context *secp_context)
Computes a Pedersen commitment to an arbitrary 32-byte scalar value using Purify's default generators...
ScalarBytes scalar_bytes(const FieldElement &value)
Serializes a Purify field element into the scalar encoding expected by the BPPP bridge.
Result< ExperimentalCircuitZkNormArgProof > prove_experimental_circuit_zk_norm_arg(const NativeBulletproofCircuit &circuit, const BulletproofAssignmentData &assignment, const ScalarBytes &nonce, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalCircuitBackend *cache=nullptr)
Produces an experimental masked circuit proof over the reduced BPPP relation.
std::array< unsigned char, 32 > GeneratorBackendCacheKey
Status require_secp_context(const purify_secp_context *context, const char *error_context)
Result< Bytes > experimental_circuit_binding_digest(const NativeBulletproofCircuit &circuit, std::span< const unsigned char > statement_binding)
constexpr Unexpected< Error > unexpected_error(ErrorCode code, const char *context=nullptr)
Constructs an unexpected Error value from a machine-readable code.
Result< BulletproofWitnessData > prove_assignment_data(const Bytes &message, const SecretKey &secret)
Computes the native Purify witness for a message and secret.
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)
bool checked_mul_size(std::size_t lhs, std::size_t rhs, std::size_t &out) noexcept
Bytes bytes_from_ascii(std::string_view input)
Encodes an ASCII string as a byte vector.
std::vector< unsigned char > Bytes
Dynamically sized byte string used for messages, serialized witnesses, and proofs.
bool checked_add_size(std::size_t lhs, std::size_t rhs, std::size_t &out) noexcept
Expected< void, Error > Status
Expected-returning convenience alias for Purify status-only APIs.
std::size_t generator_count
std::vector< FieldElement > c_vec
std::vector< std::array< FieldElement, 2 > > plus_terms
std::vector< FieldElement > public_commitment_coeffs
std::span< const unsigned char > serialized_bytes
purify_bppp_backend_resources * backend_resources
std::vector< ScalarBytes > c_vec_bytes
std::vector< FieldElement > plus_shift
CircuitNormArgPublicDataPtr public_data
std::vector< std::array< FieldElement, 2 > > minus_terms
std::vector< FieldElement > l_vec
std::vector< FieldElement > minus_shift
std::vector< PointBytes > generators
std::vector< FieldElement > n_vec
Narrow C ABI exposing secp256k1 scalar and HMAC helpers to the C++ headers.
int purify_sha256_many(unsigned char output32[32], const unsigned char *const *items, const size_t *item_lens, size_t items_count)
Computes SHA-256 over a set of byte strings.
void purify_sha256(unsigned char output32[32], const unsigned char *data, size_t data_len)
Computes SHA-256 over a byte string.
Columnar witness assignment compatible with the native Bulletproof circuit layout.
std::vector< FieldElement > output
std::vector< FieldElement > commitments
std::vector< FieldElement > right
std::vector< FieldElement > left
Native in-memory representation of a Bulletproof-style arithmetic circuit.
bool evaluate(const BulletproofAssignmentData &assignment) const
Evaluates the circuit against a witness assignment and checks all gate and row equations.
bool has_valid_shape() const
Returns true when the sparse matrix vectors match the declared circuit dimensions.
std::size_t n_commitments
void operator()(purify_bppp_backend_resources *resources) const noexcept
Purify witness bundle together with a Pedersen commitment to the output.
OwnedBpppBackendResources backend_resources
GeneratorBackendCacheKey backend_key
std::unordered_map< GeneratorBackendCacheKey, OwnedBpppBackendResources, GeneratorBackendCacheKeyHash > backend_resources
std::unordered_map< CircuitNormArgPublicDataCacheKey, std::shared_ptr< const void >, CircuitNormArgPublicDataCacheKeyHash > public_data
Experimental transparent circuit proof backed by the standalone BPPP norm argument.
PointBytes witness_commitment
Experimental masked circuit proof that hides the reduced witness before the final BPPP argument.
PointBytes a_commitment
Witness-only outer A commitment; verifiers re-anchor it to the public statement.
std::size_t operator()(const GeneratorBackendCacheKey &key) const noexcept
Standalone BPPP norm-argument proof bundle with all verifier-side inputs.
std::vector< PointBytes > generators
std::vector< ScalarBytes > c_vec
int PURIFY_UINT_FN() is_zero(const uint64_t value[PURIFY_UINT_WORDS])