// 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)); } }