diff --git a/Makefile b/Makefile
index bf8a8afb248251607cd53af3d666667e5e10655e..529b80e141bd5e11bca40138e60cab25f6600b9c 100644
--- a/Makefile
+++ b/Makefile
@@ -12,7 +12,7 @@ ci-rector: vendor ## Check all files using Rector (CI/CD)
 	vendor/bin/rector process --ansi --dry-run
 
 ci-mu: vendor ## Mutation tests (CI/CD)
-	vendor/bin/infection --logger-github --git-diff-filter=AM -s --threads=$(nproc) --min-msi=70 --min-covered-msi=50 --test-framework-options="--exclude-group=Performance"
+	vendor/bin/infection --logger-github -s --threads=$$(nproc) --min-msi=70 --min-covered-msi=50 --test-framework-options="--exclude-group=Performance"
 
 ########################
 #      Everyday        #
@@ -44,7 +44,7 @@ rector: vendor ## Check all files using Rector
 ########################
 
 mu: vendor ## Mutation tests
-	vendor/bin/infection -s --threads=$(nproc) --min-msi=70 --min-covered-msi=50 --test-framework-options="--exclude-group=Performance"
+	vendor/bin/infection -s --threads=$$(nproc) --min-msi=70 --min-covered-msi=50 --test-framework-options="--exclude-group=Performance"
 
 cc: vendor ## Show test coverage rates (HTML)
 	vendor/bin/phpunit --coverage-html ./build
diff --git a/composer.json b/composer.json
index 7102414e3304fc99324c9528d52cdefdbdb0da91..bff24fa1580d2ee1036c4afe2b257035724fd701 100644
--- a/composer.json
+++ b/composer.json
@@ -18,23 +18,19 @@
     "require": {
         "php": "^8.1",
         "ext-mbstring": "*",
-        "paragonie/constant_time_encoding": "^2.0",
-        "beberlei/assert": "^3.0",
-        "thecodingmachine/safe": "^1.0|^2.0"
+        "paragonie/constant_time_encoding": "^2.0"
     },
     "require-dev": {
         "ekino/phpstan-banned-code": "^1.0",
         "infection/infection": "^0.26",
         "phpstan/phpstan": "^1.0",
-        "phpstan/phpstan-beberlei-assert": "^1.0",
         "phpstan/phpstan-deprecation-rules": "^1.0",
         "phpstan/phpstan-phpunit": "^1.0",
         "phpstan/phpstan-strict-rules": "^1.0",
         "phpunit/phpunit": "^9.5",
-        "rector/rector": "^0.13",
+        "rector/rector": "^0.14",
         "symfony/phpunit-bridge": "^6.0",
-        "symplify/easy-coding-standard": "^11.0",
-        "thecodingmachine/phpstan-safe-rule": "^1.0"
+        "symplify/easy-coding-standard": "^11.0"
     },
     "autoload": {
         "psr-4": { "OTPHP\\": "src/" }
diff --git a/phpstan.neon b/phpstan.neon
index 53210dd7205f727c3b6200e9fb594382a07a47ab..9cb0a821528c05e82c1f29abc1fcb84e9e4ff5c2 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -10,7 +10,5 @@ includes:
     - vendor/phpstan/phpstan-strict-rules/rules.neon
     - vendor/phpstan/phpstan-phpunit/extension.neon
     - vendor/phpstan/phpstan-deprecation-rules/rules.neon
-    - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon
-    - vendor/phpstan/phpstan-beberlei-assert/extension.neon
     - vendor/phpstan/phpstan-phpunit/rules.neon
     - vendor/ekino/phpstan-banned-code/extension.neon
diff --git a/src/Factory.php b/src/Factory.php
index b904c399249f53d08da5d3cbbf7f02e18b34b7e3..dc98c76eee442e7b93475491dcf0a3f78212db94 100644
--- a/src/Factory.php
+++ b/src/Factory.php
@@ -4,7 +4,6 @@ declare(strict_types=1);
 
 namespace OTPHP;
 
-use Assert\Assertion;
 use function count;
 use InvalidArgumentException;
 use Throwable;
@@ -20,7 +19,7 @@ final class Factory implements FactoryInterface
     {
         try {
             $parsed_url = Url::fromString($uri);
-            Assertion::eq('otpauth', $parsed_url->getScheme());
+            $parsed_url->getScheme() === 'otpauth' || throw new InvalidArgumentException('Invalid scheme.');
         } catch (Throwable $throwable) {
             throw new InvalidArgumentException('Not a valid OTP provisioning URI', $throwable->getCode(), $throwable);
         }
@@ -51,7 +50,9 @@ final class Factory implements FactoryInterface
         }
 
         if ($otp->getIssuer() !== null) {
-            Assertion::eq($result[0], $otp->getIssuer(), 'Invalid OTP: invalid issuer in parameter');
+            $result[0] === $otp->getIssuer() || throw new InvalidArgumentException(
+                'Invalid OTP: invalid issuer in parameter'
+            );
             $otp->setIssuerIncludedAsParameter(true);
         }
         $otp->setIssuer($result[0]);
diff --git a/src/HOTP.php b/src/HOTP.php
index 98a3ac8d6f15003bfaa4370680d2fbcc2bb455db..5336a605c8f038e18e86c8954069125202acd048 100644
--- a/src/HOTP.php
+++ b/src/HOTP.php
@@ -4,7 +4,8 @@ declare(strict_types=1);
 
 namespace OTPHP;
 
-use Assert\Assertion;
+use InvalidArgumentException;
+use function is_int;
 
 /**
  * @see \OTPHP\Test\HOTPTest
@@ -29,7 +30,7 @@ final class HOTP extends OTP implements HOTPInterface
     public function getCounter(): int
     {
         $value = $this->getParameter('counter');
-        Assertion::integer($value, 'Invalid "counter" parameter.');
+        is_int($value) || throw new InvalidArgumentException('Invalid "counter" parameter.');
 
         return $value;
     }
@@ -46,7 +47,7 @@ final class HOTP extends OTP implements HOTPInterface
      */
     public function verify(string $otp, null|int $counter = null, null|int $window = null): bool
     {
-        Assertion::greaterOrEqualThan($counter, 0, 'The counter must be at least 0.');
+        $counter >= 0 || throw new InvalidArgumentException('The counter must be at least 0.');
 
         if ($counter === null) {
             $counter = $this->getCounter();
@@ -69,7 +70,7 @@ final class HOTP extends OTP implements HOTPInterface
     {
         return [...parent::getParameterMap(), ...[
             'counter' => static function ($value): int {
-                Assertion::greaterOrEqualThan((int) $value, 0, 'Counter must be at least 0.');
+                (int) $value >= 0 || throw new InvalidArgumentException('Counter must be at least 0.');
 
                 return (int) $value;
             },
diff --git a/src/OTP.php b/src/OTP.php
index bd4826f612f444fafc98644ee34a9196f5fbdc45..f590668824995c6118c80d7b402fc44e5799da67 100644
--- a/src/OTP.php
+++ b/src/OTP.php
@@ -4,13 +4,13 @@ declare(strict_types=1);
 
 namespace OTPHP;
 
-use Assert\Assertion;
 use function chr;
 use function count;
 use Exception;
+use InvalidArgumentException;
+use function is_string;
 use ParagonIE\ConstantTime\Base32;
 use RuntimeException;
-use function Safe\unpack;
 use const STR_PAD_LEFT;
 
 abstract class OTP implements OTPInterface
@@ -42,11 +42,12 @@ abstract class OTP implements OTPInterface
     protected function generateOTP(int $input): string
     {
         $hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret(), true);
-
-        $hmac = array_values(unpack('C*', $hash));
+        $unpacked = unpack('C*', $hash);
+        $unpacked !== false || throw new InvalidArgumentException('Invalid data.');
+        $hmac = array_values($unpacked);
 
         $offset = ($hmac[count($hmac) - 1] & 0xF);
-        $code = ($hmac[$offset + 0] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
+        $code = ($hmac[$offset] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
         $otp = $code % (10 ** $this->getDigits());
 
         return str_pad((string) $otp, $this->getDigits(), '0', STR_PAD_LEFT);
@@ -76,8 +77,8 @@ abstract class OTP implements OTPInterface
     protected function generateURI(string $type, array $options): string
     {
         $label = $this->getLabel();
-        Assertion::string($label, 'The label is not set.');
-        Assertion::false($this->hasColon($label), 'Label must not contain a colon.');
+        is_string($label) || throw new InvalidArgumentException('The label is not set.');
+        $this->hasColon($label) === false || throw new InvalidArgumentException('Label must not contain a colon.');
         $options = [...$options, ...$this->getParameters()];
         $this->filterOptions($options);
         $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options));
diff --git a/src/ParameterTrait.php b/src/ParameterTrait.php
index ddefb983df3a925eb63e2c0cd414ceffa44ac786..cef50f3e53a503fee583637292413b68870b98bf 100644
--- a/src/ParameterTrait.php
+++ b/src/ParameterTrait.php
@@ -5,8 +5,10 @@ declare(strict_types=1);
 namespace OTPHP;
 
 use function array_key_exists;
-use Assert\Assertion;
+use function in_array;
 use InvalidArgumentException;
+use function is_int;
+use function is_string;
 use ParagonIE\ConstantTime\Base32;
 
 trait ParameterTrait
@@ -39,7 +41,7 @@ trait ParameterTrait
     public function getSecret(): string
     {
         $value = $this->getParameter('secret');
-        Assertion::string($value, 'Invalid "secret" parameter.');
+        is_string($value) || throw new InvalidArgumentException('Invalid "secret" parameter.');
 
         return $value;
     }
@@ -77,7 +79,7 @@ trait ParameterTrait
     public function getDigits(): int
     {
         $value = $this->getParameter('digits');
-        Assertion::integer($value, 'Invalid "digits" parameter.');
+        is_int($value) || throw new InvalidArgumentException('Invalid "digits" parameter.');
 
         return $value;
     }
@@ -85,7 +87,7 @@ trait ParameterTrait
     public function getDigest(): string
     {
         $value = $this->getParameter('algorithm');
-        Assertion::string($value, 'Invalid "algorithm" parameter.');
+        is_string($value) || throw new InvalidArgumentException('Invalid "algorithm" parameter.');
 
         return $value;
     }
@@ -127,7 +129,9 @@ trait ParameterTrait
     {
         return [
             'label' => function ($value) {
-                Assertion::false($this->hasColon($value), 'Label must not contain a colon.');
+                $this->hasColon($value) === false || throw new InvalidArgumentException(
+                    'Label must not contain a colon.'
+                );
 
                 return $value;
             },
@@ -140,17 +144,22 @@ trait ParameterTrait
             },
             'algorithm' => static function ($value): string {
                 $value = mb_strtolower($value);
-                Assertion::inArray($value, hash_algos(), sprintf('The "%s" digest is not supported.', $value));
+                in_array($value, hash_algos(), true) || throw new InvalidArgumentException(sprintf(
+                    'The "%s" digest is not supported.',
+                    $value
+                ));
 
                 return $value;
             },
             'digits' => static function ($value): int {
-                Assertion::greaterThan($value, 0, 'Digits must be at least 1.');
+                $value > 0 || throw new InvalidArgumentException('Digits must be at least 1.');
 
                 return (int) $value;
             },
             'issuer' => function ($value) {
-                Assertion::false($this->hasColon($value), 'Issuer must not contain a colon.');
+                $this->hasColon($value) === false || throw new InvalidArgumentException(
+                    'Issuer must not contain a colon.'
+                );
 
                 return $value;
             },
diff --git a/src/TOTP.php b/src/TOTP.php
index 881a1afefb1dde62ea61bafb3b10f71957b9b766..2e673675e9c15f29f2a4b52ffa480301f258b96a 100644
--- a/src/TOTP.php
+++ b/src/TOTP.php
@@ -4,7 +4,8 @@ declare(strict_types=1);
 
 namespace OTPHP;
 
-use Assert\Assertion;
+use InvalidArgumentException;
+use function is_int;
 
 /**
  * @see \OTPHP\Test\TOTPTest
@@ -31,7 +32,7 @@ final class TOTP extends OTP implements TOTPInterface
     public function getPeriod(): int
     {
         $value = $this->getParameter('period');
-        Assertion::integer($value, 'Invalid "period" parameter.');
+        is_int($value) || throw new InvalidArgumentException('Invalid "period" parameter.');
 
         return $value;
     }
@@ -39,7 +40,7 @@ final class TOTP extends OTP implements TOTPInterface
     public function getEpoch(): int
     {
         $value = $this->getParameter('epoch');
-        Assertion::integer($value, 'Invalid "epoch" parameter.');
+        is_int($value) || throw new InvalidArgumentException('Invalid "epoch" parameter.');
 
         return $value;
     }
@@ -68,14 +69,16 @@ final class TOTP extends OTP implements TOTPInterface
     public function verify(string $otp, null|int $timestamp = null, null|int $leeway = null): bool
     {
         $timestamp ??= time();
-        Assertion::greaterOrEqualThan($timestamp, 0, 'Timestamp must be at least 0.');
+        $timestamp >= 0 || throw new InvalidArgumentException('Timestamp must be at least 0.');
 
         if ($leeway === null) {
             return $this->compareOTP($this->at($timestamp), $otp);
         }
 
         $leeway = abs($leeway);
-        Assertion::lessThan($leeway, $this->getPeriod(), 'The leeway must be lower than the TOTP period');
+        $leeway < $this->getPeriod() || throw new InvalidArgumentException(
+            'The leeway must be lower than the TOTP period'
+        );
 
         return $this->compareOTP($this->at($timestamp - $leeway), $otp)
             || $this->compareOTP($this->at($timestamp), $otp)
@@ -110,12 +113,14 @@ final class TOTP extends OTP implements TOTPInterface
             parent::getParameterMap(),
             [
                 'period' => static function ($value): int {
-                    Assertion::greaterThan((int) $value, 0, 'Period must be at least 1.');
+                    (int) $value > 0 || throw new InvalidArgumentException('Period must be at least 1.');
 
                     return (int) $value;
                 },
                 'epoch' => static function ($value): int {
-                    Assertion::greaterOrEqualThan((int) $value, 0, 'Epoch must be greater than or equal to 0.');
+                    (int) $value >= 0 || throw new InvalidArgumentException(
+                        'Epoch must be greater than or equal to 0.'
+                    );
 
                     return (int) $value;
                 },
diff --git a/src/Url.php b/src/Url.php
index 81cebfc5674e22913d78ba477ce55cd32ef4a2e5..f3dce71f83c966b467d56caebafbd14344f38d4e 100644
--- a/src/Url.php
+++ b/src/Url.php
@@ -4,8 +4,9 @@ declare(strict_types=1);
 
 namespace OTPHP;
 
-use Assert\Assertion;
-use function Safe\parse_url;
+use function array_key_exists;
+use InvalidArgumentException;
+use function is_string;
 
 /**
  * @internal
@@ -53,19 +54,26 @@ final class Url
     public static function fromString(string $uri): self
     {
         $parsed_url = parse_url($uri);
-        Assertion::isArray($parsed_url, 'Not a valid OTP provisioning URI');
+        $parsed_url !== false || throw new InvalidArgumentException('Invalid URI.');
         foreach (['scheme', 'host', 'path', 'query'] as $key) {
-            Assertion::keyExists($parsed_url, $key, 'Not a valid OTP provisioning URI');
-            Assertion::string($parsed_url[$key], 'Not a valid OTP provisioning URI');
+            array_key_exists($key, $parsed_url) || throw new InvalidArgumentException(
+                'Not a valid OTP provisioning URI'
+            );
+            is_string($parsed_url[$key]) || throw new InvalidArgumentException('Not a valid OTP provisioning URI');
         }
-        $scheme = $parsed_url['scheme'];
-        Assertion::eq('otpauth', $scheme, 'Not a valid OTP provisioning URI');
-        $host = $parsed_url['host'];
-        $path = $parsed_url['path'];
-        $query = $parsed_url['query'];
+        $scheme = $parsed_url['scheme'] ?? null;
+        $host = $parsed_url['host'] ?? null;
+        $path = $parsed_url['path'] ?? null;
+        $query = $parsed_url['query'] ?? null;
+        $scheme === 'otpauth' || throw new InvalidArgumentException('Not a valid OTP provisioning URI');
+        is_string($host) || throw new InvalidArgumentException('Invalid URI.');
+        is_string($path) || throw new InvalidArgumentException('Invalid URI.');
+        is_string($query) || throw new InvalidArgumentException('Invalid URI.');
         $parsedQuery = [];
-        parse_str((string) $query, $parsedQuery);
-        Assertion::keyExists($parsedQuery, 'secret', 'Not a valid OTP provisioning URI');
+        parse_str($query, $parsedQuery);
+        array_key_exists('secret', $parsedQuery) || throw new InvalidArgumentException(
+            'Not a valid OTP provisioning URI'
+        );
         $secret = $parsedQuery['secret'];
         unset($parsedQuery['secret']);
 
diff --git a/tests/HOTPTest.php b/tests/HOTPTest.php
index 4026de20cfc175577d5878531260fc8c9e9294d3..b0acca0bf723d1bd1164d06da4768208d194c0a9 100644
--- a/tests/HOTPTest.php
+++ b/tests/HOTPTest.php
@@ -4,7 +4,6 @@ declare(strict_types=1);
 
 namespace OTPHP\Test;
 
-use Assert\Assertion;
 use InvalidArgumentException;
 use OTPHP\HOTP;
 use PHPUnit\Framework\TestCase;
@@ -188,7 +187,6 @@ final class HOTPTest extends TestCase
         $otp = HOTP::create($secret, $counter, $digest, $digits);
         $otp->setLabel($label);
         $otp->setIssuer($issuer);
-        Assertion::isInstanceOf($otp, HOTP::class);
 
         return $otp;
     }
diff --git a/tests/TOTPTest.php b/tests/TOTPTest.php
index 01a2bdfbd2accf0fc7d2a5575b2cab9d45ac484e..922deb01557543965dd56ab0f7ea9600530588f4 100644
--- a/tests/TOTPTest.php
+++ b/tests/TOTPTest.php
@@ -4,7 +4,6 @@ declare(strict_types=1);
 
 namespace OTPHP\Test;
 
-use Assert\Assertion;
 use InvalidArgumentException;
 use OTPHP\TOTP;
 use OTPHP\TOTPInterface;
@@ -406,7 +405,6 @@ final class TOTPTest extends TestCase
         $otp = TOTP::create($secret, $period, $digest, $digits, $epoch);
         $otp->setLabel($label);
         $otp->setIssuer($issuer);
-        Assertion::isInstanceOf($otp, TOTP::class);
 
         return $otp;
     }