diff --git a/build.zig b/build.zig index f9d3729..c54780c 100644 --- a/build.zig +++ b/build.zig @@ -69,6 +69,7 @@ pub const Examples = struct { squarewave: *microzig.EmbeddedExecutable, //uart_pins: microzig.EmbeddedExecutable, flash_program: *microzig.EmbeddedExecutable, + random: *microzig.EmbeddedExecutable, pub fn init(b: *Builder, optimize: std.builtin.OptimizeMode) Examples { var ret: Examples = undefined; diff --git a/examples/random.zig b/examples/random.zig new file mode 100644 index 0000000..dbf00d5 --- /dev/null +++ b/examples/random.zig @@ -0,0 +1,69 @@ +//! Example that generates a 4 byte random number every second and outputs the result over UART + +const std = @import("std"); +const microzig = @import("microzig"); + +const rp2040 = microzig.hal; +const flash = rp2040.flash; +const time = rp2040.time; +const gpio = rp2040.gpio; +const clocks = rp2040.clocks; +const rand = rp2040.rand; + +const led = 25; +const uart_id = 0; +const baud_rate = 115200; +const uart_tx_pin = 0; +const uart_rx_pin = 1; + +pub fn panic(message: []const u8, _: ?*std.builtin.StackTrace, _: ?usize) noreturn { + std.log.err("panic: {s}", .{message}); + @breakpoint(); + while (true) {} +} + +pub const std_options = struct { + pub const log_level = .debug; + pub const logFn = rp2040.uart.log; +}; + +pub fn main() !void { + gpio.reset(); + gpio.init(led); + gpio.set_direction(led, .out); + gpio.put(led, 1); + + const uart = rp2040.uart.UART.init(uart_id, .{ + .baud_rate = baud_rate, + .tx_pin = uart_tx_pin, + .rx_pin = uart_rx_pin, + .clock_config = rp2040.clock_config, + }); + + var ascon = rand.Ascon.init(); + var rng = ascon.random(); + + rp2040.uart.init_logger(uart); + + var buffer: [8]u8 = undefined; + var dist: [256]usize = .{0} ** 256; + var counter: usize = 0; + + while (true) { + rng.bytes(buffer[0..]); + counter += 8; + for (buffer) |byte| { + dist[@intCast(usize, byte)] += 1; + } + std.log.info("Generate random number: {any}", .{buffer}); + + if (counter % 256 == 0) { + var i: usize = 0; + std.log.info("Distribution:", .{}); + while (i < 256) : (i += 1) { + std.log.info("{} -> {}, {d:2}%", .{ i, dist[i], @intToFloat(f32, dist[i]) / @intToFloat(f32, counter) }); + } + } + time.sleep_ms(1000); + } +} diff --git a/src/hal.zig b/src/hal.zig index 5813cf9..1350d07 100644 --- a/src/hal.zig +++ b/src/hal.zig @@ -16,6 +16,7 @@ pub const irq = @import("hal/irq.zig"); pub const rom = @import("hal/rom.zig"); pub const flash = @import("hal/flash.zig"); pub const pio = @import("hal/pio.zig"); +pub const rand = @import("hal/random.zig"); pub const clock_config = clocks.GlobalConfiguration.init(.{ .ref = .{ .source = .src_xosc }, diff --git a/src/hal/random.zig b/src/hal/random.zig new file mode 100644 index 0000000..3a292a6 --- /dev/null +++ b/src/hal/random.zig @@ -0,0 +1,87 @@ +//! Random number generator (RNG) using the Ascon CSPRNG + +const std = @import("std"); +const assert = std.debug.assert; +const Random = std.rand.Random; + +const microzig = @import("microzig"); +const peripherals = microzig.chip.peripherals; + +/// Wrapper around the Ascon CSPRNG with automatic reseed using the ROSC +/// +/// ## Usage +/// +/// ```zig +/// var ascon = Ascon.init(); +/// var rng = ascon.random(); +/// ``` +/// +/// _WARNING_: This might not meet the requirements of randomness +/// for security systems because the ROSC as entropy source can be +/// compromised. However, it promises at least equal distribution. +pub const Ascon = struct { + state: std.rand.Ascon, + counter: usize = 0, + + const reseed_threshold = 4096; + const secret_seed_length = std.rand.Ascon.secret_seed_length; + + pub fn init() @This() { + // Ensure that the system clocks run from the XOSC and/or PLLs + const ref_src = peripherals.CLOCKS.CLK_REF_CTRL.read().SRC.value; + const sys_clk_src = peripherals.CLOCKS.CLK_SYS_CTRL.read().SRC.value; + const aux_src = peripherals.CLOCKS.CLK_SYS_CTRL.read().AUXSRC.value; + assert((ref_src != .rosc_clksrc_ph and sys_clk_src == .clk_ref) or + (sys_clk_src == .clksrc_clk_sys_aux and aux_src != .rosc_clksrc)); + + // Get `secret_seed_length` random bytes from the ROSC ... + var b: [secret_seed_length]u8 = undefined; + rosc(&b); + + return @This(){ .state = std.rand.Ascon.init(b) }; + } + + /// Returns a `std.rand.Random` structure backed by the current RNG + pub fn random(self: *@This()) Random { + return Random.init(self, fill); + } + + /// Fills the buffer with random bytes + pub fn fill(self: *@This(), buf: []u8) void { + // Reseed every `secret_seed_length` bytes + if (self.counter > reseed_threshold) { + var b: [secret_seed_length]u8 = undefined; + rosc(&b); + self.state.addEntropy(&b); + self.counter = 0; + } + self.state.fill(buf); + self.counter += buf.len; + } + + /// Fill the buffer with up to buffer.len random bytes + /// + /// rand uses the RANDOMBIT register of the ROSC as its source, i. e., + /// the system clocks _MUST_ run from the XOSC and/or PLLs. + /// + /// _WARNING_: This function does not meet the requirements of randomness + /// for security systems because it can be compromised, but it may be useful + /// in less critical applications. + fn rosc(buffer: []u8) void { + const rosc_state = peripherals.ROSC.CTRL.read().ENABLE.value; + // Enable the ROSC so it generates random bits for us + peripherals.ROSC.CTRL.modify(.{ .ENABLE = .{ .value = .ENABLE } }); + defer peripherals.ROSC.CTRL.modify(.{ .ENABLE = .{ .value = rosc_state } }); + + var i: usize = 0; + while (i < buffer.len) : (i += 1) { + // We poll RANDOMBIT eight times per cycle to build a random byte + var r: u8 = @intCast(u8, peripherals.ROSC.RANDOMBIT.read().RANDOMBIT); + var j: usize = 0; + while (j < 7) : (j += 1) { + r = (r << 1) | @intCast(u8, peripherals.ROSC.RANDOMBIT.read().RANDOMBIT); + } + buffer[i] = r; + } + } +};