1
0
Fork 0
forked from fun/fun
fun/lib/crypt/sha256.fun
2025-09-29 09:57:54 +02:00

319 lines
8.4 KiB
Standard ML

/*
* 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 ISC license.
* https://opensource.org/license/isc-license-txt
*
* Added: 2025-09-29
*/
// lib/crypt/sha256.fun
// Pure Fun SHA-256 implementation operating on hex-string input.
//
// Public API (class):
// sha = SHA256()
// sha.sha256_hex(hexStr) -> digest hex string (lowercase)
//
// Example:
// print(SHA256().sha256_hex("616263")) // "abc" => ba7816bf...
class SHA256()
// 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 add32_5(this, a, b, c, d, e)
return this.u32(this.u32(this.u32(this.u32(a + b) + c) + d) + e)
// bitwise (use native VM ops)
fun rotr32(this, x, s)
return ror(this.u32(x), s)
fun shr32(this, x, s)
return shr(this.u32(x), s)
fun ch(this, x, y, z)
// (x & y) ^ (~x & z)
return bxor(band(x, y), band(bnot(x), z))
fun maj(this, x, y, z)
// (x & y) ^ (x & z) ^ (y & z)
return bxor(bxor(band(x, y), band(x, z)), band(y, z))
fun bigSigma0(this, x)
// ROTR2 ^ ROTR13 ^ ROTR22
return bxor(bxor(this.rotr32(x, 2), this.rotr32(x, 13)), this.rotr32(x, 22))
fun bigSigma1(this, x)
// ROTR6 ^ ROTR11 ^ ROTR25
return bxor(bxor(this.rotr32(x, 6), this.rotr32(x, 11)), this.rotr32(x, 25))
fun smallSigma0(this, x)
// ROTR7 ^ ROTR18 ^ SHR3
return bxor(bxor(this.rotr32(x, 7), this.rotr32(x, 18)), this.shr32(x, 3))
fun smallSigma1(this, x)
// ROTR17 ^ ROTR19 ^ SHR10
return bxor(bxor(this.rotr32(x, 17), this.rotr32(x, 19)), this.shr32(x, 10))
// constants
IV = [
1779033703, 3144134277, 1013904242, 2773480762,
1359893119, 2600822924, 528734635, 1541459225
]
K = [
1116352408, 1899447441, 3049323471, 3921009573, 961987163, 1508970993, 2453635748, 2870763221,
3624381080, 310598401, 607225278, 1426881987, 1925078388, 2162078206, 2614888103, 3248222580,
3835390401, 4022224774, 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986,
2554220882, 2821834349, 2952996808, 3210313671, 3336571891, 3584528711, 113926993, 338241895,
666307205, 773529912, 1294757372, 1396182291, 1695183700, 1986661051, 2177026350, 2456956037,
2730485921, 2820302411, 3259730800, 3345764771, 3516065817, 3600352804, 4094571909, 275423344,
430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 1537002063, 1747873779,
1955562222, 2024104815, 2227730452, 2361852424, 2428436474, 2756734187, 3204031479, 3329325298
]
// 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-256)
fun pad_bytes(this, bytes)
L = len(bytes)
out = []
i = 0
while i < L
push(out, bytes[i])
i = i + 1
// append 0x80
push(out, 128)
// zeros until length % 64 == 56
while (len(out) % 64) != 56
push(out, 0)
// length in bits as 64-bit big-endian
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)
// big-endian
return this.u32(b0 * 16777216 + b1 * 65536 + b2 * 256 + b3)
fun process_block(this, state, block)
// prepare message schedule W[64]
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 < 64
s0 = this.smallSigma0(W[t - 15])
s1 = this.smallSigma1(W[t - 2])
wt = this.add32_5(W[t - 16], s0, W[t - 7], s1, 0)
// reuse array position via push (we can index existing after push)
push(W, wt)
t = t + 1
a = state[0]
b = state[1]
c = state[2]
d = state[3]
e = state[4]
f = state[5]
g = state[6]
h = state[7]
t = 0
while t < 64
T1 = this.add32_5(h, this.bigSigma1(e), this.ch(e, f, g), this.K[t], W[t])
T2 = this.add32(this.bigSigma0(a), this.maj(a, b, c))
h = g
g = f
f = e
e = this.add32(d, T1)
d = c
c = b
b = a
a = this.add32(T1, T2)
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)
state[5] = this.add32(state[5], f)
state[6] = this.add32(state[6], g)
state[7] = this.add32(state[7], h)
return state
fun sha256_bytes(this, bytes)
data = this.pad_bytes(bytes)
// initial hash value (copy IV)
H = [ this.IV[0], this.IV[1], this.IV[2], this.IV[3], this.IV[4], this.IV[5], this.IV[6], this.IV[7] ]
off = 0
N = len(data)
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
// digest: 32 bytes big-endian
out = []
j = 0
while j < 8
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 sha256_hex(this, hexStr)
bytes = this.from_hex(hexStr)
digest = this.sha256_bytes(bytes)
return this.bytes_to_hex(digest)
// Convert a printable ASCII string (0x20..0x7E) to byte array
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
// Hash raw string bytes (printable ASCII). Example: sha256_str("616263") -> ee2109...
fun sha256_str(this, str)
bytes = this.string_to_bytes_ascii(str)
digest = this.sha256_bytes(bytes)
return this.bytes_to_hex(digest)