/****************************************************************************** __ ____ __ / / ___ ____ _/ __/_____________ _/ /__ / / / _ \/ __ `/ /_/ ___/ ___/ __ `/ / _ \ / /___/ __/ /_/ / __(__ ) /__/ /_/ / / __/ /_____/\___/\__,_/_/ /____/\___/\__,_/_/\___/ (C)opyright 2025, Leafscale, LLC - https://www.leafscale.com Project: Zygaena Filename: checksum.reef Authors: Chris Tusa License: Description: SHA256 checksum verification utilities ******************************************************************************/ module util.checksum import io.file import core.str import sys.process import crypto.sha256 as sha import core.result as res export // Compute SHA256 hash of a file, returns hex string (lowercase) fn sha256_file(path: string): string // Compute SHA256 hash of a string, returns hex string (lowercase) fn sha256_string(data: string): string // Verify a file matches expected checksum fn verify_checksum(path: string, expected: string): bool // Parse checksum format "algo:hash" and verify fn verify_checksum_with_algo(path: string, checksum_str: string): bool end export // Compute SHA256 of a file // Uses Reef's native streaming sha256_file() (Reef 0.1.14+) // Falls back to sha256sum command if native fails fn sha256_file(path: string): string if not file.fileExists(path) return "" end if // Use native streaming API (memory efficient, works on all file sizes) // Reef 0.1.14 fixed the large file hang/segfault issue let hash = sha.sha256_file(path) if str.length(hash) == 64 return str.to_lower(hash) end if // Fallback to sha256sum command if native fails return sha256_file_cmd(path) end sha256_file // Compute SHA256 using sha256sum command (for large files) fn sha256_file_cmd(path: string): string let output_file = "/tmp/coral_checksum_" + extract_basename(path) let cmd = "sha256sum \"" + path + "\" | cut -d' ' -f1 > \"" + output_file + "\"" let pid = process.process_spawn_shell(cmd) if pid < 0 return "" end if process.process_wait(pid) // Read the hash from output file if not file.fileExists(output_file) return "" end if let hash = str.trim_ws(res.unwrap_or(file.readFile(output_file), "")) // Clean up let rm_pid = process.process_spawn_shell("rm -f \"" + output_file + "\"") if rm_pid > 0 process.process_wait(rm_pid) end if return hash end sha256_file_cmd // Extract basename from path for temp file naming fn extract_basename(path: string): string let last_slash = str.last_index_of_char(path, '/') if last_slash < 0 return path end if return str.substring(path, last_slash + 1, str.length(path) - last_slash - 1) end extract_basename // Compute SHA256 of a string using native crypto fn sha256_string(data: string): string let hash = sha.sha256(data) return str.to_lower(hash) end sha256_string // Verify that a file's SHA256 matches the expected value fn verify_checksum(path: string, expected: string): bool let computed = sha256_file(path) if str.length(computed) == 0 return false end if // Compare case-insensitively (normalize to lowercase) let computed_lower = str.to_lower(computed) let expected_lower = str.to_lower(expected) return computed_lower == expected_lower end verify_checksum // Parse and verify checksum in format "algo:hash" or just "hash" // Supported algorithms: sha256, sha256sum // If no algorithm prefix, assumes sha256 fn verify_checksum_with_algo(path: string, checksum_str: string): bool // Check for algorithm prefix let colon_idx = str.index_of_char(checksum_str, ':') if colon_idx < 0 // No prefix, assume SHA256 return verify_checksum(path, checksum_str) end if // Extract algorithm and hash let algo = str.to_lower(str.substring(checksum_str, 0, colon_idx)) let hash = str.substring(checksum_str, colon_idx + 1, str.length(checksum_str) - colon_idx - 1) // Verify based on algorithm if algo == "sha256" or algo == "sha256sum" return verify_checksum(path, hash) end if // Unsupported algorithm return false end verify_checksum_with_algo end module