hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

commit d01e8a831282bc948ad5c02e25413f17289af02e
parent fc32da6afa066bdd290380373f01c0642bbdbbc5
Author: Thomas Bracht Laumann Jespersen <t@laumann.xyz>
Date:   Mon, 15 Mar 2021 13:17:02 +0100

crypto: Add sha1

This is basically a transcription of Go's generic SHA-1 implementation,
mirroring the implementation from crypto::sha256.

Diffstat:
Acrypto/sha1/+test.ha | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrypto/sha1/sha1.ha | 221+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgen-stdlib | 13+++++++++++++
Mstdlib.mk | 27+++++++++++++++++++++++++++
4 files changed, 333 insertions(+), 0 deletions(-)

diff --git a/crypto/sha1/+test.ha b/crypto/sha1/+test.ha @@ -0,0 +1,72 @@ +use fmt; +use hash; +use strings; +use strio; +use io; + +@test fn sha1() void = { + let sha = sha1(); + defer hash::finish(sha); + + const vectors = [ + ("", "da39a3ee5e6b4b0d3255bfef95601890afd80709"), + ("abc", "a9993e364706816aba3e25717850c26c9cd0d89d"), + ("hello world", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"), + ("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", "84983e441c3bd26ebaae4aa1f95129e5e54670f1"), + ("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", "a49b2446a02c645bf419f995b67091253a04a259"), + // From Pro Git Chapter 10.2 + // https://git-scm.com/book/en/v2/Git-Internals-Git-Objects#_object_storage + // output of: echo -n 'what is up, doc?' | git hash-object --stdin + ("blob 16\0what is up, doc?", "bd9dbf5aae1a3862dd1526723246b20206e5fc37"), + ("Hare is a cool language", "947feae3d0d65cc083c8f3e87858206e36aae908"), + ("'UNIX was not designed to stop its users from doing stupid things, as that would also stop them from doing clever things' - Doug Gwyn", "05c8dd2605161bdd0b5d70f1f225f4dd69a01e3b"), + ("'Life is too short to run proprietary software' - Bdale Garbee", "91ad4bdc2fbe2b731cbe8bf2958099391c7af3b8"), + ("'The central enemy of reliability is complexity.' - Geer et al", "4b6eb2aa55ef59cc59be6d181c64141e7c1e5eab"), + ]; + + for (let i = 0z; i < len(vectors); i += 1) { + const vector = vectors[i]; + hash::reset(sha); + hash::write(sha, strings::to_utf8(vector.0)); + let sum = hash::sum(sha); + defer free(sum); + + let hex = strio::dynamic(); + defer io::close(hex); + for (let j = 0z; j < SIZE; j += 1) { + fmt::fprintf(hex, "{:02x}", sum[j]); + }; + if (strio::string(hex) != vector.1) { + fmt::errorfln("Vector {}: {} != {}", + i, strio::string(hex), vector.1); + abort(); + }; + }; +}; + +// Uncomment this test to run the 1GB vector - takes a while to run, so it's +// left disabled. +//@test fn sha1_1gb() void = { +// const input = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno"; +// const expected = "7789f0c9ef7bfc40d93311143dfbe69e2017f592"; +// +// let sha = sha1(); +// defer hash::finish(sha); +// +// for (let i = 0z; i < 16777216; i += 1) +// hash::write(sha, strings::to_utf8(input)); +// +// let sum = hash::sum(sha); +// defer free(sum); +// +// let hex = strio::dynamic(); +// defer io::close(hex); +// for (let i = 0z; i < SIZE; i += 1) +// fmt::fprintf(hex, "{:02x}", sum[i]); +// +// if (strio::string(hex) != expected) { +// fmt::errorfln("1GB vector: {} != {}", +// strio::string(hex), expected); +// abort(); +// }; +//}; diff --git a/crypto/sha1/sha1.ha b/crypto/sha1/sha1.ha @@ -0,0 +1,221 @@ +use hash; +use io; +use crypto::math; +use endian; + +// The size, in bytes, of a SHA-1 digest. +export def SIZE: size = 20; + +export def BLOCKSIZE: size = 64; + +def chunk: size = 64; +def init0: u32 = 0x67452301; +def init1: u32 = 0xEFCDAB89; +def init2: u32 = 0x98BADCFE; +def init3: u32 = 0x10325476; +def init4: u32 = 0xC3D2E1F0; + +type digest = struct { + hash: hash::hash, + h: [5]u32, + x: [chunk]u8, + nx: size, + ln: size, +}; + +export fn sha1() *hash::hash = { + let sha = alloc(digest { + hash = hash::hash { + stream = io::stream { + writer = &write, + closer = &close, + ... + }, + sum = &sum, + reset = &reset, + sz = SIZE, + ... + }, + }); + let hash = &sha.hash; + hash::reset(hash); + return hash; +}; + +fn write(st: *io::stream, buf: const []u8) (size | io::error) = { + let h = st: *digest; + let b: []u8 = buf; + let nn = len(buf); + + h.ln += nn; + + if (h.nx > 0) { + // Compute how many bytes can be copied into h.x + let r = len(h.x) - h.nx; + let n = if (nn > r) r else nn; + h.x[h.nx..] = b[..n]; + h.nx += n; + if (h.nx == chunk) { + block(h, h.x[..]); + h.nx = 0; + }; + b = b[n..]; + }; + if (len(b) >= chunk) { + let n = len(b) & ~(chunk - 1); + block(h, b[..n]); + b = b[n..]; + }; + if (len(b) > 0) { + let n = len(b); + h.x[..n] = b[..]; + h.nx = n; + }; + return nn; +}; + +fn close(st: *io::stream) void = { + free(st); +}; + +fn reset(h: *hash::hash) void = { + let h = h: *digest; + h.h[0] = init0; + h.h[1] = init1; + h.h[2] = init2; + h.h[3] = init3; + h.h[4] = init4; + h.nx = 0; + h.ln = 0; +}; + +fn sum(h: *hash::hash) []u8 = { + let h = h: *digest; + let copy = *h; + let h = &copy; + + // Padding. Add a 1 bit and 0 bits until 56 bytes mod 64. + let ln = h.ln; + let tmp: [64]u8 = [0x80, 0...]; + const pad = if ((ln % 64z) < 56z) 56z - ln % 64z + else 64 + 56z - ln % 64z; + write(&h.hash.stream, tmp[..pad]); + + // Length in bits. + ln <<= 3; + endian::beputu64(tmp, ln: u64); + write(&h.hash.stream, tmp[..8]); + + assert(h.nx == 0); + + // Where we write the digest + let d: [SIZE]u8 = [0...]; + endian::beputu32(d[0..], h.h[0]); + endian::beputu32(d[4..], h.h[1]); + endian::beputu32(d[8..], h.h[2]); + endian::beputu32(d[12..], h.h[3]); + endian::beputu32(d[16..], h.h[4]); + + let slice: []u8 = alloc([], SIZE); + append(slice, ...d); + return slice; +}; + +let K0: u32 = 0x5A827999; +let K1: u32 = 0x6ED9EBA1; +let K2: u32 = 0x8F1BBCDC; +let K3: u32 = 0xCA62C1D6; + +// A generic, pure Hare version of the SHA-1 block step +fn block(h: *digest, p: []u8) void = { + let w: [16]u32 = [0...]; + + let h0 = h.h[0]; + let h1 = h.h[1]; + let h2 = h.h[2]; + let h3 = h.h[3]; + let h4 = h.h[4]; + + for (len(p) >= chunk) { + for (let i = 0z; i < 16; i += 1) { + let j = i * 4; + w[i] = p[j]: u32 << 24 + | p[j+1]: u32 << 16 + | p[j+2]: u32 << 8 + | p[j+3]: u32; + }; + let a = h0; + let b = h1; + let c = h2; + let d = h3; + let e = h4; + + // Each of the four 20-iteration rounds differs only in the + // computation of f and the choice of Ki for i=0..5 + let i = 0z; + for (i < 16; i += 1) { + let f = (b & c) | (~b & d); + let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K0; + // The order matters here! + e = d; d = c; c = math::rotl32(b, 30); b = a; a = t; + }; + for (i < 20; i += 1) { + let tmp = w[(i - 3) & 0xf] + ^ w[(i - 8) & 0xf] + ^ w[(i - 14) & 0xf] + ^ w[i & 0xf]; + w[i & 0xf] = tmp << 1 | tmp >> 31; + + let f = (b & c) | (~b & d); + let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K0; + e = d; d = c; c = math::rotl32(b, 30); b = a; a = t; + }; + for (i < 40; i += 1) { + let tmp = w[(i - 3) & 0xf] + ^ w[(i - 8) & 0xf] + ^ w[(i - 14) & 0xf] + ^ w[i & 0xf]; + w[i & 0xf] = tmp << 1 | tmp >> 31; + + let f = b ^ c ^ d; + let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K1; + e = d; d = c; c = math::rotl32(b, 30); b = a; a = t; + }; + for (i < 60; i += 1) { + let tmp = w[(i - 3) & 0xf] + ^ w[(i - 8) & 0xf] + ^ w[(i - 14) & 0xf] + ^ w[i & 0xf]; + w[i & 0xf] = tmp << 1 | tmp >> 31; + + let f = ((b | c) & d) | (b & c); + let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K2; + e = d; d = c; c = math::rotl32(b, 30); b = a; a = t; + }; + for (i < 80; i += 1) { + let tmp = w[(i - 3) & 0xf] + ^ w[(i - 8) & 0xf] + ^ w[(i - 14) & 0xf] + ^ w[i & 0xf]; + w[i & 0xf] = tmp << 1 | tmp >> 31; + + let f = b ^ c ^ d; + let t = math::rotl32(a, 5) + f + e + w[i & 0xf] + K3; + e = d; d = c; c = math::rotl32(b, 30); b = a; a = t; + }; + + h0 += a; + h1 += b; + h2 += c; + h3 += d; + h4 += e; + + p = p[chunk..]; + }; + + h.h[0] = h0; + h.h[1] = h1; + h.h[2] = h2; + h.h[3] = h3; + h.h[4] = h4; +}; diff --git a/gen-stdlib b/gen-stdlib @@ -164,6 +164,18 @@ crypto_sha256() { fi } +crypto_sha1() { + printf '# crypto::sha1\n' + if [ $testing -eq 0 ] + then + gen_srcs crypto::sha1 sha1.ha + gen_ssa crypto::sha1 hash io endian + else + gen_srcs crypto::sha1 sha1.ha +test.ha + gen_ssa crypto::sha1 hash io endian fmt strio strings + fi +} + dirs() { printf '# dirs\n' gen_srcs dirs \ @@ -478,6 +490,7 @@ bufio bytes crypto_math crypto_random +crypto_sha1 crypto_sha256 dirs encoding_hex diff --git a/stdlib.mk b/stdlib.mk @@ -83,6 +83,9 @@ hare_stdlib_deps+=$(stdlib_crypto_math) stdlib_crypto_random=$(HARECACHE)/crypto/random/crypto_random.o hare_stdlib_deps+=$(stdlib_crypto_random) +stdlib_crypto_sha1=$(HARECACHE)/crypto/sha1/crypto_sha1.o +hare_stdlib_deps+=$(stdlib_crypto_sha1) + stdlib_crypto_sha256=$(HARECACHE)/crypto/sha256/crypto_sha256.o hare_stdlib_deps+=$(stdlib_crypto_sha256) @@ -226,6 +229,16 @@ $(HARECACHE)/crypto/random/crypto_random.ssa: $(stdlib_crypto_random_srcs) $(std @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::random \ -t$(HARECACHE)/crypto/random/crypto_random.td $(stdlib_crypto_random_srcs) +# crypto::sha1 +stdlib_crypto_sha1_srcs= \ + $(STDLIB)/crypto/sha1/sha1.ha + +$(HARECACHE)/crypto/sha1/crypto_sha1.ssa: $(stdlib_crypto_sha1_srcs) $(stdlib_rt) $(stdlib_hash) $(stdlib_io) $(stdlib_endian) + @printf 'HAREC \t$@\n' + @mkdir -p $(HARECACHE)/crypto/sha1 + @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Ncrypto::sha1 \ + -t$(HARECACHE)/crypto/sha1/crypto_sha1.td $(stdlib_crypto_sha1_srcs) + # crypto::sha256 stdlib_crypto_sha256_srcs= \ $(STDLIB)/crypto/sha256/sha256.ha @@ -648,6 +661,9 @@ hare_testlib_deps+=$(testlib_crypto_math) testlib_crypto_random=$(TESTCACHE)/crypto/random/crypto_random.o hare_testlib_deps+=$(testlib_crypto_random) +testlib_crypto_sha1=$(TESTCACHE)/crypto/sha1/crypto_sha1.o +hare_testlib_deps+=$(testlib_crypto_sha1) + testlib_crypto_sha256=$(TESTCACHE)/crypto/sha256/crypto_sha256.o hare_testlib_deps+=$(testlib_crypto_sha256) @@ -791,6 +807,17 @@ $(TESTCACHE)/crypto/random/crypto_random.ssa: $(testlib_crypto_random_srcs) $(te @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::random \ -t$(TESTCACHE)/crypto/random/crypto_random.td $(testlib_crypto_random_srcs) +# crypto::sha1 +testlib_crypto_sha1_srcs= \ + $(STDLIB)/crypto/sha1/sha1.ha \ + $(STDLIB)/crypto/sha1/+test.ha + +$(TESTCACHE)/crypto/sha1/crypto_sha1.ssa: $(testlib_crypto_sha1_srcs) $(testlib_rt) $(testlib_hash) $(testlib_io) $(testlib_endian) $(testlib_fmt) $(testlib_strio) $(testlib_strings) + @printf 'HAREC \t$@\n' + @mkdir -p $(TESTCACHE)/crypto/sha1 + @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Ncrypto::sha1 \ + -t$(TESTCACHE)/crypto/sha1/crypto_sha1.td $(testlib_crypto_sha1_srcs) + # crypto::sha256 testlib_crypto_sha256_srcs= \ $(STDLIB)/crypto/sha256/sha256.ha \