master
  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
382    /// npub: public nonce
383    /// k: private key
384    pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
385        debug.assert(c.len == m.len);
386        const extended = extend(rounds, k, npub);
387        var block0 = [_]u8{0} ** 64;
388        const mlen0 = @min(32, m.len);
389        @memcpy(block0[32..][0..mlen0], m[0..mlen0]);
390        Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
391        @memcpy(c[0..mlen0], block0[32..][0..mlen0]);
392        Salsa20.xor(c[mlen0..], m[mlen0..], 1, extended.key, extended.nonce);
393        var mac = Poly1305.init(block0[0..32]);
394        mac.update(ad);
395        mac.update(c);
396        mac.final(tag);
397    }
398
399    /// `m`: Message
400    /// `c`: Ciphertext
401    /// `tag`: Authentication tag
402    /// `ad`: Associated data
403    /// `npub`: Public nonce
404    /// `k`: Private key
405    /// Asserts `c.len == m.len`.
406    ///
407    /// Contents of `m` are undefined if an error is returned.
408    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 {
409        debug.assert(c.len == m.len);
410        const extended = extend(rounds, k, npub);
411        var block0 = [_]u8{0} ** 64;
412        const mlen0 = @min(32, c.len);
413        @memcpy(block0[32..][0..mlen0], c[0..mlen0]);
414        Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
415        var mac = Poly1305.init(block0[0..32]);
416        mac.update(ad);
417        mac.update(c);
418        var computed_tag: [tag_length]u8 = undefined;
419        mac.final(&computed_tag);
420
421        const verify = crypto.timing_safe.eql([tag_length]u8, computed_tag, tag);
422        if (!verify) {
423            crypto.secureZero(u8, &computed_tag);
424            @memset(m, undefined);
425            return error.AuthenticationFailed;
426        }
427        @memcpy(m[0..mlen0], block0[32..][0..mlen0]);
428        Salsa20.xor(m[mlen0..], c[mlen0..], 1, extended.key, extended.nonce);
429    }
430};
431
432/// NaCl-compatible secretbox API.
433///
434/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
435/// A secret key shared by all the recipients must be already known in order to use this API.
436///
437/// Nonces are 192-bit large and can safely be chosen with a random number generator.
438pub const SecretBox = struct {
439    /// Key length in bytes.
440    pub const key_length = XSalsa20Poly1305.key_length;
441    /// Nonce length in bytes.
442    pub const nonce_length = XSalsa20Poly1305.nonce_length;
443    /// Authentication tag length in bytes.
444    pub const tag_length = XSalsa20Poly1305.tag_length;
445
446    /// Encrypt and authenticate `m` using a nonce `npub` and a key `k`.
447    /// `c` must be exactly `tag_length` longer than `m`, as it will store both the ciphertext and the authentication tag.
448    pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
449        debug.assert(c.len == tag_length + m.len);
450        XSalsa20Poly1305.encrypt(c[tag_length..], c[0..tag_length], m, "", npub, k);
451    }
452
453    /// Verify and decrypt `c` using a nonce `npub` and a key `k`.
454    /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message.
455    pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) AuthenticationError!void {
456        if (c.len < tag_length) {
457            return error.AuthenticationFailed;
458        }
459        debug.assert(m.len == c.len - tag_length);
460        return XSalsa20Poly1305.decrypt(m, c[tag_length..], c[0..tag_length].*, "", npub, k);
461    }
462};
463
464/// NaCl-compatible box API.
465///
466/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
467/// This construction uses public-key cryptography. A shared secret doesn't have to be known in advance by both parties.
468/// Instead, a message is encrypted using a sender's secret key and a recipient's public key,
469/// and is decrypted using the recipient's secret key and the sender's public key.
470///
471/// Nonces are 192-bit large and can safely be chosen with a random number generator.
472pub const Box = struct {
473    /// Public key length in bytes.
474    pub const public_length = X25519.public_length;
475    /// Secret key length in bytes.
476    pub const secret_length = X25519.secret_length;
477    /// Shared key length in bytes.
478    pub const shared_length = XSalsa20Poly1305.key_length;
479    /// Seed (for key pair creation) length in bytes.
480    pub const seed_length = X25519.seed_length;
481    /// Nonce length in bytes.
482    pub const nonce_length = XSalsa20Poly1305.nonce_length;
483    /// Authentication tag length in bytes.
484    pub const tag_length = XSalsa20Poly1305.tag_length;
485
486    /// A key pair.
487    pub const KeyPair = X25519.KeyPair;
488
489    /// Compute a secret suitable for `secretbox` given a recipient's public key and a sender's secret key.
490    pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)![shared_length]u8 {
491        const p = try X25519.scalarmult(secret_key, public_key);
492        const zero = [_]u8{0} ** 16;
493        return SalsaImpl(20).hsalsa(zero, p);
494    }
495
496    /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`.
497    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 {
498        const shared_key = try createSharedSecret(public_key, secret_key);
499        return SecretBox.seal(c, m, npub, shared_key);
500    }
501
502    /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`.
503    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 {
504        const shared_key = try createSharedSecret(public_key, secret_key);
505        return SecretBox.open(m, c, npub, shared_key);
506    }
507};
508
509/// libsodium-compatible sealed boxes
510///
511/// Sealed boxes are designed to anonymously send messages to a recipient given their public key.
512/// Only the recipient can decrypt these messages, using their private key.
513/// While the recipient can verify the integrity of the message, it cannot verify the identity of the sender.
514///
515/// A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption process.
516pub const SealedBox = struct {
517    pub const public_length = Box.public_length;
518    pub const secret_length = Box.secret_length;
519    pub const seed_length = Box.seed_length;
520    pub const seal_length = Box.public_length + Box.tag_length;
521
522    /// A key pair.
523    pub const KeyPair = Box.KeyPair;
524
525    fn createNonce(pk1: [public_length]u8, pk2: [public_length]u8) [Box.nonce_length]u8 {
526        var hasher = Blake2b(Box.nonce_length * 8).init(.{});
527        hasher.update(&pk1);
528        hasher.update(&pk2);
529        var nonce: [Box.nonce_length]u8 = undefined;
530        hasher.final(&nonce);
531        return nonce;
532    }
533
534    /// Encrypt a message `m` for a recipient whose public key is `public_key`.
535    /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
536    pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void {
537        debug.assert(c.len == m.len + seal_length);
538        var ekp = KeyPair.generate();
539        const nonce = createNonce(ekp.public_key, public_key);
540        c[0..public_length].* = ekp.public_key;
541        try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key);
542        crypto.secureZero(u8, ekp.secret_key[0..]);
543    }
544
545    /// Decrypt a message using a key pair.
546    /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata.
547    pub fn open(m: []u8, c: []const u8, keypair: KeyPair) (IdentityElementError || WeakPublicKeyError || AuthenticationError)!void {
548        if (c.len < seal_length) {
549            return error.AuthenticationFailed;
550        }
551        const epk = c[0..public_length];
552        const nonce = createNonce(epk.*, keypair.public_key);
553        return Box.open(m, c[public_length..], nonce, epk.*, keypair.secret_key);
554    }
555};
556
557const htest = @import("test.zig");
558
559test "(x)salsa20" {
560    if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
561
562    const key = [_]u8{0x69} ** 32;
563    const nonce = [_]u8{0x42} ** 8;
564    const msg = [_]u8{0} ** 20;
565    var c: [msg.len]u8 = undefined;
566
567    Salsa20.xor(&c, msg[0..], 0, key, nonce);
568    try htest.assertEqual("30ff9933aa6534ff5207142593cd1fca4b23bdd8", c[0..]);
569
570    const extended_nonce = [_]u8{0x42} ** 24;
571    XSalsa20.xor(&c, msg[0..], 0, key, extended_nonce);
572    try htest.assertEqual("b4ab7d82e750ec07644fa3281bce6cd91d4243f9", c[0..]);
573}
574
575test "xsalsa20poly1305" {
576    var msg: [100]u8 = undefined;
577    var msg2: [msg.len]u8 = undefined;
578    var c: [msg.len]u8 = undefined;
579    var key: [XSalsa20Poly1305.key_length]u8 = undefined;
580    var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
581    var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
582    crypto.random.bytes(&msg);
583    crypto.random.bytes(&key);
584    crypto.random.bytes(&nonce);
585
586    XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
587    try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
588}
589
590test "xsalsa20poly1305 secretbox" {
591    var msg: [100]u8 = undefined;
592    var msg2: [msg.len]u8 = undefined;
593    var key: [XSalsa20Poly1305.key_length]u8 = undefined;
594    var nonce: [Box.nonce_length]u8 = undefined;
595    var boxed: [msg.len + Box.tag_length]u8 = undefined;
596    crypto.random.bytes(&msg);
597    crypto.random.bytes(&key);
598    crypto.random.bytes(&nonce);
599
600    SecretBox.seal(boxed[0..], msg[0..], nonce, key);
601    try SecretBox.open(msg2[0..], boxed[0..], nonce, key);
602}
603
604test "xsalsa20poly1305 box" {
605    if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
606
607    var msg: [100]u8 = undefined;
608    var msg2: [msg.len]u8 = undefined;
609    var nonce: [Box.nonce_length]u8 = undefined;
610    var boxed: [msg.len + Box.tag_length]u8 = undefined;
611    crypto.random.bytes(&msg);
612    crypto.random.bytes(&nonce);
613
614    const kp1 = Box.KeyPair.generate();
615    const kp2 = Box.KeyPair.generate();
616    try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
617    try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
618}
619
620test "xsalsa20poly1305 sealedbox" {
621    if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299
622
623    var msg: [100]u8 = undefined;
624    var msg2: [msg.len]u8 = undefined;
625    var boxed: [msg.len + SealedBox.seal_length]u8 = undefined;
626    crypto.random.bytes(&msg);
627
628    const kp = Box.KeyPair.generate();
629    try SealedBox.seal(boxed[0..], msg[0..], kp.public_key);
630    try SealedBox.open(msg2[0..], boxed[0..], kp);
631}
632
633test "secretbox twoblocks" {
634    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 };
635    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 };
636    const msg = [_]u8{'a'} ** 97;
637    var ciphertext: [msg.len + SecretBox.tag_length]u8 = undefined;
638    SecretBox.seal(&ciphertext, &msg, nonce, key);
639    try htest.assertEqual("b05760e217288ba079caa2fd57fd3701784974ffcfda20fe523b89211ad8af065a6eb37cdb29d51aca5bd75dafdd21d18b044c54bb7c526cf576c94ee8900f911ceab0147e82b667a28c52d58ceb29554ff45471224d37b03256b01c119b89ff6d36855de8138d103386dbc9d971f52261", &ciphertext);
640}