diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c12cca..b93a683 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,25 +19,6 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '7.4' - ini-values: extension=secp256k1.so - - - name: Clone libsecp256k1 repository - uses: GuillaumeFalourd/clone-github-repo-action@v2 - with: - owner: 'bitcoin-core' - repository: 'secp256k1' - - - name: Configure secp256k1 - run: cd secp256k1 && ./autogen.sh && ./configure --enable-experimental --enable-module-{ecdh,recovery} && make && sudo make install && cd .. - - - name: Clone secp256k1-php repository - uses: GuillaumeFalourd/clone-github-repo-action@v2 - with: - owner: 'Bit-Wasp' - repository: 'secp256k1-php' - - - name: Install secp256k1-php - run: cd secp256k1-php/secp256k1 && phpize && ./configure --with-secp256k1 && make && sudo make install && cd .. - uses: actions/checkout@v3 @@ -47,4 +28,4 @@ jobs: - name: Run test suite run: composer run-script test env: - CASPER_PHP_SDK_TEST_NODE_URL: "88.99.100.42:7777" + CASPER_PHP_SDK_TEST_NODE_URL: "95.216.240.135:7777" diff --git a/README.md b/README.md index f9c2fbf..667c0f0 100644 --- a/README.md +++ b/README.md @@ -6,29 +6,6 @@ The PHP SDK allows developers to interact with the Casper Network using PHP. Thi composer require make-software/casper-php-sdk ``` -### IMPORTANT -For using `Secp256K1` keys you need to install and enable [secp256k1-php](https://github.com/Bit-Wasp/secp256k1-php) extension: -```bash -git clone git@github.com:bitcoin-core/secp256k1 && \ - cd secp256k1 && \ - ./autogen.sh && \ - ./configure --enable-experimental --enable-module-{ecdh,recovery} && \ - make && sudo make install && \ - cd ../ -``` -```bash -git clone git@github.com:Bit-Wasp/secp256k1-php && \ - cd secp256k1-php/secp256k1 && \ - phpize && \ - ./configure --with-secp256k1 && \ - make && sudo make install && \ - cd ../../ -``` -Enable extension by adding the following line to your `php.ini` file -``` -extension=secp256k1.so -``` - ## Usage ### Creating RpcClient Create `RpcClient` by passing node url and headers (optional) to constructor diff --git a/composer.json b/composer.json index 869f40e..e6f4a08 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,8 @@ "ext-sodium": "*", "ext-gmp": "*", "ext-mbstring": "*", - "mdanter/ecc": "^1.0", - "ionux/phactor": "1.0.8" + "ionux/phactor": "1.0.8", + "paragonie/ecc": "^2.3" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index ee7a104..4b31f5b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,28 +4,28 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2cb7f13871fdd0e115b525c79fa30ac4", + "content-hash": "46127640d4f29ee3ad8dda2fb0405796", "packages": [ { - "name": "fgrosse/phpasn1", - "version": "v2.3.0", + "name": "genkgo/php-asn1", + "version": "v2.5.0", "source": { "type": "git", - "url": "https://github.com/fgrosse/PHPASN1.git", - "reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e" + "url": "https://github.com/genkgo/php-asn1.git", + "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fgrosse/PHPASN1/zipball/20299033c35f4300eb656e7e8e88cf52d1d6694e", - "reference": "20299033c35f4300eb656e7e8e88cf52d1d6694e", + "url": "https://api.github.com/repos/genkgo/php-asn1/zipball/42060ed45344789fb9f21f9f1864fc47b9e3507b", + "reference": "42060ed45344789fb9f21f9f1864fc47b9e3507b", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": "^7.1 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~6.3", - "satooshi/php-coveralls": "~2.0" + "php-coveralls/php-coveralls": "~2.0", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "suggest": { "ext-bcmath": "BCmath is the fallback extension for big integer calculations", @@ -76,10 +76,10 @@ "x690" ], "support": { - "issues": "https://github.com/fgrosse/PHPASN1/issues", - "source": "https://github.com/fgrosse/PHPASN1/tree/v2.3.0" + "issues": "https://github.com/genkgo/php-asn1/issues", + "source": "https://github.com/genkgo/php-asn1/tree/v2.5.0" }, - "time": "2021-04-24T19:01:55+00:00" + "time": "2022-12-19T11:08:26+00:00" }, { "name": "ionux/phactor", @@ -180,28 +180,34 @@ "time": "2018-04-27T16:49:28+00:00" }, { - "name": "mdanter/ecc", - "version": "v1.0.0", + "name": "paragonie/ecc", + "version": "v2.3.0", "source": { "type": "git", - "url": "https://github.com/phpecc/phpecc.git", - "reference": "34e2eec096bf3dcda814e8f66dd91ae87a2db7cd" + "url": "https://github.com/paragonie/phpecc.git", + "reference": "46ea6f3614aada164e89e87c695e324474c2b5a5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpecc/phpecc/zipball/34e2eec096bf3dcda814e8f66dd91ae87a2db7cd", - "reference": "34e2eec096bf3dcda814e8f66dd91ae87a2db7cd", + "url": "https://api.github.com/repos/paragonie/phpecc/zipball/46ea6f3614aada164e89e87c695e324474c2b5a5", + "reference": "46ea6f3614aada164e89e87c695e324474c2b5a5", "shasum": "" }, "require": { "ext-gmp": "*", - "fgrosse/phpasn1": "^2.0", - "php": "^7.0||^8.0" + "genkgo/php-asn1": "^2", + "paragonie/sodium_compat": "^1|^2", + "php": "^7.1||^8.0" }, "require-dev": { - "phpunit/phpunit": "^6.0||^8.0||^9.0", - "squizlabs/php_codesniffer": "^2.0", - "symfony/yaml": "^2.6|^3.0" + "ext-json": "*", + "phpunit/phpunit": "^6|^7|^8|^9", + "squizlabs/php_codesniffer": "^2|^3", + "symfony/yaml": "^2.6|^3.0|^4", + "vimeo/psalm": "^2|^3|^4|^5" + }, + "suggest": { + "ext-openssl": "(PHP 8.1, OpenSSL 3+) Improved performance, less worries about side-channels" }, "type": "library", "autoload": { @@ -214,21 +220,27 @@ "MIT" ], "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "New Maintainer" + }, { "name": "Matyas Danter", "homepage": "http://matejdanter.com/", - "role": "Author" + "role": "Previous Author" }, { "name": "Thibaud Fabre", "email": "thibaud@aztech.io", "homepage": "http://aztech.io", - "role": "Maintainer" + "role": "Previous Maintainer" }, { "name": "Thomas Kerin", "email": "afk11@users.noreply.github.com", - "role": "Maintainer" + "role": "Previous Maintainer" } ], "description": "PHP Elliptic Curve Cryptography library", @@ -250,10 +262,146 @@ "secp256r1" ], "support": { - "issues": "https://github.com/phpecc/phpecc/issues", - "source": "https://github.com/phpecc/phpecc/tree/v1.0.0" + "issues": "https://github.com/paragonie/phpecc/issues", + "source": "https://github.com/paragonie/phpecc/tree/v2.3.0" + }, + "time": "2024-05-01T14:04:05+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "paragonie/sodium_compat", + "version": "v1.21.1", + "source": { + "type": "git", + "url": "https://github.com/paragonie/sodium_compat.git", + "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bb312875dcdd20680419564fe42ba1d9564b9e37", + "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37", + "shasum": "" + }, + "require": { + "paragonie/random_compat": ">=1", + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9" + }, + "suggest": { + "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", + "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + }, + "type": "library", + "autoload": { + "files": [ + "autoload.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "ISC" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com" + }, + { + "name": "Frank Denis", + "email": "jedisct1@pureftpd.org" + } + ], + "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists", + "keywords": [ + "Authentication", + "BLAKE2b", + "ChaCha20", + "ChaCha20-Poly1305", + "Chapoly", + "Curve25519", + "Ed25519", + "EdDSA", + "Edwards-curve Digital Signature Algorithm", + "Elliptic Curve Diffie-Hellman", + "Poly1305", + "Pure-PHP cryptography", + "RFC 7748", + "RFC 8032", + "Salpoly", + "Salsa20", + "X25519", + "XChaCha20-Poly1305", + "XSalsa20-Poly1305", + "Xchacha20", + "Xsalsa20", + "aead", + "cryptography", + "ecdh", + "elliptic curve", + "elliptic curve cryptography", + "encryption", + "libsodium", + "php", + "public-key cryptography", + "secret-key cryptography", + "side-channel resistant" + ], + "support": { + "issues": "https://github.com/paragonie/sodium_compat/issues", + "source": "https://github.com/paragonie/sodium_compat/tree/v1.21.1" }, - "time": "2021-01-16T19:42:14+00:00" + "time": "2024-04-22T22:05:04+00:00" } ], "packages-dev": [ @@ -2366,5 +2514,5 @@ "ext-mbstring": "*" }, "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Util/Crypto/AsymmetricKey.php b/src/Util/Crypto/AsymmetricKey.php index ee86068..e22ccb8 100644 --- a/src/Util/Crypto/AsymmetricKey.php +++ b/src/Util/Crypto/AsymmetricKey.php @@ -78,7 +78,7 @@ abstract public function exportPublicKeyInPem(): string; abstract public function exportPrivateKeyInPem(): string; /** - * Sign the message and retrun hex-encoded signature + * Sign the message and return hex-encoded signature * * @param string $message * @return string diff --git a/src/Util/Crypto/Secp256K1Key.php b/src/Util/Crypto/Secp256K1Key.php index 3c3a5fc..fe07af2 100644 --- a/src/Util/Crypto/Secp256K1Key.php +++ b/src/Util/Crypto/Secp256K1Key.php @@ -5,31 +5,45 @@ use Casper\Util\ByteUtil; use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface; +use Mdanter\Ecc\Crypto\Signature\Signature; +use Mdanter\Ecc\Crypto\Signature\SignatureInterface; +use Mdanter\Ecc\Crypto\Signature\Signer; +use Mdanter\Ecc\Crypto\Signature\SignHasher; use Mdanter\Ecc\EccFactory; +use Mdanter\Ecc\Math\GmpMathInterface; +use Mdanter\Ecc\Primitives\GeneratorPoint; +use Mdanter\Ecc\Random\RandomGeneratorFactory; use Mdanter\Ecc\Serializer\PrivateKey\DerPrivateKeySerializer; use Mdanter\Ecc\Serializer\PrivateKey\PemPrivateKeySerializer; use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer; use Mdanter\Ecc\Serializer\PublicKey\PemPublicKeySerializer; +use Casper\Util\NumUtil; + /** * Secp256K1 key implementation */ final class Secp256K1Key extends AsymmetricKey { - protected const RESULT_OK = 1; - protected const HASHING_ALGORITHM = 'sha256'; + private const HASHING_ALGORITHM = 'sha256'; + + private GmpMathInterface $adapter; - protected PrivateKeyInterface $privateKeyObject; + private GeneratorPoint $generator; + + private PrivateKeyInterface $privateKeyObject; public function __construct(PrivateKeyInterface $privateKeyObject = null) { - $this->privateKeyObject = $privateKeyObject ?? - EccFactory::getSecgCurves() + $this->adapter = EccFactory::getAdapter(); + $this->generator = EccFactory::getSecgCurves() + ->generator256k1(null, true); + $this->privateKeyObject = $privateKeyObject ?? EccFactory::getSecgCurves() ->generator256k1() ->createPrivateKey(); $privateKey = ByteUtil::hexToString(gmp_strval($this->privateKeyObject->getSecret(), 16)); - $publicKey= ByteUtil::hexToString($this->getCompressedPublicKeyHex()); + $publicKey = ByteUtil::hexToString($this->getCompressedPublicKeyHex()); parent::__construct($publicKey, $privateKey, self::ALGO_SECP255K1); } @@ -48,7 +62,6 @@ public static function createFromPrivateKeyFile(string $pathToPrivateKey): self /** * @inheritDoc - * @return string */ public function exportPublicKeyInPem(): string { @@ -58,7 +71,6 @@ public function exportPublicKeyInPem(): string /** * @inheritDoc - * @return string */ public function exportPrivateKeyInPem(): string { @@ -68,71 +80,41 @@ public function exportPrivateKeyInPem(): string /** * @inheritDoc - * @param string $message - * @return string - * * @throws \Exception */ public function sign(string $message): string { - $context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - - $signature = null; - $signResult = secp256k1_ecdsa_sign( - $context, - $signature, - hash(self::HASHING_ALGORITHM, $message, true), - $this->privateKey - ); + $hash = (new SignHasher('sha256', $this->adapter)) + ->makeHash($message, $this->generator); - if ($signResult !== self::RESULT_OK) { - throw new \Exception("Failed to create signature"); - } + $randomK = RandomGeneratorFactory::getHmacRandomGenerator($this->privateKeyObject, $hash, self::HASHING_ALGORITHM) + ->generate($this->generator->getOrder()); + + $signature = (new Signer($this->adapter, true)) + ->sign($this->privateKeyObject, $hash, $randomK); - $signatureSerialized = ''; - secp256k1_ecdsa_signature_serialize_compact($context, $signatureSerialized, $signature); + $r = $signature->getR(); + $s = $signature->getS(); - return ByteUtil::stringToHex($signatureSerialized); + return NumUtil::padNumberLeft($r) . NumUtil::padNumberLeft($s); } /** * @inheritDoc - * @param string $hexSignature - * @param string $message - * @return bool - * * @throws \Exception */ public function verify(string $hexSignature, string $message): bool { - $context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY); - - $publicKey = null; - $publicKeyParseResult = secp256k1_ec_pubkey_parse($context, $publicKey, $this->publicKey); - - if ($publicKeyParseResult !== self::RESULT_OK) { - throw new \Exception("Failed to parse public key"); - } - - $signature = null; - $signatureParseResult = secp256k1_ecdsa_signature_parse_compact( - $context, - $signature, - ByteUtil::hexToString($hexSignature) - ); - - if ($signatureParseResult !== self::RESULT_OK) { - throw new \Exception("Failed to parse DER signature"); + try { + $hash = (new SignHasher(self::HASHING_ALGORITHM))->makeHash($message, $this->generator); + $publicKey = $this->privateKeyObject->getPublicKey(); + $signature = $this->hexToSignature($hexSignature); + + return (new Signer($this->adapter)) + ->verify($publicKey, $signature, $hash); + } catch (\Exception $e) { + return false; } - - $isVerified = secp256k1_ecdsa_verify( - $context, - $signature, - hash(self::HASHING_ALGORITHM, $message, true), - $publicKey - ); - - return $isVerified === self::RESULT_OK; } private function getCompressedPublicKeyHex(): string @@ -152,4 +134,25 @@ private function getCompressedPublicKeyHex(): string return $prefix . $xPointValueHex; } + + private function hexToSignature(string $hex): SignatureInterface + { + $hex = mb_strtolower($hex); + + if (strpos($hex, '0x') >= 0) { + $hex = str_replace('0x', '', $hex); + } + + if (mb_strlen($hex) !== 128) { + throw new \InvalidArgumentException('Incorrect hex length'); + } + + $rHex = mb_substr($hex, 0, 64); + $sHex = mb_substr($hex, 64, 64); + + $r = gmp_init($rHex, 16); + $s = gmp_init($sHex, 16); + + return new Signature($r, $s); + } } diff --git a/src/Util/NumUtil.php b/src/Util/NumUtil.php new file mode 100644 index 0000000..a198447 --- /dev/null +++ b/src/Util/NumUtil.php @@ -0,0 +1,18 @@ +