1const std = @import("std");
2const builtin = @import("builtin");
3const crypto = std.crypto;
4const debug = std.debug;
5const math = std.math;
6const mem = std.mem;
7
8const Poly1305 = crypto.onetimeauth.Poly1305;
9const Blake2b = crypto.hash.blake2.Blake2b;
10const X25519 = crypto.dh.X25519;
11
12const AuthenticationError = crypto.errors.AuthenticationError;
13const IdentityElementError = crypto.errors.IdentityElementError;
14const WeakPublicKeyError = crypto.errors.WeakPublicKeyError;
15
16/// The Salsa cipher with 20 rounds.
17pub const Salsa20 = Salsa(20);
18
19/// The XSalsa cipher with 20 rounds.
20pub const XSalsa20 = XSalsa(20);
21
22fn SalsaVecImpl(comptime rounds: comptime_int) type {
23 return struct {
24 const Lane = @Vector(4, u32);
25 const Half = @Vector(2, u32);
26 const BlockVec = [4]Lane;
27
28 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
29 const c = "expand 32-byte k";
30 const constant_le = comptime [4]u32{
31 mem.readInt(u32, c[0..4], .little),
32 mem.readInt(u32, c[4..8], .little),
33 mem.readInt(u32, c[8..12], .little),
34 mem.readInt(u32, c[12..16], .little),
35 };
36 return BlockVec{
37 Lane{ key[0], key[1], key[2], key[3] },
38 Lane{ key[4], key[5], key[6], key[7] },
39 Lane{ constant_le[0], constant_le[1], constant_le[2], constant_le[3] },
40 Lane{ d[0], d[1], d[2], d[3] },
41 };
42 }
43
44 fn salsaCore(x: *BlockVec, input: BlockVec, comptime feedback: bool) void {
45 const n1n2n3n0 = Lane{ input[3][1], input[3][2], input[3][3], input[3][0] };
46 const n1n2 = Half{ n1n2n3n0[0], n1n2n3n0[1] };
47 const n3n0 = Half{ n1n2n3n0[2], n1n2n3n0[3] };
48 const k0k1 = Half{ input[0][0], input[0][1] };
49 const k2k3 = Half{ input[0][2], input[0][3] };
50 const k4k5 = Half{ input[1][0], input[1][1] };
51 const k6k7 = Half{ input[1][2], input[1][3] };
52 const n0k0 = Half{ n3n0[1], k0k1[0] };
53 const k0n0 = Half{ n0k0[1], n0k0[0] };
54 const k4k5k0n0 = Lane{ k4k5[0], k4k5[1], k0n0[0], k0n0[1] };
55 const k1k6 = Half{ k0k1[1], k6k7[0] };
56 const k6k1 = Half{ k1k6[1], k1k6[0] };
57 const n1n2k6k1 = Lane{ n1n2[0], n1n2[1], k6k1[0], k6k1[1] };
58 const k7n3 = Half{ k6k7[1], n3n0[0] };
59 const n3k7 = Half{ k7n3[1], k7n3[0] };
60 const k2k3n3k7 = Lane{ k2k3[0], k2k3[1], n3k7[0], n3k7[1] };
61
62 var diag0 = input[2];
63 var diag1 = @shuffle(u32, k4k5k0n0, undefined, [_]i32{ 1, 2, 3, 0 });
64 var diag2 = @shuffle(u32, n1n2k6k1, undefined, [_]i32{ 1, 2, 3, 0 });
65 var diag3 = @shuffle(u32, k2k3n3k7, undefined, [_]i32{ 1, 2, 3, 0 });
66
67 const start0 = diag0;
68 const start1 = diag1;
69 const start2 = diag2;
70 const start3 = diag3;
71
72 var i: usize = 0;
73 while (i < rounds) : (i += 2) {
74 diag3 ^= math.rotl(Lane, diag1 +% diag0, 7);
75 diag2 ^= math.rotl(Lane, diag0 +% diag3, 9);
76 diag1 ^= math.rotl(Lane, diag3 +% diag2, 13);
77 diag0 ^= math.rotl(Lane, diag2 +% diag1, 18);
78
79 diag3 = @shuffle(u32, diag3, undefined, [_]i32{ 3, 0, 1, 2 });
80 diag2 = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 });
81 diag1 = @shuffle(u32, diag1, undefined, [_]i32{ 1, 2, 3, 0 });
82
83 diag1 ^= math.rotl(Lane, diag3 +% diag0, 7);
84 diag2 ^= math.rotl(Lane, diag0 +% diag1, 9);
85 diag3 ^= math.rotl(Lane, diag1 +% diag2, 13);
86 diag0 ^= math.rotl(Lane, diag2 +% diag3, 18);
87
88 diag1 = @shuffle(u32, diag1, undefined, [_]i32{ 3, 0, 1, 2 });
89 diag2 = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 });
90 diag3 = @shuffle(u32, diag3, undefined, [_]i32{ 1, 2, 3, 0 });
91 }
92
93 if (feedback) {
94 diag0 +%= start0;
95 diag1 +%= start1;
96 diag2 +%= start2;
97 diag3 +%= start3;
98 }
99
100 const x0x1x10x11 = Lane{ diag0[0], diag1[1], diag0[2], diag1[3] };
101 const x12x13x6x7 = Lane{ diag1[0], diag2[1], diag1[2], diag2[3] };
102 const x8x9x2x3 = Lane{ diag2[0], diag3[1], diag2[2], diag3[3] };
103 const x4x5x14x15 = Lane{ diag3[0], diag0[1], diag3[2], diag0[3] };
104
105 x[0] = Lane{ x0x1x10x11[0], x0x1x10x11[1], x8x9x2x3[2], x8x9x2x3[3] };
106 x[1] = Lane{ x4x5x14x15[0], x4x5x14x15[1], x12x13x6x7[2], x12x13x6x7[3] };
107 x[2] = Lane{ x8x9x2x3[0], x8x9x2x3[1], x0x1x10x11[2], x0x1x10x11[3] };
108 x[3] = Lane{ x12x13x6x7[0], x12x13x6x7[1], x4x5x14x15[2], x4x5x14x15[3] };
109 }
110
111 fn hashToBytes(out: *[64]u8, x: BlockVec) void {
112 var i: usize = 0;
113 while (i < 4) : (i += 1) {
114 mem.writeInt(u32, out[16 * i + 0 ..][0..4], x[i][0], .little);
115 mem.writeInt(u32, out[16 * i + 4 ..][0..4], x[i][1], .little);
116 mem.writeInt(u32, out[16 * i + 8 ..][0..4], x[i][2], .little);
117 mem.writeInt(u32, out[16 * i + 12 ..][0..4], x[i][3], .little);
118 }
119 }
120
121 fn salsaXor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
122 var ctx = initContext(key, d);
123 var x: BlockVec = undefined;
124 var buf: [64]u8 = undefined;
125 var i: usize = 0;
126 while (i + 64 <= in.len) : (i += 64) {
127 salsaCore(x[0..], ctx, true);
128 hashToBytes(buf[0..], x);
129 var xout = out[i..];
130 const xin = in[i..];
131 var j: usize = 0;
132 while (j < 64) : (j += 1) {
133 xout[j] = xin[j];
134 }
135 j = 0;
136 while (j < 64) : (j += 1) {
137 xout[j] ^= buf[j];
138 }
139 ctx[3][2] +%= 1;
140 if (ctx[3][2] == 0) {
141 ctx[3][3] += 1;
142 }
143 }
144 if (i < in.len) {
145 salsaCore(x[0..], ctx, true);
146 hashToBytes(buf[0..], x);
147
148 var xout = out[i..];
149 const xin = in[i..];
150 var j: usize = 0;
151 while (j < in.len % 64) : (j += 1) {
152 xout[j] = xin[j] ^ buf[j];
153 }
154 }
155 }
156
157 fn hsalsa(input: [16]u8, key: [32]u8) [32]u8 {
158 var c: [4]u32 = undefined;
159 for (c, 0..) |_, i| {
160 c[i] = mem.readInt(u32, input[4 * i ..][0..4], .little);
161 }
162 const ctx = initContext(keyToWords(key), c);
163 var x: BlockVec = undefined;
164 salsaCore(x[0..], ctx, false);
165 var out: [32]u8 = undefined;
166 mem.writeInt(u32, out[0..4], x[0][0], .little);
167 mem.writeInt(u32, out[4..8], x[1][1], .little);
168 mem.writeInt(u32, out[8..12], x[2][2], .little);
169 mem.writeInt(u32, out[12..16], x[3][3], .little);
170 mem.writeInt(u32, out[16..20], x[1][2], .little);
171 mem.writeInt(u32, out[20..24], x[1][3], .little);
172 mem.writeInt(u32, out[24..28], x[2][0], .little);
173 mem.writeInt(u32, out[28..32], x[2][1], .little);
174 return out;
175 }
176 };
177}
178
179fn SalsaNonVecImpl(comptime rounds: comptime_int) type {
180 return struct {
181 const BlockVec = [16]u32;
182
183 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
184 const c = "expand 32-byte k";
185 const constant_le = comptime [4]u32{
186 mem.readInt(u32, c[0..4], .little),
187 mem.readInt(u32, c[4..8], .little),
188 mem.readInt(u32, c[8..12], .little),
189 mem.readInt(u32, c[12..16], .little),
190 };
191 return BlockVec{
192 constant_le[0], key[0], key[1], key[2],
193 key[3], constant_le[1], d[0], d[1],
194 d[2], d[3], constant_le[2], key[4],
195 key[5], key[6], key[7], constant_le[3],
196 };
197 }
198
199 const QuarterRound = struct {
200 a: usize,
201 b: usize,
202 c: usize,
203 d: u6,
204 };
205
206 fn Rp(a: usize, b: usize, c: usize, d: u6) QuarterRound {
207 return QuarterRound{
208 .a = a,
209 .b = b,
210 .c = c,
211 .d = d,
212 };
213 }
214
215 fn salsaCore(x: *BlockVec, input: BlockVec, comptime feedback: bool) void {
216 const arx_steps = comptime [_]QuarterRound{
217 Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18),
218 Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18),
219 Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18),
220 Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18),
221 Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18),
222 Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18),
223 Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18),
224 Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),
225 };
226 x.* = input;
227 var j: usize = 0;
228 while (j < rounds) : (j += 2) {
229 inline for (arx_steps) |r| {
230 x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);
231 }
232 }
233 if (feedback) {
234 j = 0;
235 while (j < 16) : (j += 1) {
236 x[j] +%= input[j];
237 }
238 }
239 }
240
241 fn hashToBytes(out: *[64]u8, x: BlockVec) void {
242 for (x, 0..) |w, i| {
243 mem.writeInt(u32, out[i * 4 ..][0..4], w, .little);
244 }
245 }
246
247 fn salsaXor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
248 var ctx = initContext(key, d);
249 var x: BlockVec = undefined;
250 var buf: [64]u8 = undefined;
251 var i: usize = 0;
252 while (i + 64 <= in.len) : (i += 64) {
253 salsaCore(x[0..], ctx, true);
254 hashToBytes(buf[0..], x);
255 var xout = out[i..];
256 const xin = in[i..];
257 var j: usize = 0;
258 while (j < 64) : (j += 1) {
259 xout[j] = xin[j];
260 }
261 j = 0;
262 while (j < 64) : (j += 1) {
263 xout[j] ^= buf[j];
264 }
265 const ov = @addWithOverflow(ctx[8], 1);
266 ctx[8] = ov[0];
267 ctx[9] += ov[1];
268 }
269 if (i < in.len) {
270 salsaCore(x[0..], ctx, true);
271 hashToBytes(buf[0..], x);
272
273 var xout = out[i..];
274 const xin = in[i..];
275 var j: usize = 0;
276 while (j < in.len % 64) : (j += 1) {
277 xout[j] = xin[j] ^ buf[j];
278 }
279 }
280 }
281
282 fn hsalsa(input: [16]u8, key: [32]u8) [32]u8 {
283 var c: [4]u32 = undefined;
284 for (c, 0..) |_, i| {
285 c[i] = mem.readInt(u32, input[4 * i ..][0..4], .little);
286 }
287 const ctx = initContext(keyToWords(key), c);
288 var x: BlockVec = undefined;
289 salsaCore(x[0..], ctx, false);
290 var out: [32]u8 = undefined;
291 mem.writeInt(u32, out[0..4], x[0], .little);
292 mem.writeInt(u32, out[4..8], x[5], .little);
293 mem.writeInt(u32, out[8..12], x[10], .little);
294 mem.writeInt(u32, out[12..16], x[15], .little);
295 mem.writeInt(u32, out[16..20], x[6], .little);
296 mem.writeInt(u32, out[20..24], x[7], .little);
297 mem.writeInt(u32, out[24..28], x[8], .little);
298 mem.writeInt(u32, out[28..32], x[9], .little);
299 return out;
300 }
301 };
302}
303
304const SalsaImpl = if (builtin.cpu.arch == .x86_64)
305 SalsaVecImpl
306else
307 SalsaNonVecImpl;
308
309fn keyToWords(key: [32]u8) [8]u32 {
310 var k: [8]u32 = undefined;
311 var i: usize = 0;
312 while (i < 8) : (i += 1) {
313 k[i] = mem.readInt(u32, key[i * 4 ..][0..4], .little);
314 }
315 return k;
316}
317
318fn extend(comptime rounds: comptime_int, key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } {
319 return .{
320 .key = SalsaImpl(rounds).hsalsa(nonce[0..16].*, key),
321 .nonce = nonce[16..24].*,
322 };
323}
324
325/// The Salsa stream cipher.
326pub fn Salsa(comptime rounds: comptime_int) type {
327 return struct {
328 /// Nonce length in bytes.
329 pub const nonce_length = 8;
330 /// Key length in bytes.
331 pub const key_length = 32;
332
333 /// Add the output of the Salsa stream cipher to `in` and stores the result into `out`.
334 /// WARNING: This function doesn't provide authenticated encryption.
335 /// Using the AEAD or one of the `box` versions is usually preferred.
336 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
337 debug.assert(in.len == out.len);
338
339 var d: [4]u32 = undefined;
340 d[0] = mem.readInt(u32, nonce[0..4], .little);
341 d[1] = mem.readInt(u32, nonce[4..8], .little);
342 d[2] = @as(u32, @truncate(counter));
343 d[3] = @as(u32, @truncate(counter >> 32));
344 SalsaImpl(rounds).salsaXor(out, in, keyToWords(key), d);
345 }
346 };
347}
348
349/// The XSalsa stream cipher.
350pub fn XSalsa(comptime rounds: comptime_int) type {
351 return struct {
352 /// Nonce length in bytes.
353 pub const nonce_length = 24;
354 /// Key length in bytes.
355 pub const key_length = 32;
356
357 /// Add the output of the XSalsa stream cipher to `in` and stores the result into `out`.
358 /// WARNING: This function doesn't provide authenticated encryption.
359 /// Using the AEAD or one of the `box` versions is usually preferred.
360 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
361 const extended = extend(rounds, key, nonce);
362 Salsa(rounds).xor(out, in, counter, extended.key, extended.nonce);
363 }
364 };
365}
366
367/// The XSalsa stream cipher, combined with the Poly1305 MAC
368pub const XSalsa20Poly1305 = struct {
369 /// Authentication tag length in bytes.
370 pub const tag_length = Poly1305.mac_length;
371 /// Nonce length in bytes.
372 pub const nonce_length = XSalsa20.nonce_length;
373 /// Key length in bytes.
374 pub const key_length = XSalsa20.key_length;
375
376 const rounds = 20;
377
378 /// c: ciphertext: output buffer should be of size m.len
379 /// tag: authentication tag: output MAC
380 /// m: message
381 /// ad: Associated Data (see below)
382 /// npub: public nonce
383 /// k: private key
384 ///
385 /// With this construction, if the associated data is not empty,
386 /// it must only contain fixed-length information, such as
387 /// session identifiers and sequence numbers.
388 ///
389 /// Since there is no separation between the associated data and
390 /// the ciphertext, its length must not be under an adversary's
391 /// control.
392 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
393 debug.assert(c.len == m.len);
394 const extended = extend(rounds, k, npub);
395 var block0: [64]u8 = @splat(0);
396 const mlen0 = @min(32, m.len);
397 @memcpy(block0[32..][0..mlen0], m[0..mlen0]);
398 Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
399 @memcpy(c[0..mlen0], block0[32..][0..mlen0]);
400 Salsa20.xor(c[mlen0..], m[mlen0..], 1, extended.key, extended.nonce);
401 var mac = Poly1305.init(block0[0..32]);
402 mac.update(ad);
403 mac.update(c);
404 mac.final(tag);
405 }
406
407 /// `m`: Message
408 /// `c`: Ciphertext
409 /// `tag`: Authentication tag
410 /// `ad`: Associated data (see below)
411 /// `npub`: Public nonce
412 /// `k`: Private key
413 /// Asserts `c.len == m.len`.
414 ///
415 /// With this construction, if the associated data is not empty,
416 /// it must only contain fixed-length information, such as
417 /// sessions identifiers and sequence numbers.
418 ///
419 /// Since there is no separation between the associated data and
420 /// the ciphertext, its length must not be under an adversary's
421 /// control.
422 ///
423 /// Contents of `m` are undefined if an error is returned.
424 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
425 debug.assert(c.len == m.len);
426 const extended = extend(rounds, k, npub);
427 var block0: [64]u8 = @splat(0);
428 const mlen0 = @min(32, c.len);
429 @memcpy(block0[32..][0..mlen0], c[0..mlen0]);
430 Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
431 var mac = Poly1305.init(block0[0..32]);
432 mac.update(ad);
433 mac.update(c);
434 var computed_tag: [tag_length]u8 = undefined;
435 mac.final(&computed_tag);
436
437 const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
438 if (!verify) {
439 crypto.secureZero(u8, &computed_tag);
440 @memset(m, undefined);
441 return error.AuthenticationFailed;
442 }
443 @memcpy(m[0..mlen0], block0[32..][0..mlen0]);
444 Salsa20.xor(m[mlen0..], c[mlen0..], 1, extended.key, extended.nonce);
445 }
446};
447
448/// NaCl-compatible secretbox API.
449///
450/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
451/// A secret key shared by all the recipients must be already known in order to use this API.
452///
453/// Nonces are 192-bit large and can safely be chosen with a random number generator.
454pub const SecretBox = struct {
455 /// Key length in bytes.
456 pub const key_length = XSalsa20Poly1305.key_length;
457 /// Nonce length in bytes.
458 pub const nonce_length = XSalsa20Poly1305.nonce_length;
459 /// Authentication tag length in bytes.
460 pub const tag_length = XSalsa20Poly1305.tag_length;
461
462 /// Encrypt and authenticate `m` using a nonce `npub` and a key `k`.
463 /// `c` must be exactly `tag_length` longer than `m`, as it will store both the ciphertext and the authentication tag.
464 pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
465 debug.assert(c.len == tag_length + m.len);
466 XSalsa20Poly1305.encrypt(c[tag_length..], c[0..tag_length], m, "", npub, k);
467 }
468
469 /// Verify and decrypt `c` using a nonce `npub` and a key `k`.
470 /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message.
471 pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
472 if (c.len < tag_length) {
473 return error.AuthenticationFailed;
474 }
475 debug.assert(m.len == c.len - tag_length);
476 return XSalsa20Poly1305.decrypt(m, c[tag_length..], c[0..tag_length].*, "", npub, k);
477 }
478};
479
480/// NaCl-compatible box API.
481///
482/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
483/// This construction uses public-key cryptography. A shared secret doesn't have to be known in advance by both parties.
484/// Instead, a message is encrypted using a sender's secret key and a recipient's public key,
485/// and is decrypted using the recipient's secret key and the sender's public key.
486///
487/// Nonces are 192-bit large and can safely be chosen with a random number generator.
488pub const Box = struct {
489 /// Public key length in bytes.
490 pub const public_length = X25519.public_length;
491 /// Secret key length in bytes.
492 pub const secret_length = X25519.secret_length;
493 /// Shared key length in bytes.
494 pub const shared_length = XSalsa20Poly1305.key_length;
495 /// Seed (for key pair creation) length in bytes.
496 pub const seed_length = X25519.seed_length;
497 /// Nonce length in bytes.
498 pub const nonce_length = XSalsa20Poly1305.nonce_length;
499 /// Authentication tag length in bytes.
500 pub const tag_length = XSalsa20Poly1305.tag_length;
501
502 /// A key pair.
503 pub const KeyPair = X25519.KeyPair;
504
505 /// Compute a secret suitable for `secretbox` given a recipient's public key and a sender's secret key.
506 pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)![shared_length]u8 {
507 const p = try X25519.scalarmult(secret_key, public_key);
508 const zero: [16]u8 = @splat(0);
509 return SalsaImpl(20).hsalsa(zero, p);
510 }
511
512 /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`.
513 pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)!void {
514 const shared_key = try createSharedSecret(public_key, secret_key);
515 return SecretBox.seal(c, m, npub, shared_key);
516 }
517
518 /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`.
519 pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError || AuthenticationError)!void {
520 const shared_key = try createSharedSecret(public_key, secret_key);
521 return SecretBox.open(m, c, npub, shared_key);
522 }
523};
524
525/// libsodium-compatible sealed boxes
526///
527/// Sealed boxes are designed to anonymously send messages to a recipient given their public key.
528/// Only the recipient can decrypt these messages, using their private key.
529/// While the recipient can verify the integrity of the message, it cannot verify the identity of the sender.
530///
531/// A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption process.
532pub const SealedBox = struct {
533 pub const public_length = Box.public_length;
534 pub const secret_length = Box.secret_length;
535 pub const seed_length = Box.seed_length;
536 pub const seal_length = Box.public_length + Box.tag_length;
537
538 /// A key pair.
539 pub const KeyPair = Box.KeyPair;
540
541 fn createNonce(pk1: [public_length]u8, pk2: [public_length]u8) [Box.nonce_length]u8 {
542 var hasher = Blake2b(Box.nonce_length * 8).init(.{});
543 hasher.update(&pk1);
544 hasher.update(&pk2);
545 var nonce: [Box.nonce_length]u8 = undefined;
546 hasher.final(&nonce);
547 return nonce;
548 }
549
550 /// Encrypt a message `m` for a recipient whose public key is `public_key`.
551 /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
552 pub fn seal(io: std.Io, c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
553 debug.assert(c.len == m.len + seal_length);
554 var ekp = KeyPair.generate(io);
555 const nonce = createNonce(ekp.public_key, public_key);
556 c[0..public_length].* = ekp.public_key;
557 try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);
558 crypto.secureZero(u8, ekp.secret_key[0..]);
559 }
560
561 /// Decrypt a message using a key pair.
562 /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata.
563 pub fn open(m: []u8, c: []const u8, keypair: KeyPair) (IdentityElementError || WeakPublicKeyError || AuthenticationError)!void {
564 if (c.len < seal_length) {
565 return error.AuthenticationFailed;
566 }
567 const epk = c[0..public_length];
568 const nonce = createNonce(epk.*, keypair.public_key);
569 return Box.open(m, c[public_length..], nonce, epk.*, keypair.secret_key);
570 }
571};
572
573const htest = @import("test.zig");
574
575test "(x)salsa20" {
576 const key: [32]u8 = @splat(0x69);
577 const nonce: [8]u8 = @splat(0x42);
578 const msg: [20]u8 = @splat(0);
579 var c: [msg.len]u8 = undefined;
580
581 Salsa20.xor(&c, msg[0..], 0, key, nonce);
582 try htest.assertEqual("30ff9933aa6534ff5207142593cd1fca4b23bdd8", c[0..]);
583
584 const extended_nonce: [24]u8 = @splat(0x42);
585 XSalsa20.xor(&c, msg[0..], 0, key, extended_nonce);
586 try htest.assertEqual("b4ab7d82e750ec07644fa3281bce6cd91d4243f9", c[0..]);
587}
588
589test "xsalsa20poly1305" {
590 const io = std.testing.io;
591 var msg: [100]u8 = undefined;
592 var msg2: [msg.len]u8 = undefined;
593 var c: [msg.len]u8 = undefined;
594 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
595 var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
596 var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
597 io.random(&msg);
598 io.random(&key);
599 io.random(&nonce);
600
601 XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
602 try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
603}
604
605test "xsalsa20poly1305 secretbox" {
606 const io = std.testing.io;
607 var msg: [100]u8 = undefined;
608 var msg2: [msg.len]u8 = undefined;
609 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
610 var nonce: [Box.nonce_length]u8 = undefined;
611 var boxed: [msg.len + Box.tag_length]u8 = undefined;
612 io.random(&msg);
613 io.random(&key);
614 io.random(&nonce);
615
616 SecretBox.seal(boxed[0..], msg[0..], nonce, key);
617 try SecretBox.open(msg2[0..], boxed[0..], nonce, key);
618}
619
620test "xsalsa20poly1305 box" {
621 const io = std.testing.io;
622 var msg: [100]u8 = undefined;
623 var msg2: [msg.len]u8 = undefined;
624 var nonce: [Box.nonce_length]u8 = undefined;
625 var boxed: [msg.len + Box.tag_length]u8 = undefined;
626 io.random(&msg);
627 io.random(&nonce);
628
629 const kp1 = Box.KeyPair.generate(io);
630 const kp2 = Box.KeyPair.generate(io);
631 try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
632 try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
633}
634
635test "xsalsa20poly1305 sealedbox" {
636 const io = std.testing.io;
637 var msg: [100]u8 = undefined;
638 var msg2: [msg.len]u8 = undefined;
639 var boxed: [msg.len + SealedBox.seal_length]u8 = undefined;
640 io.random(&msg);
641
642 const kp = Box.KeyPair.generate(io);
643 try SealedBox.seal(io, boxed[0..], msg[0..], kp.public_key);
644 try SealedBox.open(msg2[0..], boxed[0..], kp);
645}
646
647test "secretbox twoblocks" {
648 const key = [_]u8{ 0xc9, 0xc9, 0x4d, 0xcf, 0x68, 0xbe, 0x00, 0xe4, 0x7f, 0xe6, 0x13, 0x26, 0xfc, 0xc4, 0x2f, 0xd0, 0xdb, 0x93, 0x91, 0x1c, 0x09, 0x94, 0x89, 0xe1, 0x1b, 0x88, 0x63, 0x18, 0x86, 0x64, 0x8b, 0x7b };
649 const nonce = [_]u8{ 0xa4, 0x33, 0xe9, 0x0a, 0x07, 0x68, 0x6e, 0x9a, 0x2b, 0x6d, 0xd4, 0x59, 0x04, 0x72, 0x3e, 0xd3, 0x8a, 0x67, 0x55, 0xc7, 0x9e, 0x3e, 0x77, 0xdc };
650 const msg: [97]u8 = @splat('a');
651 var ciphertext: [msg.len + SecretBox.tag_length]u8 = undefined;
652 SecretBox.seal(&ciphertext, &msg, nonce, key);
653 try htest.assertEqual("b05760e217288ba079caa2fd57fd3701784974ffcfda20fe523b89211ad8af065a6eb37cdb29d51aca5bd75dafdd21d18b044c54bb7c526cf576c94ee8900f911ceab0147e82b667a28c52d58ceb29554ff45471224d37b03256b01c119b89ff6d36855de8138d103386dbc9d971f52261", &ciphertext);
654}