Add Md5 and Sha1 hash functions

Some performance comparisons to C.

We take the fastest time measurement taken across multiple runs.

The block hashing functions use the same md5/sha1 methods.

```
Cpu: Intel(R) Core(TM) i5-6500 CPU @ 3.20GHz
Gcc: 7.2.1 20171224
Clang: 5.0.1
Zig: 0.1.1.304f6f1d
```

See https://www.nayuki.io/page/fast-md5-hash-implementation-in-x86-assembly:

```
gcc -O2
    661 Mb/s
clang -O2
    490 Mb/s
zig --release-fast and zig --release-safe
    570 Mb/s
zig
    50 Mb/s
```

See https://www.nayuki.io/page/fast-sha1-hash-implementation-in-x86-assembly:

```
gcc -O2
    588 Mb/s
clang -O2
    563 Mb/s
zig --release-fast and zig --release-safe
    610 Mb/s
zig
    21 Mb/s
```

In short, zig provides pretty useful tools for writing this sort of
code. We are in the lead against clang (which uses the same LLVM
backend) with us being slower only against md5 with GCC.
master
Marc Tiehuis 2018-01-13 14:40:21 +13:00
parent 304f6f1d01
commit 51fdbf7f8c
5 changed files with 544 additions and 0 deletions

View File

@ -364,6 +364,9 @@ set(ZIG_STD_FILES
"c/index.zig"
"c/linux.zig"
"c/windows.zig"
"crypto/index.zig"
"crypto/md5.zig"
"crypto/sha1.zig"
"cstr.zig"
"debug/failing_allocator.zig"
"debug/index.zig"

7
std/crypto/index.zig Normal file
View File

@ -0,0 +1,7 @@
pub const Sha1 = @import("md5.zig").Sha1;
pub const Md5 = @import("sha1.zig").Md5;
test "crypto" {
_ = @import("md5.zig");
_ = @import("sha1.zig");
}

252
std/crypto/md5.zig Normal file
View File

@ -0,0 +1,252 @@
const mem = @import("../mem.zig");
const math = @import("../math/index.zig");
const endian = @import("../endian.zig");
const debug = @import("../debug/index.zig");
const RoundParam = struct {
a: u32, b: u32, c: u32, d: u32,
k: u32, s: u32, t: u32
};
fn Rp(a: u32, b: u32, c: u32, d: u32, k: u32, s: u5, t: u32) -> RoundParam {
return RoundParam { .a = a, .b = b, .c = c, .d = d, .k = k, .s = s, .t = t };
}
/// const hash1 = Md5.hash("my input");
///
/// const hasher = Md5.init();
/// hasher.update("my ");
/// hasher.update("input");
/// const hash2 = hasher.final();
pub const Md5 = struct {
const Self = this;
s: [4]u32,
// Streaming Cache
buf: [64]u8,
buf_len: u8,
total_len: u64,
pub fn init() -> Self {
var d: Self = undefined;
d.reset();
return d;
}
pub fn reset(d: &Self) {
d.s[0] = 0x67452301;
d.s[1] = 0xEFCDAB89;
d.s[2] = 0x98BADCFE;
d.s[3] = 0x10325476;
d.buf_len = 0;
d.total_len = 0;
}
pub fn hash(b: []const u8) -> u128 {
var d = Md5.init();
d.update(b);
return d.final();
}
pub fn update(d: &Self, b: []const u8) {
var off: usize = 0;
// Partial buffer exists from previous update. Copy into buffer then hash.
if (d.buf_len != 0 and d.buf_len + b.len > 64) {
off += 64 - d.buf_len;
mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
d.round(d.buf[0..]);
d.buf_len = 0;
}
// Full middle blocks.
while (off + 64 < b.len) : (off += 64) {
d.round(b[off..off + 64]);
}
// Copy any remainder for next pass.
mem.copy(u8, d.buf[d.buf_len..], b[off..]);
d.buf_len += u8(b[off..].len);
d.total_len += b.len;
}
pub fn final(d: &Self) -> u128 {
// The buffer here will never be completely full.
mem.set(u8, d.buf[d.buf_len..], 0);
// Append padding bits.
d.buf[d.buf_len] = 0x80;
d.buf_len += 1;
// > 448 mod 512 so need to add an extra round to wrap around.
if (64 - d.buf_len < 8) {
d.round(d.buf[0..]);
mem.set(u8, d.buf[0..], 0);
}
// Append message length.
var i: usize = 1;
var len = d.total_len >> 5;
d.buf[56] = u8(d.total_len & 0x1f) << 3;
while (i < 8) : (i += 1) {
d.buf[56 + i] = u8(len & 0xff);
len >>= 8;
}
d.round(d.buf[0..]);
const r =
(u128(d.s[3]) << 96) |
(u128(d.s[2]) << 64) |
(u128(d.s[1]) << 32) |
(u128(d.s[0]) << 0);
return endian.swapIfLe(u128, r);
}
fn round(d: &Self, b: []const u8) {
debug.assert(b.len == 64);
var s: [16]u32 = undefined;
// ERROR: cannot unroll this at comptime
var i: usize = 0;
while (i < 16) : (i += 1) {
// NOTE: Performing or's separately improves perf by ~10%
s[i] = 0;
s[i] |= u32(b[i*4+0]);
s[i] |= u32(b[i*4+1]) << 8;
s[i] |= u32(b[i*4+2]) << 16;
s[i] |= u32(b[i*4+3]) << 24;
}
var v: [4]u32 = []u32 {
d.s[0], d.s[1], d.s[2], d.s[3],
};
const round0 = comptime []RoundParam {
Rp(0, 1, 2, 3, 0, 7, 0xD76AA478),
Rp(3, 0, 1, 2, 1, 12, 0xE8C7B756),
Rp(2, 3, 0, 1, 2, 17, 0x242070DB),
Rp(1, 2, 3, 0, 3, 22, 0xC1BDCEEE),
Rp(0, 1, 2, 3, 4, 7, 0xF57C0FAF),
Rp(3, 0, 1, 2, 5, 12, 0x4787C62A),
Rp(2, 3, 0, 1, 6, 17, 0xA8304613),
Rp(1, 2, 3, 0, 7, 22, 0xFD469501),
Rp(0, 1, 2, 3, 8, 7, 0x698098D8),
Rp(3, 0, 1, 2, 9, 12, 0x8B44F7AF),
Rp(2, 3, 0, 1, 10, 17, 0xFFFF5BB1),
Rp(1, 2, 3, 0, 11, 22, 0x895CD7BE),
Rp(0, 1, 2, 3, 12, 7, 0x6B901122),
Rp(3, 0, 1, 2, 13, 12, 0xFD987193),
Rp(2, 3, 0, 1, 14, 17, 0xA679438E),
Rp(1, 2, 3, 0, 15, 22, 0x49B40821),
};
inline for (round0) |r| {
v[r.a] = v[r.a] +% (v[r.d] ^ (v[r.b] & (v[r.c] ^ v[r.d]))) +% r.t +% s[r.k];
v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
}
const round1 = comptime []RoundParam {
Rp(0, 1, 2, 3, 1, 5, 0xF61E2562),
Rp(3, 0, 1, 2, 6, 9, 0xC040B340),
Rp(2, 3, 0, 1, 11, 14, 0x265E5A51),
Rp(1, 2, 3, 0, 0, 20, 0xE9B6C7AA),
Rp(0, 1, 2, 3, 5, 5, 0xD62F105D),
Rp(3, 0, 1, 2, 10, 9, 0x02441453),
Rp(2, 3, 0, 1, 15, 14, 0xD8A1E681),
Rp(1, 2, 3, 0, 4, 20, 0xE7D3FBC8),
Rp(0, 1, 2, 3, 9, 5, 0x21E1CDE6),
Rp(3, 0, 1, 2, 14, 9, 0xC33707D6),
Rp(2, 3, 0, 1, 3, 14, 0xF4D50D87),
Rp(1, 2, 3, 0, 8, 20, 0x455A14ED),
Rp(0, 1, 2, 3, 13, 5, 0xA9E3E905),
Rp(3, 0, 1, 2, 2, 9, 0xFCEFA3F8),
Rp(2, 3, 0, 1, 7, 14, 0x676F02D9),
Rp(1, 2, 3, 0, 12, 20, 0x8D2A4C8A),
};
inline for (round1) |r| {
v[r.a] = v[r.a] +% (v[r.c] ^ (v[r.d] & (v[r.b] ^ v[r.c]))) +% r.t +% s[r.k];
v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
}
const round2 = comptime []RoundParam {
Rp(0, 1, 2, 3, 5, 4, 0xFFFA3942),
Rp(3, 0, 1, 2, 8, 11, 0x8771F681),
Rp(2, 3, 0, 1, 11, 16, 0x6D9D6122),
Rp(1, 2, 3, 0, 14, 23, 0xFDE5380C),
Rp(0, 1, 2, 3, 1, 4, 0xA4BEEA44),
Rp(3, 0, 1, 2, 4, 11, 0x4BDECFA9),
Rp(2, 3, 0, 1, 7, 16, 0xF6BB4B60),
Rp(1, 2, 3, 0, 10, 23, 0xBEBFBC70),
Rp(0, 1, 2, 3, 13, 4, 0x289B7EC6),
Rp(3, 0, 1, 2, 0, 11, 0xEAA127FA),
Rp(2, 3, 0, 1, 3, 16, 0xD4EF3085),
Rp(1, 2, 3, 0, 6, 23, 0x04881D05),
Rp(0, 1, 2, 3, 9, 4, 0xD9D4D039),
Rp(3, 0, 1, 2, 12, 11, 0xE6DB99E5),
Rp(2, 3, 0, 1, 15, 16, 0x1FA27CF8),
Rp(1, 2, 3, 0, 2, 23, 0xC4AC5665),
};
inline for (round2) |r| {
v[r.a] = v[r.a] +% (v[r.b] ^ v[r.c] ^ v[r.d]) +% r.t +% s[r.k];
v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
}
const round3 = comptime []RoundParam {
Rp(0, 1, 2, 3, 0, 6, 0xF4292244),
Rp(3, 0, 1, 2, 7, 10, 0x432AFF97),
Rp(2, 3, 0, 1, 14, 15, 0xAB9423A7),
Rp(1, 2, 3, 0, 5, 21, 0xFC93A039),
Rp(0, 1, 2, 3, 12, 6, 0x655B59C3),
Rp(3, 0, 1, 2, 3, 10, 0x8F0CCC92),
Rp(2, 3, 0, 1, 10, 15, 0xFFEFF47D),
Rp(1, 2, 3, 0, 1, 21, 0x85845DD1),
Rp(0, 1, 2, 3, 8, 6, 0x6FA87E4F),
Rp(3, 0, 1, 2, 15, 10, 0xFE2CE6E0),
Rp(2, 3, 0, 1, 6, 15, 0xA3014314),
Rp(1, 2, 3, 0, 13, 21, 0x4E0811A1),
Rp(0, 1, 2, 3, 4, 6, 0xF7537E82),
Rp(3, 0, 1, 2, 11, 10, 0xBD3AF235),
Rp(2, 3, 0, 1, 2, 15, 0x2AD7D2BB),
Rp(1, 2, 3, 0, 9, 21, 0xEB86D391),
};
inline for (round3) |r| {
v[r.a] = v[r.a] +% (v[r.c] ^ (v[r.b] | ~v[r.d])) +% r.t +% s[r.k];
v[r.a] = v[r.b] +% math.rotl(u32, v[r.a], r.s);
}
d.s[0] +%= v[0];
d.s[1] +%= v[1];
d.s[2] +%= v[2];
d.s[3] +%= v[3];
}
};
test "md5 single" {
debug.assert(0xd41d8cd98f00b204e9800998ecf8427e == Md5.hash(""));
debug.assert(0x0cc175b9c0f1b6a831c399e269772661 == Md5.hash("a"));
debug.assert(0x900150983cd24fb0d6963f7d28e17f72 == Md5.hash("abc"));
debug.assert(0xf96b697d7cb7938d525a2f31aaf161d0 == Md5.hash("message digest"));
debug.assert(0xc3fcd3d76192e4007dfb496cca67e13b == Md5.hash("abcdefghijklmnopqrstuvwxyz"));
debug.assert(0xd174ab98d277d9f5a5611c2c9f419d9f == Md5.hash("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"));
debug.assert(0x57edf4a22be3c955ac49da2e2107b67a == Md5.hash("12345678901234567890123456789012345678901234567890123456789012345678901234567890"));
}
test "md5 streaming" {
var h = Md5.init();
debug.assert(0xd41d8cd98f00b204e9800998ecf8427e == h.final());
h.reset();
h.update("abc");
debug.assert(0x900150983cd24fb0d6963f7d28e17f72 == h.final());
h.reset();
h.update("a");
h.update("b");
h.update("c");
debug.assert(0x900150983cd24fb0d6963f7d28e17f72 == h.final());
}

280
std/crypto/sha1.zig Normal file
View File

@ -0,0 +1,280 @@
const mem = @import("../mem.zig");
const math = @import("../math/index.zig");
const endian = @import("../endian.zig");
const debug = @import("../debug/index.zig");
pub const u160 = @IntType(false, 160);
const RoundParam = struct {
a: u32, b: u32, c: u32, d: u32, e: u32, i: u32,
};
fn Rp(a: u32, b: u32, c: u32, d: u32, e: u32, i: u32) -> RoundParam {
return RoundParam { .a = a, .b = b, .c = c, .d = d, .e = e, .i = i };
}
pub const Sha1 = struct {
const Self = this;
s: [5]u32,
// Streaming Cache
buf: [64]u8,
buf_len: u8,
total_len: u64,
pub fn init() -> Self {
var d: Self = undefined;
d.reset();
return d;
}
pub fn reset(d: &Self) {
d.s[0] = 0x67452301;
d.s[1] = 0xEFCDAB89;
d.s[2] = 0x98BADCFE;
d.s[3] = 0x10325476;
d.s[4] = 0xC3D2E1F0;
d.buf_len = 0;
d.total_len = 0;
}
pub fn hash(b: []const u8) -> u160 {
var d = Sha1.init();
d.update(b);
return d.final();
}
pub fn update(d: &Self, b: []const u8) {
var off: usize = 0;
// Partial buffer exists from previous update. Copy into buffer then hash.
if (d.buf_len != 0 and d.buf_len + b.len > 64) {
off += 64 - d.buf_len;
mem.copy(u8, d.buf[d.buf_len..], b[0..off]);
d.round(d.buf[0..]);
d.buf_len = 0;
}
// Full middle blocks.
while (off + 64 < b.len) : (off += 64) {
d.round(b[off..off + 64]);
}
// Copy any remainder for next pass.
mem.copy(u8, d.buf[d.buf_len..], b[off..]);
d.buf_len += u8(b[off..].len);
d.total_len += b.len;
}
pub fn final(d: &Self) -> u160 {
// The buffer here will never be completely full.
mem.set(u8, d.buf[d.buf_len..], 0);
// Append padding bits.
d.buf[d.buf_len] = 0x80;
d.buf_len += 1;
// > 448 mod 512 so need to add an extra round to wrap around.
if (64 - d.buf_len < 8) {
d.round(d.buf[0..]);
mem.set(u8, d.buf[0..], 0);
}
// Append message length.
var i: usize = 1;
var len = d.total_len >> 5;
d.buf[63] = u8(d.total_len & 0x1f) << 3;
while (i < 8) : (i += 1) {
d.buf[63 - i] = u8(len & 0xff);
len >>= 8;
}
d.round(d.buf[0..]);
const r =
(u160(d.s[0]) << 128) |
(u160(d.s[1]) << 96) |
(u160(d.s[2]) << 64) |
(u160(d.s[3]) << 32) |
(u160(d.s[4]) << 0);
return endian.swapIfBe(u160, r);
}
fn round(d: &Self, b: []const u8) {
debug.assert(b.len == 64);
var s: [16]u32 = undefined;
var v: [5]u32 = []u32 {
d.s[0], d.s[1], d.s[2], d.s[3], d.s[4],
};
const round0a = comptime []RoundParam {
Rp(0, 1, 2, 3, 4, 0),
Rp(4, 0, 1, 2, 3, 1),
Rp(3, 4, 0, 1, 2, 2),
Rp(2, 3, 4, 0, 1, 3),
Rp(1, 2, 3, 4, 0, 4),
Rp(0, 1, 2, 3, 4, 5),
Rp(4, 0, 1, 2, 3, 6),
Rp(3, 4, 0, 1, 2, 7),
Rp(2, 3, 4, 0, 1, 8),
Rp(1, 2, 3, 4, 0, 9),
Rp(0, 1, 2, 3, 4, 10),
Rp(4, 0, 1, 2, 3, 11),
Rp(3, 4, 0, 1, 2, 12),
Rp(2, 3, 4, 0, 1, 13),
Rp(1, 2, 3, 4, 0, 14),
Rp(0, 1, 2, 3, 4, 15),
};
inline for (round0a) |r| {
s[r.i] = (u32(b[r.i * 4 + 0]) << 24) |
(u32(b[r.i * 4 + 1]) << 16) |
(u32(b[r.i * 4 + 2]) << 8) |
(u32(b[r.i * 4 + 3]) << 0);
v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0x5A827999 +% s[r.i & 0xf]
+% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
v[r.b] = math.rotl(u32, v[r.b], u32(30));
}
const round0b = comptime []RoundParam {
Rp(4, 0, 1, 2, 3, 16),
Rp(3, 4, 0, 1, 2, 17),
Rp(2, 3, 4, 0, 1, 18),
Rp(1, 2, 3, 4, 0, 19),
};
inline for (round0b) |r| {
const t = s[(r.i-3) & 0xf] ^ s[(r.i-8) & 0xf] ^ s[(r.i-14) & 0xf] ^ s[(r.i-16) & 0xf];
s[r.i & 0xf] = math.rotl(u32, t, u32(1));
v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0x5A827999 +% s[r.i & 0xf]
+% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
v[r.b] = math.rotl(u32, v[r.b], u32(30));
}
const round1 = comptime []RoundParam {
Rp(0, 1, 2, 3, 4, 20),
Rp(4, 0, 1, 2, 3, 21),
Rp(3, 4, 0, 1, 2, 22),
Rp(2, 3, 4, 0, 1, 23),
Rp(1, 2, 3, 4, 0, 24),
Rp(0, 1, 2, 3, 4, 25),
Rp(4, 0, 1, 2, 3, 26),
Rp(3, 4, 0, 1, 2, 27),
Rp(2, 3, 4, 0, 1, 28),
Rp(1, 2, 3, 4, 0, 29),
Rp(0, 1, 2, 3, 4, 30),
Rp(4, 0, 1, 2, 3, 31),
Rp(3, 4, 0, 1, 2, 32),
Rp(2, 3, 4, 0, 1, 33),
Rp(1, 2, 3, 4, 0, 34),
Rp(0, 1, 2, 3, 4, 35),
Rp(4, 0, 1, 2, 3, 36),
Rp(3, 4, 0, 1, 2, 37),
Rp(2, 3, 4, 0, 1, 38),
Rp(1, 2, 3, 4, 0, 39),
};
inline for (round1) |r| {
const t = s[(r.i-3) & 0xf] ^ s[(r.i-8) & 0xf] ^ s[(r.i-14) & 0xf] ^ s[(r.i-16) & 0xf];
s[r.i & 0xf] = math.rotl(u32, t, u32(1));
v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0x6ED9EBA1 +% s[r.i & 0xf]
+% (v[r.b] ^ v[r.c] ^ v[r.d]);
v[r.b] = math.rotl(u32, v[r.b], u32(30));
}
const round2 = comptime []RoundParam {
Rp(0, 1, 2, 3, 4, 40),
Rp(4, 0, 1, 2, 3, 41),
Rp(3, 4, 0, 1, 2, 42),
Rp(2, 3, 4, 0, 1, 43),
Rp(1, 2, 3, 4, 0, 44),
Rp(0, 1, 2, 3, 4, 45),
Rp(4, 0, 1, 2, 3, 46),
Rp(3, 4, 0, 1, 2, 47),
Rp(2, 3, 4, 0, 1, 48),
Rp(1, 2, 3, 4, 0, 49),
Rp(0, 1, 2, 3, 4, 50),
Rp(4, 0, 1, 2, 3, 51),
Rp(3, 4, 0, 1, 2, 52),
Rp(2, 3, 4, 0, 1, 53),
Rp(1, 2, 3, 4, 0, 54),
Rp(0, 1, 2, 3, 4, 55),
Rp(4, 0, 1, 2, 3, 56),
Rp(3, 4, 0, 1, 2, 57),
Rp(2, 3, 4, 0, 1, 58),
Rp(1, 2, 3, 4, 0, 59),
};
inline for (round2) |r| {
const t = s[(r.i-3) & 0xf] ^ s[(r.i-8) & 0xf] ^ s[(r.i-14) & 0xf] ^ s[(r.i-16) & 0xf];
s[r.i & 0xf] = math.rotl(u32, t, u32(1));
v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0x8F1BBCDC +% s[r.i & 0xf]
+% ((v[r.b] & v[r.c]) ^ (v[r.b] & v[r.d]) ^ (v[r.c] & v[r.d]));
v[r.b] = math.rotl(u32, v[r.b], u32(30));
}
const round3 = comptime []RoundParam {
Rp(0, 1, 2, 3, 4, 60),
Rp(4, 0, 1, 2, 3, 61),
Rp(3, 4, 0, 1, 2, 62),
Rp(2, 3, 4, 0, 1, 63),
Rp(1, 2, 3, 4, 0, 64),
Rp(0, 1, 2, 3, 4, 65),
Rp(4, 0, 1, 2, 3, 66),
Rp(3, 4, 0, 1, 2, 67),
Rp(2, 3, 4, 0, 1, 68),
Rp(1, 2, 3, 4, 0, 69),
Rp(0, 1, 2, 3, 4, 70),
Rp(4, 0, 1, 2, 3, 71),
Rp(3, 4, 0, 1, 2, 72),
Rp(2, 3, 4, 0, 1, 73),
Rp(1, 2, 3, 4, 0, 74),
Rp(0, 1, 2, 3, 4, 75),
Rp(4, 0, 1, 2, 3, 76),
Rp(3, 4, 0, 1, 2, 77),
Rp(2, 3, 4, 0, 1, 78),
Rp(1, 2, 3, 4, 0, 79),
};
inline for (round3) |r| {
const t = s[(r.i-3) & 0xf] ^ s[(r.i-8) & 0xf] ^ s[(r.i-14) & 0xf] ^ s[(r.i-16) & 0xf];
s[r.i & 0xf] = math.rotl(u32, t, u32(1));
v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], u32(5)) +% 0xCA62C1D6 +% s[r.i & 0xf]
+% (v[r.b] ^ v[r.c] ^ v[r.d]);
v[r.b] = math.rotl(u32, v[r.b], u32(30));
}
d.s[0] +%= v[0];
d.s[1] +%= v[1];
d.s[2] +%= v[2];
d.s[3] +%= v[3];
d.s[4] +%= v[4];
}
};
test "sha1 single" {
debug.assert(0xda39a3ee5e6b4b0d3255bfef95601890afd80709 == Sha1.hash(""));
debug.assert(0xa9993e364706816aba3e25717850c26c9cd0d89d == Sha1.hash("abc"));
debug.assert(0xa49b2446a02c645bf419f995b67091253a04a259 == Sha1.hash("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"));
}
test "sha1 streaming" {
var h = Sha1.init();
debug.assert(0xda39a3ee5e6b4b0d3255bfef95601890afd80709 == h.final());
h.reset();
h.update("abc");
debug.assert(0xa9993e364706816aba3e25717850c26c9cd0d89d == h.final());
h.reset();
h.update("a");
h.update("b");
h.update("c");
debug.assert(0xa9993e364706816aba3e25717850c26c9cd0d89d == h.final());
}

View File

@ -10,6 +10,7 @@ pub const LinkedList = @import("linked_list.zig").LinkedList;
pub const base64 = @import("base64.zig");
pub const build = @import("build.zig");
pub const c = @import("c/index.zig");
pub const crypto = @import("crypto/index.zig");
pub const cstr = @import("cstr.zig");
pub const debug = @import("debug/index.zig");
pub const dwarf = @import("dwarf.zig");
@ -39,6 +40,7 @@ test "std" {
_ = @import("base64.zig");
_ = @import("build.zig");
_ = @import("c/index.zig");
_ = @import("crypto/index.zig");
_ = @import("cstr.zig");
_ = @import("debug/index.zig");
_ = @import("dwarf.zig");