1
0
Fork 0
forked from fun/fun

Added SHA-1 class to Fun's standard libs. (0.16.1)

This commit is contained in:
Johannes Findeisen 2025-10-01 02:06:26 +02:00
commit 71682293fe
5 changed files with 331 additions and 202 deletions

View file

@ -1,187 +0,0 @@
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-09-29
*/
// lib/crypt/crc32.fun
// Pure Fun CRC32 (IEEE 802.3, polynomial 0xEDB88320, reflected) operating on hex-string input.
//
// Public API (class):
// crc = CRC32()
// crc.crc32_hex(hexStr) -> digest hex string (lowercase, 8 hex chars)
//
// Example:
// print(CRC32().crc32_hex("616263")) // "abc" => 352441c2
class CRC32()
// hex helpers
fun hex_val(this, ch)
if (ch == "0")
return 0
else if (ch == "1")
return 1
else if (ch == "2")
return 2
else if (ch == "3")
return 3
else if (ch == "4")
return 4
else if (ch == "5")
return 5
else if (ch == "6")
return 6
else if (ch == "7")
return 7
else if (ch == "8")
return 8
else if (ch == "9")
return 9
else if (ch == "a" || ch == "A")
return 10
else if (ch == "b" || ch == "B")
return 11
else if (ch == "c" || ch == "C")
return 12
else if (ch == "d" || ch == "D")
return 13
else if (ch == "e" || ch == "E")
return 14
else if (ch == "f" || ch == "F")
return 15
else
return 0
fun byte_from_hex_pair(this, hh)
hi = this.hex_val(substr(hh, 0, 1))
lo = this.hex_val(substr(hh, 1, 1))
return hi * 16 + lo
fun from_hex(this, hex)
// ensure we operate on a string
hex = to_string(hex)
arr = []
number i = 0
while true
ch1 = substr(hex, i, 1)
if (typeof(ch1) != "String")
break
if (ch1 == "")
break
ch2 = substr(hex, i + 1, 1)
if (typeof(ch2) != "String")
break
if (ch2 == "")
break
pair = join([ch1, ch2], "")
number b = this.byte_from_hex_pair(pair)
push(arr, b)
i = i + 2
return arr
fun two_hex(this, n)
n = n % 256
hexd = "0123456789abcdef"
number hi = n / 16
number lo = n % 16
ch1 = substr(hexd, hi, 1)
ch2 = substr(hexd, lo, 1)
return join([ch1, ch2], "")
fun u32(this, x)
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
// Core per-byte CRC (bit-by-bit, reflected), polynomial 0xEDB88320
fun crc32_bytes(this, bytes)
crc = 4294967295 // 0xFFFFFFFF initial
poly = 3988292384 // 0xEDB88320
number i = 0
L = len(bytes)
while i < L
// crc ^= byte
crc = bxor(crc, bytes[i])
// 8 times
j = 0
while j < 8
if (band(crc, 1) == 1)
// crc = (crc >> 1) ^ poly
crc = bxor(shr(crc, 1), poly)
else
// crc >>= 1
crc = shr(crc, 1)
j = j + 1
i = i + 1
// final xor
crc = bxor(crc, 4294967295)
return this.u32(crc)
fun crc32_hex(this, hexStr)
// Process hex input directly with only core built-ins; no method calls on 'this'
hex = to_string(hexStr)
hexd = "0123456789abcdef"
// CRC state
number crc = 4294967295 // 0xFFFFFFFF
number poly = 3988292384 // 0xEDB88320
// Iterate two hex nibbles at a time
number i = 0
while true
ch1 = substr(hex, i, 1)
if (typeof(ch1) != "String" || ch1 == "")
break
ch2 = substr(hex, i + 1, 1)
if (typeof(ch2) != "String" || ch2 == "")
break
// Convert two hex chars to a byte without helper calls
// find returns index of substring or -1; ensure lowercase
h1 = find(hexd, to_string(ch1))
h2 = find(hexd, to_string(ch2))
if (h1 < 0 || h2 < 0)
// invalid hex -> stop
break
number b = h1 * 16 + h2
// crc ^= b
crc = bxor(crc, b)
// 8 rounds
number j = 0
while j < 8
if (band(crc, 1) == 1)
crc = bxor(shr(crc, 1), poly)
else
crc = shr(crc, 1)
j = j + 1
i = i + 2
// final xor
crc = bxor(crc, 4294967295)
// Format result as 8 hex chars big-endian without calling this.two_hex
number b3 = (crc / 16777216) % 256
number b2 = (crc / 65536) % 256
number b1 = (crc / 256) % 256
number b0 = crc % 256
// encode a single byte to two hex chars using hexd and substr
fun b2hex(byte)
number hi = (byte / 16) % 16
number lo = (byte % 16)
c1 = substr(hexd, hi, 1)
c2 = substr(hexd, lo, 1)
return join([c1, c2], "")
parts = [ b2hex(b3), b2hex(b2), b2hex(b1), b2hex(b0) ]
return join(parts, "")

265
lib/crypt/sha1.fun Normal file
View file

@ -0,0 +1,265 @@
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2025-10-01
*/
// lib/crypt/sha1.fun
// Pure Fun SHA-1 implementation operating on hex-string or ASCII inputs.
//
// Public API (class):
// sha = SHA1()
// sha.sha1_hex(hexStr) -> digest hex string (lowercase)
// sha.sha1_str("abc") -> digest hex string of ASCII bytes
class SHA1()
// 32-bit helpers
fun u32(this, x)
m = 4294967296
while x < 0
x = x + m
while x >= m
x = x - m
return x
fun add32(this, a, b)
return this.u32(a + b)
fun rol32(this, x, s)
x = this.u32(x)
return rol(x, s)
// hex helpers
fun hex_val(this, ch)
if (ch == "0")
return 0
else if (ch == "1")
return 1
else if (ch == "2")
return 2
else if (ch == "3")
return 3
else if (ch == "4")
return 4
else if (ch == "5")
return 5
else if (ch == "6")
return 6
else if (ch == "7")
return 7
else if (ch == "8")
return 8
else if (ch == "9")
return 9
else if (ch == "a" || ch == "A")
return 10
else if (ch == "b" || ch == "B")
return 11
else if (ch == "c" || ch == "C")
return 12
else if (ch == "d" || ch == "D")
return 13
else if (ch == "e" || ch == "E")
return 14
else if (ch == "f" || ch == "F")
return 15
else
return 0
fun byte_from_hex_pair(this, hh)
hi = this.hex_val(substr(hh, 0, 1))
lo = this.hex_val(substr(hh, 1, 1))
return hi * 16 + lo
fun from_hex(this, hex)
arr = []
i = 0
n = len(hex)
while i + 1 < n
b = this.byte_from_hex_pair(substr(hex, i, 2))
push(arr, b)
i = i + 2
return arr
fun two_hex(this, n)
n = n % 256
d = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"]
hi = n / 16
lo = n % 16
parts = [d[hi], d[lo]]
return join(parts, "")
fun bytes_to_hex(this, arr)
i = 0
out = []
while i < len(arr)
push(out, this.two_hex(arr[i]))
i = i + 1
return join(out, "")
// padding (SHA-1): append 0x80, then zeros until length 56 (mod 64), then 64-bit length big-endian
fun pad_bytes(this, bytes)
L = len(bytes)
out = []
i = 0
while i < L
push(out, bytes[i])
i = i + 1
push(out, 128)
while (len(out) % 64) != 56
push(out, 0)
len_bits = L * 8
j = 7
while j >= 0
b = (len_bits / pow(2, 8 * (7 - j))) % 256
push(out, b)
j = j - 1
return out
fun word32_be(this, b0, b1, b2, b3)
return this.u32(b0 * 16777216 + b1 * 65536 + b2 * 256 + b3)
// process 512-bit block
fun process_block(this, state, block)
// W[80]
W = []
t = 0
while t < 16
j = t * 4
push(W, this.word32_be(block[j], block[j+1], block[j+2], block[j+3]))
t = t + 1
while t < 80
wt = bxor(bxor(bxor(W[t-3], W[t-8]), W[t-14]), W[t-16])
wt = this.rol32(wt, 1)
push(W, wt)
t = t + 1
a = state[0]
b = state[1]
c = state[2]
d = state[3]
e = state[4]
t = 0
while t < 80
if (t < 20)
f = bor(band(b, c), band(bnot(b), d))
k = 1518500249 // 0x5A827999
else if (t < 40)
f = bxor(b, bxor(c, d))
k = 1859775393 // 0x6ED9EBA1
else if (t < 60)
f = bor(bor(band(b, c), band(b, d)), band(c, d))
k = 2400959708 // 0x8F1BBCDC
else
f = bxor(b, bxor(c, d))
k = 3395469782 // 0xCA62C1D6
temp = this.u32(this.u32(this.rol32(a, 5) + f) + this.u32(this.u32(e + k) + W[t]))
e = d
d = c
c = this.rol32(b, 30)
b = a
a = temp
t = t + 1
state[0] = this.add32(state[0], a)
state[1] = this.add32(state[1], b)
state[2] = this.add32(state[2], c)
state[3] = this.add32(state[3], d)
state[4] = this.add32(state[4], e)
return state
fun sha1_bytes(this, bytes)
// initial H
H = [ 1732584193, 4023233417, 2562383102, 271733878, 3285377520 ]
data = this.pad_bytes(bytes)
N = len(data)
off = 0
while off < N
block = []
i = 0
while i < 64
push(block, data[off + i])
i = i + 1
H = this.process_block(H, block)
off = off + 64
// output 20-byte big-endian
out = []
j = 0
while j < 5
v = H[j]
push(out, (v / 16777216) % 256)
push(out, (v / 65536) % 256)
push(out, (v / 256) % 256)
push(out, v % 256)
j = j + 1
return out
fun sha1_hex(this, hexStr)
bytes = this.from_hex(hexStr)
digest = this.sha1_bytes(bytes)
return this.bytes_to_hex(digest)
// ASCII string to bytes (printable ASCII 0x20..0x7E)
fun string_to_bytes_ascii(this, s)
str = to_string(s)
out = []
number i = 0
// ASCII printable ranges
P1 = " !\"#$%&'()*+,-./" // 32..47
P2 = "0123456789" // 48..57
P3 = ":;<=>?@" // 58..64
P4 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // 65..90
P5 = "[\\]^_`" // 91..96
P6 = "abcdefghijklmnopqrstuvwxyz" // 97..122
P7 = "{|}~" // 123..126
while true
ch = substr(str, i, 1)
if (typeof(ch) != "String" || ch == "")
break
number code = -1
idx = find(P1, ch)
if (idx >= 0)
code = 32 + idx
else
idx = find(P2, ch)
if (idx >= 0)
code = 48 + idx
else
idx = find(P3, ch)
if (idx >= 0)
code = 58 + idx
else
idx = find(P4, ch)
if (idx >= 0)
code = 65 + idx
else
idx = find(P5, ch)
if (idx >= 0)
code = 91 + idx
else
idx = find(P6, ch)
if (idx >= 0)
code = 97 + idx
else
idx = find(P7, ch)
if (idx >= 0)
code = 123 + idx
else
// non-printable -> 0
code = 0
push(out, code)
i = i + 1
return out
fun sha1_str(this, str)
bytes = this.string_to_bytes_ascii(str)
digest = this.sha1_bytes(bytes)
return this.bytes_to_hex(digest)