diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 0f6fa48d0a..a90f5ec0d1 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -6,15 +6,14 @@ /// Authenticated Encryption with Associated Data pub const aead = struct { - const chacha20 = @import("crypto/chacha20.zig"); - pub const Gimli = @import("crypto/gimli.zig").Aead; - pub const ChaCha20Poly1305 = chacha20.Chacha20Poly1305; - pub const XChaCha20Poly1305 = chacha20.XChacha20Poly1305; + pub const ChaCha20Poly1305 = @import("crypto/chacha20.zig").Chacha20Poly1305; + pub const XChaCha20Poly1305 = @import("crypto/chacha20.zig").XChacha20Poly1305; pub const Aegis128L = @import("crypto/aegis.zig").Aegis128L; pub const Aegis256 = @import("crypto/aegis.zig").Aegis256; pub const Aes128Gcm = @import("crypto/aes_gcm.zig").Aes128Gcm; pub const Aes256Gcm = @import("crypto/aes_gcm.zig").Aes256Gcm; + pub const XSalsa20Poly1305 = @import("crypto/salsa20.zig").XSalsa20Poly1305; }; /// Authentication (MAC) functions. @@ -101,6 +100,15 @@ pub const stream = struct { pub const ChaCha20IETF = @import("crypto/chacha20.zig").ChaCha20IETF; pub const XChaCha20IETF = @import("crypto/chacha20.zig").XChaCha20IETF; pub const ChaCha20With64BitNonce = @import("crypto/chacha20.zig").ChaCha20With64BitNonce; + pub const Salsa20 = @import("crypto/salsa20.zig").Salsa20; + pub const XSalsa20 = @import("crypto/salsa20.zig").XSalsa20; +}; + +pub const nacl = struct { + const salsa20 = @import("crypto/salsa20.zig"); + pub const box = salsa20.box; + pub const secretBox = salsa20.secretBox; + pub const sealedBox = salsa20.sealedBox; }; const std = @import("std.zig"); @@ -134,6 +142,7 @@ test "crypto" { _ = @import("crypto/sha1.zig"); _ = @import("crypto/sha2.zig"); _ = @import("crypto/sha3.zig"); + _ = @import("crypto/salsa20.zig"); _ = @import("crypto/siphash.zig"); _ = @import("crypto/25519/curve25519.zig"); _ = @import("crypto/25519/ed25519.zig"); diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index abbba22f11..842b08d706 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -4,6 +4,8 @@ // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const std = @import("std"); +const crypto = std.crypto; +const debug = std.debug; const fmt = std.fmt; const mem = std.mem; const Sha512 = std.crypto.hash.sha2.Sha512; @@ -14,8 +16,8 @@ pub const Ed25519 = struct { pub const Curve = @import("edwards25519.zig").Edwards25519; /// Length (in bytes) of a seed required to create a key pair. pub const seed_length = 32; - /// Length (in bytes) of a compressed key pair. - pub const keypair_length = 64; + /// Length (in bytes) of a compressed secret key. + pub const secret_length = 64; /// Length (in bytes) of a compressed public key. pub const public_length = 32; /// Length (in bytes) of a signature. @@ -23,41 +25,61 @@ pub const Ed25519 = struct { /// Length (in bytes) of optional random bytes, for non-deterministic signatures. pub const noise_length = 32; - /// Derive a key pair from a secret seed. - /// - /// As in RFC 8032, an Ed25519 public key is generated by hashing - /// the secret key using the SHA-512 function, and interpreting the - /// bit-swapped, clamped lower-half of the output as the secret scalar. - /// - /// For this reason, an EdDSA secret key is commonly called a seed, - /// from which the actual secret is derived. - pub fn createKeyPair(seed: [seed_length]u8) ![keypair_length]u8 { - var az: [Sha512.digest_length]u8 = undefined; - var h = Sha512.init(.{}); - h.update(&seed); - h.final(&az); - const p = try Curve.basePoint.clampedMul(az[0..32].*); - var keypair: [keypair_length]u8 = undefined; - mem.copy(u8, &keypair, &seed); - mem.copy(u8, keypair[seed_length..], &p.toBytes()); - return keypair; - } + /// An Ed25519 key pair. + pub const KeyPair = struct { + /// Public part. + public_key: [public_length]u8, + /// Secret part. What we expose as a secret key is, under the hood, the concatenation of the seed and the public key. + secret_key: [secret_length]u8, - /// Return the public key for a given key pair. - pub fn publicKey(key_pair: [keypair_length]u8) [public_length]u8 { - var public_key: [public_length]u8 = undefined; - mem.copy(u8, public_key[0..], key_pair[seed_length..]); - return public_key; - } + /// Derive a key pair from an optional secret seed. + /// + /// As in RFC 8032, an Ed25519 public key is generated by hashing + /// the secret key using the SHA-512 function, and interpreting the + /// bit-swapped, clamped lower-half of the output as the secret scalar. + /// + /// For this reason, an EdDSA secret key is commonly called a seed, + /// from which the actual secret is derived. + pub fn create(seed: ?[seed_length]u8) !KeyPair { + const ss = seed orelse ss: { + var random_seed: [seed_length]u8 = undefined; + try crypto.randomBytes(&random_seed); + break :ss random_seed; + }; + var az: [Sha512.digest_length]u8 = undefined; + var h = Sha512.init(.{}); + h.update(&ss); + h.final(&az); + const p = try Curve.basePoint.clampedMul(az[0..32].*); + var sk: [secret_length]u8 = undefined; + mem.copy(u8, &sk, &ss); + const pk = p.toBytes(); + mem.copy(u8, sk[seed_length..], &pk); + + return KeyPair{ .public_key = pk, .secret_key = sk }; + } + + /// Create a KeyPair from a secret key. + pub fn fromSecretKey(secret_key: [secret_length]u8) KeyPair { + return KeyPair{ + .secret_key = secret_key, + .public_key = secret_key[seed_length..].*, + }; + } + }; /// Sign a message using a key pair, and optional random noise. /// Having noise creates non-standard, non-deterministic signatures, /// but has been proven to increase resilience against fault attacks. - pub fn sign(msg: []const u8, key_pair: [keypair_length]u8, noise: ?[noise_length]u8) ![signature_length]u8 { - const public_key = key_pair[32..]; + pub fn sign(msg: []const u8, key_pair: KeyPair, noise: ?[noise_length]u8) ![signature_length]u8 { + const seed = key_pair.secret_key[0..seed_length]; + const public_key = key_pair.secret_key[seed_length..]; + if (!mem.eql(u8, public_key, &key_pair.public_key)) { + return error.KeyMismatch; + } var az: [Sha512.digest_length]u8 = undefined; var h = Sha512.init(.{}); - h.update(key_pair[0..seed_length]); + h.update(seed); h.final(&az); h = Sha512.init(.{}); @@ -186,50 +208,44 @@ pub const Ed25519 = struct { test "ed25519 key pair creation" { var seed: [32]u8 = undefined; try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); - const key_pair = try Ed25519.createKeyPair(seed); + const key_pair = try Ed25519.KeyPair.create(seed); var buf: [256]u8 = undefined; - std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); - - const public_key = Ed25519.publicKey(key_pair); - std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); + std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.secret_key}), "8052030376D47112BE7F73ED7A019293DD12AD910B654455798B4667D73DE1662D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); + std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{key_pair.public_key}), "2D6F7455D97B4A3A10D7293909D1A4F2058CB9A370E43FA8154BB280DB839083"); } test "ed25519 signature" { var seed: [32]u8 = undefined; try fmt.hexToBytes(seed[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); - const key_pair = try Ed25519.createKeyPair(seed); + const key_pair = try Ed25519.KeyPair.create(seed); const sig = try Ed25519.sign("test", key_pair, null); var buf: [128]u8 = undefined; std.testing.expectEqualStrings(try std.fmt.bufPrint(&buf, "{X}", .{sig}), "10A442B4A80CC4225B154F43BEF28D2472CA80221951262EB8E0DF9091575E2687CC486E77263C3418C757522D54F84B0359236ABBBD4ACD20DC297FDCA66808"); - const public_key = Ed25519.publicKey(key_pair); - try Ed25519.verify(sig, "test", public_key); - std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", public_key)); + try Ed25519.verify(sig, "test", key_pair.public_key); + std.testing.expectError(error.InvalidSignature, Ed25519.verify(sig, "TEST", key_pair.public_key)); } test "ed25519 batch verification" { var i: usize = 0; while (i < 100) : (i += 1) { - var seed: [32]u8 = undefined; - try std.crypto.randomBytes(&seed); - const key_pair = try Ed25519.createKeyPair(seed); + const key_pair = try Ed25519.KeyPair.create(null); var msg1: [32]u8 = undefined; var msg2: [32]u8 = undefined; try std.crypto.randomBytes(&msg1); try std.crypto.randomBytes(&msg2); const sig1 = try Ed25519.sign(&msg1, key_pair, null); const sig2 = try Ed25519.sign(&msg2, key_pair, null); - const public_key = Ed25519.publicKey(key_pair); var signature_batch = [_]Ed25519.BatchElement{ Ed25519.BatchElement{ .sig = sig1, .msg = &msg1, - .public_key = public_key, + .public_key = key_pair.public_key, }, Ed25519.BatchElement{ .sig = sig2, .msg = &msg2, - .public_key = public_key, + .public_key = key_pair.public_key, }, }; try Ed25519.verifyBatch(2, signature_batch); diff --git a/lib/std/crypto/25519/x25519.zig b/lib/std/crypto/25519/x25519.zig index dc1bd5a5ef..17c2e84e65 100644 --- a/lib/std/crypto/25519/x25519.zig +++ b/lib/std/crypto/25519/x25519.zig @@ -4,6 +4,7 @@ // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. const std = @import("std"); +const crypto = std.crypto; const mem = std.mem; const fmt = std.fmt; @@ -13,40 +14,46 @@ pub const X25519 = struct { pub const Curve = @import("curve25519.zig").Curve25519; /// Length (in bytes) of a secret key. pub const secret_length = 32; + /// Length (in bytes) of a public key. + pub const public_length = 32; /// Length (in bytes) of the output of the DH function. - pub const key_length = 32; + pub const shared_length = 32; + /// Seed (for key pair creation) length in bytes. + pub const seed_length = 32; + + /// An X25519 key pair. + pub const KeyPair = struct { + /// Public part. + public_key: [public_length]u8, + /// Secret part. + secret_key: [secret_length]u8, + + /// Create a new key pair using an optional seed. + pub fn create(seed: ?[seed_length]u8) !KeyPair { + const sk = seed orelse sk: { + var random_seed: [seed_length]u8 = undefined; + try crypto.randomBytes(&random_seed); + break :sk random_seed; + }; + var kp: KeyPair = undefined; + mem.copy(u8, &kp.secret_key, sk[0..]); + try X25519.recoverPublicKey(&kp.public_key, sk); + return kp; + } + }; /// Compute the public key for a given private key. - pub fn createPublicKey(public_key: []u8, private_key: []const u8) bool { - std.debug.assert(private_key.len >= key_length); - std.debug.assert(public_key.len >= key_length); - var s: [32]u8 = undefined; - mem.copy(u8, &s, private_key[0..32]); - if (Curve.basePoint.clampedMul(s)) |q| { - mem.copy(u8, public_key, q.toBytes()[0..]); - return true; - } else |_| { - return false; - } + pub fn recoverPublicKey(public_key: *[public_length]u8, secret_key: [secret_length]u8) !void { + const q = try Curve.basePoint.clampedMul(secret_key); + mem.copy(u8, public_key, q.toBytes()[0..]); } /// Compute the scalar product of a public key and a secret scalar. /// Note that the output should not be used as a shared secret without /// hashing it first. - pub fn create(out: []u8, private_key: []const u8, public_key: []const u8) bool { - std.debug.assert(out.len >= secret_length); - std.debug.assert(private_key.len >= key_length); - std.debug.assert(public_key.len >= key_length); - var s: [32]u8 = undefined; - var b: [32]u8 = undefined; - mem.copy(u8, &s, private_key[0..32]); - mem.copy(u8, &b, public_key[0..32]); - if (Curve.fromBytes(b).clampedMul(s)) |q| { - mem.copy(u8, out, q.toBytes()[0..]); - return true; - } else |_| { - return false; - } + pub fn scalarmult(out: *[shared_length]u8, secret_key: [secret_length]u8, public_key: [public_length]u8) !void { + const q = try Curve.fromBytes(public_key).clampedMul(secret_key); + mem.copy(u8, out, q.toBytes()[0..]); } }; @@ -56,7 +63,7 @@ test "x25519 public key calculation from secret key" { var pk_calculated: [32]u8 = undefined; try fmt.hexToBytes(sk[0..], "8052030376d47112be7f73ed7a019293dd12ad910b654455798b4667d73de166"); try fmt.hexToBytes(pk_expected[0..], "f1814f0e8ff1043d8a44d25babff3cedcae6c22c3edaa48f857ae70de2baae50"); - std.testing.expect(X25519.createPublicKey(pk_calculated[0..], &sk)); + try X25519.recoverPublicKey(&pk_calculated, sk); std.testing.expectEqual(pk_calculated, pk_expected); } @@ -68,7 +75,7 @@ test "x25519 rfc7748 vector1" { var output: [32]u8 = undefined; - std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..])); + try X25519.scalarmult(&output, secret_key, public_key); std.testing.expectEqual(output, expected_output); } @@ -80,7 +87,7 @@ test "x25519 rfc7748 vector2" { var output: [32]u8 = undefined; - std.testing.expect(X25519.create(output[0..], secret_key[0..], public_key[0..])); + try X25519.scalarmult(&output, secret_key, public_key); std.testing.expectEqual(output, expected_output); } @@ -94,7 +101,7 @@ test "x25519 rfc7748 one iteration" { var i: usize = 0; while (i < 1) : (i += 1) { var output: [32]u8 = undefined; - std.testing.expect(X25519.create(output[0..], &k, &u)); + try X25519.scalarmult(output[0..], k, u); mem.copy(u8, u[0..], k[0..]); mem.copy(u8, k[0..], output[0..]); @@ -118,7 +125,7 @@ test "x25519 rfc7748 1,000 iterations" { var i: usize = 0; while (i < 1000) : (i += 1) { var output: [32]u8 = undefined; - std.testing.expect(X25519.create(output[0..], &k, &u)); + std.testing.expect(X25519.scalarmult(output[0..], &k, &u)); mem.copy(u8, u[0..], k[0..]); mem.copy(u8, k[0..], output[0..]); @@ -141,7 +148,7 @@ test "x25519 rfc7748 1,000,000 iterations" { var i: usize = 0; while (i < 1000000) : (i += 1) { var output: [32]u8 = undefined; - std.testing.expect(X25519.create(output[0..], &k, &u)); + std.testing.expect(X25519.scalarmult(output[0..], &k, &u)); mem.copy(u8, u[0..], k[0..]); mem.copy(u8, k[0..], output[0..]); diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index 27da9f1f42..d27a4624f7 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -96,12 +96,12 @@ pub fn benchmarkMac(comptime Mac: anytype, comptime bytes: comptime_int) !u64 { const exchanges = [_]Crypto{Crypto{ .ty = crypto.dh.X25519, .name = "x25519" }}; pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_count: comptime_int) !u64 { - std.debug.assert(DhKeyExchange.key_length >= DhKeyExchange.secret_length); + std.debug.assert(DhKeyExchange.shared_length >= DhKeyExchange.secret_length); - var in: [DhKeyExchange.key_length]u8 = undefined; + var in: [DhKeyExchange.shared_length]u8 = undefined; prng.random.bytes(in[0..]); - var out: [DhKeyExchange.key_length]u8 = undefined; + var out: [DhKeyExchange.shared_length]u8 = undefined; prng.random.bytes(out[0..]); var timer = try Timer.start(); @@ -109,7 +109,7 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c { var i: usize = 0; while (i < exchange_count) : (i += 1) { - _ = DhKeyExchange.create(out[0..], out[0..], in[0..]); + try DhKeyExchange.scalarmult(&out, out, in); mem.doNotOptimizeAway(&out); } } @@ -124,10 +124,8 @@ pub fn benchmarkKeyExchange(comptime DhKeyExchange: anytype, comptime exchange_c const signatures = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { - var seed: [Signature.seed_length]u8 = undefined; - prng.random.bytes(seed[0..]); const msg = [_]u8{0} ** 64; - const key_pair = try Signature.createKeyPair(seed); + const key_pair = try Signature.KeyPair.create(null); var timer = try Timer.start(); const start = timer.lap(); @@ -149,11 +147,8 @@ pub fn benchmarkSignature(comptime Signature: anytype, comptime signatures_count const signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { - var seed: [Signature.seed_length]u8 = undefined; - prng.random.bytes(seed[0..]); const msg = [_]u8{0} ** 64; - const key_pair = try Signature.createKeyPair(seed); - const public_key = Signature.publicKey(key_pair); + const key_pair = try Signature.KeyPair.create(null); const sig = try Signature.sign(&msg, key_pair, null); var timer = try Timer.start(); @@ -161,7 +156,7 @@ pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime sign { var i: usize = 0; while (i < signatures_count) : (i += 1) { - try Signature.verify(sig, &msg, public_key); + try Signature.verify(sig, &msg, key_pair.public_key); mem.doNotOptimizeAway(&sig); } } @@ -176,16 +171,13 @@ pub fn benchmarkSignatureVerification(comptime Signature: anytype, comptime sign const batch_signature_verifications = [_]Crypto{Crypto{ .ty = crypto.sign.Ed25519, .name = "ed25519" }}; pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime signatures_count: comptime_int) !u64 { - var seed: [Signature.seed_length]u8 = undefined; - prng.random.bytes(seed[0..]); const msg = [_]u8{0} ** 64; - const key_pair = try Signature.createKeyPair(seed); - const public_key = Signature.publicKey(key_pair); + const key_pair = try Signature.KeyPair.create(null); const sig = try Signature.sign(&msg, key_pair, null); var batch: [64]Signature.BatchElement = undefined; for (batch) |*element| { - element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = public_key }; + element.* = Signature.BatchElement{ .sig = sig, .msg = &msg, .public_key = key_pair.public_key }; } var timer = try Timer.start(); @@ -208,6 +200,7 @@ pub fn benchmarkBatchSignatureVerification(comptime Signature: anytype, comptime const aeads = [_]Crypto{ Crypto{ .ty = crypto.aead.ChaCha20Poly1305, .name = "chacha20Poly1305" }, Crypto{ .ty = crypto.aead.XChaCha20Poly1305, .name = "xchacha20Poly1305" }, + Crypto{ .ty = crypto.aead.XSalsa20Poly1305, .name = "xsalsa20Poly1305" }, Crypto{ .ty = crypto.aead.Gimli, .name = "gimli-aead" }, Crypto{ .ty = crypto.aead.Aegis128L, .name = "aegis-128l" }, Crypto{ .ty = crypto.aead.Aegis256, .name = "aegis-256" }, diff --git a/lib/std/crypto/salsa20.zig b/lib/std/crypto/salsa20.zig new file mode 100644 index 0000000000..a07539bad5 --- /dev/null +++ b/lib/std/crypto/salsa20.zig @@ -0,0 +1,430 @@ +const std = @import("std"); +const crypto = std.crypto; +const debug = std.debug; +const math = std.math; +const mem = std.mem; + +const Poly1305 = crypto.onetimeauth.Poly1305; +const Blake2b = crypto.hash.blake2.Blake2b; +const X25519 = crypto.dh.X25519; + +const Salsa20NonVecImpl = struct { + const BlockVec = [16]u32; + + fn initContext(key: [8]u32, d: [4]u32) BlockVec { + const c = "expand 32-byte k"; + const constant_le = comptime [4]u32{ + mem.readIntLittle(u32, c[0..4]), + mem.readIntLittle(u32, c[4..8]), + mem.readIntLittle(u32, c[8..12]), + mem.readIntLittle(u32, c[12..16]), + }; + return BlockVec{ + constant_le[0], key[0], key[1], key[2], + key[3], constant_le[1], d[0], d[1], + d[2], d[3], constant_le[2], key[4], + key[5], key[6], key[7], constant_le[3], + }; + } + + const QuarterRound = struct { + a: usize, + b: usize, + c: usize, + d: u6, + }; + + inline fn Rp(comptime a: usize, comptime b: usize, comptime c: usize, comptime d: u6) QuarterRound { + return QuarterRound{ + .a = a, + .b = b, + .c = c, + .d = d, + }; + } + + inline fn salsa20Core(x: *BlockVec, input: BlockVec) void { + const arx_steps = comptime [_]QuarterRound{ + Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18), + Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18), + Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18), + Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18), + Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18), + Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18), + Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18), + Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18), + }; + x.* = input; + var j: usize = 0; + while (j < 20) : (j += 2) { + inline for (arx_steps) |r| { + x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d); + } + } + } + + fn hashToBytes(out: *[64]u8, x: BlockVec) void { + for (x) |w, i| { + mem.writeIntLittle(u32, out[i * 4 ..][0..4], w); + } + } + + fn contextFeedback(x: *BlockVec, ctx: BlockVec) void { + var i: usize = 0; + while (i < 16) : (i += 1) { + x[i] +%= ctx[i]; + } + } + + fn salsa20Internal(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void { + var ctx = initContext(key, d); + var x: BlockVec = undefined; + var buf: [64]u8 = undefined; + var i: usize = 0; + while (i + 64 <= in.len) : (i += 64) { + salsa20Core(x[0..], ctx); + contextFeedback(&x, ctx); + hashToBytes(buf[0..], x); + var xout = out[i..]; + const xin = in[i..]; + var j: usize = 0; + while (j < 64) : (j += 1) { + xout[j] = xin[j]; + } + j = 0; + while (j < 64) : (j += 1) { + xout[j] ^= buf[j]; + } + ctx[9] += @boolToInt(@addWithOverflow(u32, ctx[8], 1, &ctx[8])); + } + if (i < in.len) { + salsa20Core(x[0..], ctx); + contextFeedback(&x, ctx); + hashToBytes(buf[0..], x); + + var xout = out[i..]; + const xin = in[i..]; + var j: usize = 0; + while (j < in.len % 64) : (j += 1) { + xout[j] = xin[j] ^ buf[j]; + } + } + } + + fn hsalsa20(input: [16]u8, key: [32]u8) [32]u8 { + var c: [4]u32 = undefined; + for (c) |_, i| { + c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]); + } + const ctx = initContext(keyToWords(key), c); + var x: BlockVec = undefined; + salsa20Core(x[0..], ctx); + var out: [32]u8 = undefined; + mem.writeIntLittle(u32, out[0..4], x[0]); + mem.writeIntLittle(u32, out[4..8], x[5]); + mem.writeIntLittle(u32, out[8..12], x[10]); + mem.writeIntLittle(u32, out[12..16], x[15]); + mem.writeIntLittle(u32, out[16..20], x[6]); + mem.writeIntLittle(u32, out[20..24], x[7]); + mem.writeIntLittle(u32, out[24..28], x[8]); + mem.writeIntLittle(u32, out[28..32], x[9]); + return out; + } +}; + +const Salsa20Impl = Salsa20NonVecImpl; + +fn keyToWords(key: [32]u8) [8]u32 { + var k: [8]u32 = undefined; + var i: usize = 0; + while (i < 8) : (i += 1) { + k[i] = mem.readIntLittle(u32, key[i * 4 ..][0..4]); + } + return k; +} + +fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } { + return .{ + .key = Salsa20Impl.hsalsa20(nonce[0..16].*, key), + .nonce = nonce[16..24].*, + }; +} + +/// The Salsa20 stream cipher. +pub const Salsa20 = struct { + /// Nonce length in bytes. + pub const nonce_length = 8; + /// Key length in bytes. + pub const key_length = 32; + + /// Add the output of the Salsa20 stream cipher to `in` and stores the result into `out`. + /// WARNING: This function doesn't provide authenticated encryption. + /// Using the AEAD or one of the `box` versions is usually preferred. + pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void { + debug.assert(in.len == out.len); + + var d: [4]u32 = undefined; + d[0] = mem.readIntLittle(u32, nonce[0..4]); + d[1] = mem.readIntLittle(u32, nonce[4..8]); + d[2] = @truncate(u32, counter); + d[3] = @truncate(u32, counter >> 32); + Salsa20Impl.salsa20Internal(out, in, keyToWords(key), d); + } +}; + +/// The XSalsa20 stream cipher. +pub const XSalsa20 = struct { + /// Nonce length in bytes. + pub const nonce_length = 24; + /// Key length in bytes. + pub const key_length = 32; + + /// Add the output of the XSalsa20 stream cipher to `in` and stores the result into `out`. + /// WARNING: This function doesn't provide authenticated encryption. + /// Using the AEAD or one of the `box` versions is usually preferred. + pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void { + const extended = extend(key, nonce); + Salsa20.xor(out, in, counter, extended.key, extended.nonce); + } +}; + +/// The XSalsa20 stream cipher, combined with the Poly1305 MAC +pub const XSalsa20Poly1305 = struct { + /// Authentication tag length in bytes. + pub const tag_length = Poly1305.mac_length; + /// Nonce length in bytes. + pub const nonce_length = XSalsa20.nonce_length; + /// Key length in bytes. + pub const key_length = XSalsa20.key_length; + + /// c: ciphertext: output buffer should be of size m.len + /// tag: authentication tag: output MAC + /// m: message + /// ad: Associated Data + /// npub: public nonce + /// k: private key + pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { + debug.assert(c.len == m.len); + const extended = extend(k, npub); + var block0 = [_]u8{0} ** 64; + const mlen0 = math.min(32, m.len); + mem.copy(u8, block0[32..][0..mlen0], m[0..mlen0]); + Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce); + mem.copy(u8, c[0..mlen0], block0[32..][0..mlen0]); + Salsa20.xor(c[mlen0..], m[mlen0..], 1, extended.key, extended.nonce); + var mac = Poly1305.init(block0[0..32]); + mac.update(ad); + mac.update(c); + mac.final(tag); + } + + /// m: message: output buffer should be of size c.len + /// c: ciphertext + /// tag: authentication tag + /// ad: Associated Data + /// npub: public nonce + /// k: private key + pub fn decrypt(m: []u8, c: []const u8, tag: [tag_length]u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { + debug.assert(c.len == m.len); + const extended = extend(k, npub); + var block0 = [_]u8{0} ** 64; + const mlen0 = math.min(32, c.len); + mem.copy(u8, block0[32..][0..mlen0], c[0..mlen0]); + Salsa20.xor(block0[0..], block0[0..], 0, extended.key, extended.nonce); + var mac = Poly1305.init(block0[0..32]); + mac.update(ad); + mac.update(c); + var computedTag: [tag_length]u8 = undefined; + mac.final(&computedTag); + var acc: u8 = 0; + for (computedTag) |_, i| { + acc |= (computedTag[i] ^ tag[i]); + } + if (acc != 0) { + mem.secureZero(u8, &computedTag); + return error.AuthenticationFailed; + } + mem.copy(u8, m[0..mlen0], block0[32..][0..mlen0]); + Salsa20.xor(m[mlen0..], c[mlen0..], 1, extended.key, extended.nonce); + } +}; + +/// NaCl-compatible secretbox API. +/// +/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with. +/// A secret key shared by all the recipients must be already known in order to use this API. +/// +/// Nonces are 192-bit large and can safely be chosen with a random number generator. +pub const secretBox = struct { + /// Key length in bytes. + pub const key_length = XSalsa20Poly1305.key_length; + /// Nonce length in bytes. + pub const nonce_length = XSalsa20Poly1305.nonce_length; + /// Authentication tag length in bytes. + pub const tag_length = XSalsa20Poly1305.tag_length; + + /// Encrypt and authenticate `m` using a nonce `npub` and a key `k`. + /// `c` must be exactly `tag_length` longer than `m`, as it will store both the ciphertext and the authentication tag. + pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void { + debug.assert(c.len == tag_length + m.len); + XSalsa20Poly1305.encrypt(c[tag_length..], c[0..tag_length], m, "", npub, k); + } + + /// Verify and decrypt `c` using a nonce `npub` and a key `k`. + /// `m` must be exactly `tag_length` smaller than `c`, as `c` includes an authentication tag in addition to the encrypted message. + pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, k: [key_length]u8) !void { + if (c.len < tag_length) { + return error.AuthenticationFailed; + } + debug.assert(m.len == c.len - tag_length); + return XSalsa20Poly1305.decrypt(m, c[tag_length..], c[0..tag_length].*, "", npub, k); + } +}; + +/// NaCl-compatible box API. +/// +/// A secretbox contains both an encrypted message and an authentication tag to verify that it hasn't been tampered with. +/// This construction uses public-key cryptography. A shared secret doesn't have to be known in advance by both parties. +/// Instead, a message is encrypted using a sender's secret key and a recipient's public key, +/// and is decrypted using the recipient's secret key and the sender's public key. +/// +/// Nonces are 192-bit large and can safely be chosen with a random number generator. +pub const box = struct { + /// Public key length in bytes. + pub const public_length = X25519.public_length; + /// Secret key length in bytes. + pub const secret_length = X25519.secret_length; + /// Shared key length in bytes. + pub const shared_length = XSalsa20Poly1305.key_length; + /// Seed (for key pair creation) length in bytes. + pub const seed_length = X25519.seed_length; + /// Nonce length in bytes. + pub const nonce_length = XSalsa20Poly1305.nonce_length; + /// Authentication tag length in bytes. + pub const tag_length = XSalsa20Poly1305.tag_length; + + /// A key pair. + pub const KeyPair = X25519.KeyPair; + + /// Compute a secret suitable for `secretbox` given a recipent's public key and a sender's secret key. + pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) ![shared_length]u8 { + var p: [32]u8 = undefined; + try X25519.scalarmult(&p, secret_key, public_key); + const zero = [_]u8{0} ** 16; + return Salsa20Impl.hsalsa20(zero, p); + } + + /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`. + pub fn seal(c: []u8, m: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void { + const shared_key = try createSharedSecret(public_key, secret_key); + return secretBox.seal(c, m, npub, shared_key); + } + + /// Verify and decrypt a message using a recipient's secret key `public_key` and a sender's `public_key`. + pub fn open(m: []u8, c: []const u8, npub: [nonce_length]u8, public_key: [public_length]u8, secret_key: [secret_length]u8) !void { + const shared_key = try createSharedSecret(public_key, secret_key); + return secretBox.open(m, c, npub, shared_key); + } +}; + +/// libsodium-compatible sealed boxes +/// +/// Sealed boxes are designed to anonymously send messages to a recipient given their public key. +/// Only the recipient can decrypt these messages, using their private key. +/// While the recipient can verify the integrity of the message, it cannot verify the identity of the sender. +/// +/// A message is encrypted using an ephemeral key pair, whose secret part is destroyed right after the encryption process. +pub const sealedBox = struct { + pub const public_length = box.public_length; + pub const secret_length = box.secret_length; + pub const seed_length = box.seed_length; + pub const seal_length = box.public_length + box.tag_length; + + /// A key pair. + pub const KeyPair = box.KeyPair; + + fn createNonce(pk1: [public_length]u8, pk2: [public_length]u8) [box.nonce_length]u8 { + var hasher = Blake2b(box.nonce_length * 8).init(.{}); + hasher.update(&pk1); + hasher.update(&pk2); + var nonce: [box.nonce_length]u8 = undefined; + hasher.final(&nonce); + return nonce; + } + + /// Encrypt a message `m` for a recipient whose public key is `public_key`. + /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added. + pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) !void { + debug.assert(c.len == m.len + seal_length); + var ekp = try KeyPair.create(null); + const nonce = createNonce(ekp.public_key, public_key); + mem.copy(u8, c[0..public_length], ekp.public_key[0..]); + try box.seal(c[box.public_length..], m, nonce, public_key, ekp.secret_key); + mem.secureZero(u8, ekp.secret_key[0..]); + } + + /// Decrypt a message using a key pair. + /// `m` must be exactly `seal_length` bytes smaller than `c`, as `c` also includes metadata. + pub fn open(m: []u8, c: []const u8, keypair: KeyPair) !void { + if (c.len < seal_length) { + return error.AuthenticationFailed; + } + const epk = c[0..public_length]; + const nonce = createNonce(epk.*, keypair.public_key); + return box.open(m, c[public_length..], nonce, epk.*, keypair.secret_key); + } +}; + +test "xsalsa20poly1305" { + var msg: [100]u8 = undefined; + var msg2: [msg.len]u8 = undefined; + var c: [msg.len]u8 = undefined; + var key: [XSalsa20Poly1305.key_length]u8 = undefined; + var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined; + var tag: [XSalsa20Poly1305.tag_length]u8 = undefined; + try crypto.randomBytes(&msg); + try crypto.randomBytes(&key); + try crypto.randomBytes(&nonce); + + XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key); + try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key); +} + +test "xsalsa20poly1305 secretbox" { + var msg: [100]u8 = undefined; + var msg2: [msg.len]u8 = undefined; + var key: [XSalsa20Poly1305.key_length]u8 = undefined; + var nonce: [box.nonce_length]u8 = undefined; + var boxed: [msg.len + box.tag_length]u8 = undefined; + try crypto.randomBytes(&msg); + try crypto.randomBytes(&key); + try crypto.randomBytes(&nonce); + + secretBox.seal(boxed[0..], msg[0..], nonce, key); + try secretBox.open(msg2[0..], boxed[0..], nonce, key); +} + +test "xsalsa20poly1305 box" { + var msg: [100]u8 = undefined; + var msg2: [msg.len]u8 = undefined; + var nonce: [box.nonce_length]u8 = undefined; + var boxed: [msg.len + box.tag_length]u8 = undefined; + try crypto.randomBytes(&msg); + try crypto.randomBytes(&nonce); + + var kp1 = try box.KeyPair.create(null); + var kp2 = try box.KeyPair.create(null); + try box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key); + try box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key); +} + +test "xsalsa20poly1305 sealedbox" { + var msg: [100]u8 = undefined; + var msg2: [msg.len]u8 = undefined; + var boxed: [msg.len + sealedBox.seal_length]u8 = undefined; + try crypto.randomBytes(&msg); + + var kp = try box.KeyPair.create(null); + try sealedBox.seal(boxed[0..], msg[0..], kp.public_key); + try sealedBox.open(msg2[0..], boxed[0..], kp); +}