64 hex chars — large enough that guessing is not a realistic attack vector even without rate limiting, which this feature also has as defense in depth (see Draft_Service::is_rate_limited()). */ const BYTES = 32; /** A fresh, cryptographically random token. Callers store only hash() of this — never the return value itself. */ public static function generate() { return bin2hex( random_bytes( self::BYTES ) ); } /** The value actually persisted to Draft::token_hash. Deterministic (same token always hashes the same way) but one-way — the hash alone can't be turned back into a usable token. */ public static function hash( $token ) { return hash( 'sha256', (string) $token ); } /** Constant-time comparison — a plain `===`/`==` on hashes would leak timing information about how many leading bytes matched, the same reason WordPress core's own key-verification functions use hash_equals(). */ public static function matches( $token, $stored_hash ) { if ( ! is_string( $token ) || '' === $token || ! is_string( $stored_hash ) || '' === $stored_hash ) { return false; } return hash_equals( $stored_hash, self::hash( $token ) ); } }