private static uint chacha_next32([NativeTypeName("mi_random_ctx_t*")] ref mi_random_ctx_t ctx) { if (ctx.output_available <= 0) { chacha_block(ref ctx); ctx.output_available = 16; // (assign again to suppress static analysis warning) } uint x = ctx.output[16 - ctx.output_available]; ctx.output[16 - ctx.output_available] = 0; // reset once the data is handed out ctx.output_available--; return(x); }
private static void chacha_block([NativeTypeName("mi_random_ctx_t*")] ref mi_random_ctx_t ctx) { // scramble into `x` uint *x = stackalloc uint[16]; for (nuint i = 0; i < 16; i++) { x[i] = ctx.input[i]; } for (nuint i = 0; i < MI_CHACHA_ROUNDS; i += 2) { qround(x, 0, 4, 8, 12); qround(x, 1, 5, 9, 13); qround(x, 2, 6, 10, 14); qround(x, 3, 7, 11, 15); qround(x, 0, 5, 10, 15); qround(x, 1, 6, 11, 12); qround(x, 2, 7, 8, 13); qround(x, 3, 4, 9, 14); } // add scrambled data to the initial state for (nuint i = 0; i < 16; i++) { ctx.output[i] = unchecked (x[i] + ctx.input[i]); } ctx.output_available = 16; // increment the counter for the next round ctx.input[12] += 1; if (ctx.input[12] == 0) { ctx.input[13] += 1; if (ctx.input[13] == 0) { // and keep increasing into the nonce ctx.input[14] += 1; } } }
private static void chacha_init([NativeTypeName("mi_random_ctx_t*")] out mi_random_ctx_t ctx, [NativeTypeName("const uint8_t*")] ReadOnlySpan <byte> key, [NativeTypeName("uint64_t")] ulong nonce) { // since we only use chacha for randomness (and not encryption) we // do not _need_ to read 32-bit values as little endian but we do anyways // just for being compatible :-) ctx = default; for (nuint i = 0; i < 4; i++) { ctx.input[i] = read32(sigma, i); } for (nuint i = 0; i < 8; i++) { ctx.input[i + 4] = read32(key, i); } ctx.input[12] = 0; ctx.input[13] = 0; ctx.input[14] = unchecked ((uint)nonce); ctx.input[15] = (uint)(nonce >> 32); }
// random.c private static partial void _mi_random_init([NativeTypeName("mi_random_ctx_t*")] out mi_random_ctx_t ctx);