authorgravatar for 124872+jedisct1@users.noreply.github.comFrank Denis <124872+jedisct1@users.noreply.github.com> 2022-11-06 23:52:41+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-09 13:51:56-07:00
log30d392a87e7ebd51269a557b6bccbc3163e8db75
tree89d6b99524223fb3938c719e3cd587a88292aa7d
parent8d488246da1665f147dcc00b5e6d253cfc3598eb

crypto.salsa20: make the number of rounds a comptime parameter (#13442)

...instead of hard-coding it to 20. - This is consistent with the ChaCha implementation - NaCl and libsodium, that this API is designed to interop with, also support 8 and 12 round variants. The 12 round variant, in particular, provides the same security level as the 20 round variant, but is obviously faster. - scrypt currently uses its own non optimized version of Salsa, just because it use 8 rounds instead of 20. This will help remove code duplication. No behavior nor public API changes. The Salsa20 and XSalsa20 still represent the 20-round variant.

2 files changed, 304 insertions(+), 300 deletions(-)

lib/std/crypto.zig+2
...@@ -147,6 +147,8 @@ pub const stream = struct {...@@ -147,6 +147,8 @@ pub const stream = struct {
147 };147 };
148148
149 pub const salsa = struct {149 pub const salsa = struct {
150 pub const Salsa = @import("crypto/salsa20.zig").Salsa;
151 pub const XSalsa = @import("crypto/salsa20.zig").XSalsa;
150 pub const Salsa20 = @import("crypto/salsa20.zig").Salsa20;152 pub const Salsa20 = @import("crypto/salsa20.zig").Salsa20;
151 pub const XSalsa20 = @import("crypto/salsa20.zig").XSalsa20;153 pub const XSalsa20 = @import("crypto/salsa20.zig").XSalsa20;
152 };154 };
lib/std/crypto/salsa20.zig+302-300
...@@ -14,297 +14,293 @@ const AuthenticationError = crypto.errors.AuthenticationError;...@@ -14,297 +14,293 @@ const AuthenticationError = crypto.errors.AuthenticationError;
14const IdentityElementError = crypto.errors.IdentityElementError;14const IdentityElementError = crypto.errors.IdentityElementError;
15const WeakPublicKeyError = crypto.errors.WeakPublicKeyError;15const WeakPublicKeyError = crypto.errors.WeakPublicKeyError;
1616
17const Salsa20VecImpl = struct {17/// The Salsa cipher with 20 rounds.
18 const Lane = @Vector(4, u32);18pub const Salsa20 = Salsa(20);
19 const Half = @Vector(2, u32);19
20 const BlockVec = [4]Lane;20/// The XSalsa cipher with 20 rounds.
2121pub const XSalsa20 = XSalsa(20);
22 fn initContext(key: [8]u32, d: [4]u32) BlockVec {22
23 const c = "expand 32-byte k";23fn SalsaVecImpl(comptime rounds: comptime_int) type {
24 const constant_le = comptime [4]u32{24 return struct {
25 mem.readIntLittle(u32, c[0..4]),25 const Lane = @Vector(4, u32);
26 mem.readIntLittle(u32, c[4..8]),26 const Half = @Vector(2, u32);
27 mem.readIntLittle(u32, c[8..12]),27 const BlockVec = [4]Lane;
28 mem.readIntLittle(u32, c[12..16]),28
29 };29 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
30 return BlockVec{30 const c = "expand 32-byte k";
31 Lane{ key[0], key[1], key[2], key[3] },31 const constant_le = comptime [4]u32{
32 Lane{ key[4], key[5], key[6], key[7] },32 mem.readIntLittle(u32, c[0..4]),
33 Lane{ constant_le[0], constant_le[1], constant_le[2], constant_le[3] },33 mem.readIntLittle(u32, c[4..8]),
34 Lane{ d[0], d[1], d[2], d[3] },34 mem.readIntLittle(u32, c[8..12]),
35 };35 mem.readIntLittle(u32, c[12..16]),
36 }36 };
3737 return BlockVec{
38 inline fn salsa20Core(x: *BlockVec, input: BlockVec, comptime feedback: bool) void {38 Lane{ key[0], key[1], key[2], key[3] },
39 const n1n2n3n0 = Lane{ input[3][1], input[3][2], input[3][3], input[3][0] };39 Lane{ key[4], key[5], key[6], key[7] },
40 const n1n2 = Half{ n1n2n3n0[0], n1n2n3n0[1] };40 Lane{ constant_le[0], constant_le[1], constant_le[2], constant_le[3] },
41 const n3n0 = Half{ n1n2n3n0[2], n1n2n3n0[3] };41 Lane{ d[0], d[1], d[2], d[3] },
42 const k0k1 = Half{ input[0][0], input[0][1] };42 };
43 const k2k3 = Half{ input[0][2], input[0][3] };
44 const k4k5 = Half{ input[1][0], input[1][1] };
45 const k6k7 = Half{ input[1][2], input[1][3] };
46 const n0k0 = Half{ n3n0[1], k0k1[0] };
47 const k0n0 = Half{ n0k0[1], n0k0[0] };
48 const k4k5k0n0 = Lane{ k4k5[0], k4k5[1], k0n0[0], k0n0[1] };
49 const k1k6 = Half{ k0k1[1], k6k7[0] };
50 const k6k1 = Half{ k1k6[1], k1k6[0] };
51 const n1n2k6k1 = Lane{ n1n2[0], n1n2[1], k6k1[0], k6k1[1] };
52 const k7n3 = Half{ k6k7[1], n3n0[0] };
53 const n3k7 = Half{ k7n3[1], k7n3[0] };
54 const k2k3n3k7 = Lane{ k2k3[0], k2k3[1], n3k7[0], n3k7[1] };
55
56 var diag0 = input[2];
57 var diag1 = @shuffle(u32, k4k5k0n0, undefined, [_]i32{ 1, 2, 3, 0 });
58 var diag2 = @shuffle(u32, n1n2k6k1, undefined, [_]i32{ 1, 2, 3, 0 });
59 var diag3 = @shuffle(u32, k2k3n3k7, undefined, [_]i32{ 1, 2, 3, 0 });
60
61 const start0 = diag0;
62 const start1 = diag1;
63 const start2 = diag2;
64 const start3 = diag3;
65
66 var i: usize = 0;
67 while (i < 20) : (i += 2) {
68 var a0 = diag1 +% diag0;
69 diag3 ^= math.rotl(Lane, a0, 7);
70 var a1 = diag0 +% diag3;
71 diag2 ^= math.rotl(Lane, a1, 9);
72 var a2 = diag3 +% diag2;
73 diag1 ^= math.rotl(Lane, a2, 13);
74 var a3 = diag2 +% diag1;
75 diag0 ^= math.rotl(Lane, a3, 18);
76
77 var diag3_shift = @shuffle(u32, diag3, undefined, [_]i32{ 3, 0, 1, 2 });
78 var diag2_shift = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 });
79 var diag1_shift = @shuffle(u32, diag1, undefined, [_]i32{ 1, 2, 3, 0 });
80 diag3 = diag3_shift;
81 diag2 = diag2_shift;
82 diag1 = diag1_shift;
83
84 a0 = diag3 +% diag0;
85 diag1 ^= math.rotl(Lane, a0, 7);
86 a1 = diag0 +% diag1;
87 diag2 ^= math.rotl(Lane, a1, 9);
88 a2 = diag1 +% diag2;
89 diag3 ^= math.rotl(Lane, a2, 13);
90 a3 = diag2 +% diag3;
91 diag0 ^= math.rotl(Lane, a3, 18);
92
93 diag1_shift = @shuffle(u32, diag1, undefined, [_]i32{ 3, 0, 1, 2 });
94 diag2_shift = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 });
95 diag3_shift = @shuffle(u32, diag3, undefined, [_]i32{ 1, 2, 3, 0 });
96 diag1 = diag1_shift;
97 diag2 = diag2_shift;
98 diag3 = diag3_shift;
99 }43 }
10044
101 if (feedback) {45 inline fn salsaCore(x: *BlockVec, input: BlockVec, comptime feedback: bool) void {
102 diag0 +%= start0;46 const n1n2n3n0 = Lane{ input[3][1], input[3][2], input[3][3], input[3][0] };
103 diag1 +%= start1;47 const n1n2 = Half{ n1n2n3n0[0], n1n2n3n0[1] };
104 diag2 +%= start2;48 const n3n0 = Half{ n1n2n3n0[2], n1n2n3n0[3] };
105 diag3 +%= start3;49 const k0k1 = Half{ input[0][0], input[0][1] };
106 }50 const k2k3 = Half{ input[0][2], input[0][3] };
51 const k4k5 = Half{ input[1][0], input[1][1] };
52 const k6k7 = Half{ input[1][2], input[1][3] };
53 const n0k0 = Half{ n3n0[1], k0k1[0] };
54 const k0n0 = Half{ n0k0[1], n0k0[0] };
55 const k4k5k0n0 = Lane{ k4k5[0], k4k5[1], k0n0[0], k0n0[1] };
56 const k1k6 = Half{ k0k1[1], k6k7[0] };
57 const k6k1 = Half{ k1k6[1], k1k6[0] };
58 const n1n2k6k1 = Lane{ n1n2[0], n1n2[1], k6k1[0], k6k1[1] };
59 const k7n3 = Half{ k6k7[1], n3n0[0] };
60 const n3k7 = Half{ k7n3[1], k7n3[0] };
61 const k2k3n3k7 = Lane{ k2k3[0], k2k3[1], n3k7[0], n3k7[1] };
62
63 var diag0 = input[2];
64 var diag1 = @shuffle(u32, k4k5k0n0, undefined, [_]i32{ 1, 2, 3, 0 });
65 var diag2 = @shuffle(u32, n1n2k6k1, undefined, [_]i32{ 1, 2, 3, 0 });
66 var diag3 = @shuffle(u32, k2k3n3k7, undefined, [_]i32{ 1, 2, 3, 0 });
67
68 const start0 = diag0;
69 const start1 = diag1;
70 const start2 = diag2;
71 const start3 = diag3;
72
73 var i: usize = 0;
74 while (i < rounds) : (i += 2) {
75 diag3 ^= math.rotl(Lane, diag1 +% diag0, 7);
76 diag2 ^= math.rotl(Lane, diag0 +% diag3, 9);
77 diag1 ^= math.rotl(Lane, diag3 +% diag2, 13);
78 diag0 ^= math.rotl(Lane, diag2 +% diag1, 18);
79
80 diag3 = @shuffle(u32, diag3, undefined, [_]i32{ 3, 0, 1, 2 });
81 diag2 = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 });
82 diag1 = @shuffle(u32, diag1, undefined, [_]i32{ 1, 2, 3, 0 });
83
84 diag1 ^= math.rotl(Lane, diag3 +% diag0, 7);
85 diag2 ^= math.rotl(Lane, diag0 +% diag1, 9);
86 diag3 ^= math.rotl(Lane, diag1 +% diag2, 13);
87 diag0 ^= math.rotl(Lane, diag2 +% diag3, 18);
88
89 diag1 = @shuffle(u32, diag1, undefined, [_]i32{ 3, 0, 1, 2 });
90 diag2 = @shuffle(u32, diag2, undefined, [_]i32{ 2, 3, 0, 1 });
91 diag3 = @shuffle(u32, diag3, undefined, [_]i32{ 1, 2, 3, 0 });
92 }
10793
108 const x0x1x10x11 = Lane{ diag0[0], diag1[1], diag0[2], diag1[3] };94 if (feedback) {
109 const x12x13x6x7 = Lane{ diag1[0], diag2[1], diag1[2], diag2[3] };95 diag0 +%= start0;
110 const x8x9x2x3 = Lane{ diag2[0], diag3[1], diag2[2], diag3[3] };96 diag1 +%= start1;
111 const x4x5x14x15 = Lane{ diag3[0], diag0[1], diag3[2], diag0[3] };97 diag2 +%= start2;
98 diag3 +%= start3;
99 }
112100
113 x[0] = Lane{ x0x1x10x11[0], x0x1x10x11[1], x8x9x2x3[2], x8x9x2x3[3] };101 const x0x1x10x11 = Lane{ diag0[0], diag1[1], diag0[2], diag1[3] };
114 x[1] = Lane{ x4x5x14x15[0], x4x5x14x15[1], x12x13x6x7[2], x12x13x6x7[3] };102 const x12x13x6x7 = Lane{ diag1[0], diag2[1], diag1[2], diag2[3] };
115 x[2] = Lane{ x8x9x2x3[0], x8x9x2x3[1], x0x1x10x11[2], x0x1x10x11[3] };103 const x8x9x2x3 = Lane{ diag2[0], diag3[1], diag2[2], diag3[3] };
116 x[3] = Lane{ x12x13x6x7[0], x12x13x6x7[1], x4x5x14x15[2], x4x5x14x15[3] };104 const x4x5x14x15 = Lane{ diag3[0], diag0[1], diag3[2], diag0[3] };
117 }
118105
119 fn hashToBytes(out: *[64]u8, x: BlockVec) void {106 x[0] = Lane{ x0x1x10x11[0], x0x1x10x11[1], x8x9x2x3[2], x8x9x2x3[3] };
120 var i: usize = 0;107 x[1] = Lane{ x4x5x14x15[0], x4x5x14x15[1], x12x13x6x7[2], x12x13x6x7[3] };
121 while (i < 4) : (i += 1) {108 x[2] = Lane{ x8x9x2x3[0], x8x9x2x3[1], x0x1x10x11[2], x0x1x10x11[3] };
122 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]);109 x[3] = Lane{ x12x13x6x7[0], x12x13x6x7[1], x4x5x14x15[2], x4x5x14x15[3] };
123 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]);
124 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]);
125 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]);
126 }110 }
127 }
128111
129 fn salsa20Xor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {112 fn hashToBytes(out: *[64]u8, x: BlockVec) void {
130 var ctx = initContext(key, d);113 var i: usize = 0;
131 var x: BlockVec = undefined;114 while (i < 4) : (i += 1) {
132 var buf: [64]u8 = undefined;115 mem.writeIntLittle(u32, out[16 * i + 0 ..][0..4], x[i][0]);
133 var i: usize = 0;116 mem.writeIntLittle(u32, out[16 * i + 4 ..][0..4], x[i][1]);
134 while (i + 64 <= in.len) : (i += 64) {117 mem.writeIntLittle(u32, out[16 * i + 8 ..][0..4], x[i][2]);
135 salsa20Core(x[0..], ctx, true);118 mem.writeIntLittle(u32, out[16 * i + 12 ..][0..4], x[i][3]);
136 hashToBytes(buf[0..], x);
137 var xout = out[i..];
138 const xin = in[i..];
139 var j: usize = 0;
140 while (j < 64) : (j += 1) {
141 xout[j] = xin[j];
142 }119 }
143 j = 0;120 }
144 while (j < 64) : (j += 1) {121
145 xout[j] ^= buf[j];122 fn salsaXor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
123 var ctx = initContext(key, d);
124 var x: BlockVec = undefined;
125 var buf: [64]u8 = undefined;
126 var i: usize = 0;
127 while (i + 64 <= in.len) : (i += 64) {
128 salsaCore(x[0..], ctx, true);
129 hashToBytes(buf[0..], x);
130 var xout = out[i..];
131 const xin = in[i..];
132 var j: usize = 0;
133 while (j < 64) : (j += 1) {
134 xout[j] = xin[j];
135 }
136 j = 0;
137 while (j < 64) : (j += 1) {
138 xout[j] ^= buf[j];
139 }
140 ctx[3][2] +%= 1;
141 if (ctx[3][2] == 0) {
142 ctx[3][3] += 1;
143 }
146 }144 }
147 ctx[3][2] +%= 1;145 if (i < in.len) {
148 if (ctx[3][2] == 0) {146 salsaCore(x[0..], ctx, true);
149 ctx[3][3] += 1;147 hashToBytes(buf[0..], x);
148
149 var xout = out[i..];
150 const xin = in[i..];
151 var j: usize = 0;
152 while (j < in.len % 64) : (j += 1) {
153 xout[j] = xin[j] ^ buf[j];
154 }
150 }155 }
151 }156 }
152 if (i < in.len) {
153 salsa20Core(x[0..], ctx, true);
154 hashToBytes(buf[0..], x);
155157
156 var xout = out[i..];158 fn hsalsa(input: [16]u8, key: [32]u8) [32]u8 {
157 const xin = in[i..];159 var c: [4]u32 = undefined;
158 var j: usize = 0;160 for (c) |_, i| {
159 while (j < in.len % 64) : (j += 1) {161 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
160 xout[j] = xin[j] ^ buf[j];
161 }162 }
163 const ctx = initContext(keyToWords(key), c);
164 var x: BlockVec = undefined;
165 salsaCore(x[0..], ctx, false);
166 var out: [32]u8 = undefined;
167 mem.writeIntLittle(u32, out[0..4], x[0][0]);
168 mem.writeIntLittle(u32, out[4..8], x[1][1]);
169 mem.writeIntLittle(u32, out[8..12], x[2][2]);
170 mem.writeIntLittle(u32, out[12..16], x[3][3]);
171 mem.writeIntLittle(u32, out[16..20], x[1][2]);
172 mem.writeIntLittle(u32, out[20..24], x[1][3]);
173 mem.writeIntLittle(u32, out[24..28], x[2][0]);
174 mem.writeIntLittle(u32, out[28..32], x[2][1]);
175 return out;
162 }176 }
163 }177 };
178}
164179
165 fn hsalsa20(input: [16]u8, key: [32]u8) [32]u8 {180fn SalsaNonVecImpl(comptime rounds: comptime_int) type {
166 var c: [4]u32 = undefined;181 return struct {
167 for (c) |_, i| {182 const BlockVec = [16]u32;
168 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);183
184 fn initContext(key: [8]u32, d: [4]u32) BlockVec {
185 const c = "expand 32-byte k";
186 const constant_le = comptime [4]u32{
187 mem.readIntLittle(u32, c[0..4]),
188 mem.readIntLittle(u32, c[4..8]),
189 mem.readIntLittle(u32, c[8..12]),
190 mem.readIntLittle(u32, c[12..16]),
191 };
192 return BlockVec{
193 constant_le[0], key[0], key[1], key[2],
194 key[3], constant_le[1], d[0], d[1],
195 d[2], d[3], constant_le[2], key[4],
196 key[5], key[6], key[7], constant_le[3],
197 };
169 }198 }
170 const ctx = initContext(keyToWords(key), c);
171 var x: BlockVec = undefined;
172 salsa20Core(x[0..], ctx, false);
173 var out: [32]u8 = undefined;
174 mem.writeIntLittle(u32, out[0..4], x[0][0]);
175 mem.writeIntLittle(u32, out[4..8], x[1][1]);
176 mem.writeIntLittle(u32, out[8..12], x[2][2]);
177 mem.writeIntLittle(u32, out[12..16], x[3][3]);
178 mem.writeIntLittle(u32, out[16..20], x[1][2]);
179 mem.writeIntLittle(u32, out[20..24], x[1][3]);
180 mem.writeIntLittle(u32, out[24..28], x[2][0]);
181 mem.writeIntLittle(u32, out[28..32], x[2][1]);
182 return out;
183 }
184};
185199
186const Salsa20NonVecImpl = struct {200 const QuarterRound = struct {
187 const BlockVec = [16]u32;201 a: usize,
188202 b: usize,
189 fn initContext(key: [8]u32, d: [4]u32) BlockVec {203 c: usize,
190 const c = "expand 32-byte k";204 d: u6,
191 const constant_le = comptime [4]u32{
192 mem.readIntLittle(u32, c[0..4]),
193 mem.readIntLittle(u32, c[4..8]),
194 mem.readIntLittle(u32, c[8..12]),
195 mem.readIntLittle(u32, c[12..16]),
196 };
197 return BlockVec{
198 constant_le[0], key[0], key[1], key[2],
199 key[3], constant_le[1], d[0], d[1],
200 d[2], d[3], constant_le[2], key[4],
201 key[5], key[6], key[7], constant_le[3],
202 };205 };
203 }
204206
205 const QuarterRound = struct {207 inline fn Rp(a: usize, b: usize, c: usize, d: u6) QuarterRound {
206 a: usize,208 return QuarterRound{
207 b: usize,209 .a = a,
208 c: usize,210 .b = b,
209 d: u6,211 .c = c,
210 };212 .d = d,
211213 };
212 inline fn Rp(a: usize, b: usize, c: usize, d: u6) QuarterRound {214 }
213 return QuarterRound{
214 .a = a,
215 .b = b,
216 .c = c,
217 .d = d,
218 };
219 }
220215
221 inline fn salsa20Core(x: *BlockVec, input: BlockVec, comptime feedback: bool) void {216 inline fn salsaCore(x: *BlockVec, input: BlockVec, comptime feedback: bool) void {
222 const arx_steps = comptime [_]QuarterRound{217 const arx_steps = comptime [_]QuarterRound{
223 Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18),218 Rp(4, 0, 12, 7), Rp(8, 4, 0, 9), Rp(12, 8, 4, 13), Rp(0, 12, 8, 18),
224 Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18),219 Rp(9, 5, 1, 7), Rp(13, 9, 5, 9), Rp(1, 13, 9, 13), Rp(5, 1, 13, 18),
225 Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18),220 Rp(14, 10, 6, 7), Rp(2, 14, 10, 9), Rp(6, 2, 14, 13), Rp(10, 6, 2, 18),
226 Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18),221 Rp(3, 15, 11, 7), Rp(7, 3, 15, 9), Rp(11, 7, 3, 13), Rp(15, 11, 7, 18),
227 Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18),222 Rp(1, 0, 3, 7), Rp(2, 1, 0, 9), Rp(3, 2, 1, 13), Rp(0, 3, 2, 18),
228 Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18),223 Rp(6, 5, 4, 7), Rp(7, 6, 5, 9), Rp(4, 7, 6, 13), Rp(5, 4, 7, 18),
229 Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18),224 Rp(11, 10, 9, 7), Rp(8, 11, 10, 9), Rp(9, 8, 11, 13), Rp(10, 9, 8, 18),
230 Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),225 Rp(12, 15, 14, 7), Rp(13, 12, 15, 9), Rp(14, 13, 12, 13), Rp(15, 14, 13, 18),
231 };226 };
232 x.* = input;227 x.* = input;
233 var j: usize = 0;228 var j: usize = 0;
234 while (j < 20) : (j += 2) {229 while (j < rounds) : (j += 2) {
235 inline for (arx_steps) |r| {230 inline for (arx_steps) |r| {
236 x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);231 x[r.a] ^= math.rotl(u32, x[r.b] +% x[r.c], r.d);
232 }
237 }233 }
238 }234 if (feedback) {
239 if (feedback) {235 j = 0;
240 j = 0;236 while (j < 16) : (j += 1) {
241 while (j < 16) : (j += 1) {237 x[j] +%= input[j];
242 x[j] +%= input[j];238 }
243 }239 }
244 }240 }
245 }
246241
247 fn hashToBytes(out: *[64]u8, x: BlockVec) void {242 fn hashToBytes(out: *[64]u8, x: BlockVec) void {
248 for (x) |w, i| {243 for (x) |w, i| {
249 mem.writeIntLittle(u32, out[i * 4 ..][0..4], w);244 mem.writeIntLittle(u32, out[i * 4 ..][0..4], w);
245 }
250 }246 }
251 }
252247
253 fn salsa20Xor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {248 fn salsaXor(out: []u8, in: []const u8, key: [8]u32, d: [4]u32) void {
254 var ctx = initContext(key, d);249 var ctx = initContext(key, d);
255 var x: BlockVec = undefined;250 var x: BlockVec = undefined;
256 var buf: [64]u8 = undefined;251 var buf: [64]u8 = undefined;
257 var i: usize = 0;252 var i: usize = 0;
258 while (i + 64 <= in.len) : (i += 64) {253 while (i + 64 <= in.len) : (i += 64) {
259 salsa20Core(x[0..], ctx, true);254 salsaCore(x[0..], ctx, true);
260 hashToBytes(buf[0..], x);255 hashToBytes(buf[0..], x);
261 var xout = out[i..];256 var xout = out[i..];
262 const xin = in[i..];257 const xin = in[i..];
263 var j: usize = 0;258 var j: usize = 0;
264 while (j < 64) : (j += 1) {259 while (j < 64) : (j += 1) {
265 xout[j] = xin[j];260 xout[j] = xin[j];
261 }
262 j = 0;
263 while (j < 64) : (j += 1) {
264 xout[j] ^= buf[j];
265 }
266 ctx[9] += @boolToInt(@addWithOverflow(u32, ctx[8], 1, &ctx[8]));
266 }267 }
267 j = 0;268 if (i < in.len) {
268 while (j < 64) : (j += 1) {269 salsaCore(x[0..], ctx, true);
269 xout[j] ^= buf[j];270 hashToBytes(buf[0..], x);
271
272 var xout = out[i..];
273 const xin = in[i..];
274 var j: usize = 0;
275 while (j < in.len % 64) : (j += 1) {
276 xout[j] = xin[j] ^ buf[j];
277 }
270 }278 }
271 ctx[9] += @boolToInt(@addWithOverflow(u32, ctx[8], 1, &ctx[8]));
272 }279 }
273 if (i < in.len) {
274 salsa20Core(x[0..], ctx, true);
275 hashToBytes(buf[0..], x);
276280
277 var xout = out[i..];281 fn hsalsa(input: [16]u8, key: [32]u8) [32]u8 {
278 const xin = in[i..];282 var c: [4]u32 = undefined;
279 var j: usize = 0;283 for (c) |_, i| {
280 while (j < in.len % 64) : (j += 1) {284 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
281 xout[j] = xin[j] ^ buf[j];
282 }285 }
286 const ctx = initContext(keyToWords(key), c);
287 var x: BlockVec = undefined;
288 salsaCore(x[0..], ctx, false);
289 var out: [32]u8 = undefined;
290 mem.writeIntLittle(u32, out[0..4], x[0]);
291 mem.writeIntLittle(u32, out[4..8], x[5]);
292 mem.writeIntLittle(u32, out[8..12], x[10]);
293 mem.writeIntLittle(u32, out[12..16], x[15]);
294 mem.writeIntLittle(u32, out[16..20], x[6]);
295 mem.writeIntLittle(u32, out[20..24], x[7]);
296 mem.writeIntLittle(u32, out[24..28], x[8]);
297 mem.writeIntLittle(u32, out[28..32], x[9]);
298 return out;
283 }299 }
284 }300 };
285301}
286 fn hsalsa20(input: [16]u8, key: [32]u8) [32]u8 {
287 var c: [4]u32 = undefined;
288 for (c) |_, i| {
289 c[i] = mem.readIntLittle(u32, input[4 * i ..][0..4]);
290 }
291 const ctx = initContext(keyToWords(key), c);
292 var x: BlockVec = undefined;
293 salsa20Core(x[0..], ctx, false);
294 var out: [32]u8 = undefined;
295 mem.writeIntLittle(u32, out[0..4], x[0]);
296 mem.writeIntLittle(u32, out[4..8], x[5]);
297 mem.writeIntLittle(u32, out[8..12], x[10]);
298 mem.writeIntLittle(u32, out[12..16], x[15]);
299 mem.writeIntLittle(u32, out[16..20], x[6]);
300 mem.writeIntLittle(u32, out[20..24], x[7]);
301 mem.writeIntLittle(u32, out[24..28], x[8]);
302 mem.writeIntLittle(u32, out[28..32], x[9]);
303 return out;
304 }
305};
306302
307const Salsa20Impl = if (builtin.cpu.arch == .x86_64) Salsa20VecImpl else Salsa20NonVecImpl;303const SalsaImpl = if (builtin.cpu.arch == .x86_64) SalsaVecImpl else SalsaNonVecImpl;
308304
309fn keyToWords(key: [32]u8) [8]u32 {305fn keyToWords(key: [32]u8) [8]u32 {
310 var k: [8]u32 = undefined;306 var k: [8]u32 = undefined;
...@@ -315,52 +311,56 @@ fn keyToWords(key: [32]u8) [8]u32 {...@@ -315,52 +311,56 @@ fn keyToWords(key: [32]u8) [8]u32 {
315 return k;311 return k;
316}312}
317313
318fn extend(key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } {314fn extend(comptime rounds: comptime_int, key: [32]u8, nonce: [24]u8) struct { key: [32]u8, nonce: [8]u8 } {
319 return .{315 return .{
320 .key = Salsa20Impl.hsalsa20(nonce[0..16].*, key),316 .key = SalsaImpl(rounds).hsalsa(nonce[0..16].*, key),
321 .nonce = nonce[16..24].*,317 .nonce = nonce[16..24].*,
322 };318 };
323}319}
324320
325/// The Salsa20 stream cipher.321/// The Salsa stream cipher.
326pub const Salsa20 = struct {322pub fn Salsa(comptime rounds: comptime_int) type {
327 /// Nonce length in bytes.323 return struct {
328 pub const nonce_length = 8;324 /// Nonce length in bytes.
329 /// Key length in bytes.325 pub const nonce_length = 8;
330 pub const key_length = 32;326 /// Key length in bytes.
331327 pub const key_length = 32;
332 /// Add the output of the Salsa20 stream cipher to `in` and stores the result into `out`.328
333 /// WARNING: This function doesn't provide authenticated encryption.329 /// Add the output of the Salsa stream cipher to `in` and stores the result into `out`.
334 /// Using the AEAD or one of the `box` versions is usually preferred.330 /// WARNING: This function doesn't provide authenticated encryption.
335 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {331 /// Using the AEAD or one of the `box` versions is usually preferred.
336 debug.assert(in.len == out.len);332 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
337333 debug.assert(in.len == out.len);
338 var d: [4]u32 = undefined;334
339 d[0] = mem.readIntLittle(u32, nonce[0..4]);335 var d: [4]u32 = undefined;
340 d[1] = mem.readIntLittle(u32, nonce[4..8]);336 d[0] = mem.readIntLittle(u32, nonce[0..4]);
341 d[2] = @truncate(u32, counter);337 d[1] = mem.readIntLittle(u32, nonce[4..8]);
342 d[3] = @truncate(u32, counter >> 32);338 d[2] = @truncate(u32, counter);
343 Salsa20Impl.salsa20Xor(out, in, keyToWords(key), d);339 d[3] = @truncate(u32, counter >> 32);
344 }340 SalsaImpl(rounds).salsaXor(out, in, keyToWords(key), d);
345};341 }
342 };
343}
346344
347/// The XSalsa20 stream cipher.345/// The XSalsa stream cipher.
348pub const XSalsa20 = struct {346pub fn XSalsa(comptime rounds: comptime_int) type {
349 /// Nonce length in bytes.347 return struct {
350 pub const nonce_length = 24;348 /// Nonce length in bytes.
351 /// Key length in bytes.349 pub const nonce_length = 24;
352 pub const key_length = 32;350 /// Key length in bytes.
353351 pub const key_length = 32;
354 /// Add the output of the XSalsa20 stream cipher to `in` and stores the result into `out`.352
355 /// WARNING: This function doesn't provide authenticated encryption.353 /// Add the output of the XSalsa stream cipher to `in` and stores the result into `out`.
356 /// Using the AEAD or one of the `box` versions is usually preferred.354 /// WARNING: This function doesn't provide authenticated encryption.
357 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {355 /// Using the AEAD or one of the `box` versions is usually preferred.
358 const extended = extend(key, nonce);356 pub fn xor(out: []u8, in: []const u8, counter: u64, key: [key_length]u8, nonce: [nonce_length]u8) void {
359 Salsa20.xor(out, in, counter, extended.key, extended.nonce);357 const extended = extend(rounds, key, nonce);
360 }358 Salsa(rounds).xor(out, in, counter, extended.key, extended.nonce);
361};359 }
360 };
361}
362362
363/// The XSalsa20 stream cipher, combined with the Poly1305 MAC363/// The XSalsa stream cipher, combined with the Poly1305 MAC
364pub const XSalsa20Poly1305 = struct {364pub const XSalsa20Poly1305 = struct {
365 /// Authentication tag length in bytes.365 /// Authentication tag length in bytes.
366 pub const tag_length = Poly1305.mac_length;366 pub const tag_length = Poly1305.mac_length;
...@@ -369,6 +369,8 @@ pub const XSalsa20Poly1305 = struct {...@@ -369,6 +369,8 @@ pub const XSalsa20Poly1305 = struct {
369 /// Key length in bytes.369 /// Key length in bytes.
370 pub const key_length = XSalsa20.key_length;370 pub const key_length = XSalsa20.key_length;
371371
372 const rounds = 20;
373
372 /// c: ciphertext: output buffer should be of size m.len374 /// c: ciphertext: output buffer should be of size m.len
373 /// tag: authentication tag: output MAC375 /// tag: authentication tag: output MAC
374 /// m: message376 /// m: message
...@@ -377,7 +379,7 @@ pub const XSalsa20Poly1305 = struct {...@@ -377,7 +379,7 @@ pub const XSalsa20Poly1305 = struct {
377 /// k: private key379 /// k: private key
378 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {380 pub fn encrypt(c: []u8, tag: *[tag_length]u8, m: []const u8, ad: []const u8, npub: [nonce_length]u8, k: [key_length]u8) void {
379 debug.assert(c.len == m.len);381 debug.assert(c.len == m.len);
380 const extended = extend(k, npub);382 const extended = extend(rounds, k, npub);
381 var block0 = [_]u8{0} ** 64;383 var block0 = [_]u8{0} ** 64;
382 const mlen0 = math.min(32, m.len);384 const mlen0 = math.min(32, m.len);
383 mem.copy(u8, block0[32..][0..mlen0], m[0..mlen0]);385 mem.copy(u8, block0[32..][0..mlen0], m[0..mlen0]);
...@@ -398,7 +400,7 @@ pub const XSalsa20Poly1305 = struct {...@@ -398,7 +400,7 @@ pub const XSalsa20Poly1305 = struct {
398 /// k: private key400 /// k: private key
399 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 {401 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 {
400 debug.assert(c.len == m.len);402 debug.assert(c.len == m.len);
401 const extended = extend(k, npub);403 const extended = extend(rounds, k, npub);
402 var block0 = [_]u8{0} ** 64;404 var block0 = [_]u8{0} ** 64;
403 const mlen0 = math.min(32, c.len);405 const mlen0 = math.min(32, c.len);
404 mem.copy(u8, block0[32..][0..mlen0], c[0..mlen0]);406 mem.copy(u8, block0[32..][0..mlen0], c[0..mlen0]);
...@@ -482,7 +484,7 @@ pub const Box = struct {...@@ -482,7 +484,7 @@ pub const Box = struct {
482 pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)![shared_length]u8 {484 pub fn createSharedSecret(public_key: [public_length]u8, secret_key: [secret_length]u8) (IdentityElementError || WeakPublicKeyError)![shared_length]u8 {
483 const p = try X25519.scalarmult(secret_key, public_key);485 const p = try X25519.scalarmult(secret_key, public_key);
484 const zero = [_]u8{0} ** 16;486 const zero = [_]u8{0} ** 16;
485 return Salsa20Impl.hsalsa20(zero, p);487 return SalsaImpl(20).hsalsa(zero, p);
486 }488 }
487489
488 /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`.490 /// Encrypt and authenticate a message using a recipient's public key `public_key` and a sender's `secret_key`.