purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
c_api.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.h"
11
12#include <algorithm>
13#include <array>
14#include <cstdint>
15#include <span>
16#include <string_view>
17
18#include "purify/curve.hpp"
19#include "purify/error.hpp"
20#include "purify/secret.hpp"
21#include "core.h"
22#include "error_bridge.hpp"
23
25
26bool ranges_overlap(const void* lhs, std::size_t lhs_size, const void* rhs, std::size_t rhs_size) noexcept {
27 if (lhs_size == 0 || rhs_size == 0 || lhs == nullptr || rhs == nullptr) {
28 return false;
29 }
30
31 const auto lhs_addr = reinterpret_cast<std::uintptr_t>(lhs);
32 const auto rhs_addr = reinterpret_cast<std::uintptr_t>(rhs);
33 if (lhs_addr <= rhs_addr) {
34 return (rhs_addr - lhs_addr) < lhs_size;
35 }
36 return (lhs_addr - rhs_addr) < rhs_size;
37}
38
39Bytes copy_bytes(const unsigned char* data, std::size_t size) {
40 if (size == 0) {
41 return {};
42 }
43 return Bytes(data, data + size);
44}
45
47 if (out == nullptr) {
48 return;
49 }
50 detail::secure_clear_bytes(out->secret_key, sizeof(out->secret_key));
51 std::fill(std::begin(out->public_key), std::end(out->public_key), 0);
52}
53
55 if (out == nullptr) {
56 return;
57 }
58 detail::secure_clear_bytes(out->secret_key, sizeof(out->secret_key));
59 std::fill(std::begin(out->xonly_public_key), std::end(out->xonly_public_key), 0);
60}
61
63 static const UInt256 value =
64 UInt256::from_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
65 return value;
66}
67
69 static const UInt256 value = [] {
72 return out;
73 }();
74 return value;
75}
76
77Bytes tagged_message(std::string_view prefix, const Bytes& message) {
78 Bytes out;
79 out.reserve(prefix.size() + message.size());
80 out.insert(out.end(), prefix.begin(), prefix.end());
81 out.insert(out.end(), message.begin(), message.end());
82 return out;
83}
84
85Result<UInt512> parse_secret_key(const unsigned char* secret_key) {
86 if (secret_key == nullptr) {
87 return unexpected_error(ErrorCode::MissingValue, "parse_secret_key:null_secret_key");
88 }
89 const purify_error_code code = purify_validate_secret_key(secret_key);
90 if (code != PURIFY_ERROR_OK) {
92 "parse_secret_key:purify_validate_secret_key");
93 }
95 return packed;
96}
97
98Result<UInt512> parse_public_key(const unsigned char* public_key) {
99 if (public_key == nullptr) {
100 return unexpected_error(ErrorCode::MissingValue, "parse_public_key:null_public_key");
101 }
102 const purify_error_code code = purify_validate_public_key(public_key);
103 if (code != PURIFY_ERROR_OK) {
105 "parse_public_key:purify_validate_public_key");
106 }
108 return packed;
109}
110
111void write_uint512(const UInt512& value, unsigned char* out) {
112 const std::array<unsigned char, PURIFY_PUBLIC_KEY_BYTES> bytes = value.to_bytes_be();
113 std::copy(bytes.begin(), bytes.end(), out);
114}
115
116void write_field_element(const FieldElement& value, unsigned char* out) {
117 const std::array<unsigned char, PURIFY_FIELD_ELEMENT_BYTES> bytes = value.to_bytes_be();
118 std::copy(bytes.begin(), bytes.end(), out);
119}
120
122 PURIFY_ASSIGN_OR_RETURN(const auto& unpacked, unpack_secret(secret), "derive_public_key_from_secret:unpack_secret");
123 PURIFY_ASSIGN_OR_RETURN(const auto& p1, curve1().mul_secret_affine(generator1(), unpacked.first),
124 "derive_public_key_from_secret:mul_secret_affine_p1");
125 PURIFY_ASSIGN_OR_RETURN(const auto& p2, curve2().mul_secret_affine(generator2(), unpacked.second),
126 "derive_public_key_from_secret:mul_secret_affine_p2");
127 return pack_public(p1.x.to_uint256(), p2.x.to_uint256());
128}
129
130} // namespace purify::capi_detail
131
132extern "C" {
133
137 purify_error_code status;
138 if (out == nullptr) {
140 }
143 if (status != PURIFY_ERROR_OK) {
145 return status;
146 }
148 if (!packed_secret.has_value()) {
150 return purify::core_api_detail::to_core_error_code(packed_secret.error().code);
151 }
152 public_key = purify::capi_detail::derive_public_key_from_secret(*packed_secret);
153 if (!public_key.has_value()) {
155 return purify::core_api_detail::to_core_error_code(public_key.error().code);
156 }
158 return PURIFY_ERROR_OK;
159}
160
162 const unsigned char* seed,
163 size_t seed_len) {
166 purify_error_code status;
167 const unsigned char* seed_input = seed;
168 purify::Bytes seed_copy;
169 if (out == nullptr) {
171 }
172 if (purify::capi_detail::ranges_overlap(out, sizeof(*out), seed, seed_len)) {
173 seed_copy = purify::capi_detail::copy_bytes(seed, seed_len);
174 seed_input = seed_copy.data();
175 }
177 status = purify_core_seed_secret_key(out->secret_key, seed_input, seed_len);
178 if (status != PURIFY_ERROR_OK) {
180 return status;
181 }
183 if (!packed_secret.has_value()) {
185 return purify::core_api_detail::to_core_error_code(packed_secret.error().code);
186 }
187 public_key = purify::capi_detail::derive_public_key_from_secret(*packed_secret);
188 if (!public_key.has_value()) {
190 return purify::core_api_detail::to_core_error_code(public_key.error().code);
191 }
193 return PURIFY_ERROR_OK;
194}
195
197 const unsigned char secret_key[PURIFY_SECRET_KEY_BYTES]) {
198 if (out_public_key == nullptr) {
200 }
201
203 if (!packed_secret.has_value()) {
204 return purify::core_api_detail::to_core_error_code(packed_secret.error().code);
205 }
206
208 if (!public_key.has_value()) {
209 return purify::core_api_detail::to_core_error_code(public_key.error().code);
210 }
211
212 std::fill(out_public_key, out_public_key + PURIFY_PUBLIC_KEY_BYTES, 0);
213 purify::capi_detail::write_uint512(*public_key, out_public_key);
214 return PURIFY_ERROR_OK;
215}
216
218 const unsigned char secret_key[PURIFY_SECRET_KEY_BYTES],
219 purify_secp_context* secp_context) {
220 if (out == nullptr) {
222 }
223
225 if (!packed_secret.has_value()) {
226 return purify::core_api_detail::to_core_error_code(packed_secret.error().code);
227 }
228
230
231 static const purify::TaggedHash kBip340KeyGenTag("Purify/BIP340/KeyGen");
232 std::array<unsigned char, PURIFY_SECRET_KEY_BYTES> packed_secret_bytes = packed_secret->to_bytes_be();
233 purify::Bytes ikm(packed_secret_bytes.begin(), packed_secret_bytes.end());
234#if PURIFY_USE_LEGACY_FIELD_HASHES
235 std::optional<purify::UInt256> scalar =
236 purify::hash_to_int<4>(ikm, purify::capi_detail::secp256k1_order_minus_one(),
237 purify::bytes_from_ascii("Purify/BIP340/KeyGen"));
238#else
239 std::optional<purify::UInt256> scalar = purify::tagged_hash_to_int<4>(
240 std::span<const unsigned char>(ikm.data(), ikm.size()), purify::capi_detail::secp256k1_order_minus_one(),
241 kBip340KeyGenTag);
242#endif
243 purify::detail::secure_clear_bytes(ikm.data(), ikm.size());
244 purify::detail::secure_clear_bytes(packed_secret_bytes.data(), packed_secret_bytes.size());
245 if (!scalar.has_value()) {
247 }
248 scalar->add_small(1);
249
250 const std::array<unsigned char, PURIFY_BIP340_SECRET_KEY_BYTES> scalar_bytes = scalar->to_bytes_be();
251 std::copy(scalar_bytes.begin(), scalar_bytes.end(), out->secret_key);
252 if (secp_context == nullptr) {
255 }
256 if (purify_bip340_key_from_seckey(secp_context, out->secret_key, out->xonly_public_key) == 0) {
259 }
260 return PURIFY_ERROR_OK;
261}
262
264 const unsigned char secret_key[PURIFY_SECRET_KEY_BYTES],
265 const unsigned char* message,
266 size_t message_len) {
267 if (out_field_element == nullptr) {
269 }
270 if (message_len != 0 && message == nullptr) {
272 }
273
275 if (!packed_secret.has_value()) {
276 return purify::core_api_detail::to_core_error_code(packed_secret.error().code);
277 }
278
280 if (!unpacked.has_value()) {
282 }
283
284 const purify::Bytes message_bytes = purify::capi_detail::copy_bytes(message, message_len);
285 std::fill(out_field_element, out_field_element + PURIFY_FIELD_ELEMENT_BYTES, 0);
286
289 if (!m1.has_value()) {
291 }
294 if (!m2.has_value()) {
296 }
297
299 purify::curve1().mul_secret_affine(*m1, unpacked->first);
300 if (!q1.has_value()) {
302 }
304 purify::curve2().mul_secret_affine(*m2, unpacked->second);
305 if (!q2.has_value()) {
307 }
308
309 const purify::FieldElement output = purify::combine(q1->x, q2->x);
310 purify::capi_detail::write_field_element(output, out_field_element);
311 return PURIFY_ERROR_OK;
312}
313
314} // extern "C"
purify_error_code purify_generate_key(purify_generated_key *out)
Generates one random Purify keypair.
Definition c_api.cpp:134
purify_error_code purify_generate_key_from_seed(purify_generated_key *out, const unsigned char *seed, size_t seed_len)
Deterministically derives one Purify keypair from seed material.
Definition c_api.cpp:161
purify_error_code purify_eval(unsigned char out_field_element[PURIFY_FIELD_ELEMENT_BYTES], const unsigned char secret_key[PURIFY_SECRET_KEY_BYTES], const unsigned char *message, size_t message_len)
Evaluates the Purify PRF for one packed secret and message.
Definition c_api.cpp:263
purify_error_code purify_derive_public_key(unsigned char out_public_key[PURIFY_PUBLIC_KEY_BYTES], const unsigned char secret_key[PURIFY_SECRET_KEY_BYTES])
Derives the packed public key corresponding to one packed Purify secret.
Definition c_api.cpp:196
purify_error_code purify_derive_bip340_key(purify_bip340_key *out, const unsigned char secret_key[PURIFY_SECRET_KEY_BYTES], purify_secp_context *secp_context)
Derives one canonical BIP340 keypair from one packed Purify secret.
Definition c_api.cpp:217
Result< AffinePoint > mul_secret_affine(const JacobianPoint &point, const UInt256 &scalar) const
Multiplies a public point by a secret scalar using exception-free complete formulas.
Definition curve.cpp:152
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::array< unsigned char, 32 > to_bytes_be() const
Serializes the field element in big-endian form.
Definition numeric.cpp:84
Reusable BIP340-style tagged SHA-256 helper.
Definition curve.hpp:111
purify_error_code purify_core_sample_secret_key(unsigned char out_secret_key[PURIFY_SECRET_KEY_BYTES])
Definition core.c:315
purify_error_code purify_core_seed_secret_key(unsigned char out_secret_key[PURIFY_SECRET_KEY_BYTES], const unsigned char *seed, size_t seed_len)
Definition core.c:334
Elliptic-curve helpers, fixed parameters, and hash-to-curve utilities for Purify.
Library-level error taxonomy used to classify Purify failures.
#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
void write_field_element(const FieldElement &value, unsigned char *out)
Definition c_api.cpp:116
void clear_generated_key(purify_generated_key *out) noexcept
Definition c_api.cpp:46
const UInt256 & secp256k1_order()
Definition c_api.cpp:62
void clear_bip340_key(purify_bip340_key *out) noexcept
Definition c_api.cpp:54
void write_uint512(const UInt512 &value, unsigned char *out)
Definition c_api.cpp:111
Bytes copy_bytes(const unsigned char *data, std::size_t size)
Definition c_api.cpp:39
bool ranges_overlap(const void *lhs, std::size_t lhs_size, const void *rhs, std::size_t rhs_size) noexcept
Definition c_api.cpp:26
Bytes tagged_message(std::string_view prefix, const Bytes &message)
Definition c_api.cpp:77
Result< UInt512 > parse_public_key(const unsigned char *public_key)
Definition c_api.cpp:98
Result< UInt512 > derive_public_key_from_secret(const UInt512 &secret)
Definition c_api.cpp:121
Result< UInt512 > parse_secret_key(const unsigned char *secret_key)
Definition c_api.cpp:85
const UInt256 & secp256k1_order_minus_one()
Definition c_api.cpp:68
constexpr ErrorCode from_core_error_code(purify_error_code code) noexcept
constexpr purify_error_code to_core_error_code(ErrorCode code) noexcept
void secure_clear_bytes(void *data, std::size_t size) noexcept
Definition secret.hpp:22
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
Bytes bytes_from_ascii(std::string_view input)
Encodes an ASCII string as a byte vector.
Definition curve.cpp:163
const EllipticCurve & curve1()
Returns the first Purify curve instance.
Definition curve.cpp:256
std::vector< unsigned char > Bytes
Dynamically sized byte string used for messages, serialized witnesses, and proofs.
Definition common.hpp:99
const JacobianPoint & generator1()
Returns the fixed generator for the first curve.
Definition curve.cpp:277
UInt512 pack_public(const UInt256 &x1, const UInt256 &x2)
Packs two x-coordinates into the reference 512-bit public-key encoding.
Definition curve.cpp:339
Result< std::pair< UInt256, UInt256 > > unpack_secret(const UInt512 &z)
Splits a packed private key into its two per-curve secret scalars.
Definition curve.cpp:321
Result< JacobianPoint > hash_to_curve(const Bytes &data, const EllipticCurve &curve)
Hashes arbitrary data onto the supplied curve by rejection sampling x-coordinates.
Definition curve.cpp:268
const EllipticCurve & curve2()
Returns the second Purify curve instance.
Definition curve.cpp:261
const JacobianPoint & generator2()
Returns the fixed generator for the second curve.
Definition curve.cpp:288
FieldElement combine(const FieldElement &x1, const FieldElement &x2)
Applies the Purify curve-combination map to two x-coordinates.
Definition curve.cpp:345
Scalar32 scalar
Definition bppp.cpp:119
Public C core for Purify key validation, key derivation, key generation, and evaluation.
#define PURIFY_SECRET_KEY_BYTES
Definition purify.h:17
#define PURIFY_PUBLIC_KEY_BYTES
Definition purify.h:18
purify_error_code
Machine-readable status code returned by the Purify C core.
Definition purify.h:28
@ PURIFY_ERROR_MISSING_VALUE
Definition purify.h:41
@ PURIFY_ERROR_OK
Definition purify.h:29
@ PURIFY_ERROR_INTERNAL_MISMATCH
Definition purify.h:57
@ PURIFY_ERROR_BACKEND_REJECTED_INPUT
Definition purify.h:53
purify_error_code purify_validate_secret_key(const unsigned char secret_key[PURIFY_SECRET_KEY_BYTES])
Validates one packed Purify secret key.
Definition core.c:307
#define PURIFY_FIELD_ELEMENT_BYTES
Definition purify.h:19
purify_error_code purify_validate_public_key(const unsigned char public_key[PURIFY_PUBLIC_KEY_BYTES])
Validates one packed Purify public key.
Definition core.c:311
int purify_bip340_key_from_seckey(purify_secp_context *context, unsigned char seckey32[32], unsigned char xonly_pubkey32[32])
Canonicalizes a valid secp256k1 secret key for BIP340 and derives its x-only public key.
Secret-owning Purify key material wrappers.
static BigUInt from_bytes_be(const unsigned char *data, std::size_t size)
Parses a big-endian byte string into the fixed-width integer.
Definition numeric.hpp:235
void sub_assign(const BigUInt &other)
Subtracts another fixed-width integer in place.
Definition numeric.hpp:467
static BigUInt from_hex(std::string_view hex)
Parses a hexadecimal string with the precondition that the value fits exactly.
Definition numeric.hpp:280
static BigUInt one()
Returns the multiplicative identity.
Definition numeric.hpp:213
std::array< unsigned char, Words *8 > to_bytes_be() const
Serializes the value to a fixed-width big-endian byte array.
Definition numeric.hpp:621
Canonical BIP340 keypair derived from one packed Purify secret.
Definition purify.h:68
unsigned char secret_key[PURIFY_BIP340_SECRET_KEY_BYTES]
Definition purify.h:69
unsigned char xonly_public_key[PURIFY_BIP340_XONLY_PUBKEY_BYTES]
Definition purify.h:70
Seed/public-key bundle returned by the C core key-generation entry points.
Definition purify.h:62
unsigned char secret_key[PURIFY_SECRET_KEY_BYTES]
Definition purify.h:63
unsigned char public_key[PURIFY_PUBLIC_KEY_BYTES]
Definition purify.h:64