Longfellow ZK 0290cb32
Loading...
Searching...
No Matches
nat.h
1// Copyright 2025 Google LLC.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#ifndef PRIVACY_PROOFS_ZK_LIB_ALGEBRA_NAT_H_
16#define PRIVACY_PROOFS_ZK_LIB_ALGEBRA_NAT_H_
17
18#include <array>
19#include <cctype>
20#include <cstddef>
21#include <cstdint>
22#include <optional>
23
24#include "algebra/limb.h"
25#include "algebra/static_string.h"
26#include "algebra/sysdep.h"
27#include "util/panic.h"
28
29namespace proofs {
30
31// return a^-1 mod 2^L where L is the number of bits in limb_t
32template <class limb_t>
33static limb_t inv_mod_b(limb_t a) {
34 // Let v=1-a. We have 1/a=1/(1-v)=1+v+v^2+..., or
35 // 1/a=(1+v)(1+v^2)(1+v^4)... At some point v^(2^k) becomes 0 mod
36 // 2^L because v is even.
37
38 // A more complicated variant of this idea appears in Dumas,
39 // J.G. "On Newton–Raphson Iteration for Multiplicative Inverses
40 // Modulo Prime Powers", Algorithm 3, where they use v'=a-1
41 // instead of v=1-a, and so the first term needs to be handled
42 // separately as 2-a instead of 1+v, breaking the uniformity of
43 // the algorithm. The sign difference disappears after the first
44 // squaring.
45 check((a & 1) != 0, "even A in inv_mod_b()");
46
47 limb_t v = 1u - a;
48 limb_t u = 1u;
49 while (v != 0) {
50 u *= (1u + v);
51 v *= v;
52 }
53 return u;
54}
55
56// This function should only be called on static input known at compile time.
57unsigned digit(char c);
58
59template <size_t W64>
60class Nat : public Limb<W64> {
61 public:
62 using Super = Limb<W64>;
63 using T = Nat<W64>;
64 using limb_t = typename Super::limb_t;
65 using Super::kLimbs;
66 using Super::kU64;
67 using Super::limb_;
68
69 // Maximum length for an untrusted string, 2^64 ~ 20 decimal digits.
70 static constexpr size_t kMaxStringLen = 20 * W64 + 1;
71
72 Nat() = default; // uninitialized
73 explicit Nat(uint64_t x) : Super(x) {}
74
75 explicit Nat(const std::array<uint64_t, kU64>& a) : Super(a) {}
76
77 // Pre-condition: the caller of this function must check that the string
78 // s is either a valid base-10 or base-16 representation of a natural number
79 // that does not overflow the representation.
80 // In our current implementation, this method is only used on static strings.
81 explicit Nat(const StaticString& ss) : Super(0) {
82 limb_t base = 10u;
83 const char* s = ss.as_pointer;
84 if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
85 s += 2;
86 base = 16u;
87 }
88 for (; *s; s++) {
89 T d(digit(*s));
90 bool ok = muls(limb_, base);
91 check(ok, "overflow in nat(const char *s)");
92 limb_t ah = add_limb(kLimbs, limb_, d.limb_);
93 check(ah == 0, "overflow in nat(const char *s)");
94 }
95 }
96
97 template <size_t LEN>
98 explicit Nat(const char (&p)[LEN]) : Nat(StaticString(p)) {}
99
100 // Interpret A[] as a little-endian nat
101 static T of_bytes(const uint8_t a[/* kBytes */]) {
102 T r;
103 for (size_t i = 0; i < kLimbs; ++i) {
104 a = Super::of_bytes(&r.limb_[i], a);
105 }
106 return r;
107 }
108
109 static std::optional<unsigned> safe_digit(char c, limb_t base) {
110 c = tolower(c);
111 if (c >= '0' && c <= '9') {
112 return c - '0';
113 } else if (base == 16u && c >= 'a' && c <= 'f') {
114 return c - 'a' + 10;
115 }
116 return std::nullopt;
117 }
118
119 static std::optional<T> of_untrusted_string(const char* s) {
120 T r(0);
121 limb_t base = 10u;
122 if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
123 s += 2;
124 base = 16u;
125 }
126 const char* p = s;
127 for (size_t len = 0; len < kMaxStringLen && *p; ++len, ++p) {
128 auto d = safe_digit(*p, base);
129 if (!d.has_value()) {
130 return std::nullopt;
131 }
132 T td(d.value());
133 if (!muls(r.limb_, base)) {
134 return std::nullopt;
135 }
136 limb_t ah = add_limb(kLimbs, r.limb_, td.limb_);
137 if (ah != 0) {
138 return std::nullopt;
139 }
140 }
141 // If the loop terminates due to the length limit, then the string is not
142 // a valid base-10 or base-16 representation of a natural number.
143 if (*p) {
144 return std::nullopt;
145 }
146 return r;
147 }
148
149 bool operator<(const T& y) const {
150 T b = *this;
151 limb_t bh = sub_limb(kLimbs, b.limb_, y.limb_);
152 return (bh != 0);
153 }
154
155 T& add(const T& y) {
156 (void)add_limb(kLimbs, limb_, y.limb_);
157 return *this;
158 }
159 T& sub(const T& y) {
160 (void)sub_limb(kLimbs, limb_, y.limb_);
161 return *this;
162 }
163
164 private:
165 // b *= a, returns false if overflow occurred.
166 static bool muls(limb_t b[kLimbs], limb_t a) {
167 limb_t h[kLimbs];
168 mulhl(kLimbs, b, h, a, b);
169 limb_t bh = addh(kLimbs, b, h);
170 return bh == 0;
171 }
172};
173
174} // namespace proofs
175
176#endif // PRIVACY_PROOFS_ZK_LIB_ALGEBRA_NAT_H_
Definition static_string.h:22