authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-10-25 20:34:35-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-10-25 20:34:35-04:00
log0088efc4b22645698faf328369a1deca2dc9070f
tree69f2fa8dd0325331f4a622bef8f545a18379e354
parentbbd1e122d443dce8342a82a91f529fdec0007d46
parent0c7a99b38d2fb881cde5125ef0729ff0427c06a6
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #6809 from jedisct1/salsa

std/crypto: add (X)Salsa20 and NaCl boxes

5 files changed, 553 insertions(+), 98 deletions(-)

lib/std/crypto.zig+13-4
......@@ -6,15 +6,14 @@
66
77/// Authenticated Encryption with Associated Data
88pub const aead = struct {
9 const chacha20 = @import("crypto/chacha20.zig");
10
119 pub const Gimli = @import("crypto/gimli.zig").Aead;
12 pub const ChaCha20Poly1305 = chacha20.Chacha20Poly1305;
13 pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305;
10 pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305;
11 pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305;
1412 pub const Aegis128L = @import("crypto/aegis.zig").Aegis128L;
1513 pub const Aegis256 = @import("crypto/aegis.zig").Aegis256;
1614 pub const Aes128Gcm = @import("crypto/aes_gcm.zig").Aes128Gcm;
1715 pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm;
16 pub const XSalsa20Poly1305 = @import("crypto/salsa20.zig").XSalsa20Poly1305;
1817};
1918
2019/// Authentication (MAC) functions.
......@@ -101,6 +100,15 @@ pub const stream = struct {
101100 pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF;
102101 pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF;
103102 pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce;
103 pub const Salsa20 = @import("crypto/salsa20.zig").Salsa20;
104 pub const XSalsa20 = @import("crypto/salsa20.zig").XSalsa20;
105};
106
107pub const nacl = struct {
108 const salsa20 = @import("crypto/salsa20.zig");
109 pub const box = salsa20.box;
110 pub const secretBox = salsa20.secretBox;
111 pub const sealedBox = salsa20.sealedBox;
104112};
105113
106114const std = @import("std.zig");
......@@ -134,6 +142,7 @@ test "crypto" {
134142 _ = @import("crypto/sha1.zig");
135143 _ = @import("crypto/sha2.zig");
136144 _ = @import("crypto/sha3.zig");
145 _ = @import("crypto/salsa20.zig");
137146 _ = @import("crypto/siphash.zig");
138147 _ = @import("crypto/25519/curve25519.zig");
139148 _ = @import("crypto/25519/ed25519.zig");
lib/std/crypto/25519/ed25519.zig+61-45
......@@ -4,6 +4,8 @@
44// The MIT license requires this copyright notice to be included in all copies
55// and substantial portions of the software.
66const std = @import("std");
7const crypto = std.crypto;
8const debug = std.debug;
79const fmt = std.fmt;
810const mem = std.mem;
911const Sha512 = std.crypto.hash.sha2.Sha512;
......@@ -14,8 +16,8 @@ pub const Ed25519 = struct {
1416 pub const Curve = @import("edwards25519.zig").Edwards25519;
1517 /// Length (in bytes) of a seed required to create a key pair.
1618 pub const seed_length = 32;
17 /// Length (in bytes) of a compressed key pair.
18 pub const keypair_length = 64;
19 /// Length (in bytes) of a compressed secret key.
20 pub const secret_length = 64;
1921 /// Length (in bytes) of a compressed public key.
2022 pub const public_length = 32;
2123 /// Length (in bytes) of a signature.
......@@ -23,41 +25,61 @@ pub const Ed25519 = struct {
2325 /// Length (in bytes) of optional random bytes, for non-deterministic signatures.
2426 pub const noise_length = 32;
2527
26 /// Derive a key pair from a secret seed.
27 ///
28 /// As in RFC 8032, an Ed25519 public key is generated by hashing
29 /// the secret key using the SHA-512 function, and interpreting the
30 /// bit-swapped, clamped lower-half of the output as the secret scalar.
31 ///
32 /// For this reason, an EdDSA secret key is commonly called a seed,
33 /// from which the actual secret is derived.
34 pub fn createKeyPair(seed: [seed_length]u8) ![keypair_length]u8 {
35 var az: [Sha512.digest_length]u8 = undefined;
36 var h = Sha512.init(.{});
37 h.update(&seed);
38 h.final(&az);
39 const p = try Curve.basePoint.clampedMul(az[0..32].*);
40 var keypair: [keypair_length]u8 = undefined;
41 mem.copy(u8, &keypair, &seed);
42 mem.copy(u8, keypair[seed_length..], &p.toBytes());
43 return keypair;
44 }
28 /// An Ed25519 key pair.
29 pub const KeyPair = struct {
30 /// Public part.
31 public_key: [public_length]u8,
32 /// Secret part. What we expose as a secret key is, under the hood, the concatenation of the seed and the public key.
33 secret_key: [secret_length]u8,
4534
46 /// Return the public key for a given key pair.
47 pub fn publicKey(key_pair: [keypair_length]u8) [public_length]u8 {
48 var public_key: [public_length]u8 = undefined;
49 mem.copy(u8, public_key[0..], key_pair[seed_length..]);
50 return public_key;
51 }
35 /// Derive a key pair from an optional secret seed.
36 ///
37 /// As in RFC 8032, an Ed25519 public key is generated by hashing
38 /// the secret key using the SHA-512 function, and interpreting the
39 /// bit-swapped, clamped lower-half of the output as the secret scalar.
40 ///
41 /// For this reason, an EdDSA secret key is commonly called a seed,
42 /// from which the actual secret is derived.
43 pub fn create(seed: ?[seed_length]u8) !KeyPair {
44 const ss = seed orelse ss: {
45 var random_seed: [seed_length]u8 = undefined;
46 try crypto.randomBytes(&random_seed);
47 break :ss random_seed;
48 };
49 var az: [Sha512.digest_length]u8 = undefined;
50 var h = Sha512.init(.{});
51 h.update(&ss);
52 h.final(&az);
53 const p = try Curve.basePoint.clampedMul(az[0..32].*);
54 var sk: [secret_length]u8 = undefined;
55 mem.copy(u8, &sk, &ss);
56 const pk = p.toBytes();
57 mem.copy(u8, sk[seed_length..], &pk);
58
59 return KeyPair{ .public_key = pk, .secret_key = sk };
60 }
61
62 /// Create a KeyPair from a secret key.
63 pub fn fromSecretKey(secret_key: [secret_length]u8) KeyPair {
64 return KeyPair{
65 .secret_key = secret_key,
66 .public_key = secret_key[seed_length..].*,
67 };
68 }
69 };
5270
5371 /// Sign a message using a key pair, and optional random noise.
5472 /// Having noise creates non-standard, non-deterministic signatures,
5573 /// but has been proven to increase resilience against fault attacks.
56 pub fn sign(msg: []const u8, key_pair: [keypair_length]u8, noise: ?[noise_length]u8) ![signature_length]u8 {
57 const public_key = key_pair[32..];
74 pub fn sign(msg: []const u8, key_pair: KeyPair, noise: ?[noise_length]u8) ![signature_length]u8 {
75 const seed = key_pair.secret_key[0..seed_length];
76 const public_key = key_pair.secret_key[seed_length..];
77 if (!mem.eql(u8, public_key, &key_pair.public_key)) {
78 return error.KeyMismatch;
79 }
5880 var az: [Sha512.digest_length]u8 = undefined;
5981 var h = Sha512.init(.{});
60 h.update(key_pair[0..seed_length]);
82 h.update(seed);
6183 h.final(&az);
6284
6385 h = Sha512.init(.{});
......@@ -186,50 +208,44 @@ pub const Ed25519 = struct {
186208test "ed25519 key pair creation" {
187209 var seed: [32]u8 = undefined;
188210 try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
189 const key_pair = try Ed25519.createKeyPair(seed);
211 const key_pair = try Ed25519.KeyPair.create(seed);
190212 var buf: [256]u8 = undefined;
191 std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
192
193 const public_key = Ed25519.publicKey(key_pair);
194 std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
213 std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.secret_key}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
214 std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083");
195215}
196216
197217test "ed25519 signature" {
198218 var seed: [32]u8 = undefined;
199219 try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
200 const key_pair = try Ed25519.createKeyPair(seed);
220 const key_pair = try Ed25519.KeyPair.create(seed);
201221
202222 const sig = try Ed25519.sign("test", key_pair, null);
203223 var buf: [128]u8 = undefined;
204224 std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{sig}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808");
205 const public_key = Ed25519.publicKey(key_pair);
206 try Ed25519.verify(sig, "test", public_key);
207 std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", public_key));
225 try Ed25519.verify(sig, "test", key_pair.public_key);
226 std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", key_pair.public_key));
208227}
209228
210229test "ed25519 batch verification" {
211230 var i: usize = 0;
212231 while (i < 100) : (i += 1) {
213 var seed: [32]u8 = undefined;
214 try std.crypto.randomBytes(&seed);
215 const key_pair = try Ed25519.createKeyPair(seed);
232 const key_pair = try Ed25519.KeyPair.create(null);
216233 var msg1: [32]u8 = undefined;
217234 var msg2: [32]u8 = undefined;
218235 try std.crypto.randomBytes(&msg1);
219236 try std.crypto.randomBytes(&msg2);
220237 const sig1 = try Ed25519.sign(&msg1, key_pair, null);
221238 const sig2 = try Ed25519.sign(&msg2, key_pair, null);
222 const public_key = Ed25519.publicKey(key_pair);
223239 var signature_batch = [_]Ed25519.BatchElement{
224240 Ed25519.BatchElement{
225241 .sig = sig1,
226242 .msg = &msg1,
227 .public_key = public_key,
243 .public_key = key_pair.public_key,
228244 },
229245 Ed25519.BatchElement{
230246 .sig = sig2,
231247 .msg = &msg2,
232 .public_key = public_key,
248 .public_key = key_pair.public_key,
233249 },
234250 };
235251 try Ed25519.verifyBatch(2, signature_batch);
lib/std/crypto/25519/x25519.zig+39-32
......@@ -4,6 +4,7 @@
44// The MIT license requires this copyright notice to be included in all copies
55// and substantial portions of the software.
66const std = @import("std");
7const crypto = std.crypto;
78const mem = std.mem;
89const fmt = std.fmt;
910
......@@ -13,40 +14,46 @@ pub const X25519 = struct {
1314 pub const Curve = @import("curve25519.zig").Curve25519;
1415 /// Length (in bytes) of a secret key.
1516 pub const secret_length = 32;
17 /// Length (in bytes) of a public key.
18 pub const public_length = 32;
1619 /// Length (in bytes) of the output of the DH function.
17 pub const key_length = 32;
20 pub const shared_length = 32;
21 /// Seed (for key pair creation) length in bytes.
22 pub const seed_length = 32;
23
24 /// An X25519 key pair.
25 pub const KeyPair = struct {
26 /// Public part.
27 public_key: [public_length]u8,
28 /// Secret part.
29 secret_key: [secret_length]u8,
30
31 /// Create a new key pair using an optional seed.
32 pub fn create(seed: ?[seed_length]u8) !KeyPair {
33 const sk = seed orelse sk: {
34 var random_seed: [seed_length]u8 = undefined;
35 try crypto.randomBytes(&random_seed);
36 break :sk random_seed;
37 };
38 var kp: KeyPair = undefined;
39 mem.copy(u8, &kp.secret_key, sk[0..]);
40 try X25519.recoverPublicKey(&kp.public_key, sk);
41 return kp;
42 }
43 };
1844
1945 /// Compute the public key for a given private key.
20 pub fn createPublicKey(public_key: []u8, private_key: []const u8) bool {
21 std.debug.assert(private_key.len >= key_length);
22 std.debug.assert(public_key.len >= key_length);
23 var s: [32]u8 = undefined;
24 mem.copy(u8, &s, private_key[0..32]);
25 if (Curve.basePoint.clampedMul(s)) |q| {
26 mem.copy(u8, public_key, q.toBytes()[0..]);
27 return true;
28 } else |_| {
29 return false;
30 }
46 pub fn recoverPublicKey(public_key: *[public_length]u8, secret_key: [secret_length]u8) !void {
47 const q = try Curve.basePoint.clampedMul(secret_key);
48 mem.copy(u8, public_key, q.toBytes()[0..]);
3149 }
3250
3351 /// Compute the scalar product of a public key and a secret scalar.
3452 /// Note that the output should not be used as a shared secret without
3553 /// hashing it first.
36 pub fn create(out: []u8, private_key: []const u8, public_key: []const u8) bool {
37 std.debug.assert(out.len >= secret_length);
38 std.debug.assert(private_key.len >= key_length);
39 std.debug.assert(public_key.len >= key_length);
40 var s: [32]u8 = undefined;
41 var b: [32]u8 = undefined;
42 mem.copy(u8, &s, private_key[0..32]);
43 mem.copy(u8, &b, public_key[0..32]);
44 if (Curve.fromBytes(b).clampedMul(s)) |q| {
45 mem.copy(u8, out, q.toBytes()[0..]);
46 return true;
47 } else |_| {
48 return false;
49 }
54 pub fn scalarmult(out: *[shared_length]u8, secret_key: [secret_length]u8, public_key: [public_length]u8) !void {
55 const q = try Curve.fromBytes(public_key).clampedMul(secret_key);
56 mem.copy(u8, out, q.toBytes()[0..]);
5057 }
5158};
5259
......@@ -56,7 +63,7 @@ test "x25519 public key calculation from secret key" {
5663 var pk_calculated: [32]u8 = undefined;
5764 try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166");
5865 try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50");
59 std.testing.expect(X25519.createPublicKey(pk_calculated[0..], &sk));
66 try X25519.recoverPublicKey(&pk_calculated, sk);
6067 std.testing.expectEqual(pk_calculated, pk_expected);
6168}
6269
......@@ -68,7 +75,7 @@ test "x25519 rfc7748 vector1" {
6875
6976 var output: [32]u8 = undefined;
7077
71 std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..]));
78 try X25519.scalarmult(&output, secret_key, public_key);
7279 std.testing.expectEqual(output, expected_output);
7380}
7481
......@@ -80,7 +87,7 @@ test "x25519 rfc7748 vector2" {
8087
8188 var output: [32]u8 = undefined;
8289
83 std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..]));
90 try X25519.scalarmult(&output, secret_key, public_key);
8491 std.testing.expectEqual(output, expected_output);
8592}
8693
......@@ -94,7 +101,7 @@ test "x25519 rfc7748 one iteration" {
94101 var i: usize = 0;
95102 while (i < 1) : (i += 1) {
96103 var output: [32]u8 = undefined;
97 std.testing.expect(X25519.create(output[0..], &k, &u));
104 try X25519.scalarmult(output[0..], k, u);
98105
99106 mem.copy(u8, u[0..], k[0..]);
100107 mem.copy(u8, k[0..], output[0..]);
......@@ -118,7 +125,7 @@ test "x25519 rfc7748 1,000 iterations" {
118125 var i: usize = 0;
119126 while (i < 1000) : (i += 1) {
120127 var output: [32]u8 = undefined;
121 std.testing.expect(X25519.create(output[0..], &k, &u));
128 std.testing.expect(X25519.scalarmult(output[0..], &k, &u));
122129
123130 mem.copy(u8, u[0..], k[0..]);
124131 mem.copy(u8, k[0..], output[0..]);
......@@ -141,7 +148,7 @@ test "x25519 rfc7748 1,000,000 iterations" {
141148 var i: usize = 0;
142149 while (i < 1000000) : (i += 1) {
143150 var output: [32]u8 = undefined;
144 std.testing.expect(X25519.create(output[0..], &k, &u));
151 std.testing.expect(X25519.scalarmult(output[0..], &k, &u));
145152
146153 mem.copy(u8, u[0..], k[0..]);
147154 mem.copy(u8, k[0..], output[0..]);
lib/std/crypto/benchmark.zig+10-17
......@@ -96,12 +96,12 @@ pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 {
9696const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }};
9797
9898pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 {
99 std.debug.assert(DhKeyExchange.key_length >= DhKeyExchange.secret_length);
99 std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length);
100100
101 var in: [DhKeyExchange.key_length]u8 = undefined;
101 var in: [DhKeyExchange.shared_length]u8 = undefined;
102102 prng.random.bytes(in[0..]);
103103
104 var out: [DhKeyExchange.key_length]u8 = undefined;
104 var out: [DhKeyExchange.shared_length]u8 = undefined;
105105 prng.random.bytes(out[0..]);
106106
107107 var timer = try Timer.start();
......@@ -109,7 +109,7 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c
109109 {
110110 var i: usize = 0;
111111 while (i < exchange_count) : (i += 1) {
112 _ = DhKeyExchange.create(out[0..], out[0..], in[0..]);
112 try DhKeyExchange.scalarmult(&out, out, in);
113113 mem.doNotOptimizeAway(&out);
114114 }
115115 }
......@@ -124,10 +124,8 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c
124124const signatures = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }};
125125
126126pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
127 var seed: [Signature.seed_length]u8 = undefined;
128 prng.random.bytes(seed[0..]);
129127 const msg = [_]u8{0} ** 64;
130 const key_pair = try Signature.createKeyPair(seed);
128 const key_pair = try Signature.KeyPair.create(null);
131129
132130 var timer = try Timer.start();
133131 const start = timer.lap();
......@@ -149,11 +147,8 @@ pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count
149147const signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }};
150148
151149pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
152 var seed: [Signature.seed_length]u8 = undefined;
153 prng.random.bytes(seed[0..]);
154150 const msg = [_]u8{0} ** 64;
155 const key_pair = try Signature.createKeyPair(seed);
156 const public_key = Signature.publicKey(key_pair);
151 const key_pair = try Signature.KeyPair.create(null);
157152 const sig = try Signature.sign(&msg, key_pair, null);
158153
159154 var timer = try Timer.start();
......@@ -161,7 +156,7 @@ pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime sign
161156 {
162157 var i: usize = 0;
163158 while (i < signatures_count) : (i += 1) {
164 try Signature.verify(sig, &msg, public_key);
159 try Signature.verify(sig, &msg, key_pair.public_key);
165160 mem.doNotOptimizeAway(&sig);
166161 }
167162 }
......@@ -176,16 +171,13 @@ pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime sign
176171const batch_signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }};
177172
178173pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 {
179 var seed: [Signature.seed_length]u8 = undefined;
180 prng.random.bytes(seed[0..]);
181174 const msg = [_]u8{0} ** 64;
182 const key_pair = try Signature.createKeyPair(seed);
183 const public_key = Signature.publicKey(key_pair);
175 const key_pair = try Signature.KeyPair.create(null);
184176 const sig = try Signature.sign(&msg, key_pair, null);
185177
186178 var batch: [64]Signature.BatchElement = undefined;
187179 for (batch) |*element| {
188 element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = public_key };
180 element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = key_pair.public_key };
189181 }
190182
191183 var timer = try Timer.start();
......@@ -208,6 +200,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime
208200const aeads = [_]Crypto{
209201 Crypto{ .ty = crypto.aead.ChaCha20Poly1305, .name = "chacha20Poly1305" },
210202 Crypto{ .ty = crypto.aead.XChaCha20Poly1305, .name = "xchacha20Poly1305" },
203 Crypto{ .ty = crypto.aead.XSalsa20Poly1305, .name = "xsalsa20Poly1305" },
211204 Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" },
212205 Crypto{ .ty = crypto.aead.Aegis128L, .name = "aegis-128l" },
213206 Crypto{ .ty = crypto.aead.Aegis256, .name = "aegis-256" },
lib/std/crypto/salsa20.zig created+430
......@@ -0,0 +1,430 @@
1const std = @import("std");
2const crypto = std.crypto;
3const debug = std.debug;
4const math = std.math;
5const mem = std.mem;
6
7const Poly1305 = crypto.onetimeauth.Poly1305;
8const Blake2b = crypto.hash.blake2.Blake2b;
9const X25519 = crypto.dh.X25519;
10
11const Salsa20NonVecImpl = struct {
12 const BlockVec = [16]u32;
13
14 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
15 const c = "expand 32-byte k";
16 const constant_le = comptime [4]u32{
17 mem.readIntLittle(u32, c[0..4]),
18 mem.readIntLittle(u32, c[4..8]),
19 mem.readIntLittle(u32, c[8..12]),
20 mem.readIntLittle(u32, c[12..16]),
21 };
22 return BlockVec{
23 constant_le[0], key[0], key[1], key[2],
24 key[3], constant_le[1], d[0], d[1],
25 d[2], d[3], constant_le[2], key[4],
26 key[5], key[6], key[7], constant_le[3],
27 };
28 }
29
30 const QuarterRound = struct {
31 a: usize,
32 b: usize,
33 c: usize,
34 d: u6,
35 };
36
37 inline fn Rp(comptime a: usize, comptime b: usize, comptime c: usize, comptime d: u6) QuarterRound {
38 return QuarterRound{
39 .a = a,
40 .b = b,
41 .c = c,
42 .d = d,
43 };
44 }
45
46 inline fn salsa20Core(x: *BlockVec, input: BlockVec) void {
47 const arx_steps = comptime [_]QuarterRound{
48 Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18),
49 Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18),
50 Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18),
51 Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18),
52 Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18),
53 Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18),
54 Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18),
55 Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),
56 };
57 x.* = input;
58 var j: usize = 0;
59 while (j < 20) : (j += 2) {
60 inline for (arx_steps) |r| {
61 x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);
62 }
63 }
64 }
65
66 fn hashToBytes(out: *[64]u8, x: BlockVec) void {
67 for (x) |w, i| {
68 mem.writeIntLittle(u32, out[i * 4 ..][0..4], w);
69 }
70 }
71
72 fn contextFeedback(x: *BlockVec, ctx: BlockVec) void {
73 var i: usize = 0;
74 while (i < 16) : (i += 1) {
75 x[i] +%= ctx[i];
76 }
77 }
78
79 fn salsa20Internal(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
80 var ctx = initContext(key, d);
81 var x: BlockVec = undefined;
82 var buf: [64]u8 = undefined;
83 var i: usize = 0;
84 while (i + 64 <= in.len) : (i += 64) {
85 salsa20Core(x[0..], ctx);
86 contextFeedback(&x, ctx);
87 hashToBytes(buf[0..], x);
88 var xout = out[i..];
89 const xin = in[i..];
90 var j: usize = 0;
91 while (j < 64) : (j += 1) {
92 xout[j] = xin[j];
93 }
94 j = 0;
95 while (j < 64) : (j += 1) {
96 xout[j] ^= buf[j];
97 }
98 ctx[9] += @boolToInt(@addWithOverflow(u32, ctx[8], 1, &ctx[8]));
99 }
100 if (i < in.len) {
101 salsa20Core(x[0..], ctx);
102 contextFeedback(&x, ctx);
103 hashToBytes(buf[0..], x);
104
105 var xout = out[i..];
106 const xin = in[i..];
107 var j: usize = 0;
108 while (j < in.len % 64) : (j += 1) {
109 xout[j] = xin[j] ^ buf[j];
110 }
111 }
112 }
113
114 fn hsalsa20(input: [16]u8, key: [32]u8) [32]u8 {
115 var c: [4]u32 = undefined;
116 for (c) |_, i| {
117 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
118 }
119 const ctx = initContext(keyToWords(key), c);
120 var x: BlockVec = undefined;
121 salsa20Core(x[0..], ctx);
122 var out: [32]u8 = undefined;
123 mem.writeIntLittle(u32, out[0..4], x[0]);
124 mem.writeIntLittle(u32, out[4..8], x[5]);
125 mem.writeIntLittle(u32, out[8..12], x[10]);
126 mem.writeIntLittle(u32, out[12..16], x[15]);
127 mem.writeIntLittle(u32, out[16..20], x[6]);
128 mem.writeIntLittle(u32, out[20..24], x[7]);
129 mem.writeIntLittle(u32, out[24..28], x[8]);
130 mem.writeIntLittle(u32, out[28..32], x[9]);
131 return out;
132 }
133};
134
135const Salsa20Impl = Salsa20NonVecImpl;
136
137fn keyToWords(key: [32]u8) [8]u32 {
138 var k: [8]u32 = undefined;
139 var i: usize = 0;
140 while (i < 8) : (i += 1) {
141 k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]);
142 }
143 return k;
144}
145
146fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } {
147 return .{
148 .key = Salsa20Impl.hsalsa20(nonce[0..16].*, key),
149 .nonce = nonce[16..24].*,
150 };
151}
152
153/// The Salsa20 stream cipher.
154pub const Salsa20 = struct {
155 /// Nonce length in bytes.
156 pub const nonce_length = 8;
157 /// Key length in bytes.
158 pub const key_length = 32;
159
160 /// Add the output of the Salsa20 stream cipher to `in` and stores the result into `out`.
161 /// WARNING: This function doesn't provide authenticated encryption.
162 /// Using the AEAD or one of the `box` versions is usually preferred.
163 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
164 debug.assert(in.len == out.len);
165
166 var d: [4]u32 = undefined;
167 d[0] = mem.readIntLittle(u32, nonce[0..4]);
168 d[1] = mem.readIntLittle(u32, nonce[4..8]);
169 d[2] = @truncate(u32, counter);
170 d[3] = @truncate(u32, counter >> 32);
171 Salsa20Impl.salsa20Internal(out, in, keyToWords(key), d);
172 }
173};
174
175/// The XSalsa20 stream cipher.
176pub const XSalsa20 = struct {
177 /// Nonce length in bytes.
178 pub const nonce_length = 24;
179 /// Key length in bytes.
180 pub const key_length = 32;
181
182 /// Add the output of the XSalsa20 stream cipher to `in` and stores the result into `out`.
183 /// WARNING: This function doesn't provide authenticated encryption.
184 /// Using the AEAD or one of the `box` versions is usually preferred.
185 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
186 const extended = extend(key, nonce);
187 Salsa20.xor(out, in, counter, extended.key, extended.nonce);
188 }
189};
190
191/// The XSalsa20 stream cipher, combined with the Poly1305 MAC
192pub const XSalsa20Poly1305 = struct {
193 /// Authentication tag length in bytes.
194 pub const tag_length = Poly1305.mac_length;
195 /// Nonce length in bytes.
196 pub const nonce_length = XSalsa20.nonce_length;
197 /// Key length in bytes.
198 pub const key_length = XSalsa20.key_length;
199
200 /// c: ciphertext: output buffer should be of size m.len
201 /// tag: authentication tag: output MAC
202 /// m: message
203 /// ad: Associated Data
204 /// npub: public nonce
205 /// k: private key
206 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
207 debug.assert(c.len == m.len);
208 const extended = extend(k, npub);
209 var block0 = [_]u8{0} ** 64;
210 const mlen0 = math.min(32, m.len);
211 mem.copy(u8, block0[32..][0..mlen0], m[0..mlen0]);
212 Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
213 mem.copy(u8, c[0..mlen0], block0[32..][0..mlen0]);
214 Salsa20.xor(c[mlen0..], m[mlen0..], 1, extended.key, extended.nonce);
215 var mac = Poly1305.init(block0[0..32]);
216 mac.update(ad);
217 mac.update(c);
218 mac.final(tag);
219 }
220
221 /// m: message: output buffer should be of size c.len
222 /// c: ciphertext
223 /// tag: authentication tag
224 /// ad: Associated Data
225 /// npub: public nonce
226 /// k: private key
227 pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void {
228 debug.assert(c.len == m.len);
229 const extended = extend(k, npub);
230 var block0 = [_]u8{0} ** 64;
231 const mlen0 = math.min(32, c.len);
232 mem.copy(u8, block0[32..][0..mlen0], c[0..mlen0]);
233 Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce);
234 var mac = Poly1305.init(block0[0..32]);
235 mac.update(ad);
236 mac.update(c);
237 var computedTag: [tag_length]u8 = undefined;
238 mac.final(&computedTag);
239 var acc: u8 = 0;
240 for (computedTag) |_, i| {
241 acc |= (computedTag[i] ^ tag[i]);
242 }
243 if (acc != 0) {
244 mem.secureZero(u8, &computedTag);
245 return error.AuthenticationFailed;
246 }
247 mem.copy(u8, m[0..mlen0], block0[32..][0..mlen0]);
248 Salsa20.xor(m[mlen0..], c[mlen0..], 1, extended.key, extended.nonce);
249 }
250};
251
252/// NaCl-compatible secretbox API.
253///
254/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
255/// A secret key shared by all the recipients must be already known in order to use this API.
256///
257/// Nonces are 192-bit large and can safely be chosen with a random number generator.
258pub const secretBox = struct {
259 /// Key length in bytes.
260 pub const key_length = XSalsa20Poly1305.key_length;
261 /// Nonce length in bytes.
262 pub const nonce_length = XSalsa20Poly1305.nonce_length;
263 /// Authentication tag length in bytes.
264 pub const tag_length = XSalsa20Poly1305.tag_length;
265
266 /// Encrypt and authenticate `m` using a nonce `npub` and a key `k`.
267 /// `c` must be exactly `tag_length` longer than `m`, as it will store both the ciphertext and the authentication tag.
268 pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
269 debug.assert(c.len == tag_length + m.len);
270 XSalsa20Poly1305.encrypt(c[tag_length..], c[0..tag_length], m, "", npub, k);
271 }
272
273 /// Verify and decrypt `c` using a nonce `npub` and a key `k`.
274 /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message.
275 pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void {
276 if (c.len < tag_length) {
277 return error.AuthenticationFailed;
278 }
279 debug.assert(m.len == c.len - tag_length);
280 return XSalsa20Poly1305.decrypt(m, c[tag_length..], c[0..tag_length].*, "", npub, k);
281 }
282};
283
284/// NaCl-compatible box API.
285///
286/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with.
287/// This construction uses public-key cryptography. A shared secret doesn't have to be known in advance by both parties.
288/// Instead, a message is encrypted using a sender's secret key and a recipient's public key,
289/// and is decrypted using the recipient's secret key and the sender's public key.
290///
291/// Nonces are 192-bit large and can safely be chosen with a random number generator.
292pub const box = struct {
293 /// Public key length in bytes.
294 pub const public_length = X25519.public_length;
295 /// Secret key length in bytes.
296 pub const secret_length = X25519.secret_length;
297 /// Shared key length in bytes.
298 pub const shared_length = XSalsa20Poly1305.key_length;
299 /// Seed (for key pair creation) length in bytes.
300 pub const seed_length = X25519.seed_length;
301 /// Nonce length in bytes.
302 pub const nonce_length = XSalsa20Poly1305.nonce_length;
303 /// Authentication tag length in bytes.
304 pub const tag_length = XSalsa20Poly1305.tag_length;
305
306 /// A key pair.
307 pub const KeyPair = X25519.KeyPair;
308
309 /// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key.
310 pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 {
311 var p: [32]u8 = undefined;
312 try X25519.scalarmult(&p, secret_key, public_key);
313 const zero = [_]u8{0} ** 16;
314 return Salsa20Impl.hsalsa20(zero, p);
315 }
316
317 /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`.
318 pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void {
319 const shared_key = try createSharedSecret(public_key, secret_key);
320 return secretBox.seal(c, m, npub, shared_key);
321 }
322
323 /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`.
324 pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void {
325 const shared_key = try createSharedSecret(public_key, secret_key);
326 return secretBox.open(m, c, npub, shared_key);
327 }
328};
329
330/// libsodium-compatible sealed boxes
331///
332/// Sealed boxes are designed to anonymously send messages to a recipient given their public key.
333/// Only the recipient can decrypt these messages, using their private key.
334/// While the recipient can verify the integrity of the message, it cannot verify the identity of the sender.
335///
336/// A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption process.
337pub const sealedBox = struct {
338 pub const public_length = box.public_length;
339 pub const secret_length = box.secret_length;
340 pub const seed_length = box.seed_length;
341 pub const seal_length = box.public_length + box.tag_length;
342
343 /// A key pair.
344 pub const KeyPair = box.KeyPair;
345
346 fn createNonce(pk1: [public_length]u8, pk2: [public_length]u8) [box.nonce_length]u8 {
347 var hasher = Blake2b(box.nonce_length * 8).init(.{});
348 hasher.update(&pk1);
349 hasher.update(&pk2);
350 var nonce: [box.nonce_length]u8 = undefined;
351 hasher.final(&nonce);
352 return nonce;
353 }
354
355 /// Encrypt a message `m` for a recipient whose public key is `public_key`.
356 /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added.
357 pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) !void {
358 debug.assert(c.len == m.len + seal_length);
359 var ekp = try KeyPair.create(null);
360 const nonce = createNonce(ekp.public_key, public_key);
361 mem.copy(u8, c[0..public_length], ekp.public_key[0..]);
362 try box.seal(c[box.public_length..], m, nonce, public_key, ekp.secret_key);
363 mem.secureZero(u8, ekp.secret_key[0..]);
364 }
365
366 /// Decrypt a message using a key pair.
367 /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata.
368 pub fn open(m: []u8, c: []const u8, keypair: KeyPair) !void {
369 if (c.len < seal_length) {
370 return error.AuthenticationFailed;
371 }
372 const epk = c[0..public_length];
373 const nonce = createNonce(epk.*, keypair.public_key);
374 return box.open(m, c[public_length..], nonce, epk.*, keypair.secret_key);
375 }
376};
377
378test "xsalsa20poly1305" {
379 var msg: [100]u8 = undefined;
380 var msg2: [msg.len]u8 = undefined;
381 var c: [msg.len]u8 = undefined;
382 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
383 var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined;
384 var tag: [XSalsa20Poly1305.tag_length]u8 = undefined;
385 try crypto.randomBytes(&msg);
386 try crypto.randomBytes(&key);
387 try crypto.randomBytes(&nonce);
388
389 XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key);
390 try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key);
391}
392
393test "xsalsa20poly1305 secretbox" {
394 var msg: [100]u8 = undefined;
395 var msg2: [msg.len]u8 = undefined;
396 var key: [XSalsa20Poly1305.key_length]u8 = undefined;
397 var nonce: [box.nonce_length]u8 = undefined;
398 var boxed: [msg.len + box.tag_length]u8 = undefined;
399 try crypto.randomBytes(&msg);
400 try crypto.randomBytes(&key);
401 try crypto.randomBytes(&nonce);
402
403 secretBox.seal(boxed[0..], msg[0..], nonce, key);
404 try secretBox.open(msg2[0..], boxed[0..], nonce, key);
405}
406
407test "xsalsa20poly1305 box" {
408 var msg: [100]u8 = undefined;
409 var msg2: [msg.len]u8 = undefined;
410 var nonce: [box.nonce_length]u8 = undefined;
411 var boxed: [msg.len + box.tag_length]u8 = undefined;
412 try crypto.randomBytes(&msg);
413 try crypto.randomBytes(&nonce);
414
415 var kp1 = try box.KeyPair.create(null);
416 var kp2 = try box.KeyPair.create(null);
417 try box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key);
418 try box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key);
419}
420
421test "xsalsa20poly1305 sealedbox" {
422 var msg: [100]u8 = undefined;
423 var msg2: [msg.len]u8 = undefined;
424 var boxed: [msg.len + sealedBox.seal_length]u8 = undefined;
425 try crypto.randomBytes(&msg);
426
427 var kp = try box.KeyPair.create(null);
428 try sealedBox.seal(boxed[0..], msg[0..], kp.public_key);
429 try sealedBox.open(msg2[0..], boxed[0..], kp);
430}