## tests/test_hash_verifier.nim ## Comprehensive unit tests for hash verification module ## ## Includes golden test vectors and edge case testing import std/[unittest, os, streams, strutils, tempfiles, options] import ../src/nimpak/security/hash_verifier suite "Hash Algorithm Detection": test "detect BLAKE2b hash formats": check detectHashAlgorithm("blake2b-abc123") == HashBlake2b check detectHashAlgorithm("a".repeat(128)) == HashBlake2b # 128 hex chars = BLAKE2b-512 test "detect BLAKE3 hash formats": # BLAKE3 is detected but falls back to BLAKE2b check detectHashAlgorithm("blake3-abc123") == HashBlake3 test "detect SHA256 hash formats": check detectHashAlgorithm("sha256-abc123") == HashSha256 check detectHashAlgorithm("a".repeat(64)) == HashSha256 # 64 hex chars = SHA256 test "invalid hash formats": expect ValueError: discard detectHashAlgorithm("invalid-hash") expect ValueError: discard detectHashAlgorithm("abc") # Too short suite "Hash String Parsing": test "parse BLAKE2b hash strings": let (alg1, digest1) = parseHashString("blake2b-abc123def456") check alg1 == HashBlake2b check digest1 == "abc123def456" # Test with a valid 128-character BLAKE2b hash (without prefix) let validBlake2bHash = "a".repeat(128) let (alg2, digest2) = parseHashString(validBlake2bHash) check alg2 == HashBlake2b check digest2 == validBlake2bHash test "format hash strings": check formatHashString(HashBlake2b, "abc123") == "blake2b-abc123" check formatHashString(HashBlake3, "def456") == "blake3-def456" check formatHashString(HashSha256, "789abc") == "sha256-789abc" suite "Golden Test Vectors": # These are known-good test vectors for BLAKE2b-512 const BLAKE2B_GOLDEN_VECTORS = [ ("", "blake2b-786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce"), ("a", "blake2b-333fcb4ee1aa7c115355ec66ceac917c8bfd815bf7587d325aec1864edd24e34d5abe2c6b1b5ee3face62fed78dbef802f2a85cb91d455a8f5249d330853cb3c"), ("abc", "blake2b-ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923"), ("The quick brown fox jumps over the lazy dog", "blake2b-a8add4bdddfd93e4877d2746e62817b116364a1fa7bc148d95090bc7333b3673f82401cf7aa2e4cb1ecd90296e3f14cb5413f8ed77be73045b13914cdcd6a918"), ("NimPak", "blake2b-5274ed32fc344107b69805a6e77c9ab0dfc4c9b83d94a400bce3cc70d11f9d5c06738c319dace5b81859d56667550744b240f52d493dfc2e8afbbe76b00df1ae"), ("1234567890".repeat(10), "blake2b-800bb78cd4da18995c8074713bb6743cd94b2b6490a693fe4000ed00833b88b7b474d94af9cfed246b1b4ce1935a76154d7ea7c410493557741d18ec3a08da75") ] # SHA256 test vectors for legacy support const SHA256_GOLDEN_VECTORS = [ ("", "sha256-e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), ("a", "sha256-ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb"), ("abc", "sha256-ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"), ("The quick brown fox jumps over the lazy dog", "sha256-d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"), ("NimPak", "sha256-1ba47abd74914c637330282fad2a2eb807970548b532a552128d6de4af131cbf"), ("1234567890".repeat(10), "sha256-b20e12a7bcf7a0bcc5150265aab9c40b1d673781c143a73be76232d81e6038ec") ] test "BLAKE2b golden vectors": for (input, expectedHash) in BLAKE2B_GOLDEN_VECTORS: let result = computeStringHash(input, HashBlake2b) let formattedHash = formatHashString(result.algorithm, result.digest) check formattedHash == expectedHash check result.verified == false # Not verified yet # Test verification let verifyResult = verifyStringHash(input, expectedHash) check verifyResult.verified == true check verifyResult.digest == result.digest test "SHA256 golden vectors": for (input, expectedHash) in SHA256_GOLDEN_VECTORS: let result = computeStringHash(input, HashSha256) let formattedHash = formatHashString(result.algorithm, result.digest) check formattedHash == expectedHash check result.verified == false # Not verified yet # Test verification let verifyResult = verifyStringHash(input, expectedHash) check verifyResult.verified == true check verifyResult.digest == result.digest suite "Streaming Hash Computation": test "streaming hasher initialization": let hasher = initStreamingHasher(HashBlake2b) check hasher.algorithm == HashBlake2b check hasher.bytesProcessed == 0 test "streaming hash with multiple updates": var hasher = initStreamingHasher(HashBlake2b) hasher.update("The quick ") hasher.update("brown fox ") hasher.update("jumps over ") hasher.update("the lazy dog") let result = hasher.finalize() check hasher.bytesProcessed == 43 # Length of the string # Should match the golden vector for the full string let expectedResult = computeStringHash("The quick brown fox jumps over the lazy dog", HashBlake2b) check result.digest == expectedResult.digest test "streaming hash with byte arrays": var hasher = initStreamingHasher(HashBlake2b) let dataStr = "test data" hasher.update(dataStr.toOpenArrayByte(0, dataStr.high)) let result = hasher.finalize() check hasher.bytesProcessed == 9 # Should match direct string hash let expectedResult = computeStringHash("test data", HashBlake2b) check result.digest == expectedResult.digest suite "File Hash Computation": test "compute hash of existing file": # Create a temporary file with known content let (tempFile, tempPath) = createTempFile("nimpak_test_", ".txt") tempFile.write("Hello, NimPak!") tempFile.close() try: let result = computeFileHash(tempPath, HashBlake2b) check result.algorithm == HashBlake2b check result.digest.len == 128 # BLAKE2b-512 hex length check result.computeTime >= 0.0 # Verify the hash let expectedHash = formatHashString(result.algorithm, result.digest) let verifyResult = verifyFileHash(tempPath, expectedHash) check verifyResult.verified == true finally: removeFile(tempPath) test "file not found error": expect IOError: discard computeFileHash("/nonexistent/file.txt") test "large file handling": # Create a larger temporary file (1MB) let (tempFile, tempPath) = createTempFile("nimpak_large_", ".bin") let chunkData = "A".repeat(1024) # 1KB chunk for i in 0..<1024: # Write 1MB total tempFile.write(chunkData) tempFile.close() try: let result = computeFileHash(tempPath, HashBlake2b) check result.algorithm == HashBlake2b check result.digest.len == 128 check result.computeTime > 0.0 # Check that we can format the hash rate let stats = getHashStatistics(result, 1024 * 1024) check "MB/s" in stats or "KB/s" in stats or "GB/s" in stats finally: removeFile(tempPath) suite "Stream Hash Computation": test "compute hash from string stream": let data = "Stream test data" var stream = newStringStream(data) let result = computeStreamHash(stream, HashBlake2b) check result.algorithm == HashBlake2b # Should match direct string hash let expectedResult = computeStringHash(data, HashBlake2b) check result.digest == expectedResult.digest stream.close() suite "Hash Verification": test "successful hash verification": let data = "Verification test" let hashResult = computeStringHash(data, HashBlake2b) let hashString = formatHashString(hashResult.algorithm, hashResult.digest) let verifyResult = verifyStringHash(data, hashString) check verifyResult.verified == true check verifyResult.digest == hashResult.digest test "failed hash verification": let data = "Verification test" let wrongHash = "blake2b-" & "0".repeat(128) expect HashVerificationError: discard verifyStringHash(data, wrongHash) test "file hash verification": let (tempFile, tempPath) = createTempFile("nimpak_verify_", ".txt") tempFile.write("File verification test") tempFile.close() try: # Compute correct hash let hashResult = computeFileHash(tempPath, HashBlake2b) let hashString = formatHashString(hashResult.algorithm, hashResult.digest) # Verify should succeed let verifyResult = verifyFileHash(tempPath, hashString) check verifyResult.verified == true # Wrong hash should fail let wrongHash = "blake2b-" & "1".repeat(128) expect HashVerificationError: discard verifyFileHash(tempPath, wrongHash) finally: removeFile(tempPath) suite "Batch Verification": test "verify multiple files": # Create multiple temporary files var entries: seq[FileHashEntry] = @[] var tempPaths: seq[string] = @[] for i in 0..<3: let (tempFile, tempPath) = createTempFile("nimpak_batch_", ".txt") tempFile.write($"File content " & $i) tempFile.close() tempPaths.add(tempPath) # Compute correct hash let hashResult = computeFileHash(tempPath, HashBlake2b) let hashString = formatHashString(hashResult.algorithm, hashResult.digest) entries.add(FileHashEntry( filePath: tempPath, expectedHash: hashString, result: none(HashResult), error: "" )) try: let (verified, failed) = verifyMultipleFiles(entries) check verified == 3 check failed == 0 for entry in entries: check entry.result.isSome() check entry.result.get().verified == true check entry.error == "" finally: for path in tempPaths: removeFile(path) test "batch verification with failures": var entries: seq[FileHashEntry] = @[ FileHashEntry( filePath: "/nonexistent/file.txt", expectedHash: "blake2b-" & "0".repeat(128), result: none(HashResult), error: "" ) ] let (verified, failed) = verifyMultipleFiles(entries) check verified == 0 check failed == 1 check entries[0].result.isNone() check entries[0].error != "" suite "Performance and Statistics": test "hash rate formatting": check formatHashRate(1000, 1.0) == "1.0 KB/s" check formatHashRate(1_000_000, 1.0) == "1.0 MB/s" check formatHashRate(1_000_000_000, 1.0) == "1.0 GB/s" check formatHashRate(500, 1.0) == "500 B/s" check formatHashRate(1000, 0.0) == "N/A" test "hash statistics formatting": let result = HashResult( algorithm: HashBlake2b, digest: "abc123", verified: true, computeTime: 0.5 ) let stats = getHashStatistics(result, 1000) check "blake2b" in stats.toLower() check "0.500s" in stats check "KB/s" in stats suite "Algorithm Fallback and Support": test "algorithm fallback": check getFallbackAlgorithm(HashBlake3) == HashBlake2b check getFallbackAlgorithm(HashBlake2b) == HashBlake2b check getFallbackAlgorithm(HashSha256) == HashSha256 test "algorithm support detection": check isAlgorithmSupported(HashBlake2b) == true check isAlgorithmSupported(HashSha256) == true check isAlgorithmSupported(HashBlake3) == false # Not natively supported test "BLAKE3 fallback behavior": # BLAKE3 should fallback to BLAKE2b let result = computeStringHash("test", HashBlake3) check result.algorithm == HashBlake2b # Should have fallen back # Compare with direct BLAKE2b computation let blake2bResult = computeStringHash("test", HashBlake2b) check result.digest == blake2bResult.digest suite "Large File Handling": test "large file hash computation": # Create a file larger than the threshold for testing let (tempFile, tempPath) = createTempFile("nimpak_large_", ".bin") let chunkData = "X".repeat(1024) # 1KB chunk # Write 2MB total (smaller than 1GB threshold for testing) for i in 0..<2048: tempFile.write(chunkData) tempFile.close() try: var progressCalls = 0 let result = computeLargeFileHash(tempPath, HashBlake2b) do (processed: int64, total: int64): inc progressCalls check processed <= total check processed >= 0 check result.algorithm == HashBlake2b check result.digest.len == 128 check result.computeTime > 0.0 check progressCalls > 0 # Progress callback should have been called # Verify the result matches regular file hash let regularResult = computeFileHash(tempPath, HashBlake2b) check result.digest == regularResult.digest finally: removeFile(tempPath) test "chunk size optimization": # Test that large files use larger chunk sizes let (tempFile, tempPath) = createTempFile("nimpak_chunk_", ".bin") let smallData = "small" tempFile.write(smallData) tempFile.close() try: let result = computeFileHash(tempPath, HashBlake2b) check result.algorithm == HashBlake2b check result.digest.len == 128 finally: removeFile(tempPath) suite "Utility Functions": test "hash string validation": check isValidHashString("blake2b-" & "a".repeat(128)) == true check isValidHashString("sha256-" & "b".repeat(64)) == true check isValidHashString("invalid-hash") == false check isValidHashString("") == false test "preferred algorithm": check getPreferredHashAlgorithm() == HashBlake2b test "supported algorithms": let supported = getSupportedAlgorithms() check HashBlake2b in supported check HashSha256 in supported # BLAKE3 not yet natively supported check HashBlake3 notin supported suite "Edge Cases and Error Handling": test "empty string hash": let result = computeStringHash("", HashBlake2b) check result.digest.len == 128 check result.algorithm == HashBlake2b test "very long string hash": let longString = "A".repeat(100_000) # 100KB string let result = computeStringHash(longString, HashBlake2b) check result.digest.len == 128 check result.computeTime >= 0.0 test "unsupported algorithm error": # BLAKE3 falls back to BLAKE2b, so no error let hasher1 = initStreamingHasher(HashBlake3) check hasher1.algorithm == HashBlake2b # SHA256 is supported let hasher2 = initStreamingHasher(HashSha256) check hasher2.algorithm == HashSha256 test "hash verification error details": try: discard verifyStringHash("test", "blake2b-wrong") fail() except HashVerificationError as e: check e.algorithm == HashBlake2b check "wrong" in e.expectedHash check e.actualHash != e.expectedHash