authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-10-17 22:19:54+02:00
committergravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2020-10-22 09:58:26+02:00
log8e79b3cf23b344e2f40e125581f2090dbe756c33
treeae0be3b0c1f62dd6c062b588d515c2e7267a1182
parent0c355bef9e424fbf06085f12fc28979d73b3d6af

std/crypto/25519: add support for batch Ed25519 signature verification


3 files changed, 265 insertions(+), 1 deletions(-)

lib/std/crypto/25519/ed25519.zig+202
......@@ -97,6 +97,7 @@ pub const Ed25519 = struct {
9797 try Curve.rejectNonCanonical(public_key);
9898 const a = try Curve.fromBytes(public_key);
9999 try a.rejectIdentity();
100 try Curve.rejectNonCanonical(r.*);
100101 const expected_r = try Curve.fromBytes(r.*);
101102
102103 var h = Sha512.init(.{});
......@@ -113,6 +114,78 @@ pub const Ed25519 = struct {
113114 return error.InvalidSignature;
114115 } else |_| {}
115116 }
117
118 /// A (signature, message, public_key) tuple for batch verification
119 pub const BatchElement = struct {
120 sig: [signature_length]u8,
121 msg: []const u8,
122 public_key: [public_length]u8,
123 };
124
125 /// Verify several signatures in a single operation, much faster than verifying signatures one-by-one
126 pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) !void {
127 var r_batch: [count][32]u8 = undefined;
128 var s_batch: [count][32]u8 = undefined;
129 var a_batch: [count]Curve = undefined;
130 var expected_r_batch: [count]Curve = undefined;
131
132 for (signature_batch) |signature, i| {
133 const r = signature.sig[0..32];
134 const s = signature.sig[32..64];
135 try Curve.scalar.rejectNonCanonical(s.*);
136 try Curve.rejectNonCanonical(signature.public_key);
137 const a = try Curve.fromBytes(signature.public_key);
138 try a.rejectIdentity();
139 try Curve.rejectNonCanonical(r.*);
140 const expected_r = try Curve.fromBytes(r.*);
141 expected_r_batch[i] = expected_r;
142 r_batch[i] = r.*;
143 s_batch[i] = s.*;
144 a_batch[i] = a;
145 }
146
147 var hram_batch: [count]Curve.scalar.Scalar = undefined;
148 for (signature_batch) |signature, i| {
149 var h = Sha512.init(.{});
150 h.update(&r_batch[i]);
151 h.update(&signature.public_key);
152 h.update(signature.msg);
153 var hram64: [Sha512.digest_length]u8 = undefined;
154 h.final(&hram64);
155 hram_batch[i] = Curve.scalar.reduce64(hram64);
156 }
157
158 var z_batch: [count]Curve.scalar.Scalar = undefined;
159 for (z_batch) |*z| {
160 try std.crypto.randomBytes(z[0..16]);
161 mem.set(u8, z[16..], 0);
162 }
163
164 var zs_sum = Curve.scalar.zero;
165 for (z_batch) |z, i| {
166 const zs = Curve.scalar.mul(z, s_batch[i]);
167 zs_sum = Curve.scalar.add(zs_sum, zs);
168 }
169 zs_sum = Curve.scalar.mul8(zs_sum);
170
171 var zr = Curve.neutralElement;
172 for (z_batch) |z, i| {
173 zr = zr.add(try expected_r_batch[i].mul(z));
174 }
175 zr = zr.clearCofactor();
176
177 var zah = Curve.neutralElement;
178 for (z_batch) |z, i| {
179 const zh = Curve.scalar.mul(z, hram_batch[i]);
180 zah = zah.add(try a_batch[i].mul(zh));
181 }
182 zah = zah.clearCofactor();
183
184 const zsb = try Curve.basePoint.mul(zs_sum);
185 if (zr.add(zah).sub(zsb).rejectIdentity()) |_| {
186 return error.InvalidSignature;
187 } else |_| {}
188 }
116189};
117190
118191test "ed25519 key pair creation" {
......@@ -138,3 +211,132 @@ test "ed25519 signature" {
138211 try Ed25519.verify(sig, "test", public_key);
139212 std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", public_key));
140213}
214
215test "ed25519 batch verification" {
216 var i: usize = 0;
217 while (i < 100) : (i += 1) {
218 var seed: [32]u8 = undefined;
219 try std.crypto.randomBytes(&seed);
220 const key_pair = try Ed25519.createKeyPair(seed);
221 var msg1: [32]u8 = undefined;
222 var msg2: [32]u8 = undefined;
223 try std.crypto.randomBytes(&msg1);
224 try std.crypto.randomBytes(&msg2);
225 const sig1 = try Ed25519.sign(&msg1, key_pair, null);
226 const sig2 = try Ed25519.sign(&msg2, key_pair, null);
227 const public_key = Ed25519.publicKey(key_pair);
228 var signature_batch = [_]Ed25519.BatchElement{
229 Ed25519.BatchElement{
230 .sig = sig1,
231 .msg = &msg1,
232 .public_key = public_key,
233 },
234 Ed25519.BatchElement{
235 .sig = sig2,
236 .msg = &msg2,
237 .public_key = public_key,
238 },
239 };
240 try Ed25519.verifyBatch(2, signature_batch);
241
242 signature_batch[1].sig = sig1;
243 std.testing.expectError(error.InvalidSignature, Ed25519.verifyBatch(signature_batch.len, signature_batch));
244 }
245}
246
247test "ed25519 test vectors" {
248 const Vec = struct {
249 msg_hex: *const [64:0]u8,
250 public_key_hex: *const [64:0]u8,
251 sig_hex: *const [128:0]u8,
252 expected: ?anyerror,
253 };
254
255 const entries = [_]Vec{
256 Vec{
257 .msg_hex = "8c93255d71dcab10e8f379c26200f3c7bd5f09d9bc3068d3ef4edeb4853022b6",
258 .public_key_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
259 .sig_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a0000000000000000000000000000000000000000000000000000000000000000",
260 .expected = error.WeakPublicKey, // 0
261 },
262 Vec{
263 .msg_hex = "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
264 .public_key_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa",
265 .sig_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43a5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
266 .expected = error.WeakPublicKey, // 1
267 },
268 Vec{
269 .msg_hex = "aebf3f2601a0c8c5d39cc7d8911642f740b78168218da8471772b35f9d35b9ab",
270 .public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
271 .sig_hex = "c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa8c4bd45aecaca5b24fb97bc10ac27ac8751a7dfe1baff8b953ec9f5833ca260e",
272 .expected = null, // 2 - small order R is acceptable
273 },
274 Vec{
275 .msg_hex = "9bd9f44f4dcc75bd531b56b2cd280b0bb38fc1cd6d1230e14861d861de092e79",
276 .public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
277 .sig_hex = "9046a64750444938de19f227bb80485e92b83fdb4b6506c160484c016cc1852f87909e14428a7a1d62e9f22f3d3ad7802db02eb2e688b6c52fcd6648a98bd009",
278 .expected = null, // 3 - mixed orders
279 },
280 Vec{
281 .msg_hex = "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
282 .public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
283 .sig_hex = "160a1cb0dc9c0258cd0a7d23e94d8fa878bcb1925f2c64246b2dee1796bed5125ec6bc982a269b723e0668e540911a9a6a58921d6925e434ab10aa7940551a09",
284 .expected = null, // 4 - cofactored verification
285 },
286 Vec{
287 .msg_hex = "e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec4011eaccd55b53f56c",
288 .public_key_hex = "cdb267ce40c5cd45306fa5d2f29731459387dbf9eb933b7bd5aed9a765b88d4d",
289 .sig_hex = "21122a84e0b5fca4052f5b1235c80a537878b38f3142356b2c2384ebad4668b7e40bc836dac0f71076f9abe3a53f9c03c1ceeeddb658d0030494ace586687405",
290 .expected = null, // 5 - cofactored verification
291 },
292 Vec{
293 .msg_hex = "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
294 .public_key_hex = "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
295 .sig_hex = "e96f66be976d82e60150baecff9906684aebb1ef181f67a7189ac78ea23b6c0e547f7690a0e2ddcd04d87dbc3490dc19b3b3052f7ff0538cb68afb369ba3a514",
296 .expected = error.NonCanonical, // 6 - S > L
297 },
298 Vec{
299 .msg_hex = "85e241a07d148b41e47d62c63f830dc7a6851a0b1f33ae4bb2f507fb6cffec40",
300 .public_key_hex = "442aad9f089ad9e14647b1ef9099a1ff4798d78589e66f28eca69c11f582a623",
301 .sig_hex = "8ce5b96c8f26d0ab6c47958c9e68b937104cd36e13c33566acd2fe8d38aa19427e71f98a4734e74f2f13f06f97c20d58cc3f54b8bd0d272f42b695dd7e89a8c2",
302 .expected = error.NonCanonical, // 7 - S >> L
303 },
304 Vec{
305 .msg_hex = "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
306 .public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
307 .sig_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03be9678ac102edcd92b0210bb34d7428d12ffc5df5f37e359941266a4e35f0f",
308 .expected = error.InvalidSignature, // 8 - non-canonical R
309 },
310 Vec{
311 .msg_hex = "9bedc267423725d473888631ebf45988bad3db83851ee85c85e241a07d148b41",
312 .public_key_hex = "f7badec5b8abeaf699583992219b7b223f1df3fbbea919844e3f7c554a43dd43",
313 .sig_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffca8c5b64cd208982aa38d4936621a4775aa233aa0505711d8fdcfdaa943d4908",
314 .expected = null, // 9 - non-canonical R
315 },
316 Vec{
317 .msg_hex = "e96b7021eb39c1a163b6da4e3093dcd3f21387da4cc4572be588fafae23c155b",
318 .public_key_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
319 .sig_hex = "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
320 .expected = error.IdentityElement, // 10 - small-order A
321 },
322 Vec{
323 .msg_hex = "39a591f5321bbe07fd5a23dc2f39d025d74526615746727ceefd6e82ae65c06f",
324 .public_key_hex = "ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
325 .sig_hex = "a9d55260f765261eb9b84e106f665e00b867287a761990d7135963ee0a7d59dca5bb704786be79fc476f91d3f3f89b03984d8068dcf1bb7dfc6637b45450ac04",
326 .expected = error.IdentityElement, // 11 - small-order A
327 },
328 };
329 for (entries) |entry, i| {
330 var msg: [entry.msg_hex.len / 2]u8 = undefined;
331 try fmt.hexToBytes(&msg, entry.msg_hex);
332 var public_key: [32]u8 = undefined;
333 try fmt.hexToBytes(&public_key, entry.public_key_hex);
334 var sig: [64]u8 = undefined;
335 try fmt.hexToBytes(&sig, entry.sig_hex);
336 if (entry.expected) |error_type| {
337 std.testing.expectError(error_type, Ed25519.verify(sig, &msg, public_key));
338 } else {
339 try Ed25519.verify(sig, &msg, public_key);
340 }
341 }
342}
lib/std/crypto/25519/edwards25519.zig+9
......@@ -64,6 +64,15 @@ pub const Edwards25519 = struct {
6464 .is_base = true,
6565 };
6666
67 /// The edwards25519 neutral element.
68 pub const neutralElement = Edwards25519{
69 .x = Fe{ .limbs = .{ 2251799813685229, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247 } },
70 .y = Fe{ .limbs = .{ 1507481815385608, 2223447444246085, 1083941587175919, 2059929906842505, 1581435440146976 } },
71 .z = Fe{ .limbs = .{ 1507481815385608, 2223447444246085, 1083941587175919, 2059929906842505, 1581435440146976 } },
72 .t = Fe{ .limbs = .{ 2251799813685229, 2251799813685247, 2251799813685247, 2251799813685247, 2251799813685247 } },
73 .is_base = false,
74 };
75
6776 const identityElement = Edwards25519{ .x = Fe.zero, .y = Fe.one, .z = Fe.one, .t = Fe.zero };
6877
6978 /// Reject the neutral element.
lib/std/crypto/25519/scalar.zig+54-1
......@@ -6,10 +6,18 @@
66const std = @import("std");
77const mem = std.mem;
88
9const field_size = [32]u8{
9/// A 32-byte representation of a scalar in 0 .. 2^252 + 27742317777372353535851937790883648493
10pub const Scalar = [32]u8;
11
12/// 2^252 + 27742317777372353535851937790883648493
13pub const field_size = [32]u8{
1014 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, // 2^252+27742317777372353535851937790883648493
1115};
1216
17/// Zero
18pub const zero = [_]u8{0} ** 32;
19
20/// Double-word scalar representation
1321const ScalarExpanded = struct {
1422 limbs: [64]i64 = [_]i64{0} ** 64,
1523
......@@ -62,6 +70,11 @@ const ScalarExpanded = struct {
6270 inline while (j < 32) : (j += 1) {
6371 limbs[j + 1] += limbs[j] >> 8;
6472 }
73 j = 0;
74 inline while (j < 32) : (j += 1) {
75 limbs[j] &= 0xff;
76 }
77 limbs[32] = 0;
6578 }
6679
6780 fn toBytes(e: *ScalarExpanded) [32]u8 {
......@@ -150,11 +163,51 @@ pub inline fn clamp(s: *[32]u8) void {
150163 s[31] = (s[31] & 127) | 64;
151164}
152165
166/// Return a*b (mod L)
167pub fn mul(a: [32]u8, b: [32]u8) [32]u8 {
168 return ScalarExpanded.fromBytes(a).mul(ScalarExpanded.fromBytes(b)).toBytes();
169}
170
153171/// Return a*b+c (mod L)
154172pub fn mulAdd(a: [32]u8, b: [32]u8, c: [32]u8) [32]u8 {
155173 return ScalarExpanded.fromBytes(a).mulAdd(ScalarExpanded.fromBytes(b), ScalarExpanded.fromBytes(c)).toBytes();
156174}
157175
176/// Return a*8 (mod L)
177pub fn mul8(s: [32]u8) [32]u8 {
178 var x = ScalarExpanded.fromBytes(s);
179 x = x.add(x);
180 x = x.add(x);
181 x = x.add(x);
182 return x.toBytes();
183}
184
185/// Return a+b (mod L)
186pub fn add(a: [32]u8, b: [32]u8) [32]u8 {
187 return ScalarExpanded.fromBytes(a).add(ScalarExpanded.fromBytes(b)).toBytes();
188}
189
190/// Return -s (mod L)
191pub fn neg(s: [32]u8) [32]u8 {
192 const fs: [64]u8 = field_size ++ [_]u8{0} ** 32;
193 var sx: [64]u8 = undefined;
194 mem.copy(u8, sx[0..32], s[0..]);
195 mem.set(u8, sx[32..], 0);
196 var carry: u32 = 0;
197 var i: usize = 0;
198 while (i < 64) : (i += 1) {
199 carry = @as(u32, fs[i]) -% sx[i] -% @as(u32, carry);
200 sx[i] = @truncate(u8, carry);
201 carry = (carry >> 8) & 1;
202 }
203 return reduce64(sx);
204}
205
206/// Return (a-b) (mod L)
207pub fn sub(a: [32]u8, b: [32]u8) [32]u8 {
208 return add(a, neg(b));
209}
210
158211test "scalar25519" {
159212 const bytes: [32]u8 = .{ 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 255 };
160213 var x = ScalarExpanded.fromBytes(bytes);