purify
C++ Purify implementation with native circuit and BPP support
Loading...
Searching...
No Matches
numeric.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/common.hpp"
13#include "purify/uint.h"
14
15namespace purify {
16
17namespace detail {
18
19#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
20#if defined(__clang__) || defined(__GNUC__)
21#pragma GCC diagnostic push
22#pragma GCC diagnostic ignored "-Wpedantic"
23#endif
24#endif
25
26// `UInt128` intentionally uses the compiler `__int128` extension when it is
27// available; the fallback branch below remains the standards-only path.
28class UInt128 {
29public:
30 [[nodiscard]] static UInt128 from_words(std::uint64_t hi, std::uint64_t lo) {
31#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
32 return UInt128((static_cast<unsigned __int128>(hi) << 64) | lo);
33#else
34 return UInt128(hi, lo);
35#endif
36 }
37
38 [[nodiscard]] static UInt128 mul_u64(std::uint64_t lhs, std::uint64_t rhs) {
39#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
40 return UInt128(static_cast<unsigned __int128>(lhs) * rhs);
41#else
42 constexpr std::uint64_t kMask32 = 0xffffffffULL;
43
44 std::uint64_t lhs_lo = lhs & kMask32;
45 std::uint64_t lhs_hi = lhs >> 32;
46 std::uint64_t rhs_lo = rhs & kMask32;
47 std::uint64_t rhs_hi = rhs >> 32;
48
49 std::uint64_t lo_lo = lhs_lo * rhs_lo;
50 std::uint64_t hi_lo = lhs_hi * rhs_lo;
51 std::uint64_t lo_hi = lhs_lo * rhs_hi;
52 std::uint64_t hi_hi = lhs_hi * rhs_hi;
53
54 std::uint64_t cross = (lo_lo >> 32) + (hi_lo & kMask32) + (lo_hi & kMask32);
55 return UInt128(hi_hi + (hi_lo >> 32) + (lo_hi >> 32) + (cross >> 32),
56 (lo_lo & kMask32) | (cross << 32));
57#endif
58 }
59
60 [[nodiscard]] UInt128 add_u64(std::uint64_t addend) const {
61#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
62 return UInt128(value_ + addend);
63#else
64 UInt128 out = *this;
65 out.lo_ += addend;
66 out.hi_ += (out.lo_ < addend) ? 1U : 0U;
67 return out;
68#endif
69 }
70
71 [[nodiscard]] std::pair<UInt128, std::uint32_t> divmod_u32(std::uint32_t divisor) const {
72 assert(divisor != 0 && "UInt128::divmod_u32 requires a non-zero divisor");
73#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
74 unsigned __int128 quotient = value_ / divisor;
75 return std::make_pair(UInt128(quotient), static_cast<std::uint32_t>(value_ % divisor));
76#else
77 constexpr std::uint64_t kMask32 = 0xffffffffULL;
78
79 std::uint64_t rem = 0;
80 auto step = [&](std::uint32_t word) {
81 std::uint64_t cur = (rem << 32) | word;
82 std::uint32_t qword = static_cast<std::uint32_t>(cur / divisor);
83 rem = cur % divisor;
84 return qword;
85 };
86
87 // `step()` mutates `rem`, so sequence each word explicitly instead of
88 // relying on constructor-argument evaluation order, which differs across
89 // compilers and broke the MSVC fallback decimal conversion path.
90 const std::uint32_t q3 = step(static_cast<std::uint32_t>(hi_ >> 32));
91 const std::uint32_t q2 = step(static_cast<std::uint32_t>(hi_ & kMask32));
92 const std::uint32_t q1 = step(static_cast<std::uint32_t>(lo_ >> 32));
93 const std::uint32_t q0 = step(static_cast<std::uint32_t>(lo_ & kMask32));
94
95 UInt128 quotient((static_cast<std::uint64_t>(q3) << 32) | q2, (static_cast<std::uint64_t>(q1) << 32) | q0);
96 return std::make_pair(quotient, static_cast<std::uint32_t>(rem));
97#endif
98 }
99
100 [[nodiscard]] std::uint64_t low64() const {
101#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
102 return static_cast<std::uint64_t>(value_);
103#else
104 return lo_;
105#endif
106 }
107
108 [[nodiscard]] std::uint64_t high64() const {
109#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
110 return static_cast<std::uint64_t>(value_ >> 64);
111#else
112 return hi_;
113#endif
114 }
115
116private:
117#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
118 explicit UInt128(unsigned __int128 value) : value_(value) {}
119 unsigned __int128 value_ = 0;
120#else
121 UInt128(std::uint64_t hi, std::uint64_t lo) : lo_(lo), hi_(hi) {}
122 std::uint64_t lo_ = 0;
123 std::uint64_t hi_ = 0;
124#endif
125};
126
127#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER)
128#if defined(__clang__) || defined(__GNUC__)
129#pragma GCC diagnostic pop
130#endif
131#endif
132
133[[nodiscard]] inline std::size_t bit_length_u64(std::uint64_t value) {
134 return value == 0 ? 0U : 64U - static_cast<std::size_t>(std::countl_zero(value));
135}
136
137template <std::size_t Words>
139 static constexpr bool kAvailable = false;
140};
141
142#define PURIFY_DEFINE_BIGUINT_C_BRIDGE(words, bits) \
143template <> \
144struct BigUIntCBridge<words> { \
145 static constexpr bool kAvailable = true; \
146 static void set_zero(std::uint64_t* out) { purify_u##bits##_set_zero(out); } \
147 static void set_u64(std::uint64_t* out, std::uint64_t value) { purify_u##bits##_set_u64(out, value); } \
148 static void from_bytes_be(std::uint64_t* out, const unsigned char* data, std::size_t size) { \
149 purify_u##bits##_from_bytes_be(out, data, size); \
150 } \
151 static bool is_zero(const std::uint64_t* value) { return purify_u##bits##_is_zero(value) != 0; } \
152 static int compare(const std::uint64_t* lhs, const std::uint64_t* rhs) { return purify_u##bits##_compare(lhs, rhs); } \
153 static bool try_add_small(std::uint64_t* value, std::uint32_t addend) { \
154 return purify_u##bits##_try_add_small(value, addend) != 0; \
155 } \
156 static bool try_mul_small(std::uint64_t* value, std::uint32_t factor) { \
157 return purify_u##bits##_try_mul_small(value, factor) != 0; \
158 } \
159 static bool try_add(std::uint64_t* value, const std::uint64_t* addend) { \
160 return purify_u##bits##_try_add(value, addend) != 0; \
161 } \
162 static bool try_sub(std::uint64_t* value, const std::uint64_t* subtrahend) { \
163 return purify_u##bits##_try_sub(value, subtrahend) != 0; \
164 } \
165 static std::size_t bit_length(const std::uint64_t* value) { return purify_u##bits##_bit_length(value); } \
166 static bool bit(const std::uint64_t* value, std::size_t index) { return purify_u##bits##_bit(value, index) != 0; } \
167 static bool try_set_bit(std::uint64_t* value, std::size_t index) { \
168 return purify_u##bits##_try_set_bit(value, index) != 0; \
169 } \
170 static void shifted_left(std::uint64_t* out, const std::uint64_t* value, std::size_t bits_value) { \
171 purify_u##bits##_shifted_left(out, value, bits_value); \
172 } \
173 static void shifted_right(std::uint64_t* out, const std::uint64_t* value, std::size_t bits_value) { \
174 purify_u##bits##_shifted_right(out, value, bits_value); \
175 } \
176 static void shift_right_one(std::uint64_t* value) { purify_u##bits##_shift_right_one(value); } \
177 static void mask_bits(std::uint64_t* value, std::size_t bits_value) { purify_u##bits##_mask_bits(value, bits_value); } \
178 static std::uint32_t divmod_small(std::uint64_t* value, std::uint32_t divisor) { \
179 return purify_u##bits##_divmod_small(value, divisor); \
180 } \
181 static void to_bytes_be(unsigned char* out, const std::uint64_t* value) { purify_u##bits##_to_bytes_be(out, value); } \
182}
183
187
188#undef PURIFY_DEFINE_BIGUINT_C_BRIDGE
189
190struct FieldElementAccess;
191
192} // namespace detail
193
199template <std::size_t Words>
200struct BigUInt {
201 std::array<std::uint64_t, Words> limbs{};
202
204 static BigUInt zero() {
205 BigUInt out;
208 }
209 return out;
210 }
211
213 static BigUInt one() {
214 BigUInt out;
217 } else {
218 out.limbs[0] = 1;
219 }
220 return out;
221 }
222
224 static BigUInt from_u64(std::uint64_t value) {
225 BigUInt out;
228 } else {
229 out.limbs[0] = value;
230 }
231 return out;
232 }
233
235 static BigUInt from_bytes_be(const unsigned char* data, std::size_t size) {
236 BigUInt out;
239 } else {
240 for (std::size_t i = 0; i < size; ++i) {
241 out.mul_small(256);
242 out.add_small(data[i]);
243 }
244 }
245 return out;
246 }
247
249 static Result<BigUInt> try_from_hex(std::string_view hex) {
250 BigUInt out;
251 if (hex.size() >= 2 && hex[0] == '0' && (hex[1] == 'x' || hex[1] == 'X')) {
252 hex.remove_prefix(2);
253 }
254 for (char ch : hex) {
255 if (std::isspace(static_cast<unsigned char>(ch)) != 0) {
256 continue;
257 }
258 unsigned value;
259 if (ch >= '0' && ch <= '9') {
260 value = static_cast<unsigned>(ch - '0');
261 } else if (ch >= 'a' && ch <= 'f') {
262 value = static_cast<unsigned>(10 + ch - 'a');
263 } else if (ch >= 'A' && ch <= 'F') {
264 value = static_cast<unsigned>(10 + ch - 'A');
265 } else {
266 return unexpected_error(ErrorCode::InvalidHex, "BigUInt::try_from_hex:invalid_digit");
267 }
268 if (!out.try_mul_small(16) || !out.try_add_small(value)) {
269 return unexpected_error(ErrorCode::Overflow, "BigUInt::try_from_hex:overflow");
270 }
271 }
272 return out;
273 }
274
280 static BigUInt from_hex(std::string_view hex) {
281 Result<BigUInt> out = try_from_hex(hex);
282 assert(out.has_value() && "BigUInt::from_hex() requires valid in-range input");
283 return std::move(*out);
284 }
285
287 bool is_zero() const {
290 } else {
291 for (std::uint64_t limb : limbs) {
292 if (limb != 0) {
293 return false;
294 }
295 }
296 return true;
297 }
298 }
299
301 int compare(const BigUInt& other) const {
303 return detail::BigUIntCBridge<Words>::compare(limbs.data(), other.limbs.data());
304 } else {
305 for (std::size_t i = Words; i != 0; --i) {
306 const std::size_t idx = i - 1;
307 if (limbs[idx] < other.limbs[idx]) {
308 return -1;
309 }
310 if (limbs[idx] > other.limbs[idx]) {
311 return 1;
312 }
313 }
314 return 0;
315 }
316 }
317
318 bool operator==(const BigUInt& other) const {
319 return limbs == other.limbs;
320 }
321
322 bool operator!=(const BigUInt& other) const {
323 return !(*this == other);
324 }
325
326 bool operator<(const BigUInt& other) const {
327 return compare(other) < 0;
328 }
329
330 bool operator>=(const BigUInt& other) const {
331 return compare(other) >= 0;
332 }
333
335 bool try_add_small(std::uint32_t value) {
336 BigUInt out = *this;
338 if (!detail::BigUIntCBridge<Words>::try_add_small(out.limbs.data(), value)) {
339 return false;
340 }
341 } else {
342 std::uint64_t carry = value;
343 for (std::size_t i = 0; i < Words && carry != 0; ++i) {
344 std::uint64_t sum = out.limbs[i] + carry;
345 carry = (sum < out.limbs[i]) ? 1U : 0U;
346 out.limbs[i] = sum;
347 }
348 if (carry != 0) {
349 return false;
350 }
351 }
352 *this = out;
353 return true;
354 }
355
361 void add_small(std::uint32_t value) {
362 bool ok = try_add_small(value);
363 assert(ok && "BigUInt::add_small() overflow");
364 (void)ok;
365 }
366
368 bool try_mul_small(std::uint32_t value) {
369 BigUInt out = *this;
371 if (!detail::BigUIntCBridge<Words>::try_mul_small(out.limbs.data(), value)) {
372 return false;
373 }
374 } else {
375 std::uint64_t carry = 0;
376 for (std::size_t i = 0; i < Words; ++i) {
377 detail::UInt128 accum = detail::UInt128::mul_u64(out.limbs[i], value);
378 accum = accum.add_u64(carry);
379 out.limbs[i] = accum.low64();
380 carry = accum.high64();
381 }
382 if (carry != 0) {
383 return false;
384 }
385 }
386 *this = out;
387 return true;
388 }
389
395 void mul_small(std::uint32_t value) {
396 bool ok = try_mul_small(value);
397 assert(ok && "BigUInt::mul_small() overflow");
398 (void)ok;
399 }
400
402 bool try_add_assign(const BigUInt& other) {
403 BigUInt out = *this;
405 if (!detail::BigUIntCBridge<Words>::try_add(out.limbs.data(), other.limbs.data())) {
406 return false;
407 }
408 } else {
409 std::uint64_t carry = 0;
410 for (std::size_t i = 0; i < Words; ++i) {
411 std::uint64_t sum = out.limbs[i] + other.limbs[i];
412 std::uint64_t carry1 = (sum < out.limbs[i]) ? 1U : 0U;
413 std::uint64_t next = sum + carry;
414 std::uint64_t carry2 = (next < sum) ? 1U : 0U;
415 out.limbs[i] = next;
416 carry = carry1 | carry2;
417 }
418 if (carry != 0) {
419 return false;
420 }
421 }
422 *this = out;
423 return true;
424 }
425
431 void add_assign(const BigUInt& other) {
432 bool ok = try_add_assign(other);
433 assert(ok && "BigUInt::add_assign() overflow");
434 (void)ok;
435 }
436
438 bool try_sub_assign(const BigUInt& other) {
439 BigUInt out = *this;
441 if (!detail::BigUIntCBridge<Words>::try_sub(out.limbs.data(), other.limbs.data())) {
442 return false;
443 }
444 } else {
445 std::uint64_t borrow = 0;
446 for (std::size_t i = 0; i < Words; ++i) {
447 std::uint64_t rhs = other.limbs[i] + borrow;
448 std::uint64_t rhs_overflow = (rhs < other.limbs[i]) ? 1U : 0U;
449 std::uint64_t next = out.limbs[i] - rhs;
450 std::uint64_t needs_borrow = (out.limbs[i] < rhs) ? 1U : 0U;
451 out.limbs[i] = next;
452 borrow = rhs_overflow | needs_borrow;
453 }
454 if (borrow != 0) {
455 return false;
456 }
457 }
458 *this = out;
459 return true;
460 }
461
467 void sub_assign(const BigUInt& other) {
468 bool ok = try_sub_assign(other);
469 assert(ok && "BigUInt::sub_assign() underflow");
470 (void)ok;
471 }
472
474 std::size_t bit_length() const {
477 } else {
478 for (std::size_t i = Words; i != 0; --i) {
479 const std::size_t idx = i - 1;
480 if (limbs[idx] != 0) {
481 return idx * 64 + detail::bit_length_u64(limbs[idx]);
482 }
483 }
484 return 0;
485 }
486 }
487
489 bool bit(std::size_t index) const {
491 return detail::BigUIntCBridge<Words>::bit(limbs.data(), index);
492 } else {
493 std::size_t word = index / 64;
494 std::size_t shift = index % 64;
495 if (word >= Words) {
496 return false;
497 }
498 return ((limbs[word] >> shift) & 1U) != 0;
499 }
500 }
501
503 bool try_set_bit(std::size_t index) {
506 } else {
507 std::size_t word = index / 64;
508 std::size_t shift = index % 64;
509 if (word >= Words) {
510 return false;
511 }
512 limbs[word] |= (static_cast<std::uint64_t>(1) << shift);
513 return true;
514 }
515 }
516
522 void set_bit(std::size_t index) {
523 bool ok = try_set_bit(index);
524 assert(ok && "BigUInt::set_bit() index out of range");
525 (void)ok;
526 }
527
529 BigUInt shifted_left(std::size_t bits) const {
530 BigUInt out;
533 } else {
534 std::size_t word_shift = bits / 64;
535 std::size_t bit_shift = bits % 64;
536 for (std::size_t i = Words; i != 0; --i) {
537 const std::size_t idx = i - 1;
538 if (idx < word_shift) {
539 continue;
540 }
541 std::size_t src = idx - word_shift;
542 out.limbs[idx] |= limbs[src] << bit_shift;
543 if (bit_shift != 0 && src > 0) {
544 out.limbs[idx] |= limbs[src - 1] >> (64 - bit_shift);
545 }
546 }
547 }
548 return out;
549 }
550
552 BigUInt shifted_right(std::size_t bits) const {
553 BigUInt out;
556 } else {
557 std::size_t word_shift = bits / 64;
558 std::size_t bit_shift = bits % 64;
559 for (std::size_t i = 0; i < Words; ++i) {
560 std::size_t src = i + word_shift;
561 if (src >= Words) {
562 break;
563 }
564 out.limbs[i] |= limbs[src] >> bit_shift;
565 if (bit_shift != 0 && src + 1 < Words) {
566 out.limbs[i] |= limbs[src + 1] << (64 - bit_shift);
567 }
568 }
569 }
570 return out;
571 }
572
577 } else {
578 for (std::size_t i = 0; i < Words; ++i) {
579 std::uint64_t next = (i + 1 < Words) ? limbs[i + 1] : 0;
580 limbs[i] = (limbs[i] >> 1) | (next << 63);
581 }
582 }
583 }
584
586 void mask_bits(std::size_t bits) {
589 } else {
590 std::size_t full_words = bits / 64;
591 std::size_t extra_bits = bits % 64;
592 for (std::size_t i = full_words + (extra_bits != 0 ? 1 : 0); i < Words; ++i) {
593 limbs[i] = 0;
594 }
595 if (extra_bits != 0 && full_words < Words) {
596 std::uint64_t mask =
597 (extra_bits == 64) ? ~static_cast<std::uint64_t>(0) : ((static_cast<std::uint64_t>(1) << extra_bits) - 1);
598 limbs[full_words] &= mask;
599 }
600 }
601 }
602
604 std::uint32_t divmod_small(std::uint32_t divisor) {
607 } else {
608 std::uint64_t rem = 0;
609 for (std::size_t i = Words; i != 0; --i) {
610 const std::size_t idx = i - 1;
611 auto [quotient, next_rem] = detail::UInt128::from_words(rem, limbs[idx]).divmod_u32(divisor);
612 assert(quotient.high64() == 0 && "BigUInt::divmod_small() quotient must fit in 64 bits");
613 limbs[idx] = quotient.low64();
614 rem = next_rem;
615 }
616 return static_cast<std::uint32_t>(rem);
617 }
618 }
619
621 std::array<unsigned char, Words * 8> to_bytes_be() const {
622 std::array<unsigned char, Words * 8> out{};
625 } else {
626 for (std::size_t i = 0; i < Words; ++i) {
627 std::uint64_t limb = limbs[i];
628 for (std::size_t j = 0; j < 8; ++j) {
629 out[out.size() - 1 - (i * 8 + j)] = static_cast<unsigned char>(limb & 0xffU);
630 limb >>= 8;
631 }
632 }
633 }
634 return out;
635 }
636
638 std::string to_hex() const {
639 auto bytes = to_bytes_be();
640 std::ostringstream out;
641 bool started = false;
642 out << std::hex << std::nouppercase;
643 for (unsigned char byte : bytes) {
644 if (!started) {
645 if (byte == 0) {
646 continue;
647 }
648 out << static_cast<unsigned>(byte);
649 started = true;
650 } else {
651 out << std::setw(2) << std::setfill('0') << static_cast<unsigned>(byte);
652 }
653 }
654 return started ? out.str() : "0";
655 }
656
658 std::string to_decimal() const {
659 if (is_zero()) {
660 return "0";
661 }
662 BigUInt copy = *this;
663 std::vector<std::uint32_t> parts;
664 while (!copy.is_zero()) {
665 parts.push_back(copy.divmod_small(1000000000U));
666 }
667 std::ostringstream out;
668 out << parts.back();
669 for (std::size_t i = parts.size(); i > 1; --i) {
670 out << std::setw(9) << std::setfill('0') << parts[i - 2];
671 }
672 return out.str();
673 }
674};
675
677template <std::size_t OutWords, std::size_t InWords>
679 static_assert(OutWords >= InWords, "Cannot narrow with widen");
681 if constexpr (OutWords == 5 && InWords == 4) {
682 purify_u320_widen_u256(out.limbs.data(), value.limbs.data());
683 } else if constexpr (OutWords == 8 && InWords == 4) {
684 purify_u512_widen_u256(out.limbs.data(), value.limbs.data());
685 } else {
686 for (std::size_t i = 0; i < InWords; ++i) {
687 out.limbs[i] = value.limbs[i];
688 }
689 }
690 return out;
691}
692
694template <std::size_t OutWords, std::size_t InWords>
696 static_assert(OutWords <= InWords, "Cannot widen with narrow");
698 if constexpr (OutWords == 4 && InWords == 5) {
699 if (!purify_u256_try_narrow_u320(out.limbs.data(), value.limbs.data())) {
700 return unexpected_error(ErrorCode::NarrowingOverflow, "try_narrow:high_bits_set");
701 }
702 } else if constexpr (OutWords == 4 && InWords == 8) {
703 if (!purify_u256_try_narrow_u512(out.limbs.data(), value.limbs.data())) {
704 return unexpected_error(ErrorCode::NarrowingOverflow, "try_narrow:high_bits_set");
705 }
706 } else {
707 for (std::size_t i = 0; i < OutWords; ++i) {
708 out.limbs[i] = value.limbs[i];
709 }
710 for (std::size_t i = OutWords; i < InWords; ++i) {
711 if (value.limbs[i] != 0) {
712 return unexpected_error(ErrorCode::NarrowingOverflow, "try_narrow:high_bits_set");
713 }
714 }
715 }
716 return out;
717}
718
720template <std::size_t OutWords, std::size_t InWords>
722 Result<BigUInt<OutWords>> out = try_narrow<OutWords>(value);
723 assert(out.has_value() && "narrow() requires the source value to fit");
724 return std::move(*out);
725}
726
728template <std::size_t Words>
730 BigUInt<Words> quotient;
731 BigUInt<Words> remainder = numerator;
732 if constexpr (Words == 8) {
733 if (!purify_u512_try_divmod_same(quotient.limbs.data(), remainder.limbs.data(),
734 numerator.limbs.data(), denominator.limbs.data())) {
735 return unexpected_error(ErrorCode::DivisionByZero, "try_divmod_same:zero_denominator");
736 }
737 } else {
738 if (denominator.is_zero()) {
739 return unexpected_error(ErrorCode::DivisionByZero, "try_divmod_same:zero_denominator");
740 }
741 std::size_t n_bits = remainder.bit_length();
742 std::size_t d_bits = denominator.bit_length();
743 if (n_bits < d_bits) {
744 return std::make_pair(quotient, remainder);
745 }
746 std::size_t shift = n_bits - d_bits;
747 BigUInt<Words> shifted = denominator.shifted_left(shift);
748 for (std::size_t i = shift + 1; i != 0; --i) {
749 const std::size_t idx = i - 1;
750 if (remainder.compare(shifted) >= 0) {
751 bool sub_ok = remainder.try_sub_assign(shifted);
752 bool bit_ok = quotient.try_set_bit(idx);
753 assert(sub_ok && "divmod_same() subtraction should stay in range");
754 assert(bit_ok && "divmod_same() quotient bit index should stay in range");
755 if (!sub_ok || !bit_ok) {
756 return unexpected_error(ErrorCode::InternalMismatch, "try_divmod_same:internal_step");
757 }
758 }
759 if (idx != 0) {
760 shifted.shift_right_one();
761 }
762 }
763 }
764 return std::make_pair(quotient, remainder);
765}
766
768template <std::size_t Words>
769std::pair<BigUInt<Words>, BigUInt<Words>> divmod_same(const BigUInt<Words>& numerator, const BigUInt<Words>& denominator) {
770 Result<std::pair<BigUInt<Words>, BigUInt<Words>>> out = try_divmod_same(numerator, denominator);
771 assert(out.has_value() && "divmod_same() requires a non-zero denominator");
772 return std::move(*out);
773}
774
776template <std::size_t LeftWords, std::size_t RightWords>
779 if constexpr (LeftWords == 4 && RightWords == 4) {
780 purify_u512_multiply_u256(out.limbs.data(), lhs.limbs.data(), rhs.limbs.data());
781 } else {
782 for (std::size_t i = 0; i < LeftWords; ++i) {
783 std::uint64_t carry = 0;
784 for (std::size_t j = 0; j < RightWords; ++j) {
785 detail::UInt128 accum = detail::UInt128::mul_u64(lhs.limbs[i], rhs.limbs[j]);
786 accum = accum.add_u64(out.limbs[i + j]);
787 accum = accum.add_u64(carry);
788 out.limbs[i + j] = accum.low64();
789 carry = accum.high64();
790 }
791 out.limbs[i + RightWords] += carry;
792 }
793 }
794 return out;
795}
796
803
804class FieldElement;
805
807const UInt256& prime_p();
808
816public:
817 FieldElement();
818
820 static FieldElement zero();
821
823 static FieldElement one();
824
826 static FieldElement from_u64(std::uint64_t value);
827
829 static FieldElement from_int(std::int64_t value);
830
832 static Result<FieldElement> try_from_bytes32(const std::array<unsigned char, 32>& bytes);
833
839 static FieldElement from_bytes32(const std::array<unsigned char, 32>& bytes);
840
842 static Result<FieldElement> try_from_uint256(const UInt256& value);
843
849 static FieldElement from_uint256(const UInt256& value);
850
852 UInt256 to_uint256() const;
853
855 std::array<unsigned char, 32> to_bytes_be() const;
856
858 std::array<unsigned char, 32> to_bytes_le() const;
859
861 std::string to_hex() const;
862
864 std::string to_decimal() const;
865
867 bool is_zero() const;
868
870 bool is_one() const;
871
873 bool is_odd() const;
874
876 bool is_square() const;
877
879 FieldElement negate() const;
880
882 void conditional_assign(const FieldElement& other, bool flag);
883
886
888 FieldElement inverse() const;
889
891 std::optional<FieldElement> sqrt() const;
892
894 FieldElement pow(const UInt256& exponent) const;
895
897 friend bool operator==(const FieldElement& lhs, const FieldElement& rhs);
898
900 friend bool operator!=(const FieldElement& lhs, const FieldElement& rhs);
901
903 friend FieldElement operator+(const FieldElement& lhs, const FieldElement& rhs);
904
906 friend FieldElement operator-(const FieldElement& lhs, const FieldElement& rhs);
907
909 friend FieldElement operator*(const FieldElement& lhs, const FieldElement& rhs);
910
911private:
913 explicit FieldElement(const purify_scalar& raw) : value_(raw) {}
914
915 purify_scalar value_{};
916};
917
918namespace detail {
919
920struct FieldElementAccess {
921 static const purify_scalar& raw(const FieldElement& value) noexcept {
922 return value.value_;
923 }
924
925 static FieldElement from_raw(const purify_scalar& raw) noexcept {
926 return FieldElement(raw);
927 }
928};
929
930} // namespace detail
931
933FieldElement square(const FieldElement& value);
934
936int legendre_symbol(const FieldElement& value);
937
938} // namespace 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
Field element modulo the backend scalar field used by this implementation.
Definition numeric.hpp:815
bool is_square() const
Returns true when the element is a quadratic residue in the field.
Definition numeric.cpp:116
bool is_one() const
Returns true when the element is one.
Definition numeric.cpp:108
std::array< unsigned char, 32 > to_bytes_le() const
Serializes the field element in little-endian form.
Definition numeric.cpp:90
static FieldElement from_bytes32(const std::array< unsigned char, 32 > &bytes)
Decodes a 32-byte big-endian field element.
Definition numeric.cpp:63
static FieldElement from_uint256(const UInt256 &value)
Converts a 256-bit unsigned integer into the scalar field representation.
Definition numeric.cpp:73
friend bool operator!=(const FieldElement &lhs, const FieldElement &rhs)
Compares two field elements for inequality.
Definition numeric.cpp:163
FieldElement inverse() const
Returns the multiplicative inverse modulo the field prime using the faster variable-time backend.
Definition numeric.cpp:137
friend FieldElement operator+(const FieldElement &lhs, const FieldElement &rhs)
Adds two field elements modulo the field prime.
Definition numeric.cpp:167
FieldElement pow(const UInt256 &exponent) const
Raises the element to an unsigned exponent via square-and-multiply.
Definition numeric.cpp:152
bool is_odd() const
Returns true when the canonical representative is odd.
Definition numeric.cpp:112
static FieldElement from_u64(std::uint64_t value)
Constructs a field element from an unsigned 64-bit integer.
Definition numeric.cpp:40
std::string to_hex() const
Formats the field element as lowercase hexadecimal.
Definition numeric.cpp:96
static Result< FieldElement > try_from_uint256(const UInt256 &value)
Converts a canonical 256-bit unsigned integer into the scalar field representation.
Definition numeric.cpp:69
std::optional< FieldElement > sqrt() const
Computes a square root when one exists, otherwise returns std::nullopt.
Definition numeric.cpp:143
friend bool operator==(const FieldElement &lhs, const FieldElement &rhs)
Compares two field elements for exact equality.
Definition numeric.cpp:159
std::string to_decimal() const
Formats the field element as an unsigned decimal string.
Definition numeric.cpp:100
static FieldElement one()
Returns the multiplicative identity of the scalar field.
Definition numeric.cpp:36
void conditional_assign(const FieldElement &other, bool flag)
Conditionally assigns other into *this when flag is true.
Definition numeric.cpp:127
static Result< FieldElement > try_from_bytes32(const std::array< unsigned char, 32 > &bytes)
Decodes a canonical 32-byte big-endian field element.
Definition numeric.cpp:53
FieldElement negate() const
Returns the additive inverse modulo the field prime.
Definition numeric.cpp:121
static FieldElement from_int(std::int64_t value)
Constructs a field element from a signed integer, reducing negatives modulo the field.
Definition numeric.cpp:46
UInt256 to_uint256() const
Exports the field element as a canonical 256-bit unsigned integer.
Definition numeric.cpp:79
friend struct detail::FieldElementAccess
Definition numeric.hpp:912
bool is_zero() const
Returns true when the element is zero.
Definition numeric.cpp:104
static FieldElement zero()
Returns the additive identity of the scalar field.
Definition numeric.cpp:32
friend FieldElement operator-(const FieldElement &lhs, const FieldElement &rhs)
Subtracts two field elements modulo the field prime.
Definition numeric.cpp:173
std::array< unsigned char, 32 > to_bytes_be() const
Serializes the field element in big-endian form.
Definition numeric.cpp:84
friend FieldElement operator*(const FieldElement &lhs, const FieldElement &rhs)
Multiplies two field elements modulo the field prime.
Definition numeric.cpp:177
FieldElement inverse_consttime() const
Returns the multiplicative inverse modulo the field prime in constant time.
Definition numeric.cpp:131
static UInt128 mul_u64(std::uint64_t lhs, std::uint64_t rhs)
Definition numeric.hpp:38
std::pair< UInt128, std::uint32_t > divmod_u32(std::uint32_t divisor) const
Definition numeric.hpp:71
std::uint64_t low64() const
Definition numeric.hpp:100
std::uint64_t high64() const
Definition numeric.hpp:108
static UInt128 from_words(std::uint64_t hi, std::uint64_t lo)
Definition numeric.hpp:30
UInt128 add_u64(std::uint64_t addend) const
Definition numeric.hpp:60
Shared includes and foundational aliases for the Purify C++ implementation.
std::size_t bit_length_u64(std::uint64_t value)
Definition numeric.hpp:133
Definition api.hpp:21
BigUInt< LeftWords+RightWords > multiply(const BigUInt< LeftWords > &lhs, const BigUInt< RightWords > &rhs)
Multiplies two fixed-width integers and returns the full-width product.
Definition numeric.hpp:777
BigUInt< OutWords > widen(const BigUInt< InWords > &value)
Widens an integer to a larger limb count by zero-extending high limbs.
Definition numeric.hpp:678
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::pair< BigUInt< Words >, BigUInt< Words > > > try_divmod_same(const BigUInt< Words > &numerator, const BigUInt< Words > &denominator)
Performs long division where numerator and denominator have the same width.
Definition numeric.hpp:729
BigUInt< OutWords > narrow(const BigUInt< InWords > &value)
Narrows an integer to a smaller limb count, requiring that no high bits are lost.
Definition numeric.hpp:721
FieldElement square(const FieldElement &value)
Squares a field element.
Definition numeric.cpp:183
Result< BigUInt< OutWords > > try_narrow(const BigUInt< InWords > &value)
Narrows an integer to a smaller limb count, rejecting truncated high bits.
Definition numeric.hpp:695
std::pair< BigUInt< Words >, BigUInt< Words > > divmod_same(const BigUInt< Words > &numerator, const BigUInt< Words > &denominator)
Performs long division where numerator and denominator have the same width.
Definition numeric.hpp:769
int legendre_symbol(const FieldElement &value)
Returns 0 for zero, 1 for quadratic residues, and -1 for non-residues.
Definition numeric.cpp:190
#define PURIFY_DEFINE_BIGUINT_C_BRIDGE(words, bits)
Definition numeric.hpp:142
Little-endian fixed-width unsigned integer with simple arithmetic utilities.
Definition numeric.hpp:200
std::size_t bit_length() const
Returns the index of the highest set bit plus one.
Definition numeric.hpp:474
bool bit(std::size_t index) const
Returns the bit at the given little-endian bit index.
Definition numeric.hpp:489
static BigUInt zero()
Returns the additive identity.
Definition numeric.hpp:204
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
std::array< std::uint64_t, Words > limbs
Definition numeric.hpp:201
std::uint32_t divmod_small(std::uint32_t divisor)
Divides by a small unsigned value in place and returns the remainder.
Definition numeric.hpp:604
bool is_zero() const
Returns true when all limbs are zero.
Definition numeric.hpp:287
bool try_sub_assign(const BigUInt &other)
Subtracts another fixed-width integer when the minuend is large enough.
Definition numeric.hpp:438
bool try_mul_small(std::uint32_t value)
Multiplies by a small unsigned value in place when the result fits.
Definition numeric.hpp:368
void sub_assign(const BigUInt &other)
Subtracts another fixed-width integer in place.
Definition numeric.hpp:467
static BigUInt from_u64(std::uint64_t value)
Constructs a value from a single 64-bit limb.
Definition numeric.hpp:224
int compare(const BigUInt &other) const
Compares two fixed-width integers using unsigned ordering.
Definition numeric.hpp:301
void add_small(std::uint32_t value)
Adds a small unsigned value in place.
Definition numeric.hpp:361
bool operator==(const BigUInt &other) const
Definition numeric.hpp:318
void mul_small(std::uint32_t value)
Multiplies by a small unsigned value in place.
Definition numeric.hpp:395
void set_bit(std::size_t index)
Sets the bit at the given little-endian bit index.
Definition numeric.hpp:522
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
bool try_set_bit(std::size_t index)
Sets the bit at the given little-endian bit index when it is in range.
Definition numeric.hpp:503
bool operator!=(const BigUInt &other) const
Definition numeric.hpp:322
bool try_add_assign(const BigUInt &other)
Adds another fixed-width integer when the result fits.
Definition numeric.hpp:402
static Result< BigUInt > try_from_hex(std::string_view hex)
Parses a hexadecimal string, ignoring optional 0x and whitespace.
Definition numeric.hpp:249
BigUInt shifted_right(std::size_t bits) const
Returns a copy shifted right by the requested bit count.
Definition numeric.hpp:552
bool operator>=(const BigUInt &other) const
Definition numeric.hpp:330
void shift_right_one()
Shifts the value right by one bit in place.
Definition numeric.hpp:574
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
bool operator<(const BigUInt &other) const
Definition numeric.hpp:326
void mask_bits(std::size_t bits)
Clears all bits above the requested width.
Definition numeric.hpp:586
BigUInt shifted_left(std::size_t bits) const
Returns a copy shifted left by the requested bit count.
Definition numeric.hpp:529
std::string to_decimal() const
Formats the value as an unsigned decimal string.
Definition numeric.hpp:658
void add_assign(const BigUInt &other)
Adds another fixed-width integer in place.
Definition numeric.hpp:431
std::string to_hex() const
Formats the value as lowercase hexadecimal without leading zero padding.
Definition numeric.hpp:638
bool try_add_small(std::uint32_t value)
Adds a small unsigned value in place when the result fits.
Definition numeric.hpp:335
static constexpr bool kAvailable
Definition numeric.hpp:139
Opaque scalar storage compatible with secp256k1-zkp internal scalar storage.
Definition secp_bridge.h:22
Fixed-width unsigned integer helpers implemented in C for Purify.
int purify_u256_try_narrow_u512(uint64_t out[4], const uint64_t value[8])
Definition uint.c:183
void purify_u512_widen_u256(uint64_t out[8], const uint64_t value[4])
Definition uint.c:170
void purify_u320_widen_u256(uint64_t out[5], const uint64_t value[4])
Definition uint.c:165
void purify_u512_multiply_u256(uint64_t out[8], const uint64_t lhs[4], const uint64_t rhs[4])
Definition uint.c:263
int purify_u512_try_divmod_same(uint64_t quotient[8], uint64_t remainder[8], const uint64_t numerator[8], const uint64_t denominator[8])
Definition uint.c:194
int purify_u256_try_narrow_u320(uint64_t out[4], const uint64_t value[5])
Definition uint.c:175