purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
bppp.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Judica, Inc.
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or https://opensource.org/license/mit/.
4
10#include "purify/bppp.hpp"
11
12#include <algorithm>
13#include <cstdint>
14#include <cstring>
15#include <limits>
16#include <memory>
17#include <span>
18#include <string>
19#include <string_view>
20#include <tuple>
21#include <type_traits>
22#include <unordered_map>
23#include <vector>
24
25#include "bppp_bridge.h"
27#include "purify/secp_bridge.h"
28
29namespace purify::bppp {
30
36
38 std::unique_ptr<purify_bppp_backend_resources, BpppBackendResourcesDeleter>;
39
40using GeneratorBackendCacheKey = std::array<unsigned char, 32>;
41
42template <typename Digest>
43std::size_t digest_prefix_hash(const Digest &digest) noexcept {
44 static_assert(sizeof(std::size_t) <= std::tuple_size_v<Digest>,
45 "digest prefix hash must fit within digest output");
46 std::size_t out = 0;
47 std::memcpy(&out, digest.data(), sizeof(std::size_t));
48 return out;
49}
50
52 std::size_t operator()(const GeneratorBackendCacheKey &key) const noexcept {
53 return digest_prefix_hash(key);
54 }
55};
56
57using CircuitNormArgPublicDataCacheKey = std::array<unsigned char, 32>;
59
61 std::span<const PointBytes> generators) {
63 auto serialized_view = std::as_bytes(generators);
64 const unsigned char *serialized =
65 serialized_view.empty()
66 ? nullptr
67 : reinterpret_cast<const unsigned char *>(serialized_view.data());
68 purify_sha256(key.data(), serialized, serialized_view.size());
69 return key;
70}
71
73
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>{};
80}
81
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));
87 }
88}
89
91detail::ExperimentalCircuitBackendAccess::get_or_create_backend_resources(
92 ExperimentalCircuitBackend *backend,
93 std::span<const PointBytes> generators,
94 purify_secp_context *secp_context) {
95 return backend != nullptr
96 ? backend->get_or_create_backend_resources_impl(generators,
97 secp_context)
98 : nullptr;
99}
100
106
116
119
121 ExperimentalCircuitCacheLine &&other) noexcept = default;
122
124 ExperimentalCircuitCacheLine &&other) noexcept = default;
125
127
129 return impl_ == nullptr || impl_->backend_resources == nullptr;
130}
131
132std::shared_ptr<const void> ExperimentalCircuitCacheLine::find_public_data_impl(
133 const std::array<unsigned char, 32> &key) const {
134 (void)key;
135 return {};
136}
137
138void ExperimentalCircuitCacheLine::insert_public_data_impl(
139 std::array<unsigned char, 32> key, std::shared_ptr<const void> value) {
140 (void)key;
141 (void)value;
142}
143
145ExperimentalCircuitCacheLine::get_or_create_backend_resources_impl(
146 std::span<const PointBytes> generators,
147 purify_secp_context *secp_context) {
148 (void)secp_context;
149 if (impl_ == nullptr || impl_->backend_resources == nullptr ||
150 generators.empty() || !impl_->has_backend_key) {
151 return nullptr;
152 }
153 return generator_backend_cache_key(generators) == impl_->backend_key
154 ? impl_->backend_resources.get()
155 : nullptr;
156}
157
159 : impl_(std::make_unique<Impl>()) {}
160
162 ExperimentalCircuitCache &&other) noexcept = default;
163
165 ExperimentalCircuitCache &&other) noexcept = default;
166
168
170 if (impl_ != nullptr) {
171 impl_->public_data.clear();
172 impl_->backend_resources.clear();
173 }
174}
175
176std::size_t ExperimentalCircuitCache::size() const noexcept {
177 return impl_ != nullptr
178 ? impl_->public_data.size() + impl_->backend_resources.size()
179 : 0;
180}
181
183 std::span<const PointBytes> generators) const {
185 if (impl_ == nullptr || generators.empty()) {
186 return clone;
187 }
188
190 auto it = impl_->backend_resources.find(key);
191 if (it == impl_->backend_resources.end()) {
192 return clone;
193 }
194
196 purify_bppp_backend_resources_clone(it->second.get());
197 if (cloned == nullptr) {
198 return unexpected_error(
200 "ExperimentalCircuitCache::clone_line_for_thread:backend_resources");
201 }
202 clone.impl_->backend_key = key;
203 clone.impl_->has_backend_key = true;
204 clone.impl_->backend_resources.reset(cloned);
205 return clone;
206}
207
209 const std::array<unsigned char, 32> &key) const {
210 return find_public_data_impl(key);
211}
212
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));
217}
218
221 std::span<const PointBytes> generators,
222 purify_secp_context *secp_context) {
223 return get_or_create_backend_resources_impl(generators, secp_context);
224}
225
226std::shared_ptr<const void> ExperimentalCircuitCache::find_public_data_impl(
227 const std::array<unsigned char, 32> &key) const {
228 if (impl_ == nullptr) {
229 return {};
230 }
231 auto it = impl_->public_data.find(key);
232 if (it == impl_->public_data.end()) {
233 return {};
234 }
235 return it->second;
236}
237
238void ExperimentalCircuitCache::insert_public_data_impl(
239 std::array<unsigned char, 32> key,
240 std::shared_ptr<const void> value) {
241 if (impl_ == nullptr) {
242 return;
243 }
244 impl_->public_data.emplace(std::move(key), std::move(value));
245}
246
248ExperimentalCircuitCache::get_or_create_backend_resources_impl(
249 std::span<const PointBytes> generators,
250 purify_secp_context *secp_context) {
251 if (impl_ == nullptr || secp_context == nullptr || generators.empty()) {
252 return nullptr;
253 }
254
255 auto serialized_view = std::as_bytes(generators);
256 const unsigned char *serialized =
257 serialized_view.empty()
258 ? nullptr
259 : reinterpret_cast<const unsigned char *>(serialized_view.data());
260
262
263 auto it = impl_->backend_resources.find(key);
264 if (it != impl_->backend_resources.end()) {
265 return it->second.get();
266 }
267
269 purify_bppp_backend_resources_create(secp_context, serialized,
270 generators.size());
271 if (created == nullptr) {
272 return nullptr;
273 }
274
275 auto inserted =
276 impl_->backend_resources.emplace(key, OwnedBpppBackendResources(created));
277 return inserted.first->second.get();
278}
279
280namespace {
281
282template <typename ByteArray> constexpr void assert_packed_byte_array_layout() {
283 static_assert(
284 std::is_same_v<typename ByteArray::value_type, unsigned char>,
285 "byte-span bridge helpers require unsigned-char array elements");
286 static_assert(
287 std::is_trivially_copyable_v<ByteArray>,
288 "byte-span bridge helpers require trivially copyable array wrappers");
289 static_assert(
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 "
296 "with no padding");
297}
298
299template <typename ByteArray>
300std::span<const unsigned char> byte_span(const std::vector<ByteArray> &values) {
301 assert_packed_byte_array_layout<ByteArray>();
302 auto bytes =
303 std::as_bytes(std::span<const ByteArray>(values.data(), values.size()));
304 return {reinterpret_cast<const unsigned char *>(bytes.data()), bytes.size()};
305}
306
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()};
313}
314
316bool require_ok(int ok) { return ok != 0; }
317
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);
341
342struct ResolvedBpppGeneratorBackend {
343 std::span<const unsigned char> serialized_bytes{};
344 std::size_t generator_count = 0;
346};
347
348ResolvedBpppGeneratorBackend resolve_bppp_generator_backend(
349 ExperimentalCircuitBackend *cache,
350 const std::vector<PointBytes> &serialized_generators,
351 purify_secp_context *secp_context) {
352 ResolvedBpppGeneratorBackend out;
353 out.serialized_bytes = byte_span(serialized_generators);
354 out.generator_count = serialized_generators.size();
355
356 if (cache == nullptr || serialized_generators.empty()) {
357 return out;
358 }
359 out.backend_resources =
360 detail::ExperimentalCircuitBackendAccess::get_or_create_backend_resources(
361 cache, serialized_generators, secp_context);
362 return out;
363}
364
365Result<std::size_t> round_up_power_of_two(std::size_t value,
366 const char *context) {
367 if (value == 0) {
368 return unexpected_error(ErrorCode::Overflow, context);
369 }
370 std::size_t out = 1;
371 while (out < value) {
372 if (out > std::numeric_limits<std::size_t>::max() / 2) {
373 return unexpected_error(ErrorCode::Overflow, context);
374 }
375 out <<= 1;
376 }
377 return out;
378}
379
380CircuitNormArgPublicDataCacheKey circuit_norm_arg_public_data_cache_key(
381 std::span<const unsigned char> binding_digest,
382 bool externalize_commitments) {
384 const unsigned char mode = externalize_commitments ? '\x01' : '\x00';
385 const std::array<const unsigned char *, 2> items{
386 {&mode, binding_digest.data()}};
387 const std::array<size_t, 2> item_lens{{1, binding_digest.size()}};
388 const int ok = purify_sha256_many(key.data(), items.data(), item_lens.data(),
389 items.size());
390 assert(ok != 0 &&
391 "circuit norm arg public data cache key generation must succeed");
392 (void)ok;
393 return key;
394}
395
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));
399 }
400}
401
402Result<FieldElement> derive_nonzero_scalar(std::span<const unsigned char> seed,
403 const TaggedHash& tag,
404 std::size_t index) {
405 Bytes prefix(seed.begin(), seed.end());
406 PURIFY_ASSIGN_OR_RETURN(const auto &encoded_index,
407 narrow_size_to_u64(index,
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);
414 ScalarBytes candidate_bytes{};
415 candidate_bytes =
416 tag.digest(std::span<const unsigned char>(input.data(), input.size()));
417 Result<FieldElement> candidate =
418 FieldElement::try_from_bytes32(candidate_bytes);
419 if (candidate.has_value() && !candidate->is_zero()) {
420 return candidate;
421 }
422 }
424 "derive_nonzero_scalar:exhausted");
425}
426
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());
431 PURIFY_ASSIGN_OR_RETURN(const auto &encoded_index,
432 narrow_size_to_u64(index, "derive_scalar:index"),
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);
439 ScalarBytes candidate_bytes{};
440 candidate_bytes =
441 tag.digest(std::span<const unsigned char>(input.data(), input.size()));
442 Result<FieldElement> candidate =
443 FieldElement::try_from_bytes32(candidate_bytes);
444 if (candidate.has_value()) {
445 return candidate;
446 }
447 }
449 "derive_scalar:exhausted");
450}
451
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;
459 FieldElement total = FieldElement::zero();
460 for (std::size_t i = 0; i < lhs.size(); ++i) {
461 total = total + (weight * lhs[i] * rhs[i]);
462 weight = weight * mu;
463 }
464 return total;
465}
466
467struct CircuitNormArgPublicData {
468 FieldElement rho = FieldElement::zero();
470 FieldElement target = FieldElement::zero();
471 std::vector<PointBytes> generators;
472 std::vector<FieldElement> c_vec;
473 std::vector<ScalarBytes> c_vec_bytes;
474 std::vector<FieldElement> public_commitment_coeffs;
475 std::vector<std::array<FieldElement, 2>> plus_terms;
476 std::vector<std::array<FieldElement, 2>> minus_terms;
477 std::vector<FieldElement> plus_shift;
478 std::vector<FieldElement> minus_shift;
479};
480
481using CircuitNormArgPublicDataPtr =
482 std::shared_ptr<const CircuitNormArgPublicData>;
483
484struct CircuitNormArgReduction {
485 CircuitNormArgPublicDataPtr public_data;
486 std::vector<FieldElement> n_vec;
487 std::vector<FieldElement> l_vec;
488};
489
490Result<CircuitNormArgPublicDataPtr> build_circuit_norm_arg_public_data(
491 const NativeBulletproofCircuit &circuit,
492 std::span<const unsigned char> statement_binding,
493 purify_secp_context *secp_context,
494 bool externalize_commitments = false,
495 ExperimentalCircuitBackend *cache = nullptr) {
497 require_secp_context(secp_context, "build_circuit_norm_arg_public_data:secp_context"),
498 "build_circuit_norm_arg_public_data:secp_context");
499 if (!circuit.has_valid_shape()) {
501 "build_circuit_norm_arg_public_data:circuit_shape");
502 }
503 if (!is_power_of_two_size(circuit.n_gates)) {
504 return unexpected_error(
506 "build_circuit_norm_arg_public_data:n_gates_power_of_two");
507 }
508
510 const auto &binding_digest,
511 experimental_circuit_binding_digest(circuit, statement_binding),
512 "build_circuit_norm_arg_public_data:binding_digest");
514 const auto &rho,
515 derive_nonzero_scalar(binding_digest, kCircuitNormArgRhoTaggedHash, 0),
516 "build_circuit_norm_arg_public_data:rho");
518 circuit_norm_arg_public_data_cache_key(binding_digest,
519 externalize_commitments);
520 if (std::shared_ptr<const void> cached =
521 detail::ExperimentalCircuitBackendAccess::find_public_data(cache,
522 cache_key)) {
523 return std::static_pointer_cast<const CircuitNormArgPublicData>(cached);
524 }
525
526 std::optional<FieldElement> sqrt_minus_one =
528 if (!sqrt_minus_one.has_value()) {
529 return unexpected_error(
531 "build_circuit_norm_arg_public_data:sqrt_minus_one");
532 }
533
534 const FieldElement zero = FieldElement::zero();
535 const FieldElement one = FieldElement::one();
536 const FieldElement two = FieldElement::from_int(2);
537 const FieldElement four = FieldElement::from_int(4);
538 const FieldElement inv2 = two.inverse();
539 const FieldElement inv4 = four.inverse();
540
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;
548 }
549
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,
555 i),
556 "build_circuit_norm_arg_public_data:constraint_weight");
557 row_weights[i] = challenge;
558 }
559
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();
566 }
567
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]);
571 }
572
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);
581 }
582 }
583 };
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);
588
589 auto out = std::make_shared<CircuitNormArgPublicData>();
590 out->rho = rho;
591 out->rho_bytes = scalar_bytes(rho);
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);
596
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};
601 };
602
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;
608
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);
615 }
616
617 std::size_t l_value_count = 0;
618 if (!checked_add_size(circuit.n_gates,
619 externalize_commitments ? 0 : circuit.n_commitments,
620 l_value_count) ||
621 !checked_add_size(l_value_count, 1, l_value_count)) {
622 return unexpected_error(
624 "build_circuit_norm_arg_public_data:l_value_count");
625 }
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];
634 }
635 if (externalize_commitments) {
636 out->public_commitment_coeffs = std::move(commitment_coeffs);
637 } else {
638 for (std::size_t i = 0; i < circuit.n_commitments; ++i) {
639 out->c_vec[circuit.n_gates + i] = commitment_coeffs[i];
640 }
641 }
642 out->target = constant.negate();
643 out->c_vec_bytes = scalar_bytes(out->c_vec);
644
645 std::size_t witness_generator_count = 0;
646 std::size_t generator_count = 0;
647 if (!checked_mul_size(circuit.n_gates, 4, witness_generator_count) ||
648 !checked_add_size(witness_generator_count, out->c_vec.size(),
650 return unexpected_error(
652 "build_circuit_norm_arg_public_data:generator_count");
653 }
655 auto generators, create_generators(generator_count, secp_context),
656 "build_circuit_norm_arg_public_data:create_generators");
657 out->generators = std::move(generators);
658 CircuitNormArgPublicDataPtr shared = out;
659 if (cache != nullptr) {
660 detail::ExperimentalCircuitBackendAccess::insert_public_data(cache,
661 cache_key,
662 shared);
663 }
664 return shared;
665}
666
667Result<CircuitNormArgReduction> reduce_experimental_circuit_to_norm_arg(
668 const NativeBulletproofCircuit &circuit,
669 const BulletproofAssignmentData &assignment,
670 purify_secp_context *secp_context,
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) {
678 return unexpected_error(
680 "reduce_experimental_circuit_to_norm_arg:assignment_shape");
681 }
682
684 const auto &public_data,
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");
688
689 CircuitNormArgReduction out;
690 out.public_data = public_data;
691 std::size_t n_vec_capacity = 0;
692 if (!checked_mul_size(circuit.n_gates, 4, n_vec_capacity)) {
693 return unexpected_error(
695 "reduce_experimental_circuit_to_norm_arg:n_vec_capacity");
696 }
697 out.n_vec.reserve(n_vec_capacity);
698 out.l_vec.assign(out.public_data->c_vec.size(), FieldElement::zero());
699
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];
705 for (const FieldElement &term : out.public_data->plus_terms[i]) {
706 out.n_vec.push_back(term * plus_value * rho_weight_inv);
707 rho_weight_inv = rho_weight_inv * rho_inv;
708 }
709
710 const FieldElement minus_value = assignment.left[i] - assignment.right[i] +
711 out.public_data->minus_shift[i];
712 for (const FieldElement &term : out.public_data->minus_terms[i]) {
713 out.n_vec.push_back(term * minus_value * rho_weight_inv);
714 rho_weight_inv = rho_weight_inv * rho_inv;
715 }
716
717 out.l_vec[i] = assignment.output[i];
718 }
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];
722 }
723 }
724 return out;
725}
726
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;
731 inputs.n_vec = scalar_bytes(reduction.n_vec);
732 inputs.l_vec = scalar_bytes(reduction.l_vec);
733 inputs.c_vec = reduction.public_data->c_vec_bytes;
734 return inputs;
735}
736
737Result<PointBytes>
738commit_norm_arg_witness_only(const NormArgInputs &inputs,
739 purify_secp_context *secp_context,
740 ExperimentalCircuitBackend *cache = nullptr) {
742 require_secp_context(secp_context, "commit_norm_arg_witness_only:secp_context"),
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");
747 }
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");
752 }
753
754 const std::vector<PointBytes> *generators = &inputs.generators;
755 std::vector<PointBytes> generated_generators;
756 if (generators->empty()) {
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);
761 generators = &generated_generators;
762 }
763
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);
768 PointBytes commitment{};
769 const int ok =
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())
775 secp_context, resolved.serialized_bytes.data(),
776 resolved.generator_count,
777 n_vec.data(), inputs.n_vec.size(), l_vec.data(),
778 inputs.l_vec.size(), commitment.data());
779 if (!require_ok(ok)) {
781 "commit_norm_arg_witness_only:backend");
782 }
783 return commitment;
784}
785
786Result<PointBytes> offset_commitment(const PointBytes &commitment,
787 const FieldElement &scalar,
788 purify_secp_context *secp_context) {
789 PointBytes out{};
790 ScalarBytes scalar32 = scalar_bytes(scalar);
791 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "offset_commitment:secp_context"),
792 "offset_commitment:secp_context");
793 if (!require_ok(purify_bppp_offset_commitment(secp_context, commitment.data(),
794 scalar32.data(), out.data()))) {
796 "offset_commitment:backend");
797 }
798 return out;
799}
800
801Result<PointBytes> point_scale(const PointBytes &point,
802 const FieldElement &scalar,
803 purify_secp_context *secp_context) {
804 PointBytes out{};
805 ScalarBytes scalar32 = scalar_bytes(scalar);
806 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "point_scale:secp_context"),
807 "point_scale:secp_context");
808 if (!require_ok(
809 purify_point_scale(secp_context, point.data(), scalar32.data(),
810 out.data()))) {
812 "point_scale:backend");
813 }
814 return out;
815}
816
817Result<PointBytes> point_add(const PointBytes &lhs,
818 const PointBytes &rhs,
819 purify_secp_context *secp_context) {
820 PointBytes out{};
821 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "point_add:secp_context"),
822 "point_add:secp_context");
823 if (!require_ok(
824 purify_point_add(secp_context, lhs.data(), rhs.data(), out.data()))) {
826 "point_add:backend");
827 }
828 return out;
829}
830
831Result<PointBytes> point_from_scalar_base(const FieldElement &scalar,
832 purify_secp_context *secp_context) {
833 ScalarBytes blind{};
834 return pedersen_commit_char(blind, scalar_bytes(scalar), secp_context,
835 base_generator(secp_context),
836 base_generator(secp_context));
837}
838
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,
844 narrow_size_to_u64(public_commitments.size(),
845 "bind_public_commitments:count"),
846 "bind_public_commitments:count");
848 const auto &encoded_binding_size,
849 narrow_size_to_u64(statement_binding.size(),
850 "bind_public_commitments:statement_binding"),
851 "bind_public_commitments:statement_binding");
852
853 std::size_t point_bytes = 0;
854 std::size_t total_size = 0;
855 Bytes out = bytes_from_ascii(kCircuitNormArgPublicCommitmentTag);
856 if (!checked_mul_size(public_commitments.size(), sizeof(PointBytes),
857 point_bytes) ||
858 !checked_add_size(out.size(), 8, total_size) ||
859 !checked_add_size(total_size, point_bytes, total_size) ||
860 !checked_add_size(total_size, 8, total_size) ||
861 !checked_add_size(total_size, statement_binding.size(), total_size)) {
863 "bind_public_commitments:reserve");
864 }
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());
869 }
870 append_u64_le(out, encoded_binding_size);
871 out.insert(out.end(), statement_binding.begin(), statement_binding.end());
872 return out;
873}
874
875Status
876validate_public_commitments(std::span<const PointBytes> public_commitments,
877 std::span<const FieldElement> commitments,
878 purify_secp_context *secp_context) {
879 if (public_commitments.size() != commitments.size()) {
881 "validate_public_commitments:size");
882 }
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");
890 }
891 }
892 return {};
893}
894
895Result<PointBytes> add_scaled_points(const PointBytes &base_commitment,
896 std::span<const PointBytes> points,
897 std::span<const FieldElement> scalars,
898 purify_secp_context *secp_context) {
899 if (points.size() != scalars.size()) {
901 "add_scaled_points:size_mismatch");
902 }
903
904 PointBytes out = base_commitment;
905 for (std::size_t i = 0; i < points.size(); ++i) {
906 if (scalars[i].is_zero()) {
907 continue;
908 }
909 PURIFY_ASSIGN_OR_RETURN(const auto &scaled, point_scale(points[i], scalars[i], secp_context),
910 "add_scaled_points:scale");
911 PURIFY_ASSIGN_OR_RETURN(const auto &combined, point_add(out, scaled, secp_context),
912 "add_scaled_points:add");
913 out = combined;
914 }
915 return out;
916}
917
918Result<PointBytes>
919anchor_zk_a_commitment(const PointBytes &a_witness_commitment,
920 const CircuitNormArgPublicData &public_data,
921 std::span<const PointBytes> public_commitments,
922 purify_secp_context *secp_context) {
923 if (public_commitments.size() !=
924 public_data.public_commitment_coeffs.size()) {
926 "anchor_zk_a_commitment:public_commitment_size");
927 }
928
930 auto anchored, offset_commitment(a_witness_commitment, public_data.target, secp_context),
931 "anchor_zk_a_commitment:target");
932 if (public_commitments.empty()) {
933 return anchored;
934 }
935
936 std::vector<FieldElement> negated_coeffs;
937 negated_coeffs.reserve(public_data.public_commitment_coeffs.size());
938 for (const FieldElement &coeff : public_data.public_commitment_coeffs) {
939 negated_coeffs.push_back(coeff.negate());
940 }
941 return add_scaled_points(anchored, public_commitments, negated_coeffs, secp_context);
942}
943
944Result<PointBytes>
945commit_explicit_norm_arg(const NormArgInputs &inputs, const FieldElement &value,
946 purify_secp_context *secp_context,
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);
952}
953
954Result<PointBytes> combine_zk_commitments(const PointBytes &a_commitment,
955 const PointBytes &s_commitment,
956 const FieldElement &challenge,
957 const FieldElement &t2,
958 purify_secp_context *secp_context) {
959 PURIFY_ASSIGN_OR_RETURN(const auto &scaled_s, point_scale(s_commitment, challenge, secp_context),
960 "combine_zk_commitments:scale_s");
961 PURIFY_ASSIGN_OR_RETURN(const auto &combined, point_add(a_commitment, scaled_s, secp_context),
962 "combine_zk_commitments:add");
963 return offset_commitment(combined, challenge * challenge * t2, secp_context);
964}
965
966template <typename Inputs>
967Result<NormArgProof>
968prove_norm_arg_impl(Inputs &&inputs,
969 purify_secp_context *secp_context,
970 ExperimentalCircuitBackend *cache = nullptr) {
971 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "prove_norm_arg:secp_context"),
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");
976 }
977 if (inputs.l_vec.size() != inputs.c_vec.size()) {
979 "prove_norm_arg:l_c_size_mismatch");
980 }
981
982 const std::vector<PointBytes> *generators = &inputs.generators;
983 std::vector<PointBytes> generated_generators;
984 if (generators->empty()) {
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);
989 generators = &generated_generators;
990 }
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 =
997 purify_bppp_required_proof_size(inputs.n_vec.size(), inputs.c_vec.size());
998 PointBytes commitment{};
999 Bytes proof(proof_len);
1000
1001 if (proof_len == 0) {
1003 "prove_norm_arg:proof_len_zero");
1004 }
1005 const int ok =
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)
1013 secp_context, inputs.rho.data(), resolved.serialized_bytes.data(),
1014 resolved.generator_count, n_vec.data(), inputs.n_vec.size(),
1015 l_vec.data(), inputs.l_vec.size(), c_vec.data(),
1016 inputs.c_vec.size(), commitment.data(), proof.data(),
1017 &proof_len);
1018 if (!require_ok(ok)) {
1020 "prove_norm_arg:backend");
1021 }
1022 proof.resize(proof_len);
1023
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);
1030 } else {
1031 proof_generators = inputs.generators;
1032 }
1033
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);
1038 } else {
1039 proof_c_vec = inputs.c_vec;
1040 }
1041
1042 return NormArgProof{inputs.rho,
1043 std::move(proof_generators),
1044 std::move(proof_c_vec),
1045 inputs.n_vec.size(),
1046 commitment,
1047 std::move(proof)};
1048}
1049
1050Result<FieldElement>
1051derive_zk_challenge(std::span<const unsigned char> binding_digest,
1052 const PointBytes &a_commitment,
1053 const PointBytes &s_commitment, const FieldElement &t2) {
1054 Bytes seed(binding_digest.begin(), binding_digest.end());
1055 seed.insert(seed.end(), a_commitment.begin(), a_commitment.end());
1056 seed.insert(seed.end(), s_commitment.begin(), s_commitment.end());
1057 ScalarBytes t2_bytes = scalar_bytes(t2);
1058 seed.insert(seed.end(), t2_bytes.begin(), t2_bytes.end());
1059 return derive_nonzero_scalar(seed, kCircuitZkChallengeTaggedHash, 0);
1060}
1061
1062} // namespace
1063
1065 GeneratorBytes out{};
1066 bool ok = secp_context != nullptr
1067 && require_ok(purify_bppp_base_generator(secp_context, out.data()));
1068 assert(ok && "base_generator() requires a functioning backend");
1069 (void)ok;
1070 return out;
1071}
1072
1074 GeneratorBytes out{};
1075 bool ok = secp_context != nullptr
1076 && require_ok(
1077 purify_bppp_value_generator_h(secp_context, out.data()));
1078 assert(ok && "value_generator_h() requires a functioning backend");
1079 (void)ok;
1080 return out;
1081}
1082
1084 purify_secp_context *secp_context) {
1085 std::vector<PointBytes> out(count);
1086 if (count == 0) {
1087 return out;
1088 }
1089 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "create_generators:secp_context"),
1090 "create_generators:secp_context");
1091 std::span<unsigned char> serialized = writable_byte_span(out);
1092 std::size_t serialized_len = serialized.size();
1093 if (!require_ok(purify_bppp_create_generators(secp_context, count,
1094 serialized.data(),
1095 &serialized_len))) {
1097 "create_generators:backend");
1098 }
1099 if (serialized_len != serialized.size()) {
1101 "create_generators:serialized_len");
1102 }
1103 return out;
1104}
1105
1108 purify_secp_context *secp_context,
1109 ExperimentalCircuitBackend *cache = nullptr) {
1110 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "commit_norm_arg:secp_context"),
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");
1115 }
1116 if (inputs.l_vec.size() != inputs.c_vec.size()) {
1118 "commit_norm_arg:l_c_size_mismatch");
1119 }
1120
1121 const std::vector<PointBytes> *generators = &inputs.generators;
1122 std::vector<PointBytes> generated_generators;
1123 if (generators->empty()) {
1125 auto generated, create_generators(inputs.n_vec.size() + inputs.l_vec.size(), secp_context),
1126 "commit_norm_arg:create_generators");
1127 generated_generators = std::move(generated);
1128 generators = &generated_generators;
1129 }
1130
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);
1136 PointBytes commitment{};
1137 const int ok =
1138 resolved.backend_resources != nullptr
1140 resolved.backend_resources, inputs.rho.data(), n_vec.data(),
1141 inputs.n_vec.size(), l_vec.data(), inputs.l_vec.size(),
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(),
1146 l_vec.data(), inputs.l_vec.size(), c_vec.data(),
1147 inputs.c_vec.size(), commitment.data());
1148 if (!require_ok(ok)) {
1150 "commit_norm_arg:backend");
1151 }
1152 return commitment;
1153}
1154
1156 const ScalarBytes &value,
1157 purify_secp_context *secp_context) {
1158 return pedersen_commit_char(blind, value, secp_context,
1159 value_generator_h(secp_context),
1160 base_generator(secp_context));
1161}
1162
1164 const ScalarBytes &value,
1165 purify_secp_context *secp_context,
1166 const GeneratorBytes &value_gen,
1167 const GeneratorBytes &blind_gen) {
1168 PointBytes commitment{};
1169 PURIFY_RETURN_IF_ERROR(require_secp_context(secp_context, "pedersen_commit_char:secp_context"),
1170 "pedersen_commit_char:secp_context");
1171 if (!require_ok(purify_pedersen_commit_char(
1172 secp_context, blind.data(), value.data(), value_gen.data(), blind_gen.data(),
1173 commitment.data()))) {
1175 "pedersen_commit_char:backend");
1176 }
1177 return commitment;
1178}
1179
1181 purify_secp_context *secp_context) {
1182 return prove_norm_arg_impl(inputs, secp_context, nullptr);
1183}
1184
1186 purify_secp_context *secp_context) {
1187 return prove_norm_arg_impl(std::move(inputs), secp_context, nullptr);
1188}
1189
1191 const NormArgInputs &inputs, const PointBytes &commitment,
1192 purify_secp_context *secp_context,
1193 ExperimentalCircuitBackend *cache = nullptr) {
1195 require_secp_context(secp_context, "prove_norm_arg_to_commitment:secp_context"),
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");
1200 }
1201 if (inputs.l_vec.size() != inputs.c_vec.size()) {
1203 "prove_norm_arg_to_commitment:l_c_size_mismatch");
1204 }
1205
1206 const std::vector<PointBytes> *generators = &inputs.generators;
1207 std::vector<PointBytes> generated_generators;
1208 if (generators->empty()) {
1210 auto generated, create_generators(inputs.n_vec.size() + inputs.l_vec.size(), secp_context),
1211 "prove_norm_arg_to_commitment:create_generators");
1212 generated_generators = std::move(generated);
1213 generators = &generated_generators;
1214 }
1215
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 =
1222 purify_bppp_required_proof_size(inputs.n_vec.size(), inputs.c_vec.size());
1223 Bytes proof(proof_len);
1224 if (proof_len == 0) {
1226 "prove_norm_arg_to_commitment:proof_len_zero");
1227 }
1228 const int ok =
1229 resolved.backend_resources != nullptr
1231 resolved.backend_resources, inputs.rho.data(), n_vec.data(),
1232 inputs.n_vec.size(), l_vec.data(), inputs.l_vec.size(),
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(),
1239 l_vec.data(), inputs.l_vec.size(), c_vec.data(),
1240 inputs.c_vec.size(), commitment.data(), proof.data(),
1241 &proof_len);
1242 if (!require_ok(ok)) {
1244 "prove_norm_arg_to_commitment:backend");
1245 }
1246 proof.resize(proof_len);
1247
1248 std::vector<PointBytes> proof_generators;
1249 if (!generated_generators.empty()) {
1250 proof_generators = std::move(generated_generators);
1251 } else {
1252 proof_generators = inputs.generators;
1253 }
1254 return NormArgProof{inputs.rho, std::move(proof_generators),
1255 inputs.c_vec, inputs.n_vec.size(),
1256 commitment, std::move(proof)};
1257}
1258
1260 purify_secp_context *secp_context,
1261 ExperimentalCircuitBackend *cache = nullptr) {
1262 if (secp_context == nullptr) {
1263 return false;
1264 }
1265 if (proof.n_vec_len == 0 || proof.c_vec.empty()) {
1266 return false;
1267 }
1268
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);
1272 const int ok =
1273 resolved.backend_resources != nullptr
1275 resolved.backend_resources, proof.rho.data(), c_vec.data(),
1276 proof.c_vec.size(), proof.n_vec_len, proof.commitment.data(),
1277 proof.proof.data(), proof.proof.size())
1279 secp_context, proof.rho.data(), resolved.serialized_bytes.data(),
1280 resolved.generator_count, c_vec.data(), proof.c_vec.size(),
1281 proof.n_vec_len, proof.commitment.data(), proof.proof.data(),
1282 proof.proof.size());
1283 return ok != 0;
1284}
1285
1287 purify_secp_context *secp_context) {
1288 return commit_norm_arg_with_cache(inputs, secp_context, nullptr);
1289}
1290
1293 const PointBytes &commitment,
1294 purify_secp_context *secp_context) {
1295 return prove_norm_arg_to_commitment_with_cache(inputs, commitment, secp_context, nullptr);
1296}
1297
1299 purify_secp_context *secp_context) {
1300 return verify_norm_arg_with_cache(proof, secp_context, nullptr);
1301}
1302
1304 const NativeBulletproofCircuit &circuit,
1305 const BulletproofAssignmentData &assignment,
1306 purify_secp_context *secp_context,
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);
1315}
1316
1319 const NativeBulletproofCircuit &circuit,
1320 const BulletproofAssignmentData &assignment,
1321 const PointBytes &witness_commitment,
1322 purify_secp_context *secp_context,
1323 std::span<const unsigned char> statement_binding,
1325 if (!circuit.has_valid_shape()) {
1326 return unexpected_error(
1328 "prove_experimental_circuit_norm_arg_to_commitment:circuit_shape");
1329 }
1330 if (!is_power_of_two_size(circuit.n_gates)) {
1332 "prove_experimental_circuit_norm_arg_to_commitment:"
1333 "n_gates_power_of_two");
1334 }
1335 if (assignment.left.size() != circuit.n_gates ||
1336 assignment.right.size() != circuit.n_gates ||
1337 assignment.output.size() != circuit.n_gates ||
1338 assignment.commitments.size() != circuit.n_commitments) {
1339 return unexpected_error(
1341 "prove_experimental_circuit_norm_arg_to_commitment:assignment_shape");
1342 }
1343 if (!circuit.evaluate(assignment)) {
1344 return unexpected_error(
1346 "prove_experimental_circuit_norm_arg_to_commitment:assignment_invalid");
1347 }
1348
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");
1354
1355 NormArgInputs inputs = build_norm_arg_inputs(reduction);
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");
1364 }
1365
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");
1370
1372 auto proof,
1373 prove_norm_arg_to_commitment_with_cache(inputs, anchored_commitment, secp_context, cache),
1374 "prove_experimental_circuit_norm_arg_to_commitment:prove");
1375 return ExperimentalCircuitNormArgProof{witness_commitment,
1376 std::move(proof.proof)};
1377}
1378
1380 const NativeBulletproofCircuit &circuit,
1381 const BulletproofAssignmentData &assignment,
1382 purify_secp_context *secp_context,
1383 std::span<const unsigned char> statement_binding,
1386 const auto &witness_commitment,
1387 commit_experimental_circuit_witness(circuit, assignment, secp_context, statement_binding,
1388 cache),
1389 "prove_experimental_circuit_norm_arg:commit_witness");
1391 circuit, assignment, witness_commitment, secp_context, statement_binding, cache);
1392}
1393
1395 const NativeBulletproofCircuit &circuit,
1397 purify_secp_context *secp_context,
1398 std::span<const unsigned char> statement_binding,
1400 if (!circuit.has_valid_shape()) {
1401 return unexpected_error(
1403 "verify_experimental_circuit_norm_arg:circuit_shape");
1404 }
1405 if (!is_power_of_two_size(circuit.n_gates)) {
1406 return unexpected_error(
1408 "verify_experimental_circuit_norm_arg:n_gates_power_of_two");
1409 }
1410 if (proof.proof.empty()) {
1412 "verify_experimental_circuit_norm_arg:proof_empty");
1413 }
1414
1416 const auto &public_data,
1417 build_circuit_norm_arg_public_data(circuit, statement_binding, secp_context, false,
1418 cache),
1419 "verify_experimental_circuit_norm_arg:public_data");
1421 const auto &anchored_commitment,
1422 offset_commitment(proof.witness_commitment, public_data->target, secp_context),
1423 "verify_experimental_circuit_norm_arg:anchor");
1424
1425 NormArgProof bundle;
1426 bundle.rho = public_data->rho_bytes;
1427 bundle.generators = public_data->generators;
1428 bundle.c_vec = public_data->c_vec_bytes;
1429 if (!checked_mul_size(circuit.n_gates, 4, bundle.n_vec_len)) {
1430 return unexpected_error(
1432 "verify_experimental_circuit_norm_arg:n_vec_len");
1433 }
1434 bundle.commitment = anchored_commitment;
1435 bundle.proof = proof.proof;
1436 return verify_norm_arg_with_cache(bundle, secp_context, cache);
1437}
1438
1441 const NativeBulletproofCircuit &circuit,
1442 const BulletproofAssignmentData &assignment, const ScalarBytes &nonce,
1443 std::span<const PointBytes> public_commitments,
1444 purify_secp_context *secp_context,
1445 std::span<const unsigned char> statement_binding,
1446 bool externalize_commitments, ExperimentalCircuitBackend *cache) {
1447 if (!circuit.has_valid_shape()) {
1448 return unexpected_error(
1450 "prove_experimental_circuit_zk_norm_arg_impl:circuit_shape");
1451 }
1452 if (!is_power_of_two_size(circuit.n_gates)) {
1453 return unexpected_error(
1455 "prove_experimental_circuit_zk_norm_arg_impl:n_gates_power_of_two");
1456 }
1457 if (assignment.left.size() != circuit.n_gates ||
1458 assignment.right.size() != circuit.n_gates ||
1459 assignment.output.size() != circuit.n_gates ||
1460 assignment.commitments.size() != circuit.n_commitments) {
1461 return unexpected_error(
1463 "prove_experimental_circuit_zk_norm_arg_impl:assignment_shape");
1464 }
1465 if (!circuit.evaluate(assignment)) {
1466 return unexpected_error(
1468 "prove_experimental_circuit_zk_norm_arg_impl:assignment_invalid");
1469 }
1470
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");
1477 } else {
1478 bound_statement_binding =
1479 Bytes(statement_binding.begin(), statement_binding.end());
1480 }
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");
1486 }
1487
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");
1494
1496 const auto &binding_digest,
1497 experimental_circuit_binding_digest(circuit, bound_statement_binding),
1498 "prove_experimental_circuit_zk_norm_arg_impl:binding_digest");
1499 Bytes seed = binding_digest;
1500 seed.insert(seed.end(), nonce.begin(), nonce.end());
1501 std::size_t used_l = 0;
1502 if (!checked_add_size(circuit.n_gates,
1503 externalize_commitments ? 0 : circuit.n_commitments,
1504 used_l)) {
1505 return unexpected_error(
1507 "prove_experimental_circuit_zk_norm_arg_impl:used_l");
1508 }
1509 std::optional<Error> masked_failure;
1510
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) {
1515 const auto &blind,
1516 derive_scalar(seed, kCircuitZkBlindTaggedHash, i - used_l, attempt),
1517 "prove_experimental_circuit_zk_norm_arg_impl:blind");
1518 hidden.l_vec[i] = blind;
1519 }
1520
1521 std::vector<FieldElement> mask_n(hidden.n_vec.size(), FieldElement::zero());
1522 for (std::size_t i = 0; i < mask_n.size(); ++i) {
1524 const auto &value,
1525 derive_scalar(seed, kCircuitZkMaskNTaggedHash, i, attempt),
1526 "prove_experimental_circuit_zk_norm_arg_impl:mask_n");
1527 mask_n[i] = value;
1528 }
1529
1530 std::vector<FieldElement> mask_l(hidden.l_vec.size(), FieldElement::zero());
1531 for (std::size_t i = 0; i < mask_l.size(); ++i) {
1533 const auto &value,
1534 derive_scalar(seed, kCircuitZkMaskLTaggedHash, i, attempt),
1535 "prove_experimental_circuit_zk_norm_arg_impl:mask_l");
1536 mask_l[i] = value;
1537 }
1538
1539 const FieldElement t2 =
1540 weighted_bppp_inner_product(mask_n, mask_n, hidden.public_data->rho);
1541 if (t2.is_zero()) {
1542 continue;
1543 }
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]);
1549 }
1550
1551 NormArgInputs hidden_inputs = build_norm_arg_inputs(hidden);
1552 NormArgInputs mask_inputs;
1553 mask_inputs.rho = hidden.public_data->rho_bytes;
1554 mask_inputs.generators = hidden.public_data->generators;
1555 mask_inputs.n_vec = scalar_bytes(mask_n);
1556 mask_inputs.l_vec = scalar_bytes(mask_l);
1557 mask_inputs.c_vec = hidden.public_data->c_vec_bytes;
1558
1559 Result<PointBytes> a_witness_commitment =
1560 commit_norm_arg_witness_only(hidden_inputs, secp_context, cache);
1561 if (!a_witness_commitment.has_value()) {
1562 if (!masked_failure.has_value()) {
1563 masked_failure = unexpected_error(a_witness_commitment.error(),
1564 "prove_experimental_circuit_zk_norm_"
1565 "arg_impl:a_witness_commitment")
1566 .error();
1567 }
1568 continue;
1569 }
1570 Result<PointBytes> a_commitment = anchor_zk_a_commitment(
1571 *a_witness_commitment, *hidden.public_data, public_commitments, secp_context);
1572 if (!a_commitment.has_value()) {
1573 if (!masked_failure.has_value()) {
1574 masked_failure =
1576 a_commitment.error(),
1577 "prove_experimental_circuit_zk_norm_arg_impl:a_commitment")
1578 .error();
1579 }
1580 continue;
1581 }
1582 Result<PointBytes> s_commitment =
1583 commit_explicit_norm_arg(mask_inputs, t1, secp_context, cache);
1584 if (!s_commitment.has_value()) {
1585 if (!masked_failure.has_value()) {
1586 masked_failure =
1588 s_commitment.error(),
1589 "prove_experimental_circuit_zk_norm_arg_impl:s_commitment")
1590 .error();
1591 }
1592 continue;
1593 }
1595 const auto &challenge,
1596 derive_zk_challenge(binding_digest, *a_commitment, *s_commitment, t2),
1597 "prove_experimental_circuit_zk_norm_arg_impl:challenge");
1598
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]);
1602 }
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]);
1605 }
1606 NormArgInputs masked_inputs = build_norm_arg_inputs(masked);
1607
1608 Result<PointBytes> combined_commitment =
1609 combine_zk_commitments(*a_commitment, *s_commitment, challenge, t2, secp_context);
1610 if (!combined_commitment.has_value()) {
1611 if (!masked_failure.has_value()) {
1612 masked_failure = unexpected_error(combined_commitment.error(),
1613 "prove_experimental_circuit_zk_norm_"
1614 "arg_impl:combined_commitment")
1615 .error();
1616 }
1617 continue;
1618 }
1619 Result<PointBytes> direct_commitment =
1620 commit_norm_arg_with_cache(masked_inputs, secp_context, cache);
1621 if (!direct_commitment.has_value()) {
1622 if (!masked_failure.has_value()) {
1623 masked_failure =
1625 direct_commitment.error(),
1626 "prove_experimental_circuit_zk_norm_arg_impl:direct_commitment")
1627 .error();
1628 }
1629 continue;
1630 }
1631 if (*combined_commitment != *direct_commitment) {
1632 return unexpected_error(
1634 "prove_experimental_circuit_zk_norm_arg_impl:commitment_mismatch");
1635 }
1636
1638 masked_inputs, *combined_commitment, secp_context, cache);
1639 if (!proof.has_value()) {
1640 if (!masked_failure.has_value()) {
1641 masked_failure =
1642 unexpected_error(proof.error(),
1643 "prove_experimental_circuit_zk_norm_arg_impl:"
1644 "prove_norm_arg_to_commitment")
1645 .error();
1646 }
1647 continue;
1648 }
1649 return ExperimentalCircuitZkNormArgProof{*a_witness_commitment,
1650 *s_commitment, scalar_bytes(t2),
1651 std::move(proof->proof)};
1652 }
1653
1654 if (masked_failure.has_value()) {
1655 return unexpected_error(
1656 *masked_failure,
1657 "prove_experimental_circuit_zk_norm_arg_impl:masking_attempts");
1658 }
1659 return unexpected_error(
1661 "prove_experimental_circuit_zk_norm_arg_impl:masking_attempts");
1662}
1663
1665 const NativeBulletproofCircuit &circuit,
1667 std::span<const PointBytes> public_commitments,
1668 purify_secp_context *secp_context,
1669 std::span<const unsigned char> statement_binding,
1670 bool externalize_commitments, ExperimentalCircuitBackend *cache) {
1671 if (!circuit.has_valid_shape()) {
1672 return unexpected_error(
1674 "verify_experimental_circuit_zk_norm_arg_impl:circuit_shape");
1675 }
1676 if (!is_power_of_two_size(circuit.n_gates)) {
1677 return unexpected_error(
1679 "verify_experimental_circuit_zk_norm_arg_impl:n_gates_power_of_two");
1680 }
1681 if (proof.proof.empty()) {
1682 return unexpected_error(
1684 "verify_experimental_circuit_zk_norm_arg_impl:proof_empty");
1685 }
1686
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");
1693 } else {
1694 bound_statement_binding =
1695 Bytes(statement_binding.begin(), statement_binding.end());
1696 }
1698 const auto &public_data,
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");
1703 const auto &t2, FieldElement::try_from_bytes32(proof.t2),
1704 "verify_experimental_circuit_zk_norm_arg_impl:t2");
1706 const auto &a_commitment,
1707 anchor_zk_a_commitment(proof.a_commitment, *public_data, public_commitments, secp_context),
1708 "verify_experimental_circuit_zk_norm_arg_impl:a_commitment");
1710 const auto &binding_digest,
1711 experimental_circuit_binding_digest(circuit, bound_statement_binding),
1712 "verify_experimental_circuit_zk_norm_arg_impl:binding_digest");
1714 const auto &challenge,
1715 derive_zk_challenge(binding_digest, a_commitment, proof.s_commitment, t2),
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");
1721
1722 NormArgProof bundle;
1723 bundle.rho = public_data->rho_bytes;
1724 bundle.generators = public_data->generators;
1725 bundle.c_vec = public_data->c_vec_bytes;
1726 if (!checked_mul_size(circuit.n_gates, 4, bundle.n_vec_len)) {
1727 return unexpected_error(
1729 "verify_experimental_circuit_zk_norm_arg_impl:n_vec_len");
1730 }
1731 bundle.commitment = commitment;
1732 bundle.proof = proof.proof;
1733 return verify_norm_arg_with_cache(bundle, secp_context, cache);
1734}
1735
1738 const NativeBulletproofCircuit &circuit,
1739 const BulletproofAssignmentData &assignment, const ScalarBytes &nonce,
1740 purify_secp_context *secp_context,
1741 std::span<const unsigned char> statement_binding,
1744 circuit, assignment, nonce, {}, secp_context, statement_binding, false, cache);
1745}
1746
1748 const NativeBulletproofCircuit &circuit,
1750 purify_secp_context *secp_context,
1751 std::span<const unsigned char> statement_binding,
1754 circuit, proof, {}, secp_context, statement_binding, false, cache);
1755}
1756
1759 const NativeBulletproofCircuit &circuit,
1760 const BulletproofAssignmentData &assignment, const ScalarBytes &nonce,
1761 std::span<const PointBytes> public_commitments,
1762 purify_secp_context *secp_context,
1763 std::span<const unsigned char> statement_binding,
1766 circuit, assignment, nonce, public_commitments, secp_context, statement_binding, true,
1767 cache);
1768}
1769
1771 const NativeBulletproofCircuit &circuit,
1773 std::span<const PointBytes> public_commitments,
1774 purify_secp_context *secp_context,
1775 std::span<const unsigned char> statement_binding,
1778 circuit, proof, public_commitments, secp_context, statement_binding, true, cache);
1779}
1780
1782commit_output_witness(const Bytes &message, const SecretKey &secret,
1783 const ScalarBytes &blind, purify_secp_context *secp_context) {
1784 return commit_output_witness(message, secret, blind, secp_context,
1785 value_generator_h(secp_context),
1786 base_generator(secp_context));
1787}
1788
1790commit_output_witness(const Bytes &message, const SecretKey &secret,
1791 const ScalarBytes &blind, purify_secp_context *secp_context,
1792 const GeneratorBytes &value_gen,
1793 const GeneratorBytes &blind_gen) {
1794 PURIFY_ASSIGN_OR_RETURN(auto witness, prove_assignment_data(message, secret),
1795 "commit_output_witness:prove_assignment_data");
1797 const auto &commitment,
1798 pedersen_commit_char(blind, scalar_bytes(witness.output), secp_context, value_gen,
1799 blind_gen),
1800 "commit_output_witness:pedersen_commit_char");
1801 return CommittedPurifyWitness{witness.public_key, witness.output,
1802 std::move(witness.assignment), commitment};
1803}
1804
1805} // namespace purify::bppp
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.
Definition expected.hpp:64
bool has_value() const noexcept
Definition expected.hpp:170
Field element modulo the backend scalar field used by this implementation.
Definition numeric.hpp:815
std::optional< FieldElement > sqrt() const
Computes a square root when one exists, otherwise returns std::nullopt.
Definition numeric.cpp:143
static FieldElement one()
Returns the multiplicative identity of the scalar field.
Definition numeric.cpp:36
static Result< FieldElement > try_from_bytes32(const std::array< unsigned char, 32 > &bytes)
Decodes a canonical 32-byte big-endian field element.
Definition numeric.cpp:53
FieldElement negate() const
Returns the additive inverse modulo the field prime.
Definition numeric.cpp:121
static FieldElement from_int(std::int64_t value)
Constructs a field element from a signed integer, reducing negatives modulo the field.
Definition numeric.cpp:46
bool is_zero() const
Returns true when the element is zero.
Definition numeric.cpp:104
static FieldElement zero()
Returns the additive identity of the scalar field.
Definition numeric.cpp:32
Move-only packed Purify secret stored in dedicated heap memory.
Definition secret.hpp:52
Common interface for reusable experimental BPPP backend state.
Definition bppp.hpp:119
Thread-local clone of one warmed experimental BPPP backend-resource line.
Definition bppp.hpp:159
ExperimentalCircuitCacheLine & operator=(const ExperimentalCircuitCacheLine &)=delete
Caller-owned cache for reusable experimental circuit reduction and BPPP backend data.
Definition bppp.hpp:186
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.
Definition bppp.cpp:182
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.
Definition bppp.cpp:208
void insert_public_data(std::array< unsigned char, 32 > key, std::shared_ptr< const void > value)
Stores opaque cached reduction data by digest key.
Definition bppp.cpp:213
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.
Definition bppp.cpp:220
std::size_t size() const noexcept
Definition bppp.cpp:176
#define PURIFY_RETURN_IF_ERROR(expr, context)
Evaluates an expected-like expression and returns the wrapped error on failure.
Definition error.hpp:329
#define PURIFY_ASSIGN_OR_RETURN(lhs, expr, context)
Evaluates an expected-like expression, binds the value to lhs, and propagates errors.
Definition error.hpp:338
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)
Definition bppp.cpp:1440
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.
Definition bppp.cpp:1292
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.
Definition bppp.cpp:1318
std::array< unsigned char, 32 > ScalarBytes
Big-endian 32-byte scalar encoding.
Definition bppp.hpp:30
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.
Definition bppp.cpp:1303
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.
Definition bppp.cpp:1286
bool verify_norm_arg_with_cache(const NormArgProof &proof, purify_secp_context *secp_context, ExperimentalCircuitBackend *cache=nullptr)
Definition bppp.cpp:1259
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)
Definition bppp.cpp:1664
std::size_t digest_prefix_hash(const Digest &digest) noexcept
Definition bppp.cpp:43
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.
Definition bppp.cpp:1394
GeneratorBytes base_generator(purify_secp_context *secp_context)
Returns the serialized secp256k1 base generator used as the blind generator.
Definition bppp.cpp:1064
std::unique_ptr< purify_bppp_backend_resources, BpppBackendResourcesDeleter > OwnedBpppBackendResources
Definition bppp.cpp:38
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.
Definition bppp.cpp:1770
std::array< unsigned char, 33 > PointBytes
Compressed 33-byte curve-point encoding.
Definition bppp.hpp:32
Result< NormArgProof > prove_norm_arg_to_commitment_with_cache(const NormArgInputs &inputs, const PointBytes &commitment, purify_secp_context *secp_context, ExperimentalCircuitBackend *cache=nullptr)
Definition bppp.cpp:1190
bool verify_norm_arg(const NormArgProof &proof, purify_secp_context *secp_context)
Verifies a standalone BPPP norm argument.
Definition bppp.cpp:1298
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.
Definition bppp.cpp:1758
Result< PointBytes > commit_norm_arg_with_cache(const NormArgInputs &inputs, purify_secp_context *secp_context, ExperimentalCircuitBackend *cache=nullptr)
Definition bppp.cpp:1107
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.
Definition bppp.cpp:1379
GeneratorBytes value_generator_h(purify_secp_context *secp_context)
Returns the serialized alternate generator used for committed values.
Definition bppp.cpp:1073
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.
Definition bppp.cpp:1782
Result< NormArgProof > prove_norm_arg(const NormArgInputs &inputs, purify_secp_context *secp_context)
Produces a standalone BPPP norm argument.
Definition bppp.cpp:1180
GeneratorBackendCacheKey generator_backend_cache_key(std::span< const PointBytes > generators)
Definition bppp.cpp:60
Result< std::vector< PointBytes > > create_generators(std::size_t count, purify_secp_context *secp_context)
Expands the BPPP generator list.
Definition bppp.cpp:1083
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.
Definition bppp.cpp:1747
std::array< unsigned char, 32 > CircuitNormArgPublicDataCacheKey
Definition bppp.cpp:57
std::array< unsigned char, 33 > GeneratorBytes
Serialized generator encoding used by the BPPP bridge.
Definition bppp.hpp:34
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...
Definition bppp.cpp:1155
ScalarBytes scalar_bytes(const FieldElement &value)
Serializes a Purify field element into the scalar encoding expected by the BPPP bridge.
Definition bppp.hpp:78
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.
Definition bppp.cpp:1737
std::array< unsigned char, 32 > GeneratorBackendCacheKey
Definition bppp.cpp:40
Status require_secp_context(const purify_secp_context *context, const char *error_context)
Definition common.hpp:56
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.
Definition error.hpp:293
Result< BulletproofWitnessData > prove_assignment_data(const Bytes &message, const SecretKey &secret)
Computes the native Purify witness for a message and secret.
Definition api.cpp:236
bool is_power_of_two_size(std::size_t value) noexcept
Definition common.hpp:94
Result< std::uint64_t > narrow_size_to_u64(std::size_t value, const char *context)
Definition common.hpp:87
bool checked_mul_size(std::size_t lhs, std::size_t rhs, std::size_t &out) noexcept
Definition common.hpp:71
Bytes bytes_from_ascii(std::string_view input)
Encodes an ASCII string as a byte vector.
Definition curve.cpp:163
std::vector< unsigned char > Bytes
Dynamically sized byte string used for messages, serialized witnesses, and proofs.
Definition common.hpp:99
bool checked_add_size(std::size_t lhs, std::size_t rhs, std::size_t &out) noexcept
Definition common.hpp:63
Expected< void, Error > Status
Expected-returning convenience alias for Purify status-only APIs.
Definition error.hpp:102
std::size_t generator_count
Definition bppp.cpp:344
std::vector< FieldElement > c_vec
Definition bppp.cpp:472
std::vector< std::array< FieldElement, 2 > > plus_terms
Definition bppp.cpp:475
std::vector< FieldElement > public_commitment_coeffs
Definition bppp.cpp:474
std::span< const unsigned char > serialized_bytes
Definition bppp.cpp:343
purify_bppp_backend_resources * backend_resources
Definition bppp.cpp:345
std::vector< ScalarBytes > c_vec_bytes
Definition bppp.cpp:473
std::vector< FieldElement > plus_shift
Definition bppp.cpp:477
CircuitNormArgPublicDataPtr public_data
Definition bppp.cpp:485
FieldElement rho
Definition bppp.cpp:468
FieldElement target
Definition bppp.cpp:470
std::vector< std::array< FieldElement, 2 > > minus_terms
Definition bppp.cpp:476
std::vector< FieldElement > l_vec
Definition bppp.cpp:487
std::vector< FieldElement > minus_shift
Definition bppp.cpp:478
std::vector< PointBytes > generators
Definition bppp.cpp:471
ScalarBytes rho_bytes
Definition bppp.cpp:469
std::vector< FieldElement > n_vec
Definition bppp.cpp:486
Scalar32 scalar
Definition bppp.cpp:119
Nonce nonce
Definition bppp.cpp:120
XOnly32 binding_digest
Definition bppp.cpp:122
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.
void operator()(purify_bppp_backend_resources *resources) const noexcept
Definition bppp.cpp:32
Purify witness bundle together with a Pedersen commitment to the output.
Definition bppp.hpp:390
OwnedBpppBackendResources backend_resources
Definition bppp.cpp:104
std::unordered_map< GeneratorBackendCacheKey, OwnedBpppBackendResources, GeneratorBackendCacheKeyHash > backend_resources
Definition bppp.cpp:114
std::unordered_map< CircuitNormArgPublicDataCacheKey, std::shared_ptr< const void >, CircuitNormArgPublicDataCacheKeyHash > public_data
Definition bppp.cpp:111
Experimental transparent circuit proof backed by the standalone BPPP norm argument.
Definition bppp.hpp:251
Experimental masked circuit proof that hides the reduced witness before the final BPPP argument.
Definition bppp.hpp:257
PointBytes a_commitment
Witness-only outer A commitment; verifiers re-anchor it to the public statement.
Definition bppp.hpp:259
std::size_t operator()(const GeneratorBackendCacheKey &key) const noexcept
Definition bppp.cpp:52
Inputs required to produce a standalone BPPP norm argument.
Definition bppp.hpp:97
std::vector< ScalarBytes > l_vec
Definition bppp.hpp:101
std::vector< PointBytes > generators
Definition bppp.hpp:99
std::vector< ScalarBytes > n_vec
Definition bppp.hpp:100
std::vector< ScalarBytes > c_vec
Definition bppp.hpp:102
Standalone BPPP norm-argument proof bundle with all verifier-side inputs.
Definition bppp.hpp:109
std::vector< PointBytes > generators
Definition bppp.hpp:111
std::vector< ScalarBytes > c_vec
Definition bppp.hpp:112
int PURIFY_UINT_FN() is_zero(const uint64_t value[PURIFY_UINT_WORDS])
Definition uint_impl.h:38