/* * This file is part of the Fun programming language. * https://hanez.org/project/fun/ * * Copyright 2025 Johannes Findeisen * 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: sha512_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)