purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
core.c
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
5#include "core.h"
6
7#include <stddef.h>
8#include <stdlib.h>
9#include <string.h>
10
11#include "purify/secp_bridge.h"
12
13#if defined(_WIN32)
14#ifndef NOMINMAX
15#define NOMINMAX
16#endif
17#ifndef WIN32_LEAN_AND_MEAN
18#define WIN32_LEAN_AND_MEAN
19#endif
20#include <windows.h>
21#include <bcrypt.h>
22#elif defined(__linux__)
23#include <errno.h>
24#include <sys/random.h>
25#include <unistd.h>
26#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
27#include <unistd.h>
28#else
29#include <errno.h>
30#include <stdio.h>
31#include <unistd.h>
32#endif
33
34/*
35 * Big-endian encoding of half_n1 * half_n2.
36 *
37 * Packed secrets use the mixed-radix encoding:
38 * z = (z1 - 1) + half_n1 * (z2 - 1)
39 * with 1 <= z1 <= half_n1 and 1 <= z2 <= half_n2.
40 * Therefore the valid packed range is [0, half_n1 * half_n2).
41 */
43 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
44 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
45 0x5d, 0x57, 0x6e, 0x73, 0x57, 0xa4, 0x50, 0x1d,
46 0xdf, 0xe9, 0x2f, 0x46, 0x68, 0x1b, 0x20, 0xa0,
47 0xb2, 0x92, 0x66, 0xf8, 0xfd, 0xd3, 0x36, 0x23,
48 0x17, 0x0b, 0xa9, 0x62, 0x08, 0xc6, 0x3e, 0x47,
49 0x58, 0xfb, 0xa2, 0xd2, 0xca, 0xf0, 0xc1, 0x8d,
50 0xc4, 0x8a, 0xf1, 0x1c, 0xeb, 0xe3, 0xf4, 0x64,
51};
52
53/*
54 * Big-endian encoding of p^2.
55 *
56 * Packed public keys use:
57 * packed = x1 + p * x2
58 * with 0 <= x1, x2 < p.
59 * Therefore the valid packed range is [0, p^2).
60 */
62 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
63 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd,
64 0x75, 0x5d, 0xb9, 0xcd, 0x5e, 0x91, 0x40, 0x77,
65 0x7f, 0xa4, 0xbd, 0x19, 0xa0, 0x6c, 0x82, 0x83,
66 0x9d, 0x67, 0x1c, 0xd5, 0x81, 0xc6, 0x9b, 0xc5,
67 0xe6, 0x97, 0xf5, 0xe4, 0x5b, 0xcd, 0x07, 0xc5,
68 0x2e, 0xc3, 0x73, 0xa8, 0xbd, 0xc5, 0x98, 0xb4,
69 0x49, 0x3f, 0x50, 0xa1, 0x38, 0x0e, 0x12, 0x81,
70};
71
72static const char* const kErrorNames[] = {
73 "ok",
74 "invalid_hex",
75 "invalid_hex_length",
76 "invalid_fixed_size",
77 "overflow",
78 "underflow",
79 "narrowing_overflow",
80 "division_by_zero",
81 "bit_index_out_of_range",
82 "range_violation",
83 "empty_input",
84 "size_mismatch",
85 "missing_value",
86 "invalid_symbol",
87 "unsupported_symbol",
88 "uninitialized_state",
89 "index_out_of_range",
90 "invalid_dimensions",
91 "non_boolean_value",
92 "equation_mismatch",
93 "binding_mismatch",
94 "io_open_failed",
95 "io_write_failed",
96 "entropy_unavailable",
97 "backend_rejected_input",
98 "hash_to_curve_exhausted",
99 "unexpected_size",
100 "generator_order_check_failed",
101 "internal_mismatch",
102 "transcript_check_failed",
103};
104
105static const char* const kErrorMessages[] = {
106 "success",
107 "hex input contains a non-hexadecimal character",
108 "hex input has an invalid length",
109 "input does not have the required fixed size",
110 "operation overflowed the target representation",
111 "operation underflowed the target representation",
112 "narrowing conversion would discard non-zero bits",
113 "division by zero is not permitted",
114 "bit index is outside the valid range",
115 "input is outside the documented valid range",
116 "input must not be empty",
117 "related inputs do not have matching sizes",
118 "required value is missing",
119 "symbol encoding is malformed",
120 "symbol is well-formed but not supported",
121 "object must be initialized before this operation",
122 "index is outside the valid range",
123 "inputs imply an invalid shape or dimension",
124 "value violates a required boolean constraint",
125 "value violates a required equality constraint",
126 "prepared state is bound to a different secret, message, or topic",
127 "unable to open the requested file or stream",
128 "unable to write the requested file or stream",
129 "unable to obtain secure operating-system randomness",
130 "the cryptographic backend rejected the supplied input",
131 "hash-to-curve sampling exhausted all retry attempts",
132 "backend returned an unexpected serialized size",
133 "fixed generator failed its subgroup order check",
134 "internal consistency check failed",
135 "internally generated transcript failed validation",
136};
137
138static void purify_core_secure_clear(void* data, size_t size) {
139 volatile unsigned char* out = (volatile unsigned char*)data;
140 while (size != 0) {
141 *out = 0;
142 ++out;
143 --size;
144 }
145}
146
147static int purify_core_compare_be(const unsigned char* lhs, const unsigned char* rhs, size_t size) {
148 size_t i = 0;
149 for (i = 0; i < size; ++i) {
150 if (lhs[i] < rhs[i]) {
151 return -1;
152 }
153 if (lhs[i] > rhs[i]) {
154 return 1;
155 }
156 }
157 return 0;
158}
159
160static purify_error_code purify_core_validate_below(const unsigned char* value,
161 size_t size,
162 const unsigned char* upper_bound) {
163 if (value == NULL) {
165 }
166 if (purify_core_compare_be(value, upper_bound, size) >= 0) {
168 }
169 return PURIFY_ERROR_OK;
170}
171
173 size_t out_len,
174 const unsigned char* ikm,
175 size_t ikm_len,
176 const unsigned char* salt,
177 size_t salt_len,
178 const unsigned char* info,
179 size_t info_len) {
180 static const unsigned char kZeroSalt[32] = {0};
181 unsigned char prk[32];
182 unsigned char t[32];
183 size_t offset = 0;
184 size_t block_index = 0;
185
186 if (out_len != 0 && out == NULL) {
188 }
189 if (ikm_len != 0 && ikm == NULL) {
191 }
192 if (salt_len != 0 && salt == NULL) {
194 }
195 if (info_len != 0 && info == NULL) {
197 }
198
199 memset(prk, 0, sizeof(prk));
200 memset(t, 0, sizeof(t));
202 salt_len == 0 ? kZeroSalt : salt,
203 salt_len == 0 ? sizeof(kZeroSalt) : salt_len,
204 ikm,
205 ikm_len);
206
207 while (offset < out_len) {
208 const size_t prev_len = block_index == 0 ? 0 : sizeof(t);
209 const size_t input_len = prev_len + info_len + 1;
210 unsigned char* input = (unsigned char*)malloc(input_len == 0 ? 1 : input_len);
211 size_t copy_len;
212 if (input == NULL) {
213 purify_core_secure_clear(prk, sizeof(prk));
214 purify_core_secure_clear(t, sizeof(t));
216 }
217 if (prev_len != 0) {
218 memcpy(input, t, prev_len);
219 }
220 if (info_len != 0) {
221 memcpy(input + prev_len, info, info_len);
222 }
223 input[input_len - 1] = (unsigned char)(block_index + 1);
224 purify_hmac_sha256(t, prk, sizeof(prk), input, input_len);
225 purify_core_secure_clear(input, input_len);
226 free(input);
227
228 copy_len = sizeof(t);
229 if (copy_len > out_len - offset) {
230 copy_len = out_len - offset;
231 }
232 memcpy(out + offset, t, copy_len);
233 offset += copy_len;
234 ++block_index;
235 }
236
237 purify_core_secure_clear(prk, sizeof(prk));
238 purify_core_secure_clear(t, sizeof(t));
239 return PURIFY_ERROR_OK;
240}
241
243 const size_t count = sizeof(kErrorNames) / sizeof(kErrorNames[0]);
244 const unsigned int index = (unsigned int)code;
245 if (index >= count) {
246 return "unknown";
247 }
248 return kErrorNames[index];
249}
250
252 const size_t count = sizeof(kErrorMessages) / sizeof(kErrorMessages[0]);
253 const unsigned int index = (unsigned int)code;
254 if (index >= count) {
255 return "unknown status code";
256 }
257 return kErrorMessages[index];
258}
259
260purify_error_code purify_fill_secure_random(unsigned char* bytes, size_t bytes_len) {
261 if (bytes_len != 0 && bytes == NULL) {
263 }
264#if defined(_WIN32)
265 while (bytes_len != 0) {
266 ULONG chunk = (ULONG)(bytes_len > 0xFFFFFFFFu ? 0xFFFFFFFFu : bytes_len);
267 NTSTATUS status = BCryptGenRandom(NULL, bytes, chunk, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
268 if (status < 0) {
270 }
271 bytes += chunk;
272 bytes_len -= chunk;
273 }
274 return PURIFY_ERROR_OK;
275#elif defined(__linux__)
276 while (bytes_len != 0) {
277 ssize_t written = getrandom(bytes, bytes_len, 0);
278 if (written > 0) {
279 bytes += (size_t)written;
280 bytes_len -= (size_t)written;
281 continue;
282 }
283 if (written < 0 && errno == EINTR) {
284 continue;
285 }
287 }
288 return PURIFY_ERROR_OK;
289#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
290 arc4random_buf(bytes, bytes_len);
291 return PURIFY_ERROR_OK;
292#else
293 FILE* file = fopen("/dev/urandom", "rb");
294 size_t read_count;
295 if (file == NULL) {
297 }
298 read_count = fread(bytes, 1, bytes_len, file);
299 fclose(file);
300 if (read_count != bytes_len) {
302 }
303 return PURIFY_ERROR_OK;
304#endif
305}
306
310
314
316 purify_error_code status;
317 if (out_secret_key == NULL) {
319 }
320 memset(out_secret_key, 0, PURIFY_SECRET_KEY_BYTES);
321 while (1) {
322 status = purify_fill_secure_random(out_secret_key, PURIFY_SECRET_KEY_BYTES);
323 if (status != PURIFY_ERROR_OK) {
325 return status;
326 }
327 out_secret_key[0] &= 0x3f;
329 return PURIFY_ERROR_OK;
330 }
331 }
332}
333
335 const unsigned char* seed,
336 size_t seed_len) {
337 static const unsigned char kInfo[] = "Purify/KeyGen";
338 unsigned char salt[1];
339 unsigned int attempt;
340 purify_error_code status;
341
342 if (out_secret_key == NULL) {
344 }
345 memset(out_secret_key, 0, PURIFY_SECRET_KEY_BYTES);
346 if (seed_len != 0 && seed == NULL) {
348 }
349 if (seed_len < 16) {
351 }
352
353 /* Preserve the legacy hash_to_int<8>(seed, key_space_size(), "Purify/KeyGen") derivation. */
354 for (attempt = 0; attempt < 256; ++attempt) {
355 salt[0] = (unsigned char)attempt;
356 status = purify_core_hkdf_sha256(out_secret_key,
358 seed,
359 seed_len,
360 salt,
361 sizeof(salt),
362 kInfo,
363 sizeof(kInfo) - 1);
364 if (status != PURIFY_ERROR_OK) {
366 return status;
367 }
368 out_secret_key[0] &= 0x3f;
370 return PURIFY_ERROR_OK;
371 }
372 }
373
376}
static int purify_core_compare_be(const unsigned char *lhs, const unsigned char *rhs, size_t size)
Definition core.c:147
static void purify_core_secure_clear(void *data, size_t size)
Definition core.c:138
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_fill_secure_random(unsigned char *bytes, size_t bytes_len)
Fills a caller-owned buffer with secure operating-system randomness.
Definition core.c:260
const char * purify_error_message(purify_error_code code)
Returns a human-facing description for one status code.
Definition core.c:251
static purify_error_code purify_core_validate_below(const unsigned char *value, size_t size, const unsigned char *upper_bound)
Definition core.c:160
static const char *const kErrorNames[]
Definition core.c:72
static const char *const kErrorMessages[]
Definition core.c:105
const char * purify_error_name(purify_error_code code)
Returns a stable programmatic name for one status code.
Definition core.c:242
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
static const unsigned char kPackedSecretKeySpaceSize[PURIFY_SECRET_KEY_BYTES]
Definition core.c:42
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
static purify_error_code purify_core_hkdf_sha256(unsigned char *out, size_t out_len, const unsigned char *ikm, size_t ikm_len, const unsigned char *salt, size_t salt_len, const unsigned char *info, size_t info_len)
Definition core.c:172
static const unsigned char kPackedPublicKeySpaceSize[PURIFY_PUBLIC_KEY_BYTES]
Definition core.c:61
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
#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_ENTROPY_UNAVAILABLE
Definition purify.h:52
@ PURIFY_ERROR_INTERNAL_MISMATCH
Definition purify.h:57
@ PURIFY_ERROR_RANGE_VIOLATION
Definition purify.h:38
Narrow C ABI exposing secp256k1 scalar and HMAC helpers to the C++ headers.
void purify_hmac_sha256(unsigned char output32[32], const unsigned char *key, size_t key_len, const unsigned char *data, size_t data_len)
Computes HMAC-SHA256 over a byte string.