purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
curve.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/curve.hpp"
11
12#include <algorithm>
13
14#include "curve.h"
15
16namespace purify {
17
18namespace {
19
20purify_fe to_core(const FieldElement& value) {
21 return purify_fe{detail::FieldElementAccess::raw(value)};
22}
23
24FieldElement from_core(const purify_fe& value) {
25 return detail::FieldElementAccess::from_raw(value.value);
26}
27
28purify_jacobian_point to_core(const JacobianPoint& point) {
29 return purify_jacobian_point{to_core(point.x), to_core(point.y), to_core(point.z), point.infinity ? 1 : 0};
30}
31
32JacobianPoint from_core(const purify_jacobian_point& point) {
33 return JacobianPoint{from_core(point.x), from_core(point.y), from_core(point.z), point.infinity != 0};
34}
35
36purify_affine_point to_core(const AffinePoint& point) {
37 return purify_affine_point{to_core(point.x), to_core(point.y), point.infinity ? 1 : 0};
38}
39
40AffinePoint from_core(const purify_affine_point& point) {
41 return AffinePoint{from_core(point.x), from_core(point.y), point.infinity != 0};
42}
43
44UInt256 uint256_from_core(void (*fill)(uint64_t*)) {
45 UInt256 out;
46 fill(out.limbs.data());
47 return out;
48}
49
50UInt320 uint320_from_core(void (*fill)(uint64_t*)) {
51 UInt320 out;
52 fill(out.limbs.data());
53 return out;
54}
55
56UInt512 uint512_from_core(void (*fill)(uint64_t*)) {
57 UInt512 out;
58 fill(out.limbs.data());
59 return out;
60}
61
62FieldElement field_from_core(void (*fill)(purify_fe*)) {
63 purify_fe out{};
64 fill(&out);
65 return from_core(out);
66}
67
68purify_curve make_core_curve(const FieldElement& a, const FieldElement& b, const UInt256& n) {
69 purify_curve out{};
70 out.a = to_core(a);
71 out.b = to_core(b);
72 std::copy(n.limbs.begin(), n.limbs.end(), out.n);
73 return out;
74}
75
76} // namespace
77
83
85 : a_(std::move(a)), b_(std::move(b)), n_(std::move(n)) {}
86
88 purify_curve curve = make_core_curve(a_, b_, n_);
89 purify_jacobian_point in = to_core(point);
91 purify_curve_affine(&out, &curve, &in);
92 return from_core(out);
93}
94
96 purify_jacobian_point in = to_core(point);
98 purify_curve_negate(&out, &in);
99 return from_core(out);
100}
101
103 purify_curve curve = make_core_curve(a_, b_, n_);
104 purify_fe in = to_core(x);
105 return purify_curve_is_x_coord(&curve, &in) != 0;
106}
107
108std::optional<JacobianPoint> EllipticCurve::lift_x(const FieldElement& x) const {
109 purify_curve curve = make_core_curve(a_, b_, n_);
110 purify_fe in = to_core(x);
112 if (purify_curve_lift_x(&out, &curve, &in) == 0) {
113 return std::nullopt;
114 }
115 return from_core(out);
116}
117
119 purify_curve curve = make_core_curve(a_, b_, n_);
120 purify_jacobian_point in = to_core(point);
122 purify_curve_double(&out, &curve, &in);
123 return from_core(out);
124}
125
127 purify_curve curve = make_core_curve(a_, b_, n_);
128 purify_jacobian_point lhs_core = to_core(lhs);
129 purify_affine_point rhs_core = to_core(rhs);
131 purify_curve_add_mixed(&out, &curve, &lhs_core, &rhs_core);
132 return from_core(out);
133}
134
136 purify_curve curve = make_core_curve(a_, b_, n_);
137 purify_jacobian_point lhs_core = to_core(lhs);
138 purify_jacobian_point rhs_core = to_core(rhs);
140 purify_curve_add(&out, &curve, &lhs_core, &rhs_core);
141 return from_core(out);
142}
143
145 purify_curve curve = make_core_curve(a_, b_, n_);
146 purify_jacobian_point in = to_core(point);
148 purify_curve_mul(&out, &curve, &in, scalar.limbs.data());
149 return from_core(out);
150}
151
153 purify_curve curve = make_core_curve(a_, b_, n_);
154 purify_jacobian_point point_core = to_core(point);
156 if (purify_curve_mul_secret_affine(&out, &curve, &point_core, scalar.limbs.data()) == 0) {
158 "EllipticCurve::mul_secret_affine:purify_curve_mul_secret_affine");
159 }
160 return from_core(out);
161}
162
163Bytes bytes_from_ascii(std::string_view input) {
164 return Bytes(input.begin(), input.end());
165}
166
167Bytes operator+(Bytes lhs, const Bytes& rhs) {
168 lhs.insert(lhs.end(), rhs.begin(), rhs.end());
169 return lhs;
170}
171
172std::uint64_t ceil_div(std::uint64_t lhs, std::uint64_t rhs) {
173 return (lhs + rhs - 1) / rhs;
174}
175
176Bytes hmac_sha256(const Bytes& key, const Bytes& data) {
177 Bytes out(32);
178 purify_hmac_sha256(out.data(), key.data(), key.size(), data.data(), data.size());
179 return out;
180}
181
182Bytes hkdf(std::size_t length, const Bytes& ikm, const Bytes& salt, const Bytes& info) {
183 constexpr std::size_t hash_len = 32;
184 Bytes zero_salt(hash_len, 0);
185 Bytes prk = hmac_sha256(salt.empty() ? zero_salt : salt, ikm);
186 Bytes t;
187 Bytes okm;
188 for (std::size_t i = 0; i < ceil_div(length, hash_len); ++i) {
189 Bytes input = t;
190 input.insert(input.end(), info.begin(), info.end());
191 input.push_back(static_cast<unsigned char>(i + 1));
192 t = hmac_sha256(prk, input);
193 okm.insert(okm.end(), t.begin(), t.end());
194 }
195 okm.resize(length);
196 return okm;
197}
198
199const UInt256& prime_p() {
200 static const UInt256 value = uint256_from_core(purify_curve_prime_p);
201 return value;
202}
203
205 static const UInt256 value = uint256_from_core(purify_curve_order_n1);
206 return value;
207}
208
210 static const UInt256 value = uint256_from_core(purify_curve_order_n2);
211 return value;
212}
213
214const UInt256& half_n1() {
215 static const UInt256 value = uint256_from_core(purify_curve_half_n1);
216 return value;
217}
218
219const UInt256& half_n2() {
220 static const UInt256 value = uint256_from_core(purify_curve_half_n2);
221 return value;
222}
223
225 static const UInt512 value = uint512_from_core(purify_curve_packed_secret_key_space_size);
226 return value;
227}
228
230 static const UInt512 value = uint512_from_core(purify_curve_packed_public_key_space_size);
231 return value;
232}
233
234const UInt320& two_p() {
235 static const UInt320 value = uint320_from_core(purify_curve_two_p);
236 return value;
237}
238
240 return field_from_core(purify_curve_field_a);
241}
242
244 return field_from_core(purify_curve_field_b);
245}
246
248 return field_from_core(purify_curve_field_d);
249}
250
252 static const FieldElement value = field_from_core(purify_curve_field_di);
253 return value;
254}
255
257 static const EllipticCurve curve(field_a(), field_b(), order_n1());
258 return curve;
259}
260
262 static const EllipticCurve curve(field_a() * field_d() * field_d(),
263 field_b() * field_d() * field_d() * field_d(),
264 order_n2());
265 return curve;
266}
267
269 purify_curve curve_core = make_core_curve(curve.a_, curve.b_, curve.n_);
271 if (purify_curve_hash_to_curve(&out, &curve_core, data.data(), data.size()) == 0) {
272 return unexpected_error(ErrorCode::HashToCurveExhausted, "hash_to_curve:purify_curve_hash_to_curve");
273 }
274 return from_core(out);
275}
276
278 static const JacobianPoint value = [] {
279 Result<JacobianPoint> point_result = hash_to_curve(bytes_from_ascii("Generator/1"), curve1());
280 assert(point_result.has_value() && "generator1() hash_to_curve should not exhaust");
281 JacobianPoint point = *point_result;
282 assert(curve1().mul(point, order_n1()).infinity && "generator1() subgroup order check failed");
283 return point;
284 }();
285 return value;
286}
287
289 static const JacobianPoint value = [] {
290 Result<JacobianPoint> point_result = hash_to_curve(bytes_from_ascii("Generator/2"), curve2());
291 assert(point_result.has_value() && "generator2() hash_to_curve should not exhaust");
292 JacobianPoint point = *point_result;
293 assert(curve2().mul(point, order_n2()).infinity && "generator2() subgroup order check failed");
294 return point;
295 }();
296 return value;
297}
298
300 return purify_curve_is_valid_secret_key(z.limbs.data()) != 0;
301}
302
303bool is_valid_public_key(const UInt512& packed) {
304 return purify_curve_is_valid_public_key(packed.limbs.data()) != 0;
305}
306
308 if (!is_valid_secret_key(z)) {
309 return unexpected_error(ErrorCode::RangeViolation, "validate_secret_key:out_of_range");
310 }
311 return {};
312}
313
315 if (!is_valid_public_key(packed)) {
316 return unexpected_error(ErrorCode::RangeViolation, "validate_public_key:out_of_range");
317 }
318 return {};
319}
320
322 UInt256 first;
323 UInt256 second;
324 if (purify_curve_unpack_secret(first.limbs.data(), second.limbs.data(), z.limbs.data()) == 0) {
325 return unexpected_error(ErrorCode::RangeViolation, "unpack_secret:validate_secret_key");
326 }
327 return std::make_pair(first, second);
328}
329
331 UInt256 first;
332 UInt256 second;
333 if (purify_curve_unpack_public(first.limbs.data(), second.limbs.data(), packed.limbs.data()) == 0) {
334 return unexpected_error(ErrorCode::RangeViolation, "unpack_public:validate_public_key");
335 }
336 return std::make_pair(first, second);
337}
338
339UInt512 pack_public(const UInt256& x1, const UInt256& x2) {
340 UInt512 out;
341 purify_curve_pack_public(out.limbs.data(), x1.limbs.data(), x2.limbs.data());
342 return out;
343}
344
346 purify_fe lhs = to_core(x1);
347 purify_fe rhs = to_core(x2);
348 purify_fe out{};
349 purify_curve_combine(&out, &lhs, &rhs);
350 return from_core(out);
351}
352
354 std::vector<int> out(max_value.bit_length());
355 if (purify_curve_key_to_bits(out.data(), out.size(), n.limbs.data(), max_value.limbs.data()) == 0) {
356 return unexpected_error(ErrorCode::RangeViolation, "key_to_bits:out_of_range");
357 }
358 return out;
359}
360
361} // namespace purify
Minimal elliptic-curve arithmetic over the Purify base field.
Definition curve.hpp:46
JacobianPoint mul(const JacobianPoint &point, const UInt256 &scalar) const
Multiplies a point by a scalar using double-and-add.
Definition curve.cpp:144
AffinePoint affine(const JacobianPoint &point) const
Converts a Jacobian point to affine coordinates.
Definition curve.cpp:87
FieldElement a_
Definition curve.hpp:89
JacobianPoint add_mixed(const JacobianPoint &lhs, const AffinePoint &rhs) const
Adds an affine point to a Jacobian point.
Definition curve.cpp:126
JacobianPoint negate(const JacobianPoint &point) const
Negates a point without changing its projective scale.
Definition curve.cpp:95
bool is_x_coord(const FieldElement &x) const
Returns true if the supplied x-coordinate lifts to a curve point.
Definition curve.cpp:102
std::optional< JacobianPoint > lift_x(const FieldElement &x) const
Lifts an x-coordinate to a Jacobian point when a square root exists.
Definition curve.cpp:108
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
EllipticCurve(FieldElement a, FieldElement b, UInt256 n)
Constructs a curve from its Weierstrass coefficients and subgroup order.
Definition curve.cpp:84
JacobianPoint add(const JacobianPoint &lhs, const JacobianPoint &rhs) const
Adds two Jacobian points.
Definition curve.cpp:135
JacobianPoint double_point(const JacobianPoint &point) const
Doubles a Jacobian point.
Definition curve.cpp:118
FieldElement b_
Definition curve.hpp:90
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
void purify_curve_half_n1(uint64_t out[4])
Definition curve.c:404
int purify_curve_is_x_coord(const purify_curve *curve, const purify_fe *x)
Definition curve.c:482
void purify_curve_jacobian_infinity(purify_jacobian_point *out)
Definition curve.c:443
void purify_curve_pack_public(uint64_t out[8], const uint64_t x1[4], const uint64_t x2[4])
Definition curve.c:897
int purify_curve_is_valid_public_key(const uint64_t value[8])
Definition curve.c:837
void purify_curve_order_n2(uint64_t out[4])
Definition curve.c:400
void purify_curve_packed_public_key_space_size(uint64_t out[8])
Definition curve.c:416
void purify_curve_order_n1(uint64_t out[4])
Definition curve.c:396
void purify_curve_packed_secret_key_space_size(uint64_t out[8])
Definition curve.c:412
int purify_curve_unpack_public(uint64_t first[4], uint64_t second[4], const uint64_t value[8])
Definition curve.c:878
void purify_curve_double(purify_jacobian_point *out, const purify_curve *curve, const purify_jacobian_point *point)
Definition curve.c:517
int purify_curve_key_to_bits(int *out_bits, size_t out_len, const uint64_t value[4], const uint64_t max_value[4])
Definition curve.c:937
void purify_curve_field_a(purify_fe *out)
Definition curve.c:425
void purify_curve_two_p(uint64_t out[5])
Definition curve.c:420
void purify_curve_negate(purify_jacobian_point *out, const purify_jacobian_point *point)
Definition curve.c:471
void purify_curve_half_n2(uint64_t out[4])
Definition curve.c:408
int purify_curve_lift_x(purify_jacobian_point *out, const purify_curve *curve, const purify_fe *x)
Definition curve.c:496
int purify_curve_is_valid_secret_key(const uint64_t value[8])
Definition curve.c:831
void purify_curve_combine(purify_fe *out, const purify_fe *x1, const purify_fe *x2)
Definition curve.c:904
void purify_curve_field_di(purify_fe *out)
Definition curve.c:437
void purify_curve_field_d(purify_fe *out)
Definition curve.c:433
int purify_curve_hash_to_curve(purify_jacobian_point *out, const purify_curve *curve, const unsigned char *data, size_t data_len)
Definition curve.c:778
void purify_curve_field_b(purify_fe *out)
Definition curve.c:429
int purify_curve_mul_secret_affine(purify_affine_point *out, const purify_curve *curve, const purify_jacobian_point *point, const uint64_t scalar[4])
Definition curve.c:759
void purify_curve_mul(purify_jacobian_point *out, const purify_curve *curve, const purify_jacobian_point *point, const uint64_t scalar[4])
Definition curve.c:697
void purify_curve_add(purify_jacobian_point *out, const purify_curve *curve, const purify_jacobian_point *lhs, const purify_jacobian_point *rhs)
Definition curve.c:619
void purify_curve_add_mixed(purify_jacobian_point *out, const purify_curve *curve, const purify_jacobian_point *lhs, const purify_affine_point *rhs)
Definition curve.c:566
void purify_curve_affine(purify_affine_point *out, const purify_curve *curve, const purify_jacobian_point *point)
Definition curve.c:450
void purify_curve_prime_p(uint64_t out[4])
Definition curve.c:392
int purify_curve_unpack_secret(uint64_t first[4], uint64_t second[4], const uint64_t value[8])
Definition curve.c:863
Elliptic-curve helpers, fixed parameters, and hash-to-curve utilities for Purify.
Definition api.hpp:21
Bytes hmac_sha256(const Bytes &key, const Bytes &data)
Computes an HMAC-SHA256 digest using the secp bridge implementation.
Definition curve.cpp:176
Status validate_secret_key(const UInt512 &z)
Validates the packed secret-key encoding range.
Definition curve.cpp:307
FieldElement field_di()
Returns the inverse of the twist factor in the field.
Definition curve.cpp:251
const UInt256 & half_n1()
Returns floor(order_n1 / 2).
Definition curve.cpp:214
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
const UInt256 & prime_p()
Returns the Purify base-field modulus.
Definition curve.cpp:199
Result< std::vector< int > > key_to_bits(UInt256 n, const UInt256 &max_value)
Encodes a scalar into the signed 3-bit window bit schedule used by the circuit.
Definition curve.cpp:353
Result< std::pair< UInt256, UInt256 > > unpack_public(const UInt512 &packed)
Splits a packed public key into its two x-coordinates.
Definition curve.cpp:330
const UInt256 & half_n2()
Returns floor(order_n2 / 2).
Definition curve.cpp:219
BigUInt< 8 > UInt512
512-bit unsigned integer used for private and packed public keys.
Definition numeric.hpp:802
Bytes bytes_from_ascii(std::string_view input)
Encodes an ASCII string as a byte vector.
Definition curve.cpp:163
std::uint64_t ceil_div(std::uint64_t lhs, std::uint64_t rhs)
Computes ceiling division for unsigned 64-bit values.
Definition curve.cpp:172
Bytes hkdf(std::size_t length, const Bytes &ikm, const Bytes &salt={}, const Bytes &info={})
Expands input key material using HKDF-SHA256.
Definition curve.cpp:182
const UInt256 & order_n2()
Returns the subgroup order for the second curve.
Definition curve.cpp:209
Status validate_public_key(const UInt512 &packed)
Validates the packed public-key encoding range.
Definition curve.cpp:314
bool is_valid_public_key(const UInt512 &packed)
Returns true when a packed public key is encoded canonically.
Definition curve.cpp:303
const UInt320 & two_p()
Returns 2 * prime_p() as a widened integer for hash-to-curve sampling.
Definition curve.cpp:234
FieldElement field_a()
Returns the shared Weierstrass a coefficient used by Purify.
Definition curve.cpp:239
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
const UInt512 & packed_secret_key_space_size()
Returns the size of the packed secret-key encoding space.
Definition curve.cpp:224
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
FieldElement field_d()
Returns the twist factor used to derive the second curve.
Definition curve.cpp:247
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
BigUInt< 5 > UInt320
320-bit unsigned integer used during hash-to-curve sampling.
Definition numeric.hpp:800
FieldElement field_b()
Returns the shared Weierstrass b coefficient used by Purify.
Definition curve.cpp:243
bool is_valid_secret_key(const UInt512 &z)
Returns true when a packed secret is encoded canonically.
Definition curve.cpp:299
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
const UInt256 & order_n1()
Returns the subgroup order for the first curve.
Definition curve.cpp:204
FieldElement combine(const FieldElement &x1, const FieldElement &x2)
Applies the Purify curve-combination map to two x-coordinates.
Definition curve.cpp:345
const UInt512 & packed_public_key_space_size()
Returns the size of the packed public-key encoding space.
Definition curve.cpp:229
BigUInt< 4 > UInt256
256-bit unsigned integer used for field elements and curve orders.
Definition numeric.hpp:798
Bytes operator+(Bytes lhs, const Bytes &rhs)
Concatenates two byte vectors.
Definition curve.cpp:167
Scalar32 scalar
Definition bppp.cpp:119
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.
Affine point representation used for serialization and lookup tables.
Definition curve.hpp:28
std::size_t bit_length() const
Returns the index of the highest set bit plus one.
Definition numeric.hpp:474
std::array< std::uint64_t, Words > limbs
Definition numeric.hpp:201
Jacobian point representation used for curve arithmetic.
Definition curve.hpp:17
static JacobianPoint infinity_point()
Returns the point at infinity in Jacobian coordinates.
Definition curve.cpp:78
purify_fe y
Definition curve.h:25
purify_fe x
Definition curve.h:24
purify_fe a
Definition curve.h:36
purify_scalar value
Definition field.h:17