authorgravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2018-08-30 18:02:19+12:00
committergravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2018-08-30 18:02:19+12:00
log65b89f598c63719ab80553163424feb2d8e6f9e4
treec502fdf979ecf04944887f3dc3c94ff369b1d86c
parent9de0f900e1a80554ac72c8675fc2896977f4930b

Add poly1305 and x25519 crypto primitives

These are translated from [monocypher](https://monocypher.org/) which has fairly competitive performance while remaining quite simple. Initial performance comparision: Zig: Poly1305: 1423 MiB/s X25519: 8671 exchanges per second Monocypher: Poly1305: 1567 MiB/s X25519: 10539 exchanges per second There is room for improvement and no real effort has been made at all in optimization beyond a direct translation.

4 files changed, 895 insertions(+), 1 deletions(-)

CMakeLists.txt+3-1
......@@ -445,13 +445,15 @@ set(ZIG_STD_FILES
445445 "c/linux.zig"
446446 "c/windows.zig"
447447 "crypto/blake2.zig"
448 "crypto/chacha20.zig"
448449 "crypto/hmac.zig"
449450 "crypto/index.zig"
450451 "crypto/md5.zig"
451452 "crypto/sha1.zig"
452453 "crypto/sha2.zig"
453454 "crypto/sha3.zig"
454 "crypto/chacha20.zig"
455 "crypto/poly1305.zig"
456 "crypto/x25519.zig"
455457 "cstr.zig"
456458 "debug/failing_allocator.zig"
457459 "debug/index.zig"
std/crypto/index.zig+5
......@@ -28,6 +28,9 @@ const import_chaCha20 = @import("chacha20.zig");
2828pub const chaCha20IETF = import_chaCha20.chaCha20IETF;
2929pub const chaCha20With64BitNonce = import_chaCha20.chaCha20With64BitNonce;
3030
31const poly1305 = @import("poly1305.zig");
32const x25519 = @import("x25519.zig");
33
3134test "crypto" {
3235 _ = @import("md5.zig");
3336 _ = @import("sha1.zig");
......@@ -36,4 +39,6 @@ test "crypto" {
3639 _ = @import("blake2.zig");
3740 _ = @import("hmac.zig");
3841 _ = @import("chacha20.zig");
42 _ = @import("poly1305.zig");
43 _ = @import("x25519.zig");
3944}
std/crypto/poly1305.zig created+220
......@@ -0,0 +1,220 @@
1// Translated from monocypher which is licensed under CC-0/BSD-3.
2//
3// https://monocypher.org/
4
5const std = @import("../index.zig");
6const builtin = @import("builtin");
7
8const Endian = builtin.Endian;
9const readInt = std.mem.readInt;
10const writeInt = std.mem.writeInt;
11
12const crypto_poly1305_ctx = struct {
13 // constant multiplier (from the secret key)
14 r: [4]u32,
15 // accumulated hash
16 h: [5]u32,
17 // chunk of the message
18 c: [5]u32,
19 // random number added at the end (from the secret key)
20 pad: [4]u32,
21 // How many bytes are there in the chunk.
22 c_idx: usize,
23
24 fn secure_zero(self: *crypto_poly1305_ctx) void {
25 std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(crypto_poly1305_ctx)]);
26 }
27};
28
29// h = (h + c) * r
30// preconditions:
31// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff
32// ctx->c <= 1_ffffffff_ffffffff_ffffffff_ffffffff
33// ctx->r <= 0ffffffc_0ffffffc_0ffffffc_0fffffff
34// Postcondition:
35// ctx->h <= 4_ffffffff_ffffffff_ffffffff_ffffffff
36fn poly_block(ctx: *crypto_poly1305_ctx) void {
37 // s = h + c, without carry propagation
38 const s0 = u64(ctx.h[0]) + ctx.c[0]; // s0 <= 1_fffffffe
39 const s1 = u64(ctx.h[1]) + ctx.c[1]; // s1 <= 1_fffffffe
40 const s2 = u64(ctx.h[2]) + ctx.c[2]; // s2 <= 1_fffffffe
41 const s3 = u64(ctx.h[3]) + ctx.c[3]; // s3 <= 1_fffffffe
42 const s4 = u64(ctx.h[4]) + ctx.c[4]; // s4 <= 5
43
44 // Local all the things!
45 const r0 = ctx.r[0]; // r0 <= 0fffffff
46 const r1 = ctx.r[1]; // r1 <= 0ffffffc
47 const r2 = ctx.r[2]; // r2 <= 0ffffffc
48 const r3 = ctx.r[3]; // r3 <= 0ffffffc
49 const rr0 = (r0 >> 2) * 5; // rr0 <= 13fffffb // lose 2 bits...
50 const rr1 = (r1 >> 2) + r1; // rr1 <= 13fffffb // rr1 == (r1 >> 2) * 5
51 const rr2 = (r2 >> 2) + r2; // rr2 <= 13fffffb // rr1 == (r2 >> 2) * 5
52 const rr3 = (r3 >> 2) + r3; // rr3 <= 13fffffb // rr1 == (r3 >> 2) * 5
53
54 // (h + c) * r, without carry propagation
55 const x0 = s0 * r0 + s1 * rr3 + s2 * rr2 + s3 * rr1 + s4 * rr0; //<=97ffffe007fffff8
56 const x1 = s0 * r1 + s1 * r0 + s2 * rr3 + s3 * rr2 + s4 * rr1; //<=8fffffe20ffffff6
57 const x2 = s0 * r2 + s1 * r1 + s2 * r0 + s3 * rr3 + s4 * rr2; //<=87ffffe417fffff4
58 const x3 = s0 * r3 + s1 * r2 + s2 * r1 + s3 * r0 + s4 * rr3; //<=7fffffe61ffffff2
59 const x4 = s4 * (r0 & 3); // ...recover 2 bits //<= f
60
61 // partial reduction modulo 2^130 - 5
62 const _u5 = @truncate(u32, x4 + (x3 >> 32)); // u5 <= 7ffffff5
63 const _u0 = (_u5 >> 2) * 5 + (x0 & 0xffffffff);
64 const _u1 = (_u0 >> 32) + (x1 & 0xffffffff) + (x0 >> 32);
65 const _u2 = (_u1 >> 32) + (x2 & 0xffffffff) + (x1 >> 32);
66 const _u3 = (_u2 >> 32) + (x3 & 0xffffffff) + (x2 >> 32);
67 const _u4 = (_u3 >> 32) + (_u5 & 3);
68
69 // Update the hash
70 ctx.h[0] = @truncate(u32, _u0); // u0 <= 1_9ffffff0
71 ctx.h[1] = @truncate(u32, _u1); // u1 <= 1_97ffffe0
72 ctx.h[2] = @truncate(u32, _u2); // u2 <= 1_8fffffe2
73 ctx.h[3] = @truncate(u32, _u3); // u3 <= 1_87ffffe4
74 ctx.h[4] = @truncate(u32, _u4); // u4 <= 4
75}
76
77// (re-)initializes the input counter and input buffer
78fn poly_clear_c(ctx: *crypto_poly1305_ctx) void {
79 ctx.c[0] = 0;
80 ctx.c[1] = 0;
81 ctx.c[2] = 0;
82 ctx.c[3] = 0;
83 ctx.c_idx = 0;
84}
85
86fn poly_take_input(ctx: *crypto_poly1305_ctx, input: u8) void {
87 const word = ctx.c_idx >> 2;
88 const byte = ctx.c_idx & 3;
89 ctx.c[word] |= std.math.shl(u32, input, byte * 8);
90 ctx.c_idx += 1;
91}
92
93fn poly_update(ctx: *crypto_poly1305_ctx, message: []const u8) void {
94 for (message) |b| {
95 poly_take_input(ctx, b);
96 if (ctx.c_idx == 16) {
97 poly_block(ctx);
98 poly_clear_c(ctx);
99 }
100 }
101}
102
103pub fn crypto_poly1305_init(ctx: *crypto_poly1305_ctx, key: [32]u8) void {
104 // Initial hash is zero
105 {
106 var i: usize = 0;
107 while (i < 5) : (i += 1) {
108 ctx.h[i] = 0;
109 }
110 }
111 // add 2^130 to every input block
112 ctx.c[4] = 1;
113 poly_clear_c(ctx);
114
115 // load r and pad (r has some of its bits cleared)
116 {
117 var i: usize = 0;
118 while (i < 1) : (i += 1) {
119 ctx.r[0] = readInt(key[0..4], u32, Endian.Little) & 0x0fffffff;
120 }
121 }
122 {
123 var i: usize = 1;
124 while (i < 4) : (i += 1) {
125 ctx.r[i] = readInt(key[i * 4 .. i * 4 + 4], u32, Endian.Little) & 0x0ffffffc;
126 }
127 }
128 {
129 var i: usize = 0;
130 while (i < 4) : (i += 1) {
131 ctx.pad[i] = readInt(key[i * 4 + 16 .. i * 4 + 16 + 4], u32, Endian.Little);
132 }
133 }
134}
135
136inline fn alignto(x: usize, block_size: usize) usize {
137 return ((~x) +% 1) & (block_size - 1);
138}
139
140pub fn crypto_poly1305_update(ctx: *crypto_poly1305_ctx, message: []const u8) void {
141 // Align ourselves with block boundaries
142 const alignm = std.math.min(alignto(ctx.c_idx, 16), message.len);
143 poly_update(ctx, message[0..alignm]);
144
145 var nmessage = message[alignm..];
146
147 // Process the message block by block
148 const nb_blocks = nmessage.len >> 4;
149 var i: usize = 0;
150 while (i < nb_blocks) : (i += 1) {
151 ctx.c[0] = readInt(nmessage[0..4], u32, Endian.Little);
152 ctx.c[1] = readInt(nmessage[4..8], u32, Endian.Little);
153 ctx.c[2] = readInt(nmessage[8..12], u32, Endian.Little);
154 ctx.c[3] = readInt(nmessage[12..16], u32, Endian.Little);
155 poly_block(ctx);
156 nmessage = nmessage[16..];
157 }
158 if (nb_blocks > 0) {
159 poly_clear_c(ctx);
160 }
161
162 // remaining bytes
163 poly_update(ctx, nmessage[0..]);
164}
165
166pub fn crypto_poly1305_final(ctx: *crypto_poly1305_ctx, mac: []u8) void {
167 // Process the last block (if any)
168 if (ctx.c_idx != 0) {
169 // move the final 1 according to remaining input length
170 // (We may add less than 2^130 to the last input block)
171 ctx.c[4] = 0;
172 poly_take_input(ctx, 1);
173 // one last hash update
174 poly_block(ctx);
175 }
176
177 // check if we should subtract 2^130-5 by performing the
178 // corresponding carry propagation.
179 const _u0 = u64(5) + ctx.h[0]; // <= 1_00000004
180 const _u1 = (_u0 >> 32) + ctx.h[1]; // <= 1_00000000
181 const _u2 = (_u1 >> 32) + ctx.h[2]; // <= 1_00000000
182 const _u3 = (_u2 >> 32) + ctx.h[3]; // <= 1_00000000
183 const _u4 = (_u3 >> 32) + ctx.h[4]; // <= 5
184 // u4 indicates how many times we should subtract 2^130-5 (0 or 1)
185
186 // h + pad, minus 2^130-5 if u4 exceeds 3
187 const uu0 = (_u4 >> 2) * 5 + ctx.h[0] + ctx.pad[0]; // <= 2_00000003
188 const uu1 = (uu0 >> 32) + ctx.h[1] + ctx.pad[1]; // <= 2_00000000
189 const uu2 = (uu1 >> 32) + ctx.h[2] + ctx.pad[2]; // <= 2_00000000
190 const uu3 = (uu2 >> 32) + ctx.h[3] + ctx.pad[3]; // <= 2_00000000
191
192 writeInt(mac[0..], uu0, Endian.Little);
193 writeInt(mac[4..], uu1, Endian.Little);
194 writeInt(mac[8..], uu2, Endian.Little);
195 writeInt(mac[12..], uu3, Endian.Little);
196
197 ctx.secure_zero();
198}
199
200pub fn crypto_poly1305(mac: []u8, message: []const u8, key: [32]u8) void {
201 std.debug.assert(mac.len >= 16);
202
203 var ctx: crypto_poly1305_ctx = undefined;
204 crypto_poly1305_init(&ctx, key);
205 crypto_poly1305_update(&ctx, message);
206 crypto_poly1305_final(&ctx, mac);
207}
208
209test "poly1305 rfc7439 vector1" {
210 const expected_mac = "\xa8\x06\x1d\xc1\x30\x51\x36\xc6\xc2\x2b\x8b\xaf\x0c\x01\x27\xa9";
211
212 const msg = "Cryptographic Forum Research Group";
213 const key = "\x85\xd6\xbe\x78\x57\x55\x6d\x33\x7f\x44\x52\xfe\x42\xd5\x06\xa8" ++
214 "\x01\x03\x80\x8a\xfb\x0d\xb2\xfd\x4a\xbf\xf6\xaf\x41\x49\xf5\x1b";
215
216 var mac: [16]u8 = undefined;
217 crypto_poly1305(mac[0..], msg, key);
218
219 std.debug.assert(std.mem.eql(u8, mac, expected_mac));
220}
std/crypto/x25519.zig created+667
......@@ -0,0 +1,667 @@
1// Translated from monocypher which is licensed under CC-0/BSD-3.
2//
3// https://monocypher.org/
4
5const std = @import("../index.zig");
6const builtin = @import("builtin");
7
8const Endian = builtin.Endian;
9const readInt = std.mem.readInt;
10const writeInt = std.mem.writeInt;
11
12// Constant time compare to zero.
13fn zerocmp(comptime T: type, a: []const T) bool {
14 var s: T = 0;
15 for (a) |b| {
16 s |= b;
17 }
18 return s == 0;
19}
20
21////////////////////////////////////
22/// Arithmetic modulo 2^255 - 19 ///
23////////////////////////////////////
24// Taken from Supercop's ref10 implementation.
25// A bit bigger than TweetNaCl, over 4 times faster.
26
27// field element
28const Fe = struct {
29 b: [10]i32,
30
31 fn secure_zero(self: *Fe) void {
32 std.mem.secureZero(u8, @ptrCast([*]u8, self)[0..@sizeOf(Fe)]);
33 }
34};
35
36fn fe_0(h: *Fe) void {
37 for (h.b) |*e| {
38 e.* = 0;
39 }
40}
41
42fn fe_1(h: *Fe) void {
43 for (h.b[1..]) |*e| {
44 e.* = 0;
45 }
46 h.b[0] = 1;
47}
48
49fn fe_copy(h: *Fe, f: *const Fe) void {
50 for (h.b) |_, i| {
51 h.b[i] = f.b[i];
52 }
53}
54
55fn fe_neg(h: *Fe, f: *const Fe) void {
56 for (h.b) |_, i| {
57 h.b[i] = -f.b[i];
58 }
59}
60
61fn fe_add(h: *Fe, f: *const Fe, g: *const Fe) void {
62 for (h.b) |_, i| {
63 h.b[i] = f.b[i] + g.b[i];
64 }
65}
66
67fn fe_sub(h: *Fe, f: *const Fe, g: *const Fe) void {
68 for (h.b) |_, i| {
69 h.b[i] = f.b[i] - g.b[i];
70 }
71}
72
73fn fe_cswap(f: *Fe, g: *Fe, b: i32) void {
74 for (f.b) |_, i| {
75 const x = (f.b[i] ^ g.b[i]) & -b;
76 f.b[i] ^= x;
77 g.b[i] ^= x;
78 }
79}
80
81fn fe_ccopy(f: *Fe, g: *const Fe, b: i32) void {
82 for (f.b) |_, i| {
83 const x = (f.b[i] ^ g.b[i]) & -b;
84 f.b[i] ^= x;
85 }
86}
87
88inline fn carryround(c: []i64, t: []i64, comptime i: comptime_int, comptime shift: comptime_int, comptime mult: comptime_int) void {
89 const j = (i + 1) % 10;
90
91 c[i] = (t[i] + (i64(1) << shift)) >> (shift + 1);
92 t[j] += c[i] * mult;
93 t[i] -= c[i] * (i64(1) << (shift + 1));
94}
95
96fn feCarry1(h: *Fe, t: []i64) void {
97 var c: [10]i64 = undefined;
98
99 var sc = c[0..];
100 var st = t[0..];
101
102 carryround(sc, st, 9, 24, 19);
103 carryround(sc, st, 1, 24, 1);
104 carryround(sc, st, 3, 24, 1);
105 carryround(sc, st, 5, 24, 1);
106 carryround(sc, st, 7, 24, 1);
107 carryround(sc, st, 0, 25, 1);
108 carryround(sc, st, 2, 25, 1);
109 carryround(sc, st, 4, 25, 1);
110 carryround(sc, st, 6, 25, 1);
111 carryround(sc, st, 8, 25, 1);
112
113 for (h.b) |_, i| {
114 h.b[i] = @intCast(i32, t[i]);
115 }
116}
117
118fn feCarry2(h: *Fe, t: []i64) void {
119 var c: [10]i64 = undefined;
120
121 var sc = c[0..];
122 var st = t[0..];
123
124 carryround(sc, st, 0, 25, 1);
125 carryround(sc, st, 4, 25, 1);
126 carryround(sc, st, 1, 24, 1);
127 carryround(sc, st, 5, 24, 1);
128 carryround(sc, st, 2, 25, 1);
129 carryround(sc, st, 6, 25, 1);
130 carryround(sc, st, 3, 24, 1);
131 carryround(sc, st, 7, 24, 1);
132 carryround(sc, st, 4, 25, 1);
133 carryround(sc, st, 8, 25, 1);
134 carryround(sc, st, 9, 24, 19);
135 carryround(sc, st, 0, 25, 1);
136
137 for (h.b) |_, i| {
138 h.b[i] = @intCast(i32, t[i]);
139 }
140}
141
142// TODO: Use readInt(u24) but double check alignment since currently it produces different values.
143fn load24_le(s: []const u8) u32 {
144 return s[0] | (u32(s[1]) << 8) | (u32(s[2]) << 16);
145}
146
147fn fe_frombytes(h: *Fe, s: [32]u8) void {
148 var t: [10]i64 = undefined;
149
150 t[0] = readInt(s[0..4], u32, Endian.Little);
151 t[1] = load24_le(s[4..7]) << 6;
152 t[2] = load24_le(s[7..10]) << 5;
153 t[3] = load24_le(s[10..13]) << 3;
154 t[4] = load24_le(s[13..16]) << 2;
155 t[5] = readInt(s[16..20], u32, Endian.Little);
156 t[6] = load24_le(s[20..23]) << 7;
157 t[7] = load24_le(s[23..26]) << 5;
158 t[8] = load24_le(s[26..29]) << 4;
159 t[9] = (load24_le(s[29..32]) & 0x7fffff) << 2;
160
161 feCarry1(h, t[0..]);
162}
163
164fn fe_mul_small(h: *Fe, f: *const Fe, comptime g: comptime_int) void {
165 var t: [10]i64 = undefined;
166
167 for (t[0..]) |_, i| {
168 t[i] = i64(f.b[i]) * g;
169 }
170
171 feCarry1(h, t[0..]);
172}
173
174fn fe_mul121666(h: *Fe, f: *const Fe) void {
175 fe_mul_small(h, f, 121666);
176}
177
178fn fe_mul(h: *Fe, f1: *const Fe, g1: *const Fe) void {
179 const f = f1.b;
180 const g = g1.b;
181
182 var F: [10]i32 = undefined;
183 var G: [10]i32 = undefined;
184
185 F[1] = f[1] * 2;
186 F[3] = f[3] * 2;
187 F[5] = f[5] * 2;
188 F[7] = f[7] * 2;
189 F[9] = f[9] * 2;
190
191 G[1] = g[1] * 19;
192 G[2] = g[2] * 19;
193 G[3] = g[3] * 19;
194 G[4] = g[4] * 19;
195 G[5] = g[5] * 19;
196 G[6] = g[6] * 19;
197 G[7] = g[7] * 19;
198 G[8] = g[8] * 19;
199 G[9] = g[9] * 19;
200
201 // t's become h
202 var t: [10]i64 = undefined;
203
204 t[0] = f[0] * i64(g[0]) + F[1] * i64(G[9]) + f[2] * i64(G[8]) + F[3] * i64(G[7]) + f[4] * i64(G[6]) + F[5] * i64(G[5]) + f[6] * i64(G[4]) + F[7] * i64(G[3]) + f[8] * i64(G[2]) + F[9] * i64(G[1]);
205 t[1] = f[0] * i64(g[1]) + f[1] * i64(g[0]) + f[2] * i64(G[9]) + f[3] * i64(G[8]) + f[4] * i64(G[7]) + f[5] * i64(G[6]) + f[6] * i64(G[5]) + f[7] * i64(G[4]) + f[8] * i64(G[3]) + f[9] * i64(G[2]);
206 t[2] = f[0] * i64(g[2]) + F[1] * i64(g[1]) + f[2] * i64(g[0]) + F[3] * i64(G[9]) + f[4] * i64(G[8]) + F[5] * i64(G[7]) + f[6] * i64(G[6]) + F[7] * i64(G[5]) + f[8] * i64(G[4]) + F[9] * i64(G[3]);
207 t[3] = f[0] * i64(g[3]) + f[1] * i64(g[2]) + f[2] * i64(g[1]) + f[3] * i64(g[0]) + f[4] * i64(G[9]) + f[5] * i64(G[8]) + f[6] * i64(G[7]) + f[7] * i64(G[6]) + f[8] * i64(G[5]) + f[9] * i64(G[4]);
208 t[4] = f[0] * i64(g[4]) + F[1] * i64(g[3]) + f[2] * i64(g[2]) + F[3] * i64(g[1]) + f[4] * i64(g[0]) + F[5] * i64(G[9]) + f[6] * i64(G[8]) + F[7] * i64(G[7]) + f[8] * i64(G[6]) + F[9] * i64(G[5]);
209 t[5] = f[0] * i64(g[5]) + f[1] * i64(g[4]) + f[2] * i64(g[3]) + f[3] * i64(g[2]) + f[4] * i64(g[1]) + f[5] * i64(g[0]) + f[6] * i64(G[9]) + f[7] * i64(G[8]) + f[8] * i64(G[7]) + f[9] * i64(G[6]);
210 t[6] = f[0] * i64(g[6]) + F[1] * i64(g[5]) + f[2] * i64(g[4]) + F[3] * i64(g[3]) + f[4] * i64(g[2]) + F[5] * i64(g[1]) + f[6] * i64(g[0]) + F[7] * i64(G[9]) + f[8] * i64(G[8]) + F[9] * i64(G[7]);
211 t[7] = f[0] * i64(g[7]) + f[1] * i64(g[6]) + f[2] * i64(g[5]) + f[3] * i64(g[4]) + f[4] * i64(g[3]) + f[5] * i64(g[2]) + f[6] * i64(g[1]) + f[7] * i64(g[0]) + f[8] * i64(G[9]) + f[9] * i64(G[8]);
212 t[8] = f[0] * i64(g[8]) + F[1] * i64(g[7]) + f[2] * i64(g[6]) + F[3] * i64(g[5]) + f[4] * i64(g[4]) + F[5] * i64(g[3]) + f[6] * i64(g[2]) + F[7] * i64(g[1]) + f[8] * i64(g[0]) + F[9] * i64(G[9]);
213 t[9] = f[0] * i64(g[9]) + f[1] * i64(g[8]) + f[2] * i64(g[7]) + f[3] * i64(g[6]) + f[4] * i64(g[5]) + f[5] * i64(g[4]) + f[6] * i64(g[3]) + f[7] * i64(g[2]) + f[8] * i64(g[1]) + f[9] * i64(g[0]);
214
215 feCarry2(h, t[0..]);
216}
217
218// we could use fe_mul() for this, but this is significantly faster
219fn fe_sq(h: *Fe, fz: *const Fe) void {
220 const f0 = fz.b[0];
221 const f1 = fz.b[1];
222 const f2 = fz.b[2];
223 const f3 = fz.b[3];
224 const f4 = fz.b[4];
225 const f5 = fz.b[5];
226 const f6 = fz.b[6];
227 const f7 = fz.b[7];
228 const f8 = fz.b[8];
229 const f9 = fz.b[9];
230
231 const f0_2 = f0 * 2;
232 const f1_2 = f1 * 2;
233 const f2_2 = f2 * 2;
234 const f3_2 = f3 * 2;
235 const f4_2 = f4 * 2;
236 const f5_2 = f5 * 2;
237 const f6_2 = f6 * 2;
238 const f7_2 = f7 * 2;
239 const f5_38 = f5 * 38;
240 const f6_19 = f6 * 19;
241 const f7_38 = f7 * 38;
242 const f8_19 = f8 * 19;
243 const f9_38 = f9 * 38;
244
245 var t: [10]i64 = undefined;
246
247 t[0] = f0 * i64(f0) + f1_2 * i64(f9_38) + f2_2 * i64(f8_19) + f3_2 * i64(f7_38) + f4_2 * i64(f6_19) + f5 * i64(f5_38);
248 t[1] = f0_2 * i64(f1) + f2 * i64(f9_38) + f3_2 * i64(f8_19) + f4 * i64(f7_38) + f5_2 * i64(f6_19);
249 t[2] = f0_2 * i64(f2) + f1_2 * i64(f1) + f3_2 * i64(f9_38) + f4_2 * i64(f8_19) + f5_2 * i64(f7_38) + f6 * i64(f6_19);
250 t[3] = f0_2 * i64(f3) + f1_2 * i64(f2) + f4 * i64(f9_38) + f5_2 * i64(f8_19) + f6 * i64(f7_38);
251 t[4] = f0_2 * i64(f4) + f1_2 * i64(f3_2) + f2 * i64(f2) + f5_2 * i64(f9_38) + f6_2 * i64(f8_19) + f7 * i64(f7_38);
252 t[5] = f0_2 * i64(f5) + f1_2 * i64(f4) + f2_2 * i64(f3) + f6 * i64(f9_38) + f7_2 * i64(f8_19);
253 t[6] = f0_2 * i64(f6) + f1_2 * i64(f5_2) + f2_2 * i64(f4) + f3_2 * i64(f3) + f7_2 * i64(f9_38) + f8 * i64(f8_19);
254 t[7] = f0_2 * i64(f7) + f1_2 * i64(f6) + f2_2 * i64(f5) + f3_2 * i64(f4) + f8 * i64(f9_38);
255 t[8] = f0_2 * i64(f8) + f1_2 * i64(f7_2) + f2_2 * i64(f6) + f3_2 * i64(f5_2) + f4 * i64(f4) + f9 * i64(f9_38);
256 t[9] = f0_2 * i64(f9) + f1_2 * i64(f8) + f2_2 * i64(f7) + f3_2 * i64(f6) + f4 * i64(f5_2);
257
258 feCarry2(h, t[0..]);
259}
260
261fn fe_sq2(h: *Fe, f: *const Fe) void {
262 fe_sq(h, f);
263 fe_mul_small(h, h, 2);
264}
265
266// This could be simplified, but it would be slower
267fn fe_invert(out: *Fe, z: *const Fe) void {
268 var i: usize = undefined;
269
270 var t: [4]Fe = undefined;
271 var t0 = &t[0];
272 var t1 = &t[1];
273 var t2 = &t[2];
274 var t3 = &t[3];
275
276 fe_sq(t0, z);
277 fe_sq(t1, t0);
278 fe_sq(t1, t1);
279 fe_mul(t1, z, t1);
280 fe_mul(t0, t0, t1);
281
282 fe_sq(t2, t0);
283 fe_mul(t1, t1, t2);
284
285 fe_sq(t2, t1);
286 i = 1;
287 while (i < 5) : (i += 1) fe_sq(t2, t2);
288 fe_mul(t1, t2, t1);
289
290 fe_sq(t2, t1);
291 i = 1;
292 while (i < 10) : (i += 1) fe_sq(t2, t2);
293 fe_mul(t2, t2, t1);
294
295 fe_sq(t3, t2);
296 i = 1;
297 while (i < 20) : (i += 1) fe_sq(t3, t3);
298 fe_mul(t2, t3, t2);
299
300 fe_sq(t2, t2);
301 i = 1;
302 while (i < 10) : (i += 1) fe_sq(t2, t2);
303 fe_mul(t1, t2, t1);
304
305 fe_sq(t2, t1);
306 i = 1;
307 while (i < 50) : (i += 1) fe_sq(t2, t2);
308 fe_mul(t2, t2, t1);
309
310 fe_sq(t3, t2);
311 i = 1;
312 while (i < 100) : (i += 1) fe_sq(t3, t3);
313 fe_mul(t2, t3, t2);
314
315 fe_sq(t2, t2);
316 i = 1;
317 while (i < 50) : (i += 1) fe_sq(t2, t2);
318 fe_mul(t1, t2, t1);
319
320 fe_sq(t1, t1);
321 i = 1;
322 while (i < 5) : (i += 1) fe_sq(t1, t1);
323 fe_mul(out, t1, t0);
324
325 t0.secure_zero();
326 t1.secure_zero();
327 t2.secure_zero();
328 t3.secure_zero();
329}
330
331// This could be simplified, but it would be slower
332fn fe_pow22523(out: *Fe, z: *const Fe) void {
333 var i: usize = undefined;
334
335 var t: [3]Fe = undefined;
336 var t0 = &t[0];
337 var t1 = &t[1];
338 var t2 = &t[2];
339
340 fe_sq(t0, z);
341 fe_sq(t1, t0);
342 fe_sq(t1, t1);
343 fe_mul(t1, z, t1);
344 fe_mul(t0, t0, t1);
345
346 fe_sq(t0, t0);
347 fe_mul(t0, t1, t0);
348
349 fe_sq(t1, t0);
350 i = 1;
351 while (i < 5) : (i += 1) fe_sq(t1, t1);
352 fe_mul(t0, t1, t0);
353
354 fe_sq(t1, t0);
355 i = 1;
356 while (i < 10) : (i += 1) fe_sq(t1, t1);
357 fe_mul(t1, t1, t0);
358
359 fe_sq(t2, t1);
360 i = 1;
361 while (i < 20) : (i += 1) fe_sq(t2, t2);
362 fe_mul(t1, t2, t1);
363
364 fe_sq(t1, t1);
365 i = 1;
366 while (i < 10) : (i += 1) fe_sq(t1, t1);
367 fe_mul(t0, t1, t0);
368
369 fe_sq(t1, t0);
370 i = 1;
371 while (i < 50) : (i += 1) fe_sq(t1, t1);
372 fe_mul(t1, t1, t0);
373
374 fe_sq(t2, t1);
375 i = 1;
376 while (i < 100) : (i += 1) fe_sq(t2, t2);
377 fe_mul(t1, t2, t1);
378
379 fe_sq(t1, t1);
380 i = 1;
381 while (i < 50) : (i += 1) fe_sq(t1, t1);
382 fe_mul(t0, t1, t0);
383
384 fe_sq(t0, t0);
385 i = 1;
386 while (i < 2) : (i += 1) fe_sq(t0, t0);
387 fe_mul(out, t0, z);
388
389 t0.secure_zero();
390 t1.secure_zero();
391 t2.secure_zero();
392}
393
394inline fn tobytesround(c: []i64, t: []i64, comptime i: comptime_int, comptime shift: comptime_int) void {
395 c[i] = t[i] >> shift;
396 if (i + 1 < 10) {
397 t[i + 1] += c[i];
398 }
399 t[i] -= c[i] * (i32(1) << shift);
400}
401
402fn fe_tobytes(s: []u8, h: *const Fe) void {
403 std.debug.assert(s.len >= 32);
404
405 var t: [10]i64 = undefined;
406 for (h.b[0..]) |_, i| {
407 t[i] = h.b[i];
408 }
409
410 var q = (19 * t[9] + ((i32(1) << 24))) >> 25;
411 {
412 var i: usize = 0;
413 while (i < 5) : (i += 1) {
414 q += t[2 * i];
415 q >>= 26;
416 q += t[2 * i + 1];
417 q >>= 25;
418 }
419 }
420 t[0] += 19 * q;
421
422 var c: [10]i64 = undefined;
423
424 var st = t[0..];
425 var sc = c[0..];
426
427 tobytesround(sc, st, 0, 26);
428 tobytesround(sc, st, 1, 25);
429 tobytesround(sc, st, 2, 26);
430 tobytesround(sc, st, 3, 25);
431 tobytesround(sc, st, 4, 26);
432 tobytesround(sc, st, 5, 25);
433 tobytesround(sc, st, 6, 26);
434 tobytesround(sc, st, 7, 25);
435 tobytesround(sc, st, 8, 26);
436 tobytesround(sc, st, 9, 25);
437
438 var ut: [10]u32 = undefined;
439 for (ut[0..]) |_, i| {
440 ut[i] = @bitCast(u32, @intCast(i32, t[i]));
441 }
442
443 writeInt(s[0..], (ut[0] >> 0) | (ut[1] << 26), Endian.Little);
444 writeInt(s[4..], (ut[1] >> 6) | (ut[2] << 19), Endian.Little);
445 writeInt(s[8..], (ut[2] >> 13) | (ut[3] << 13), Endian.Little);
446 writeInt(s[12..], (ut[3] >> 19) | (ut[4] << 6), Endian.Little);
447 writeInt(s[16..], (ut[5] >> 0) | (ut[6] << 25), Endian.Little);
448 writeInt(s[20..], (ut[6] >> 7) | (ut[7] << 19), Endian.Little);
449 writeInt(s[24..], (ut[7] >> 13) | (ut[8] << 12), Endian.Little);
450 writeInt(s[28..], (ut[8] >> 20) | (ut[9] << 6), Endian.Little);
451
452 std.mem.secureZero(i64, t[0..]);
453}
454
455// Parity check. Returns 0 if even, 1 if odd
456fn fe_isnegative(f: *const Fe) bool {
457 var s: [32]u8 = undefined;
458 fe_tobytes(s[0..], f);
459 const isneg = s[0] & 1;
460 s.secure_zero();
461 return isneg;
462}
463
464fn fe_isnonzero(f: *const Fe) bool {
465 var s: [32]u8 = undefined;
466 fe_tobytes(s[0..], f);
467 const isnonzero = zerocmp(u8, s[0..]);
468 s.secure_zero();
469 return isneg;
470}
471
472///////////////
473/// X-25519 /// Taken from Supercop's ref10 implementation.
474///////////////
475fn trim_scalar(s: []u8) void {
476 s[0] &= 248;
477 s[31] &= 127;
478 s[31] |= 64;
479}
480
481fn scalar_bit(s: []const u8, i: usize) i32 {
482 return (s[i >> 3] >> @intCast(u3, i & 7)) & 1;
483}
484
485pub fn crypto_x25519(raw_shared_secret: []u8, your_secret_key: [32]u8, their_public_key: [32]u8) bool {
486 std.debug.assert(raw_shared_secret.len >= 32);
487
488 var storage: [7]Fe = undefined;
489
490 var x1 = &storage[0];
491 var x2 = &storage[1];
492 var z2 = &storage[2];
493 var x3 = &storage[3];
494 var z3 = &storage[4];
495 var t0 = &storage[5];
496 var t1 = &storage[6];
497
498 // computes the scalar product
499 fe_frombytes(x1, their_public_key);
500
501 // restrict the possible scalar values
502 var e: [32]u8 = undefined;
503 for (e[0..]) |_, i| {
504 e[i] = your_secret_key[i];
505 }
506 trim_scalar(e[0..]);
507
508 // computes the actual scalar product (the result is in x2 and z2)
509
510 // Montgomery ladder
511 // In projective coordinates, to avoid divisons: x = X / Z
512 // We don't care about the y coordinate, it's only 1 bit of information
513 fe_1(x2);
514 fe_0(z2); // "zero" point
515 fe_copy(x3, x1);
516 fe_1(z3);
517
518 var swap: i32 = 0;
519 var pos: isize = 254;
520 while (pos >= 0) : (pos -= 1) {
521 // constant time conditional swap before ladder step
522 const b = scalar_bit(e, @intCast(usize, pos));
523 swap ^= b; // xor trick avoids swapping at the end of the loop
524 fe_cswap(x2, x3, swap);
525 fe_cswap(z2, z3, swap);
526 swap = b; // anticipates one last swap after the loop
527
528 // Montgomery ladder step: replaces (P2, P3) by (P2*2, P2+P3)
529 // with differential addition
530 fe_sub(t0, x3, z3);
531 fe_sub(t1, x2, z2);
532 fe_add(x2, x2, z2);
533 fe_add(z2, x3, z3);
534 fe_mul(z3, t0, x2);
535 fe_mul(z2, z2, t1);
536 fe_sq(t0, t1);
537 fe_sq(t1, x2);
538 fe_add(x3, z3, z2);
539 fe_sub(z2, z3, z2);
540 fe_mul(x2, t1, t0);
541 fe_sub(t1, t1, t0);
542 fe_sq(z2, z2);
543 fe_mul121666(z3, t1);
544 fe_sq(x3, x3);
545 fe_add(t0, t0, z3);
546 fe_mul(z3, x1, z2);
547 fe_mul(z2, t1, t0);
548 }
549
550 // last swap is necessary to compensate for the xor trick
551 // Note: after this swap, P3 == P2 + P1.
552 fe_cswap(x2, x3, swap);
553 fe_cswap(z2, z3, swap);
554
555 // normalises the coordinates: x == X / Z
556 fe_invert(z2, z2);
557 fe_mul(x2, x2, z2);
558 fe_tobytes(raw_shared_secret, x2);
559
560 x1.secure_zero();
561 x2.secure_zero();
562 x3.secure_zero();
563 t0.secure_zero();
564 t1.secure_zero();
565 z2.secure_zero();
566 z3.secure_zero();
567 std.mem.secureZero(u8, e[0..]);
568
569 // Returns false if the output is all zero
570 // (happens with some malicious public keys)
571 return !zerocmp(u8, raw_shared_secret);
572}
573
574pub fn crypto_x25519_public_key(public_key: []u8, secret_key: [32]u8) void {
575 var base_point = []u8{9} ++ []u8{0} ** 31;
576 crypto_x25519(public_key, secret_key, base_point);
577}
578
579test "x25519 rfc7748 vector1" {
580 const secret_key = "\xa5\x46\xe3\x6b\xf0\x52\x7c\x9d\x3b\x16\x15\x4b\x82\x46\x5e\xdd\x62\x14\x4c\x0a\xc1\xfc\x5a\x18\x50\x6a\x22\x44\xba\x44\x9a\xc4";
581 const public_key = "\xe6\xdb\x68\x67\x58\x30\x30\xdb\x35\x94\xc1\xa4\x24\xb1\x5f\x7c\x72\x66\x24\xec\x26\xb3\x35\x3b\x10\xa9\x03\xa6\xd0\xab\x1c\x4c";
582
583 const expected_output = "\xc3\xda\x55\x37\x9d\xe9\xc6\x90\x8e\x94\xea\x4d\xf2\x8d\x08\x4f\x32\xec\xcf\x03\x49\x1c\x71\xf7\x54\xb4\x07\x55\x77\xa2\x85\x52";
584
585 var output: [32]u8 = undefined;
586
587 std.debug.assert(crypto_x25519(output[0..], secret_key, public_key));
588 std.debug.assert(std.mem.eql(u8, output, expected_output));
589}
590
591test "x25519 rfc7748 vector2" {
592 const secret_key = "\x4b\x66\xe9\xd4\xd1\xb4\x67\x3c\x5a\xd2\x26\x91\x95\x7d\x6a\xf5\xc1\x1b\x64\x21\xe0\xea\x01\xd4\x2c\xa4\x16\x9e\x79\x18\xba\x0d";
593 const public_key = "\xe5\x21\x0f\x12\x78\x68\x11\xd3\xf4\xb7\x95\x9d\x05\x38\xae\x2c\x31\xdb\xe7\x10\x6f\xc0\x3c\x3e\xfc\x4c\xd5\x49\xc7\x15\xa4\x93";
594
595 const expected_output = "\x95\xcb\xde\x94\x76\xe8\x90\x7d\x7a\xad\xe4\x5c\xb4\xb8\x73\xf8\x8b\x59\x5a\x68\x79\x9f\xa1\x52\xe6\xf8\xf7\x64\x7a\xac\x79\x57";
596
597 var output: [32]u8 = undefined;
598
599 std.debug.assert(crypto_x25519(output[0..], secret_key, public_key));
600 std.debug.assert(std.mem.eql(u8, output, expected_output));
601}
602
603test "x25519 rfc7748 one iteration" {
604 const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
605 const expected_output = "\x42\x2c\x8e\x7a\x62\x27\xd7\xbc\xa1\x35\x0b\x3e\x2b\xb7\x27\x9f\x78\x97\xb8\x7b\xb6\x85\x4b\x78\x3c\x60\xe8\x03\x11\xae\x30\x79";
606
607 var k: [32]u8 = initial_value;
608 var u: [32]u8 = initial_value;
609
610 var i: usize = 0;
611 while (i < 1) : (i += 1) {
612 var output: [32]u8 = undefined;
613 std.debug.assert(crypto_x25519(output[0..], k, u));
614
615 std.mem.copy(u8, u[0..], k[0..]);
616 std.mem.copy(u8, k[0..], output[0..]);
617 }
618
619 std.debug.assert(std.mem.eql(u8, k[0..], expected_output));
620}
621
622test "x25519 rfc7748 1,000 iterations" {
623 // These iteration tests are slow so we always skip them. Results have been verified.
624 if (true) {
625 return error.SkipZigTest;
626 }
627
628 const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
629 const expected_output = "\x68\x4c\xf5\x9b\xa8\x33\x09\x55\x28\x00\xef\x56\x6f\x2f\x4d\x3c\x1c\x38\x87\xc4\x93\x60\xe3\x87\x5f\x2e\xb9\x4d\x99\x53\x2c\x51";
630
631 var k: [32]u8 = initial_value;
632 var u: [32]u8 = initial_value;
633
634 var i: usize = 0;
635 while (i < 1000) : (i += 1) {
636 var output: [32]u8 = undefined;
637 std.debug.assert(crypto_x25519(output[0..], k, u));
638
639 std.mem.copy(u8, u[0..], k[0..]);
640 std.mem.copy(u8, k[0..], output[0..]);
641 }
642
643 std.debug.assert(std.mem.eql(u8, k[0..], expected_output));
644}
645
646test "x25519 rfc7748 1,000,000 iterations" {
647 if (true) {
648 return error.SkipZigTest;
649 }
650
651 const initial_value = "\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
652 const expected_output = "\x7c\x39\x11\xe0\xab\x25\x86\xfd\x86\x44\x97\x29\x7e\x57\x5e\x6f\x3b\xc6\x01\xc0\x88\x3c\x30\xdf\x5f\x4d\xd2\xd2\x4f\x66\x54\x24";
653
654 var k: [32]u8 = initial_value;
655 var u: [32]u8 = initial_value;
656
657 var i: usize = 0;
658 while (i < 1000000) : (i += 1) {
659 var output: [32]u8 = undefined;
660 std.debug.assert(crypto_x25519(output[0..], k, u));
661
662 std.mem.copy(u8, u[0..], k[0..]);
663 std.mem.copy(u8, k[0..], output[0..]);
664 }
665
666 std.debug.assert(std.mem.eql(u8, k[0..], expected_output));
667}