From 09882358129ac88cecdea8808a47c3d3d7903d02 Mon Sep 17 00:00:00 2001 From: Meghan Denny Date: Mon, 2 Mar 2026 13:41:50 -0800 Subject: [PATCH] add decodeBase32 --- test.zig | 8 ++++++++ totp.zig | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/test.zig b/test.zig index b1bc6394085a6c3e00f9c3ac28c6d38ae83b227c..4aa8362ad1b227909745a358a6f9009cb1f5c568 100644 --- a/test.zig +++ b/test.zig @@ -59,3 +59,11 @@ test { defer allocator.free(url); try expect(url).toEqualString("otpauth://totp/otpauth%20demo:username@example.org?secret=AAAABBBB22223333YYYYZZZZ66667777&algorithm=SHA1&digits=6&period=30&issuer=otpauth%20demo"); } + +test { + const b32 = "SC5IH4S3HJLEWOGXJM4BL6CH5BDSYHBV"; + const hex_expected = "90ba83f25b3a564b38d74b3815f847e8472c1c35"; + const raw_actual = try totp.decodeBase32(b32, std.crypto.hash.Sha1); + const hex_actual = extras.to_hex(raw_actual); + try expect(&hex_actual).toEqualString(hex_expected); +} diff --git a/totp.zig b/totp.zig index 2396a2659c300e384b93159fc87fb541c6033911..2739d35ea82c3a60ca69bd6dffb7961076882fed 100644 --- a/totp.zig +++ b/totp.zig @@ -123,3 +123,25 @@ const BufBiterator = struct { return @intFromBool(result); } }; + +pub fn decodeBase32(input: []const u8, Hash: type) ![Hash.digest_length]u8 { + var result: std.bit_set.ArrayBitSet(u8, Hash.digest_length * 8) = .initEmpty(); + const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + for (input, 0..) |c, i| { + const x = std.mem.indexOfScalar(u8, alphabet, c) orelse return error.InvalidCharacter; + const n: u5 = @intCast(x); + inline for (0..5) |j| blk: { + if (i * 5 > result.capacity()) return error.Overflow; + const d = i * 5 + j; + if (d >= result.capacity()) break :blk; + result.setValue(d, n & (1 << 5 - 1 - (j)) != 0); + } + } + return bitReverse(result.masks); +} + +fn bitReverse(array: anytype) [array.len]std.meta.Child(@TypeOf(array)) { + var result: [array.len]std.meta.Child(@TypeOf(array)) = @splat(0); + for (&array, &result) |x, *y| y.* = @bitReverse(x); + return result; +} -- 2.54.0