// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/**
 * @title AerePQCVerifier — on-chain post-quantum signature verification
 * @notice Verifies a WOTS+ (Winternitz One-Time Signature) — a HASH-BASED
 *         signature that is secure against quantum computers (its security rests
 *         only on the pre-image resistance of keccak256, which Shor's algorithm
 *         does not break, unlike ECDSA). This lets AERE contracts natively
 *         verify a post-quantum signature TODAY, without touching consensus.
 *
 *         Scheme (n=32, w=16): the 256-bit message hash is split into 64 base-16
 *         digits plus a 3-digit checksum (67 chains total). Each signature
 *         element is a keccak hash-chain value; verification completes each
 *         chain to its end and checks that the hash of all chain-ends equals the
 *         committed public key. The checksum makes forgery infeasible: raising
 *         any message digit lowers the checksum, which would require inverting
 *         keccak.
 *
 *         WOTS+ is ONE-TIME (each key signs one message); it is the building
 *         block of the stateful many-time schemes XMSS / SPHINCS+. A public
 *         AereXmssVerifier (WOTS+ leaf + Merkle authentication path to a long-
 *         term root) is the natural extension.
 */
contract AerePQCVerifier {
    uint256 public constant W = 16;      // Winternitz parameter
    uint256 public constant LEN1 = 64;   // message digits (256 bits / 4)
    uint256 public constant LEN2 = 3;    // checksum digits
    uint256 public constant LEN = 67;    // total chains

    /// @notice Derive the 67 base-16 digits (message + checksum) from a hash.
    function digits(bytes32 m) public pure returns (uint8[67] memory d) {
        uint256 csum = 0;
        for (uint256 i = 0; i < 32; i++) {
            uint8 b = uint8(m[i]);
            uint8 hi = b >> 4;
            uint8 lo = b & 0x0f;
            d[2 * i] = hi;
            d[2 * i + 1] = lo;
            csum += (15 - uint256(hi)) + (15 - uint256(lo));
        }
        // checksum (max 64*15 = 960 < 16^3) as 3 base-16 digits, MSB first
        d[64] = uint8((csum >> 8) & 0x0f);
        d[65] = uint8((csum >> 4) & 0x0f);
        d[66] = uint8(csum & 0x0f);
    }

    function _chain(bytes32 x, uint256 count) internal pure returns (bytes32) {
        for (uint256 i = 0; i < count; i++) {
            x = keccak256(abi.encodePacked(x));
        }
        return x;
    }

    /**
     * @notice Verify a WOTS+ signature.
     * @param msgHash      keccak256 of the message being signed
     * @param sig          the 67 hash-chain signature elements
     * @param compressedPK keccak256 of the 67 public-key chain-ends
     * @return ok true iff the signature is valid for compressedPK
     */
    function verify(bytes32 msgHash, bytes32[67] calldata sig, bytes32 compressedPK) public pure returns (bool ok) {
        uint8[67] memory d = digits(msgHash);
        bytes32[67] memory ends;
        for (uint256 i = 0; i < LEN; i++) {
            // complete each chain from the signature to the public-key end
            ends[i] = _chain(sig[i], 15 - uint256(d[i]));
        }
        return keccak256(abi.encodePacked(ends)) == compressedPK;
    }

    /// @notice Compute a WOTS+ public key from a full private key (test/helper).
    function derivePublicKey(bytes32[67] calldata sk) external pure returns (bytes32 compressedPK) {
        bytes32[67] memory ends;
        for (uint256 i = 0; i < LEN; i++) {
            ends[i] = _chain(sk[i], 15);
        }
        return keccak256(abi.encodePacked(ends));
    }
}
