From 0e63f278c4421dfb1be321f7e3dd6214e1f9399d Mon Sep 17 00:00:00 2001 From: sc0vu Date: Tue, 26 Jan 2021 23:50:50 +0800 Subject: [PATCH 01/16] Update .travis.yml --- .travis.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7ff137e..3988092 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,17 +6,11 @@ php: - 7.1 - 7.2 - 7.3 + - 7.4 install: - composer install -notifications: - email: - recipients: - - alk03073135@gmail.com - on_success: always - on_failure: always - script: - vendor/bin/phpunit --coverage-clover=coverage.xml From a52be6f192b80e11fbe3082b9be0422b0df987ba Mon Sep 17 00:00:00 2001 From: sc0Vu Date: Sat, 5 Jun 2021 15:35:31 +0800 Subject: [PATCH 02/16] Update .gitignore --- .gitignore | 3 +- composer.lock | 1796 ------------------------------------------------- 2 files changed, 2 insertions(+), 1797 deletions(-) delete mode 100644 composer.lock diff --git a/.gitignore b/.gitignore index c422267..084ea1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ composer.phar /vendor/ - +composer.lock +.DS_Store # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file # composer.lock diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 9249fb7..0000000 --- a/composer.lock +++ /dev/null @@ -1,1796 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", - "This file is @generated automatically" - ], - "content-hash": "de28da405ac699ffb57506b1f832536f", - "packages": [ - { - "name": "kornrunner/keccak", - "version": "1.0.3", - "source": { - "type": "git", - "url": "https://github.com/kornrunner/php-keccak.git", - "reference": "ad761f528f4d1e3ce378b8a0841e5f82c9973535" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/kornrunner/php-keccak/zipball/ad761f528f4d1e3ce378b8a0841e5f82c9973535", - "reference": "ad761f528f4d1e3ce378b8a0841e5f82c9973535", - "shasum": "" - }, - "require": { - "php": ">=7.1.0", - "symfony/polyfill-mbstring": "^1.8" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "~7" - }, - "type": "library", - "autoload": { - "psr-4": { - "kornrunner\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Boris Momcilovic", - "homepage": "https://github.com/kornrunner/php-keccak" - } - ], - "description": "Pure PHP implementation of Keccak", - "keywords": [ - "keccak", - "sha-3", - "sha3-256" - ], - "time": "2018-07-30T22:16:23+00:00" - }, - { - "name": "simplito/bigint-wrapper-php", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/simplito/bigint-wrapper-php.git", - "reference": "cf21ec76d33f103add487b3eadbd9f5033a25930" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/simplito/bigint-wrapper-php/zipball/cf21ec76d33f103add487b3eadbd9f5033a25930", - "reference": "cf21ec76d33f103add487b3eadbd9f5033a25930", - "shasum": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "BI\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Simplito Team", - "email": "s.smyczynski@simplito.com", - "homepage": "https://simplito.com" - } - ], - "description": "Common interface for php_gmp and php_bcmath modules", - "time": "2018-02-27T12:38:08+00:00" - }, - { - "name": "simplito/bn-php", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/simplito/bn-php.git", - "reference": "e852fcd27e4acbc32459606d7606e45a85e42465" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/simplito/bn-php/zipball/e852fcd27e4acbc32459606d7606e45a85e42465", - "reference": "e852fcd27e4acbc32459606d7606e45a85e42465", - "shasum": "" - }, - "require": { - "simplito/bigint-wrapper-php": "~1.0.0" - }, - "require-dev": { - "phpunit/phpunit": "*" - }, - "type": "library", - "autoload": { - "psr-4": { - "BN\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Simplito Team", - "email": "s.smyczynski@simplito.com", - "homepage": "https://simplito.com" - } - ], - "description": "Big number implementation compatible with bn.js", - "time": "2018-04-12T11:07:43+00:00" - }, - { - "name": "simplito/elliptic-php", - "version": "1.0.6", - "source": { - "type": "git", - "url": "https://github.com/simplito/elliptic-php.git", - "reference": "15652609aa55968d56685c2a9120535ccdc00fd9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/simplito/elliptic-php/zipball/15652609aa55968d56685c2a9120535ccdc00fd9", - "reference": "15652609aa55968d56685c2a9120535ccdc00fd9", - "shasum": "" - }, - "require": { - "ext-gmp": "*", - "simplito/bn-php": "~1.1.0" - }, - "require-dev": { - "phpbench/phpbench": "@dev", - "phpunit/phpunit": "*" - }, - "type": "library", - "autoload": { - "psr-4": { - "Elliptic\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Simplito Team", - "email": "s.smyczynski@simplito.com", - "homepage": "https://simplito.com" - } - ], - "description": "Fast elliptic curve cryptography", - "homepage": "https://github.com/simplito/elliptic-php", - "keywords": [ - "Curve25519", - "ECDSA", - "Ed25519", - "EdDSA", - "cryptography", - "curve", - "ecc", - "ecdh", - "elliptic", - "nistp192", - "nistp224", - "nistp256", - "nistp384", - "nistp521", - "secp256k1" - ], - "time": "2019-11-14T13:43:07+00:00" - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.9.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], - "time": "2018-08-06T14:22:27+00:00" - }, - { - "name": "web3p/ethereum-util", - "version": "0.1.2", - "source": { - "type": "git", - "url": "https://github.com/web3p/ethereum-util.git", - "reference": "c7b5edd1a3ca20d0dae93f6fb9d253c61dfb73c6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web3p/ethereum-util/zipball/c7b5edd1a3ca20d0dae93f6fb9d253c61dfb73c6", - "reference": "c7b5edd1a3ca20d0dae93f6fb9d253c61dfb73c6", - "shasum": "" - }, - "require": { - "kornrunner/keccak": "~1", - "php": "^7.1", - "simplito/elliptic-php": "~1.0.6" - }, - "require-dev": { - "phpunit/phpunit": "^6.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "Web3p\\EthereumUtil\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "sc0Vu", - "email": "alk03073135@gmail.com" - } - ], - "description": "A collection of utility functions for Ethereum written in PHP.", - "time": "2019-11-23T08:43:24+00:00" - }, - { - "name": "web3p/rlp", - "version": "0.3.2", - "source": { - "type": "git", - "url": "https://github.com/web3p/rlp.git", - "reference": "bc9e29f7a1f658408f25322ea21ff5687a851470" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/web3p/rlp/zipball/bc9e29f7a1f658408f25322ea21ff5687a851470", - "reference": "bc9e29f7a1f658408f25322ea21ff5687a851470", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "~7" - }, - "type": "library", - "autoload": { - "psr-4": { - "Web3p\\RLP\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "sc0Vu", - "email": "alk03073135@gmail.com" - } - ], - "description": "Recursive Length Prefix Encoding in PHP.", - "time": "2019-11-23T08:20:30+00:00" - } - ], - "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2017-07-22T11:58:36+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.8.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "shasum": "" - }, - "require": { - "php": "^7.1" - }, - "replace": { - "myclabs/deep-copy": "self.version" - }, - "require-dev": { - "doctrine/collections": "^1.0", - "doctrine/common": "^2.6", - "phpunit/phpunit": "^7.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - }, - "files": [ - "src/DeepCopy/deep_copy.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2018-06-11T23:09:50+00:00" - }, - { - "name": "phar-io/manifest", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-phar": "*", - "phar-io/version": "^1.0.1", - "php": "^5.6 || ^7.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" - }, - { - "name": "phar-io/version", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2017-09-11T18:02:19+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", - "shasum": "" - }, - "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0", - "phpdocumentor/type-resolver": "^0.4.0", - "webmozart/assert": "^1.0" - }, - "require-dev": { - "doctrine/instantiator": "~1.0.5", - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", - "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", - "shasum": "" - }, - "require": { - "php": "^5.5 || ^7.0", - "phpdocumentor/reflection-common": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "time": "2017-07-14T14:27:02+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "1.8.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2018-08-05T17:53:17+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "5.3.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-xmlwriter": "*", - "php": "^7.0", - "phpunit/php-file-iterator": "^1.4.2", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", - "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", - "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "ext-xdebug": "^2.5.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2018-04-06T15:36:58+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2017-11-27T13:52:08+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21T13:50:34+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.9", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2017-02-26T11:10:40+00:00" - }, - { - "name": "phpunit/php-token-stream", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.2.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2017-11-27T05:48:46+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "6.5.11", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7bab54cb366076023bbf457a2a0d513332cd40f2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7bab54cb366076023bbf457a2a0d513332cd40f2", - "reference": "7bab54cb366076023bbf457a2a0d513332cd40f2", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.0", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", - "phpunit/php-file-iterator": "^1.4.3", - "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.9", - "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", - "sebastian/environment": "^3.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^2.0", - "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", - "sebastian/version": "^2.0.1" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" - }, - "require-dev": { - "ext-pdo": "*" - }, - "suggest": { - "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "6.5.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2018-08-07T07:05:35+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "5.0.10", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", - "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.0", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5.11" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "abandoned": true, - "time": "2018-08-09T05:50:03+00:00" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2017-03-04T06:30:41+00:00" - }, - { - "name": "sebastian/comparator", - "version": "2.1.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", - "shasum": "" - }, - "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", - "sebastian/exporter": "^3.1" - }, - "require-dev": { - "phpunit/phpunit": "^6.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2018-02-01T13:46:46+00:00" - }, - { - "name": "sebastian/diff", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2017-08-03T08:09:46+00:00" - }, - { - "name": "sebastian/environment", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2017-07-01T08:51:00+00:00" - }, - { - "name": "sebastian/exporter", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", - "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", - "shasum": "" - }, - "require": { - "php": "^7.0", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2017-04-03T13:19:02+00:00" - }, - { - "name": "sebastian/global-state", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2017-04-27T15:39:26+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", - "shasum": "" - }, - "require": { - "php": "^7.0", - "sebastian/object-reflector": "^1.1.1", - "sebastian/recursion-context": "^3.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2017-08-03T12:35:26+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "773f97c67f28de00d397be301821b06708fca0be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", - "reference": "773f97c67f28de00d397be301821b06708fca0be", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2017-03-29T09:07:27+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", - "shasum": "" - }, - "require": { - "php": "^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2017-03-03T06:23:57+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" - }, - { - "name": "sebastian/version", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", - "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^7.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2018-01-29T19:49:41+00:00" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": [], - "platform-dev": [] -} From a3a9ab1cd892ede54727b9f2b156e1a79f11e82c Mon Sep 17 00:00:00 2001 From: sc0Vu Date: Sat, 5 Jun 2021 15:37:37 +0800 Subject: [PATCH 03/16] Support php 8.0 --- composer.json | 7 ++++--- test/TestCase.php | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 3daaf81..33f95bc 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "~7 | ~8.0" }, "autoload": { "psr-4": { @@ -23,8 +23,9 @@ } }, "require": { - "web3p/rlp": "0.3.2", - "web3p/ethereum-util": "~0.1.2", + "PHP": "^7.1 | ^8.0", + "web3p/rlp": "0.3.3", + "web3p/ethereum-util": "~0.1.3", "kornrunner/keccak": "~1", "simplito/elliptic-php": "~1.0.6" } diff --git a/test/TestCase.php b/test/TestCase.php index d1acdac..524377e 100644 --- a/test/TestCase.php +++ b/test/TestCase.php @@ -26,7 +26,7 @@ class TestCase extends BaseTestCase * * @return void */ - public function setUp() + public function setUp(): void { $this->rlp = new RLP; } @@ -36,5 +36,5 @@ public function setUp() * * @return void */ - public function tearDown() {} + public function tearDown(): void {} } \ No newline at end of file From c3c4ee7b7f8e64bbfa3845cdafa141c3b18bca19 Mon Sep 17 00:00:00 2001 From: sc0Vu Date: Sat, 5 Jun 2021 15:38:21 +0800 Subject: [PATCH 04/16] Add github ci --- .github/workflows/php.yml | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/php.yml diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..f8a94a5 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,47 @@ +name: PHP + +on: ["push", "pull_request"] + +jobs: + build_and_test: + name: Build and test ethereum-tx with ${{ matrix.php-version }} + strategy: + matrix: + php-version: ["7.3", "7.4", "8.0"] + + runs-on: ubuntu-latest + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + + - name: PHP version + run: | + php --version + + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run test suite + run: vendor/bin/phpunit --coverage-clover=coverage.xml + + - uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} From 97677704bba1c70a5f169be3e91fb980811a38a0 Mon Sep 17 00:00:00 2001 From: sc0Vu Date: Sat, 5 Jun 2021 16:09:35 +0800 Subject: [PATCH 05/16] Remove travi ci --- .travis.yml | 18 ------------------ README.md | 2 +- 2 files changed, 1 insertion(+), 19 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3988092..0000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -sudo: required - -language: php - -php: - - 7.1 - - 7.2 - - 7.3 - - 7.4 - -install: - - composer install - -script: - - vendor/bin/phpunit --coverage-clover=coverage.xml - -after_success: - - bash <(curl -s https://codecov.io/bash) -t 284c5fc4-2109-4bb3-822f-82026d67b1bd diff --git a/README.md b/README.md index d369663..6826d12 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # ethereum-tx -[![Build Status](https://travis-ci.org/web3p/ethereum-tx.svg?branch=master)](https://travis-ci.org/web3p/ethereum-tx) +[![PHP](https://github.com/web3p/ethereum-tx/actions/workflows/php.yml/badge.svg)](https://github.com/web3p/ethereum-tx/actions/workflows/php.yml) [![codecov](https://codecov.io/gh/web3p/ethereum-tx/branch/master/graph/badge.svg)](https://codecov.io/gh/web3p/ethereum-tx) Ethereum transaction library in PHP. From 10bfcf0a0ba9f1130158e08ab99fde5e9e450124 Mon Sep 17 00:00:00 2001 From: sc0vu Date: Sun, 15 Aug 2021 04:43:35 +0000 Subject: [PATCH 06/16] Clean up - Remove space from composer.json - Remove syntaxCheck from phpunit.xml - Update .gitignore --- .gitignore | 1 + composer.json | 4 ++-- phpunit.xml | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 084ea1b..8b7f132 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ composer.phar /vendor/ composer.lock .DS_Store +.phpunit.result.cache # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file # composer.lock diff --git a/composer.json b/composer.json index 33f95bc..748e386 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ } ], "require-dev": { - "phpunit/phpunit": "~7 | ~8.0" + "phpunit/phpunit": "~7|~8.0" }, "autoload": { "psr-4": { @@ -23,7 +23,7 @@ } }, "require": { - "PHP": "^7.1 | ^8.0", + "PHP": "^7.1|^8.0", "web3p/rlp": "0.3.3", "web3p/ethereum-util": "~0.1.3", "kornrunner/keccak": "~1", diff --git a/phpunit.xml b/phpunit.xml index e2c2311..379e15c 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,8 +7,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" - syntaxCheck="false"> + stopOnFailure="false"> ./test/unit From 0de3effc6b9f954db425b9fb0e939d52c60c3bb3 Mon Sep 17 00:00:00 2001 From: sc0vu Date: Sat, 28 Aug 2021 17:43:19 +0000 Subject: [PATCH 07/16] Add EIP2718 transaction type --- src/Transaction.php | 37 +++++++++++++++++++++++++++++++++++ test/unit/TransactionTest.php | 24 +++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/Transaction.php b/src/Transaction.php index bca994d..a3ef26b 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -70,6 +70,11 @@ class Transaction implements ArrayAccess 'chainId' => [ 'key' => -2 ], + 'transactionType' => [ + 'key' => -3, + 'length' => 2, + 'allowZero' => true + ], 'nonce' => [ 'key' => 0, 'length' => 32, @@ -181,6 +186,15 @@ public function __construct($txData=[]) $tx = []; if ($this->util->isHex($txData)) { + // check first byte + $txData = $this->util->stripZero($txData); + $firstByteStr = substr($txData, 0, 2); + $firstByte = hexdec($firstByteStr); + if ($firstByte >= 0 && $firstByte <= 127) { + // first byte is transaction type + $tx[$this->attributeMap['transactionType']['key']] = $firstByteStr; + $txData = substr($txData, 2); + } $txData = $this->rlp->decode($txData); foreach ($txData as $txKey => $data) { @@ -340,6 +354,17 @@ public function getTxData() return $this->txData; } + /** + * Return whether transaction type is valid (0x0 <= $transactionType <= 0x7f). + * + * @param integer $transactionType + * @return boolean is transaction valid + */ + protected function isTransactionTypeValid(int $transactionType) + { + return $transactionType >= 0 && $transactionType <= 127; + } + /** * RLP serialize the ethereum transaction. * @@ -363,6 +388,18 @@ public function serialize() $txData[$key] = $data; } } + $transactionType = $this->offsetGet('transactionType'); + if ( + $transactionType + ) { + $transactionType = $this->util->stripZero($transactionType); + if ($this->isTransactionTypeValid(hexdec($transactionType))) { + if (strlen($transactionType) % 2 != 0) { + $transactionType = '0' . $transactionType; + } + return $transactionType . $this->rlp->encode($txData); + } + } return $this->rlp->encode($txData); } diff --git a/test/unit/TransactionTest.php b/test/unit/TransactionTest.php index 197a7e8..d20c1cb 100644 --- a/test/unit/TransactionTest.php +++ b/test/unit/TransactionTest.php @@ -347,4 +347,28 @@ public function testIssue26() $this->assertEquals($transaction->txData, []); } } + + /** + * testEIP2718 + * see: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md + * + * @return void + */ + public function testEIP2718() + { + $transaction = new Transaction([ + 'transactionType' => '0x0', + 'nonce' => '0x09', + 'to' => '0x3535353535353535353535353535353535353535', + 'gas' => '0x5208', + 'gasPrice' => '0x4a817c800', + 'value' => '0x0', + 'chainId' => 1, + 'data' => '' + ]); + $this->assertEquals('00f864098504a817c800825208943535353535353535353535353535353535353535808025a0855ec9b7d4fcabf535fe4ac4a7c31a9e521214d05bc6efbc058d4757c35e92bba0043d7df30c8a79e5522b3de8fc169df5fa7145714100ee8ec413292d97ce4d3a', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); + + $transaction = new Transaction('0x00f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83'); + $this->assertEquals('00f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83', $transaction->serialize()); + } } From 03eb24bc2e029694c2297f12d1aefafd612833f7 Mon Sep 17 00:00:00 2001 From: sc0vu Date: Sun, 29 Aug 2021 04:24:00 +0000 Subject: [PATCH 08/16] Add EIP2930 type 1 transaction --- src/EIP2930Transaction.php | 514 ++++++++++++++++++++++++++++++++++ src/Transaction.php | 37 --- test/unit/TransactionTest.php | 23 +- 3 files changed, 527 insertions(+), 47 deletions(-) create mode 100644 src/EIP2930Transaction.php diff --git a/src/EIP2930Transaction.php b/src/EIP2930Transaction.php new file mode 100644 index 0000000..82787ac --- /dev/null +++ b/src/EIP2930Transaction.php @@ -0,0 +1,514 @@ + + * + * @author Peter Lai + * @license MIT + */ + +namespace Web3p\EthereumTx; + +use InvalidArgumentException; +use RuntimeException; +use Web3p\RLP\RLP; +use Elliptic\EC; +use Elliptic\EC\KeyPair; +use ArrayAccess; +use Web3p\EthereumUtil\Util; + +/** + * It's a instance for generating/serializing ethereum transaction. + * + * ```php + * use Web3p\EthereumTx\EIP2930Transaction; + * + * // generate transaction instance with transaction parameters + * $transaction = new Transaction([ + * 'nonce' => '0x01', + * 'from' => '0xb60e8dd61c5d32be8058bb8eb970870f07233155', + * 'to' => '0xd46e8dd67c5d32be8058bb8eb970870f07244567', + * 'gas' => '0x76c0', + * 'gasPrice' => '0x9184e72a000', + * 'value' => '0x9184e72a', + * 'chainId' => 1, // required + * 'accessList' => [], + * 'data' => '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675' + * ]); + * + * // generate transaction instance with hex encoded transaction + * $transaction = new Transaction('0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83'); + * ``` + * + * ```php + * After generate transaction instance, you can sign transaction with your private key. + * + * $signedTransaction = $transaction->sign('your private key'); + * ``` + * + * Then you can send serialized transaction to ethereum through http rpc with web3.php. + * ```php + * $hashedTx = $transaction->serialize(); + * ``` + * + * @author Peter Lai + * @link https://www.web3p.xyz + * @filesource https://github.com/web3p/ethereum-tx + */ +class EIP2930Transaction implements ArrayAccess +{ + /** + * Attribute map for keeping order of transaction key/value + * + * @var array + */ + protected $attributeMap = [ + 'from' => [ + 'key' => -1 + ], + 'chainId' => [ + 'key' => 0 + ], + 'nonce' => [ + 'key' => 1, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'gasPrice' => [ + 'key' => 2, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'gasLimit' => [ + 'key' => 3, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'gas' => [ + 'key' => 3, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'to' => [ + 'key' => 4, + 'length' => 20, + 'allowZero' => true, + ], + 'value' => [ + 'key' => 5, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'data' => [ + 'key' => 6, + 'allowLess' => true, + 'allowZero' => true + ], + 'accessList' => [ + 'key' => 7, + 'allowLess' => true, + 'allowZero' => true, + 'allowArray' => true + ], + 'v' => [ + 'key' => 8, + 'allowZero' => true + ], + 'r' => [ + 'key' => 9, + 'length' => 32, + 'allowZero' => true + ], + 's' => [ + 'key' => 10, + 'length' => 32, + 'allowZero' => true + ] + ]; + + /** + * Raw transaction data + * + * @var array + */ + protected $txData = []; + + /** + * RLP encoding instance + * + * @var \Web3p\RLP\RLP + */ + protected $rlp; + + /** + * secp256k1 elliptic curve instance + * + * @var \Elliptic\EC + */ + protected $secp256k1; + + /** + * Private key instance + * + * @var \Elliptic\EC\KeyPair + */ + protected $privateKey; + + /** + * Ethereum util instance + * + * @var \Web3p\EthereumUtil\Util + */ + protected $util; + + /** + * Transaction type + * + * @var string + */ + protected $transactionType = '01'; + + /** + * construct + * + * @param array|string $txData + * @return void + */ + public function __construct($txData=[]) + { + $this->rlp = new RLP; + $this->secp256k1 = new EC('secp256k1'); + $this->util = new Util; + + if (is_array($txData)) { + foreach ($txData as $key => $data) { + $this->offsetSet($key, $data); + } + } elseif (is_string($txData)) { + $tx = []; + + if ($this->util->isHex($txData)) { + // check first byte + $txData = $this->util->stripZero($txData); + $firstByteStr = substr($txData, 0, 2); + $firstByte = hexdec($firstByteStr); + if ($this->isTransactionTypeValid($firstByte)) { + $txData = substr($txData, 2); + } + $txData = $this->rlp->decode($txData); + + foreach ($txData as $txKey => $data) { + if (is_int($txKey)) { + if (is_string($data) && strlen($data) > 0) { + $tx[$txKey] = '0x' . $data; + } else { + $tx[$txKey] = $data; + } + } + } + } + $this->txData = $tx; + } + } + + /** + * Return the value in the transaction with given key or return the protected property value if get(property_name} function is existed. + * + * @param string $name key or protected property name + * @return mixed + */ + public function __get(string $name) + { + $method = 'get' . ucfirst($name); + + if (method_exists($this, $method)) { + return call_user_func_array([$this, $method], []); + } + return $this->offsetGet($name); + } + + /** + * Set the value in the transaction with given key or return the protected value if set(property_name} function is existed. + * + * @param string $name key, eg: to + * @param mixed value + * @return void + */ + public function __set(string $name, $value) + { + $method = 'set' . ucfirst($name); + + if (method_exists($this, $method)) { + return call_user_func_array([$this, $method], [$value]); + } + return $this->offsetSet($name, $value); + } + + /** + * Return hash of the ethereum transaction without signature. + * + * @return string hex encoded of the transaction + */ + public function __toString() + { + return $this->hash(false); + } + + /** + * Set the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @param string value + * @return void + */ + public function offsetSet($offset, $value) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey)) { + if (is_array($value)) { + if (!isset($txKey['allowArray']) || (isset($txKey['allowArray']) && $txKey['allowArray'] === false)) { + throw new InvalidArgumentException($offset . ' should\'t be array.'); + } + if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { + // check length + if (isset($txKey['length'])) { + if (count($value) > $txKey['length'] * 2) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } + } + if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { + // check zero + foreach ($value as $key => $v) { + $checkedV = $v ? (string) $v : ''; + if (preg_match('/^0*$/', $checkedV) === 1) { + // set value to empty string + $checkedV = ''; + $value[$key] = $checkedV; + } + } + } + } else { + $checkedValue = ($value) ? (string) $value : ''; + $isHex = $this->util->isHex($checkedValue); + $checkedValue = $this->util->stripZero($checkedValue); + + if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { + // check length + if (isset($txKey['length'])) { + if ($isHex) { + if (strlen($checkedValue) > $txKey['length'] * 2) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } else { + if (strlen($checkedValue) > $txKey['length']) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } + } + } + if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { + // check zero + if (preg_match('/^0*$/', $checkedValue) === 1) { + // set value to empty string + $value = ''; + } + } + } + $this->txData[$txKey['key']] = $value; + } + } + + /** + * Return whether the value is in the transaction with given key. + * + * @param string $offset key, eg: to + * @return bool + */ + public function offsetExists($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey)) { + return isset($this->txData[$txKey['key']]); + } + return false; + } + + /** + * Unset the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @return void + */ + public function offsetUnset($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey) && isset($this->txData[$txKey['key']])) { + unset($this->txData[$txKey['key']]); + } + } + + /** + * Return the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @return mixed value of the transaction + */ + public function offsetGet($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey) && isset($this->txData[$txKey['key']])) { + return $this->txData[$txKey['key']]; + } + return null; + } + + /** + * Return raw ethereum transaction data. + * + * @return array raw ethereum transaction data + */ + public function getTxData() + { + return $this->txData; + } + + /** + * Return whether transaction type is valid (0x0 <= $transactionType <= 0x7f). + * + * @param integer $transactionType + * @return boolean is transaction valid + */ + protected function isTransactionTypeValid(int $transactionType) + { + return $transactionType >= 0 && $transactionType <= 127; + } + + /** + * RLP serialize the ethereum transaction. + * + * @return \Web3p\RLP\RLP\Buffer serialized ethereum transaction + */ + public function serialize() + { + $chainId = $this->offsetGet('chainId'); + + // sort tx data + if (ksort($this->txData) !== true) { + throw new RuntimeException('Cannot sort tx data by keys.'); + } + if ($chainId && $chainId > 0) { + $txData = array_fill(0, 11, ''); + } else { + $txData = array_fill(0, 8, ''); + } + foreach ($this->txData as $key => $data) { + if ($key >= 0) { + $txData[$key] = $data; + } + } + $transactionType = $this->transactionType; + return $transactionType . $this->rlp->encode($txData); + } + + /** + * Sign the transaction with given hex encoded private key. + * + * @param string $privateKey hex encoded private key + * @return string hex encoded signed ethereum transaction + */ + public function sign(string $privateKey) + { + if ($this->util->isHex($privateKey)) { + $privateKey = $this->util->stripZero($privateKey); + $ecPrivateKey = $this->secp256k1->keyFromPrivate($privateKey, 'hex'); + } else { + throw new InvalidArgumentException('Private key should be hex encoded string'); + } + $txHash = $this->hash(); + $signature = $ecPrivateKey->sign($txHash, [ + 'canonical' => true + ]); + $r = $signature->r; + $s = $signature->s; + $v = $signature->recoveryParam; + + $this->offsetSet('r', '0x' . $r->toString(16)); + $this->offsetSet('s', '0x' . $s->toString(16)); + $this->offsetSet('v', $v); + $this->privateKey = $ecPrivateKey; + + return $this->serialize(); + } + + /** + * Return hash of the ethereum transaction with/without signature. + * + * @return string hex encoded hash of the ethereum transaction + */ + public function hash() + { + $chainId = $this->offsetGet('chainId'); + + // sort tx data + if (ksort($this->txData) !== true) { + throw new RuntimeException('Cannot sort tx data by keys.'); + } + $rawTxData = array_fill(0, 8, ''); + foreach ($this->txData as $key => $data) { + if ($key >= 0 && $key < 8) { + $rawTxData[$key] = $data; + } + } + $serializedTx = $this->rlp->encode($rawTxData); + $transactionType = $this->transactionType; + return $this->util->sha3(hex2bin($transactionType . $serializedTx)); + } + + /** + * Recover from address with given signature (r, s, v) if didn't set from. + * + * @return string hex encoded ethereum address + */ + public function getFromAddress() + { + $from = $this->offsetGet('from'); + + if ($from) { + return $from; + } + if (!isset($this->privateKey) || !($this->privateKey instanceof KeyPair)) { + // recover from hash + $r = $this->offsetGet('r'); + $s = $this->offsetGet('s'); + $v = $this->offsetGet('v'); + + if (!$r || !$s) { + throw new RuntimeException('Invalid signature r and s.'); + } + $txHash = $this->hash(); + $publicKey = $this->secp256k1->recoverPubKey($txHash, [ + 'r' => $r, + 's' => $s + ], $v); + $publicKey = $publicKey->encode('hex'); + } else { + $publicKey = $this->privateKey->getPublic(false, 'hex'); + } + $from = '0x' . substr($this->util->sha3(substr(hex2bin($publicKey), 1)), 24); + + $this->offsetSet('from', $from); + return $from; + } +} \ No newline at end of file diff --git a/src/Transaction.php b/src/Transaction.php index a3ef26b..bca994d 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -70,11 +70,6 @@ class Transaction implements ArrayAccess 'chainId' => [ 'key' => -2 ], - 'transactionType' => [ - 'key' => -3, - 'length' => 2, - 'allowZero' => true - ], 'nonce' => [ 'key' => 0, 'length' => 32, @@ -186,15 +181,6 @@ public function __construct($txData=[]) $tx = []; if ($this->util->isHex($txData)) { - // check first byte - $txData = $this->util->stripZero($txData); - $firstByteStr = substr($txData, 0, 2); - $firstByte = hexdec($firstByteStr); - if ($firstByte >= 0 && $firstByte <= 127) { - // first byte is transaction type - $tx[$this->attributeMap['transactionType']['key']] = $firstByteStr; - $txData = substr($txData, 2); - } $txData = $this->rlp->decode($txData); foreach ($txData as $txKey => $data) { @@ -354,17 +340,6 @@ public function getTxData() return $this->txData; } - /** - * Return whether transaction type is valid (0x0 <= $transactionType <= 0x7f). - * - * @param integer $transactionType - * @return boolean is transaction valid - */ - protected function isTransactionTypeValid(int $transactionType) - { - return $transactionType >= 0 && $transactionType <= 127; - } - /** * RLP serialize the ethereum transaction. * @@ -388,18 +363,6 @@ public function serialize() $txData[$key] = $data; } } - $transactionType = $this->offsetGet('transactionType'); - if ( - $transactionType - ) { - $transactionType = $this->util->stripZero($transactionType); - if ($this->isTransactionTypeValid(hexdec($transactionType))) { - if (strlen($transactionType) % 2 != 0) { - $transactionType = '0' . $transactionType; - } - return $transactionType . $this->rlp->encode($txData); - } - } return $this->rlp->encode($txData); } diff --git a/test/unit/TransactionTest.php b/test/unit/TransactionTest.php index d20c1cb..fc17b93 100644 --- a/test/unit/TransactionTest.php +++ b/test/unit/TransactionTest.php @@ -4,6 +4,7 @@ use Test\TestCase; use Web3p\EthereumTx\Transaction; +use Web3p\EthereumTx\EIP2930Transaction; class TransactionTest extends TestCase { @@ -349,26 +350,28 @@ public function testIssue26() } /** - * testEIP2718 - * see: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2718.md + * testEIP2930 + * see: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2930.md * * @return void */ - public function testEIP2718() + public function testEIP2930() { - $transaction = new Transaction([ - 'transactionType' => '0x0', - 'nonce' => '0x09', + $transaction = new EIP2930Transaction([ + 'nonce' => '0x15', 'to' => '0x3535353535353535353535353535353535353535', 'gas' => '0x5208', 'gasPrice' => '0x4a817c800', 'value' => '0x0', - 'chainId' => 1, + 'chainId' => 4, + 'accessList' => [ + ], 'data' => '' ]); - $this->assertEquals('00f864098504a817c800825208943535353535353535353535353535353535353535808025a0855ec9b7d4fcabf535fe4ac4a7c31a9e521214d05bc6efbc058d4757c35e92bba0043d7df30c8a79e5522b3de8fc169df5fa7145714100ee8ec413292d97ce4d3a', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); + $this->assertEquals('01f86604158504a817c8008252089435353535353535353535353535353535353535358080c001a09753969d39f6a5109095d5082d67fc99a05fd66a339ba80934504ff79474e77aa07a907eb764b72b3088a331e7b97c2bad5fd43f1d574ddc80edeb022476454adb', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); - $transaction = new Transaction('0x00f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83'); - $this->assertEquals('00f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83', $transaction->serialize()); + $transaction = new EIP2930Transaction('0x01f86604158504a817c8008252089435353535353535353535353535353535353535358080c001a09753969d39f6a5109095d5082d67fc99a05fd66a339ba80934504ff79474e77aa07a907eb764b72b3088a331e7b97c2bad5fd43f1d574ddc80edeb022476454adb'); + $this->assertEquals('01f86604158504a817c8008252089435353535353535353535353535353535353535358080c001a09753969d39f6a5109095d5082d67fc99a05fd66a339ba80934504ff79474e77aa07a907eb764b72b3088a331e7b97c2bad5fd43f1d574ddc80edeb022476454adb', $transaction->serialize()); + $this->assertEquals('01f86604158504a817c8008252089435353535353535353535353535353535353535358080c001a09753969d39f6a5109095d5082d67fc99a05fd66a339ba80934504ff79474e77aa07a907eb764b72b3088a331e7b97c2bad5fd43f1d574ddc80edeb022476454adb', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); } } From 3f2f865b2e36fda86fb160c04f51eaf057b0c3b8 Mon Sep 17 00:00:00 2001 From: sc0vu Date: Sun, 29 Aug 2021 04:48:37 +0000 Subject: [PATCH 09/16] Add EIP1559 type 2 transaction --- src/EIP1559Transaction.php | 521 ++++++++++++++++++++++++++++++++++ test/unit/TransactionTest.php | 28 ++ 2 files changed, 549 insertions(+) create mode 100644 src/EIP1559Transaction.php diff --git a/src/EIP1559Transaction.php b/src/EIP1559Transaction.php new file mode 100644 index 0000000..0da02b8 --- /dev/null +++ b/src/EIP1559Transaction.php @@ -0,0 +1,521 @@ + + * + * @author Peter Lai + * @license MIT + */ + +namespace Web3p\EthereumTx; + +use InvalidArgumentException; +use RuntimeException; +use Web3p\RLP\RLP; +use Elliptic\EC; +use Elliptic\EC\KeyPair; +use ArrayAccess; +use Web3p\EthereumUtil\Util; + +/** + * It's a instance for generating/serializing ethereum transaction. + * + * ```php + * use Web3p\EthereumTx\EIP1559Transaction; + * + * // generate transaction instance with transaction parameters + * $transaction = new EIP1559Transaction([ + * 'nonce' => '0x01', + * 'from' => '0xb60e8dd61c5d32be8058bb8eb970870f07233155', + * 'to' => '0xd46e8dd67c5d32be8058bb8eb970870f07244567', + * 'maxPriorityFeePerGas' => '0x9184e72a000', + * 'maxFeePerGas' => '0x9184e72a000', + * 'gas' => '0x76c0', + * 'value' => '0x9184e72a', + * 'chainId' => 1, // required + * 'accessList' => [], + * 'data' => '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675' + * ]); + * + * // generate transaction instance with hex encoded transaction + * $transaction = new EIP1559Transaction('0x02f86c04158504a817c8008504a817c8008252089435353535353535353535353535353535353535358080c080a03fd48c8a173e9669c33cb5271f03b1af4f030dc8315be8ec9442b7fbdde893c8a010af381dab1df3e7012a3c8421d65a810859a5dd9d58991ad7c07f12d0c651c7'); + * ``` + * + * ```php + * After generate transaction instance, you can sign transaction with your private key. + * + * $signedTransaction = $transaction->sign('your private key'); + * ``` + * + * Then you can send serialized transaction to ethereum through http rpc with web3.php. + * ```php + * $hashedTx = $transaction->serialize(); + * ``` + * + * @author Peter Lai + * @link https://www.web3p.xyz + * @filesource https://github.com/web3p/ethereum-tx + */ +class EIP1559Transaction implements ArrayAccess +{ + /** + * Attribute map for keeping order of transaction key/value + * + * @var array + */ + protected $attributeMap = [ + 'from' => [ + 'key' => -1 + ], + 'chainId' => [ + 'key' => 0 + ], + 'nonce' => [ + 'key' => 1, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'maxPriorityFeePerGas' => [ + 'key' => 2, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'maxFeePerGas' => [ + 'key' => 3, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'gasLimit' => [ + 'key' => 4, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'gas' => [ + 'key' => 4, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'to' => [ + 'key' => 5, + 'length' => 20, + 'allowZero' => true, + ], + 'value' => [ + 'key' => 6, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'data' => [ + 'key' => 7, + 'allowLess' => true, + 'allowZero' => true + ], + 'accessList' => [ + 'key' => 8, + 'allowLess' => true, + 'allowZero' => true, + 'allowArray' => true + ], + 'v' => [ + 'key' => 9, + 'allowZero' => true + ], + 'r' => [ + 'key' => 10, + 'length' => 32, + 'allowZero' => true + ], + 's' => [ + 'key' => 11, + 'length' => 32, + 'allowZero' => true + ] + ]; + + /** + * Raw transaction data + * + * @var array + */ + protected $txData = []; + + /** + * RLP encoding instance + * + * @var \Web3p\RLP\RLP + */ + protected $rlp; + + /** + * secp256k1 elliptic curve instance + * + * @var \Elliptic\EC + */ + protected $secp256k1; + + /** + * Private key instance + * + * @var \Elliptic\EC\KeyPair + */ + protected $privateKey; + + /** + * Ethereum util instance + * + * @var \Web3p\EthereumUtil\Util + */ + protected $util; + + /** + * Transaction type + * + * @var string + */ + protected $transactionType = '02'; + + /** + * construct + * + * @param array|string $txData + * @return void + */ + public function __construct($txData=[]) + { + $this->rlp = new RLP; + $this->secp256k1 = new EC('secp256k1'); + $this->util = new Util; + + if (is_array($txData)) { + foreach ($txData as $key => $data) { + $this->offsetSet($key, $data); + } + } elseif (is_string($txData)) { + $tx = []; + + if ($this->util->isHex($txData)) { + // check first byte + $txData = $this->util->stripZero($txData); + $firstByteStr = substr($txData, 0, 2); + $firstByte = hexdec($firstByteStr); + if ($this->isTransactionTypeValid($firstByte)) { + $txData = substr($txData, 2); + } + $txData = $this->rlp->decode($txData); + + foreach ($txData as $txKey => $data) { + if (is_int($txKey)) { + if (is_string($data) && strlen($data) > 0) { + $tx[$txKey] = '0x' . $data; + } else { + $tx[$txKey] = $data; + } + } + } + } + $this->txData = $tx; + } + } + + /** + * Return the value in the transaction with given key or return the protected property value if get(property_name} function is existed. + * + * @param string $name key or protected property name + * @return mixed + */ + public function __get(string $name) + { + $method = 'get' . ucfirst($name); + + if (method_exists($this, $method)) { + return call_user_func_array([$this, $method], []); + } + return $this->offsetGet($name); + } + + /** + * Set the value in the transaction with given key or return the protected value if set(property_name} function is existed. + * + * @param string $name key, eg: to + * @param mixed value + * @return void + */ + public function __set(string $name, $value) + { + $method = 'set' . ucfirst($name); + + if (method_exists($this, $method)) { + return call_user_func_array([$this, $method], [$value]); + } + return $this->offsetSet($name, $value); + } + + /** + * Return hash of the ethereum transaction without signature. + * + * @return string hex encoded of the transaction + */ + public function __toString() + { + return $this->hash(false); + } + + /** + * Set the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @param string value + * @return void + */ + public function offsetSet($offset, $value) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey)) { + if (is_array($value)) { + if (!isset($txKey['allowArray']) || (isset($txKey['allowArray']) && $txKey['allowArray'] === false)) { + throw new InvalidArgumentException($offset . ' should\'t be array.'); + } + if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { + // check length + if (isset($txKey['length'])) { + if (count($value) > $txKey['length'] * 2) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } + } + if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { + // check zero + foreach ($value as $key => $v) { + $checkedV = $v ? (string) $v : ''; + if (preg_match('/^0*$/', $checkedV) === 1) { + // set value to empty string + $checkedV = ''; + $value[$key] = $checkedV; + } + } + } + } else { + $checkedValue = ($value) ? (string) $value : ''; + $isHex = $this->util->isHex($checkedValue); + $checkedValue = $this->util->stripZero($checkedValue); + + if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { + // check length + if (isset($txKey['length'])) { + if ($isHex) { + if (strlen($checkedValue) > $txKey['length'] * 2) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } else { + if (strlen($checkedValue) > $txKey['length']) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } + } + } + if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { + // check zero + if (preg_match('/^0*$/', $checkedValue) === 1) { + // set value to empty string + $value = ''; + } + } + } + $this->txData[$txKey['key']] = $value; + } + } + + /** + * Return whether the value is in the transaction with given key. + * + * @param string $offset key, eg: to + * @return bool + */ + public function offsetExists($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey)) { + return isset($this->txData[$txKey['key']]); + } + return false; + } + + /** + * Unset the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @return void + */ + public function offsetUnset($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey) && isset($this->txData[$txKey['key']])) { + unset($this->txData[$txKey['key']]); + } + } + + /** + * Return the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @return mixed value of the transaction + */ + public function offsetGet($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey) && isset($this->txData[$txKey['key']])) { + return $this->txData[$txKey['key']]; + } + return null; + } + + /** + * Return raw ethereum transaction data. + * + * @return array raw ethereum transaction data + */ + public function getTxData() + { + return $this->txData; + } + + /** + * Return whether transaction type is valid (0x0 <= $transactionType <= 0x7f). + * + * @param integer $transactionType + * @return boolean is transaction valid + */ + protected function isTransactionTypeValid(int $transactionType) + { + return $transactionType >= 0 && $transactionType <= 127; + } + + /** + * RLP serialize the ethereum transaction. + * + * @return \Web3p\RLP\RLP\Buffer serialized ethereum transaction + */ + public function serialize() + { + $chainId = $this->offsetGet('chainId'); + + // sort tx data + if (ksort($this->txData) !== true) { + throw new RuntimeException('Cannot sort tx data by keys.'); + } + if ($chainId && $chainId > 0) { + $txData = array_fill(0, 12, ''); + } else { + $txData = array_fill(0, 9, ''); + } + foreach ($this->txData as $key => $data) { + if ($key >= 0) { + $txData[$key] = $data; + } + } + $transactionType = $this->transactionType; + return $transactionType . $this->rlp->encode($txData); + } + + /** + * Sign the transaction with given hex encoded private key. + * + * @param string $privateKey hex encoded private key + * @return string hex encoded signed ethereum transaction + */ + public function sign(string $privateKey) + { + if ($this->util->isHex($privateKey)) { + $privateKey = $this->util->stripZero($privateKey); + $ecPrivateKey = $this->secp256k1->keyFromPrivate($privateKey, 'hex'); + } else { + throw new InvalidArgumentException('Private key should be hex encoded string'); + } + $txHash = $this->hash(); + $signature = $ecPrivateKey->sign($txHash, [ + 'canonical' => true + ]); + $r = $signature->r; + $s = $signature->s; + $v = $signature->recoveryParam; + + $this->offsetSet('r', '0x' . $r->toString(16)); + $this->offsetSet('s', '0x' . $s->toString(16)); + $this->offsetSet('v', $v); + $this->privateKey = $ecPrivateKey; + + return $this->serialize(); + } + + /** + * Return hash of the ethereum transaction with/without signature. + * + * @return string hex encoded hash of the ethereum transaction + */ + public function hash() + { + $chainId = $this->offsetGet('chainId'); + + // sort tx data + if (ksort($this->txData) !== true) { + throw new RuntimeException('Cannot sort tx data by keys.'); + } + $rawTxData = array_fill(0, 9, ''); + foreach ($this->txData as $key => $data) { + if ($key >= 0 && $key < 9) { + $rawTxData[$key] = $data; + } + } + $serializedTx = $this->rlp->encode($rawTxData); + $transactionType = $this->transactionType; + return $this->util->sha3(hex2bin($transactionType . $serializedTx)); + } + + /** + * Recover from address with given signature (r, s, v) if didn't set from. + * + * @return string hex encoded ethereum address + */ + public function getFromAddress() + { + $from = $this->offsetGet('from'); + + if ($from) { + return $from; + } + if (!isset($this->privateKey) || !($this->privateKey instanceof KeyPair)) { + // recover from hash + $r = $this->offsetGet('r'); + $s = $this->offsetGet('s'); + $v = $this->offsetGet('v'); + + if (!$r || !$s) { + throw new RuntimeException('Invalid signature r and s.'); + } + $txHash = $this->hash(); + $publicKey = $this->secp256k1->recoverPubKey($txHash, [ + 'r' => $r, + 's' => $s + ], $v); + $publicKey = $publicKey->encode('hex'); + } else { + $publicKey = $this->privateKey->getPublic(false, 'hex'); + } + $from = '0x' . substr($this->util->sha3(substr(hex2bin($publicKey), 1)), 24); + + $this->offsetSet('from', $from); + return $from; + } +} \ No newline at end of file diff --git a/test/unit/TransactionTest.php b/test/unit/TransactionTest.php index fc17b93..f97f9d6 100644 --- a/test/unit/TransactionTest.php +++ b/test/unit/TransactionTest.php @@ -5,6 +5,7 @@ use Test\TestCase; use Web3p\EthereumTx\Transaction; use Web3p\EthereumTx\EIP2930Transaction; +use Web3p\EthereumTx\EIP1559Transaction; class TransactionTest extends TestCase { @@ -374,4 +375,31 @@ public function testEIP2930() $this->assertEquals('01f86604158504a817c8008252089435353535353535353535353535353535353535358080c001a09753969d39f6a5109095d5082d67fc99a05fd66a339ba80934504ff79474e77aa07a907eb764b72b3088a331e7b97c2bad5fd43f1d574ddc80edeb022476454adb', $transaction->serialize()); $this->assertEquals('01f86604158504a817c8008252089435353535353535353535353535353535353535358080c001a09753969d39f6a5109095d5082d67fc99a05fd66a339ba80934504ff79474e77aa07a907eb764b72b3088a331e7b97c2bad5fd43f1d574ddc80edeb022476454adb', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); } + + /** + * testEIP1559 + * see: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md + * + * @return void + */ + public function testEIP1559() + { + $transaction = new EIP1559Transaction([ + 'nonce' => '0x15', + 'to' => '0x3535353535353535353535353535353535353535', + 'gas' => '0x5208', + 'maxPriorityFeePerGas' => '0x4a817c800', + 'maxFeePerGas' => '0x4a817c800', + 'value' => '0x0', + 'chainId' => 4, + 'accessList' => [ + ], + 'data' => '' + ]); + $this->assertEquals('02f86c04158504a817c8008504a817c8008252089435353535353535353535353535353535353535358080c080a03fd48c8a173e9669c33cb5271f03b1af4f030dc8315be8ec9442b7fbdde893c8a010af381dab1df3e7012a3c8421d65a810859a5dd9d58991ad7c07f12d0c651c7', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); + + $transaction = new EIP1559Transaction('0x02f86c04158504a817c8008504a817c8008252089435353535353535353535353535353535353535358080c080a03fd48c8a173e9669c33cb5271f03b1af4f030dc8315be8ec9442b7fbdde893c8a010af381dab1df3e7012a3c8421d65a810859a5dd9d58991ad7c07f12d0c651c7'); + $this->assertEquals('02f86c04158504a817c8008504a817c8008252089435353535353535353535353535353535353535358080c080a03fd48c8a173e9669c33cb5271f03b1af4f030dc8315be8ec9442b7fbdde893c8a010af381dab1df3e7012a3c8421d65a810859a5dd9d58991ad7c07f12d0c651c7', $transaction->serialize()); + $this->assertEquals('02f86c04158504a817c8008504a817c8008252089435353535353535353535353535353535353535358080c080a03fd48c8a173e9669c33cb5271f03b1af4f030dc8315be8ec9442b7fbdde893c8a010af381dab1df3e7012a3c8421d65a810859a5dd9d58991ad7c07f12d0c651c7', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); + } } From 20b622e1e558f14bdf6657cfd6348b408c40c057 Mon Sep 17 00:00:00 2001 From: sc0vu Date: Sun, 29 Aug 2021 04:50:04 +0000 Subject: [PATCH 10/16] Update EIP2930 transaction --- src/EIP2930Transaction.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EIP2930Transaction.php b/src/EIP2930Transaction.php index 82787ac..c83bc87 100644 --- a/src/EIP2930Transaction.php +++ b/src/EIP2930Transaction.php @@ -26,7 +26,7 @@ * use Web3p\EthereumTx\EIP2930Transaction; * * // generate transaction instance with transaction parameters - * $transaction = new Transaction([ + * $transaction = new EIP2930Transaction([ * 'nonce' => '0x01', * 'from' => '0xb60e8dd61c5d32be8058bb8eb970870f07233155', * 'to' => '0xd46e8dd67c5d32be8058bb8eb970870f07244567', @@ -39,7 +39,7 @@ * ]); * * // generate transaction instance with hex encoded transaction - * $transaction = new Transaction('0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83'); + * $transaction = new EIP2930Transaction('0x01f86604158504a817c8008252089435353535353535353535353535353535353535358080c001a09753969d39f6a5109095d5082d67fc99a05fd66a339ba80934504ff79474e77aa07a907eb764b72b3088a331e7b97c2bad5fd43f1d574ddc80edeb022476454adb'); * ``` * * ```php From 94e077550b24d903f70a8060ce92bbcfb3e8fceb Mon Sep 17 00:00:00 2001 From: sc0vu Date: Sun, 29 Aug 2021 06:21:02 +0000 Subject: [PATCH 11/16] Refactor EIP1559/EIP2930 transaction --- src/EIP1559Transaction.php | 297 +---------------------- src/EIP2930Transaction.php | 327 +------------------------- src/TypeTransaction.php | 470 +++++++++++++++++++++++++++++++++++++ 3 files changed, 480 insertions(+), 614 deletions(-) create mode 100755 src/TypeTransaction.php diff --git a/src/EIP1559Transaction.php b/src/EIP1559Transaction.php index 0da02b8..52e44e6 100644 --- a/src/EIP1559Transaction.php +++ b/src/EIP1559Transaction.php @@ -18,9 +18,10 @@ use Elliptic\EC\KeyPair; use ArrayAccess; use Web3p\EthereumUtil\Util; +use Web3p\EthereumTx\TypeTransaction; /** - * It's a instance for generating/serializing ethereum transaction. + * It's a instance for generating/serializing ethereum eip1559 transaction. * * ```php * use Web3p\EthereumTx\EIP1559Transaction; @@ -58,7 +59,7 @@ * @link https://www.web3p.xyz * @filesource https://github.com/web3p/ethereum-tx */ -class EIP1559Transaction implements ArrayAccess +class EIP1559Transaction extends TypeTransaction { /** * Attribute map for keeping order of transaction key/value @@ -140,41 +141,6 @@ class EIP1559Transaction implements ArrayAccess ] ]; - /** - * Raw transaction data - * - * @var array - */ - protected $txData = []; - - /** - * RLP encoding instance - * - * @var \Web3p\RLP\RLP - */ - protected $rlp; - - /** - * secp256k1 elliptic curve instance - * - * @var \Elliptic\EC - */ - protected $secp256k1; - - /** - * Private key instance - * - * @var \Elliptic\EC\KeyPair - */ - protected $privateKey; - - /** - * Ethereum util instance - * - * @var \Web3p\EthereumUtil\Util - */ - protected $util; - /** * Transaction type * @@ -190,216 +156,7 @@ class EIP1559Transaction implements ArrayAccess */ public function __construct($txData=[]) { - $this->rlp = new RLP; - $this->secp256k1 = new EC('secp256k1'); - $this->util = new Util; - - if (is_array($txData)) { - foreach ($txData as $key => $data) { - $this->offsetSet($key, $data); - } - } elseif (is_string($txData)) { - $tx = []; - - if ($this->util->isHex($txData)) { - // check first byte - $txData = $this->util->stripZero($txData); - $firstByteStr = substr($txData, 0, 2); - $firstByte = hexdec($firstByteStr); - if ($this->isTransactionTypeValid($firstByte)) { - $txData = substr($txData, 2); - } - $txData = $this->rlp->decode($txData); - - foreach ($txData as $txKey => $data) { - if (is_int($txKey)) { - if (is_string($data) && strlen($data) > 0) { - $tx[$txKey] = '0x' . $data; - } else { - $tx[$txKey] = $data; - } - } - } - } - $this->txData = $tx; - } - } - - /** - * Return the value in the transaction with given key or return the protected property value if get(property_name} function is existed. - * - * @param string $name key or protected property name - * @return mixed - */ - public function __get(string $name) - { - $method = 'get' . ucfirst($name); - - if (method_exists($this, $method)) { - return call_user_func_array([$this, $method], []); - } - return $this->offsetGet($name); - } - - /** - * Set the value in the transaction with given key or return the protected value if set(property_name} function is existed. - * - * @param string $name key, eg: to - * @param mixed value - * @return void - */ - public function __set(string $name, $value) - { - $method = 'set' . ucfirst($name); - - if (method_exists($this, $method)) { - return call_user_func_array([$this, $method], [$value]); - } - return $this->offsetSet($name, $value); - } - - /** - * Return hash of the ethereum transaction without signature. - * - * @return string hex encoded of the transaction - */ - public function __toString() - { - return $this->hash(false); - } - - /** - * Set the value in the transaction with given key. - * - * @param string $offset key, eg: to - * @param string value - * @return void - */ - public function offsetSet($offset, $value) - { - $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; - - if (is_array($txKey)) { - if (is_array($value)) { - if (!isset($txKey['allowArray']) || (isset($txKey['allowArray']) && $txKey['allowArray'] === false)) { - throw new InvalidArgumentException($offset . ' should\'t be array.'); - } - if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { - // check length - if (isset($txKey['length'])) { - if (count($value) > $txKey['length'] * 2) { - throw new InvalidArgumentException($offset . ' exceeds the length limit.'); - } - } - } - if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { - // check zero - foreach ($value as $key => $v) { - $checkedV = $v ? (string) $v : ''; - if (preg_match('/^0*$/', $checkedV) === 1) { - // set value to empty string - $checkedV = ''; - $value[$key] = $checkedV; - } - } - } - } else { - $checkedValue = ($value) ? (string) $value : ''; - $isHex = $this->util->isHex($checkedValue); - $checkedValue = $this->util->stripZero($checkedValue); - - if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { - // check length - if (isset($txKey['length'])) { - if ($isHex) { - if (strlen($checkedValue) > $txKey['length'] * 2) { - throw new InvalidArgumentException($offset . ' exceeds the length limit.'); - } - } else { - if (strlen($checkedValue) > $txKey['length']) { - throw new InvalidArgumentException($offset . ' exceeds the length limit.'); - } - } - } - } - if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { - // check zero - if (preg_match('/^0*$/', $checkedValue) === 1) { - // set value to empty string - $value = ''; - } - } - } - $this->txData[$txKey['key']] = $value; - } - } - - /** - * Return whether the value is in the transaction with given key. - * - * @param string $offset key, eg: to - * @return bool - */ - public function offsetExists($offset) - { - $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; - - if (is_array($txKey)) { - return isset($this->txData[$txKey['key']]); - } - return false; - } - - /** - * Unset the value in the transaction with given key. - * - * @param string $offset key, eg: to - * @return void - */ - public function offsetUnset($offset) - { - $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; - - if (is_array($txKey) && isset($this->txData[$txKey['key']])) { - unset($this->txData[$txKey['key']]); - } - } - - /** - * Return the value in the transaction with given key. - * - * @param string $offset key, eg: to - * @return mixed value of the transaction - */ - public function offsetGet($offset) - { - $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; - - if (is_array($txKey) && isset($this->txData[$txKey['key']])) { - return $this->txData[$txKey['key']]; - } - return null; - } - - /** - * Return raw ethereum transaction data. - * - * @return array raw ethereum transaction data - */ - public function getTxData() - { - return $this->txData; - } - - /** - * Return whether transaction type is valid (0x0 <= $transactionType <= 0x7f). - * - * @param integer $transactionType - * @return boolean is transaction valid - */ - protected function isTransactionTypeValid(int $transactionType) - { - return $transactionType >= 0 && $transactionType <= 127; + parent::__construct($txData); } /** @@ -409,17 +166,11 @@ protected function isTransactionTypeValid(int $transactionType) */ public function serialize() { - $chainId = $this->offsetGet('chainId'); - // sort tx data if (ksort($this->txData) !== true) { throw new RuntimeException('Cannot sort tx data by keys.'); } - if ($chainId && $chainId > 0) { - $txData = array_fill(0, 12, ''); - } else { - $txData = array_fill(0, 9, ''); - } + $txData = array_fill(0, 12, ''); foreach ($this->txData as $key => $data) { if ($key >= 0) { $txData[$key] = $data; @@ -466,8 +217,6 @@ public function sign(string $privateKey) */ public function hash() { - $chainId = $this->offsetGet('chainId'); - // sort tx data if (ksort($this->txData) !== true) { throw new RuntimeException('Cannot sort tx data by keys.'); @@ -482,40 +231,4 @@ public function hash() $transactionType = $this->transactionType; return $this->util->sha3(hex2bin($transactionType . $serializedTx)); } - - /** - * Recover from address with given signature (r, s, v) if didn't set from. - * - * @return string hex encoded ethereum address - */ - public function getFromAddress() - { - $from = $this->offsetGet('from'); - - if ($from) { - return $from; - } - if (!isset($this->privateKey) || !($this->privateKey instanceof KeyPair)) { - // recover from hash - $r = $this->offsetGet('r'); - $s = $this->offsetGet('s'); - $v = $this->offsetGet('v'); - - if (!$r || !$s) { - throw new RuntimeException('Invalid signature r and s.'); - } - $txHash = $this->hash(); - $publicKey = $this->secp256k1->recoverPubKey($txHash, [ - 'r' => $r, - 's' => $s - ], $v); - $publicKey = $publicKey->encode('hex'); - } else { - $publicKey = $this->privateKey->getPublic(false, 'hex'); - } - $from = '0x' . substr($this->util->sha3(substr(hex2bin($publicKey), 1)), 24); - - $this->offsetSet('from', $from); - return $from; - } } \ No newline at end of file diff --git a/src/EIP2930Transaction.php b/src/EIP2930Transaction.php index c83bc87..3128897 100644 --- a/src/EIP2930Transaction.php +++ b/src/EIP2930Transaction.php @@ -18,9 +18,10 @@ use Elliptic\EC\KeyPair; use ArrayAccess; use Web3p\EthereumUtil\Util; +use Web3p\EthereumTx\TypeTransaction; /** - * It's a instance for generating/serializing ethereum transaction. + * It's a instance for generating/serializing ethereum eip2930 transaction. * * ```php * use Web3p\EthereumTx\EIP2930Transaction; @@ -57,7 +58,7 @@ * @link https://www.web3p.xyz * @filesource https://github.com/web3p/ethereum-tx */ -class EIP2930Transaction implements ArrayAccess +class EIP2930Transaction extends TypeTransaction { /** * Attribute map for keeping order of transaction key/value @@ -133,41 +134,6 @@ class EIP2930Transaction implements ArrayAccess ] ]; - /** - * Raw transaction data - * - * @var array - */ - protected $txData = []; - - /** - * RLP encoding instance - * - * @var \Web3p\RLP\RLP - */ - protected $rlp; - - /** - * secp256k1 elliptic curve instance - * - * @var \Elliptic\EC - */ - protected $secp256k1; - - /** - * Private key instance - * - * @var \Elliptic\EC\KeyPair - */ - protected $privateKey; - - /** - * Ethereum util instance - * - * @var \Web3p\EthereumUtil\Util - */ - protected $util; - /** * Transaction type * @@ -183,216 +149,7 @@ class EIP2930Transaction implements ArrayAccess */ public function __construct($txData=[]) { - $this->rlp = new RLP; - $this->secp256k1 = new EC('secp256k1'); - $this->util = new Util; - - if (is_array($txData)) { - foreach ($txData as $key => $data) { - $this->offsetSet($key, $data); - } - } elseif (is_string($txData)) { - $tx = []; - - if ($this->util->isHex($txData)) { - // check first byte - $txData = $this->util->stripZero($txData); - $firstByteStr = substr($txData, 0, 2); - $firstByte = hexdec($firstByteStr); - if ($this->isTransactionTypeValid($firstByte)) { - $txData = substr($txData, 2); - } - $txData = $this->rlp->decode($txData); - - foreach ($txData as $txKey => $data) { - if (is_int($txKey)) { - if (is_string($data) && strlen($data) > 0) { - $tx[$txKey] = '0x' . $data; - } else { - $tx[$txKey] = $data; - } - } - } - } - $this->txData = $tx; - } - } - - /** - * Return the value in the transaction with given key or return the protected property value if get(property_name} function is existed. - * - * @param string $name key or protected property name - * @return mixed - */ - public function __get(string $name) - { - $method = 'get' . ucfirst($name); - - if (method_exists($this, $method)) { - return call_user_func_array([$this, $method], []); - } - return $this->offsetGet($name); - } - - /** - * Set the value in the transaction with given key or return the protected value if set(property_name} function is existed. - * - * @param string $name key, eg: to - * @param mixed value - * @return void - */ - public function __set(string $name, $value) - { - $method = 'set' . ucfirst($name); - - if (method_exists($this, $method)) { - return call_user_func_array([$this, $method], [$value]); - } - return $this->offsetSet($name, $value); - } - - /** - * Return hash of the ethereum transaction without signature. - * - * @return string hex encoded of the transaction - */ - public function __toString() - { - return $this->hash(false); - } - - /** - * Set the value in the transaction with given key. - * - * @param string $offset key, eg: to - * @param string value - * @return void - */ - public function offsetSet($offset, $value) - { - $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; - - if (is_array($txKey)) { - if (is_array($value)) { - if (!isset($txKey['allowArray']) || (isset($txKey['allowArray']) && $txKey['allowArray'] === false)) { - throw new InvalidArgumentException($offset . ' should\'t be array.'); - } - if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { - // check length - if (isset($txKey['length'])) { - if (count($value) > $txKey['length'] * 2) { - throw new InvalidArgumentException($offset . ' exceeds the length limit.'); - } - } - } - if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { - // check zero - foreach ($value as $key => $v) { - $checkedV = $v ? (string) $v : ''; - if (preg_match('/^0*$/', $checkedV) === 1) { - // set value to empty string - $checkedV = ''; - $value[$key] = $checkedV; - } - } - } - } else { - $checkedValue = ($value) ? (string) $value : ''; - $isHex = $this->util->isHex($checkedValue); - $checkedValue = $this->util->stripZero($checkedValue); - - if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { - // check length - if (isset($txKey['length'])) { - if ($isHex) { - if (strlen($checkedValue) > $txKey['length'] * 2) { - throw new InvalidArgumentException($offset . ' exceeds the length limit.'); - } - } else { - if (strlen($checkedValue) > $txKey['length']) { - throw new InvalidArgumentException($offset . ' exceeds the length limit.'); - } - } - } - } - if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { - // check zero - if (preg_match('/^0*$/', $checkedValue) === 1) { - // set value to empty string - $value = ''; - } - } - } - $this->txData[$txKey['key']] = $value; - } - } - - /** - * Return whether the value is in the transaction with given key. - * - * @param string $offset key, eg: to - * @return bool - */ - public function offsetExists($offset) - { - $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; - - if (is_array($txKey)) { - return isset($this->txData[$txKey['key']]); - } - return false; - } - - /** - * Unset the value in the transaction with given key. - * - * @param string $offset key, eg: to - * @return void - */ - public function offsetUnset($offset) - { - $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; - - if (is_array($txKey) && isset($this->txData[$txKey['key']])) { - unset($this->txData[$txKey['key']]); - } - } - - /** - * Return the value in the transaction with given key. - * - * @param string $offset key, eg: to - * @return mixed value of the transaction - */ - public function offsetGet($offset) - { - $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; - - if (is_array($txKey) && isset($this->txData[$txKey['key']])) { - return $this->txData[$txKey['key']]; - } - return null; - } - - /** - * Return raw ethereum transaction data. - * - * @return array raw ethereum transaction data - */ - public function getTxData() - { - return $this->txData; - } - - /** - * Return whether transaction type is valid (0x0 <= $transactionType <= 0x7f). - * - * @param integer $transactionType - * @return boolean is transaction valid - */ - protected function isTransactionTypeValid(int $transactionType) - { - return $transactionType >= 0 && $transactionType <= 127; + parent::__construct($txData); } /** @@ -402,17 +159,11 @@ protected function isTransactionTypeValid(int $transactionType) */ public function serialize() { - $chainId = $this->offsetGet('chainId'); - // sort tx data if (ksort($this->txData) !== true) { throw new RuntimeException('Cannot sort tx data by keys.'); } - if ($chainId && $chainId > 0) { - $txData = array_fill(0, 11, ''); - } else { - $txData = array_fill(0, 8, ''); - } + $txData = array_fill(0, 11, ''); foreach ($this->txData as $key => $data) { if ($key >= 0) { $txData[$key] = $data; @@ -422,36 +173,6 @@ public function serialize() return $transactionType . $this->rlp->encode($txData); } - /** - * Sign the transaction with given hex encoded private key. - * - * @param string $privateKey hex encoded private key - * @return string hex encoded signed ethereum transaction - */ - public function sign(string $privateKey) - { - if ($this->util->isHex($privateKey)) { - $privateKey = $this->util->stripZero($privateKey); - $ecPrivateKey = $this->secp256k1->keyFromPrivate($privateKey, 'hex'); - } else { - throw new InvalidArgumentException('Private key should be hex encoded string'); - } - $txHash = $this->hash(); - $signature = $ecPrivateKey->sign($txHash, [ - 'canonical' => true - ]); - $r = $signature->r; - $s = $signature->s; - $v = $signature->recoveryParam; - - $this->offsetSet('r', '0x' . $r->toString(16)); - $this->offsetSet('s', '0x' . $s->toString(16)); - $this->offsetSet('v', $v); - $this->privateKey = $ecPrivateKey; - - return $this->serialize(); - } - /** * Return hash of the ethereum transaction with/without signature. * @@ -459,8 +180,6 @@ public function sign(string $privateKey) */ public function hash() { - $chainId = $this->offsetGet('chainId'); - // sort tx data if (ksort($this->txData) !== true) { throw new RuntimeException('Cannot sort tx data by keys.'); @@ -475,40 +194,4 @@ public function hash() $transactionType = $this->transactionType; return $this->util->sha3(hex2bin($transactionType . $serializedTx)); } - - /** - * Recover from address with given signature (r, s, v) if didn't set from. - * - * @return string hex encoded ethereum address - */ - public function getFromAddress() - { - $from = $this->offsetGet('from'); - - if ($from) { - return $from; - } - if (!isset($this->privateKey) || !($this->privateKey instanceof KeyPair)) { - // recover from hash - $r = $this->offsetGet('r'); - $s = $this->offsetGet('s'); - $v = $this->offsetGet('v'); - - if (!$r || !$s) { - throw new RuntimeException('Invalid signature r and s.'); - } - $txHash = $this->hash(); - $publicKey = $this->secp256k1->recoverPubKey($txHash, [ - 'r' => $r, - 's' => $s - ], $v); - $publicKey = $publicKey->encode('hex'); - } else { - $publicKey = $this->privateKey->getPublic(false, 'hex'); - } - $from = '0x' . substr($this->util->sha3(substr(hex2bin($publicKey), 1)), 24); - - $this->offsetSet('from', $from); - return $from; - } } \ No newline at end of file diff --git a/src/TypeTransaction.php b/src/TypeTransaction.php new file mode 100755 index 0000000..4d2cbc0 --- /dev/null +++ b/src/TypeTransaction.php @@ -0,0 +1,470 @@ + + * + * @author Peter Lai + * @license MIT + */ + +namespace Web3p\EthereumTx; + +use InvalidArgumentException; +use RuntimeException; +use Web3p\RLP\RLP; +use Elliptic\EC; +use Elliptic\EC\KeyPair; +use ArrayAccess; +use Web3p\EthereumUtil\Util; + +/** + * It's a base transaction for generating/serializing ethereum type transaction (EIP1559/EIP2930). + * Only use this class to generate new type transaction + * + * @author Peter Lai + * @link https://www.web3p.xyz + * @filesource https://github.com/web3p/ethereum-tx + */ +class TypeTransaction implements ArrayAccess +{ + /** + * Attribute map for keeping order of transaction key/value + * + * @var array + */ + protected $attributeMap = [ + 'from' => [ + 'key' => -1 + ], + 'chainId' => [ + 'key' => 0 + ], + 'nonce' => [ + 'key' => 1, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'gasPrice' => [ + 'key' => 2, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'gasLimit' => [ + 'key' => 3, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'gas' => [ + 'key' => 3, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'to' => [ + 'key' => 4, + 'length' => 20, + 'allowZero' => true, + ], + 'value' => [ + 'key' => 5, + 'length' => 32, + 'allowLess' => true, + 'allowZero' => false + ], + 'data' => [ + 'key' => 6, + 'allowLess' => true, + 'allowZero' => true + ], + 'v' => [ + 'key' => 7, + 'allowZero' => true + ], + 'r' => [ + 'key' => 8, + 'length' => 32, + 'allowZero' => true + ], + 's' => [ + 'key' => 9, + 'length' => 32, + 'allowZero' => true + ] + ]; + + /** + * Raw transaction data + * + * @var array + */ + protected $txData = []; + + /** + * RLP encoding instance + * + * @var \Web3p\RLP\RLP + */ + protected $rlp; + + /** + * secp256k1 elliptic curve instance + * + * @var \Elliptic\EC + */ + protected $secp256k1; + + /** + * Private key instance + * + * @var \Elliptic\EC\KeyPair + */ + protected $privateKey; + + /** + * Ethereum util instance + * + * @var \Web3p\EthereumUtil\Util + */ + protected $util; + + /** + * Transaction type + * + * @var string + */ + protected $transactionType = '00'; + + /** + * construct + * + * @param array|string $txData + * @return void + */ + public function __construct($txData=[]) + { + $this->rlp = new RLP; + $this->secp256k1 = new EC('secp256k1'); + $this->util = new Util; + + if (is_array($txData)) { + foreach ($txData as $key => $data) { + $this->offsetSet($key, $data); + } + } elseif (is_string($txData)) { + $tx = []; + + if ($this->util->isHex($txData)) { + // check first byte + $txData = $this->util->stripZero($txData); + $firstByteStr = substr($txData, 0, 2); + $firstByte = hexdec($firstByteStr); + if ($this->isTransactionTypeValid($firstByte)) { + $txData = substr($txData, 2); + } + $txData = $this->rlp->decode($txData); + + foreach ($txData as $txKey => $data) { + if (is_int($txKey)) { + if (is_string($data) && strlen($data) > 0) { + $tx[$txKey] = '0x' . $data; + } else { + $tx[$txKey] = $data; + } + } + } + } + $this->txData = $tx; + } + } + + /** + * Return the value in the transaction with given key or return the protected property value if get(property_name} function is existed. + * + * @param string $name key or protected property name + * @return mixed + */ + public function __get(string $name) + { + $method = 'get' . ucfirst($name); + + if (method_exists($this, $method)) { + return call_user_func_array([$this, $method], []); + } + return $this->offsetGet($name); + } + + /** + * Set the value in the transaction with given key or return the protected value if set(property_name} function is existed. + * + * @param string $name key, eg: to + * @param mixed value + * @return void + */ + public function __set(string $name, $value) + { + $method = 'set' . ucfirst($name); + + if (method_exists($this, $method)) { + return call_user_func_array([$this, $method], [$value]); + } + return $this->offsetSet($name, $value); + } + + /** + * Return hash of the ethereum transaction without signature. + * + * @return string hex encoded of the transaction + */ + public function __toString() + { + return $this->hash(false); + } + + /** + * Set the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @param string value + * @return void + */ + public function offsetSet($offset, $value) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey)) { + if (is_array($value)) { + if (!isset($txKey['allowArray']) || (isset($txKey['allowArray']) && $txKey['allowArray'] === false)) { + throw new InvalidArgumentException($offset . ' should\'t be array.'); + } + if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { + // check length + if (isset($txKey['length'])) { + if (count($value) > $txKey['length'] * 2) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } + } + if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { + // check zero + foreach ($value as $key => $v) { + $checkedV = $v ? (string) $v : ''; + if (preg_match('/^0*$/', $checkedV) === 1) { + // set value to empty string + $checkedV = ''; + $value[$key] = $checkedV; + } + } + } + } else { + $checkedValue = ($value) ? (string) $value : ''; + $isHex = $this->util->isHex($checkedValue); + $checkedValue = $this->util->stripZero($checkedValue); + + if (!isset($txKey['allowLess']) || (isset($txKey['allowLess']) && $txKey['allowLess'] === false)) { + // check length + if (isset($txKey['length'])) { + if ($isHex) { + if (strlen($checkedValue) > $txKey['length'] * 2) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } else { + if (strlen($checkedValue) > $txKey['length']) { + throw new InvalidArgumentException($offset . ' exceeds the length limit.'); + } + } + } + } + if (!isset($txKey['allowZero']) || (isset($txKey['allowZero']) && $txKey['allowZero'] === false)) { + // check zero + if (preg_match('/^0*$/', $checkedValue) === 1) { + // set value to empty string + $value = ''; + } + } + } + $this->txData[$txKey['key']] = $value; + } + } + + /** + * Return whether the value is in the transaction with given key. + * + * @param string $offset key, eg: to + * @return bool + */ + public function offsetExists($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey)) { + return isset($this->txData[$txKey['key']]); + } + return false; + } + + /** + * Unset the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @return void + */ + public function offsetUnset($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey) && isset($this->txData[$txKey['key']])) { + unset($this->txData[$txKey['key']]); + } + } + + /** + * Return the value in the transaction with given key. + * + * @param string $offset key, eg: to + * @return mixed value of the transaction + */ + public function offsetGet($offset) + { + $txKey = isset($this->attributeMap[$offset]) ? $this->attributeMap[$offset] : null; + + if (is_array($txKey) && isset($this->txData[$txKey['key']])) { + return $this->txData[$txKey['key']]; + } + return null; + } + + /** + * Return raw ethereum transaction data. + * + * @return array raw ethereum transaction data + */ + public function getTxData() + { + return $this->txData; + } + + /** + * Return whether transaction type is valid (0x0 <= $transactionType <= 0x7f). + * + * @param integer $transactionType + * @return boolean is transaction valid + */ + protected function isTransactionTypeValid(int $transactionType) + { + return $transactionType >= 0 && $transactionType <= 127; + } + + /** + * RLP serialize the ethereum transaction. + * + * @return \Web3p\RLP\RLP\Buffer serialized ethereum transaction + */ + public function serialize() + { + // sort tx data + if (ksort($this->txData) !== true) { + throw new RuntimeException('Cannot sort tx data by keys.'); + } + $txData = array_fill(0, 10, ''); + foreach ($this->txData as $key => $data) { + if ($key >= 0) { + $txData[$key] = $data; + } + } + $transactionType = $this->transactionType; + return $transactionType . $this->rlp->encode($txData); + } + + /** + * Sign the transaction with given hex encoded private key. + * + * @param string $privateKey hex encoded private key + * @return string hex encoded signed ethereum transaction + */ + public function sign(string $privateKey) + { + if ($this->util->isHex($privateKey)) { + $privateKey = $this->util->stripZero($privateKey); + $ecPrivateKey = $this->secp256k1->keyFromPrivate($privateKey, 'hex'); + } else { + throw new InvalidArgumentException('Private key should be hex encoded string'); + } + $txHash = $this->hash(); + $signature = $ecPrivateKey->sign($txHash, [ + 'canonical' => true + ]); + $r = $signature->r; + $s = $signature->s; + $v = $signature->recoveryParam; + + $this->offsetSet('r', '0x' . $r->toString(16)); + $this->offsetSet('s', '0x' . $s->toString(16)); + $this->offsetSet('v', $v); + $this->privateKey = $ecPrivateKey; + + return $this->serialize(); + } + + /** + * Return hash of the ethereum transaction with/without signature. + * + * @return string hex encoded hash of the ethereum transaction + */ + public function hash() + { + // sort tx data + if (ksort($this->txData) !== true) { + throw new RuntimeException('Cannot sort tx data by keys.'); + } + $rawTxData = array_fill(0, 7, ''); + foreach ($this->txData as $key => $data) { + if ($key >= 0 && $key < 8) { + $rawTxData[$key] = $data; + } + } + $serializedTx = $this->rlp->encode($rawTxData); + $transactionType = $this->transactionType; + return $this->util->sha3(hex2bin($transactionType . $serializedTx)); + } + + /** + * Recover from address with given signature (r, s, v) if didn't set from. + * + * @return string hex encoded ethereum address + */ + public function getFromAddress() + { + $from = $this->offsetGet('from'); + + if ($from) { + return $from; + } + if (!isset($this->privateKey) || !($this->privateKey instanceof KeyPair)) { + // recover from hash + $r = $this->offsetGet('r'); + $s = $this->offsetGet('s'); + $v = $this->offsetGet('v'); + + if (!$r || !$s) { + throw new RuntimeException('Invalid signature r and s.'); + } + $txHash = $this->hash(); + $publicKey = $this->secp256k1->recoverPubKey($txHash, [ + 'r' => $r, + 's' => $s + ], $v); + $publicKey = $publicKey->encode('hex'); + } else { + $publicKey = $this->privateKey->getPublic(false, 'hex'); + } + $from = '0x' . substr($this->util->sha3(substr(hex2bin($publicKey), 1)), 24); + + $this->offsetSet('from', $from); + return $from; + } +} \ No newline at end of file From 88ea684d26353f8880a3e8aa5f4680a5ea8c53e8 Mon Sep 17 00:00:00 2001 From: sc0vu Date: Sun, 29 Aug 2021 06:27:03 +0000 Subject: [PATCH 12/16] Update README.md --- README.md | 114 +++++++++++------------------------------------------- 1 file changed, 22 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 6826d12..1d9746c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ composer require web3p/ethereum-tx # Usage -Create a transaction: +## Create a transaction ```php use Web3p\EthereumTx\Transaction; @@ -24,7 +24,7 @@ $transaction = new Transaction([ 'gas' => '0x76c0', 'gasPrice' => '0x9184e72a000', 'value' => '0x9184e72a', - 'data' => '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675' + 'data' => '' ]); // with chainId @@ -43,122 +43,52 @@ $transaction = new Transaction([ $transaction = new Transaction('0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83'); ``` -Sign a transaction: -```php -use Web3p\EthereumTx\Transaction; - -$signedTransaction = $transaction->sign('your private key'); -``` - -# API - -### Web3p\EthereumTx\Transaction - -#### sha3 - -Returns keccak256 encoding of given data. - -> It will be removed in the next version. - -`sha3(string $input)` - -String input - -###### Example - -* Encode string. - +## Create a EIP1559 transaction ```php -use Web3p\EthereumTx\Transaction; +use Web3p\EthereumTx\EIP1559Transaction; -$transaction = new Transaction([ +// generate transaction instance with transaction parameters +$transaction = new EIP1559Transaction([ 'nonce' => '0x01', 'from' => '0xb60e8dd61c5d32be8058bb8eb970870f07233155', 'to' => '0xd46e8dd67c5d32be8058bb8eb970870f07244567', + 'maxPriorityFeePerGas' => '0x9184e72a000', + 'maxFeePerGas' => '0x9184e72a000', 'gas' => '0x76c0', - 'gasPrice' => '0x9184e72a000', 'value' => '0x9184e72a', - 'data' => '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675' + 'chainId' => 1, // required + 'accessList' => [], + 'data' => '' ]); -$hashedString = $transaction->sha3('web3p'); ``` -#### serialize - -Returns recursive length prefix encoding of transaction data. - -`serialize()` - -###### Example - -* Serialize the transaction data. - +## Create a EIP2930 transaction: ```php -use Web3p\EthereumTx\Transaction; +use Web3p\EthereumTx\EIP2930Transaction; -$transaction = new Transaction([ +// generate transaction instance with transaction parameters +$transaction = new EIP2930Transaction([ 'nonce' => '0x01', 'from' => '0xb60e8dd61c5d32be8058bb8eb970870f07233155', 'to' => '0xd46e8dd67c5d32be8058bb8eb970870f07244567', 'gas' => '0x76c0', - 'gasPrice' => '0x9184e72a000', 'value' => '0x9184e72a', - 'data' => '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675' + 'chainId' => 1, // required + 'accessList' => [], + 'data' => '' ]); -$serializedTx = $transaction->serialize(); ``` -#### sign - -Returns signed of transaction data. - -`sign(string $privateKey)` - -String privateKey - hexed private key with zero prefixed. - -###### Example - -* Sign the transaction data. - +## Sign a transaction: ```php use Web3p\EthereumTx\Transaction; -$transaction = new Transaction([ - 'nonce' => '0x01', - 'from' => '0xb60e8dd61c5d32be8058bb8eb970870f07233155', - 'to' => '0xd46e8dd67c5d32be8058bb8eb970870f07244567', - 'gas' => '0x76c0', - 'gasPrice' => '0x9184e72a000', - 'value' => '0x9184e72a', - 'data' => '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675' -]); -$signedTx = $transaction->sign($stringPrivateKey); +$signedTransaction = $transaction->sign('your private key'); ``` -#### hash - -Returns keccak256 encoding of serialized transaction data. - -`hash()` - -###### Example - -* Hash serialized transaction data. - -```php -use Web3p\EthereumTx\Transaction; +# API -$transaction = new Transaction([ - 'nonce' => '0x01', - 'from' => '0xb60e8dd61c5d32be8058bb8eb970870f07233155', - 'to' => '0xd46e8dd67c5d32be8058bb8eb970870f07244567', - 'gas' => '0x76c0', - 'gasPrice' => '0x9184e72a000', - 'value' => '0x9184e72a', - 'data' => '0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675' -]); -$hashedTx = $transaction->serialize(); -``` +https://www.web3p.xyz/ethereumtx.html # License MIT From b98241f194ab3268fa5cb95825d60b172b5341bf Mon Sep 17 00:00:00 2001 From: sc0vu Date: Mon, 30 Aug 2021 10:28:26 +0000 Subject: [PATCH 13/16] Update web3p/rlp to 0.3.4 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 748e386..058459a 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ }, "require": { "PHP": "^7.1|^8.0", - "web3p/rlp": "0.3.3", + "web3p/rlp": "0.3.4", "web3p/ethereum-util": "~0.1.3", "kornrunner/keccak": "~1", "simplito/elliptic-php": "~1.0.6" From 83d9305ba4782bc310b4b4e6f86024e27198d8e2 Mon Sep 17 00:00:00 2001 From: sc0vu Date: Tue, 23 Nov 2021 07:31:53 +0000 Subject: [PATCH 14/16] Fix #41: hash didn't return transaction hash --- src/EIP1559Transaction.php | 16 +++++++++++----- src/EIP2930Transaction.php | 16 +++++++++++----- src/TypeTransaction.php | 16 +++++++++++----- test/unit/TransactionTest.php | 1 + 4 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/EIP1559Transaction.php b/src/EIP1559Transaction.php index 52e44e6..d68aafa 100644 --- a/src/EIP1559Transaction.php +++ b/src/EIP1559Transaction.php @@ -213,18 +213,24 @@ public function sign(string $privateKey) /** * Return hash of the ethereum transaction with/without signature. * + * @param bool $includeSignature hash with signature * @return string hex encoded hash of the ethereum transaction */ - public function hash() + public function hash(bool $includeSignature=false) { // sort tx data if (ksort($this->txData) !== true) { throw new RuntimeException('Cannot sort tx data by keys.'); } - $rawTxData = array_fill(0, 9, ''); - foreach ($this->txData as $key => $data) { - if ($key >= 0 && $key < 9) { - $rawTxData[$key] = $data; + if ($includeSignature) { + $length = 12; + } else { + $length = 9; + } + $rawTxData = array_fill(0, $length, ''); + for ($key = 0; $key < $length; $key++) { + if (isset($this->txData[$key])) { + $rawTxData[$key] = $this->txData[$key]; } } $serializedTx = $this->rlp->encode($rawTxData); diff --git a/src/EIP2930Transaction.php b/src/EIP2930Transaction.php index 3128897..4d6b41a 100644 --- a/src/EIP2930Transaction.php +++ b/src/EIP2930Transaction.php @@ -176,18 +176,24 @@ public function serialize() /** * Return hash of the ethereum transaction with/without signature. * + * @param bool $includeSignature hash with signature * @return string hex encoded hash of the ethereum transaction */ - public function hash() + public function hash(bool $includeSignature=false) { // sort tx data if (ksort($this->txData) !== true) { throw new RuntimeException('Cannot sort tx data by keys.'); } - $rawTxData = array_fill(0, 8, ''); - foreach ($this->txData as $key => $data) { - if ($key >= 0 && $key < 8) { - $rawTxData[$key] = $data; + if ($includeSignature) { + $length = 11; + } else { + $length = 8; + } + $rawTxData = array_fill(0, $length, ''); + for ($key = 0; $key < $length; $key++) { + if (isset($this->txData[$key])) { + $rawTxData[$key] = $this->txData[$key]; } } $serializedTx = $this->rlp->encode($rawTxData); diff --git a/src/TypeTransaction.php b/src/TypeTransaction.php index 4d2cbc0..6e479f2 100755 --- a/src/TypeTransaction.php +++ b/src/TypeTransaction.php @@ -413,18 +413,24 @@ public function sign(string $privateKey) /** * Return hash of the ethereum transaction with/without signature. * + * @param bool $includeSignature hash with signature * @return string hex encoded hash of the ethereum transaction */ - public function hash() + public function hash(bool $includeSignature=false) { // sort tx data if (ksort($this->txData) !== true) { throw new RuntimeException('Cannot sort tx data by keys.'); } - $rawTxData = array_fill(0, 7, ''); - foreach ($this->txData as $key => $data) { - if ($key >= 0 && $key < 8) { - $rawTxData[$key] = $data; + if ($includeSignature) { + $length = 10; + } else { + $length = 7; + } + $rawTxData = array_fill(0, $length, ''); + for ($key = 0; $key < $length; $key++) { + if (isset($this->txData[$key])) { + $rawTxData[$key] = $this->txData[$key]; } } $serializedTx = $this->rlp->encode($rawTxData); diff --git a/test/unit/TransactionTest.php b/test/unit/TransactionTest.php index f97f9d6..ace9e81 100644 --- a/test/unit/TransactionTest.php +++ b/test/unit/TransactionTest.php @@ -396,6 +396,7 @@ public function testEIP1559() ], 'data' => '' ]); + var_dump($transaction->hash()); $this->assertEquals('02f86c04158504a817c8008504a817c8008252089435353535353535353535353535353535353535358080c080a03fd48c8a173e9669c33cb5271f03b1af4f030dc8315be8ec9442b7fbdde893c8a010af381dab1df3e7012a3c8421d65a810859a5dd9d58991ad7c07f12d0c651c7', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); $transaction = new EIP1559Transaction('0x02f86c04158504a817c8008504a817c8008252089435353535353535353535353535353535353535358080c080a03fd48c8a173e9669c33cb5271f03b1af4f030dc8315be8ec9442b7fbdde893c8a010af381dab1df3e7012a3c8421d65a810859a5dd9d58991ad7c07f12d0c651c7'); From d2d16042576a0e553d01e0506de591fe0eed2939 Mon Sep 17 00:00:00 2001 From: sc0Vu Date: Wed, 27 Apr 2022 14:29:00 +0800 Subject: [PATCH 15/16] Update web3p/rlp 0.3.5 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 058459a..d3fc310 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ }, "require": { "PHP": "^7.1|^8.0", - "web3p/rlp": "0.3.4", + "web3p/rlp": "0.3.5", "web3p/ethereum-util": "~0.1.3", "kornrunner/keccak": "~1", "simplito/elliptic-php": "~1.0.6" From 71ccf317d21fc8bd80baeaadcffeb0bd18a4d416 Mon Sep 17 00:00:00 2001 From: sc0Vu Date: Sun, 18 Dec 2022 09:52:32 +0800 Subject: [PATCH 16/16] Update return type of serialize function to string --- src/EIP1559Transaction.php | 2 +- src/EIP2930Transaction.php | 2 +- src/Transaction.php | 2 +- src/TypeTransaction.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EIP1559Transaction.php b/src/EIP1559Transaction.php index d68aafa..92b1abe 100644 --- a/src/EIP1559Transaction.php +++ b/src/EIP1559Transaction.php @@ -162,7 +162,7 @@ public function __construct($txData=[]) /** * RLP serialize the ethereum transaction. * - * @return \Web3p\RLP\RLP\Buffer serialized ethereum transaction + * @return string hex encoded of the serialized ethereum transaction */ public function serialize() { diff --git a/src/EIP2930Transaction.php b/src/EIP2930Transaction.php index 4d6b41a..a54ae4e 100644 --- a/src/EIP2930Transaction.php +++ b/src/EIP2930Transaction.php @@ -155,7 +155,7 @@ public function __construct($txData=[]) /** * RLP serialize the ethereum transaction. * - * @return \Web3p\RLP\RLP\Buffer serialized ethereum transaction + * @return string hex encoded of the serialized ethereum transaction */ public function serialize() { diff --git a/src/Transaction.php b/src/Transaction.php index bca994d..f7dfefa 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -343,7 +343,7 @@ public function getTxData() /** * RLP serialize the ethereum transaction. * - * @return \Web3p\RLP\RLP\Buffer serialized ethereum transaction + * @return string hex encoded of the serialized ethereum transaction */ public function serialize() { diff --git a/src/TypeTransaction.php b/src/TypeTransaction.php index 6e479f2..a64d626 100755 --- a/src/TypeTransaction.php +++ b/src/TypeTransaction.php @@ -362,7 +362,7 @@ protected function isTransactionTypeValid(int $transactionType) /** * RLP serialize the ethereum transaction. * - * @return \Web3p\RLP\RLP\Buffer serialized ethereum transaction + * @return string hex encoded of the serialized ethereum transaction */ public function serialize() {