purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
bench_purify.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 <nanobench.h>
11
12#include <array>
13#include <charconv>
14#include <chrono>
15#include <cstddef>
16#include <cstdint>
17#include <iostream>
18#include <optional>
19#include <string_view>
20
21#include "purify.hpp"
22#include "purify/bppp.hpp"
23#include "../src/bridge/bppp_bridge.h"
24
25namespace {
26
27using namespace std::chrono_literals;
28
30using purify::Bytes;
36
37constexpr std::string_view kSecretHex =
38 "11427c7268288dddf0cd24af3d30524fd817a91e103e7e02eb28b78db81cb350"
39 "b3d2562f45fa8ecd711d1becc02fa348cf2187429228e7aac6644a3da2824e93";
40
41#ifndef PURIFY_BENCH_BUILD_CONFIG
42#define PURIFY_BENCH_BUILD_CONFIG "unspecified"
43#endif
44
45#ifndef PURIFY_BENCH_IS_RELEASE
46#define PURIFY_BENCH_IS_RELEASE 0
47#endif
48
50struct BenchConfig {
51 std::size_t epochs = 5;
52 std::chrono::milliseconds min_epoch_time = 10ms;
53};
54
56struct PurifyBenchCase {
57 purify::SecpContextPtr secp_context;
58 Bytes message;
59 BulletproofWitnessData witness;
60 NativeBulletproofCircuitTemplate circuit_template;
61 NativeBulletproofCircuit circuit;
62 ExperimentalBulletproofProof experimental_proof;
63 purify::ExperimentalBulletproofBackendCache experimental_bulletproof_cache;
65 purify::bppp::ExperimentalCircuitCache experimental_bppp_cache;
66 purify::puresign::MessageProofCache message_proof_cache;
67 purify::puresign_plusplus::MessageProofCache message_proof_cache_plusplus;
68 purify::bppp::ExperimentalCircuitCache puresign_plusplus_cache;
69 std::optional<purify::puresign::KeyPair> key_pair;
71 std::optional<purify::puresign_plusplus::KeyPair> key_pair_plusplus;
72 purify::puresign_plusplus::PublicKey public_key_plusplus;
74 purify::puresign::ProvenSignature proven_signature;
75 purify::puresign_plusplus::ProvenSignature proven_signature_plusplus;
76 purify::bppp::NormArgInputs norm_arg_inputs;
77 purify::bppp::NormArgProof norm_arg_proof;
78};
79
81std::size_t estimate_bytes(const NativeBulletproofCircuitRow& row) {
82 return row.entries.capacity() * sizeof(purify::NativeBulletproofCircuitTerm);
83}
84
86std::size_t estimate_bytes(const std::vector<NativeBulletproofCircuitRow>& rows) {
87 std::size_t total = rows.capacity() * sizeof(NativeBulletproofCircuitRow);
88 for (const NativeBulletproofCircuitRow& row : rows) {
89 total += estimate_bytes(row);
90 }
91 return total;
92}
93
95std::size_t estimate_bytes(const std::vector<FieldElement>& scalars) {
96 return scalars.capacity() * sizeof(FieldElement);
97}
98
100std::size_t estimate_bytes(const NativeBulletproofCircuit& circuit) {
101 return sizeof(circuit)
102 + estimate_bytes(circuit.wl)
103 + estimate_bytes(circuit.wr)
104 + estimate_bytes(circuit.wo)
105 + estimate_bytes(circuit.wv)
106 + estimate_bytes(circuit.c);
107}
108
115std::optional<BenchConfig> parse_args(int argc, char** argv, int& exit_code) {
116 exit_code = -1;
117 BenchConfig config;
118 auto parse_u64 = [](std::string_view text, std::uint64_t& out) {
119 auto result = std::from_chars(text.data(), text.data() + text.size(), out);
120 return result.ec == std::errc() && result.ptr == text.data() + text.size();
121 };
122 auto parse_i64 = [](std::string_view text, std::int64_t& out) {
123 auto result = std::from_chars(text.data(), text.data() + text.size(), out);
124 return result.ec == std::errc() && result.ptr == text.data() + text.size();
125 };
126 for (int i = 1; i < argc; ++i) {
127 std::string_view arg(argv[i]);
128 auto consume_value = [&](const char* name, std::string_view& value) {
129 if (i + 1 >= argc) {
130 std::cerr << "Missing value for " << name << "\n";
131 exit_code = 1;
132 return false;
133 }
134 value = argv[++i];
135 return true;
136 };
137 if (arg == "--epochs") {
138 std::string_view value;
139 if (!consume_value("--epochs", value)) {
140 return std::nullopt;
141 }
142 std::uint64_t parsed = 0;
143 if (!parse_u64(value, parsed)) {
144 std::cerr << "Invalid value for --epochs\n";
145 exit_code = 1;
146 return std::nullopt;
147 }
148 config.epochs = static_cast<std::size_t>(parsed);
149 } else if (arg == "--min-epoch-ms") {
150 std::string_view value;
151 if (!consume_value("--min-epoch-ms", value)) {
152 return std::nullopt;
153 }
154 std::int64_t parsed = 0;
155 if (!parse_i64(value, parsed)) {
156 std::cerr << "Invalid value for --min-epoch-ms\n";
157 exit_code = 1;
158 return std::nullopt;
159 }
160 config.min_epoch_time = std::chrono::milliseconds(parsed);
161 } else if (arg == "--help") {
162 std::cout << "Usage: " << argv[0] << " [--epochs N] [--min-epoch-ms MS]\n";
163 exit_code = 0;
164 return std::nullopt;
165 } else {
166 std::cerr << "Unknown argument: " << arg << "\n";
167 exit_code = 1;
168 return std::nullopt;
169 }
170 }
171 return config;
172}
173
175std::optional<PurifyBenchCase> make_case() {
176 PurifyBenchCase out;
177 out.secp_context = purify::make_secp_context();
178 if (out.secp_context == nullptr) {
179 std::cerr << "failed to create secp context\n";
180 return std::nullopt;
181 }
182 out.message = Bytes{0x01, 0x23, 0x45, 0x67};
184 if (!secret.has_value()) {
185 std::cerr << secret.error().message() << "\n";
186 return std::nullopt;
187 }
189 if (!witness.has_value()) {
190 std::cerr << witness.error().message() << "\n";
191 return std::nullopt;
192 }
193 out.witness = std::move(*witness);
194
196 if (!circuit_template.has_value()) {
197 std::cerr << circuit_template.error().message() << "\n";
198 return std::nullopt;
199 }
200 out.circuit_template = std::move(*circuit_template);
201
202 purify::Result<NativeBulletproofCircuit> circuit = purify::verifier_circuit(out.message, out.witness.public_key);
203 if (!circuit.has_value()) {
204 std::cerr << circuit.error().message() << "\n";
205 return std::nullopt;
206 }
207 out.circuit = std::move(*circuit);
208
209 std::array<unsigned char, 32> proof_nonce{};
210 for (std::size_t i = 0; i < proof_nonce.size(); ++i) {
211 proof_nonce[i] = static_cast<unsigned char>(i + 17);
212 }
214 purify::prove_experimental_circuit(out.circuit, out.witness.assignment, proof_nonce,
215 purify::bppp::base_generator(out.secp_context.get()),
216 out.secp_context.get(),
217 purify::bytes_from_ascii("bench-experimental-proof"),
218 std::nullopt, &out.experimental_bulletproof_cache);
219 if (!experimental_proof.has_value()) {
220 std::cerr << experimental_proof.error().message() << "\n";
221 return std::nullopt;
222 }
223 out.experimental_proof = std::move(*experimental_proof);
224
227 out.circuit, out.witness.assignment, proof_nonce,
228 out.secp_context.get(),
229 purify::bytes_from_ascii("bench-experimental-bppp-proof"), &out.experimental_bppp_cache);
230 if (!experimental_bppp_proof.has_value()) {
231 std::cerr << experimental_bppp_proof.error().message() << "\n";
232 return std::nullopt;
233 }
234 out.experimental_bppp_proof = std::move(*experimental_bppp_proof);
235
238 if (!message_proof_cache.has_value()) {
239 std::cerr << message_proof_cache.error().message() << "\n";
240 return std::nullopt;
241 }
242 out.message_proof_cache = std::move(*message_proof_cache);
243
246 if (!message_proof_cache_plusplus.has_value()) {
247 std::cerr << message_proof_cache_plusplus.error().message() << "\n";
248 return std::nullopt;
249 }
250 out.message_proof_cache_plusplus = std::move(*message_proof_cache_plusplus);
251
253 purify::puresign::KeyPair::from_secret(*secret, out.secp_context.get());
254 if (!key_pair.has_value()) {
255 std::cerr << key_pair.error().message() << "\n";
256 return std::nullopt;
257 }
258 out.key_pair = std::move(*key_pair);
259 out.public_key = out.key_pair->public_key();
260
262 out.key_pair->sign_message(out.message, out.secp_context.get());
263 if (!signature.has_value()) {
264 std::cerr << signature.error().message() << "\n";
265 return std::nullopt;
266 }
267 out.signature = std::move(*signature);
268
270 out.key_pair->sign_message_with_proof(out.message, out.secp_context.get());
271 if (!proven_signature.has_value()) {
272 std::cerr << proven_signature.error().message() << "\n";
273 return std::nullopt;
274 }
275 out.proven_signature = std::move(*proven_signature);
276
278 purify::puresign_plusplus::KeyPair::from_secret(*secret, out.secp_context.get());
279 if (!key_pair_plusplus.has_value()) {
280 std::cerr << key_pair_plusplus.error().message() << "\n";
281 return std::nullopt;
282 }
283 out.key_pair_plusplus = std::move(*key_pair_plusplus);
284 out.public_key_plusplus = out.key_pair_plusplus->public_key();
285
287 out.key_pair_plusplus->sign_message_with_proof(out.message, out.secp_context.get(),
288 &out.puresign_plusplus_cache);
289 if (!warmed_signature_plusplus.has_value()) {
290 std::cerr << warmed_signature_plusplus.error().message() << "\n";
291 return std::nullopt;
292 }
293
295 out.key_pair_plusplus->sign_message_with_proof(out.message_proof_cache_plusplus, out.secp_context.get());
296 if (!proven_signature_plusplus.has_value()) {
297 std::cerr << proven_signature_plusplus.error().message() << "\n";
298 return std::nullopt;
299 }
300 out.proven_signature_plusplus = std::move(*proven_signature_plusplus);
301
302 out.norm_arg_inputs.rho[31] = 1;
304 out.witness.assignment.left.size() + out.witness.assignment.right.size(), out.secp_context.get());
305 if (!generators.has_value()) {
306 std::cerr << generators.error().message() << "\n";
307 return std::nullopt;
308 }
309 out.norm_arg_inputs.generators = std::move(*generators);
310 out.norm_arg_inputs.n_vec = purify::bppp::scalar_bytes(out.witness.assignment.left);
311 out.norm_arg_inputs.l_vec = purify::bppp::scalar_bytes(out.witness.assignment.right);
312 out.norm_arg_inputs.c_vec = purify::bppp::scalar_bytes(out.witness.assignment.output);
314 purify::bppp::prove_norm_arg(out.norm_arg_inputs, out.secp_context.get());
315 if (!proof.has_value()) {
316 std::cerr << proof.error().message() << "\n";
317 return std::nullopt;
318 }
319 out.norm_arg_proof = std::move(*proof);
320 if (!purify::bppp::verify_norm_arg(out.norm_arg_proof, out.secp_context.get())) {
321 std::cerr << "Initial BPPP proof verification failed\n";
322 return std::nullopt;
323 }
324 return out;
325}
326
328ankerl::nanobench::Bench make_bench(const BenchConfig& config, std::string_view unit) {
329 ankerl::nanobench::Bench bench;
330 bench.title("purify")
331 .unit(std::string(unit))
332 .epochs(config.epochs)
333 .minEpochTime(config.min_epoch_time)
334 .warmup(1)
335 .performanceCounters(false);
336 return bench;
337}
338
340void warn_if_not_release() {
341#if !PURIFY_BENCH_IS_RELEASE
342 std::cerr << "warning: bench_purify should be run from a Release CMake configuration; current configuration is "
344 << ". This target forces release optimization flags, but non-Release build settings can still skew timings.\n";
345#endif
346}
347
348} // namespace
349
356int main(int argc, char** argv) {
357 int parse_exit_code = -1;
358 std::optional<BenchConfig> config = parse_args(argc, argv, parse_exit_code);
359 if (!config.has_value()) {
360 return parse_exit_code < 0 ? 1 : parse_exit_code;
361 }
362
363 warn_if_not_release();
364
365 std::optional<PurifyBenchCase> bench_case = make_case();
366 if (!bench_case.has_value()) {
367 return 1;
368 }
369 std::array<unsigned char, 32> experimental_nonce{};
370 for (std::size_t i = 0; i < experimental_nonce.size(); ++i) {
371 experimental_nonce[i] = static_cast<unsigned char>(i + 17);
372 }
373 Bytes experimental_binding = purify::bytes_from_ascii("bench-experimental-proof");
374 Bytes experimental_bppp_binding = purify::bytes_from_ascii("bench-experimental-bppp-proof");
375 std::size_t circuit_bytes = estimate_bytes(bench_case->circuit);
376 purify::Result<Bytes> proven_signature_bytes =
377 bench_case->proven_signature.serialize(bench_case->secp_context.get());
378 if (!proven_signature_bytes.has_value()) {
379 std::cerr << proven_signature_bytes.error().message() << "\n";
380 return 1;
381 }
382 purify::Result<Bytes> proven_signature_plusplus_bytes =
383 bench_case->proven_signature_plusplus.serialize(bench_case->secp_context.get());
384 if (!proven_signature_plusplus_bytes.has_value()) {
385 std::cerr << proven_signature_plusplus_bytes.error().message() << "\n";
386 return 1;
387 }
388
389 std::cout << "purify benchmark setup\n";
390 std::cout << "proof_system=legacy_bp_and_bppp_with_puresign_legacy_and_plusplus\n";
391 std::cout << "message_bytes=" << bench_case->message.size() << "\n";
392 std::cout << "gates=" << bench_case->circuit.n_gates << "\n";
393 std::cout << "constraints=" << bench_case->circuit.c.size() << "\n";
394 std::cout << "commitments=" << bench_case->circuit.n_commitments << "\n";
395 std::cout << "circuit_size_bytes=" << circuit_bytes << "\n";
396 std::cout << "cache_eval_input_bytes=" << bench_case->message_proof_cache.eval_input.size() << "\n";
397 std::cout << "experimental_proof_size_bytes=" << bench_case->experimental_proof.proof.size() << "\n";
398 std::cout << "experimental_bppp_proof_size_bytes=" << bench_case->experimental_bppp_proof.proof.size() << "\n";
399 std::cout << "norm_arg_n_vec_len=" << bench_case->norm_arg_inputs.n_vec.size() << "\n";
400 std::cout << "norm_arg_l_vec_len=" << bench_case->norm_arg_inputs.l_vec.size() << "\n";
401 std::cout << "norm_arg_c_vec_len=" << bench_case->norm_arg_inputs.c_vec.size() << "\n";
402 std::cout << "norm_arg_proof_size_bytes=" << bench_case->norm_arg_proof.proof.size() << "\n";
403 std::cout << "puresign_signature_size_bytes=" << bench_case->signature.bytes.size() << "\n";
404 std::cout << "puresign_legacy_proven_signature_size_bytes=" << proven_signature_bytes->size() << "\n";
405 std::cout << "puresign_plusplus_proven_signature_size_bytes=" << proven_signature_plusplus_bytes->size() << "\n";
406
407 auto build_bench = make_bench(*config, "circuit");
408 build_bench.run("verifier_circuit.native.build", [&] {
410 purify::verifier_circuit(bench_case->message, bench_case->witness.public_key);
411 assert(built.has_value() && "benchmark verifier circuit build should succeed");
412 ankerl::nanobench::doNotOptimizeAway(built->c.size());
413 });
414
415 auto instantiate_bench = make_bench(*config, "circuit");
416 instantiate_bench.run("verifier_circuit.template.instantiate_native", [&] {
418 bench_case->circuit_template.instantiate(bench_case->witness.public_key);
419 assert(built.has_value() && "benchmark verifier circuit template instantiation should succeed");
420 ankerl::nanobench::doNotOptimizeAway(built->c.size());
421 });
422
423 auto instantiate_packed_bench = make_bench(*config, "circuit");
424 instantiate_packed_bench.run("verifier_circuit.template.instantiate_packed", [&] {
426 bench_case->circuit_template.instantiate_packed(bench_case->witness.public_key);
427 assert(built.has_value() && "benchmark packed verifier circuit template instantiation should succeed");
428 ankerl::nanobench::doNotOptimizeAway(built->constraint_count());
429 });
430
431 auto template_build_bench = make_bench(*config, "template");
432 template_build_bench.run("verifier_circuit.template.build", [&] {
434 purify::verifier_circuit_template(bench_case->message);
435 assert(built.has_value() && "benchmark verifier circuit template build should succeed");
436 ankerl::nanobench::doNotOptimizeAway(&*built);
437 });
438
439 auto partial_eval_bench = make_bench(*config, "evaluation");
440 partial_eval_bench.run("verifier_circuit.template.evaluate_partial", [&] {
441 purify::Result<bool> ok = bench_case->circuit_template.partial_evaluate(bench_case->witness.assignment);
442 assert(ok.has_value() && *ok && "benchmark circuit template partial evaluation should succeed");
443 ankerl::nanobench::doNotOptimizeAway(*ok);
444 });
445
446 auto final_eval_bench = make_bench(*config, "evaluation");
447 final_eval_bench.run("verifier_circuit.template.evaluate_final", [&] {
449 bench_case->circuit_template.final_evaluate(bench_case->witness.assignment, bench_case->witness.public_key);
450 assert(ok.has_value() && *ok && "benchmark circuit template final evaluation should succeed");
451 ankerl::nanobench::doNotOptimizeAway(*ok);
452 });
453
454 auto proof_cache_build_bench = make_bench(*config, "cache");
455 proof_cache_build_bench.run("puresign_legacy.message_proof_cache.build", [&] {
458 assert(built.has_value() && "benchmark message proof cache build should succeed");
459 ankerl::nanobench::doNotOptimizeAway(built->eval_input.data());
460 });
461
462 auto experimental_prove_bench = make_bench(*config, "proof");
463 experimental_prove_bench.run("experimental_circuit.legacy_bp.prove", [&] {
465 purify::prove_experimental_circuit(bench_case->circuit, bench_case->witness.assignment,
466 experimental_nonce,
467 purify::bppp::base_generator(bench_case->secp_context.get()),
468 bench_case->secp_context.get(),
469 experimental_binding, std::nullopt,
470 &bench_case->experimental_bulletproof_cache);
471 assert(proof.has_value() && "benchmark experimental circuit proof should succeed");
472 ankerl::nanobench::doNotOptimizeAway(proof->proof.data());
473 ankerl::nanobench::doNotOptimizeAway(proof->proof.size());
474 });
475
476 auto experimental_verify_bench = make_bench(*config, "proof");
477 experimental_verify_bench.run("experimental_circuit.legacy_bp.verify", [&] {
479 purify::verify_experimental_circuit(bench_case->circuit, bench_case->experimental_proof,
480 purify::bppp::base_generator(bench_case->secp_context.get()),
481 bench_case->secp_context.get(), experimental_binding,
482 &bench_case->experimental_bulletproof_cache);
483 assert(ok.has_value() && *ok && "benchmark experimental circuit verification should succeed");
484 ankerl::nanobench::doNotOptimizeAway(*ok);
485 });
486
487 auto experimental_bppp_prove_bench = make_bench(*config, "proof");
488 experimental_bppp_prove_bench.run("experimental_circuit.bppp_zk_norm_arg.prove", [&] {
491 bench_case->circuit, bench_case->witness.assignment, experimental_nonce,
492 bench_case->secp_context.get(), experimental_bppp_binding, &bench_case->experimental_bppp_cache);
493 assert(proof.has_value() && "benchmark experimental zk BPPP circuit proof should succeed");
494 ankerl::nanobench::doNotOptimizeAway(proof->proof.data());
495 ankerl::nanobench::doNotOptimizeAway(proof->proof.size());
496 });
497
498 auto experimental_bppp_verify_bench = make_bench(*config, "proof");
499 experimental_bppp_verify_bench.run("experimental_circuit.bppp_zk_norm_arg.verify", [&] {
502 bench_case->circuit, bench_case->experimental_bppp_proof, bench_case->secp_context.get(),
503 experimental_bppp_binding,
504 &bench_case->experimental_bppp_cache);
505 assert(ok.has_value() && *ok && "benchmark experimental zk BPPP circuit verification should succeed");
506 ankerl::nanobench::doNotOptimizeAway(*ok);
507 });
508
509 auto prove_bench = make_bench(*config, "proof");
510 prove_bench.run("bppp.norm_arg.prove", [&] {
512 purify::bppp::prove_norm_arg(bench_case->norm_arg_inputs, bench_case->secp_context.get());
513 assert(proof.has_value() && "benchmark norm-arg prove should succeed");
514 ankerl::nanobench::doNotOptimizeAway(proof->proof.data());
515 ankerl::nanobench::doNotOptimizeAway(proof->proof.size());
516 });
517
518 auto verify_bench = make_bench(*config, "proof");
519 verify_bench.run("bppp.norm_arg.verify", [&] {
520 bool ok = purify::bppp::verify_norm_arg(bench_case->norm_arg_proof, bench_case->secp_context.get());
521 assert(ok && "benchmark verification should succeed");
522 ankerl::nanobench::doNotOptimizeAway(ok);
523 });
524
525 auto experimental_backend_bench = make_bench(*config, "resource_set");
526 experimental_backend_bench.run("experimental_circuit.legacy_bp_backend_resources.create", [&] {
528 purify_bulletproof_backend_resources_create(bench_case->secp_context.get(), bench_case->circuit.n_gates);
529 assert(resources != nullptr && "benchmark experimental backend resource creation should succeed");
530 ankerl::nanobench::doNotOptimizeAway(resources);
532 });
533
534 auto prepare_nonce_bench = make_bench(*config, "nonce");
535 prepare_nonce_bench.run("puresign_legacy.nonce.prepare", [&] {
537 bench_case->key_pair->prepare_message_nonce(bench_case->message, bench_case->secp_context.get());
538 assert(prepared.has_value() && "benchmark PureSign nonce preparation should succeed");
539 ankerl::nanobench::doNotOptimizeAway(prepared->public_nonce().xonly.data());
540 });
541
542 auto prepare_nonce_with_proof_bench = make_bench(*config, "nonce");
543 prepare_nonce_with_proof_bench.run("puresign_legacy.nonce.prepare_with_proof", [&] {
545 bench_case->key_pair->prepare_message_nonce_with_proof(bench_case->message,
546 bench_case->secp_context.get());
547 assert(prepared.has_value() && "benchmark PureSign nonce proof preparation should succeed");
548 ankerl::nanobench::doNotOptimizeAway(prepared->proof().proof.proof.data());
549 });
550
551 auto prepare_nonce_with_cached_proof_bench = make_bench(*config, "nonce");
552 prepare_nonce_with_cached_proof_bench.run("puresign_legacy.nonce.prepare_with_proof_cached_template", [&] {
554 bench_case->key_pair->prepare_message_nonce_with_proof(bench_case->message_proof_cache,
555 bench_case->secp_context.get());
556 assert(prepared.has_value() && "benchmark cached PureSign nonce proof preparation should succeed");
557 ankerl::nanobench::doNotOptimizeAway(prepared->proof().proof.proof.data());
558 });
559
560 auto prepare_nonce_with_proof_plusplus_bench = make_bench(*config, "nonce");
561 prepare_nonce_with_proof_plusplus_bench.run("puresign_plusplus.nonce.prepare_with_proof", [&] {
563 bench_case->key_pair_plusplus->prepare_message_nonce_with_proof(
564 bench_case->message, bench_case->secp_context.get(), &bench_case->puresign_plusplus_cache);
565 assert(prepared.has_value() && "benchmark PureSign++ nonce proof preparation should succeed");
566 ankerl::nanobench::doNotOptimizeAway(prepared->proof().proof.proof.data());
567 });
568
569 auto prepare_nonce_with_cached_proof_plusplus_bench = make_bench(*config, "nonce");
570 prepare_nonce_with_cached_proof_plusplus_bench.run(
571 "puresign_plusplus.nonce.prepare_with_proof_cached_template", [&] {
572 // Cached-template PureSign++ paths should use the cache-owned backend cache.
574 bench_case->key_pair_plusplus->prepare_message_nonce_with_proof(
575 bench_case->message_proof_cache_plusplus, bench_case->secp_context.get());
576 assert(prepared.has_value() && "benchmark cached PureSign++ nonce proof preparation should succeed");
577 ankerl::nanobench::doNotOptimizeAway(prepared->proof().proof.proof.data());
578 });
579
580 auto verify_nonce_with_proof_bench = make_bench(*config, "nonce");
581 verify_nonce_with_proof_bench.run("puresign_legacy.nonce.verify_proof", [&] {
583 bench_case->public_key.verify_message_nonce_proof(bench_case->message,
584 bench_case->proven_signature.nonce_proof,
585 bench_case->secp_context.get());
586 assert(ok.has_value() && *ok && "benchmark PureSign nonce proof verification should succeed");
587 ankerl::nanobench::doNotOptimizeAway(*ok);
588 });
589
590 auto verify_nonce_with_cached_proof_bench = make_bench(*config, "nonce");
591 verify_nonce_with_cached_proof_bench.run("puresign_legacy.nonce.verify_proof_cached_template", [&] {
593 bench_case->public_key.verify_message_nonce_proof(bench_case->message_proof_cache,
594 bench_case->proven_signature.nonce_proof,
595 bench_case->secp_context.get());
596 assert(ok.has_value() && *ok && "benchmark cached PureSign nonce proof verification should succeed");
597 ankerl::nanobench::doNotOptimizeAway(*ok);
598 });
599
600 auto verify_nonce_with_proof_plusplus_bench = make_bench(*config, "nonce");
601 verify_nonce_with_proof_plusplus_bench.run("puresign_plusplus.nonce.verify_proof", [&] {
603 bench_case->public_key_plusplus.verify_message_nonce_proof(
604 bench_case->message, bench_case->proven_signature_plusplus.nonce_proof,
605 bench_case->secp_context.get(), &bench_case->puresign_plusplus_cache);
606 assert(ok.has_value() && *ok && "benchmark PureSign++ nonce proof verification should succeed");
607 ankerl::nanobench::doNotOptimizeAway(*ok);
608 });
609
610 auto verify_nonce_with_cached_proof_plusplus_bench = make_bench(*config, "nonce");
611 verify_nonce_with_cached_proof_plusplus_bench.run(
612 "puresign_plusplus.nonce.verify_proof_cached_template", [&] {
614 bench_case->public_key_plusplus.verify_message_nonce_proof(
615 bench_case->message_proof_cache_plusplus, bench_case->proven_signature_plusplus.nonce_proof,
616 bench_case->secp_context.get());
617 assert(ok.has_value() && *ok && "benchmark cached PureSign++ nonce proof verification should succeed");
618 ankerl::nanobench::doNotOptimizeAway(*ok);
619 });
620
621 auto sign_bench = make_bench(*config, "signature");
622 sign_bench.run("puresign_legacy.signature.sign", [&] {
624 bench_case->key_pair->sign_message(bench_case->message, bench_case->secp_context.get());
625 assert(signature.has_value() && "benchmark PureSign signing should succeed");
626 ankerl::nanobench::doNotOptimizeAway(signature->bytes.data());
627 });
628
629 auto sign_with_proof_bench = make_bench(*config, "signature");
630 sign_with_proof_bench.run("puresign_legacy.signature.sign_with_proof", [&] {
632 bench_case->key_pair->sign_message_with_proof(bench_case->message, bench_case->secp_context.get());
633 assert(signature.has_value() && "benchmark PureSign proof signing should succeed");
634 ankerl::nanobench::doNotOptimizeAway(signature->signature.bytes.data());
635 });
636
637 auto sign_with_cached_proof_bench = make_bench(*config, "signature");
638 sign_with_cached_proof_bench.run("puresign_legacy.signature.sign_with_proof_cached_template", [&] {
640 bench_case->key_pair->sign_message_with_proof(bench_case->message_proof_cache,
641 bench_case->secp_context.get());
642 assert(signature.has_value() && "benchmark cached PureSign proof signing should succeed");
643 ankerl::nanobench::doNotOptimizeAway(signature->signature.bytes.data());
644 });
645
646 auto sign_with_proof_plusplus_bench = make_bench(*config, "signature");
647 sign_with_proof_plusplus_bench.run("puresign_plusplus.signature.sign_with_proof", [&] {
649 bench_case->key_pair_plusplus->sign_message_with_proof(bench_case->message,
650 bench_case->secp_context.get(),
651 &bench_case->puresign_plusplus_cache);
652 assert(signature.has_value() && "benchmark PureSign++ proof signing should succeed");
653 ankerl::nanobench::doNotOptimizeAway(signature->signature.bytes.data());
654 });
655
656 auto sign_with_cached_proof_plusplus_bench = make_bench(*config, "signature");
657 sign_with_cached_proof_plusplus_bench.run("puresign_plusplus.signature.sign_with_proof_cached_template", [&] {
659 bench_case->key_pair_plusplus->sign_message_with_proof(bench_case->message_proof_cache_plusplus,
660 bench_case->secp_context.get());
661 assert(signature.has_value() && "benchmark cached PureSign++ proof signing should succeed");
662 ankerl::nanobench::doNotOptimizeAway(signature->signature.bytes.data());
663 });
664
665 auto verify_signature_bench = make_bench(*config, "signature");
666 verify_signature_bench.run("puresign_legacy.signature.verify", [&] {
668 bench_case->public_key.verify_signature(bench_case->message, bench_case->signature,
669 bench_case->secp_context.get());
670 assert(ok.has_value() && *ok && "benchmark PureSign signature verification should succeed");
671 ankerl::nanobench::doNotOptimizeAway(*ok);
672 });
673
674 auto verify_with_proof_bench = make_bench(*config, "signature");
675 verify_with_proof_bench.run("puresign_legacy.signature.verify_with_proof", [&] {
677 bench_case->public_key.verify_message_signature_with_proof(bench_case->message,
678 bench_case->proven_signature,
679 bench_case->secp_context.get());
680 assert(ok.has_value() && *ok && "benchmark PureSign proof verification should succeed");
681 ankerl::nanobench::doNotOptimizeAway(*ok);
682 });
683
684 auto verify_signature_with_cached_proof_bench = make_bench(*config, "signature");
685 verify_signature_with_cached_proof_bench.run("puresign_legacy.signature.verify_with_proof_cached_template", [&] {
687 bench_case->public_key.verify_message_signature_with_proof(bench_case->message_proof_cache,
688 bench_case->proven_signature,
689 bench_case->secp_context.get());
690 assert(ok.has_value() && *ok && "benchmark cached PureSign proof verification should succeed");
691 ankerl::nanobench::doNotOptimizeAway(*ok);
692 });
693
694 auto verify_with_proof_plusplus_bench = make_bench(*config, "signature");
695 verify_with_proof_plusplus_bench.run("puresign_plusplus.signature.verify_with_proof", [&] {
697 bench_case->public_key_plusplus.verify_message_signature_with_proof(
698 bench_case->message, bench_case->proven_signature_plusplus, bench_case->secp_context.get(),
699 &bench_case->puresign_plusplus_cache);
700 assert(ok.has_value() && *ok && "benchmark PureSign++ proof verification should succeed");
701 ankerl::nanobench::doNotOptimizeAway(*ok);
702 });
703
704 auto verify_signature_with_cached_proof_plusplus_bench = make_bench(*config, "signature");
705 verify_signature_with_cached_proof_plusplus_bench.run(
706 "puresign_plusplus.signature.verify_with_proof_cached_template", [&] {
708 bench_case->public_key_plusplus.verify_message_signature_with_proof(
709 bench_case->message_proof_cache_plusplus, bench_case->proven_signature_plusplus,
710 bench_case->secp_context.get());
711 assert(ok.has_value() && *ok && "benchmark cached PureSign++ proof verification should succeed");
712 ankerl::nanobench::doNotOptimizeAway(*ok);
713 });
714
715 return 0;
716}
int main(int argc, char **argv)
Runs the Purify benchmark suite.
#define PURIFY_BENCH_BUILD_CONFIG
C++ wrappers for the BPPP functionality used by Purify.
void purify_bulletproof_backend_resources_destroy(purify_bulletproof_backend_resources *resources)
purify_bulletproof_backend_resources * purify_bulletproof_backend_resources_create(purify_secp_context *context, size_t n_gates)
Purify result carrier that either holds a value or an error.
Definition expected.hpp:64
bool has_value() const noexcept
Definition expected.hpp:170
Caller-owned cache for reusable legacy Bulletproof backend resources keyed by gate count.
Field element modulo the backend scalar field used by this implementation.
Definition numeric.hpp:815
Public-key-agnostic native verifier-circuit template.
static Result< SecretKey > from_hex(std::string_view hex)
Parses and validates a packed Purify secret from hexadecimal text.
Definition secret.hpp:76
Caller-owned cache for reusable experimental circuit reduction and BPPP backend data.
Definition bppp.hpp:186
static Result< KeyPair > from_secret(const SecretKey &secret, purify_secp_context *secp_context)
Derives a signing key pair from one packed Purify secret.
Definition legacy.cpp:779
static Result< KeyPair > from_secret(const SecretKey &secret, purify_secp_context *secp_context)
Derives a PureSign++ signing key pair from one packed Purify secret.
Definition bppp.cpp:798
GeneratorBytes base_generator(purify_secp_context *secp_context)
Returns the serialized secp256k1 base generator used as the blind generator.
Definition bppp.cpp:1064
bool verify_norm_arg(const NormArgProof &proof, purify_secp_context *secp_context)
Verifies a standalone BPPP norm argument.
Definition bppp.cpp:1298
Result< NormArgProof > prove_norm_arg(const NormArgInputs &inputs, purify_secp_context *secp_context)
Produces a standalone BPPP norm argument.
Definition bppp.cpp:1180
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
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
Result< NativeBulletproofCircuitTemplate > verifier_circuit_template(const Bytes &message)
Builds a reusable public-key-agnostic verifier-circuit template for a message.
Definition api.cpp:210
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
Result< bool > verify_experimental_circuit(const NativeBulletproofCircuit &circuit, const ExperimentalBulletproofProof &proof, const BulletproofGeneratorBytes &value_generator, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, ExperimentalBulletproofBackendCache *backend_cache=nullptr)
Verifies a proof produced by prove_experimental_circuit against the same one-commitment native circui...
std::unique_ptr< purify_secp_context, SecpContextDeleter > SecpContextPtr
Definition common.hpp:50
Result< NativeBulletproofCircuit > verifier_circuit(const Bytes &message, const UInt512 &pubkey)
Builds the native verifier circuit for a message and public key.
Definition api.cpp:204
Bytes bytes_from_ascii(std::string_view input)
Encodes an ASCII string as a byte vector.
Definition curve.cpp:163
SecpContextPtr make_secp_context() noexcept
Definition common.hpp:52
std::vector< unsigned char > Bytes
Dynamically sized byte string used for messages, serialized witnesses, and proofs.
Definition common.hpp:99
Result< ExperimentalBulletproofProof > prove_experimental_circuit(const NativeBulletproofCircuit &circuit, const BulletproofAssignmentData &assignment, const BulletproofScalarBytes &nonce, const BulletproofGeneratorBytes &value_generator, purify_secp_context *secp_context, std::span< const unsigned char > statement_binding={}, std::optional< BulletproofScalarBytes > blind=std::nullopt, ExperimentalBulletproofBackendCache *backend_cache=nullptr)
Proves a native circuit with the experimental imported Bulletproof circuit backend.
std::vector< PointBytes > generators
Definition bppp.cpp:471
Umbrella header that re-exports the public Purify API.
Complete witness bundle for evaluating and proving a Purify instance.
Definition api.hpp:73
Experimental single-proof wrapper over the imported legacy Bulletproof circuit backend.
One sparse row of circuit coefficients.
One sparse matrix entry in a native circuit row.
Native in-memory representation of a Bulletproof-style arithmetic circuit.
Experimental masked circuit proof that hides the reduced witness before the final BPPP argument.
Definition bppp.hpp:257
Inputs required to produce a standalone BPPP norm argument.
Definition bppp.hpp:97
Standalone BPPP norm-argument proof bundle with all verifier-side inputs.
Definition bppp.hpp:109
Cacheable message-bound nonce-proof template.
Definition legacy.hpp:247
static Result< MessageProofCache > build(std::span< const unsigned char > message)
Builds a reusable verifier template for one exact message.
Definition legacy.cpp:771
Standard signature bundled with the public nonce proof it relied on.
Definition legacy.hpp:231
Public key bundle pairing a Purify packed public key with its derived BIP340 x-only key.
Definition legacy.hpp:42
Standard 64-byte BIP340 signature.
Definition legacy.hpp:201
Cacheable message-bound nonce-proof template for the BPPP-backed PureSign++ proof(R) flow.
Definition bppp.hpp:218
static Result< MessageProofCache > build(std::span< const unsigned char > message)
Builds a reusable verifier template for one exact message.
Definition bppp.cpp:271