diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index d9b369a0b9..46dfa6a715 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -43,6 +43,7 @@ pub const auth = struct { pub const core = struct { pub const aes = @import("crypto/aes.zig"); pub const Gimli = @import("crypto/gimli.zig").State; + pub const Xoodoo = @import("crypto/xoodoo.zig").State; /// Modes are generic compositions to construct encryption/decryption functions from block ciphers and permutations. /// diff --git a/lib/std/crypto/xoodoo.zig b/lib/std/crypto/xoodoo.zig new file mode 100644 index 0000000000..bbc579f073 --- /dev/null +++ b/lib/std/crypto/xoodoo.zig @@ -0,0 +1,141 @@ +//! Xoodoo is a 384-bit permutation designed to achieve high security with high +//! performance across a broad range of platforms, including 64-bit Intel/AMD +//! server CPUs, 64-bit and 32-bit ARM smartphone CPUs, 32-bit ARM +//! microcontrollers, 8-bit AVR microcontrollers, FPGAs, ASICs without +//! side-channel protection, and ASICs with side-channel protection. +//! +//! Xoodoo is the core function of Xoodyak, a finalist of the NIST lightweight cryptography competition. +//! https://csrc.nist.gov/CSRC/media/Projects/Lightweight-Cryptography/documents/round-1/spec-doc/Xoodyak-spec.pdf +//! +//! It is not meant to be used directly, but as a building block for symmetric cryptography. + +const std = @import("../std.zig"); +const builtin = @import("builtin"); +const mem = std.mem; +const math = std.math; +const testing = std.testing; + +/// A Xoodoo state. +pub const State = struct { + /// Number of bytes in the state. + pub const block_bytes = 48; + + const rcs = [12]u32{ 0x058, 0x038, 0x3c0, 0x0d0, 0x120, 0x014, 0x060, 0x02c, 0x380, 0x0f0, 0x1a0, 0x012 }; + const Lane = @Vector(4, u32); + st: [3]Lane, + + /// Initialize a state from a slice of bytes. + pub fn init(initial_state: [block_bytes]u8) State { + var state = State{ .st = undefined }; + mem.copy(u8, state.asBytes(), &initial_state); + state.endianSwap(); + return state; + } + + // A representation of the state as 32-bit words. + fn asWords(self: *State) *[12]u32 { + return @ptrCast(*[12]u32, &self.st); + } + + /// A representation of the state as bytes. The byte order is architecture-dependent. + pub fn asBytes(self: *State) *[block_bytes]u8 { + return mem.asBytes(&self.st); + } + + /// Byte-swap words storing the bytes of a given range if the architecture is not little-endian. + pub fn endianSwapPartial(self: *State, from: usize, to: usize) void { + for (self.asWords()[from / 4 .. (to + 3) / 4]) |*w| { + w.* = mem.littleToNative(u32, w.*); + } + } + + /// Byte-swap the entire state if the architecture is not little-endian. + pub fn endianSwap(self: *State) void { + for (self.asWords()) |*w| { + w.* = mem.littleToNative(u32, w.*); + } + } + + /// XOR a byte into the state at a given offset. + pub fn addByte(self: *State, byte: u8, offset: usize) void { + self.endianSwapPartial(offset, offset); + self.asBytes()[offset] ^= byte; + self.endianSwapPartial(offset, offset); + } + + /// XOR bytes into the beginning of the state. + pub fn addBytes(self: *State, bytes: []const u8) void { + self.endianSwap(); + for (self.asBytes()[0..bytes.len]) |*byte, i| { + byte.* ^= bytes[i]; + } + self.endianSwap(); + } + + /// Extract the first bytes of the state. + pub fn extract(self: *State, out: []u8) void { + self.endianSwap(); + mem.copy(u8, out, self.asBytes()[0..out.len]); + self.endianSwap(); + } + + /// Set the words storing the bytes of a given range to zero. + pub fn clear(self: *State, from: usize, to: usize) void { + mem.set(u32, self.asWords()[from / 4 .. (to + 3) / 4], 0); + } + + /// Apply the Xoodoo permutation. + pub fn permute(self: *State) void { + const rot8x32 = comptime if (builtin.target.cpu.arch.endian() == .Big) + [_]i32{ 9, 10, 11, 8, 13, 14, 15, 12, 1, 2, 3, 0, 5, 6, 7, 4 } + else + [_]i32{ 11, 8, 9, 10, 15, 12, 13, 14, 3, 0, 1, 2, 7, 4, 5, 6 }; + + var a = self.st[0]; + var b = self.st[1]; + var c = self.st[2]; + inline for (rcs) |rc| { + var p = @shuffle(u32, a ^ b ^ c, undefined, [_]i32{ 3, 0, 1, 2 }); + var e = math.rotl(Lane, p, 5); + p = math.rotl(Lane, p, 14); + e ^= p; + a ^= e; + b ^= e; + c ^= e; + b = @shuffle(u32, b, undefined, [_]i32{ 3, 0, 1, 2 }); + c = math.rotl(Lane, c, 11); + a[0] ^= rc; + a ^= ~b & c; + b ^= ~c & a; + c ^= ~a & b; + b = math.rotl(Lane, b, 1); + c = @bitCast(Lane, @shuffle(u8, @bitCast(@Vector(16, u8), c), undefined, rot8x32)); + } + self.st[0] = a; + self.st[1] = b; + self.st[2] = c; + } +}; + +test "xoodoo" { + const bytes = [_]u8{0x01} ** State.block_bytes; + var st = State.init(bytes); + var out: [State.block_bytes]u8 = undefined; + st.permute(); + st.extract(&out); + const expected1 = [_]u8{ 51, 240, 163, 117, 43, 238, 62, 200, 114, 52, 79, 41, 48, 108, 150, 181, 24, 5, 252, 185, 235, 179, 28, 3, 116, 170, 36, 15, 232, 35, 116, 61, 110, 4, 109, 227, 91, 205, 0, 180, 179, 146, 112, 235, 96, 212, 206, 205 }; + try testing.expectEqualSlices(u8, &expected1, &out); + st.clear(0, 10); + st.extract(&out); + const expected2 = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 108, 150, 181, 24, 5, 252, 185, 235, 179, 28, 3, 116, 170, 36, 15, 232, 35, 116, 61, 110, 4, 109, 227, 91, 205, 0, 180, 179, 146, 112, 235, 96, 212, 206, 205 }; + try testing.expectEqualSlices(u8, &expected2, &out); + st.addByte(1, 5); + st.addByte(2, 5); + st.extract(&out); + const expected3 = [_]u8{ 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 48, 108, 150, 181, 24, 5, 252, 185, 235, 179, 28, 3, 116, 170, 36, 15, 232, 35, 116, 61, 110, 4, 109, 227, 91, 205, 0, 180, 179, 146, 112, 235, 96, 212, 206, 205 }; + try testing.expectEqualSlices(u8, &expected3, &out); + st.addBytes(&bytes); + st.extract(&out); + const expected4 = [_]u8{ 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 49, 109, 151, 180, 25, 4, 253, 184, 234, 178, 29, 2, 117, 171, 37, 14, 233, 34, 117, 60, 111, 5, 108, 226, 90, 204, 1, 181, 178, 147, 113, 234, 97, 213, 207, 204 }; + try testing.expectEqualSlices(u8, &expected4, &out); +} diff --git a/lib/std/rand.zig b/lib/std/rand.zig index 72537e952f..a121a3f5ba 100644 --- a/lib/std/rand.zig +++ b/lib/std/rand.zig @@ -18,10 +18,10 @@ const maxInt = std.math.maxInt; pub const DefaultPrng = Xoshiro256; /// Cryptographically secure random numbers. -pub const DefaultCsprng = Gimli; +pub const DefaultCsprng = Xoodoo; pub const Isaac64 = @import("rand/Isaac64.zig"); -pub const Gimli = @import("rand/Gimli.zig"); +pub const Xoodoo = @import("rand/Xoodoo.zig"); pub const Pcg = @import("rand/Pcg.zig"); pub const Xoroshiro128 = @import("rand/Xoroshiro128.zig"); pub const Xoshiro256 = @import("rand/Xoshiro256.zig"); diff --git a/lib/std/rand/Xoodoo.zig b/lib/std/rand/Xoodoo.zig new file mode 100644 index 0000000000..3e16f2a864 --- /dev/null +++ b/lib/std/rand/Xoodoo.zig @@ -0,0 +1,42 @@ +//! CSPRNG + +const std = @import("std"); +const Random = std.rand.Random; +const min = std.math.min; +const mem = std.mem; +const Xoodoo = @This(); + +const State = std.crypto.core.Xoodoo; + +state: State, + +const rate = 16; +pub const secret_seed_length = 32; + +/// The seed must be uniform, secret and `secret_seed_length` bytes long. +pub fn init(secret_seed: [secret_seed_length]u8) Xoodoo { + var initial_state: [State.block_bytes]u8 = undefined; + mem.copy(u8, initial_state[0..secret_seed_length], &secret_seed); + mem.set(u8, initial_state[secret_seed_length..], 0); + var state = State.init(initial_state); + state.permute(); + return Xoodoo{ .state = state }; +} + +pub fn random(self: *Xoodoo) Random { + return Random.init(self, fill); +} + +pub fn fill(self: *Xoodoo, buf: []u8) void { + var i: usize = 0; + while (true) { + const left = buf.len - i; + const n = min(left, rate); + self.state.extract(buf[i..][0..n]); + if (left == 0) break; + self.state.permute(); + i += n; + } + self.state.clear(0, rate); + self.state.permute(); +}