purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
curve.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 "purify/numeric.hpp"
13
14namespace purify {
15
26
33
40
47public:
50
52 const UInt256& order() const {
53 return n_;
54 }
55
57 AffinePoint affine(const JacobianPoint& point) const;
58
60 JacobianPoint negate(const JacobianPoint& point) const;
61
63 bool is_x_coord(const FieldElement& x) const;
64
66 std::optional<JacobianPoint> lift_x(const FieldElement& x) const;
67
69 JacobianPoint double_point(const JacobianPoint& point) const;
70
72 JacobianPoint add_mixed(const JacobianPoint& lhs, const AffinePoint& rhs) const;
73
75 JacobianPoint add(const JacobianPoint& lhs, const JacobianPoint& rhs) const;
76
78 JacobianPoint mul(const JacobianPoint& point, const UInt256& scalar) const;
79
88
92};
93
95Bytes bytes_from_ascii(std::string_view input);
96
98Bytes operator+(Bytes lhs, const Bytes& rhs);
99
101std::uint64_t ceil_div(std::uint64_t lhs, std::uint64_t rhs);
102
104Bytes hmac_sha256(const Bytes& key, const Bytes& data);
105
112public:
113 explicit TaggedHash(std::string_view tag) {
114 purify_sha256(tag_hash_.data(), reinterpret_cast<const unsigned char*>(tag.data()), tag.size());
115 }
116
117 [[nodiscard]] std::array<unsigned char, 32> digest(std::span<const unsigned char> data) const {
118 return digest_many<1>({data});
119 }
120
121 template <std::size_t Segments>
122 [[nodiscard]] std::array<unsigned char, 32>
123 digest_many(const std::array<std::span<const unsigned char>, Segments>& segments) const {
124 std::array<unsigned char, 32> out{};
125 std::array<const unsigned char*, Segments + 2> items{};
126 std::array<size_t, Segments + 2> item_lens{};
127 items[0] = tag_hash_.data();
128 item_lens[0] = tag_hash_.size();
129 items[1] = tag_hash_.data();
130 item_lens[1] = tag_hash_.size();
131 for (std::size_t i = 0; i < Segments; ++i) {
132 items[i + 2] = segments[i].empty() ? nullptr : segments[i].data();
133 item_lens[i + 2] = segments[i].size();
134 }
135 int ok = purify_sha256_many(out.data(), items.data(), item_lens.data(), items.size());
136 assert(ok != 0 && "TaggedHash::digest_many() must accept well-formed segments");
137 (void)ok;
138 return out;
139 }
140
141private:
142 std::array<unsigned char, 32> tag_hash_{};
143};
144
146Bytes hkdf(std::size_t length, const Bytes& ikm, const Bytes& salt = {}, const Bytes& info = {});
147
149template <std::size_t Words>
150std::optional<BigUInt<Words>> hash_to_int(const Bytes& data, const BigUInt<Words>& range, const Bytes& info = {}) {
151 std::size_t bits = range.bit_length();
152 for (int i = 0; i < 256; ++i) {
153 Bytes salt{static_cast<unsigned char>(i)};
154 Bytes derived = hkdf((bits + 7) / 8, data, salt, info);
155 BigUInt<Words> value = BigUInt<Words>::from_bytes_be(derived.data(), derived.size());
156 value.mask_bits(bits);
157 if (value.compare(range) < 0) {
158 return value;
159 }
160 }
161 return std::nullopt;
162}
163
165template <std::size_t Words>
166std::optional<BigUInt<Words>> tagged_hash_to_int(std::span<const unsigned char> data, const BigUInt<Words>& range,
167 const TaggedHash& tag, std::span<const unsigned char> info = {}) {
168 std::size_t bits = range.bit_length();
169 std::size_t bytes_needed = (bits + 7) / 8;
170 for (std::uint64_t attempt = 0; attempt < 256; ++attempt) {
171 Bytes derived;
172 derived.reserve(bytes_needed);
173 std::array<unsigned char, 8> attempt_bytes{};
174 for (std::size_t i = 0; i < attempt_bytes.size(); ++i) {
175 attempt_bytes[attempt_bytes.size() - 1 - i] = static_cast<unsigned char>((attempt >> (8 * i)) & 0xffU);
176 }
177 for (std::uint64_t block = 0; derived.size() < bytes_needed; ++block) {
178 std::array<unsigned char, 8> block_bytes{};
179 for (std::size_t i = 0; i < block_bytes.size(); ++i) {
180 block_bytes[block_bytes.size() - 1 - i] = static_cast<unsigned char>((block >> (8 * i)) & 0xffU);
181 }
182 std::array digest_segments{
183 data,
184 info,
185 std::span<const unsigned char>(attempt_bytes.data(), attempt_bytes.size()),
186 std::span<const unsigned char>(block_bytes.data(), block_bytes.size()),
187 };
188 std::array<unsigned char, 32> digest = tag.digest_many(digest_segments);
189 std::size_t copy_len = std::min<std::size_t>(digest.size(), bytes_needed - derived.size());
190 derived.insert(derived.end(), digest.begin(), digest.begin() + static_cast<std::ptrdiff_t>(copy_len));
191 }
192 BigUInt<Words> value = BigUInt<Words>::from_bytes_be(derived.data(), derived.size());
193 value.mask_bits(bits);
194 if (value.compare(range) < 0) {
195 return value;
196 }
197 }
198 return std::nullopt;
199}
200
202const UInt256& order_n1();
203
205const UInt256& order_n2();
206
208const UInt256& half_n1();
209
211const UInt256& half_n2();
212
215
218
220const UInt320& two_p();
221
223FieldElement field_a();
224
226FieldElement field_b();
227
229FieldElement field_d();
230
232FieldElement field_di();
233
235const EllipticCurve& curve1();
236
238const EllipticCurve& curve2();
239
241Result<JacobianPoint> hash_to_curve(const Bytes& data, const EllipticCurve& curve);
242
244const JacobianPoint& generator1();
245
247const JacobianPoint& generator2();
248
250bool is_valid_secret_key(const UInt512& z);
251
253bool is_valid_public_key(const UInt512& packed);
254
257
259Status validate_public_key(const UInt512& packed);
260
262Result<std::pair<UInt256, UInt256>> unpack_secret(const UInt512& z);
263
265Result<std::pair<UInt256, UInt256>> unpack_public(const UInt512& packed);
266
268UInt512 pack_public(const UInt256& x1, const UInt256& x2);
269
271FieldElement combine(const FieldElement& x1, const FieldElement& x2);
272
274Result<std::vector<int>> key_to_bits(UInt256 n, const UInt256& max_value);
275
276} // 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
JacobianPoint add(const JacobianPoint &lhs, const JacobianPoint &rhs) const
Adds two Jacobian points.
Definition curve.cpp:135
const UInt256 & order() const
Returns the subgroup order used for scalar multiplication checks.
Definition curve.hpp:52
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
Field element modulo the backend scalar field used by this implementation.
Definition numeric.hpp:815
Reusable BIP340-style tagged SHA-256 helper.
Definition curve.hpp:111
TaggedHash(std::string_view tag)
Definition curve.hpp:113
std::array< unsigned char, 32 > digest_many(const std::array< std::span< const unsigned char >, Segments > &segments) const
Definition curve.hpp:123
std::array< unsigned char, 32 > digest(std::span< const unsigned char > data) const
Definition curve.hpp:117
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
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
std::optional< BigUInt< Words > > tagged_hash_to_int(std::span< const unsigned char > data, const BigUInt< Words > &range, const TaggedHash &tag, std::span< const unsigned char > info={})
Rejection-samples a uniformly distributed integer below range using repeated tagged hashes.
Definition curve.hpp:166
const UInt512 & packed_public_key_space_size()
Returns the size of the packed public-key encoding space.
Definition curve.cpp:229
std::optional< BigUInt< Words > > hash_to_int(const Bytes &data, const BigUInt< Words > &range, const Bytes &info={})
Rejection-samples a uniformly distributed integer below range.
Definition curve.hpp:150
BigUInt< 4 > UInt256
256-bit unsigned integer used for field elements and curve orders.
Definition numeric.hpp:798
Expected< void, Error > Status
Expected-returning convenience alias for Purify status-only APIs.
Definition error.hpp:102
Bytes operator+(Bytes lhs, const Bytes &rhs)
Concatenates two byte vectors.
Definition curve.cpp:167
Fixed-width integer and field arithmetic helpers used throughout Purify.
Scalar32 scalar
Definition bppp.cpp:119
int purify_sha256_many(unsigned char output32[32], const unsigned char *const *items, const size_t *item_lens, size_t items_count)
Computes SHA-256 over a set of byte strings.
void purify_sha256(unsigned char output32[32], const unsigned char *data, size_t data_len)
Computes SHA-256 over a byte string.
Affine point representation used for serialization and lookup tables.
Definition curve.hpp:28
FieldElement y
Definition curve.hpp:30
FieldElement x
Definition curve.hpp:29
std::size_t bit_length() const
Returns the index of the highest set bit plus one.
Definition numeric.hpp:474
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
Projective point used by the hardened secret-scalar multiplication path.
Definition curve.hpp:35
Jacobian point representation used for curve arithmetic.
Definition curve.hpp:17
FieldElement y
Definition curve.hpp:19
FieldElement z
Definition curve.hpp:20
FieldElement x
Definition curve.hpp:18
static JacobianPoint infinity_point()
Returns the point at infinity in Jacobian coordinates.
Definition curve.cpp:78