diff --git a/CMakeLists.txt b/CMakeLists.txt index f294a1f..fa6acc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16) -project(fun VERSION 0.16.2 LANGUAGES C) +project(fun VERSION 0.16.3 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/byte_for_demo.fun b/examples/byte_for_demo.fun index 54106d1..90e1980 100755 --- a/examples/byte_for_demo.fun +++ b/examples/byte_for_demo.fun @@ -29,3 +29,26 @@ print(typeof(x)) // -> "String" // Typed byte must remain numeric (uint8): // b = "oops" // Uncomment to see a runtime type error + +/* Expected output: +=== byte with hex literal and clamping === +255 +255 +255 + +=== for-loop reassigning a byte variable (shows clamping near the top end) === +250 +251 +252 +253 +254 +255 +255 +255 +255 +255 + +=== dynamic vs typed === +Number +String +*/ diff --git a/examples/md5_demo.fun b/examples/md5_demo.fun index 6f63169..c00561d 100755 --- a/examples/md5_demo.fun +++ b/examples/md5_demo.fun @@ -32,6 +32,16 @@ print(md5.md5_hex("6d65737361676520646967657374")) // -> f96b697d7cb7938d525a2f print(md5.md5_hex("6162636465666768696a6b6c6d6e6f707172737475767778797a")) // -> c3fcd3d76192e4007dfb496cca67e13b // "Have Fun!" -print(md5.md5_str("Have Fun!")) // -> +print(md5.md5_str("Have Fun!")) // -> 852438d026c018c4307b916406f98c62 print("=== Done ===") + +/* Expected output: +=== MD5 demo (hex input) === +900150983cd24fb0d6963f7d28e17f72 +d41d8cd98f00b204e9800998ecf8427e +f96b697d7cb7938d525a2f31aaf161d0 +c3fcd3d76192e4007dfb496cca67e13b +812f2c01287af0e7c0a0b3daa381a51a +=== Done === +*/ diff --git a/examples/sha256_demo.fun b/examples/sha256_demo.fun index 60f7cc5..a75354e 100755 --- a/examples/sha256_demo.fun +++ b/examples/sha256_demo.fun @@ -31,3 +31,12 @@ print(sha.sha256_hex("6d65737361676520646967657374")) // -> f7846f55cf23e14eebea print(sha.sha256_hex("6162636465666768696a6b6c6d6e6f707172737475767778797a")) // -> 71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 print("=== Done ===") + +/* Expected output: +=== SHA-256 demo (hex input) === +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +3859a4ec00dcbed7c01d4a462d18c1f4097a690a1d4d459be4e5b6f5a594ee68 +de8ff7370090823795e96e3b7fbee3cfad374492417da99d72ba55297f7e58ab +5d3ed26c5e75585f56e664c0ebc63aa185ec606109f6374fefa42c862670d257 +=== Done === +*/ diff --git a/examples/sha256_str_demo.fun b/examples/sha256_str_demo.fun index 48f75d2..de0deb7 100755 --- a/examples/sha256_str_demo.fun +++ b/examples/sha256_str_demo.fun @@ -35,3 +35,14 @@ print(sha.sha256_str("6162636465666768696a6b6c6d6e6f707172737475767778797a")) // print("=== Note ===") print("sha256_str(\"616263\") hashes the text 616263; sha256_hex(\"616263\") hashes bytes 0x61 0x62 0x63 (abc).") + +/* Expected output: +=== SHA-256 demo (raw string input via sha256_str) === +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +3859a4ec00dcbed7c01d4a462d18c1f4097a690a1d4d459be4e5b6f5a594ee68 +faee5966a2c104cf2287085428a183fe58c18afdc5ff261571e8e9c4796ad635 +57aad00934af3b05086a755a1787884f2af6554dce4fc9cab02e7008d31dab44 +a863dce86b2577846b6fd399ae6c9830999b4525c50b74705998f3df852b6d25 +=== Note === +sha256_str("616263") hashes the text 616263; sha256_hex("616263") hashes bytes 0x61 0x62 0x63 (abc). +*/ diff --git a/examples/sha384_example.fun b/examples/sha384_example.fun new file mode 100755 index 0000000..c5fd0ba --- /dev/null +++ b/examples/sha384_example.fun @@ -0,0 +1,29 @@ +#!/usr/bin/env fun + +/* + * 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 + */ + +/* + * Example: Using SHA384 from lib/crypt/sha384.fun + * + * Expected output for "abc": + * cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7 + */ + +#include + +s = SHA384() + +// Hex input representing "abc" +print(s.sha384_hex("616263")) + +// Raw string input +print(s.sha384_str("abc")) diff --git a/examples/sha512_demo.fun b/examples/sha512_demo.fun index c630bc6..1d87a41 100755 --- a/examples/sha512_demo.fun +++ b/examples/sha512_demo.fun @@ -33,3 +33,11 @@ print(sha.sha512_hex("6d65737361676520646967657374")) // 3f36cc64dab6c0c5 print("=== Done ===") + +/* Expected output: +=== SHA-512 demo (hex input) === +cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f +107dbf389d9e9f71a3a95f6c055b9251bc5268c2be16d6c13492ea45b0199f3309e16455ab1e96118e8a905d5597b72038ddb372a89826046de66687bb420e7c +=== Done === +*/ diff --git a/examples/sha512_str_demo.fun b/examples/sha512_str_demo.fun index 4eac385..b318c53 100755 --- a/examples/sha512_str_demo.fun +++ b/examples/sha512_str_demo.fun @@ -32,3 +32,12 @@ print(sha.sha512_str("616263")) // -> ee21095236a9c037f705f41ffa3cf60869891fcdb print("=== Note ===") print("sha512_str(\"616263\") hashes the text 616263; sha512_hex(\"616263\") hashes bytes 0x61 0x62 0x63 (abc).") + +/* Expected output: +=== SHA-512 demo (raw string input via sha512_str) === +cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e +ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f +ee21095236a9c037f705f41ffa3cf60869891fcdb461c5c8fe50e5b1711a27bfc02de2e387228651bdd034113cc59af777fdb9c915b70fdbed0eeacf7113296b +=== Note === +sha512_str("616263") hashes the text 616263; sha512_hex("616263") hashes bytes 0x61 0x62 0x63 (abc). +*/ diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 0000000..eab0859 --- /dev/null +++ b/lib/README.md @@ -0,0 +1,3 @@ +# README + +The Fun standard library. diff --git a/lib/core/README.md b/lib/core/README.md new file mode 100644 index 0000000..5f8ae73 --- /dev/null +++ b/lib/core/README.md @@ -0,0 +1,3 @@ +# README + +In this directory every file should just contain one function if possible! diff --git a/lib/core/string_to_bytes_ascii.fun b/lib/core/string_to_bytes_ascii.fun new file mode 100644 index 0000000..0523fe4 --- /dev/null +++ b/lib/core/string_to_bytes_ascii.fun @@ -0,0 +1,62 @@ +/* + * 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 + */ + +// ASCII string to bytes (printable ASCII 0x20..0x7E) +fun string_to_bytes_ascii(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 diff --git a/lib/crypt/md5.fun b/lib/crypt/md5.fun index c4d66bc..a77d2fd 100644 --- a/lib/crypt/md5.fun +++ b/lib/crypt/md5.fun @@ -18,265 +18,7 @@ // Example: // print(md5_hex("616263")) // "abc" => 900150983cd24fb0d6963f7d28e17f72 -/* Legacy global API and helpers are commented out below. - Please use the MD5 class (see at the end of this file). - -fun u32(x) - // bring x into [0 .. 2^32-1] by modular reduction - m = 4294967296 - while x < 0 - x = x + m - while x >= m - x = x - m - return x - -fun shl32(x, s) - return shl(u32(x), s) - -fun shr32(x, s) - return shr(u32(x), s) - -fun rol32(x, s) - return rol(u32(x), s) - -fun and32(a, b) - return band(u32(a), u32(b)) - -fun or32(a, b) - return bor(u32(a), u32(b)) - -fun xor32(a, b) - return bxor(u32(a), u32(b)) - -fun not32(x) - return bnot(u32(x)) - -fun add32(a, b) - return u32(a + b) - -fun add32_4(a, b, c, d) - return u32(u32(u32(a + b) + c) + d) - -fun hex_val(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(hh) - // hh is 2-char string - hi = hex_val(substr(hh, 0, 1)) - lo = hex_val(substr(hh, 1, 1)) - return hi * 16 + lo - -fun from_hex(hex) - // parse even-length hex string to array of bytes (numbers 0..255) - arr = [] - i = 0 - n = len(hex) - while i + 1 < n - b = byte_from_hex_pair(substr(hex, i, 2)) - push(arr, b) - i = i + 2 - return arr - -fun two_hex(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 - // build with join - parts = [d[hi], d[lo]] - return join(parts, "") - -fun bytes_to_hex(arr) - i = 0 - out = [] - while i < len(arr) - push(out, two_hex(arr[i])) - i = i + 1 - return join(out, "") - -// MD5 auxiliary functions -fun F(x, y, z) - return or32(and32(x, y), and32(not32(x), z)) - -fun G(x, y, z) - return or32(and32(x, z), and32(y, not32(z))) - -fun H(x, y, z) - return xor32(xor32(x, y), z) - -fun I(x, y, z) - return xor32(y, or32(x, not32(z))) - -// T constants (K) = floor(abs(sin(i+1)) * 2^32), i=0..63 -K = [ - 3614090360, 3905402710, 606105819, 3250441966, 4118548399, 1200080426, 2821735955, 4249261313, - 1770035416, 2336552879, 4294925233, 2304563134, 1804603682, 4254626195, 2792965006, 1236535329, - 4129170786, 3225465664, 643717713, 3921069994, 3593408605, 38016083, 3634488961, 3889429448, - 568446438, 3275163606, 4107603335, 1163531501, 2850285829, 4243563512, 1735328473, 2368359562, - 4294588738, 2272392833, 1839030562, 4259657740, 2763975236, 1272893353, 4139469664, 3200236656, - 681279174, 3936430074, 3572445317, 76029189, 3654602809, 3873151461, 530742520, 3299628645, - 4096336452, 1126891415, 2878612391, 4237533241, 1700485571, 2399980690, 4293915773, 2240044497, - 1873313359, 4264355552, 2734768916, 1309151649, 4149444226, 3174756917, 718787259, 3951481745 -] - -// per-round rotation amounts -S = [ - 7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22, - 5, 9,14,20, 5, 9,14,20, 5, 9,14,20, 5, 9,14,20, - 4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23, - 6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21 -] - -fun pad_bytes(bytes) - // returns a new array with MD5 padding - L = len(bytes) - out = [] - // copy original bytes - i = 0 - while i < L - push(out, bytes[i]) - i = i + 1 - // append 0x80 - push(out, 128) - // pad zeros until length % 64 == 56 - while (len(out) % 64) != 56 - push(out, 0) - // append length (bits) as 64-bit little-endian - len_bits = L * 8 - j = 0 - while j < 8 - b = (len_bits / pow(2, 8 * j)) % 256 - push(out, b) - j = j + 1 - return out - -fun word32_le(b0, b1, b2, b3) - return u32(b0 + b1 * 256 + b2 * 65536 + b3 * 16777216) - -fun process_block(state, block) - // state: [a0,b0,c0,d0], block: 64 bytes array - a0 = state[0] - b0 = state[1] - c0 = state[2] - d0 = state[3] - - // M[16] little-endian words - M = [] - i = 0 - while i < 16 - j = i * 4 - w = word32_le(block[j], block[j+1], block[j+2], block[j+3]) - push(M, w) - i = i + 1 - - A = a0 - B = b0 - C = c0 - D = d0 - - k = 0 - while k < 64 - if (k < 16) - f = F(B, C, D) - g = k - else if (k < 32) - f = G(B, C, D) - g = (5 * k + 1) % 16 - else if (k < 48) - f = H(B, C, D) - g = (3 * k + 5) % 16 - else - f = I(B, C, D) - g = (7 * k) % 16 - - tmp = D - sum = add32_4(A, f, K[k], M[g]) - D = C - C = B - B = add32(B, rol32(sum, S[k])) - A = tmp - k = k + 1 - - state[0] = add32(state[0], A) - state[1] = add32(state[1], B) - state[2] = add32(state[2], C) - state[3] = add32(state[3], D) - return state - -fun md5_bytes(bytes) - // returns array of 16 bytes of digest - data = pad_bytes(bytes) - // initial state - a0 = 1732584193 // 0x67452301 - b0 = 4023233417 // 0xEFCDAB89 - c0 = 2562383102 // 0x98BADCFE - d0 = 271733878 // 0x10325476 - state = [a0, b0, c0, d0] - - off = 0 - N = len(data) - while off < N - block = [] - i = 0 - while i < 64 - push(block, data[off + i]) - i = i + 1 - state = process_block(state, block) - off = off + 64 - - // output digest as little-endian bytes of A,B,C,D - out = [] - j = 0 - while j < 4 - v = state[j] - // 4 bytes little-endian - push(out, (v ) % 256) - push(out, (v / 256) % 256) - push(out, (v / 65536) % 256) - push(out, (v / 16777216) % 256) - j = j + 1 - return out - -fun md5_hex(hexStr) - bytes = from_hex(hexStr) - digest = md5_bytes(bytes) - return bytes_to_hex(digest) - // Class wrapper to avoid global name collisions and show class usage -*/ #include diff --git a/lib/crypt/md5_legacy.fun b/lib/crypt/md5_legacy.fun new file mode 100644 index 0000000..8a33d9f --- /dev/null +++ b/lib/crypt/md5_legacy.fun @@ -0,0 +1,272 @@ +/* + * 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 + */ + +#include + +// Legacy global API and helpers. Please use the MD5 class (see lib/crypt/md5.fun). +fun u32(x) + // bring x into [0 .. 2^32-1] by modular reduction + m = 4294967296 + while x < 0 + x = x + m + while x >= m + x = x - m + return x + +fun shl32(x, s) + return shl(u32(x), s) + +fun shr32(x, s) + return shr(u32(x), s) + +fun rol32(x, s) + return rol(u32(x), s) + +fun and32(a, b) + return band(u32(a), u32(b)) + +fun or32(a, b) + return bor(u32(a), u32(b)) + +fun xor32(a, b) + return bxor(u32(a), u32(b)) + +fun not32(x) + return bnot(u32(x)) + +fun add32(a, b) + return u32(a + b) + +fun add32_4(a, b, c, d) + return u32(u32(u32(a + b) + c) + d) + +fun hex_val(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(hh) + // hh is 2-char string + hi = hex_val(substr(hh, 0, 1)) + lo = hex_val(substr(hh, 1, 1)) + return hi * 16 + lo + +fun from_hex(hex) + // parse even-length hex string to array of bytes (numbers 0..255) + arr = [] + i = 0 + n = len(hex) + while i + 1 < n + b = byte_from_hex_pair(substr(hex, i, 2)) + push(arr, b) + i = i + 2 + return arr + +fun two_hex(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 + // build with join + parts = [d[hi], d[lo]] + return join(parts, "") + +fun bytes_to_hex(arr) + i = 0 + out = [] + while i < len(arr) + push(out, two_hex(arr[i])) + i = i + 1 + return join(out, "") + +// MD5 auxiliary functions +fun F(x, y, z) + return or32(and32(x, y), and32(not32(x), z)) + +fun G(x, y, z) + return or32(and32(x, z), and32(y, not32(z))) + +fun H(x, y, z) + return xor32(xor32(x, y), z) + +fun I(x, y, z) + return xor32(y, or32(x, not32(z))) + +// T constants (K) = floor(abs(sin(i+1)) * 2^32), i=0..63 +K = [ + 3614090360, 3905402710, 606105819, 3250441966, 4118548399, 1200080426, 2821735955, 4249261313, + 1770035416, 2336552879, 4294925233, 2304563134, 1804603682, 4254626195, 2792965006, 1236535329, + 4129170786, 3225465664, 643717713, 3921069994, 3593408605, 38016083, 3634488961, 3889429448, + 568446438, 3275163606, 4107603335, 1163531501, 2850285829, 4243563512, 1735328473, 2368359562, + 4294588738, 2272392833, 1839030562, 4259657740, 2763975236, 1272893353, 4139469664, 3200236656, + 681279174, 3936430074, 3572445317, 76029189, 3654602809, 3873151461, 530742520, 3299628645, + 4096336452, 1126891415, 2878612391, 4237533241, 1700485571, 2399980690, 4293915773, 2240044497, + 1873313359, 4264355552, 2734768916, 1309151649, 4149444226, 3174756917, 718787259, 3951481745 +] + +// per-round rotation amounts +S = [ + 7,12,17,22, 7,12,17,22, 7,12,17,22, 7,12,17,22, + 5, 9,14,20, 5, 9,14,20, 5, 9,14,20, 5, 9,14,20, + 4,11,16,23, 4,11,16,23, 4,11,16,23, 4,11,16,23, + 6,10,15,21, 6,10,15,21, 6,10,15,21, 6,10,15,21 +] + +fun pad_bytes(bytes) + // returns a new array with MD5 padding + L = len(bytes) + out = [] + // copy original bytes + i = 0 + while i < L + push(out, bytes[i]) + i = i + 1 + // append 0x80 + push(out, 128) + // pad zeros until length % 64 == 56 + while (len(out) % 64) != 56 + push(out, 0) + // append length (bits) as 64-bit little-endian + len_bits = L * 8 + j = 0 + while j < 8 + b = (len_bits / pow(2, 8 * j)) % 256 + push(out, b) + j = j + 1 + return out + +fun word32_le(b0, b1, b2, b3) + return u32(b0 + b1 * 256 + b2 * 65536 + b3 * 16777216) + +fun process_block(state, block) + // state: [a0,b0,c0,d0], block: 64 bytes array + a0 = state[0] + b0 = state[1] + c0 = state[2] + d0 = state[3] + + // M[16] little-endian words + M = [] + i = 0 + while i < 16 + j = i * 4 + w = word32_le(block[j], block[j+1], block[j+2], block[j+3]) + push(M, w) + i = i + 1 + + A = a0 + B = b0 + C = c0 + D = d0 + + k = 0 + while k < 64 + if (k < 16) + f = F(B, C, D) + g = k + else if (k < 32) + f = G(B, C, D) + g = (5 * k + 1) % 16 + else if (k < 48) + f = H(B, C, D) + g = (3 * k + 5) % 16 + else + f = I(B, C, D) + g = (7 * k) % 16 + + tmp = D + sum = add32_4(A, f, K[k], M[g]) + D = C + C = B + B = add32(B, rol32(sum, S[k])) + A = tmp + k = k + 1 + + state[0] = add32(state[0], A) + state[1] = add32(state[1], B) + state[2] = add32(state[2], C) + state[3] = add32(state[3], D) + return state + +fun md5_bytes(bytes) + // returns array of 16 bytes of digest + data = pad_bytes(bytes) + // initial state + a0 = 1732584193 // 0x67452301 + b0 = 4023233417 // 0xEFCDAB89 + c0 = 2562383102 // 0x98BADCFE + d0 = 271733878 // 0x10325476 + state = [a0, b0, c0, d0] + + off = 0 + N = len(data) + while off < N + block = [] + i = 0 + while i < 64 + push(block, data[off + i]) + i = i + 1 + state = process_block(state, block) + off = off + 64 + + // output digest as little-endian bytes of A,B,C,D + out = [] + j = 0 + while j < 4 + v = state[j] + // 4 bytes little-endian + push(out, (v) % 256) + push(out, (v / 256) % 256) + push(out, (v / 65536) % 256) + push(out, (v / 16777216) % 256) + j = j + 1 + return out + +fun md5_hex(hexStr) + bytes = from_hex(hexStr) + digest = md5_bytes(bytes) + return bytes_to_hex(digest) + +fun md5_str(this, str) + bytes = string_to_bytes_ascii(str) + digest = this.md5_bytes(bytes) + return this.bytes_to_hex(digest) diff --git a/lib/crypt/sha384.fun b/lib/crypt/sha384.fun new file mode 100644 index 0000000..9b05738 --- /dev/null +++ b/lib/crypt/sha384.fun @@ -0,0 +1,368 @@ +/* + * 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/sha384.fun +// Pure Fun SHA-384 implementation operating on hex-string input. +// 64-bit words are represented as [hi, lo] (two uint32 parts). +// +// Public API (class): +// s = SHA384() +// s.sha384_hex(hexStr) -> digest hex string (lowercase, 96 hex chars) +// s.sha384_str(str) -> digest hex string (lowercase, 96 hex chars) +// +// Known test vector ("abc"): +// SHA-384("abc") = +// cb00753f45a35e8bb5a03d699ac65007272c32ab0eded163 +// 1a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7 + +#include + +class SHA384() + // -------- 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) + number hi = this.hex_val(substr(hh, 0, 1)) + number lo = this.hex_val(substr(hh, 1, 1)) + return hi * 16 + lo + + fun from_hex(this, hex) + hex = to_string(hex) + arr = [] + number i = 0 + number n = len(hex) + while i + 1 < n + number 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 + hexd = "0123456789abcdef" + number hi = (n / 16) % 16 + number lo = n % 16 + c1 = substr(hexd, hi, 1) + c2 = substr(hexd, lo, 1) + return join([c1, c2], "") + + fun bytes_to_hex(this, arr) + out = [] + number i = 0 + number L = len(arr) + while i < L + push(out, this.two_hex(arr[i])) + i = i + 1 + return join(out, "") + + // -------- 32/64 helpers -------- + fun u32(this, x) + m = 4294967296 + while x < 0 + x = x + m + while x >= m + x = x - m + return x + + // 64-bit as [hi, lo] (each uint32) + fun pack64_be(this, b0,b1,b2,b3,b4,b5,b6,b7) + number hi = this.u32(b0 * 16777216 + b1 * 65536 + b2 * 256 + b3) + number lo = this.u32(b4 * 16777216 + b5 * 65536 + b6 * 256 + b7) + return [hi, lo] + + fun add64(this, a, b) + number alo = this.u32(a[1]) + number ahi = this.u32(a[0]) + number blo = this.u32(b[1]) + number bhi = this.u32(b[0]) + number sum_lo = alo + blo + number carry = sum_lo / 4294967296 + sum_lo = sum_lo % 4294967296 + number sum_hi = (ahi + bhi + carry) % 4294967296 + return [sum_hi, sum_lo] + + fun add64_5(this, a, b, c, d, e) + return this.add64(this.add64(this.add64(this.add64(a, b), c), d), e) + + fun xor64(this, a, b) + return [ bxor(a[0], b[0]), bxor(a[1], b[1]) ] + + fun and64(this, a, b) + return [ band(a[0], b[0]), band(a[1], b[1]) ] + + fun or64(this, a, b) + return [ bor(a[0], b[0]), bor(a[1], b[1]) ] + + fun not64(this, a) + return [ bnot(a[0]), bnot(a[1]) ] + + // logical right shift by n (0..63) + fun shr64(this, a, n) + number s = n % 64 + number hi = a[0] + number lo = a[1] + if (s == 0) + return [hi, lo] + else if (s < 32) + number new_lo = bor( shr(lo, s), shl(hi, 32 - s) ) + number new_hi = shr(hi, s) + return [ new_hi, new_lo ] + else + number k = s - 32 + number new_lo2 = shr(hi, k) + return [ 0, new_lo2 ] + + // rotate right by n (0..63) + fun rotr64(this, a, n) + number s = n % 64 + number hi = a[0] + number lo = a[1] + if (s == 0) + return [hi, lo] + else if (s < 32) + number new_lo = bor( shr(lo, s), shl(hi, 32 - s) ) + number new_hi = bor( shr(hi, s), shl(lo, 32 - s) ) + return [ new_hi, new_lo ] + else + number k = s - 32 + // swap roles + number new_lo2 = bor( shr(hi, k), shl(lo, 32 - k) ) + number new_hi2 = bor( shr(lo, k), shl(hi, 32 - k) ) + return [ new_hi2, new_lo2 ] + + // SHA-512 logical functions (used by SHA-384 rounds) + fun Ch(this, x, y, z) + // (x & y) ^ (~x & z) + return this.xor64(this.and64(x, y), this.and64(this.not64(x), z)) + + fun Maj(this, x, y, z) + // (x & y) ^ (x & z) ^ (y & z) + return this.xor64(this.xor64(this.and64(x, y), this.and64(x, z)), this.and64(y, z)) + + fun Sigma0(this, x) + return this.xor64(this.xor64(this.rotr64(x, 28), this.rotr64(x, 34)), this.rotr64(x, 39)) + + fun Sigma1(this, x) + return this.xor64(this.xor64(this.rotr64(x, 14), this.rotr64(x, 18)), this.rotr64(x, 41)) + + fun sigma0(this, x) + return this.xor64(this.xor64(this.rotr64(x, 1), this.rotr64(x, 8)), this.shr64(x, 7)) + + fun sigma1(this, x) + return this.xor64(this.xor64(this.rotr64(x, 19), this.rotr64(x, 61)), this.shr64(x, 6)) + + // -------- constants -------- + // SHA-384 uses the same K constants as SHA-512 + K = [ + [0x428a2f98,0xd728ae22],[0x71374491,0x23ef65cd],[0xb5c0fbcf,0xec4d3b2f],[0xe9b5dba5,0x8189dbbc], + [0x3956c25b,0xf348b538],[0x59f111f1,0xb605d019],[0x923f82a4,0xaf194f9b],[0xab1c5ed5,0xda6d8118], + [0xd807aa98,0xa3030242],[0x12835b01,0x45706fbe],[0x243185be,0x4ee4b28c],[0x550c7dc3,0xd5ffb4e2], + [0x72be5d74,0xf27b896f],[0x80deb1fe,0x3b1696b1],[0x9bdc06a7,0x25c71235],[0xc19bf174,0xcf692694], + [0xe49b69c1,0x9ef14ad2],[0xefbe4786,0x384f25e3],[0x0fc19dc6,0x8b8cd5b5],[0x240ca1cc,0x77ac9c65], + [0x2de92c6f,0x592b0275],[0x4a7484aa,0x6ea6e483],[0x5cb0a9dc,0xbd41fbd4],[0x76f988da,0x831153b5], + [0x983e5152,0xee66dfab],[0xa831c66d,0x2db43210],[0xb00327c8,0x98fb213f],[0xbf597fc7,0xbeef0ee4], + [0xc6e00bf3,0x3da88fc2],[0xd5a79147,0x930aa725],[0x06ca6351,0xe003826f],[0x14292967,0x0a0e6e70], + [0x27b70a85,0x46d22ffc],[0x2e1b2138,0x5c26c926],[0x4d2c6dfc,0x5ac42aed],[0x53380d13,0x9d95b3df], + [0x650a7354,0x8baf63de],[0x766a0abb,0x3c77b2a8],[0x81c2c92e,0x47edaee6],[0x92722c85,0x1482353b], + [0xa2bfe8a1,0x4cf10364],[0xa81a664b,0xbc423001],[0xc24b8b70,0xd0f89791],[0xc76c51a3,0x0654be30], + [0xd192e819,0xd6ef5218],[0xd6990624,0x5565a910],[0xf40e3585,0x5771202a],[0x106aa070,0x32bbd1b8], + [0x19a4c116,0xb8d2d0c8],[0x1e376c08,0x5141ab53],[0x2748774c,0xdf8eeb99],[0x34b0bcb5,0xe19b48a8], + [0x391c0cb3,0xc5c95a63],[0x4ed8aa4a,0xe3418acb],[0x5b9cca4f,0x7763e373],[0x682e6ff3,0xd6b2b8a3], + [0x748f82ee,0x5defb2fc],[0x78a5636f,0x43172f60],[0x84c87814,0xa1f0ab72],[0x8cc70208,0x1a6439ec], + [0x90befffa,0x23631e28],[0xa4506ceb,0xde82bde9],[0xbef9a3f7,0xb2c67915],[0xc67178f2,0xe372532b], + [0xca273ece,0xea26619c],[0xd186b8c7,0x21c0c207],[0xeada7dd6,0xcde0eb1e],[0xf57d4f7f,0xee6ed178], + [0x06f067aa,0x72176fba],[0x0a637dc5,0xa2c898a6],[0x113f9804,0xbef90dae],[0x1b710b35,0x131c471b], + [0x28db77f5,0x23047d84],[0x32caab7b,0x40c72493],[0x3c9ebe0a,0x15c9bebc],[0x431d67c4,0x9c100d4c], + [0x4cc5d4be,0xcb3e42b6],[0x597f299c,0xfc657e2a],[0x5fcb6fab,0x3ad6faec],[0x6c44198c,0x4a475817] + ] + + // SHA-384 initial hash value (IV), 64-bit words split into [hi, lo] + IV = [ + [0xcbbb9d5d, 0xc1059ed8], [0x629a292a, 0x367cd507], [0x9159015a, 0x3070dd17], [0x152fecd8, 0xf70e5939], + [0x67332667, 0xffc00b31], [0x8eb44a87, 0x68581511], [0xdb0c2e0d, 0x64f98fa7], [0x47b5481d, 0xbefa4fa4] + ] + + // -------- padding -------- + fun pad_bytes(this, bytes) + number L = len(bytes) + out = [] + // copy bytes + number i = 0 + while i < L + push(out, bytes[i]) + i = i + 1 + // append 0x80 + push(out, 128) + // pad zeros until length % 128 == 112 (i.e., 896 bits) + while (len(out) % 128) != 112 + push(out, 0) + // append 128-bit length (we use upper 64 bits zero, lower 64 = L*8) + number j = 0 + while j < 8 + push(out, 0) + j = j + 1 + // lower 8 bytes big-endian + number bits = L * 8 + number t = 7 + while t >= 0 + number b = (bits / pow(2, 8 * t)) % 256 + push(out, b) + t = t - 1 + return out + + // -------- core -------- + fun process_block(this, H, block) + // W ring buffer of 16 words ([hi, lo]) + W = [] + number i = 0 + while i < 16 + number j = i * 8 + push(W, this.pack64_be(block[j],block[j+1],block[j+2],block[j+3], + block[j+4],block[j+5],block[j+6],block[j+7])) + i = i + 1 + + a = H[0] + b = H[1] + c = H[2] + d = H[3] + e = H[4] + f = H[5] + g = H[6] + h = H[7] + + number t = 0 + while t < 80 + number idx = t % 16 + if (t >= 16) + w2 = W[(t - 2) % 16] + w7 = W[(t - 7) % 16] + w15 = W[(t - 15) % 16] + w16 = W[(t - 16) % 16] + s1 = this.sigma1(w2) + s0 = this.sigma0(w15) + tmp = this.add64(w16, s0) + tmp = this.add64(tmp, w7) + W[idx] = this.add64(tmp, s1) + Wt = W[idx] + + // Precompute hot terms once per round + Se1 = this.Sigma1(e) + ch = this.Ch(e, f, g) + + // T1 = h + Se1 + ch + K[t] + Wt + tmp1 = this.add64(h, Se1) + tmp1 = this.add64(tmp1, ch) + tmp1 = this.add64(tmp1, this.K[t]) + T1 = this.add64(tmp1, Wt) + + // T2 = Sigma0(a) + Maj(a, b, c) + T2 = this.add64(this.Sigma0(a), this.Maj(a, b, c)) + + // State update + h = g + g = f + f = e + e = this.add64(d, T1) + d = c + c = b + b = a + a = this.add64(T1, T2) + t = t + 1 + + H[0] = this.add64(H[0], a) + H[1] = this.add64(H[1], b) + H[2] = this.add64(H[2], c) + H[3] = this.add64(H[3], d) + H[4] = this.add64(H[4], e) + H[5] = this.add64(H[5], f) + H[6] = this.add64(H[6], g) + H[7] = this.add64(H[7], h) + return H + + fun sha384_bytes(this, bytes) + data = this.pad_bytes(bytes) + // init H (deep 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] ] + number off = 0 + number N = len(data) + while off < N + block = [] + number i = 0 + while i < 128 + push(block, data[off + i]) + i = i + 1 + H = this.process_block(H, block) + off = off + 128 + + // output 48 bytes big-endian from H[0..5] + out = [] + number j = 0 + while j < 6 + word = H[j] + number hi = word[0] + number lo = word[1] + // hi + push(out, (hi / 16777216) % 256) + push(out, (hi / 65536) % 256) + push(out, (hi / 256) % 256) + push(out, hi % 256) + // lo + push(out, (lo / 16777216) % 256) + push(out, (lo / 65536) % 256) + push(out, (lo / 256) % 256) + push(out, lo % 256) + j = j + 1 + return out + + fun sha384_hex(this, hexStr) + bytes = this.from_hex(hexStr) + digest = this.sha384_bytes(bytes) + return this.bytes_to_hex(digest) + + // Hash raw string bytes (printable ASCII) + fun sha384_str(this, str) + bytes = string_to_bytes_ascii(str) + digest = this.sha384_bytes(bytes) + return this.bytes_to_hex(digest) diff --git a/lib/strings.fun b/lib/strings.fun index 9057910..f5dfd56 100644 --- a/lib/strings.fun +++ b/lib/strings.fun @@ -1,4 +1,4 @@ - /* +/* * This file is part of the Fun programming language. * https://hanez.org/project/fun/ * @@ -9,54 +9,7 @@ * Added: 2025-10-01 */ -// 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 +// This file should not contain code! It should just include all string related +// functions from core/*.fun. + +#include diff --git a/play.fun b/play.fun index 5cc2e67..d0a3546 100755 --- a/play.fun +++ b/play.fun @@ -9,6 +9,8 @@ * https://opensource.org/license/apache-2-0 */ +include + fun foo() print("Have fun!") print("Having fun... forever.") @@ -17,6 +19,8 @@ print("Typeof foo(): " + typeof(foo)) print("Yay, the playground for having fun... ;)") +print(string_to_bytes_ascii("Have Fun!")) + number n = 23 // Every type MUST be lowercase. Sint* must be sint*. print(n) diff --git a/src/vm/len.c b/src/vm/len.c index 794af72..180e12a 100644 --- a/src/vm/len.c +++ b/src/vm/len.c @@ -28,7 +28,7 @@ * // Stack after: [5] * * @author Johannes Findeisen - * @date 2025-10-16 + * @date 2025-09-16 */ case OP_LEN: { diff --git a/src/vm/print.c b/src/vm/print.c index d54f58d..464300c 100644 --- a/src/vm/print.c +++ b/src/vm/print.c @@ -24,7 +24,7 @@ * // Stack after: [] * * @author Johannes Findeisen - * @date 2025-10-16 + * @date 2025-09-16 */ case OP_PRINT: {