purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
purify_runtime.hpp
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#pragma once
11
12#include <cctype>
13#include <fstream>
14#include <iomanip>
15#include <iostream>
16#include <optional>
17#include <sstream>
18#include <string>
19#include <string_view>
20
21#include "purify/bppp.hpp"
22#include "purify.hpp"
23
24namespace purify {
25
31inline Result<Bytes> bytes_from_hex(std::string_view hex) {
32 Bytes out;
33 std::string filtered;
34 filtered.reserve(hex.size());
35 for (char ch : hex) {
36 if (std::isspace(static_cast<unsigned char>(ch)) == 0) {
37 filtered.push_back(ch);
38 }
39 }
40 if ((filtered.size() & 1U) != 0) {
41 return unexpected_error(ErrorCode::InvalidHexLength, "bytes_from_hex:odd_length");
42 }
43 for (std::size_t i = 0; i < filtered.size(); i += 2) {
44 auto decode = [](char ch) -> int {
45 if (ch >= '0' && ch <= '9') {
46 return static_cast<int>(ch - '0');
47 }
48 if (ch >= 'a' && ch <= 'f') {
49 return static_cast<int>(10 + ch - 'a');
50 }
51 if (ch >= 'A' && ch <= 'F') {
52 return static_cast<int>(10 + ch - 'A');
53 }
54 return -1;
55 };
56 int high = decode(filtered[i]);
57 int low = decode(filtered[i + 1]);
58 if (high < 0 || low < 0) {
59 return unexpected_error(ErrorCode::InvalidHex, "bytes_from_hex:invalid_digit");
60 }
61 out.push_back(static_cast<unsigned char>((high << 4) | low));
62 }
63 return out;
64}
65
72template <std::size_t N>
74 Result<Bytes> bytes = bytes_from_hex(hex);
75 if (!bytes.has_value()) {
76 return unexpected_error(bytes.error(), "array_from_hex:parse_bytes");
77 }
78 if (bytes->size() != N) {
79 return unexpected_error(ErrorCode::InvalidFixedSize, "array_from_hex:wrong_size");
80 }
81 std::array<unsigned char, N> out{};
82 std::copy(bytes->begin(), bytes->end(), out.begin());
83 return out;
84}
85
92template <typename ByteContainer>
93inline std::string hex_from_bytes(const ByteContainer& bytes) {
94 std::ostringstream out;
95 out << std::hex << std::setfill('0');
96 for (unsigned char byte : bytes) {
97 out << std::setw(2) << static_cast<unsigned>(byte);
98 }
99 return out.str();
100}
101
107inline Status write_file(const std::string& path, const Bytes& bytes) {
108 std::ofstream file(path, std::ios::binary);
109 if (!file) {
110 return unexpected_error(ErrorCode::IoOpenFailed, "write_file:open");
111 }
112 file.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()));
113 if (!file) {
114 return unexpected_error(ErrorCode::IoWriteFailed, "write_file:write");
115 }
116 return {};
117}
118
125inline Status prove(const Bytes& message, const SecretKey& secret, const std::string& output_path = "prove.assn") {
126 Result<Bytes> assignment = prove_assignment(message, secret);
127 if (!assignment.has_value()) {
128 return unexpected_error(assignment.error(), "prove:prove_assignment");
129 }
130 return write_file(output_path, *assignment);
131}
132
139inline int run_cli(int argc, char** argv) {
140 auto print_error = [](const Error& error) {
141 std::cerr << error.message() << "\n";
142 return 1;
143 };
144
145 auto usage = [&]() {
146 std::cout << "Usage: " << argv[0] << " gen [<seckey>]: generate a key\n";
147 std::cout << " " << argv[0] << " eval <seckey> <hexmsg>: evaluate the PRF\n";
148 std::cout << " " << argv[0] << " verifier <hexmsg> <pubkey>: output verifier circuit for a given message\n";
149 std::cout << " " << argv[0] << " prove <hexmsg> <seckey>: produce input for verifier\n";
150 std::cout << " " << argv[0] << " run-circuit <hexmsg> <seckey>: build and evaluate the native verifier circuit\n";
151 std::cout << " " << argv[0] << " commit-eval <seckey> <hexmsg> <blind32>: commit to the evaluated output\n";
152 };
153
154 if (argc < 2) {
155 usage();
156 return 0;
157 }
158 std::string command = argv[1];
159 if (command == "gen") {
160 std::optional<SecretKey> secret_override;
161 if (argc >= 3) {
162 Result<SecretKey> parsed = SecretKey::from_hex(argv[2]);
163 if (!parsed.has_value()) {
164 return print_error(parsed.error());
165 }
166 secret_override = std::move(*parsed);
167 }
169 secret_override.has_value() ? derive_key(*secret_override) : generate_key();
170 if (!key.has_value()) {
171 return print_error(key.error());
172 }
173 std::cout << "z=" << key->secret.packed().to_hex() << " # private key\n";
174 std::cout << "x=" << key->public_key.to_hex() << " # public key\n";
175 return 0;
176 }
177 if (command == "eval") {
178 if (argc != 4) {
179 usage();
180 return 1;
181 }
182 Result<SecretKey> secret = SecretKey::from_hex(argv[2]);
183 if (!secret.has_value()) {
184 return print_error(secret.error());
185 }
186 Result<Bytes> message = bytes_from_hex(argv[3]);
187 if (!message.has_value()) {
188 return print_error(message.error());
189 }
190 Result<FieldElement> value = eval(*secret, *message);
191 if (!value.has_value()) {
192 return print_error(value.error());
193 }
194 std::cout << "eval: " << value->to_hex() << "\n";
195 return 0;
196 }
197 if (command == "verifier") {
198 if (argc != 4) {
199 usage();
200 return 1;
201 }
202 Result<Bytes> message = bytes_from_hex(argv[2]);
203 if (!message.has_value()) {
204 return print_error(message.error());
205 }
206 Result<UInt512> pubkey = UInt512::try_from_hex(argv[3]);
207 if (!pubkey.has_value()) {
208 return print_error(pubkey.error());
209 }
210 Result<std::string> verifier_program = verifier(*message, *pubkey);
211 if (!verifier_program.has_value()) {
212 return print_error(verifier_program.error());
213 }
214 std::cout << *verifier_program << "\n";
215 return 0;
216 }
217 if (command == "prove") {
218 if (argc != 4) {
219 usage();
220 return 1;
221 }
222 Result<Bytes> message = bytes_from_hex(argv[2]);
223 if (!message.has_value()) {
224 return print_error(message.error());
225 }
226 Result<SecretKey> secret = SecretKey::from_hex(argv[3]);
227 if (!secret.has_value()) {
228 return print_error(secret.error());
229 }
230 Status status = prove(*message, *secret);
231 if (!status.has_value()) {
232 return print_error(status.error());
233 }
234 return 0;
235 }
236 if (command == "run-circuit") {
237 if (argc != 4) {
238 usage();
239 return 1;
240 }
241 Result<Bytes> message = bytes_from_hex(argv[2]);
242 if (!message.has_value()) {
243 return print_error(message.error());
244 }
245 Result<SecretKey> secret = SecretKey::from_hex(argv[3]);
246 if (!secret.has_value()) {
247 return print_error(secret.error());
248 }
249 Result<BulletproofWitnessData> witness = prove_assignment_data(*message, *secret);
250 if (!witness.has_value()) {
251 return print_error(witness.error());
252 }
253 Result<NativeBulletproofCircuit> circuit = verifier_circuit(*message, witness->public_key);
254 if (!circuit.has_value()) {
255 return print_error(circuit.error());
256 }
257 bool ok = circuit->evaluate(witness->assignment);
258 std::cout << "gates=" << circuit->n_gates << "\n";
259 std::cout << "constraints=" << circuit->c.size() << "\n";
260 std::cout << "commitments=" << circuit->n_commitments << "\n";
261 std::cout << (ok ? "ok" : "fail") << "\n";
262 return ok ? 0 : 1;
263 }
264 if (command == "commit-eval") {
265 if (argc != 5) {
266 usage();
267 return 1;
268 }
269 SecpContextPtr secp_context = make_secp_context();
270 if (secp_context == nullptr) {
271 return print_error(Error{ErrorCode::BackendRejectedInput});
272 }
273 Result<SecretKey> secret = SecretKey::from_hex(argv[2]);
274 if (!secret.has_value()) {
275 return print_error(secret.error());
276 }
277 Result<Bytes> message = bytes_from_hex(argv[3]);
278 if (!message.has_value()) {
279 return print_error(message.error());
280 }
281 Result<std::array<unsigned char, 32>> blind = array_from_hex<32>(argv[4]);
282 if (!blind.has_value()) {
283 return print_error(blind.error());
284 }
286 bppp::commit_output_witness(*message, *secret, *blind, secp_context.get());
287 if (!committed.has_value()) {
288 return print_error(committed.error());
289 }
290 std::cout << "pubkey=" << committed->public_key.to_hex() << "\n";
291 std::cout << "output=" << committed->output.to_hex() << "\n";
292 std::cout << "commit=" << hex_from_bytes(committed->commitment) << "\n";
293 return 0;
294 }
295 std::cout << "Unknown command\n";
296 return 1;
297}
298
299} // namespace purify
C++ wrappers for the BPPP functionality used by Purify.
Purify result carrier that either holds a value or an error.
Definition expected.hpp:64
bool has_value() const noexcept
Definition expected.hpp:170
Move-only packed Purify secret stored in dedicated heap memory.
Definition secret.hpp:52
static Result< SecretKey > from_hex(std::string_view hex)
Parses and validates a packed Purify secret from hexadecimal text.
Definition secret.hpp:76
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
Definition api.hpp:21
Status prove(const Bytes &message, const SecretKey &secret, const std::string &output_path="prove.assn")
Writes a serialized witness assignment for a message and secret.
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< std::string > verifier(const Bytes &message, const UInt512 &pubkey)
Builds the legacy serialized verifier description for a message and public key.
Definition api.cpp:190
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
int run_cli(int argc, char **argv)
Dispatches the purify_cpp command-line interface.
Result< std::array< unsigned char, N > > array_from_hex(std::string_view hex)
Parses a fixed-size hexadecimal string into an array.
Result< GeneratedKey > generate_key()
Generates a random Purify keypair using the built-in OS RNG.
Definition api.cpp:112
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
SecpContextPtr make_secp_context() noexcept
Definition common.hpp:52
Result< Bytes > prove_assignment(const Bytes &message, const SecretKey &secret)
Serializes the witness assignment produced for a message and secret.
Definition api.cpp:294
Result< GeneratedKey > derive_key(const SecretKey &secret)
Derives the packed public key corresponding to a packed secret.
Definition api.cpp:142
std::vector< unsigned char > Bytes
Dynamically sized byte string used for messages, serialized witnesses, and proofs.
Definition common.hpp:99
Result< Bytes > bytes_from_hex(std::string_view hex)
Parses a hexadecimal string into raw bytes.
Status write_file(const std::string &path, const Bytes &bytes)
Writes a byte buffer to disk.
Result< FieldElement > eval(const SecretKey &secret, const Bytes &message)
Evaluates the Purify PRF for an owned secret key and message.
Definition api.cpp:177
std::string hex_from_bytes(const ByteContainer &bytes)
Encodes a byte container as lowercase hexadecimal.
Umbrella header that re-exports the public Purify API.
static Result< BigUInt > try_from_hex(std::string_view hex)
Parses a hexadecimal string, ignoring optional 0x and whitespace.
Definition numeric.hpp:249
Compact error object returned by checked APIs.
Definition error.hpp:75