authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-11-14 23:54:50+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-11-17 17:07:32-08:00
log9c2b014ea82fdfbb4b46ce789898623be450cca5
treeff302856c34987cb8833d6324b76bbe5e1e039dd
parent0d9c474ecfaf58698774022211d06da97d2fd648

std/crypto: use NAF for multi-scalar edwards25519 multiplication

Transforming scalars to non-adjacent form shrinks the number of precomputations down to 8, while still processing 4 bits at a time. However, real-world benchmarks show that the transform is only really useful with large precomputation tables and for batch signature verification. So, do it for batch verification only.

1 files changed, 85 insertions(+), 36 deletions(-)

lib/std/crypto/25519/edwards25519.zig+85-36
...@@ -144,98 +144,147 @@ pub const Edwards25519 = struct {...@@ -144,98 +144,147 @@ pub const Edwards25519 = struct {
144 p.t.cMov(a.t, c);144 p.t.cMov(a.t, c);
145 }145 }
146146
147 inline fn pcSelect(pc: [16]Edwards25519, b: u8) Edwards25519 {147 inline fn pcSelect(comptime n: usize, pc: [n]Edwards25519, b: u8) Edwards25519 {
148 var t = Edwards25519.identityElement;148 var t = Edwards25519.identityElement;
149 comptime var i: u8 = 0;149 comptime var i: u8 = 1;
150 inline while (i < 16) : (i += 1) {150 inline while (i < pc.len) : (i += 1) {
151 t.cMov(pc[i], ((@as(usize, b ^ i) -% 1) >> 8) & 1);151 t.cMov(pc[i], ((@as(usize, b ^ i) -% 1) >> 8) & 1);
152 }152 }
153 return t;153 return t;
154 }154 }
155155
156 fn pcMul(pc: [16]Edwards25519, s: [32]u8, comptime vartime: bool) !Edwards25519 {156 fn nonAdjacentForm(s: [32]u8) [2 * 32]i8 {
157 var e: [2 * 32]i8 = undefined;
158 for (s) |x, i| {
159 e[i * 2 + 0] = @as(i8, @truncate(u4, x));
160 e[i * 2 + 1] = @as(i8, @truncate(u4, x >> 4));
161 }
162 // Now, e[0..63] is between 0 and 15, e[63] is between 0 and 7
163 var carry: i8 = 0;
164 for (e[0..63]) |*x| {
165 x.* += carry;
166 carry = (x.* + 8) >> 4;
167 x.* -= carry * 16;
168 }
169 e[63] += carry;
170 // Now, e[*] is between -8 and 8, including e[63]
171 return e;
172 }
173
174 // Scalar multiplication with a 4-bit window and the first 8 multiples.
175 // This requires the scalar to be converted to non-adjacent form.
176 // Based on real-world benchmarks, we only use this for multi-scalar multiplication.
177 // NAF could be useful to half the size of precomputation tables, but we intentionally
178 // avoid these to keep the standard library lightweight.
179 fn pcMul(pc: [9]Edwards25519, s: [32]u8, comptime vartime: bool) !Edwards25519 {
180 std.debug.assert(vartime);
181 const e = nonAdjacentForm(s);
182 var q = Edwards25519.identityElement;
183 var pos: usize = 2 * 32 - 1;
184 while (true) : (pos -= 1) {
185 const slot = e[pos];
186 if (slot > 0) {
187 q = q.add(pc[@intCast(usize, slot)]);
188 } else if (slot < 0) {
189 q = q.sub(pc[@intCast(usize, -slot)]);
190 }
191 if (pos == 0) break;
192 q = q.dbl().dbl().dbl().dbl();
193 }
194 try q.rejectIdentity();
195 return q;
196 }
197
198 // Scalar multiplication with a 4-bit window and the first 15 multiples.
199 fn pcMul16(pc: [16]Edwards25519, s: [32]u8, comptime vartime: bool) !Edwards25519 {
157 var q = Edwards25519.identityElement;200 var q = Edwards25519.identityElement;
158 var pos: usize = 252;201 var pos: usize = 252;
159 while (true) : (pos -= 4) {202 while (true) : (pos -= 4) {
160 q = q.dbl().dbl().dbl().dbl();203 const slot = @truncate(u4, (s[pos >> 3] >> @truncate(u3, pos)));
161 const bit = (s[pos >> 3] >> @truncate(u3, pos)) & 0xf;
162 if (vartime) {204 if (vartime) {
163 if (bit != 0) {205 if (slot != 0) {
164 q = q.add(pc[bit]);206 q = q.add(pc[slot]);
165 }207 }
166 } else {208 } else {
167 q = q.add(pcSelect(pc, bit));209 q = q.add(pcSelect(16, pc, slot));
168 }210 }
169 if (pos == 0) break;211 if (pos == 0) break;
212 q = q.dbl().dbl().dbl().dbl();
170 }213 }
171 try q.rejectIdentity();214 try q.rejectIdentity();
172 return q;215 return q;
173 }216 }
174217
175 fn precompute(p: Edwards25519) [16]Edwards25519 {218 fn precompute(p: Edwards25519, comptime count: usize) [1 + count]Edwards25519 {
176 var pc: [16]Edwards25519 = undefined;219 var pc: [1 + count]Edwards25519 = undefined;
177 pc[0] = Edwards25519.identityElement;220 pc[0] = Edwards25519.identityElement;
178 pc[1] = p;221 pc[1] = p;
179 var i: usize = 2;222 var i: usize = 2;
180 while (i < 16) : (i += 1) {223 while (i <= count) : (i += 1) {
181 pc[i] = pc[i - 1].add(p);224 pc[i] = pc[i - 1].add(p);
182 }225 }
183 return pc;226 return pc;
184 }227 }
185228
229 const basePointPc = comptime pc: {
230 @setEvalBranchQuota(10000);
231 break :pc precompute(Edwards25519.basePoint, 15);
232 };
233
186 /// Multiply an Edwards25519 point by a scalar without clamping it.234 /// Multiply an Edwards25519 point by a scalar without clamping it.
187 /// Return error.WeakPublicKey if the resulting point is235 /// Return error.WeakPublicKey if the resulting point is
188 /// the identity element.236 /// the identity element.
189 pub fn mul(p: Edwards25519, s: [32]u8) !Edwards25519 {237 pub fn mul(p: Edwards25519, s: [32]u8) !Edwards25519 {
190 var pc: [16]Edwards25519 = undefined;238 const pc = if (p.is_base) basePointPc else pc: {
191 if (p.is_base) {239 const xpc = precompute(p, 15);
192 @setEvalBranchQuota(10000);240 xpc[4].rejectIdentity() catch |_| return error.WeakPublicKey;
193 pc = comptime precompute(Edwards25519.basePoint);241 break :pc xpc;
194 } else {242 };
195 pc = precompute(p);243 return pcMul16(pc, s, false);
196 pc[4].rejectIdentity() catch |_| return error.WeakPublicKey;
197 }
198 return pcMul(pc, s, false);
199 }244 }
200245
201 /// Multiply an Edwards25519 point by a *PUBLIC* scalar *IN VARIABLE TIME*246 /// Multiply an Edwards25519 point by a *PUBLIC* scalar *IN VARIABLE TIME*
202 /// This can be used for signature verification.247 /// This can be used for signature verification.
203 pub fn mulPublic(p: Edwards25519, s: [32]u8) !Edwards25519 {248 pub fn mulPublic(p: Edwards25519, s: [32]u8) !Edwards25519 {
204 var pc: [16]Edwards25519 = undefined;
205 if (p.is_base) {249 if (p.is_base) {
206 @setEvalBranchQuota(10000);250 return pcMul16(basePointPc, s, true);
207 pc = comptime precompute(Edwards25519.basePoint);
208 } else {251 } else {
209 pc = precompute(p);252 const pc = precompute(p, 8);
210 pc[4].rejectIdentity() catch |_| return error.WeakPublicKey;253 pc[4].rejectIdentity() catch |_| return error.WeakPublicKey;
254 return pcMul(pc, s, true);
211 }255 }
212 return pcMul(pc, s, true);
213 }256 }
214257
215 /// Multiscalar multiplication *IN VARIABLE TIME* for public data258 /// Multiscalar multiplication *IN VARIABLE TIME* for public data
216 /// Computes ps0*ss0 + ps1*ss1 + ps2*ss2... faster than doing many of these operations individually259 /// Computes ps0*ss0 + ps1*ss1 + ps2*ss2... faster than doing many of these operations individually
217 pub fn mulMulti(comptime count: usize, ps: [count]Edwards25519, ss: [count][32]u8) !Edwards25519 {260 pub fn mulMulti(comptime count: usize, ps: [count]Edwards25519, ss: [count][32]u8) !Edwards25519 {
218 var pcs: [count][16]Edwards25519 = undefined;261 var pcs: [count][9]Edwards25519 = undefined;
219 for (ps) |p, i| {262 for (ps) |p, i| {
220 if (p.is_base) {263 if (p.is_base) {
221 @setEvalBranchQuota(10000);264 @setEvalBranchQuota(10000);
222 pcs[i] = comptime precompute(Edwards25519.basePoint);265 pcs[i] = comptime precompute(Edwards25519.basePoint, 8);
223 } else {266 } else {
224 pcs[i] = precompute(p);267 pcs[i] = precompute(p, 8);
225 pcs[i][4].rejectIdentity() catch |_| return error.WeakPublicKey;268 pcs[i][4].rejectIdentity() catch |_| return error.WeakPublicKey;
226 }269 }
227 }270 }
271 var es: [count][2 * 32]i8 = undefined;
272 for (ss) |s, i| {
273 es[i] = nonAdjacentForm(s);
274 }
228 var q = Edwards25519.identityElement;275 var q = Edwards25519.identityElement;
229 var pos: usize = 252;276 var pos: usize = 2 * 32 - 1;
230 while (true) : (pos -= 4) {277 while (true) : (pos -= 1) {
231 q = q.dbl().dbl().dbl().dbl();278 for (es) |e, i| {
232 for (ss) |s, i| {279 const slot = e[pos];
233 const bit = (s[pos >> 3] >> @truncate(u3, pos)) & 0xf;280 if (slot > 0) {
234 if (bit != 0) {281 q = q.add(pcs[i][@intCast(usize, slot)]);
235 q = q.add(pcs[i][bit]);282 } else if (slot < 0) {
283 q = q.sub(pcs[i][@intCast(usize, -slot)]);
236 }284 }
237 }285 }
238 if (pos == 0) break;286 if (pos == 0) break;
287 q = q.dbl().dbl().dbl().dbl();
239 }288 }
240 try q.rejectIdentity();289 try q.rejectIdentity();
241 return q;290 return q;