import CryptoKit import XCTest @testable import LChatCore // 🤖 The hashcash solver mirrors home's pow.go: SHA-256(challenge ":" nonce) // must clear the difficulty's leading zero bits. These pin the two pure // pieces (bit counting, solve/verify) against the same cases as the Go tests, // so the native solver and the server can't drift. final class PowSolverTests: XCTestCase { func testLeadingZeroBits() { XCTAssertEqual(HomeClient.leadingZeroBits([0x80]), 0) XCTAssertEqual(HomeClient.leadingZeroBits([0x01]), 7) XCTAssertEqual(HomeClient.leadingZeroBits([0x00, 0xFF]), 8) XCTAssertEqual(HomeClient.leadingZeroBits([0x00, 0x0F]), 12) XCTAssertEqual(HomeClient.leadingZeroBits([0x00, 0x00]), 16) } func testSolveAndVerify() { // 8 bits ≈ 256 hashes — instant. Grind exactly like the client does, // then check the found nonce verifies against an independent digest. let challenge = "test-challenge" var nonce = 0 while !HomeClient.powSolves(challenge: challenge, nonce: String(nonce), bits: 8) { nonce += 1 XCTAssertLessThan(nonce, 1_000_000, "8-bit solution should be found quickly") } let digest = Array(SHA256.hash(data: Data((challenge + ":" + String(nonce)).utf8))) XCTAssertGreaterThanOrEqual(HomeClient.leadingZeroBits(digest), 8) // Bits are a floor: a 8-bit solution rarely clears 32. XCTAssertFalse(HomeClient.powSolves(challenge: challenge, nonce: "definitely-not-it", bits: 32)) // A solution is bound to its challenge text, not portable. XCTAssertTrue(HomeClient.powSolves(challenge: "anything", nonce: "0", bits: 0)) } }