diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ffac8d2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[root] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_size = 4 +indent_style = tab +trim_trailing_whitespace = true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d28dfac --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,38 @@ +name: Release +on: + push: + tags: + - "v*.*.*" + branches: + - master + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Update changelog + id: changelog + uses: requarks/changelog-action@v1 + with: + token: ${{ github.token }} + tag: ${{ github.ref_name }} + + - name: Create release + uses: ncipollo/release-action@v1.12.0 + with: + allowUpdates: true + draft: false + makeLatest: true + name: ${{ github.ref_name }} + body: ${{ steps.changelog.outputs.changes }} + token: ${{ github.token }} + + - name: Commit CHANGELOG.md + uses: stefanzweifel/git-auto-commit-action@v4 + with: + branch: master + commit_message: 'docs: update CHANGELOG.md for ${{ github.ref_name }} [skip ci]' + file_pattern: CHANGELOG.md \ No newline at end of file diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..841c806 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,34 @@ +name: Security + +on: + # Run on all pushes and on all pull requests. + push: + pull_request: + # Also run this workflow every Monday at 6:00. + schedule: + - cron: '0 6 * * 1' + # Allow manually triggering the workflow. + workflow_dispatch: + +# Cancels all previous workflow runs for the same branch that have not yet completed. +concurrency: + # The concurrency group contains the workflow name and the branch name. + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + security: + name: 'Security check' + runs-on: ubuntu-latest + + # Don't run the cronjob in this workflow on forks. + if: github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'refactor-ring') + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # This action checks the `composer.lock` file against known security vulnerabilities in the dependencies. + # https://github.com/marketplace/actions/the-php-security-checker + - name: Run Security Check + uses: symfonycorp/security-checker-action@v5 \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..0d1c385 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,48 @@ +name: "Unit Tests" + +on: + pull_request: + push: + branches: + - master + +jobs: + tests: + runs-on: ${{ matrix.os }} + + env: + PHP_EXTENSIONS: none, bcmath, ctype, curl, dom, json, gmp, mbstring, opcache, simplexml, sockets, tokenizer, xml, xmlwriter + PHP_INI_VALUES: memory_limit=-1, assert.exception=1, zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + php: [8.1, 8.2, 8.3] + dependency-version: [prefer-lowest, prefer-stable] + + name: Tests PHP${{ matrix.php }} - ${{ matrix.os }} - ${{ matrix.dependency-version }} + + steps: + - uses: actions/checkout@v4 + - name: Cache dependencies + uses: actions/cache@v4 + with: + path: ~/.composer/cache/files + key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: ${{ matrix.coverage-driver }} + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: ${{ env.PHP_INI_VALUES }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install Composer dependencies + run: composer update --${{ matrix.dependency-version }} --no-ansi --no-interaction --no-progress --prefer-dist + + - name: Run unit tests with PHPUnit + run: composer run test:unit \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3ccfd66..0e49122 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ vendor/ -build \ No newline at end of file +build +.phpunit.cache +tests/_reports \ No newline at end of file diff --git a/PUBLISHING.md b/PUBLISHING.md new file mode 100644 index 0000000..ced06ac --- /dev/null +++ b/PUBLISHING.md @@ -0,0 +1,23 @@ +# Publishing a release + +This document describes the process of publishing a new version of the package. + +1. Update the version in the `composer.json` file. +2. Commit the changes. + - ```bash + git commit -m "chore: bump version to " + ``` + + Where `` is the new version number (e.g. `1.2.3`). +3. Create a new tag. + - ```bash + git tag v + ``` + + Where `` is the new version number (e.g. `1.2.3`). +4. Push the changes. + - ```bash + git push origin master --tags + ``` + +A release will automatically be created. diff --git a/README.md b/README.md index 5762cb3..50e8cb0 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,147 @@ [![PHPCS PSR-12](https://img.shields.io/badge/PHPCS-PSR–12-226146.svg)](https://www.php-fig.org/psr/psr-12/) [![PHPStan ](.github/phpstan.svg)](https://phpstan.org/) -# Monero Library -A Monero library written in PHP by the [Monero Integrations](https://monerointegrations.com) [team](https://github.com/monero-integrations/monerophp/graphs/contributors). +# Monero-Crypto -## How It Works -This library has 3 main parts: +A Monero cryptography library written in modern PHP 8 by the [Monero Integrations team](https://monerointegrations.com) and [contributors]( +https://github.com/monero-integrations/monerophp/graphs/contributors). -1. A Monero daemon JSON RPC API wrapper, `daemonRPC.php` -2. A Monero wallet (`monero-wallet-rpc`) JSON RPC API wrapper, `walletRPC.php` -3. A Monero/Cryptonote toolbox, `cryptonote.php`, with both lower level functions used in Monero related cryptography and higher level methods for things like generating Monero private/public keys. +## Features -In addition to these features, there are other lower-level libraries included for portability, *eg.* an ed25519 library, a SHA3 library, *etc.* +This library implements/interfaces various cryptographic functions used in Monero, such as: + +- Monero's base58 encoding +- Monero's mnemonic seeds +- Keccak hash function +- Cryptonote functions on the Edwards25519 curve +- Variably-sized integers (Varint) + +Higher-level abstractions are additionally provided for things like generating Monero private/public keys, subaddresses, etc. ## Preview ![Preview](https://user-images.githubusercontent.com/4107993/38056594-b6cd6e14-3291-11e8-96e2-a771b0e9cee3.png) +## Getting Started + +The minimum PHP version required is 8.1.0. Please make sure you also have [Composer](https://getcomposer.org/) installed. + +You can check your PHP version by running: + +```bash +php -v +``` + +### Extensions + +The `bcmath` extension is required. + +It is **strongly recommended** to use the `gmp` extension for about 100x faster calculations (as opposed to BCMath). + +To check what extensions are installed, run: + +```bash +php -m +``` + +### Installation + +#### From Packagist + +```bash +composer require monero-integrations/monero-crypto +``` + +#### From Source + +```bash +git clone https://github.com/monero-integrations/monerophp.git +cd monerophp +composer install +``` + +### Usage + +From here, you can use the library in your PHP project. For example: + +```php +require 'vendor/autoload.php'; + +// To get a list of available mnemonic wordlists +use MoneroIntegrations\MoneroCrypto\Mnemonic; +$wordlists = Mnemonic::getWordsetList(); + +echo "Available wordlists: " . implode(', ', $wordlists) . PHP_EOL; +``` + ## Documentation -Documentation can be found in the [`/docs`](https://github.com/sneurlax/monerophp/tree/master/docs) folder. +Documentation is still a work-in-progress, but the library is well-documented with PHPDoc comments. + +Current documentation can be found in the [`/docs`](./docs/) folder. + +## Development + +The project uses several development tools to ensure code quality and consistency: + +1. PHP CodeSniffer: Used to check the code style against the PSR-12 standard. +2. PHPStan: Static analysis tool to find bugs and improve code quality. +3. PHPUnit: Testing framework for running unit tests. +4. Laravel Pint: Code style fixer for PSR-12 compliance. + +### Running Tests + +To ensure everything is working correctly, you can run the tests and code quality checks using Composer scripts: + +#### Lint Code + +```bash +composer lint +``` + +#### Test Lint + +Run linting on your code and test files: + +```bash +composer test:lint +``` -## Configuration -### Requirements - - Monero daemon (`monerod`) - - Webserver with PHP, for example XMPP, Apache, or NGINX - - cURL PHP extension for JSON RPC API(s) - - GMP PHP extension for about 100x faster calculations (as opposed to BCMath) +#### Analyze Code with PHPStan -Debian (or Ubuntu) are recommended. - -### Getting Started +```bash +composer test:phpstan +``` + +#### Run Unit Tests -1. Start the Monero daemon (`monerod`) on testnet. ```bash -monerod --testnet --detach +composer test:unit ``` -2. Start the Monero wallet RPC interface (`monero-wallet-rpc`) on testnet. +#### Run All Tests and Checks + +This will run linting, PHPStan analysis, and unit tests: + ```bash -monero-wallet-rpc --testnet --rpc-bind-port 28083 --disable-rpc-login --wallet-dir /path/to/wallet/directory +composer test ``` -3. Edit `example.php` with your the IP address of `monerod` and `monero-wallet-rpc` (use `127.0.0.1:28081` and `127.0.0.1:28083`, respectively, for testnet.) +### Standards + +We follow the PSR-12 coding standard. Please make sure your code adheres to these guidelines. You can use Laravel Pint to automatically fix code style issues. + +### Contributions + +We welcome contributions! If you have an idea or fix, please follow these steps: + +1. Fork the repository +2. Create a branch with your changes +3. Make your changes +4. Submit a pull request (PR) with a clear description of the changes + +Please ensure your code passes all tests and adheres to our coding standards before submitting a pull request. + +For any questions or issues, feel free to reach out to the maintainers or open an issue on GitHub. + +## License -4. Serve `example.php` with your webserver (*eg.* XMPP, Apache/Apache2, NGINX, *etc.*) and navigate to it. If everything has been set up correctly, information from your Monero daemon and wallet will be displayed. +This library is licensed under the MIT License. See the [LICENSE](./LICENSE) file for more information. \ No newline at end of file diff --git a/composer.json b/composer.json index 4110a7f..48f57d4 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { - "name": "monero-integrations/monerophp", - "description": "A Monero library written in PHP by the Monero-Integrations team.", - "keywords": ["Monero", "XMR", "monerod", "monero-wallet-rpc", "cryptonote", "JSONRPC", "JSON-RPC", "cryptocurrency"], + "name": "monero-integrations/monero-crypto", + "description": "Monero library for low-level cryptography functions.", + "keywords": ["Monero", "XMR", "cryptonote", "cryptocurrency", "crypto", "cryptography"], "homepage": "https://github.com/monero-integrations/monerophp", "type": "library", "version" : "1.0.1", @@ -21,46 +21,56 @@ } ], "config": { + "sort-packages": true, + "preferred-install": "dist", "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true, "phpstan/extension-installer": true } }, "require": { - "php": ">=7.3", + "php": "^8.1.0", "ext-bcmath": "*", - "ext-curl": "*", - "ext-json": "*", "kornrunner/keccak": "^1.1" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "*", "phpstan/extension-installer": "*", - "brainmaestro/composer-git-hooks": "^2.8", - "squizlabs/php_codesniffer": "*" + "squizlabs/php_codesniffer": "*", + "phpstan/phpstan": "^1.10.25", + "phpunit/phpunit": "^10.3.3", + "laravel/pint": "^1.10.3" }, "suggest": { - "ext-gmp": "Used to have a multiple math precision for generating address" + "ext-gmp": "For much faster elliptic curve operations, install the GMP extension." }, "autoload": { "psr-4": { - "MoneroIntegrations\\MoneroPhp\\": "src/" + "MoneroIntegrations\\MoneroCrypto\\": "src/" } }, - "extra": { - "hooks": { - "pre-commit": [ - "vendor/bin/phpcbf" - ] + "autoload-dev": { + "psr-4": { + "MoneroIntegrations\\MoneroCrypto\\Tests\\": "tests/" } }, + "minimum-stability": "dev", + "prefer-stable": true, "scripts": { - "post-install-cmd": "cghooks add --ignore-lock", - "post-update-cmd": "cghooks update", - "lint": [ - "phpcbf || true", - "phpcs || true", - "phpstan analyse --memory-limit 1G" + "lint": "pint --preset psr12 -v", + "test:lint": "pint --preset psr12 --test -v", + "test:phpstan": "phpstan analyse --ansi --memory-limit=1G", + "test:unit": "phpunit --testsuite unit", + "test": [ + "@test:lint", + "@test:phpstan", + "@test:unit" ] - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/monero-integrations/monerophp.git" + } + ] } diff --git a/composer.lock b/composer.lock index 379a184..67f8e5b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "eaabe8b13af6e0555ab3b8be36c45778", + "content-hash": "5eb631d32784cc8e314ff205c2fdeea8", "packages": [ { "name": "kornrunner/keccak", @@ -57,16 +57,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -80,9 +80,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -120,7 +117,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -136,83 +133,10 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-06-19T12:30:46+00:00" } ], "packages-dev": [ - { - "name": "brainmaestro/composer-git-hooks", - "version": "v2.8.5", - "source": { - "type": "git", - "url": "https://github.com/BrainMaestro/composer-git-hooks.git", - "reference": "ffed8803690ac12214082120eee3441b00aa390e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/BrainMaestro/composer-git-hooks/zipball/ffed8803690ac12214082120eee3441b00aa390e", - "reference": "ffed8803690ac12214082120eee3441b00aa390e", - "shasum": "" - }, - "require": { - "php": "^5.6 || >=7.0", - "symfony/console": "^3.2 || ^4.0 || ^5.0" - }, - "require-dev": { - "ext-json": "*", - "friendsofphp/php-cs-fixer": "^2.9", - "phpunit/phpunit": "^5.7 || ^7.0" - }, - "bin": [ - "cghooks" - ], - "type": "library", - "extra": { - "hooks": { - "pre-commit": "composer check-style", - "pre-push": [ - "composer test", - "appver=$(grep -o -E '\\d.\\d.\\d' cghooks)", - "tag=$(git describe --tags --abbrev=0)", - "if [ \"$tag\" != \"v$appver\" ]; then", - "echo \"The most recent tag $tag does not match the application version $appver\\n\"", - "tag=${tag#v}", - "sed -i -E \"s/$appver/$tag/\" cghooks", - "exit 1", - "fi" - ] - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "BrainMaestro\\GitHooks\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ezinwa Okpoechi", - "email": "brainmaestro@outlook.com" - } - ], - "description": "Easily manage git hooks in your composer config", - "keywords": [ - "HOOK", - "composer", - "git" - ], - "support": { - "issues": "https://github.com/BrainMaestro/composer-git-hooks/issues", - "source": "https://github.com/BrainMaestro/composer-git-hooks/tree/v2.8.5" - }, - "time": "2021-02-08T15:59:11+00:00" - }, { "name": "dealerdirect/phpcodesniffer-composer-installer", "version": "v1.0.0", @@ -292,994 +216,1886 @@ "time": "2023-01-05T11:28:13+00:00" }, { - "name": "phpstan/extension-installer", - "version": "1.3.1", + "name": "laravel/pint", + "version": "v1.17.1", "source": { "type": "git", - "url": "https://github.com/phpstan/extension-installer.git", - "reference": "f45734bfb9984c6c56c4486b71230355f066a58a" + "url": "https://github.com/laravel/pint.git", + "reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f45734bfb9984c6c56c4486b71230355f066a58a", - "reference": "f45734bfb9984c6c56c4486b71230355f066a58a", + "url": "https://api.github.com/repos/laravel/pint/zipball/b5b6f716db298671c1dfea5b1082ec2c0ae7064f", + "reference": "b5b6f716db298671c1dfea5b1082ec2c0ae7064f", "shasum": "" }, "require": { - "composer-plugin-api": "^2.0", - "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.9.0" + "ext-json": "*", + "ext-mbstring": "*", + "ext-tokenizer": "*", + "ext-xml": "*", + "php": "^8.1.0" }, "require-dev": { - "composer/composer": "^2.0", - "php-parallel-lint/php-parallel-lint": "^1.2.0", - "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "PHPStan\\ExtensionInstaller\\Plugin" + "friendsofphp/php-cs-fixer": "^3.59.3", + "illuminate/view": "^10.48.12", + "larastan/larastan": "^2.9.7", + "laravel-zero/framework": "^10.4.0", + "mockery/mockery": "^1.6.12", + "nunomaduro/termwind": "^1.15.1", + "pestphp/pest": "^2.34.8" }, + "bin": [ + "builds/pint" + ], + "type": "project", "autoload": { "psr-4": { - "PHPStan\\ExtensionInstaller\\": "src/" + "App\\": "app/", + "Database\\Seeders\\": "database/seeders/", + "Database\\Factories\\": "database/factories/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "Composer plugin for automatic installation of PHPStan extensions", + "authors": [ + { + "name": "Nuno Maduro", + "email": "enunomaduro@gmail.com" + } + ], + "description": "An opinionated code formatter for PHP.", + "homepage": "https://laravel.com", + "keywords": [ + "format", + "formatter", + "lint", + "linter", + "php" + ], "support": { - "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.3.1" + "issues": "https://github.com/laravel/pint/issues", + "source": "https://github.com/laravel/pint" }, - "time": "2023-05-24T08:59:17+00:00" + "time": "2024-08-01T09:06:33+00:00" }, { - "name": "phpstan/phpstan", - "version": "1.10.53", + "name": "myclabs/deep-copy", + "version": "1.12.0", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "0c48595cce15f67d6a7faf30e9d13c775e6e9875" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0c48595cce15f67d6a7faf30e9d13c775e6e9875", - "reference": "0c48595cce15f67d6a7faf30e9d13c775e6e9875", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": "^7.1 || ^8.0" }, "conflict": { - "phpstan/phpstan-shim": "*" + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, - "bin": [ - "phpstan", - "phpstan.phar" - ], "type": "library", "autoload": { "files": [ - "bootstrap.php" - ] + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPStan - PHP Static Analysis Tool", + "description": "Create deep copies (clones) of your objects", "keywords": [ - "dev", - "static analysis" + "clone", + "copy", + "duplicate", + "object", + "object graph" ], "support": { - "docs": "https://phpstan.org/user-guide/getting-started", - "forum": "https://github.com/phpstan/phpstan/discussions", - "issues": "https://github.com/phpstan/phpstan/issues", - "security": "https://github.com/phpstan/phpstan/security/policy", - "source": "https://github.com/phpstan/phpstan-src" + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" }, "funding": [ { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", "type": "tidelift" } ], - "time": "2024-01-05T13:55:38+00:00" + "time": "2024-06-12T14:39:25+00:00" }, { - "name": "psr/container", - "version": "1.1.1", + "name": "nikic/php-parser", + "version": "v5.1.0", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { - "php": ">=7.2.0" + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" }, + "bin": [ + "bin/php-parse" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, "autoload": { "psr-4": { - "Psr\\Container\\": "src/" + "PhpParser\\": "lib/PhpParser" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Nikita Popov" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "A PHP parser written in PHP", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "parser", + "php" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "3.8.0", + "name": "phar-io/manifest", + "version": "2.0.4", "source": { "type": "git", - "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7" + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7", - "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "2.0.x-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { - "name": "Greg Sherwood", - "role": "Former lead" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" }, { - "name": "Juliette Reinders Folmer", - "role": "Current lead" + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" }, { - "name": "Contributors", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "keywords": [ - "phpcs", - "standards", - "static analysis" - ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { - "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", - "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", - "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", - "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, "funding": [ { - "url": "https://github.com/PHPCSStandards", - "type": "github" - }, - { - "url": "https://github.com/jrfnl", + "url": "https://github.com/theseer", "type": "github" - }, - { - "url": "https://opencollective.com/php_codesniffer", - "type": "open_collective" } ], - "time": "2023-12-08T12:32:31+00:00" + "time": "2024-03-03T12:33:53+00:00" }, { - "name": "symfony/console", - "version": "v5.4.34", + "name": "phar-io/version", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "4b4d8cd118484aa604ec519062113dd87abde18c" + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/4b4d8cd118484aa604ec519062113dd87abde18c", - "reference": "4b4d8cd118484aa604ec519062113dd87abde18c", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" + "php": "^7.2 || ^8.0" }, "type": "library", "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v5.4.34" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" }, { - "url": "https://github.com/fabpot", - "type": "github" + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" } ], - "time": "2023-12-08T13:33:03+00:00" + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v2.5.2", + "name": "phpstan/extension-installer", + "version": "1.4.1", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" + "url": "https://github.com/phpstan/extension-installer.git", + "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f6b87faf9fc7978eab2f7919a8760bc9f58f9203", + "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203", "shasum": "" }, "require": { - "php": ">=7.1" + "composer-plugin-api": "^2.0", + "php": "^7.2 || ^8.0", + "phpstan/phpstan": "^1.9.0" }, - "type": "library", + "require-dev": { + "composer/composer": "^2.0", + "php-parallel-lint/php-parallel-lint": "^1.2.0", + "phpstan/phpstan-strict-rules": "^0.11 || ^0.12 || ^1.0" + }, + "type": "composer-plugin", "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } + "class": "PHPStan\\ExtensionInstaller\\Plugin" }, "autoload": { - "files": [ - "function.php" - ] + "psr-4": { + "PHPStan\\ExtensionInstaller\\": "src/" + } }, "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": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", + "description": "Composer plugin for automatic installation of PHPStan extensions", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + "issues": "https://github.com/phpstan/extension-installer/issues", + "source": "https://github.com/phpstan/extension-installer/tree/1.4.1" }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" + "time": "2024-06-10T08:20:49+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "name": "phpstan/phpstan", + "version": "1.11.9", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "url": "https://github.com/phpstan/phpstan.git", + "reference": "e370bcddadaede0c1716338b262346f40d296f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e370bcddadaede0c1716338b262346f40d296f82", + "reference": "e370bcddadaede0c1716338b262346f40d296f82", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "provide": { - "ext-ctype": "*" + "php": "^7.2|^8.0" }, - "suggest": { - "ext-ctype": "For best performance" + "conflict": { + "phpstan/phpstan-shim": "*" }, + "bin": [ + "phpstan", + "phpstan.phar" + ], "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, "autoload": { "files": [ "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", + "description": "PHPStan - PHP Static Analysis Tool", "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" + "dev", + "static analysis" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/ondrejmirtes", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" + "url": "https://github.com/phpstan", + "type": "github" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-08-01T16:25:18+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.28.0", + "name": "phpunit/php-code-coverage", + "version": "10.1.15", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "875e90aeea2777b6f135677f618529449334a612" + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", - "reference": "875e90aeea2777b6f135677f618529449334a612", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", + "reference": "5da8b1728acd1e6ffdf2ff32ffbdfd04307f26ae", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-text-template": "^3.0", + "sebastian/code-unit-reverse-lookup": "^3.0", + "sebastian/complexity": "^3.0", + "sebastian/environment": "^6.0", + "sebastian/lines-of-code": "^2.0", + "sebastian/version": "^4.0", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.1" }, "suggest": { - "ext-intl": "For best performance" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-main": "10.1-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" + "coverage", + "testing", + "xunit" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.15" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-06-29T08:25:15+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", + "name": "phpunit/php-file-iterator", + "version": "4.1.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", + "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" }, - "suggest": { - "ext-intl": "For best performance" + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-main": "4.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "classmap": [ - "Resources/stubs" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" + "filesystem", + "iterator" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2023-08-31T06:24:48+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.28.0", + "name": "phpunit/php-invoker", + "version": "4.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5" + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/fe2f306d1d9d346a7fee353d0d5012e401e984b5", - "reference": "fe2f306d1d9d346a7fee353d0d5012e401e984b5", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-pcntl": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-main": "4.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" - }, "classmap": [ - "Resources/stubs" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", - "homepage": "https://symfony.com", + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "process" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.28.0" + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "name": "phpunit/php-text-template", + "version": "3.0.1", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.28-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "dev-main": "3.0-dev" } }, "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, "classmap": [ - "Resources/stubs" + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" - }, - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", - "homepage": "https://symfony.com", + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "template" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2023-08-31T14:07:24+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.5.2", + "name": "phpunit/php-timer", + "version": "6.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" + "php": ">=8.1" }, - "suggest": { - "symfony/service-implementation": "" + "require-dev": { + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "dev-main": "6.0-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } + "classmap": [ + "src/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Generic abstractions related to writing services", - "homepage": "https://symfony.com", + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "timer" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" }, "funding": [ { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2023-02-03T06:57:52+00:00" }, { - "name": "symfony/string", - "version": "v5.4.34", + "name": "phpunit/phpunit", + "version": "10.5.29", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "e3f98bfc7885c957488f443df82d97814a3ce061" + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "8e9e80872b4e8064401788ee8a32d40b4455318f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/e3f98bfc7885c957488f443df82d97814a3ce061", - "reference": "e3f98bfc7885c957488f443df82d97814a3ce061", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/8e9e80872b4e8064401788ee8a32d40b4455318f", + "reference": "8e9e80872b4e8064401788ee8a32d40b4455318f", "shasum": "" }, "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" - }, - "conflict": { - "symfony/translation-contracts": ">=3.0" + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.15", + "phpunit/php-file-iterator": "^4.1.0", + "phpunit/php-invoker": "^4.0.0", + "phpunit/php-text-template": "^3.0.1", + "phpunit/php-timer": "^6.0.0", + "sebastian/cli-parser": "^2.0.1", + "sebastian/code-unit": "^2.0.0", + "sebastian/comparator": "^5.0.1", + "sebastian/diff": "^5.1.1", + "sebastian/environment": "^6.1.0", + "sebastian/exporter": "^5.1.2", + "sebastian/global-state": "^6.0.2", + "sebastian/object-enumerator": "^5.0.0", + "sebastian/recursion-context": "^5.0.0", + "sebastian/type": "^4.0.0", + "sebastian/version": "^4.0.1" }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files" }, + "bin": [ + "phpunit" + ], "type": "library", + "extra": { + "branch-alias": { + "dev-main": "10.5-dev" + } + }, "autoload": { "files": [ - "Resources/functions.php" + "src/Framework/Assert/Functions.php" ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "BSD-3-Clause" ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" + "phpunit", + "testing", + "xunit" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.34" + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.29" }, "funding": [ { - "url": "https://symfony.com/sponsor", + "url": "https://phpunit.de/sponsors.html", "type": "custom" }, { - "url": "https://github.com/fabpot", + "url": "https://github.com/sebastianbergmann", "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2023-12-09T13:20:28+00:00" + "time": "2024-07-30T11:08:00+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-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 for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:12:49+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-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": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:58:43+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-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/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:59:15+00:00" + }, + { + "name": "sebastian/comparator", + "version": "5.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2db5010a484d53ebf536087a70b4a5423c102372" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2db5010a484d53ebf536087a70b4a5423c102372", + "reference": "2db5010a484d53ebf536087a70b4a5423c102372", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-08-14T13:18:12+00:00" + }, + { + "name": "sebastian/complexity", + "version": "3.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "68ff824baeae169ec9f2137158ee529584553799" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", + "reference": "68ff824baeae169ec9f2137158ee529584553799", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.2-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 for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:37:17+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "sebastian/environment", + "version": "6.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.1-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": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-23T08:47:14+00:00" + }, + { + "name": "sebastian/exporter", + "version": "5.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:17:12+00:00" + }, + { + "name": "sebastian/global-state", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.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": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:19:19+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", + "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.0-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 for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-21T08:38:20+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-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/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:08:32+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-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/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:06:18+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:05:40+00:00" + }, + { + "name": "sebastian/type", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-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": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T07:10:45+00:00" + }, + { + "name": "sebastian/version", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-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", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-07T11:34:05+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.10.2", + "source": { + "type": "git", + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" + }, + "bin": [ + "bin/phpcbf", + "bin/phpcs" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "keywords": [ + "phpcs", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" + }, + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-07-21T23:26:44+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.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", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": [], - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": ">=7.3", - "ext-bcmath": "*", - "ext-curl": "*", - "ext-json": "*" + "php": "^8.1.0", + "ext-bcmath": "*" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/docs/Mnemonic.md b/docs/Mnemonic.md new file mode 100644 index 0000000..3fb990f --- /dev/null +++ b/docs/Mnemonic.md @@ -0,0 +1,247 @@ +## Mnemonic Class + +### Namespace + +```php +namespace MoneroIntegrations\MoneroCrypto; +``` + +### Class: Mnemonic + +The `Mnemonic` class provides methods for encoding, decoding, validating checksums, and managing wordsets for Monero wallets. + +#### Method: `checksum` + +Given a mnemonic seed word list, return the seed checksum. + +```php +/** + * Given a mnemonic seed word list, return the seed checksum. + * + * @param array $words + * @param int $prefix_len + * @return string + */ +public static function checksum(array $words, int $prefix_len): string +``` + +##### Example: + +```php +$seed = [ + "sighting", "pavements", "mocked", "dilute", + "lunar", "king", "bygones", "niece", "tonic", + "noises", "ostrich", "ecstatic", "hoax", "gawk", + "bays", "wiring", "total", "emulate", "update", + "bypass", "asked", "pager", "geometry", "haystack", + "geometry" +]; +$prefixLen = 3; +$checksum = Mnemonic::checksum($seed, $prefixLen); // Returns "geometry" +``` + +#### Method: `validateChecksum` + +Given a mnemonic seed word list, check if checksum word is valid. + +```php +/** + * Given a mnemonic seed word list, check if checksum word is valid. + * + * @param array $words + * @param int $prefix_len + * @return bool + */ +public static function validateChecksum(array $words, int $prefix_len): bool +``` + +Example: + +```php +$isValid = Mnemonic::validateChecksum($seed, $prefixLen); // Returns true +``` + +#### Method: `swapEndian` + +Given an 8 byte word (or shorter), pads to 8 bytes (adds 0 at left) and reverses endian byte order. + +```php +/** + * Given an 8 byte word (or shorter), pads to 8 bytes (adds 0 at left) and reverses endian byte order. + * + * @param string $word + * @return string + */ +public static function swapEndian(string $word): string +``` + +Example: + +```php +$word = "12345678"; +$swapped = Mnemonic::swapEndian($word); // Returns "78563412" +``` + +#### Method: `encode` + +Given a hexadecimal key string (seed), return its mnemonic representation. + +```php +/** + * Given a hexadecimal key string (seed), return its mnemonic representation. + * + * @param string $seed + * @param string|null $wordset_name + * @return array + */ +public static function encode(string $seed, ?string $wordset_name = null): array +``` + +Example: + +```php +$seedHex = "f2750ee6e1f326f485fdc34ac517a69cbd9c72c5766151626039f0eeab40e109"; +$encodedMnemonic = Mnemonic::encode($seedHex, "english"); // Returns an array of mnemonic words +``` + +#### Method: `encodeWithChecksum` + +Given a hexadecimal key string (seed), return its mnemonic representation plus an extra checksum word. + +```php +/** + * Given a hexadecimal key string (seed), return its mnemonic representation plus an extra checksum word. + * + * @param string $seed + * @param string|null $wordset_name + * @return array + */ +public static function encodeWithChecksum(string $seed, ?string $wordset_name = null): array +``` + +Example: + +```php +$seedHex = "f2750ee6e1f326f485fdc34ac517a69cbd9c72c5766151626039f0eeab40e109"; +$mnemonicWithChecksum = Mnemonic::encodeWithChecksum($seedHex, "english"); // Returns an array of mnemonic words with checksum +``` + +#### Method: `decode` + +Given a mnemonic word list, return a hexadecimal encoded string (seed). + +```php +/** + * Given a mnemonic word list, return a hexadecimal encoded string (seed). + * + * @param array $wlist + * @param string|null $wordset_name + * @return string + * @throws Exception if decoding fails. + */ +public static function decode(array $wlist, ?string $wordset_name = null): string +``` + +Example: + +```php +$mnemonicWords = ["sighting", "pavements", "mocked", ...]; +$decodedSeedHex = Mnemonic::decode($mnemonicWords, "english"); // Returns the hexadecimal seed string +``` + +#### Method: `getWordsetByName` + +Given a wordset identifier, returns the full wordset. + +```php +/** + * Given a wordset identifier, returns the full wordset. + * + * @param string|null $name + * @return array + * @throws Exception if the wordset name is invalid. + */ +public static function getWordsetByName(?string $name = null): array +``` + +Example: + +```php +$wordsetDetails = Mnemonic::getWordsetByName("english"); // Returns details of the English wordset +``` + +#### Method: `findWordsetByMnemonic` + +Given a mnemonic array of words, returns the name of the matching wordset. + +```php +/** + * Given a mnemonic array of words, returns the name of the matching wordset. + * + * @param array $mnemonic + * @return string|null + * @throws Exception if more than one wordset matches the mnemonic. + */ +public static function findWordsetByMnemonic(array $mnemonic): ?string +``` + +Example: + +```php +$matchedWordset = Mnemonic::findWordsetByMnemonic($mnemonicWords); // Returns "english" if the mnemonic matches +``` + +#### Method: `getWordsetList` + +Return a list of available wordset names. + +```php +/** + * Return a list of available wordset names. + * + * @return array + */ +public static function getWordsetList(): array +``` + +Example: + +```php +$wordsetList = Mnemonic::getWordsetList(); // Returns an array of available wordset names +``` + +#### Method: `getWordsets` + +Return a list of available wordsets with details. + +```php +/** + * Return a list of available wordsets with details. + * + * @return array>> + */ +public static function getWordsets(): array +``` + +Example: + +```php +$allWordsets = Mnemonic::getWordsets(); // Returns an array of all available wordsets with details +``` + +### Interfaces + +#### Interface: `Wordset` + +The `Wordset` interface is implemented by classes representing different wordsets. + +```php +interface Wordset { + public static function name(): string; + public static function englishName(): string; + public static function prefixLength(): int; + public static function words(): array; +} +``` + +This interface defines methods that must be implemented by each wordset class. It provides information about the name, English name, prefix length, and word list of a wordset. \ No newline at end of file diff --git a/example.php b/example.php deleted file mode 100644 index c7e4f5f..0000000 --- a/example.php +++ /dev/null @@ -1,164 +0,0 @@ - '127.0.0.1', 'port' => 28081]) // Passing parameters in as array; parameters can be in any order and all are optional. -$getblockcount = $daemonRPC->getblockcount(); -$on_getblockhash = $daemonRPC->on_getblockhash(42069); -// $getblocktemplate = $daemonRPC->getblocktemplate('9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn', 60); -// $submitblock = $daemonRPC->submitblock($block_blob); -$getlastblockheader = $daemonRPC->getlastblockheader(); -// $getblockheaderbyhash = $daemonRPC->getblockheaderbyhash('fc7ba2a76071f609e39517dc0388a77f3e27cc2f98c8e933918121b729ee6f27'); -// $getblockheaderbyheight = $daemonRPC->getblockheaderbyheight(696969); -// $getblock_by_hash = $daemonRPC->getblock_by_hash('fc7ba2a76071f609e39517dc0388a77f3e27cc2f98c8e933918121b729ee6f27'); -// $getblock_by_height = $daemonRPC->getblock_by_height(696969); -$get_connections = $daemonRPC->get_connections(); -$get_info = $daemonRPC->get_info(); -// $hardfork_info = $daemonRPC->hardfork_info(); -// $setbans = $daemonRPC->setbans('8.8.8.8'); -// $getbans = $daemonRPC->getbans(); - -require_once('src/walletRPC.php'); - -$walletRPC = new walletRPC('127.0.0.1', 28083); // Change to match your wallet (monero-wallet-rpc) IP address and port; 18083 is the customary port for mainnet, 28083 for testnet, 38083 for stagenet -// $daemonRPC = new walletRPC(['host' => '127.0.0.1', 'port' => 28081]) // Passing parameters in as array; parameters can be in any order and all are optional. -$create_wallet = $walletRPC->create_wallet('monero_wallet', ''); // Creates a new wallet named monero_wallet with no passphrase. Comment this line and edit the next line to use your own wallet -$open_wallet = $walletRPC->open_wallet('monero_wallet', ''); -$get_address = $walletRPC->get_address(); -$get_accounts = $walletRPC->get_accounts(); -$get_balance = $walletRPC->get_balance(); -// $create_address = $walletRPC->create_address(0, 'This is an example subaddress label'); // Create a subaddress on account 0 -// $tag_accounts = $walletRPC->tag_accounts([0], 'This is an example account tag'); -// $get_height = $walletRPC->get_height(); -// $transfer = $walletRPC->transfer(1, '9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn'); // First account generated from mnemonic 'gang dying lipstick wonders howls begun uptight humid thirsty irony adept umpire dusted update grunt water iceberg timber aloof fudge rift clue umpire venomous thirsty' -// $transfer = $walletRPC->transfer(['address' => '9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn', 'amount' => 1, 'priority' => 1]); // Passing parameters in as array -// $transfer = $walletRPC->transfer(['destinations' => ['amount' => 1, 'address' => '9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn', 'amount' => 2, 'address' => 'BhASuWq4HcBL1KAwt4wMBDhkpwseFe6pNaq5DWQnMwjBaFL8isMZzcEfcF7x6Vqgz9EBY66g5UBrueRFLCESojoaHaTPsjh'], 'priority' => 1]); // Multiple payments in one transaction -// $sweep_all = $walletRPC->sweep_all('9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn'); -// $sweep_all = $walletRPC->sweep_all(['address' => '9sZABNdyWspcpsCPma1eUD5yM3efTHfsiCx3qB8RDYH9UFST4aj34s5Ygz69zxh8vEBCCqgxEZxBAEC4pyGkN4JEPmUWrxn', 'priority' => 1]); -// $get_transfers = $walletRPC->get_transfers('in', true); -// $incoming_transfers = $walletRPC->incoming_transfers('all'); -// $mnemonic = $walletRPC->mnemonic(); - -?> - - -

- - - MoneroPHP - -

-

MoneroPHP was developed by SerHack and the Monero-Integrations team! Please report any issues or request additional features at github.com/monero-integrations/monerophp.

- -

daemonRPC.php example

-

Note: not all methods shown, nor all results from each method.

-
-
getblockcount()
-
-

Status:

-

Height:

-
-
on_getblockhash(42069)
-
-

Block hash:

-
-
getlastblockheader()
-
-

Current block hash:

-

Previous block hash:

-
-
get_connections()
-
-

Connections:

- ' . $peer['address'] . ' (' . ( $peer['height'] == $getblockcount['count'] ? 'synced' : ( $peer['height'] > $getblockcount['count'] ? 'ahead; syncing' : 'behind; syncing') ). ')

'; } ?> -
-
get_info()
-
-

Difficulty:

-

Cumulative difficulty:

-
-
- -

walletRPC.php example

-

Note: not all methods shown, nor all results from each method.

-
- -
get_accounts()
-
-

Accounts:

- Account ' . $account['account_index'] . ': ' . $account['base_address'] . '
'; - echo 'Balance: ' . $account['balance'] / pow(10, 12) . ' (' . $account['unlocked_balance'] / pow(10, 12) . ' unlocked)
'; - echo ( $account['label'] ) ? 'Label: ' . $account['label'] . '
' : ''; - echo ( $account['tag'] ) ? 'Tag: ' . $account['tag'] . '
' : ''; - echo '

'; - } - ?> -
-
get_balance()
-
-

Balance:

-

Unlocked balance:

-
-
- - - - - - - \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..732f98f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./src + + + + + tests/unit + + + \ No newline at end of file diff --git a/pint.json b/pint.json new file mode 100644 index 0000000..49c5a7f --- /dev/null +++ b/pint.json @@ -0,0 +1,6 @@ +{ + "preset": "psr12", + "rules": { + "declare_strict_types": true + } +} \ No newline at end of file diff --git a/src/Base58.php b/src/Base58.php new file mode 100644 index 0000000..2b369aa --- /dev/null +++ b/src/Base58.php @@ -0,0 +1,164 @@ + $data + */ + private static function uint8beTo64(array $data): BigInteger + { + $res = new BigInteger(0); + $size = count($data); + + for ($i = 0; $i < $size; $i++) { + $res = $res->mul(256)->add(ord($data[$i])); + } + + return $res; + } + + /** + * Converts a decimal string to a hexadecimal string. + */ + private static function decToHex(BigInteger $dec): string + { + $res = ''; + while (!$dec->equals(0)) { + $remainder = $dec->mod(16); + $dec = $dec->div(16); + $res = dechex((int)$remainder->toDec()) . $res; + } + + return $res; + } + + /** + * Encodes a block of data into Monero's Base58. + * @param array $data + * @param array $res + * @param int $res_offset + * + * @return array + */ + private static function encodeBlock(array $data, array $res, int $res_offset): array + { + $length = count($data); + + if ($length < 1 || $length > self::FULL_ENCODED_BLOCK_SIZE) { + throw new Exception("Invalid block length: " . $length); + } + + $num = self::uint8beTo64($data); + $i = self::ENCODED_BLOCK_SIZES[$length] - 1; + + while (!$num->equals(0)) { + $remainder = $num->mod(self::ALPHABET_SIZE); + $num = $num->div(self::ALPHABET_SIZE); + $res[$res_offset + $i] = self::ALPHABET[$remainder->toNumber()]; + $i--; + } + + return $res; + } + + /** + * Encodes a hexadecimal string into Monero's Base58. + */ + public static function encode(string $hex): string + { + $data = str_split(hex2bin($hex)); + $length = count($data); + + if ($length === 0) { + return ''; + } + + $full_block_count = (int)floor($length / self::BLOCK_SIZE); + $last_block_size = $length % self::BLOCK_SIZE; + $res_size = $full_block_count * self::FULL_ENCODED_BLOCK_SIZE + self::ENCODED_BLOCK_SIZES[$last_block_size]; + + $res = array_fill(0, $res_size, self::ALPHABET[0]); + + for ($i = 0; $i < $full_block_count; $i++) { + $res = self::encodeBlock(array_slice($data, $i * self::BLOCK_SIZE, self::BLOCK_SIZE), $res, $i * self::FULL_ENCODED_BLOCK_SIZE); + } + + if ($last_block_size > 0) { + $res = self::encodeBlock(array_slice($data, $full_block_count * self::BLOCK_SIZE, $last_block_size), $res, $full_block_count * self::FULL_ENCODED_BLOCK_SIZE); + } + + return implode('', $res); + } + + /** + * Decodes a block of data from Monero's Base58. + * @param array $data + * @param array $res + * + * @return array + */ + private static function decodeBlock(array $data, array $res): array + { + $length = count($data); + + if ($length < 1 || $length > self::FULL_ENCODED_BLOCK_SIZE) { + throw new Exception("Invalid block length: " . $length); + } + + $num = new BigInteger(0); + + for ($i = 0; $i < $length; $i++) { + $char = $data[$i]; + $char_value = strpos(self::ALPHABET, $char); + + if ($char_value === false) { + throw new Exception("Invalid character: " . $char); + } + + $num = $num->mul(self::ALPHABET_SIZE)->add($char_value); + } + + $res = array_merge($res, str_split(pack('H*', self::decToHex($num)))); + return $res; + } + + /** + * Decodes a string in Monero's Base58 to a hexadecimal string. + */ + public static function decode(string $base58): string + { + $data = str_split($base58); + $length = count($data); + + if ($length === 0) { + return ''; + } + + $full_block_count = (int)floor($length / self::FULL_ENCODED_BLOCK_SIZE); + $last_block_size = $length % self::FULL_ENCODED_BLOCK_SIZE; + + $res = []; + for ($i = 0; $i < $full_block_count; $i++) { + $res = self::decodeBlock(array_slice($data, $i * self::FULL_ENCODED_BLOCK_SIZE, self::FULL_ENCODED_BLOCK_SIZE), $res); + } + + if ($last_block_size > 0) { + $res = self::decodeBlock(array_slice($data, $full_block_count * self::FULL_ENCODED_BLOCK_SIZE, $last_block_size), $res); + } + + return bin2hex(implode('', $res)); + } +} diff --git a/src/BigInteger.php b/src/BigInteger.php new file mode 100644 index 0000000..3234646 --- /dev/null +++ b/src/BigInteger.php @@ -0,0 +1,759 @@ +value = $base === true ? $value : BigInteger::getGmp($value, $base); + } + + public static function createSafe(mixed $value = 0, mixed $base = 10): BigInteger|bool + { + try { + return new BigInteger($value, $base); + } catch (\Exception $e) { + return false; + } + } + + public static function isGmp(mixed $var): bool + { + if (is_resource($var)) { + return get_resource_type($var) == "GMP integer"; + } + if (class_exists("GMP") && $var instanceof \GMP) { + return true; + } + return false; + } + + public static function getGmp(mixed $value = 0, int $base = 10): \GMP + { + if ($value instanceof BigInteger) { + return $value->value; + } + if (BigInteger::isGmp($value)) { + return $value; + } + $type = gettype($value); + if ($type == "integer") { + $gmp = gmp_init($value); + // @phpstan-ignore-next-line + if ($gmp === false) { + throw new \ValueError("Cannot initialize"); + } + return $gmp; + } + if ($type == "string") { + if ($base != 2 && $base != 10 && $base != 16 && $base != 256) { + throw new \ValueError("Unsupported BigInteger base"); + } + if ($base == 256) { + $value = bin2hex((string)$value); + $base = 16; + } + $level = error_reporting(); + error_reporting(0); + $gmp = gmp_init($value, $base); + error_reporting($level); + // @phpstan-ignore-next-line + if ($gmp === false) { + throw new \ValueError("Cannot initialize"); + } + return $gmp; + } + throw new \ValueError("Unsupported value, only string and integer are allowed, receive " . $type . ($type == "object" ? ", class: " . get_class($value) : "")); + } + + public function toDec(): string + { + return gmp_strval($this->value, 10); + } + + public function toHex(): string + { + $hex = gmp_strval($this->value, 16); + return strlen($hex) % 2 == 1 ? "0" . $hex : $hex; + } + + public function toBytes(): string + { + return hex2bin($this->toHex()); + } + + public function toBase(int $base): string + { + if ($base < 2 || $base > 62) { + throw new \ValueError("Invalid base"); + } + return gmp_strval($this->value, $base); + } + + public function toBits(): string + { + return gmp_strval($this->value, 2); + } + + public function toString(int $base = 10): string + { + if ($base == 2) { + return $this->toBits(); + } + if ($base == 10) { + return $this->toDec(); + } + if ($base == 16) { + return $this->toHex(); + } + if ($base == 256) { + return $this->toBytes(); + } + return $this->toBase($base); + } + + public function __toString(): string + { + return $this->toString(); + } + + public function toNumber(): int + { + return gmp_intval($this->value); + } + + public function add(mixed $x): BigInteger + { + return new BigInteger(gmp_add($this->value, BigInteger::getGmp($x)), true); + } + + public function sub(mixed $x): BigInteger + { + return new BigInteger(gmp_sub($this->value, BigInteger::getGmp($x)), true); + } + + public function mul(mixed $x): BigInteger + { + return new BigInteger(gmp_mul($this->value, BigInteger::getGmp($x)), true); + } + + public function div(mixed $x): BigInteger + { + return new BigInteger(gmp_div_q($this->value, BigInteger::getGmp($x)), true); + } + + public function divR(mixed $x): BigInteger + { + return new BigInteger(gmp_div_r($this->value, BigInteger::getGmp($x)), true); + } + + /** + * @return array{0: BigInteger, 1: BigInteger} + */ + public function divQR(mixed $x): array + { + $res = gmp_div_qr($this->value, BigInteger::getGmp($x)); + return [new BigInteger($res[0], true), new BigInteger($res[1], true)]; + } + + public function mod(mixed $x): BigInteger + { + return new BigInteger(gmp_mod($this->value, BigInteger::getGmp($x)), true); + } + + public function gcd(mixed $x): BigInteger + { + return new BigInteger(gmp_gcd($this->value, BigInteger::getGmp($x)), true); + } + + public function modInverse(mixed $x): BigInteger|bool + { + $res = gmp_invert($this->value, BigInteger::getGmp($x)); + return $res === false ? false : new BigInteger($res, true); + } + + public function pow(mixed $x): BigInteger + { + return new BigInteger(gmp_pow($this->value, (new BigInteger($x))->toNumber()), true); + } + + public function powMod(mixed $x, mixed $n): BigInteger + { + return new BigInteger(gmp_powm($this->value, BigInteger::getGmp($x), BigInteger::getGmp($n)), true); + } + + public function abs(): BigInteger + { + return new BigInteger(gmp_abs($this->value), true); + } + + public function neg(): BigInteger + { + return new BigInteger(gmp_neg($this->value), true); + } + + public function binaryAnd(mixed $x): BigInteger + { + return new BigInteger(gmp_and($this->value, BigInteger::getGmp($x)), true); + } + + public function binaryOr(mixed $x): BigInteger + { + return new BigInteger(gmp_or($this->value, BigInteger::getGmp($x)), true); + } + + public function binaryXor(mixed $x): BigInteger + { + return new BigInteger(gmp_xor($this->value, BigInteger::getGmp($x)), true); + } + + public function setbit(int $index, bool $bitOn = true): BigInteger + { + $cpy = gmp_init(gmp_strval($this->value, 16), 16); + gmp_setbit($cpy, $index, $bitOn); + return new BigInteger($cpy, true); + } + + public function testbit(int $index): bool + { + return gmp_testbit($this->value, $index); + } + + public function scan0(int $start): int + { + return gmp_scan0($this->value, $start); + } + + public function scan1(int $start): int + { + return gmp_scan1($this->value, $start); + } + + public function cmp(mixed $x): int + { + return gmp_cmp($this->value, BigInteger::getGmp($x)); + } + + public function equals(mixed $x): bool + { + return $this->cmp($x) === 0; + } + + public function sign(): int + { + return gmp_sign($this->value); + } + + public function shiftLeft(int $n): BigInteger + { + return $this->mul((new static(2))->pow($n)); + } + + public function shiftRight(int $n): BigInteger + { + $newInt = $this->div((new static(2))->pow($n)); + + if ($newInt->add($n)->cmp(0) < 0) { + return $newInt->sub(1); + } + + return $newInt; + } + } +} elseif (S_MATH_BIGINTEGER_MODE == "bcmath") { + + if (!extension_loaded("bcmath")) { + throw new \ValueError("Extension bcmath not loaded"); + } + + final class BigInteger + { + public static string $chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv"; + public string $value; + + public function __construct(mixed $value = 0, mixed $base = 10) + { + $this->value = $base === true ? $value : BigInteger::getBC($value, $base); + } + + public static function createSafe(mixed $value = 0, mixed $base = 10): BigInteger|bool + { + try { + return new BigInteger($value, $base); + } catch (\Exception $e) { + return false; + } + } + + public static function checkBinary(string $str): bool + { + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $c = ord($str[$i]); + if (($i != 0 || $c != 45) && ($c < 48 || $c > 49)) { + return false; + } + } + return true; + } + + public static function checkDecimal(string $str): bool + { + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $c = ord($str[$i]); + if (($i != 0 || $c != 45) && ($c < 48 || $c > 57)) { + return false; + } + } + return true; + } + + public static function checkHex(string $str): bool + { + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $c = ord($str[$i]); + if (($i != 0 || $c != 45) && ($c < 48 || $c > 57) && ($c < 65 || $c > 70) && ($c < 97 || $c > 102)) { + return false; + } + } + return true; + } + + public static function getBC(mixed $value = 0, int $base = 10): string + { + if ($value instanceof BigInteger) { + return $value->value; + } + $type = gettype($value); + if ($type == "integer") { + return strval($value); + } + if ($type == "string") { + if ($base == 2) { + $value = str_replace(" ", "", $value); + if (!BigInteger::checkBinary($value)) { + throw new \ValueError("Invalid characters"); + } + $minus = $value[0] == "-"; + if ($minus) { + $value = substr($value, 1); + } + $len = strlen($value); + $m = "1"; + $res = "0"; + for ($i = $len - 1; $i >= 0; $i -= 8) { + $h = $i - 7 < 0 ? substr($value, 0, $i + 1) : substr($value, $i - 7, 8); + $res = bcadd($res, bcmul((string)bindec($h), $m, 0), 0); + $m = bcmul($m, "256", 0); + } + return ($minus ? "-" : "") . $res; + } + if ($base == 10) { + $value = str_replace(" ", "", $value); + if (!BigInteger::checkDecimal($value)) { + throw new \ValueError("Invalid characters"); + } + return $value; + } + if ($base == 16) { + $value = str_replace(" ", "", $value); + if (strtolower(substr($value, 0, 2)) === '0x') { + $value = str_replace("0x", "", $value); + } + + if (!BigInteger::checkHex($value)) { + throw new \ValueError("Invalid characters"); + } + $minus = $value[0] == "-"; + if ($minus) { + $value = substr($value, 1); + } + $len = strlen($value); + $m = "1"; + $res = "0"; + for ($i = $len - 1; $i >= 0; $i -= 2) { + $h = $i == 0 ? "0" . substr($value, 0, 1) : substr($value, $i - 1, 2); + $res = bcadd($res, bcmul((string)hexdec($h), $m, 0), 0); + $m = bcmul($m, "256", 0); + } + return ($minus ? "-" : "") . $res; + } + if ($base == 256) { + $len = strlen($value); + $m = "1"; + $res = "0"; + for ($i = $len - 1; $i >= 0; $i -= 6) { + $h = $i - 5 < 0 ? substr($value, 0, $i + 1) : substr($value, $i - 5, 6); + $res = bcadd($res, bcmul(base_convert(bin2hex($h), 16, 10), $m, 0), 0); + $m = bcmul($m, "281474976710656", 0); + } + return $res; + } + throw new \ValueError("Unsupported BigInteger base"); + } + throw new \ValueError("Unsupported value, only string and integer are allowed, receive " . $type . ($type == "object" ? ", class: " . get_class($value) : "")); + } + + public function toDec(): string + { + return $this->value; + } + + public function toHex(): string + { + return bin2hex($this->toBytes()); + } + + public function toBytes(): string + { + $value = ""; + $current = $this->value; + if ($current[0] == "-") { + $current = substr($current, 1); + } + while (bccomp($current, "0", 0) > 0) { + $temp = bcmod($current, "281474976710656"); + $value = hex2bin(str_pad(base_convert($temp, 10, 16), 12, "0", STR_PAD_LEFT)) . $value; + $current = bcdiv($current, "281474976710656", 0); + } + return ltrim($value, chr(0)); + } + + public function toBase(int $base): string + { + if ($base < 2 || $base > 62) { + throw new \ValueError("Invalid base"); + } + $value = ''; + $current = $this->value; + $base = BigInteger::getBC($base); + + if ($current[0] == '-') { + $current = substr($current, 1); + } + + while (bccomp($current, '0', 0) > 0) { + $v = bcmod($current, $base); + // @phpstan-ignore-next-line + $value = BigInteger::$chars[$v] . $value; + $current = bcdiv($current, $base, 0); + } + return $value; + } + + public function toBits(): string + { + $bytes = $this->toBytes(); + $res = ""; + $len = strlen($bytes); + for ($i = 0; $i < $len; $i++) { + $b = decbin(ord($bytes[$i])); + $res .= strlen($b) != 8 ? str_pad($b, 8, "0", STR_PAD_LEFT) : $b; + } + $res = ltrim($res, "0"); + return strlen($res) == 0 ? "0" : $res; + } + + public function toString(int $base = 10): string + { + if ($base == 2) { + return $this->toBits(); + } + if ($base == 10) { + return $this->toDec(); + } + if ($base == 16) { + return $this->toHex(); + } + if ($base == 256) { + return $this->toBytes(); + } + return $this->toBase($base); + } + + public function __toString(): string + { + return $this->toString(); + } + + public function toNumber(): int + { + return intval($this->value); + } + + public function add(mixed $x): BigInteger + { + return new BigInteger(bcadd($this->value, BigInteger::getBC($x), 0), true); + } + + public function sub(mixed $x): BigInteger + { + return new BigInteger(bcsub($this->value, BigInteger::getBC($x), 0), true); + } + + public function mul(mixed $x): BigInteger + { + return new BigInteger(bcmul($this->value, BigInteger::getBC($x), 0), true); + } + + public function div(mixed $x): BigInteger + { + return new BigInteger(bcdiv($this->value, BigInteger::getBC($x), 0), true); + } + + public function divR(mixed $x): BigInteger + { + return new BigInteger(bcmod($this->value, BigInteger::getBC($x)), true); + } + + /** + * @return array{0: BigInteger, 1: BigInteger} + */ + public function divQR(mixed $x): array + { + return [ + $this->div($x), + $this->divR($x) + ]; + } + + public function mod(mixed $x): BigInteger + { + $xv = BigInteger::getBC($x); + $mod = bcmod($this->value, $xv); + if ($mod[0] == "-") { + $mod = bcadd($mod, $xv[0] == "-" ? substr($xv, 1) : $xv, 0); + } + return new BigInteger($mod, true); + } + + /** + * @return array{gcd: BigInteger, x: BigInteger, y: BigInteger} + */ + public function extendedGcd(mixed $n): array + { + $u = $this->value; + $v = (new BigInteger($n))->abs()->value; + + $a = "1"; + $b = "0"; + $c = "0"; + $d = "1"; + + while (bccomp($v, "0", 0) != 0) { + $q = bcdiv($u, $v, 0); + + $temp = $u; + $u = $v; + $v = bcsub($temp, bcmul($v, $q, 0), 0); + + $temp = $a; + $a = $c; + $c = bcsub($temp, bcmul($a, $q, 0), 0); + + $temp = $b; + $b = $d; + $d = bcsub($temp, bcmul($b, $q, 0), 0); + } + + return [ + "gcd" => new BigInteger($u, true), + "x" => new BigInteger($a, true), + "y" => new BigInteger($b, true) + ]; + } + + public function gcd(mixed $x): BigInteger + { + return $this->extendedGcd($x)["gcd"]; + } + + public function modInverse(mixed $n): BigInteger|bool + { + $original = $n; + $n = (new BigInteger($n))->abs(); + + if ($this->sign() < 0) { + $temp = $this->abs(); + $temp = $temp->modInverse($original); + return $n->sub($temp->value); + } + + $ex = $this->extendedGcd($original); + extract($ex); + + if (!$gcd->equals(1)) { + return false; + } + + $x = $x->sign() < 0 ? $x->add($original) : $x; + + // @phpstan-ignore-next-line + return $this->sign() < 0 ? $n->sub($x) : $x; + } + + public function pow(mixed $x): BigInteger + { + return new BigInteger(bcpow($this->value, BigInteger::getBC($x), 0), true); + } + + public function powMod(mixed $x, mixed $n): BigInteger + { + return new BigInteger(bcpowmod($this->value, BigInteger::getBC($x), BigInteger::getBC($n), 0), true); + } + + public function abs(): BigInteger + { + return new BigInteger($this->value[0] == "-" ? substr($this->value, 1) : $this->value, true); + } + + public function neg(): BigInteger + { + return new BigInteger($this->value[0] == "-" ? substr($this->value, 1) : "-" . $this->value, true); + } + + public function binaryAnd(mixed $x): BigInteger + { + $left = $this->toBytes(); + $right = (new BigInteger($x))->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return new BigInteger($left & $right, 256); + } + + public function binaryOr(mixed $x): BigInteger + { + $left = $this->toBytes(); + $right = (new BigInteger($x))->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return new BigInteger($left | $right, 256); + } + + public function binaryXor(mixed $x): BigInteger + { + $left = $this->toBytes(); + $right = (new BigInteger($x))->toBytes(); + + $length = max(strlen($left), strlen($right)); + + $left = str_pad($left, $length, chr(0), STR_PAD_LEFT); + $right = str_pad($right, $length, chr(0), STR_PAD_LEFT); + + return new BigInteger($left ^ $right, 256); + } + + public function setbit(int $index, bool $bitOn = true): BigInteger + { + $bits = $this->toBits(); + $bits[strlen($bits) - $index - 1] = $bitOn ? "1" : "0"; + return new BigInteger($bits, 2); + } + + public function testbit(int $index): bool + { + $bytes = $this->toBytes(); + $bytesIndex = intval($index / 8); + $len = strlen($bytes); + $b = $bytesIndex >= $len ? 0 : ord($bytes[$len - $bytesIndex - 1]); + $v = 1 << ($index % 8); + return ($b & $v) === $v; + } + + public function scan0(int $start): int + { + $bits = $this->toBits(); + $len = strlen($bits); + if ($start < 0 || $start >= $len) { + return -1; + } + $pos = strrpos($bits, "0", -1 - $start); + return $pos === false ? -1 : $len - $pos - 1; + } + + public function scan1(int $start): int + { + $bits = $this->toBits(); + $len = strlen($bits); + if ($start < 0 || $start >= $len) { + return -1; + } + $pos = strrpos($bits, "1", -1 - $start); + return $pos === false ? -1 : $len - $pos - 1; + } + + public function cmp(mixed $x): int + { + return bccomp($this->value, BigInteger::getBC($x)); + } + + public function equals(mixed $x): bool + { + return $this->value === BigInteger::getBC($x); + } + + public function sign(): int + { + return $this->value[0] === "-" ? -1 : ($this->value === "0" ? 0 : 1); + } + + public function shiftLeft(int $n): BigInteger + { + return $this->mul((new static(2))->pow($n)); + } + + public function shiftRight(int $n): BigInteger + { + $newInt = $this->div((new static(2))->pow($n)); + + if ($newInt->add($n)->cmp(0) < 0) { + return $newInt->sub(1); + } + + return $newInt; + } + } +} else { + if (!defined("S_MATH_BIGINTEGER_QUIET")) { + throw new \ValueError("Unsupported S_MATH_BIGINTEGER_MODE " . S_MATH_BIGINTEGER_MODE); + } +} diff --git a/src/Cryptonote.php b/src/Cryptonote.php index 9f8269c..c4c98ca 100644 --- a/src/Cryptonote.php +++ b/src/Cryptonote.php @@ -1,4 +1,6 @@ [ + "STANDARD" => "12", // dechex(18) + "INTEGRATED" => "13", // dechex(19) + "SUBADDRESS" => "2A" // dechex(42) + ], + "stagenet" => [ + "STANDARD" => "18", // dechex(24) + "INTEGRATED" => "19", // dechex(25) + "SUBADDRESS" => "24", // dechex(36) + ], + "testnet" => [ + "STANDARD" => "35", // dechex(53) + "INTEGRATED" => "36", // dechex(54) + "SUBADDRESS" => "3F", // dechex(63) + ] + ]; + protected $network_prefixes; + + protected $ed25519; + + public function __construct(MoneroNetwork $network) { - // https://github.com/monero-project/monero/blob/master/src/cryptonote_config.h#L222 - private $network_prefixes; - protected $ed25519; - protected $base58; - protected $varint; - - public function __construct($network = "mainnet") - { - $networks_prefixes = [ - "mainnet" => [ - "STANDARD" => dechex(18), - "INTEGRATED" => dechex(19), - "SUBADDRESS" => dechex(42), - ], - "stagenet" => [ - "STANDARD" => dechex(24), - "INTEGRATED" => dechex(25), - "SUBADDRESS" => dechex(36), - ], - "testnet" => [ - "STANDARD" => dechex(53), - "INTEGRATED" => dechex(54), - "SUBADDRESS" => dechex(63), - ] - ]; - if (array_key_exists($network, $networks_prefixes)) { - $this->network_prefixes = $networks_prefixes[$network]; - } else { - throw new Exception("Error: Invalid Network, should be one of " . join(", ", array_keys($networks_prefixes))); - } + $this->network_prefixes = self::$all_network_prefixes[$network->value]; + $this->ed25519 = new Ed25519(); + } - $this->ed25519 = new ed25519(); - $this->base58 = new base58(); - $this->varint = new Varint(); - } + /** + * Hashes a hexadecimal string with Keccak-256. + */ + public static function keccak_256($message): string + { + $bin = new BigInteger($message, 16); + $hash = keccak::hash($bin->toBytes(), 256); - /* - * @param string Hex encoded string of the data to hash - * @return string Hex encoded string of the hashed data - * - */ - public function keccak_256($message) - { - $message_bin = hex2bin($message); - $hash = keccak::hash($message_bin, 256); + return $hash; + } - return $hash; - } + /** + * Generates a new random hexadecimal seed (32 bytes). + */ + public function gen_new_hex_seed(): string + { + $bytes = random_bytes(32); + return bin2hex($bytes); + } - /* - * @return string A hex encoded string of 32 random bytes - * - */ - public function gen_new_hex_seed() - { - $bytes = random_bytes(32); - return bin2hex($bytes); - } + /** + * Performs sc_reduce (mod l) on a hexadecimal string. + */ + public function sc_reduce(string $input): string + { + $modulo = new BigInteger($input, 16); + $result = $modulo->mod($this->ed25519->l)->toHex(); + return $result; + } - public function sc_reduce($input) - { - $integer = $this->ed25519->decodeint(hex2bin($input)); + /** + * Hashes a string and reduces it to a scalar. + */ + public function hash_to_scalar(string $data): string + { + $hash = self::keccak_256($data); + $scalar = $this->sc_reduce($hash); + return $scalar; + } - $modulo = bcmod($integer , $this->ed25519->l); + /* + * Derive a deterministic private view key from a private spend key + * @param string A private spend key represented as a 32 byte hex string + * + * @return string A deterministic private view key represented as a 32 byte hex string + */ + public function derive_viewKey(string $spendKey): string + { + return $this->hash_to_scalar($spendKey); + } - $result = bin2hex($this->ed25519->encodeint($modulo)); - return $result; - } + /* + * Generate a pair of random private keys + * + * @param string A hex string to be used as a seed (this should be random) + * + * @return array An array containing a private spend key and a deterministic view key + */ + public function gen_private_keys(string $seed): array + { + $spendKey = $this->sc_reduce($seed); + $viewKey = $this->derive_viewKey($spendKey); - /* - * Hs in the cryptonote white paper - * - * @param string Hex encoded data to hash - * - * @return string A 32 byte encoded integer - */ - public function hash_to_scalar($data) - { - $hash = $this->keccak_256($data); - $scalar = $this->sc_reduce($hash); - return $scalar; - } + return [ + "spendKey" => $spendKey, + "viewKey" => $viewKey + ]; + } - /* - * Derive a deterministic private view key from a private spend key - * @param string A private spend key represented as a 32 byte hex string - * - * @return string A deterministic private view key represented as a 32 byte hex string - */ - public function derive_viewKey($spendKey) - { - return $this->hash_to_scalar($spendKey); - } + /* + * Get a public key from a private key on the ed25519 curve + * + * @param string a 32 byte hex encoded private key + * + * @return string a 32 byte hex encoding of a point on the curve to be used as a public key + */ + public function pk_from_sk(string $privKey): string + { + return $this->ed25519->publickey($this->ed25519->decodeint($privKey)); + } - /* - * Generate a pair of random private keys - * - * @param string A hex string to be used as a seed (this should be random) - * - * @return array An array containing a private spend key and a deterministic view key - */ - public function gen_private_keys($seed) - { - $spendKey = $this->sc_reduce($seed); - $viewKey = $this->derive_viewKey($spendKey); - $result = array("spendKey" => $spendKey, - "viewKey" => $viewKey); - - return $result; - } + /* + * Generate key derivation + * + * @param string a 32 byte hex encoding of a point on the ed25519 curve used as a public key + * @param string a 32 byte hex encoded private key + * + * @return string The hex encoded key derivation + */ + public function gen_key_derivation(string $public, string $private): string + { + $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($public)), $this->ed25519->decodeint(hex2bin($private))); + $res = $this->ed25519->scalarmult($point, new BigInteger(8)); + return bin2hex($this->ed25519->encodePoint($res)); + } - /* - * Get a public key from a private key on the ed25519 curve - * - * @param string a 32 byte hex encoded private key - * - * @return string a 32 byte hex encoding of a point on the curve to be used as a public key - */ - public function pk_from_sk($privKey) - { - $keyInt = $this->ed25519->decodeint(hex2bin($privKey)); - $aG = $this->ed25519->scalarmult_base($keyInt); - return bin2hex($this->ed25519->encodepoint($aG)); - } + public function derivation_to_scalar(string $der, int $index): string + { + $encoded = Varint::encodeVarint($index); + $data = $der . $encoded; + return $this->hash_to_scalar($data); + } - /* - * Generate key derivation - * - * @param string a 32 byte hex encoding of a point on the ed25519 curve used as a public key - * @param string a 32 byte hex encoded private key - * - * @return string The hex encoded key derivation - */ - public function gen_key_derivation($public, $private) - { - $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($public)), $this->ed25519->decodeint(hex2bin($private))); - $res = $this->ed25519->scalarmult($point, 8); - return bin2hex($this->ed25519->encodepoint($res)); + // this is a one way function used for both encrypting and decrypting 8 byte payment IDs + public function stealth_payment_id(string $payment_id, string $tx_pub_key, string $viewkey): string + { + if (strlen($payment_id) != 16) { + throw new Exception("Error: Incorrect payment ID size. Should be 8 bytes"); } + $der = $this->gen_key_derivation($tx_pub_key, $viewkey); + $data = $der . '8d'; + $hash = self::keccak_256($data); + $key = substr($hash, 0, 16); + $result = bin2hex(pack('H*', $payment_id) ^ pack('H*', $key)); + return $result; + } - public function derivation_to_scalar($der, $index) - { - $encoded = $this->varint->encode_varint($index); - $data = $der . $encoded; - return $this->hash_to_scalar($data); - } + // takes transaction extra field as hex string and returns transaction public key 'R' as hex string + public function txpub_from_extra(string $extra): string + { + $parsed = array_map("hexdec", str_split($extra, 2)); - // this is a one way function used for both encrypting and decrypting 8 byte payment IDs - public function stealth_payment_id($payment_id, $tx_pub_key, $viewkey) - { - if(strlen($payment_id) != 16) - { - throw new Exception("Error: Incorrect payment ID size. Should be 8 bytes"); - } - $der = $this->gen_key_derivation($tx_pub_key, $viewkey); - $data = $der . '8d'; - $hash = $this->keccak_256($data); - $key = substr($hash, 0, 16); - $result = bin2hex(pack('H*',$payment_id) ^ pack('H*',$key)); - return $result; + if ($parsed[0] == 1) { + return substr($extra, 2, 64); } - // takes transaction extra field as hex string and returns transaction public key 'R' as hex string - public function txpub_from_extra($extra) - { - $parsed = array_map("hexdec", str_split($extra, 2)); - - if($parsed[0] == 1) - { - return substr($extra, 2, 64); - } - - if($parsed[0] == 2) - { - if($parsed[0] == 2 || $parsed[2] == 1) - { - //$offset = (($parsed[1] + 2) *2) + 2; - return substr($extra, (($parsed[1] + 2) *2) + 2, 64); - } + if ($parsed[0] == 2) { + if ($parsed[0] == 2 || $parsed[2] == 1) { + //$offset = (($parsed[1] + 2) *2) + 2; + return substr($extra, (($parsed[1] + 2) * 2) + 2, 64); } } + } - public function derive_public_key($der, $index, $pub) - { - $scalar = $this->derivation_to_scalar($der, $index); - $sG = $this->ed25519->scalarmult_base($this->ed25519->decodeint(hex2bin($scalar))); - $pubPoint = $this->ed25519->decodepoint(hex2bin($pub)); - $key = $this->ed25519->encodepoint($this->ed25519->edwards($pubPoint, $sG)); - return bin2hex($key); - } + public function derive_public_key(string $der, int $index, string $pub): string + { + $scalar = $this->derivation_to_scalar($der, $index); + $sG = $this->ed25519->scalarmult_base($this->ed25519->decodeint(hex2bin($scalar))); + $pubPoint = $this->ed25519->decodepoint(hex2bin($pub)); + $key = $this->ed25519->encodePoint($this->ed25519->edwards($pubPoint, $sG)); + return bin2hex($key); + } - /* - * Perform the calculation P = P' as described in the cryptonote whitepaper - * - * @param string 32 byte transaction public key R - * @param string 32 byte receiver private view key a - * @param string 32 byte receiver public spend key B - * @param int output index - * @param string output you want to check against P - */ - public function is_output_mine($txPublic, $privViewkey, $publicSpendkey, $index, $P) - { - $derivation = $this->gen_key_derivation($txPublic, $privViewkey); - $Pprime = $this->derive_public_key($derivation, $index, $publicSpendkey); - - if($P == $Pprime) - { - return true; - } - else - return false; + /* + * Perform the calculation P = P' as described in the cryptonote whitepaper + * + * @param string 32 byte transaction public key R + * @param string 32 byte receiver private view key a + * @param string 32 byte receiver public spend key B + * @param int output index + * @param string output you want to check against P + */ + public function is_output_mine(string $txPublic, string $privViewkey, string $publicSpendkey, int $index, string $P): bool + { + $derivation = $this->gen_key_derivation($txPublic, $privViewkey); + $Pprime = $this->derive_public_key($derivation, $index, $publicSpendkey); + + if ($P == $Pprime) { + return true; + } else { + return false; } + } - /* - * Create a valid base58 encoded Monero address from public keys - * - * @param string Public spend key - * @param string Public view key - * - * @return string Base58 encoded Monero address - */ - public function encode_address($pSpendKey, $pViewKey) + /* + * Create a valid base58 encoded Monero address from public keys + * + * @param string Public spend key + * @param string Public view key + * + * @return string Base58 encoded Monero address + */ + public function encode_address(string $pSpendKey, string $pViewKey): string { $data = $this->network_prefixes["STANDARD"] . $pSpendKey . $pViewKey; - $checksum = $this->keccak_256($data); - $encoded = $this->base58->encode($data . substr($checksum, 0, 8)); - + $checksum = self::keccak_256($data); + $encoded = Base58::encode($data . substr($checksum, 0, 8)); + return $encoded; } - public function verify_checksum($address) + public function verify_checksum(string $address): bool { - $decoded = $this->base58->decode($address); + $decoded = Base58::decode($address); $checksum = substr($decoded, -8); - $checksum_hash = $this->keccak_256(substr($decoded, 0, -8)); + $checksum_hash = self::keccak_256(substr($decoded, 0, -8)); $calculated = substr($checksum_hash, 0, 8); return $checksum === $calculated; } @@ -273,47 +262,49 @@ public function verify_checksum($address) * * @return array An array containing the Address network byte, public spend key, and public view key */ - public function decode_address($address) - { - $decoded = $this->base58->decode($address); + public function decode_address(string $address): array + { + $decoded = Base58::decode($address); - if(!$this->verify_checksum($address)){ - throw new Exception("Error: invalid checksum"); + if (!$this->verify_checksum($address)) { + throw new Exception("Error: invalid checksum"); } $network_byte = substr($decoded, 0, 2); $public_spendKey = substr($decoded, 2, 64); $public_viewKey = substr($decoded, 66, 64); - $result = array("networkByte" => $network_byte, - "spendKey" => $public_spendKey, - "viewKey" => $public_viewKey); - return $result; - } + $result = array( + "networkByte" => $network_byte, + "spendKey" => $public_spendKey, + "viewKey" => $public_viewKey + ); + return $result; + } - /* - * Get an integrated address from public keys and a payment id - * - * @param string A 32 byte hex encoded public spend key - * @param string A 32 byte hex encoded public view key - * @param string An 8 byte hex string to use as a payment id - */ - public function integrated_addr_from_keys($public_spendkey, $public_viewkey, $payment_id) - { - $data = $this->network_prefixes["INTEGRATED"].$public_spendkey.$public_viewkey.$payment_id; - $checksum = substr($this->keccak_256($data), 0, 8); - $result = $this->base58->encode($data.$checksum); - return $result; - } + /* + * Get an integrated address from public keys and a payment id + * + * @param string A 32 byte hex encoded public spend key + * @param string A 32 byte hex encoded public view key + * @param string An 8 byte hex string to use as a payment id + */ + public function integrated_addr_from_keys(string $public_spendkey, string $public_viewkey, string $payment_id): string + { + $data = $this->network_prefixes["INTEGRATED"] . $public_spendkey . $public_viewkey . $payment_id; + $checksum = substr(self::keccak_256($data), 0, 8); + $result = Base58::encode($data . $checksum); + return $result; + } - /* - * Generate a Monero address from seed - * - * @param string Hex string to use as seed - * - * @return string A base58 encoded Monero address - */ - public function address_from_seed($hex_seed) + /* + * Generate a Monero address from seed + * + * @param string Hex string to use as seed + * + * @return string A base58 encoded Monero address + */ + public function address_from_seed(string $hex_seed): string { $private_keys = $this->gen_private_keys($hex_seed); $private_viewKey = $private_keys["viewKey"]; @@ -325,60 +316,59 @@ public function address_from_seed($hex_seed) $address = $this->encode_address($public_spendKey, $public_viewKey); return $address; } - + // m = Hs(a || i) - public function generate_subaddr_secret_key($major_index, $minor_index, $sec_key) + public function generate_subaddr_secret_key(string $sec_key, int $major_index, int $minor_index): string { - $prefix = "5375624164647200"; - $index = pack("II", $major_index, $minor_index); - return $this->hash_to_scalar($prefix . $sec_key . bin2hex($index)); + $prefix = "5375624164647200"; + $index = pack("II", $major_index, $minor_index); + return $this->hash_to_scalar($prefix . $sec_key . bin2hex($index)); + } + + public function generate_subaddress_spend_public_key(string $spend_public_key, string $subaddr_secret_key): string + { + $mInt = $this->ed25519->decodeint(hex2bin($subaddr_secret_key)); + $mG = $this->ed25519->scalarmult_base($mInt); + $D = $this->ed25519->edwards($this->ed25519->decodepoint(hex2bin($spend_public_key)), $mG); + return bin2hex($this->ed25519->encodePoint($D)); } - - public function generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key) - { - $mInt = $this->ed25519->decodeint(hex2bin($subaddr_secret_key)); - $mG = $this->ed25519->scalarmult_base($mInt); - $D = $this->ed25519->edwards($this->ed25519->decodepoint(hex2bin($spend_public_key)), $mG); - return bin2hex($this->ed25519->encodepoint($D)); - } - - public function generate_subaddr_view_public_key($subaddr_spend_public_key, $view_secret_key) - { - $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($subaddr_spend_public_key)), $this->ed25519->decodeint(hex2bin($view_secret_key))); - return bin2hex($this->ed25519->encodepoint($point)); - } - - public function generate_subaddress($major_index, $minor_index, $view_secret_key, $spend_public_key) - { - $subaddr_secret_key = $this->generate_subaddr_secret_key($major_index, $minor_index, $view_secret_key); - $subaddr_public_spend_key = $this->generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key); - $subaddr_public_view_key = $this->generate_subaddr_view_public_key($subaddr_public_spend_key, $view_secret_key); - $data = $this->network_prefixes["SUBADDRESS"] . $subaddr_public_spend_key . $subaddr_public_view_key; - $checksum = $this->keccak_256($data); - $encoded = $this->base58->encode($data . substr($checksum, 0, 8)); - return $encoded; - } - public function deserialize_block_header($block) + public function generate_subaddr_view_public_key(string $subaddr_spend_public_key, string $view_secret_key): string { - $data = str_split($block, 2); - - $major_version = $this->varint->decode_varint($data); - $data = $this->varint->pop_varint($data); - - $minor_version = $this->varint->decode_varint($data); - $data = $this->varint->pop_varint($data); - - $timestamp = $this->varint->decode_varint($data); - $data = $this->varint->pop_varint($data); - - $nonce = $this->varint->decode_varint($data); - $data = $this->varint->pop_varint($data); - - return array("major_version" => $major_version, - "minor_version" => $minor_version, - "timestamp" => $timestamp, - "nonce" => $nonce); + $point = $this->ed25519->scalarmult($this->ed25519->decodepoint(hex2bin($subaddr_spend_public_key)), $this->ed25519->decodeint(hex2bin($view_secret_key))); + return bin2hex($this->ed25519->encodePoint($point)); + } + + public function generate_subaddress(string $spend_public_key, string $view_secret_key, string $major_index, int $minor_index): string + { + $subaddr_secret_key = $this->generate_subaddr_secret_key($major_index, $minor_index, $view_secret_key); + $subaddr_public_spend_key = $this->generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key); + $subaddr_public_view_key = $this->generate_subaddr_view_public_key($subaddr_public_spend_key, $view_secret_key); + $data = $this->network_prefixes["SUBADDRESS"] . $subaddr_public_spend_key . $subaddr_public_view_key; + $checksum = self::keccak_256($data); + $encoded = Base58::encode($data . substr($checksum, 0, 8)); + return $encoded; } - + + public function deserialize_block_header(string $block): array + { + $data = str_split($block, 2); + + $major_version = Varint::decodeVarint($data); + $data = array_slice($data, 1); + + $minor_version = Varint::decodeVarint($data); + $data = array_slice($data, 1); + + $timestamp = Varint::decodeVarint($data); + $data = array_slice($data, 1); + + $nonce = Varint::decodeVarint($data); + return [ + "major_version" => $major_version, + "minor_version" => $minor_version, + "timestamp" => $timestamp, + "nonce" => $nonce + ]; } +} diff --git a/src/Ed25519.php b/src/Ed25519.php new file mode 100644 index 0000000..a1fc68a --- /dev/null +++ b/src/Ed25519.php @@ -0,0 +1,323 @@ +x = $x; + $this->y = $y; + } + + public function __toString() + { + return "x: " . $this->x . ", y: " . $this->y; + } + + /** + * Determines if two points are equal. + */ + public function equals(Point $point): bool + { + return $this->x->equals($point->x) && $this->y->equals($point->y); + } +} + +class Ed25519 +{ + /** @var BigInteger */ + public $b; + /** @var BigInteger */ + public $q; + /** @var BigInteger */ + public $l; + /** @var BigInteger */ + public $d; + /** @var BigInteger */ + public $I; + /** @var Point */ + public $B; + /** @var Point */ + public $identityPoint; + + public function __construct() + { + $this->b = new BigInteger("256"); // 2^8 + $this->q = new BigInteger("57896044618658097711785492504343953926634992332820282019728792003956564819949"); // 2^255 - 19 + $this->l = new BigInteger("7237005577332262213973186563042994240857116359379907606001950938285454250989"); // 2^252 + 27742317777372353535851937790883648493 + $this->d = new BigInteger("-4513249062541557337682894930092624173785641285191125241628941591882900924598840740"); // -121665 * $this->inv(121666); + $this->I = new BigInteger("19681161376707505956807079304988542015446066515923890162744021073123829784752"); // 2^((q - 1) / 4) % q + /* + $By = 4 * inv(5) + $Bx = xrecover($By) + */ + $this->B = new Point(new BigInteger("15112221349535400772501151409588531511454012693041857206046113283949847762202"), new BigInteger("46316835694926478169428394003475163141307993866256225615783033603165251855960")); + + $this->identityPoint = new Point(new BigInteger("0"), new BigInteger("1")); + } + + /** + * Returns the SHA512 hash of a message in binary form. + */ + public static function H(string $m): string + { + return hash('sha512', $m, true); + } + + /** + * Python modulus implementation (for negative numbers). + */ + private static function pymod(BigInteger $x, BigInteger $m): BigInteger + { + $result = $x->mod($m); + if ($result->cmp(0) < 0) { + $result = $result->add($m); + } + + return $result; + } + + /** + * Returns the an exponentiation modulo of a number. + */ + public static function expmod(BigInteger $b, BigInteger $e, BigInteger $m): BigInteger + { + if ($e->cmp(0) == 0) { + return new BigInteger("1"); + } + + $t = $b->powMod($e, $m); + if ($t->cmp(0) < 0) { + $t = $t->add($m); + } + + return $t; + } + + /** + * Returns the multiplicative inverse modulo of a number. + */ + public function inv(BigInteger $x): BigInteger + { + return $x->modInverse($this->q); + } + + /** + * Performs a recovery of the x-coordinate of a point on the Edwards25519 curve given the y-coordinate. + */ + public function xrecover(BigInteger $y): BigInteger + { + $y2 = $y->pow(2); + $xx = $y2->sub(1)->mul($this->inv($this->d->mul($y2)->add(1))); + + $x = $xx->powMod($this->q->add(3)->div(8), $this->q); + $modq = self::pymod($x->pow(2)->sub($xx), $this->q); + + if ($modq->cmp(0) != 0) { + $x = $x->mul($this->I)->mod($this->q); + } + if ($x->mod(2)->cmp(0) != 0) { + $x = $this->q->sub($x); + } + + return $x; + } + + /** + * Performs the elliptic curve operation P + Q on the Edwards25519 curve. + */ + public function edwards(Point $P, Point $Q): Point + { + $x1 = $P->x; + $y1 = $P->y; + $x2 = $Q->x; + $y2 = $Q->y; + + $x3 = $x1->mul($y2)->add($x2->mul($y1))->mul($this->inv((new BigInteger("1"))->add($this->d->mul($x1)->mul($x2)->mul($y1)->mul($y2)))); + $y3 = $y1->mul($y2)->add($x1->mul($x2))->mul($this->inv((new BigInteger("1"))->sub($this->d->mul($x1)->mul($x2)->mul($y1)->mul($y2)))); + + $x3 = $x3->mod($this->q); + $y3 = $y3->mod($this->q); + + return new Point($x3, $y3); + } + + /** + * Performs the scalar multiplication of a point on the Edwards25519 curve. + */ + public function scalarmult(Point $P, BigInteger $e): Point + { + if ($e->cmp(0) == 0) { + return $this->identityPoint; + } + + $Q = $this->scalarmult($P, $e->div(2)); + $Q = $this->edwards($Q, $Q); + + if ($e->mod(2)->cmp(1) == 0) { + $Q = $this->edwards($Q, $P); + } + + return $Q; + } + + /** + * Converts a decimal number to binary. + */ + public function encodeint(BigInteger $y): string + { + return $y->toBytes(); + } + + /** + * Encodes a point on the Edwards25519 curve to a hexadecimal string. + */ + public function encodePoint(Point $P): string + { + $x = $P->x; + $y = $P->y; + + $result = $y->toBytes(); + $result .= $x->binaryAnd(new BigInteger("1"))->toBytes(); + + // Convert to little-endian + $result = strrev($result); + return bin2hex($result); + } + + /** + * Returns the bit at position i of the hash h. + */ + public function bit(string $h, BigInteger $i): int + { + return (ord($h[$i->div(8)->toNumber()]) >> $i->mod(8)->toNumber()) & 1; + } + + /** + * Returns the decimal representation of the input's hash (SHA-512). + */ + public function Hint(mixed $m, bool $asBigInt = false): BigInteger|string + { + $h = $this->H($m instanceof BigInteger ? $m->toString() : $m); + $res = new BigInteger(bin2hex($h), 16); + + return $asBigInt ? $res : $res->toDec(); + } + + /** + * Determines the public key from a secret key. + */ + public function publickey(BigInteger $sk): string + { + return $this->encodePoint($this->scalarmult($this->B, $sk)); + } + + + /** + * Determines if a point is on the Edwards25519 curve. + */ + public function isoncurve(Point $P): bool + { + $x2 = $P->x->pow(2); + $y2 = $P->y->pow(2); + + return self::pymod($y2->sub($x2)->sub(1)->sub($this->d->mul($x2)->mul($y2)), $this->q)->cmp(0) == 0; + } + + /** + * Decodes a string of bits to a decimal number. + */ + public function decodeint(string $s): BigInteger + { + $result = new BigInteger(bin2hex($s), 16); + return new BigInteger($result->toDec()); + } + + /** + * Decodes a point on the Edwards25519 curve from a hexadecimal string. + */ + public function decodepoint(string $encoded): Point + { + // Convert to little-endian + $encoded = strrev(hex2bin($encoded)); + + $y = $this->decodeint(substr($encoded, 0, 32)); + $x = $this->xrecover($y); + + if ($x->binaryAnd(new BigInteger("1"))->toNumber() != $this->bit($encoded, new BigInteger("255"))) { + $x = $this->q->sub($x); + } + + $P = new Point($x, $y); + + if (!$this->isoncurve($P)) { + throw new Exception("Decoding point that is not on curve"); + } + + return $P; + } + // The code below is by the Monero-Integrations team + + /** + * Scalar multiplication of the base point by a scalar e + */ + public function scalarmult_base(BigInteger $e): Point + { + return $this->scalarmult($this->B, $e); + } +} diff --git a/src/mnemonic.php b/src/Mnemonic.php similarity index 50% rename from src/mnemonic.php rename to src/Mnemonic.php index 15476ca..815fcfd 100644 --- a/src/mnemonic.php +++ b/src/Mnemonic.php @@ -1,12 +1,16 @@ $words */ - static function checksum($words, $prefix_len) { + public static function checksum(array $words, int $prefix_len): string + { $plen = $prefix_len; - $words = array_slice($words, null, count($words) > 13 ? 24 : 12); - + $words = array_slice($words, 0, count($words) > 13 ? 24 : 12); + $wstr = ''; - foreach($words as $word) { - $wstr .= ($plen == 0 ? $word : mb_substr($word, 0, $plen)); + foreach ($words as $word) { + $wstr .= ($plen === 0 ? $word : mb_substr($word, 0, $plen)); } $checksum = crc32($wstr); $idx = $checksum % count($words); return $words[$idx]; } - + /** * Given a mnemonic seed word list, check if checksum word is valid. - * Returns boolean value. + * @param array $words */ - static function validate_checksum($words, $prefix_len) { - return (self::checksum($words, $prefix_len) == $words[count($words)-1]) ? true : false; + public static function validateChecksum(array $words, int $prefix_len): bool + { + return self::checksum($words, $prefix_len) === $words[count($words) - 1]; } /** * Given an 8 byte word (or shorter), * pads to 8 bytes (adds 0 at left) and reverses endian byte order. */ - static function swap_endian($word) { - $word = str_pad ( $word, 8, 0, STR_PAD_LEFT); + public static function swapEndian(string $word): string + { + $word = str_pad($word, 8, '0', STR_PAD_LEFT); return implode('', array_reverse(str_split($word, 2))); } - + /** * Given a hexadecimal key string (seed), * return it's mnemonic representation. @@ -77,19 +83,22 @@ static function swap_endian($word) { * @todo if anyone can make this work reliably with * pure PHP math (no gmp or bcmath), please submit a * pull request. + * + * @return array */ - static function encode($seed, $wordset_name = null) { - assert(mb_strlen($seed) % 8 == 0); + public static function encode(string $seed, ?string $wordset_name = null): array + { + assert(mb_strlen($seed) % 8 === 0); $out = []; - - $wordset = self::get_wordset_by_name( $wordset_name ); + + $wordset = self::getWordsetByName($wordset_name); $words = $wordset['words']; - + $ng = count($words); - for($i = 0; $i < mb_strlen($seed) / 8; $i ++) { - $word = self::swap_endian(mb_substr($seed, 8*$i, (8*$i+8) - (8*$i) )); + for ($i = 0; $i < mb_strlen($seed) / 8; $i++) { + $word = self::swapEndian(mb_substr($seed, 8 * $i, 8)); $x = gmp_init($word, 16); - $w1 = gmp_mod($x,$ng); + $w1 = gmp_mod($x, $ng); $w2 = gmp_mod(gmp_add(gmp_div($x, $ng), $w1), $ng); $w3 = gmp_mod(gmp_add(gmp_div(gmp_div($x, $ng), $ng), $w2), $ng); $out[] = $words[gmp_strval($w1)]; @@ -103,25 +112,31 @@ static function encode($seed, $wordset_name = null) { * Given a hexadecimal key string (seed), * return it's mnemonic representation plus an * extra checksum word. + * + * @return array */ - static function encode_with_checksum($message, $wordset_name = null) { + public static function encodeWithChecksum(string $message, ?string $wordset_name = null): array + { $list = self::encode($message, $wordset_name); - - $wordset = self::get_wordset_by_name($wordset_name); + + $wordset = self::getWordsetByName($wordset_name); $list[] = self::checksum($list, $wordset['prefix_len']); - return $list ; + return $list; } - + /** * Given a mnemonic word list, return a hexadecimal encoded string (seed). * * @todo if anyone can make this work reliably with * pure PHP math (no gmp or bcmath), please submit a * pull request. + * + * @param array $wlist */ - static function decode($wlist, $wordset_name = null) { - $wordset = self::get_wordset_by_name( $wordset_name ); - + public static function decode(array $wlist, ?string $wordset_name = null): string + { + $wordset = self::getWordsetByName($wordset_name); + $plen = $wordset['prefix_len']; $tw = $wordset['trunc_words']; $wcount = count($tw); @@ -132,166 +147,152 @@ static function decode($wlist, $wordset_name = null) { if ($plen > 0 && (count($wlist) % 3 === 0)) { throw new \Exception("last word missing"); } - + $out = ''; - for ($i = 0; $i < count($wlist)-1; $i += 3) { - - if($plen == 0) { + for ($i = 0; $i < count($wlist) - 1; $i += 3) { + + if ($plen === 0) { $w1 = @$tw[$wlist[$i]]; $w2 = @$tw[$wlist[$i + 1]]; $w3 = @$tw[$wlist[$i + 2]]; - } - else { + } else { $w1 = @$tw[mb_substr($wlist[$i], 0, $plen)]; $w2 = @$tw[mb_substr($wlist[$i + 1], 0, $plen)]; $w3 = @$tw[mb_substr($wlist[$i + 2], 0, $plen)]; } - + if ($w1 === null || $w2 === null || $w3 === null) { throw new \Exception("invalid word in mnemonic"); } - // $x = (($w1 + ($n * (($w2 - $w1) % $n))) + (($n * $n) * (($w3 - $w2) % $n))); - $x = gmp_add(gmp_add($w1, gmp_mul($wcount, (gmp_mod(gmp_sub($w2, $w1), $wcount)))), gmp_mul((gmp_mul($wcount,$wcount)), (gmp_mod(gmp_sub($w3, $w2), $wcount)))); - $out .= self::swap_endian(gmp_strval($x, 16)); + $x = gmp_add(gmp_add($w1, gmp_mul($wcount, (gmp_mod(gmp_sub($w2, $w1), $wcount)))), gmp_mul((gmp_mul($wcount, $wcount)), (gmp_mod(gmp_sub($w3, $w2), $wcount)))); + $out .= self::swapEndian(gmp_strval($x, 16)); } return $out; } - + /** * Given a wordset identifier, returns the full wordset + * + * @param string|null $name + * */ - static public function get_wordset_by_name($name = null) { - $name = $name ?: 'english'; - $wordset = self::get_wordsets(); + public static function getWordsetByName(?string $name = null): array + { + $name = $name ?? 'english'; + $wordset = self::getWordsets(); $ws = @$wordset[$name]; - if( !$ws ) { + if (!$ws) { throw new \Exception("Invalid wordset $name"); } return $ws; } - + /** * Given a mnemonic array of words, returns name of matching * wordset that contains all words, or null if not found. * * throws an exception if more than one wordset matches all words, * but in theory that should never happen. + * + * @param array $mnemonic */ - static public function find_wordset_by_mnemonic($mnemonic) { - $sets = self::get_wordsets(); - $matched_wordsets = []; - foreach($sets as $ws_name => $ws) { - - // note, to make the search faster, we truncate each word - // according to prefix_len of the wordset, and lookup - // by key in trunc_words, rather than searching through - // entire wordset array. + public static function findWordsetByMnemonic(array $mnemonic): ?string + { + $sets = self::getWordsets(); + $matchedWordsets = []; + foreach ($sets as $ws_name => $ws) { + $allmatch = true; - foreach($mnemonic as $word) { - $tw = $ws['prefix_len'] == 0 ? $word : mb_substr($word, 0, $ws['prefix_len']); - if( @$ws['trunc_words'][$tw] === null) { + foreach ($mnemonic as $word) { + $tw = $ws['prefix_len'] === 0 ? $word : mb_substr($word, 0, $ws['prefix_len']); + if (@$ws['trunc_words'][$tw] === null) { $allmatch = false; break; } } - if( $allmatch) { - $matched_wordsets[] = $ws_name; + if ($allmatch) { + $matchedWordsets[] = $ws_name; } } - - $cnt = count($matched_wordsets); - if($cnt > 1) { + + $cnt = count($matchedWordsets); + if ($cnt > 1) { throw new \Exception("Ambiguous match. mnemonic matches $cnt wordsets."); } - - return @$matched_wordsets[0]; + + return @$matchedWordsets[0]; } - - + /** - * returns list of available wordsets + * Return a list of available wordsets. + * @return array */ - static public function get_wordset_list() { - return array_keys( self::get_wordsets() ); + public static function getWordsetList(): array + { + return array_keys(self::getWordsets()); } - + /** - * This function returns all available wordsets. - * - * Each wordset is in a separate file in wordsets/*.ws.php + * Return a list of available wordsets with details. + * @return array>> */ - static public function get_wordsets() { - + public static function getWordsets(): array + { static $wordsets = null; - if( $wordsets ) { + if ($wordsets) { return $wordsets; } - + $wordsets = []; - $files = glob(__DIR__ . 'wordsets/*.ws.php'); - foreach($files as $f) { + $files = glob(__DIR__ . '/wordsets/*.ws.php'); + foreach ($files as $f) { require_once($f); - + list($wordset) = explode('.', basename($f)); - $classname = __NAMESPACE__ . '\\' . $wordset; - + $classname = __NAMESPACE__ . '\\Mnemonic\\' . $wordset; + $wordsets[$wordset] = [ 'name' => $classname::name(), - 'english_name' => $classname::english_name(), - 'prefix_len' => $classname::prefix_length(), + 'english_name' => $classname::englishName(), + 'prefix_len' => $classname::prefixLength(), 'words' => $classname::words(), ]; } - - // This loop adds the key 'trunc_words' to each wordset, which contains - // a pre-generated list of words truncated to length prefix_len. - // This list is optimized for fast lookup of the truncated word - // with the format being [ => ]. - // This optimization assumes/requires that each truncated word is unique. - // A further optimization could be to only pre-generate trunc_words on the fly - // when a wordset is actually used, rather than for all wordsets. - foreach($wordsets as &$ws) { - + + /* + This loop adds the key 'trunc_words' to each wordset, which contains + a pre-generated list of words truncated to length prefix_len. + This list is optimized for fast lookup of the truncated word + with the format being [ => ]. + This optimization assumes/requires that each truncated word is unique. + A further optimization could be to only pre-generate trunc_words on the fly + when a wordset is actually used, rather than for all wordsets. + */ + foreach ($wordsets as &$ws) { + $tw = []; $plen = $ws['prefix_len']; $i = 0; - foreach( $ws['words'] as $w) { - $key = $plen == 0 ? $w : mb_substr($w, 0, $plen); + foreach ($ws['words'] as $w) { + $key = $plen === 0 ? $w : mb_substr($w, 0, $plen); $tw[$key] = $i++; } - + $ws['trunc_words'] = $tw; } return $wordsets; } - } +interface Wordset +{ + public static function name(): string; + public static function englishName(): string; + public static function prefixLength(): int; -interface wordset { - - /* Returns name of wordset in the wordset's native language. - * This is a human-readable string, and should be capitalized - * if the language supports it. - */ - static public function name() : string; - - /* Returns name of wordset in english. - * This is a human-readable string, and should be capitalized - */ - static public function english_name() : string; - - /* Returns integer indicating length of unique prefix, - * such that each prefix of this length is unique across - * the entire set of words. - * - * A value of 0 indicates that there is no unique prefix - * and the entire word must be used instead. - */ - static public function prefix_length() : int; - - /* Returns an array of all words in the wordset. + /** + * @return array */ - static public function words() : array; -}; \ No newline at end of file + public static function words(): array; +} diff --git a/src/Varint.php b/src/Varint.php index 36ffb1b..e899f52 100644 --- a/src/Varint.php +++ b/src/Varint.php @@ -1,4 +1,6 @@ The encoded varint. + */ + public static function encodeVarint(int $value): array + { + $data = array_fill(0, 8, 0); + $pos = 0; + + while (($value & 0xFFFFFF80) !== 0) { + $data[$pos] = ($value & 0x7F) | 0x80; + $pos++; + $value >>= 7; + } + $data[$pos] = $value & 0x7F; + $pos++; + + return array_slice($data, 0, $pos); + } + + /** + * Decodes a varint from a hexadecimal string. + * @param array $data The data to decode. + */ + public static function decodeVarint(array $data): int { - public function encode_varint($data) - { - $orig = $data; + $result = 0; + $c = 0; + $pos = 0; - if ($data < 0x80) - { - return bin2hex(pack('C', $data)); + while (true) { + $isLastByteInVarInt = true; + $i = $data[$pos] & 0xFF; // Ensure we're working with an unsigned integer + if ($i >= 128) { + $isLastByteInVarInt = false; + $i -= 128; } + $result += $i * pow(128, $c); + $c++; + $pos++; + if ($isLastByteInVarInt) { + break; + } + } + + return $result; + } - $encodedBytes = []; - while ($data > 0) - { - $encodedBytes[] = 0x80 | ($data & 0x7f); - $data >>= 7; + /** + * @todo: Fix this function + */ + public function pop_varint($data) + { + $result = 0; + $c = 0; + $pos = 0; + + while (true) { + $isLastByteInVarInt = true; + $i = hexdec($data[$pos]); + if ($i >= 128) { + $isLastByteInVarInt = false; + $i -= 128; } + $result += ($i * (pow(128, $c))); + $c += 1; + $pos += 1; - $encodedBytes[count($encodedBytes)-1] &= 0x7f; - $bytes = call_user_func_array('pack', array_merge(array('C*'), $encodedBytes));; - return bin2hex($bytes); - } - - // https://github.com/monero-project/research-lab/blob/master/source-code/StringCT-java/src/how/monero/hodl/util/VarInt.java - public function decode_varint($data) - { - $result = 0; - $c = 0; - $pos = 0; - - while (true) - { - $isLastByteInVarInt = true; - $i = hexdec($data[$pos]); - if ($i >= 128) - { - $isLastByteInVarInt = false; - $i -= 128; - } - $result += ($i * (pow(128, $c))); - $c += 1; - $pos += 1; - - if ($isLastByteInVarInt) - break; + if ($isLastByteInVarInt) { + break; } - return $result; } - - public function pop_varint($data) - { - $result = 0; - $c = 0; - $pos = 0; - - while (true) - { - $isLastByteInVarInt = true; - $i = hexdec($data[$pos]); - if ($i >= 128) - { - $isLastByteInVarInt = false; - $i -= 128; - } - $result += ($i * (pow(128, $c))); - $c += 1; - $pos += 1; - - if ($isLastByteInVarInt) - break; - } - for ($x = 0; $x < $pos; $x++) - array_shift($data); - return $data; + for ($x = 0; $x < $pos; $x++) { + array_shift($data); } + return $data; } +} diff --git a/src/base58.php b/src/base58.php deleted file mode 100644 index 1f0ef53..0000000 --- a/src/base58.php +++ /dev/null @@ -1,395 +0,0 @@ - (https://github.com/monero-integrations) - * @copyright 2018 - * @license MIT - * - * ============================================================================ - * - * // Initialize class - * $base58 = new base58(); - * - * // Encode a hexadecimal (base16) string as base58 - * $encoded = $base58->encode('0137F8F06C971B168745F562AA107B4D172F336271BC0F9D3B510C14D3460DFB27D8CEBE561E73AC1E11833D5EA40200EB3C82E9C66ACAF1AB1A6BB53C40537C0B7A22160B0E'); - * - * // Decode - * $decoded = $base58->decode('479cG5opa54beQWSyqNoWw5tna9sHUNmMTtiFqLPaUhDevpJ2YLwXAggSx5ePdeFrYF8cdbmVRSmp1Kn3t4Y9kFu7rZ7pFw'); - * - */ - -namespace MoneroIntegrations\MoneroPhp; -use Exception; - -class base58 -{ - static $alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - static $encoded_block_sizes = [0, 2, 3, 5, 6, 7, 9, 10, 11]; - static $full_block_size = 8; - static $full_encoded_block_size = 11; - - /** - * - * Convert a hexadecimal string to a binary array - * - * @param string $hex A hexadecimal string to convert to a binary array - * - * @return array - * - */ - private function hex_to_bin($hex) - { - if (!is_string($hex)) { - throw new Exception('base58->hex_to_bin(): Invalid input type (must be a string)'); - } - if (strlen($hex) % 2 != 0) { - throw new Exception('base58->hex_to_bin(): Invalid input length (must be even)'); - } - - $res = array_fill(0, strlen($hex) / 2, 0); - for ($i = 0; $i < strlen($hex) / 2; $i++) { - $res[$i] = intval(substr($hex, $i * 2, $i * 2 + 2 - $i * 2), 16); - } - return $res; - } - - /** - * - * Convert a binary array to a hexadecimal string - * - * @param array $bin A binary array to convert to a hexadecimal string - * - * @return string - * - */ - private function bin_to_hex($bin) - { - if (!is_array($bin)) { - throw new Exception('base58->bin_to_hex(): Invalid input type (must be an array)'); - } - - $res = []; - for ($i = 0, $iMax = count($bin); $i < $iMax; $i++) { - $res[] = substr('0'.dechex($bin[$i]), -2); - } - return join($res); - } - - /** - * - * Convert a string to a binary array - * - * @param string $str A string to convert to a binary array - * - * @return array - * - */ - private function str_to_bin($str) - { - if (!is_string($str)) { - throw new Exception('base58->str_to_bin(): Invalid input type (must be a string)'); - } - - $res = array_fill(0, strlen($str), 0); - for ($i = 0, $iMax = strlen($str); $i < $iMax; $i++) { - $res[$i] = ord($str[$i]); - } - return $res; - } - - /** - * - * Convert a binary array to a string - * - * @param array $bin A binary array to convert to a string - * - * @return string - * - */ - private function bin_to_str($bin) - { - if (!is_array($bin)) { - throw new Exception('base58->bin_to_str(): Invalid input type (must be an array)'); - } - - $res = array_fill(0, count($bin), 0); - for ($i = 0, $iMax = count($bin); $i < $iMax; $i++) { - $res[$i] = chr($bin[$i]); - } - return preg_replace('/[[:^print:]]/', '', join($res)); // preg_replace necessary to strip errant non-ASCII characters eg. '' - } - - /** - * - * Convert a UInt8BE (one unsigned big endian byte) array to UInt64 - * - * @param array $data A UInt8BE array to convert to UInt64 - * - * @return number - * - */ - private function uint8_be_to_64($data) - { - if (!is_array($data)) { - throw new Exception ('base58->uint8_be_to_64(): Invalid input type (must be an array)'); - } - - $res = 0; - $i = 0; - switch (9 - count($data)) { - case 1: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 2: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 3: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 4: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 5: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 6: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 7: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - case 8: - $res = bcadd(bcmul($res, bcpow(2, 8)), $data[$i++]); - break; - default: - throw new Exception('base58->uint8_be_to_64: Invalid input length (1 <= count($data) <= 8)'); - } - return $res; - } - - /** - * - * Convert a UInt64 (unsigned 64 bit integer) to a UInt8BE array - * - * @param number $num A UInt64 number to convert to a UInt8BE array - * @param integer $size Size of array to return - * - * @return array - * - */ - private function uint64_to_8_be($num, $size) - { - if (!is_numeric($num)) { - throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($num must be a number)'); - } - if (!is_int($size)) { - throw new Exception ('base58->uint64_to_8_be(): Invalid input type ($size must be an integer)'); - } - if ($size < 1 || $size > 8) { - throw new Exception ('base58->uint64_to_8_be(): Invalid size (1 <= $size <= 8)'); - } - - $res = array_fill(0, $size, 0); - for ($i = $size - 1; $i >= 0; $i--) { - $res[$i] = bcmod($num, bcpow(2, 8)); - $num = bcdiv($num, bcpow(2, 8)); - } - return $res; - } - - /** - * - * Convert a hexadecimal (Base16) array to a Base58 string - * - * @param array $data - * @param array $buf - * @param number $index - * - * @return array - * - */ - private function encode_block($data, $buf, $index) - { - if (!is_array($data)) { - throw new Exception('base58->encode_block(): Invalid input type ($data must be an array)'); - } - if (!is_array($buf)) { - throw new Exception('base58->encode_block(): Invalid input type ($buf must be an array)'); - } - if (!is_int($index) && !is_float($index)) { - throw new Exception('base58->encode_block(): Invalid input type ($index must be a number)'); - } - if (count($data) < 1 or count($data) > self::$full_encoded_block_size) { - throw new Exception('base58->encode_block(): Invalid input length (1 <= count($data) <= 8)'); - } - - $num = self::uint8_be_to_64($data); - $i = self::$encoded_block_sizes[count($data)] - 1; - while ($num > 0) { - $remainder = bcmod($num, 58); - $num = bcdiv($num, 58); - $buf[$index + $i] = ord(self::$alphabet[$remainder]); - $i--; - } - return $buf; - } - - /** - * - * Encode a hexadecimal (Base16) string to Base58 - * - * @param string $hex A hexadecimal (Base16) string to convert to Base58 - * - * @return string - * - */ - public function encode($hex) - { - if (!is_string($hex)) { - throw new Exception ('base58->encode(): Invalid input type (must be a string)'); - } - - $data = self::hex_to_bin($hex); - if (count($data) == 0) { - return ''; - } - - $full_block_count = floor(count($data) / self::$full_block_size); - $last_block_size = count($data) % self::$full_block_size; - $res_size = $full_block_count * self::$full_encoded_block_size + self::$encoded_block_sizes[$last_block_size]; - - $res = array_fill(0, $res_size, ord(self::$alphabet[0])); - - for ($i = 0; $i < $full_block_count; $i++) { - $res = self::encode_block(array_slice($data, $i * self::$full_block_size, ($i * self::$full_block_size + self::$full_block_size) - ($i * self::$full_block_size)), $res, $i * self::$full_encoded_block_size); - } - - if ($last_block_size > 0) { - $res = self::encode_block(array_slice($data, $full_block_count * self::$full_block_size, $full_block_count * self::$full_block_size + $last_block_size), $res, $full_block_count * self::$full_encoded_block_size); - } - - return self::bin_to_str($res); - } - - /** - * - * Convert a Base58 input to hexadecimal (Base16) - * - * @param array $data - * @param array $buf - * @param integer $index - * - * @return array - * - */ - private function decode_block($data, $buf, $index) - { - if (!is_array($data)) { - throw new Exception('base58->decode_block(): Invalid input type ($data must be an array)'); - } - if (!is_array($buf)) { - throw new Exception('base58->decode_block(): Invalid input type ($buf must be an array)'); - } - if (!is_int($index) && !is_float($index)) { - throw new Exception('base58->decode_block(): Invalid input type ($index must be a number)'); - } - - $res_size = self::index_of(self::$encoded_block_sizes, count($data)); - if ($res_size <= 0) { - throw new Exception('base58->decode_block(): Invalid input length ($data must be a value from base58::$encoded_block_sizes)'); - } - - $res_num = 0; - $order = 1; - for ($i = count($data) - 1; $i >= 0; $i--) { - $digit = strpos(self::$alphabet, chr($data[$i])); - if ($digit < 0) { - throw new Exception("base58->decode_block(): Invalid character ($digit \"{$digit}\" not found in base58::\$alphabet)"); - } - - $product = bcadd(bcmul($order, $digit), $res_num); - if ($product > bcpow(2, 64)) { - throw new Exception('base58->decode_block(): Integer overflow ($product exceeds the maximum 64bit integer)'); - } - - $res_num = $product; - $order = bcmul($order, 58); - } - if ($res_size < self::$full_block_size && bcpow(2, 8 * $res_size) <= 0) { - throw new Exception('base58->decode_block(): Integer overflow (bcpow(2, 8 * $res_size) exceeds the maximum 64bit integer)'); - } - - $tmp_buf = self::uint64_to_8_be($res_num, $res_size); - for ($i = 0, $iMax = count($tmp_buf); $i < $iMax; $i++) { - $buf[$i + $index] = $tmp_buf[$i]; - } - return $buf; - } - - /** - * - * Decode a Base58 string to hexadecimal (Base16) - * - * @param string $hex A Base58 string to convert to hexadecimal (Base16) - * - * @return string - * - */ - public function decode($enc) - { - if (!is_string($enc)) { - throw new Exception ('base58->decode(): Invalid input type (must be a string)'); - } - - $enc = self::str_to_bin($enc); - if (count($enc) == 0) { - return ''; - } - $full_block_count = floor(bcdiv(count($enc), self::$full_encoded_block_size)); - $last_block_size = bcmod(count($enc), self::$full_encoded_block_size); - $last_block_decoded_size = self::index_of(self::$encoded_block_sizes, $last_block_size); - - $data_size = $full_block_count * self::$full_block_size + $last_block_decoded_size; - - if ($data_size == -1) { - return ''; - } - - $data = array_fill(0, $data_size, 0); - for ($i = 0; $i <= $full_block_count; $i++) { - $data = self::decode_block(array_slice($enc, $i * self::$full_encoded_block_size, ($i * self::$full_encoded_block_size + self::$full_encoded_block_size) - ($i * self::$full_encoded_block_size)), $data, $i * self::$full_block_size); - } - - if ($last_block_size > 0) { - $data = self::decode_block(array_slice($enc, $full_block_count * self::$full_encoded_block_size, $full_block_count * self::$full_encoded_block_size + $last_block_size), $data, $full_block_count * self::$full_block_size); - } - - return self::bin_to_hex($data); - } - - /** - * - * Search an array for a value - * Source: https://stackoverflow.com/a/30994678 - * - * @param array $haystack An array to search - * @param string $needle A string to search for - *) - * @return number The index of the element found (or -1 for no match) - * - */ - private function index_of($haystack, $needle) - { - if (!is_array($haystack)) { - throw new Exception ('base58->decode(): Invalid input type ($haystack must be an array)'); - } - // if (gettype($needle) != 'string') { - // throw new Exception ('base58->decode(): Invalid input type ($needle must be a string)'); - // } - - foreach ($haystack as $key => $value) if ($value === $needle) return $key; - return -1; - } -} diff --git a/src/daemonRPC.php b/src/daemonRPC.php deleted file mode 100644 index 0ec7b2c..0000000 --- a/src/daemonRPC.php +++ /dev/null @@ -1,678 +0,0 @@ - (https://github.com/cryptochangements34) - * Serhack [Monero Integrations] (https://serhack.me) - * TheKoziTwo [xmr-integration] - * Andrew LeCody [EasyBitcoin-PHP] - * Kacper Rowinski [jsonRPCClient] - * - * @author Monero Integrations Team (https://github.com/monero-integrations) - * @copyright 2018 - * @license MIT - * - * ============================================================================ - * - * // See example.php for more examples - * - * // Initialize class - * $daemonRPC = new daemonRPC(); - * - * // Examples: - * $height = $daemonRPC->getblockcount(); - * $block = $daemonRPC->getblock_by_height(1); - * - */ - -namespace MoneroIntegrations\MoneroPhp; -use Exception; - -class daemonRPC -{ - private $client; - - private $protocol; - private $host; - private $port; - private $url; - private $user; - private $password; - private $check_SSL; - - /** - * - * Start a connection with the Monero daemon (monerod) - * - * @param string $host Monero daemon IP hostname (optional) - * @param int $port Monero daemon port (optional) - * @param string $protocol Monero daemon protocol (eg. 'http') (optional) - * @param string $user Monero daemon RPC username (optional) - * @param string $password Monero daemon RPC passphrase (optional) - * - */ - function __construct($host = '127.0.0.1', $port = 18081, $SSL = true, $user = null, $password = null) - { - if (is_array($host)) { // Parameters passed in as object/dictionary - $params = $host; - - if (array_key_exists('host', $params)) { - $host = $params['host']; - } else { - $host = '127.0.0.1'; - } - if (array_key_exists('port', $params)) { - $port = $params['port']; - } - if (array_key_exists('protocol', $params)) { - $protocol = $params['protocol']; - } - if (array_key_exists('user', $params)) { - $user = $params['user']; - } - if (array_key_exists('password', $params)) { - $password = $params['password']; - } - } - - if ($SSL) { - $protocol = 'https'; - } else { - $protocol = 'http'; - } - - $this->host = $host; - $this->port = $port; - $this->protocol = $protocol; - $this->user = $user; - $this->password = $password; - $this->check_SSL = $SSL; - - $this->url = $protocol.'://'.$host.':'.$port.'/'; - $this->client = new jsonRPCClient($this->url, $this->user, $this->password, $this->check_SSL); - } - - /** - * - * Execute command via jsonRPCClient - * - * @param string $method RPC method to call - * @param string $params Parameters to pass (optional) - * @param string $path Path of API (by default json_rpc) - * - * @return string Call result - * - */ - protected function _run($method, $params = null, $path = 'json_rpc') - { - return $this->client->_run($method, $params, $path); - } - - /** - * - * Look up how many blocks are in the longest chain known to the node - * - * @param none - * - * @return object Example: { - * "count": 993163, - * "status": "OK" - * } - * - */ - public function getblockcount() - { - return $this->_run('getblockcount'); - } - - /** - * - * Look up a block's hash by its height - * - * @param number $height Height of block to look up - * - * @return string Example: 'e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6' - * - */ - public function on_getblockhash($height) - { - $params = array($height); - - return $this->_run('on_getblockhash', $params); - } - - /** - * - * Construct a block template that can be mined upon - * - * @param string $wallet_address Address of wallet to receive coinbase transactions if block is successfully mined - * @param int $reserve_size Reserve size - * - * @return object Example: { - * "blocktemplate_blob": "01029af88cb70568b84a11dc9406ace9e635918ca03b008f7728b9726b327c1b482a98d81ed83000000000018bd03c01ffcfcf3c0493d7cec7020278dfc296544f139394e5e045fcda1ba2cca5b69b39c9ddc90b7e0de859fdebdc80e8eda1ba01029c5d518ce3cc4de26364059eadc8220a3f52edabdaf025a9bff4eec8b6b50e3d8080dd9da417021e642d07a8c33fbe497054cfea9c760ab4068d31532ff0fbb543a7856a9b78ee80c0f9decfae01023ef3a7182cb0c260732e7828606052a0645d3686d7a03ce3da091dbb2b75e5955f01ad2af83bce0d823bf3dbbed01ab219250eb36098c62cbb6aa2976936848bae53023c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f12d7c87346d6b84e17680082d9b4a1d84e36dd01bd2c7f3b3893478a8d88fb3", - * "difficulty": 982540729, - * "height": 993231, - * "prev_hash": "68b84a11dc9406ace9e635918ca03b008f7728b9726b327c1b482a98d81ed830", - * "reserved_offset": 246, - * "status": "OK" - * } - * - */ - public function getblocktemplate($wallet_address, $reserve_size) - { - $params = array('wallet_address' => $wallet_address, 'reserve_size' => $reserve_size); - - return $this->client->_run('getblocktemplate', $params, null); - } - - /** - * - * Submit a mined block to the network - * - * @param string $block Block blob - * - * @return // TODO: example - * - */ - public function submitblock($block) - { - return $this->_run('submitblock', $block); - } - - /** - * - * Look up the block header of the latest block in the longest chain known to the node - * - * @param none - * - * @return object Example: { - * "block_header": { - * "depth": 0, - * "difficulty": 746963928, - * "hash": "ac0f1e226268d45c99a16202fdcb730d8f7b36ea5e5b4a565b1ba1a8fc252eb0", - * "height": 990793, - * "major_version": 1, - * "minor_version": 1, - * "nonce": 1550, - * "orphan_status": false, - * "prev_hash": "386575e3b0b004ed8d458dbd31bff0fe37b280339937f971e06df33f8589b75c", - * "reward": 6856609225169, - * "timestamp": 1457589942 - * }, - * "status": "OK" - * } - * - */ - public function getlastblockheader() - { - return $this->_run('getlastblockheader'); - } - - /** - * - * Look up a block header from a block hash - * - * @param string $hash The block's SHA256 hash - * - * @return object Example: { - * "block_header": { - * "depth": 78376, - * "difficulty": 815625611, - * "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", - * "height": 912345, - * "major_version": 1, - * "minor_version": 2, - * "nonce": 1646, - * "orphan_status": false, - * "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", - * "reward": 7388968946286, - * "timestamp": 1452793716 - * }, - * "status": "OK" - * } - * - */ - public function getblockheaderbyhash($hash) - { - $params = array('hash' => $hash); - - return $this->_run('getblockheaderbyhash', $params); - } - - /** - * - * Look up a block header by height - * - * @param int $height Height of block - * - * @return object Example: { - * "block_header": { - * "depth": 78376, - * "difficulty": 815625611, - * "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", - * "height": 912345, - * "major_version": 1, - * "minor_version": 2, - * "nonce": 1646, - * "orphan_status": false, - * "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", - * "reward": 7388968946286, - * "timestamp": 1452793716 - * }, - * "status": "OK" - * } - * - */ - public function getblockheaderbyheight($height) - { - return $this->_run('getblockheaderbyheight', $height); - } - - /** - * - * Look up block information by SHA256 hash - * - * @param string $hash SHA256 hash of block - * - * @return object Example: { - * "blob": "...", - * "block_header": { - * "depth": 12, - * "difficulty": 964985344, - * "hash": "510ee3c4e14330a7b96e883c323a60ebd1b5556ac1262d0bc03c24a3b785516f", - * "height": 993056, - * "major_version": 1, - * "minor_version": 2, - * "nonce": 2036, - * "orphan_status": false, - * "prev_hash": "0ea4af6547c05c965afc8df6d31509ff3105dc7ae6b10172521d77e09711fd6d", - * "reward": 6932043647005, - * "timestamp": 1457720227 - * }, - * "json": "...", - * "status": "OK" - * } - * - */ - public function getblock_by_hash($hash) - { - $params = array('hash' => $hash); - - return $this->_run('getblock', $params); - } - - /** - * - * Look up block information by height - * - * @param int $height Height of block - * - * @return object Example: { - * "blob": "...", - * "block_header": { - * "depth": 80694, - * "difficulty": 815625611, - * "hash": "e22cf75f39ae720e8b71b3d120a5ac03f0db50bba6379e2850975b4859190bc6", - * "height": 912345, - * "major_version": 1, - * "minor_version": 2, - * "nonce": 1646, - * "orphan_status": false, - * "prev_hash": "b61c58b2e0be53fad5ef9d9731a55e8a81d972b8d90ed07c04fd37ca6403ff78", - * "reward": 7388968946286, - * "timestamp": 1452793716 - * }, - * "json": "...", - * "status": "OK" - * } - * - */ - public function getblock_by_height($height) - { - $params = array('height' => $height); - - return $this->_run('getblock', $params); - } - - /** - * - * Look up incoming and outgoing connections to your node - * - * @param none - * - * @return object Example: { - * "connections": [{ - * "avg_download": 0, - * "avg_upload": 0, - * "current_download": 0, - * "current_upload": 0, - * "incoming": false, - * "ip": "76.173.170.133", - * "live_time": 1865, - * "local_ip": false, - * "localhost": false, - * "peer_id": "3bfe29d6b1aa7c4c", - * "port": "18080", - * "recv_count": 116396, - * "recv_idle_time": 23, - * "send_count": 176893, - * "send_idle_time": 1457726610, - * "state": "state_normal" - * },{ - * .. - * }], - * "status": "OK" - * } - * - */ - public function get_connections() - { - return $this->_run('get_connections'); - } - - /** - * - * Look up general information about the state of your node and the network - * - * @param none - * - * @return object Example: { - * "alt_blocks_count": 5, - * "difficulty": 972165250, - * "grey_peerlist_size": 2280, - * "height": 993145, - * "incoming_connections_count": 0, - * "outgoing_connections_count": 8, - * "status": "OK", - * "target": 60, - * "target_height": 993137, - * "testnet": false, - * "top_block_hash": "", - * "tx_count": 564287, - * "tx_pool_size": 45, - * "white_peerlist_size": 529 - * } - * - */ - public function get_info() - { - return $this->_run('get_info'); - } - - /** - * - * Look up information regarding hard fork voting and readiness - * - * @param none - * - * @return object Example: { - * "alt_blocks_count": 0, - * "block_size_limit": 600000, - * "block_size_median": 85, - * "bootstrap_daemon_address": ?, - * "cumulative_difficulty": 40859323048, - * "difficulty": 57406, - * "free_space": 888592449536, - * "grey_peerlist_size": 526, - * "height": 1066107, - * "height_without_bootstrap": 1066107, - * "incoming_connections_count": 1, - * "offline": ?, - * "outgoing_connections_count": 1, - * "rpc_connections_count": 1, - * "start_time": 1519963719, - * "status": OK, - * "target": 120, - * "target_height": 1066073, - * "testnet": 1, - * "top_block_hash": e438aae56de8e5e5c8e0d230167fcb58bc8dde09e369ff7689a4af146040a20e, - * "tx_count": 52632, - * "tx_pool_size": 0, - * "untrusted": ?, - * "was_bootstrap_ever_used: ?, - * "white_peerlist_size": 5 - * } - * - */ - public function hardfork_info() - { - return $this->_run('hard_fork_info'); - } - - /** - * - * Ban another node by IP - * - * @param array $bans Array of IP addresses to ban - * - * @return object Example: { - * "status": "OK" - * } - * - */ - public function set_bans($bans) - { - if (is_string($bans)) { - $bans = array($bans); - } - $params = array('bans' => $bans); - - return $this->_run('set_bans', $params); - } - - /** - * - * Alias of set_bans - * } - * - */ - public function setbans($bans) - { - return $this->set_bans($bans); - } - - /** - * - * Get list of banned IPs - * - * @param none - * - * @return object Example: { - * "bans": [{ - * "ip": 838969536, - * "seconds": 1457748792 - * }], - * "status": "OK" - * } - * - */ - public function get_bans() - { - return $this->_run('get_bans'); - } - - /** - * - * Alias of get_bans - * - */ - public function getbans() - { - return $this->get_bans(); - } - - /** - * - *Flush Transaction Pool - * - * @param $txids - array - * Optional, list of transactions IDs to flush from pool (all tx ids flushed if empty). - * - * @return status - string; General RPC error code. "OK" means everything looks good. - */ - public function flush_txpool($txids) - { - return $this->_run('flush_txpool', $txids); - } - - /** - * Alias of flush_txpool - */ - public function flushtxpool($txids) - { - return $this->flush_txpool($txids); - } - - /** - * - * Get height - * - */ - public function get_height() - { - return $this->_run(null, null, 'getheight'); - } - - /** - * - * Get transactions - * - */ - public function get_transactions($txs_hashes = NULL) - { - $params = array('txs_hashes' => $txs_hashes, 'decode_as_json' => true); - return $this->_run(null, null, 'gettransactions'); - } - - - public function get_alt_blocks_hashes() - { - return $this->_run(null, null, 'get_alt_blocks_hashes'); - } - - public function is_key_image_spent($key_images) - { - if (is_string($key_images)) { - $key_images = array($key_images); - } - if(!is_array($key_images)){ - throw new Exception('Error: key images must be an array or a string'); - } - $params = array('key_images' => $key_images); - return $this->_run(null, $params, 'is_key_image_spent'); - } - - public function send_raw_transaction($tx_as_hex, $do_not_relay = false, $do_sanity_checks = true) - { - $params = array('tx_as_hex' => $tx_as_hex, 'do_not_relay' => $do_not_relay, 'do_sanity_checks' => $do_sanity_checks); - return $this->_run(null, $params, 'send_raw_transaction'); - } - - public function start_mining($background_mining, $ignore_battery = false, $miner_address, $threads_count = 1) - { - if($threads_count < 0){ - throw new Exception('Error: threads_count must be a positive integer'); - } - $params = array('do_background_mining' => $background_mining, 'ignore_battery' => $ignore_battery, 'miner_address' => $miner_address, 'threads_count' => $threads_count); - return $this->_run(null, $params, 'start_mining'); - } - - public function stop_mining() - { - return $this->_run(null, null, 'stop_mining'); - } - - public function mining_status() - { - return $this->_run(null, null, 'mining_status'); - } - - public function save_bc() - { - return $this->_run(null, null, 'save_bc'); - } - - public function get_peer_list($public_only = true) - { - $params = array('public_only' => $public_only); - return $this->_run(null, $params, 'get_peer_list'); - } - - public function set_log_hash_rate($visible = true) - { - $params = array('visible' => $visible); - return $this->_run(null, $params, 'set_log_hash_rate'); - } - - public function set_log_level($log_level = 0) - { - if(!is_int($log_level)){ - throw new Exception('Error: log_level must be an integer'); - } - $params = array('level' => $log_level); - return $this->_run(null, $params, 'set_log_level'); - } - - public function set_log_categories($category) - { - $params = array('categories' => $category); - return $this->_run(null, $params, 'set_log_categories'); - } - - public function get_transaction_pool() - { - return $this->_run(null, null, 'get_transaction_pool'); - } - - public function get_transaction_pool_stats(){ - return $this->_run(null, null, 'get_transaction_pool_stats'); - } - - public function stop_daemon() - { - return $this->_run(null, null, 'stop_daemon'); - } - - public function get_limit() - { - return $this->_run(null, null, 'get_limit'); - } - - public function set_limit($limit_down, $limit_up) - { - $params = array('limit_down' => $limit_down, 'limit_up' => $limit_up); - return $this->_run(null, $params, 'set_limit'); - } - - public function out_peers() - { - return $this->_run(null, null, 'out_peers'); - } - - public function in_peers() - { - return $this->_run(null, null, 'in_peers'); - } - - public function start_save_graph() - { - return $this->_run(null, null, 'start_save_graph'); - } - - public function stop_save_graph() - { - return $this->_run(null, null, 'stop_save_graph'); - } - - public function get_outs($outputs) - { - $params = array('outputs' => $outputs); - return $this->_run(null, null, 'get_outs'); - } - -} diff --git a/src/ed25519.php b/src/ed25519.php deleted file mode 100644 index df8c8e3..0000000 --- a/src/ed25519.php +++ /dev/null @@ -1,502 +0,0 @@ -b = 256; - $this->q = "57896044618658097711785492504343953926634992332820282019728792003956564819949"; //bcsub(bcpow(2, 255),19); - $this->l = "7237005577332262213973186563042994240857116359379907606001950938285454250989"; //bcadd(bcpow(2,252),27742317777372353535851937790883648493); - $this->d = "-4513249062541557337682894930092624173785641285191125241628941591882900924598840740"; //bcmul(-121665,$this->inv(121666)); - $this->I = "19681161376707505956807079304988542015446066515923890162744021073123829784752"; //$this->expmod(2, bcdiv((bcsub($this->q,1)),4),$this->q); - $this->By = "46316835694926478169428394003475163141307993866256225615783033603165251855960"; //bcmul(4,$this->inv(5)); - $this->Bx = "15112221349535400772501151409588531511454012693041857206046113283949847762202"; //$this->xrecover($this->By); - $this->B = array( - "15112221349535400772501151409588531511454012693041857206046113283949847762202", - "46316835694926478169428394003475163141307993866256225615783033603165251855960" - ); //array(bcmod($this->Bx,$this->q),bcmod($this->By,$this->q)); - - $this->gmp = extension_loaded('gmp'); - } - - public function H($m) - { - return hash('sha512', $m, true); - } - - //((n % M) + M) % M //python modulus craziness - public function pymod($x, $m) - { - if ($this->gmp) { - $mod = gmp_mod($x, $m); - if ($mod < 0) { - $mod = gmp_add($mod, $m); - } - } else { - $mod = bcmod($x, $m); - if ($mod < 0) { - $mod = bcadd($mod, $m); - } - } - - return $mod; - } - - public function expmod($b, $e, $m) - { - //if($e==0){return 1;} - if ($this->gmp) { - $t = gmp_powm($b, $e, $m); - if ($t < 0) { - $t = gmp_add($t, $m); - } - } else { - $t = bcpowmod($b, $e, $m); - if ($t[0] === '-') { - $t = bcadd($t, $m); - } - } - - return $t; - } - - public function inv($x) - { - if ($this->gmp) { - return $this->expmod($x, gmp_sub($this->q, 2), $this->q); - } else { - return $this->expmod($x, bcsub($this->q, 2), $this->q); - } - } - - public function xrecover($y) - { - if ($this->gmp) { - $y2 = gmp_pow($y, 2); - $xx = gmp_mul(gmp_sub($y2, 1), $this->inv(gmp_add(gmp_mul($this->d, $y2), 1))); - $x = $this->expmod($xx, gmp_div(gmp_add($this->q, 3), 8, 0), $this->q); - if ($this->pymod(gmp_sub(gmp_pow($x, 2), $xx), $this->q) != 0) { - $x = $this->pymod(gmp_mul($x, $this->I), $this->q); - } - if (substr($x, -1)%2 != 0) { - $x = gmp_sub($this->q, $x); - } - } else { - $y2 = bcpow($y, 2); - $xx = bcmul(bcsub($y2, 1), $this->inv(bcadd(bcmul($this->d, $y2), 1))); - $x = $this->expmod($xx, bcdiv(bcadd($this->q, 3), 8, 0), $this->q); - if ($this->pymod(bcsub(bcpow($x, 2), $xx), $this->q) != 0) { - $x = $this->pymod(bcmul($x, $this->I), $this->q); - } - if (substr($x, -1)%2 != 0) { - $x = bcsub($this->q, $x); - } - } - - return $x; - } - - public function edwards($P, $Q) - { - if ($this->gmp) { - list($x1, $y1) = $P; - list($x2, $y2) = $Q; - $xmul = gmp_mul($x1, $x2); - $ymul = gmp_mul($y1, $y2); - $com = gmp_mul($this->d, gmp_mul($xmul, $ymul)); - $x3 = gmp_mul(gmp_add(gmp_mul($x1, $y2), gmp_mul($x2, $y1)), $this->inv(gmp_add(1, $com))); - $y3 = gmp_mul(gmp_add($ymul, $xmul), $this->inv(gmp_sub(1, $com))); - - return array($this->pymod($x3, $this->q), $this->pymod($y3, $this->q)); - } else { - list($x1, $y1) = $P; - list($x2, $y2) = $Q; - $xmul = bcmul($x1, $x2); - $ymul = bcmul($y1, $y2); - $com = bcmul($this->d, bcmul($xmul, $ymul)); - $x3 = bcmul(bcadd(bcmul($x1, $y2), bcmul($x2, $y1)), $this->inv(bcadd(1, $com))); - $y3 = bcmul(bcadd($ymul, $xmul), $this->inv(bcsub(1, $com))); - - return array($this->pymod($x3, $this->q), $this->pymod($y3, $this->q)); - } - } - - public function scalarmult($P, $e) - { - if ($this->gmp) { - if ($e == 0) { - return array(0, 1); - } - $Q = $this->scalarmult($P, gmp_div($e, 2, 0)); - $Q = $this->edwards($Q, $Q); - if (substr($e, -1)%2 == 1) { - $Q = $this->edwards($Q, $P); - } - } else { - if ($e == 0) { - return array(0, 1); - } - $Q = $this->scalarmult($P, bcdiv($e, 2, 0)); - $Q = $this->edwards($Q, $Q); - if (substr($e, -1)%2 == 1) { - $Q = $this->edwards($Q, $P); - } - } - - return $Q; - } - - public function scalarloop($P, $e) - { - if ($this->gmp) { - $temp = array(); - $loopE = $e; - while ($loopE > 0) { - array_unshift($temp, $loopE); - $loopE = gmp_div($loopE, 2, 0); - } - $Q = array(); - foreach ($temp as $e) { - if ($e == 1) { - $Q = $this->edwards(array(0, 1), $P); - } elseif (substr($e, -1)%2 == 1) { - $Q = $this->edwards($this->edwards($Q, $Q), $P); - } else { - $Q = $this->edwards($Q, $Q); - } - } - } else { - $temp = array(); - $loopE = $e; - while ($loopE > 0) { - array_unshift($temp, $loopE); - $loopE = bcdiv($loopE, 2, 0); - } - $Q = array(); - foreach ($temp as $e) { - if ($e == 1) { - $Q = $this->edwards(array(0, 1), $P); - } elseif (substr($e, -1)%2 == 1) { - $Q = $this->edwards($this->edwards($Q, $Q), $P); - } else { - $Q = $this->edwards($Q, $Q); - } - } - } - - return $Q; - } - - public function bitsToString($bits) - { - $string = ''; - for ($i = 0; $i < $this->b/8; $i++) { - $sum = 0; - for ($j = 0; $j < 8; $j++) { - $bit = $bits[$i*8+$j]; - $sum += (int) $bit << $j; - } - $string .= chr($sum); - } - - return $string; - } - - public function dec2bin_i($decimal_i) - { - if ($this->gmp) { - $binary_i = ''; - do { - $binary_i = substr($decimal_i, -1)%2 .$binary_i; - $decimal_i = gmp_div($decimal_i, '2', 0); - } while (gmp_cmp($decimal_i, '0')); - } else { - $binary_i = ''; - do { - $binary_i = substr($decimal_i, -1)%2 .$binary_i; - $decimal_i = bcdiv($decimal_i, '2', 0); - } while (bccomp($decimal_i, '0')); - } - - return ($binary_i); - } - - public function encodeint($y) - { - $bits = substr(str_pad(strrev($this->dec2bin_i($y)), $this->b, '0', STR_PAD_RIGHT), 0, $this->b); - - return $this->bitsToString($bits); - } - - public function encodepoint($P) - { - list($x, $y) = $P; - $bits = substr(str_pad(strrev($this->dec2bin_i($y)), $this->b-1, '0', STR_PAD_RIGHT), 0, $this->b-1); - $bits .= (substr($x, -1)%2 == 1 ? '1' : '0'); - - return $this->bitsToString($bits); - } - - public function bit($h, $i) - { - if ($this->gmp) { - return (ord($h[(int) gmp_div($i, 8, 0)]) >> substr($i, -3)%8) & 1; - } else { - return (ord($h[(int) bcdiv($i, 8, 0)]) >> substr($i, -3)%8) & 1; - } - } - - /** - * Generates the public key of a given private key - * - * @param string $sk the secret key - * - * @return string - */ - public function publickey($sk) - { - if ($this->gmp) { - $h = $this->H($sk); - $sum = 0; - for ($i = 3; $i < $this->b-2; $i++) { - $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); - } - $a = gmp_add(gmp_pow(2, $this->b-2), $sum); - $A = $this->scalarmult($this->B, $a); - $data = $this->encodepoint($A); - } else { - $h = $this->H($sk); - $sum = 0; - for ($i = 3; $i < $this->b-2; $i++) { - $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($h, $i))); - } - $a = bcadd(bcpow(2, $this->b-2), $sum); - $A = $this->scalarmult($this->B, $a); - $data = $this->encodepoint($A); - } - - return $data; - } - - public function Hint($m) - { - if ($this->gmp) { - $h = $this->H($m); - $sum = 0; - for ($i = 0; $i < $this->b*2; $i++) { - $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); - } - } else { - $h = $this->H($m); - $sum = 0; - for ($i = 0; $i < $this->b*2; $i++) { - $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($h, $i))); - } - } - - return $sum; - } - - public function signature($m, $sk, $pk) - { - if ($this->gmp) { - $h = $this->H($sk); - $a = gmp_pow(2, (gmp_sub($this->b, 2))); - for ($i = 3; $i < $this->b-2; $i++) { - $a = gmp_add($a, gmp_mul(gmp_pow(2, $i), $this->bit($h, $i))); - } - $r = $this->Hint(substr($h, $this->b/8, ($this->b/4-$this->b/8)).$m); - $R = $this->scalarmult($this->B, $r); - $encR = $this->encodepoint($R); - $S = $this->pymod(gmp_add($r, gmp_mul($this->Hint($encR.$pk.$m), $a)), $this->l); - } else { - $h = $this->H($sk); - $a = bcpow(2, (bcsub($this->b, 2))); - for ($i = 3; $i < $this->b-2; $i++) { - $a = bcadd($a, bcmul(bcpow(2, $i), $this->bit($h, $i))); - } - $r = $this->Hint(substr($h, $this->b/8, ($this->b/4-$this->b/8)).$m); - $R = $this->scalarmult($this->B, $r); - $encR = $this->encodepoint($R); - $S = $this->pymod(bcadd($r, bcmul($this->Hint($encR.$pk.$m), $a)), $this->l); - } - - return $encR.$this->encodeint($S); - } - - public function isoncurve($P) - { - if ($this->gmp) { - list($x, $y) = $P; - $x2 = gmp_pow($x, 2); - $y2 = gmp_pow($y, 2); - - return $this->pymod(gmp_sub(gmp_sub(gmp_sub($y2, $x2), 1), gmp_mul($this->d, gmp_mul($x2, $y2))), $this->q) == 0; - } else { - list($x, $y) = $P; - $x2 = bcpow($x, 2); - $y2 = bcpow($y, 2); - - return $this->pymod(bcsub(bcsub(bcsub($y2, $x2), 1), bcmul($this->d, bcmul($x2, $y2))), $this->q) == 0; - } - } - - public function decodeint($s) - { - if ($this->gmp) { - $sum = 0; - for ($i = 0; $i < $this->b; $i++) { - $sum = gmp_add($sum, gmp_mul(gmp_pow(2, $i), $this->bit($s, $i))); - } - } else { - $sum = 0; - for ($i = 0; $i < $this->b; $i++) { - $sum = bcadd($sum, bcmul(bcpow(2, $i), $this->bit($s, $i))); - } - } - - return $sum; - } - - /* - * def decodepoint(s): - y = sum(2**i * bit(s,i) for i in range(0,b-1)) - x = xrecover(y) - if x & 1 != bit(s,b-1): x = q-x - P = [x,y] - if not isoncurve(P): raise Exception("decoding point that is not on curve") - return P - - */ - public function decodepoint($s) - { - if ($this->gmp) { - $y = 0; - for ($i = 0; $i < $this->b-1; $i++) { - $y = gmp_add($y, gmp_mul(gmp_pow(2, $i), $this->bit($s, $i))); - } - $x = $this->xrecover($y); - if (substr($x, -1)%2 != $this->bit($s, $this->b-1)) { - $x = gmp_sub($this->q, $x); - } - $P = array($x, $y); - if (!$this->isoncurve($P)) { - throw new Exception("Decoding point that is not on curve"); - } - } else { - $y = 0; - for ($i = 0; $i < $this->b-1; $i++) { - $y = bcadd($y, bcmul(bcpow(2, $i), $this->bit($s, $i))); - } - $x = $this->xrecover($y); - if (substr($x, -1)%2 != $this->bit($s, $this->b-1)) { - $x = bcsub($this->q, $x); - } - $P = array($x, $y); - if (!$this->isoncurve($P)) { - throw new Exception("Decoding point that is not on curve"); - } - } - - return $P; - } - - public function checkvalid($s, $m, $pk) - { - if (strlen($s) != $this->b/4) { - throw new Exception('Signature length is wrong'); - } - if (strlen($pk) != $this->b/8) { - throw new Exception('Public key length is wrong: '.strlen($pk)); - } - $R = $this->decodepoint(substr($s, 0, $this->b/8)); - try { - $A = $this->decodepoint($pk); - } catch (Exception $e) { - return false; - } - $S = $this->decodeint(substr($s, $this->b/8, $this->b/4)); - $h = $this->Hint($this->encodepoint($R).$pk.$m); - - return $this->scalarmult($this->B, $S) == $this->edwards($R, $this->scalarmult($A, $h)); - } - - // The code below is by the Monero-Integrations team - - public function scalarmult_base($e) - { - if ($this->gmp) { - if ($e == 0) { - return array(0, 1); - } - $Q = $this->scalarmult($this->B, gmp_div($e, 2, 0)); - $Q = $this->edwards($Q, $Q); - if (substr($e, -1)%2 == 1) { - $Q = $this->edwards($Q, $this->B); - } - } else { - if ($e == 0) { - return array(0, 1); - } - $Q = $this->scalarmult($this->B, bcdiv($e, 2, 0)); - $Q = $this->edwards($Q, $Q); - if (substr($e, -1)%2 == 1) { - $Q = $this->edwards($Q, $this->B); - } - } - - return $Q; - } -} diff --git a/src/jsonRPCClient.php b/src/jsonRPCClient.php deleted file mode 100644 index b10435a..0000000 --- a/src/jsonRPCClient.php +++ /dev/null @@ -1,181 +0,0 @@ - - * http://implix.com - */ -namespace MoneroIntegrations\MoneroPhp; - -use InvalidArgumentException; -use RuntimeException; - -class jsonRPCClient -{ - protected $url = null, $is_debug = false, $parameters_structure = 'array'; - private $username; - private $password; - private $SSL; - protected $curl_options = array( - CURLOPT_CONNECTTIMEOUT => 8, - CURLOPT_TIMEOUT => 8 - ); - - - private $httpErrors = array( - 400 => '400 Bad Request', - 401 => '401 Unauthorized', - 403 => '403 Forbidden', - 404 => '404 Not Found', - 405 => '405 Method Not Allowed', - 406 => '406 Not Acceptable', - 408 => '408 Request Timeout', - 500 => '500 Internal Server Error', - 502 => '502 Bad Gateway', - 503 => '503 Service Unavailable' - ); - - public function __construct($pUrl, $pUser, $pPass, $check_SSL) - { - $this->validate(false === extension_loaded('curl'), 'The curl extension must be loaded to use this class!'); - $this->validate(false === extension_loaded('json'), 'The json extension must be loaded to use this class!'); - - $this->url = $pUrl; - $this->username = $pUser; - $this->password = $pPass; - $this->SSL = $check_SSL; - } - - public function setDebug($pIsDebug) - { - $this->is_debug = !empty($pIsDebug); - return $this; - } - - public function setCurlOptions($pOptionsArray) - { - if (is_array($pOptionsArray)) - { - $this->curl_options = $pOptionsArray + $this->curl_options; - } - else - { - throw new InvalidArgumentException('Invalid options type.'); - } - return $this; - } - - public function _run($pMethod, $pParams, $path) - { - // check if given params are correct - $this->validate(false === is_scalar($pMethod), 'Method name has no scalar value'); - // send params as an object or an array - // Request (method invocation) - $request = json_encode(array('jsonrpc' => '2.0', 'method' => $pMethod, 'params' => $pParams)); - // if is_debug mode is true then add url and request to is_debug - $this->debug('Url: ' . $this->url . "\r\n", false); - $this->debug('Request: ' . $request . "\r\n", false); - $responseMessage = $this->getResponse($request, $path); - // if is_debug mode is true then add response to is_debug and display it - $this->debug('Response: ' . $responseMessage . "\r\n", true); - // decode and create array ( can be object, just set to false ) - $responseDecoded = json_decode($responseMessage, true); - // check if decoding json generated any errors - $jsonErrorMsg = json_last_error_msg(); - $this->validate( !is_null($jsonErrorMsg) && $jsonErrorMsg !== 'No error' , $jsonErrorMsg . ': ' . $responseMessage); - if (isset($responseDecoded['error'])) - { - $errorMessage = 'Request have return error: ' . $responseDecoded['error']['message'] . '; ' . "\n" . - 'Request: ' . $request . '; '; - if (isset($responseDecoded['error']['data'])) - { - $errorMessage .= "\n" . 'Error data: ' . $responseDecoded['error']['data']; - } - $this->validate( !is_null($responseDecoded['error']), $errorMessage); - } - return $responseDecoded['result'] ?? -1; - } - - protected function & getResponse(&$pRequest, &$path) - { - // do the actual connection - $ch = curl_init(); - if (!$ch) - { - throw new RuntimeException('Could\'t initialize a cURL session'); - } - curl_setopt($ch, CURLOPT_URL, $this->url.$path); - curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); - curl_setopt($ch, CURLOPT_USERPWD, $this->username . ":" . $this->password); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $pRequest); - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/json')); - curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate'); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - - if ($this->SSL) - { - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, '2'); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - }else{ - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - } - if (!curl_setopt_array($ch, $this->curl_options)) - { - throw new RuntimeException('Error while setting curl options'); - } - // send the request - $response = curl_exec($ch); - // check http status code - $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); - if (isset($this->httpErrors[$httpCode])) - { - throw new RuntimeException('Response Http Error - ' . $this->httpErrors[$httpCode]); - } - // check for curl error - if (0 < curl_errno($ch)) - { - throw new RuntimeException('Unable to connect to '.$this->url . ' Error: ' . curl_error($ch)); - } - // close the connection - curl_close($ch); - return $response; - } - - public function validate($pFailed, $pErrMsg) - { - if ($pFailed) - { - throw new RuntimeException($pErrMsg); - } - } - - protected function debug($pAdd, $pShow = false) - { - static $debug, $startTime; - // is_debug off return - if (false === $this->is_debug) - { - return; - } - // add - $debug .= $pAdd; - // get starttime - $startTime = empty($startTime) ? array_sum(explode(' ', microtime())) : $startTime; - if (true === $pShow and !empty($debug)) - { - // get endtime - $endTime = array_sum(explode(' ', microtime())); - // performance summary - $debug .= 'Request time: ' . round($endTime - $startTime, 3) . ' s Memory usage: ' . round(memory_get_usage() / 1024) . " kb\r\n"; - echo nl2br($debug); - // send output immediately - flush(); - // clean static - $debug = $startTime = null; - } - } -} diff --git a/src/subaddress.php b/src/subaddress.php deleted file mode 100644 index e7ebf3f..0000000 --- a/src/subaddress.php +++ /dev/null @@ -1,127 +0,0 @@ -ed25519 = new ed25519(); - $this->base58 = new base58(); - $this->gmp = extension_loaded('gmp'); - } - - private function sc_reduce($input) - { - $integer = $this->ed25519->decodeint(hex2bin($input)); - if($this->gmp) - $modulo = gmp_mod($integer , $this->ed25519->l); - else - $modulo = bcmod($integer , $this->ed25519->l); - $result = bin2hex($this->ed25519->encodeint($modulo)); - return $result; - } - - private function ge_add($point1, $point2) - { - $point3 = $this->ed25519->edwards($this->ed25519->decodepoint(hex2bin($point1)), $this->ed25519->decodepoint(hex2bin($point2))); - return bin2hex($this->ed25519->encodepoint($point3)); - } - - private function ge_scalarmult($public, $secret) - { - $point = $this->ed25519->decodepoint(hex2bin($public)); - $scalar = $this->ed25519->decodeint(hex2bin($secret)); - $res = $this->ed25519->scalarmult($point, $scalar); - return bin2hex($this->ed25519->encodepoint($res)); - } - - private function ge_scalarmult_base($scalar) - { - $decoded = $this->ed25519->decodeint(hex2bin($scalar)); - $res = $this->ed25519->scalarmult_base($decoded); - return bin2hex($this->ed25519->encodepoint($res)); - } - - /* - * @param string Hex encoded string of the data to hash - * @return string Hex encoded string of the hashed data - * - */ - private function keccak_256($message) - { - $message_bin = hex2bin($message); - $hash = keccak::hash($message_bin, 256); - return $hash; - } - - /* - * Hs in the cryptonote white paper - * - * @param string Hex encoded data to hash - * - * @return string A 32 byte encoded integer - */ - private function hash_to_scalar($data) - { - $hash = $this->keccak_256($data); - $scalar = $this->sc_reduce($hash); - return $scalar; - } - - public function generate_subaddr_secret_key($major_index, $minor_index, $sec_key) - { - $prefix = "5375624164647200"; // hex encoding of string "SubAddr" - $index = pack("II", $major_index, $minor_index); - return $this->hash_to_scalar($prefix . $sec_key . bin2hex($index)); - } - - public function generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key) - { - $mG = $this->ge_scalarmult_base($subaddr_secret_key); - $D = $this->ge_add($spend_public_key, $mG); - return $D; - } - - public function generate_subaddr_view_public_key($subaddr_spend_public_key, $view_secret_key) - { - return $this->ge_scalarmult($subaddr_spend_public_key, $view_secret_key); - } - - public function generate_subaddress($major_index, $minor_index, $view_secret_key, $spend_public_key) - { - $subaddr_secret_key = $this->generate_subaddr_secret_key($major_index, $minor_index, $view_secret_key); - $subaddr_public_spend_key = $this->generate_subaddress_spend_public_key($spend_public_key, $subaddr_secret_key); - $subaddr_public_view_key = $this->generate_subaddr_view_public_key($subaddr_public_spend_key, $view_secret_key); - // mainnet subaddress network byte is 42 (0x2a) - $data = "2a" . $subaddr_public_spend_key . $subaddr_public_view_key; - $checksum = $this->keccak_256($data); - $encoded = $this->base58->encode($data . substr($checksum, 0, 8)); - return $encoded; - } -} diff --git a/src/walletRPC.php b/src/walletRPC.php deleted file mode 100644 index 3f52b6a..0000000 --- a/src/walletRPC.php +++ /dev/null @@ -1,1987 +0,0 @@ - (https://github.com/cryptochangements34) - * Serhack [Monero Integrations] (https://serhack.me) - * TheKoziTwo [xmr-integration] - * Kacper Rowinski [jsonRPCClient] - * - * @author Monero Integrations Team (https://github.com/monero-integrations) - * @copyright 2018 - * @license MIT - * - * ============================================================================ - * - * // See example.php for more examples - * - * // Initialize class - * $walletRPC = new walletRPC(); - * - * // Examples: - * $address = $walletRPC->get_address(); - * $signed = $walletRPC->sign('The Times 03/Jan/2009 Chancellor on brink of second bailout for banks'); - * - */ - -namespace MoneroIntegrations\MoneroPhp; - -use Exception; - -class walletRPC -{ - private $client; - - private $protocol; - private $host; - private $port; - private $url; - private $user; - private $password; - private $check_SSL; - - /** - * - * Start a connection with the Monero wallet RPC interface (monero-wallet-rpc) - * - * @param string $host monero-wallet-rpc hostname (optional) - * @param int $port monero-wallet-rpc port (optional) - * @param string $protocol monero-wallet-rpc protocol (eg. 'http') (optional) - * @param string $user monero-wallet-rpc username (optional) - * @param string $password monero-wallet-rpc passphrase (optional) - * - */ - function __construct($host = '127.0.0.1', $port = 18081, $SSL = true, $user = null, $password = null) - { - if (is_array($host)) { // Parameters passed in as object/dictionary - $params = $host; - - if (array_key_exists('host', $params)) { - $host = $params['host']; - } else { - $host = '127.0.0.1'; - } - if (array_key_exists('port', $params)) { - $port = $params['port']; - } - if (array_key_exists('protocol', $params)) { - $protocol = $params['protocol']; - } - if (array_key_exists('user', $params)) { - $user = $params['user']; - } - if (array_key_exists('password', $params)) { - $password = $params['password']; - } - } - - if ($SSL) { - $protocol = 'https'; - } else { - $protocol = 'http'; - } - - $this->host = $host; - $this->port = $port; - $this->protocol = $protocol; - $this->user = $user; - $this->password = $password; - $this->check_SSL = $SSL; - - $this->url = $protocol.'://'.$host.':'.$port.'/'; - $this->client = new jsonRPCClient($this->url, $this->user, $this->password, $this->check_SSL); - } - - /** - * - * Execute command via jsonRPCClient - * - * @param string $method RPC method to call - * @param object $params Parameters to pass (optional) - * - * @return string Call result - * - */ - private function _run($method, $params = null, $path = 'json_rpc') - { - $result = $this->client->_run($method, $params, $path); - return $result; - } - - /** - * - * Print JSON object (for API) - * - * @param object $json JSON object to print - * - * @return none - * - */ - public function _print($json) - { - echo json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); - } - - /** - * - * Convert from moneroj to tacoshi (piconero) - * - * @param number $method Amount (in monero) to transform to tacoshi (piconero) (optional) - * - * @return number - * - */ - public function _transform($amount = 0) - { - return intval(bcmul($amount, 1000000000000)); - } - - /** - * - * Look up an account's balance - * - * @param number $account_index Index of account to look up (optional) - * - * @return object Example: { - * "balance": 140000000000, - * "unlocked_balance": 50000000000 - * } - * - */ - public function get_balance($account_index = 0) - { - $params = array('account_index' => $account_index); - return $this->_run('get_balance', $params); - } - - /** - * - * Alias of get_balance() - * - */ - public function getbalance($account_index = 0) - { - return $this->get_balance($account_index); - } - - /** - * - * Look up wallet address(es) - * - * @param number $account_index Index of account to look up (optional) - * @param number $address_index Index of subaddress to look up (optional) - * - * @return object Example: { - * "address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnRv53zLQH4ATSiHHrDzcSFqHpARF", - * "addresses": [ - * { - * "address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnRv53zLQH4ATSiHHrDzcSFqHpARF", - * "address_index": 0, - * "label": "Primary account", - * "used": true - * }, { - * "address": "Bh3ttLbjGFnVGCeGJF1HgVh4DfCaBNpDt7PQAgsC2GFug7WKskgfbTmB6e7UupyiijiHDQPmDC7wSCo9eLoGgbAFJQaAaDS", - * "address_index": 1, - * "label": "", - * "used": true - * } - * ] - * } - * - */ - public function get_address($account_index = 0, $address_index = 0) - { - $params = array('account_index' => $account_index, 'address_index' => $address_index); - return $this->_run('get_address', $params); - } - - /** - * @param string $address Monero address - * @return object Example: { - * "index": { - * "major": 0, - * "minor": 1 - * } - * } - */ - public function get_address_index($address){ - $params = array('address' => $address); - return $this->_run('get_address_index', $params); - } - - /** - * - * Alias of get_address() - * - * @param number $account_index Index of account to look up (optional) - * @param number $address_index Index of subaddress to look up (optional) - * - * @return object Example: { - * "address": "427ZuEhNJQRXoyJAeEoBaNW56ScQaLXyyQWgxeRL9KgAUhVzkvfiELZV7fCPBuuB2CGuJiWFQjhnhhwiH1FsHYGQGaDsaBA" - * } - * - */ - public function getaddress($account_index = 0, $address_index = 0) - { - return $this->get_address($account_index = 0, $address_index = 0); - } - - /** - * - * Create a new subaddress - * - * @param number $account_index The subaddress account index - * @param string $label A label to apply to the new subaddress - * - * @return object Example: { - * "address": "Bh3ttLbjGFnVGCeGJF1HgVh4DfCaBNpDt7PQAgsC2GFug7WKskgfbTmB6e7UupyiijiHDQPmDC7wSCo9eLoGgbAFJQaAaDS" - * "address_index": 1 - * } - * - */ - public function create_address($account_index = 0, $label = '') - { - $params = array('account_index' => $account_index, 'label' => $label); - $create_address_method = $this->_run('create_address', $params); - - $save = $this->store(); // Save wallet state after subaddress creation - - return $create_address_method; - } - - /** - * - * Label a subaddress - * - * @param number The index of the subaddress to label - * @param string The label to apply - * - * @return none - * - */ - public function label_address($index, $label) - { - $params = array('index' => $index ,'label' => $label); - return $this->_run('label_address', $params); - } - - /** - * - * Look up wallet accounts - * - * @param string $tag Optional filtering by tag - * - * @return object Example: { - * "subaddress_accounts": { - * "0": { - * "account_index": 0, - * "balance": 2808597352948771, - * "base_address": "A2XE6ArhRkVZqepY2DQ5QpW8p8P2dhDQLhPJ9scSkW6q9aYUHhrhXVvE8sjg7vHRx2HnRv53zLQH4ATSiHHrDzcSFqHpARF", - * "label": "Primary account", - * "tag": "", - * "unlocked_balance": 2717153096298162 - * }, - * "1": { - * "account_index": 1, - * "balance": 0, - * "base_address": "BcXKsfrvffKYVoNGN4HUFfaruAMRdk5DrLZDmJBnYgXrTFrXyudn81xMj7rsmU5P9dX56kRZGqSaigUxUYoaFETo9gfDKx5", - * "label": "Secondary account", - * "tag": "", - * "unlocked_balance": 0 - * }, - * "total_balance": 2808597352948771, - * "total_unlocked_balance": 2717153096298162 - * } - * - */ - public function get_accounts($tag = null) - { - return (is_null($tag)) ? $this->_run('get_accounts') : $this->_run('get_accounts', array('tag' => $tag)); - } - - /** - * - * Create a new account - * - * @param string $label Label to apply to new account - * - * @return none - * - */ - public function create_account($label = '') - { - $params = array('label' => $label); - $create_account_method = $this->_run('create_account', $params); - - $save = $this->store(); // Save wallet state after account creation - - return $create_account_method; - } - - /** - * - * Label an account - * - * @param number $account_index Index of account to label - * @param string $label Label to apply - * - * @return none - * - */ - public function label_account($account_index, $label) - { - $params = array('account_index' => $account_index, 'label' => $label); - $label_account_method = $this->_run('label_account', $params); - - $save = $this->store(); // Save wallet state after account label - - return $label_account_method; - } - - /** - * - * Look up account tags - * - * @param none - * - * @return object Example: { - * "account_tags": { - * "0": { - * "accounts": { - * "0": 0, - * "1": 1 - * }, - * "label": "", - * "tag": "Example tag" - * } - * } - * } - * - */ - public function get_account_tags() - { - return $this->_run('get_account_tags'); - } - - /** - * - * Tag accounts - * - * @param array $accounts The indices of the accounts to tag - * @param string $tag Tag to apply - * - * @return none - * - */ - public function tag_accounts($accounts, $tag) - { - $params = array('accounts' => $accounts, 'tag' => $tag); - $tag_accounts_method = $this->_run('tag_accounts', $params); - - $save = $this->store(); // Save wallet state after account tagging - - return $tag_accounts_method; - } - - /** - * - * Untag accounts - * - * @param array $accounts The indices of the accounts to untag - * - * @return none - * - */ - public function untag_accounts($accounts) - { - $params = array('accounts' => $accounts); - $untag_accounts_method = $this->_run('untag_accounts', $params); - - $save = $this->store(); // Save wallet state after untagging accounts - - return $untag_accounts_method; - } - - /** - * - * Describe a tag - * - * @param string $tag Tag to describe - * @param string $description Description to apply to tag - * - * @return object Example: { - * // TODO example - * } - * - */ - public function set_account_tag_description($tag, $description) - { - $params = array('tag' => $tag, 'description' => $description); - $set_account_tag_description_method = $this->_run('set_account_tag_description', $params); - - $save = $this->store(); // Save wallet state after describing tag - - return $set_account_tag_description_method; - } - - /** - * - * Look up how many blocks are in the longest chain known to the wallet - * - * @param none - * - * @return object Example: { - * "height": 994310 - * } - * - */ - public function get_height() - { - return $this->_run('get_height'); - } - - /** - * - * Alias of get_height() - * - */ - public function getheight() - { - return $this->get_height(); - } - - /** - * - * Send monero - * Parameters can be passed in individually (as listed below) or as an object/dictionary (as listed at bottom) - * To send to multiple recipients, use the object/dictionary (bottom) format and pass an array of recipient addresses and amount arrays in the destinations field (as in "destinations = [['amount' => 1, 'address' => ...], ['amount' => 2, 'address' => ...]]") - * - * @param string $amount Amount of monero to send - * @param string $address Address to receive funds - * @param string $payment_id Payment ID (optional) - * @param number $mixin Mixin number (ringsize - 1) (optional) - * @param number $account_index Account to send from (optional) - * @param string $subaddr_indices Comma-separated list of subaddress indices to spend from (optional) - * @param number $priority Transaction priority (optional) - * @param number $unlock_time UNIX time or block height to unlock output (optional) - * @param boolean $do_not_relay Do not relay transaction (optional) - * - * OR - * - * @param object $params Array containing any of the options listed above, where only amount and address or a destination's array are required - * - * @return object Example: { - * "amount": "1000000000000", - * "fee": "1000020000", - * "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", - * "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" - * } - * - */ - public function transfer($amount, $address = '', $payment_id = '', $mixin = 15, $account_index = 0, $subaddr_indices = '', $priority = 2, $unlock_time = 0, $do_not_relay = false, $ringsize = 11) - { - if (is_array($amount)) { // Parameters passed in as object/dictionary - $params = $amount; - - if (array_key_exists('destinations', $params)) { - $destinations = $params['destinations']; - - if (!is_array($destinations)) { - throw new Exception('Error: destinations must be an array'); - } - - foreach ($destinations as $destination_index => $destination) { - if (array_key_exists('amount', $destination)) { - $destinations[$destination_index]['amount'] = $this->_transform($destination['amount']); - } else { - throw new Exception('Error: Amount required'); - } - if (!array_key_exists('address', $destination)) { - throw new Exception('Error: Address required'); - } - } - } else { - if (array_key_exists('amount', $params)) { - $amount = $params['amount']; - } else { - throw new Exception('Error: Amount required'); - } - if (array_key_exists('address', $params)) { - $address = $params['address']; - } else { - throw new Exception('Error: Address required'); - } - $destinations = array(array('amount' => $this->_transform($amount), 'address' => $address)); - } - if (array_key_exists('payment_id', $params)) { - throw new Exception('Error: Payment ids have been deprecated.'); - } - if (array_key_exists('mixin', $params)) { - $mixin = $params['mixin']; - } - if (array_key_exists('account_index', $params)) { - $account_index = $params['account_index']; - } - if (array_key_exists('subaddr_indices', $params)) { - $subaddr_indices = $params['subaddr_indices']; - } - if (array_key_exists('priority', $params)) { - $priority = $params['priority']; - } - if (array_key_exists('unlock_time', $params)) { - $unlock_time = $params['unlock_time']; - } - if (array_key_exists('do_not_relay', $params)) { - $do_not_relay = $params['do_not_relay']; - } - } else { // Legacy parameters used - $destinations = array(array('amount' => $this->_transform($amount), 'address' => $address)); - } - - $params = array('destinations' => $destinations, 'mixin' => $mixin, 'get_tx_key' => true, 'account_index' => $account_index, 'subaddr_indices' => $subaddr_indices, 'priority' => $priority, 'do_not_relay' => $do_not_relay, 'ringsize' => $ringsize); - $transfer_method = $this->_run('transfer', $params); - - $save = $this->store(); // Save wallet state after transfer - - return $transfer_method; - } - - /** - * - * Same as transfer, but splits transfer into more than one transaction if necessary - * - */ - public function transfer_split($amount, $address = '', $payment_id = '', $mixin = 15, $account_index = 0, $subaddr_indices = '', $priority = 2, $unlock_time = 0, $do_not_relay = false) - { - if (is_array($amount)) { // Parameters passed in as object/dictionary - $params = $amount; - - if (array_key_exists('destinations', $params)) { - $destinations = $params['destinations']; - - if (!is_array($destinations)) { - throw new Exception('Error: destinations must be an array'); - } - - foreach ($destinations as $destination) { - if (array_key_exists('amount', $destinations[$destination])) { - $destinations[$destination]['amount'] = $this->_transform($destinations[$destination]['amount']); - } else { - throw new Exception('Error: Amount required'); - } - if (!array_key_exists('address', $destinations[$destination])) { - throw new Exception('Error: Address required'); - } - } - } else { - if (array_key_exists('amount', $params)) { - $amount = $params['amount']; - } else { - throw new Exception('Error: Amount required'); - } - if (array_key_exists('address', $params)) { - $address = $params['address']; - } else { - throw new Exception('Error: Address required'); - } - $destinations = array(array('amount' => $this->_transform($amount), 'address' => $address)); - } - if (array_key_exists('mixin', $params)) { - $mixin = $params['mixin']; - } - if (array_key_exists('payment_id', $params)) { - $payment_id = $params['payment_id']; - } - if (array_key_exists('account_index', $params)) { - $account_index = $params['account_index']; - } - if (array_key_exists('subaddr_indices', $params)) { - $subaddr_indices = $params['subaddr_indices']; - } - if (array_key_exists('priority', $params)) { - $priority = $params['priority']; - } - if (array_key_exists('unlock_time', $params)) { - $unlock_time = $params['unlock_time']; - } - if (array_key_exists('do_not_relay', $params)) { - $do_not_relay = $params['do_not_relay']; - } - } else { // Legacy parameters used - $destinations = array(array('amount' => $this->_transform($amount), 'address' => $address)); - } - - $params = array('destinations' => $destinations, 'mixin' => $mixin, 'get_tx_key' => true, 'account_index' => $account_index, 'subaddr_indices' => $subaddr_indices, 'payment_id' => $payment_id, 'priority' => $priority, 'unlock_time' => $unlock_time, 'do_not_relay' => $do_not_relay); - $transfer_method = $this->_run('transfer_split', $params); - - $save = $this->store(); // Save wallet state after transfer - - return $transfer_method; - } - - /** - * - * Send all dust outputs back to the wallet - * - * @param none - * - * @return object Example: { - * // TODO example - * } - * - */ - public function sweep_dust() - { - return $this->_run('sweep_dust'); - } - - /** - * - * Send all unmixable outputs back to the wallet - * - * @param none - * - * @return object Example: { - * // TODO example - * } - * - */ - public function sweep_unmixable() - { - return $this->_run('sweep_unmixable'); - } - - /** - * - * Send all unlocked outputs from an account to an address - * - * @param string $address Address to receive funds - * @param string $subaddr_indices Comma-separated list of subaddress indices to sweep (optional) - * @param number $account_index Index of the account to sweep (optional) - * @param string $payment_id Payment ID (optional) - * @param number $mixin Mixin number (ringsize - 1) (optional) - * @param number $priority Payment ID (optional) - * @param number $below_amount Only send outputs below this amount (optional) - * @param number $unlock_time UNIX time or block height to unlock output (optional) - * @param boolean $do_not_relay Do not relay transaction (optional) - * - * OR - * - * @param object $params Array containing any of the options listed above, where only address is required - * - * @return object Example: { - * "amount": "1000000000000", - * "fee": "1000020000", - * "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", - * "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" - * } - * - */ - public function sweep_all($address, $subaddr_indices = '', $account_index = 0, $payment_id = '', $mixin = 15, $priority = 2, $below_amount = 0, $unlock_time = 0, $do_not_relay = false) - { - if (is_array($address)) { // Parameters passed in as object/dictionary - $params = $address; - - if (array_key_exists('address', $params)) { - $address = $params['address']; - } else { - throw new Exception('Error: Address required'); - } - if (array_key_exists('subaddr_indices', $params)) { - $subaddr_indices = $params['subaddr_indices']; - } - if (array_key_exists('account_index', $params)) { - $account_index = $params['account_index']; - } - if (array_key_exists('payment_id', $params)) { - $payment_id = $params['payment_id']; - } - if (array_key_exists('mixin', $params)) { - $mixin = $params['mixin']; - } - if (array_key_exists('priority', $params)) { - $priority = $params['priority']; - } - if (array_key_exists('below_amount', $params)) { - $below_amount = $params['below_amount']; - } - if (array_key_exists('unlock_time', $params)) { - $unlock_time = $params['unlock_time']; - } - if (array_key_exists('do_not_relay', $params)) { - $do_not_relay = $params['do_not_relay']; - } - } - - $params = array('address' => $address, 'mixin' => $mixin, 'get_tx_key' => true, 'subaddr_indices' => $subaddr_indices, 'account_index' => $account_index, 'payment_id' => $payment_id, 'priority' => $priority, 'below_amount' => $this->_transform($below_amount), 'unlock_time' => $unlock_time, 'do_not_relay' => $do_not_relay); - $sweep_all_method = $this->_run('sweep_all', $params); - - $save = $this->store(); // Save wallet state after transfer - - return $sweep_all_method; - } - - /** - * - * Sweep a single key image to an address - * - * @param string $key_image Key image to sweep - * @param string $address Address to receive funds - * @param string $payment_id Payment ID (optional) - * @param number $below_amount Only send outputs below this amount (optional) - * @param number $mixin Mixin number (ringsize - 1) (optional) - * @param number $priority Payment ID (optional) - * @param number $unlock_time UNIX time or block height to unlock output (optional) - * @param boolean $do_not_relay Do not relay transaction (optional) - * - * OR - * - * @param object $params Array containing any of the options listed above, where only address is required - * - * @return object Example: { - * "amount": "1000000000000", - * "fee": "1000020000", - * "tx_hash": "c60a64ddae46154a75af65544f73a7064911289a7760be8fb5390cb57c06f2db", - * "tx_key": "805abdb3882d9440b6c80490c2d6b95a79dbc6d1b05e514131a91768e8040b04" - * } - * - */ - public function sweep_single($key_image, $address, $payment_id = '', $mixin = 15, $priority = 2, $below_amount = 0, $unlock_time = 0, $do_not_relay = 0) - { - if (is_array($key_image)) { // Parameters passed in as object/dictionary - $params = $key_image; - - if (array_key_exists('key_image', $params)) { - $key_image = $params['key_image']; - } else { - throw new Exception('Error: Key image required'); - } - if (array_key_exists('address', $params)) { - $address = $params['address']; - } else { - throw new Exception('Error: Address required'); - } - - if (array_key_exists('payment_id', $params)) { - $payment_id = $params['payment_id']; - } - if (array_key_exists('mixin', $params)) { - $mixin = $params['mixin']; - } - if (array_key_exists('account_index', $params)) { - $account_index = $params['account_index']; - } - if (array_key_exists('priority', $params)) { - $priority = $params['priority']; - } - if (array_key_exists('unlock_time', $params)) { - $unlock_time = $params['unlock_time']; - } - if (array_key_exists('below_amount', $params)) { - $below_amount = $params['below_amount']; - } - if (array_key_exists('do_not_relay', $params)) { - $do_not_relay = $params['do_not_relay']; - } - } - - $params = array('address' => $address, 'mixin' => $mixin, 'get_tx_key' => true, 'account_index' => $account_index, 'payment_id' => $payment_id, 'priority' => $priority, 'below_amount' => $this->_transform($below_amount), 'unlock_time' => $unlock_time, 'do_not_relay' => $do_not_relay); - $sweep_single_method = $this->_run('sweep_single', $params); - - $save = $this->store(); // Save wallet state after transfer - - return $sweep_single_method; - } - - /** - * - * Relay a transaction - * - * @param string $hex Blob of transaction to relay - * - * @return object // TODO example - * - */ - public function relay_tx($hex) - { - $params = array('hex' => $hex); - $relay_tx_method = $this->_run('relay_tx_method', $params); - - $save = $this->store(); // Save wallet state after transaction relay - - return $this->_run('relay_tx'); - } - - /** - * - * Save wallet - * - * @param none - * - * @return object Example: - * - */ - public function store() - { - return $this->_run('store'); - } - - /** - * - * Look up incoming payments by payment ID - * - * @param string $payment_id Payment ID to look up - * - * @return object Example: { - * "payments": [{ - * "amount": 10350000000000, - * "block_height": 994327, - * "payment_id": "4279257e0a20608e25dba8744949c9e1caff4fcdafc7d5362ecf14225f3d9030", - * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", - * "unlock_time": 0 - * }] - * } - * - */ - public function get_payments($payment_id) - { - // $params = array('payment_id' => $payment_id); // does not work - $params = []; - $params['payment_id'] = $payment_id; - return $this->_run('get_payments', $params); - } - - /** - * - * Look up incoming payments by payment ID (or a list of payments IDs) from a given height - * - * @param array $payment_ids Array of payment IDs to look up - * @param string $min_block_height Height to begin search - * - * @return object Example: { - * "payments": [{ - * "amount": 10350000000000, - * "block_height": 994327, - * "payment_id": "4279257e0a20608e25dba8744949c9e1caff4fcdafc7d5362ecf14225f3d9030", - * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", - * "unlock_time": 0 - * }] - * } - * - */ - public function get_bulk_payments($payment_ids, $min_block_height) - { - // $params = array('payment_ids' => $payment_ids, 'min_block_height' => $min_block_height); // does not work - //$params = array('min_block_height' => $min_block_height); // does not work - $params = []; - if (!is_array($payment_ids)) { - throw new Exception('Error: Payment IDs must be array.'); - } - if ($payment_ids) { - $params['payment_ids'] = []; - foreach ($payment_ids as $payment_id) { - $params['payment_ids'][] = $payment_id; - } - } - return $this->_run('get_bulk_payments', $params); - } - - /** - * - * Look up incoming transfers - * - * @param string $type Type of transfer to look up; must be 'all', 'available', or 'unavailable' (incoming transfers which have already been spent) - * @param number $account_index Index of account to look up (optional) - * @param string $subaddr_indices Comma-separated list of subaddress indices to look up (optional) - * - * @return object Example: { - * "transfers": [{ - * "amount": 10000000000000, - * "global_index": 711506, - * "spent": false, - * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", - * "tx_size": 5870 - * },{ - * "amount": 300000000000, - * "global_index": 794232, - * "spent": false, - * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", - * "tx_size": 5870 - * },{ - * "amount": 50000000000, - * "global_index": 213659, - * "spent": false, - * "tx_hash": "c391089f5b1b02067acc15294e3629a463412af1f1ed0f354113dd4467e4f6c1", - * "tx_size": 5870 - * }] - * } - */ - public function incoming_transfers($type = 'all', $account_index = 0, $subaddr_indices = '') - { - $params = array('transfer_type' => $type, 'account_index' => $account_index, 'subaddr_indices' => $subaddr_indices); - return $this->_run('incoming_transfers', $params); - } - - /** - * - * Look up a wallet key - * - * @param string $key_type Type of key to look up; must be 'view_key', 'spend_key', or 'mnemonic' - * - * @return object Example: { - * "key": "7e341d..." - * } - * - */ - public function query_key($key_type) - { - $params = array('key_type' => $key_type); - return $this->_run('query_key', $params); - } - - /** - * - * Look up wallet view key - * - * @param none - * - * @return object Example: { - * "key": "7e341d..." - * } - * - */ - public function view_key() - { - $params = array('key_type' => 'view_key'); - return $this->_run('query_key', $params); - } - - /** - * - * Look up wallet spend key - * - * @param none - * - * @return object Example: { - * "key": "2ab810..." - * } - * - */ - public function spend_key() - { - $params = array('key_type' => 'spend_key'); - return $this->_run('query_key', $params); - } - - /** - * - * Look up wallet mnemonic seed - * - * @param none - * - * @return object Example: { - * "key": "2ab810..." - * } - * - */ - public function mnemonic() - { - $params = array('key_type' => 'mnemonic'); - return $this->_run('query_key', $params); - } - - /** - * - * Create an integrated address from a given payment ID - * - * @param string $payment_id Payment ID (optional) - * - * @return object Example: { - * "integrated_address": "4BpEv3WrufwXoyJAeEoBaNW56ScQaLXyyQWgxeRL9KgAUhVzkvfiELZV7fCPBuuB2CGuJiWFQjhnhhwiH1FsHYGQQ8H2RRJveAtUeiFs6J" - * } - * - */ - public function make_integrated_address($payment_id = null) - { - $params = array('payment_id' => $payment_id); - return $this->_run('make_integrated_address', $params); - } - - /** - * - * Look up the wallet address and payment ID corresponding to an integrated address - * - * @param string $integrated_address Integrated address to split - * - * @return object Example: { - * "payment_id": "420fa29b2d9a49f5", - * "standard_address": "427ZuEhNJQRXoyJAeEoBaNW56ScQaLXyyQWgxeRL9KgAUhVzkvfiELZV7fCPBuuB2CGuJiWFQjhnhhwiH1FsHYGQGaDsaBA" - * } - * - */ - public function split_integrated_address($integrated_address) - { - $params = array('integrated_address' => $integrated_address); - return $this->_run('split_integrated_address', $params); - } - - /** - * - * Stop the wallet, saving the state - * - * @param none - * - * @return none - * - */ - public function stop_wallet() - { - return $this->_run('stop_wallet'); - } - - /* - * - * Rescan the blockchain from scratch - * - * @param none - * - * @return none - * - */ - - public function rescan_blockchain() - { - return $this->_run('rescan_blockchain'); - } - - /** - * - * Add notes to transactions - * - * @param array $txids Array of transaction IDs to note - * @param array $notes Array of notes (strings) to add - * - * @return none - * - */ - public function set_tx_notes($txids, $notes) - { - $params = array('txids' => $txids, 'notes' => $notes); - return $this->_run('set_tx_notes', $params); - } - - /** - * - * Look up transaction note - * - * @param array $txids Array of transaction IDs (strings) to look up - * - * @return obect Example: { - * // TODO example - * } - * - */ - public function get_tx_notes($txids) - { - $params = array('txids' => $txids); - return $this->_run('get_tx_notes', $params); - } - - /** - * - * Set a wallet option - * - * @param string $key Option to set - * @param string $value Value to set - * - * @return none - * - */ - public function set_attribute($key, $value) - { - $params = array('key' => $key, 'value' => $value); - return $this->_run('set_attribute', $params); - } - - /** - * - * Look up a wallet option - * - * @param string $key Wallet option to query - * - * @return object Example: { - * // TODO example - * } - * - */ - public function get_attribute($key) - { - $params = array('key' => $key); - return $this->_run('get_attribute', $params); - } - - /** - * - * Look up a transaction key - * - * @param string $txid Transaction ID to look up - * - * @return object Example: { - * "tx_key": "e8e97866b1606bd87178eada8f995bf96d2af3fec5db0bc570a451ab1d589b0f" - * } - * - */ - public function get_tx_key($txid) - { - $params = array('txid' => $txid); - return $this->_run('get_tx_key', $params); - } - - /** - * - * Check a transaction key - * - * @param string $address Address that sent transaction - * @param string $txid Transaction ID - * @param string $tx_key Transaction key - * - * @return object Example: { - * "confirmations": 1, - * "in_pool": , - * "received": 0 - * } - * - */ - public function check_tx_key($address, $txid, $tx_key) - { - $params = array('address' => $address, 'txid' => $txid, 'tx_key' => $tx_key); - return $this->_run('check_tx_key', $params); - } - - /** - * - * Create proof (signature) of transaction - * - * @param string $address Address that spent funds - * @param string $txid Transaction ID - * - * @return object Example: { - * "signature": "InProofV1Lq4nejMXxMnAdnLeZhHe3FGCmFdnSvzVM1AiGcXjngTRi4hfHPcDL9D4th7KUuvF9ZHnzCDXysNBhfy7gFvUfSbQWiqWtzbs35yUSmtW8orRZzJpYKNjxtzfqGthy1U3puiF" - * } - * - */ - public function get_tx_proof($address, $txid) - { - $params = array('address' => $address, 'txid' => $txid); - return $this->_run('get_tx_proof', $params); - } - - /** - * - * Verify transaction proof - * - * @param string $address Address that spent funds - * @param string $txid Transaction ID - * @param string $signature Signature (tx_proof) - * - * @return Example: { - * "confirmations": 2, - * "good": 1, - * "in_pool": , - * "received": 15752471409492, - * } - * - */ - public function check_tx_proof($address, $txid, $signature) - { - $params = array('address' => $address, 'txid' => $txid, 'signature' => $signature); - return $this->_run('check_tx_proof', $params); - } - - /** - * - * Create proof of a spend - * - * @param string $txid Transaction ID - * - * @return object Example: { - * "signature": "SpendProofV1RnP6ywcDQHuQTBzXEMiHKbe5ErzRAjpUB1h4RUMfGPNv4bbR6V7EFyiYkCrURwbbrYWWxa6Kb38ZWWYTQhr2Y1cRHVoDBkK9GzBbikj6c8GWyKbu3RKi9hoYp2fA9zze7UEdeNrYrJ3tkoE6mkR3Lk5HP6X2ixnjhUTG65EzJgfCS4qZ85oGkd17UWgQo6fKRC2GRgisER8HiNwsqZdUTM313RmdUX7AYaTUNyhdhTinVLuaEw83L6hNHANb3aQds5CwdKCUQu4pkt5zn9K66z16QGDAXqL6ttHK6K9TmDHF17SGNQVPHzffENLGUf7MXqS3Pb6eijeYirFDxmisZc1n2mh6d5EW8ugyHGfNvbLEd2vjVPDk8zZYYr7NyJ8JjaHhDmDWeLYy27afXC5HyWgJH5nDyCBptoCxxDnyRuAnNddBnLsZZES399zJBYHkGb197ZJm85TV8SRC6cuYB4MdphsFdvSzygnjFtbAcZWHy62Py3QCTVhrwdUomAkeNByM8Ygc1cg245Se1V2XjaUyXuAFjj8nmDNoZG7VDxaD2GT9dXDaPd5dimCpbeDJEVoJXkeEFsZF85WwNcd67D4s5dWySFyS8RbsEnNA5UmoF3wUstZ2TtsUhiaeXmPwjNvnyLif3ASBmFTDDu2ZEsShLdddiydJcsYFJUrN8L37dyxENJN41RnmEf1FaszBHYW1HW13bUfiSrQ9sLLtqcawHAbZWnq4ZQLkCuomHaXTRNfg63hWzMjdNrQ2wrETxyXEwSRaodLmSVBn5wTFVzJe5LfSFHMx1FY1xf8kgXVGafGcijY2hg1yw8ru9wvyba9kdr16Lxfip5RJGFkiBDANqZCBkgYcKUcTaRc1aSwHEJ5m8umpFwEY2JtakvNMnShjURRA3yr7GDHKkCRTSzguYEgiFXdEiq55d6BXDfMaKNTNZzTdJXYZ9A2j6G9gRXksYKAVSDgfWVpM5FaZNRANvaJRguQyqWRRZ1gQdHgN4DqmQ589GPmStrdfoGEhk1LnfDZVwkhvDoYfiLwk9Z2JvZ4ZF4TojUupFQyvsUb5VPz2KNSzFi5wYp1pqGHKv7psYCCodWdte1waaWgKxDken44AB4k6wg2V8y1vG7Nd4hrfkvV4Y6YBhn6i45jdiQddEo5Hj2866MWNsdpmbuith7gmTmfat77Dh68GrRukSWKetPBLw7Soh2PygGU5zWEtgaX5g79FdGZg" - * } - * - */ - public function get_spend_proof($txid, $message=null) - { - $params = array('txid' => $txid); - if( $message !== null ) { - $params['message'] = $message; - } - return $this->_run('get_spend_proof', $params); - } - - /** - * - * Verify spend proof - * - * @param string $txid Transaction ID - * @param string $signature Spend proof to verify - * - * @return object Example: { - * "good": 1 - * } - * - */ - public function check_spend_proof($txid, $signature, $message=null) - { - $params = array('txid' => $txid, 'signature' => $signature); - if( $message !== null ) { - $params['message'] = $message; - } - return $this->_run('check_spend_proof', $params); - } - - /** - * - * Create proof of reserves - * - * @param string $account_index Comma-separated list of account indices of which to prove reserves (proves reserve of all accounts if empty) (optional) - * - * @return Example: { - * "signature": "ReserveProofV11BZ23sBt9sZJeGccf84mzyAmNCP3KzYbE111111111111AjsVgKzau88VxXVGACbYgPVrDGC84vBU61Gmm2eiYxdZULAE4yzBxT1D9epWgCT7qiHFvFMbdChf3CpR2YsZj8CEhp8qDbitsfdy7iBdK6d5pPUiMEwCNsCGDp8AiAc6sLRiuTsLEJcfPYEKe" - * } - * - */ - public function get_reserve_proof($account_index = 'all') - { - if ($account_index == 'all') { - $params = array('all' => true); - } else { - $params = array('account_index' => $account_index); - } - - return $this->_run('get_reserve_proof'); - } - - /** - * - * Verify a reserve proof - * - * @param string $address Wallet address - * @param string $signature Reserve proof - * - * @return object Example: { - * "good": 1, - * "spent": 0, - * "total": 0 - * } - * - */ - public function check_reserve_proof($address, $signature) - { - $params = array('address' => $address, 'signature' => $signature); - return $this->_run('check_reserve_proof', $params); - } - - /** - * - * Look up transfers - * - * @param array $input_types Array of transfer type strings; possible values include 'all', 'in', 'out', 'pending', 'failed', and 'pool' (optional) - * @param number $account_index Index of account to look up (optional) - * @param string $subaddr_indices Comma-separated list of subaddress indices to look up (optional) - * @param number $min_height Minimum block height to use when looking up transfers (optional) - * @param number $max_height Maximum block height to use when looking up transfers (optional) - * - * OR - * - * @param object $inputs_types Array containing any of the options listed above, where only an input types array is required - * - * @return object Example: { - * "pool": [{ - * "amount": 500000000000, - * "fee": 0, - * "height": 0, - * "note": "", - * "payment_id": "758d9b225fda7b7f", - * "timestamp": 1488312467, - * "txid": "da7301d5423efa09fabacb720002e978d114ff2db6a1546f8b820644a1b96208", - * "type": "pool" - * }] - * } - * - */ - public function get_transfers($input_types = ['all'], $account_index = 0, $subaddr_indices = '', $min_height = 0, $max_height = 4206931337) - { - if (is_string($input_types)) { // If user is using old method - $params = array('subaddr_indices' => $subaddr_indices, 'min_height' => $min_height, 'max_height' => $max_height); - if (is_bool($account_index)) { // If user passed eg. get_transfers('in', true) - $params['account_index'] = 0; - $params[$input_types] = $account_index; // $params = array($input_type => $input_value); - } else { // If user passed eg. get_transfers('in') - $params['account_index'] = $account_index; - $params[$input_types] = true; - } - } else { - if (is_object($input_types) || is_array($input_types)) { // Parameters passed in as object/dictionary - $params = $input_types; - - if (array_key_exists('input_types', $params)) { - $input_types = $params['input_types']; - } else { - $input_types = ['all']; - } - if (array_key_exists('account_index', $params)) { - $account_index = $params['account_index']; - } - if (array_key_exists('subaddr_indices', $params)) { - $subaddr_indices = $params['subaddr_indices']; - } - if (array_key_exists('min_height', $params)) { - $min_height = $params['min_height']; - } - if (array_key_exists('max_height', $params)) { - $max_height = $params['max_height']; - } - } - - $params = array('account_index' => $account_index, 'subaddr_indices' => $subaddr_indices, 'min_height' => $min_height, 'max_height' => $max_height); - for ($i = 0, $iMax = count($input_types); $i < $iMax; $i++) { - $params[$input_types[$i]] = true; - } - } - - if (array_key_exists('all', $params)) { - unset($params['all']); - $params['in'] = true; - $params['out'] = true; - $params['pending'] = true; - $params['failed'] = true; - $params['pool'] = true; - } - - if (($min_height || $max_height) && $max_height != 4206931337) { - $params['filter_by_height'] = true; - } - - return $this->_run('get_transfers', $params); - } - - /** - * - * Look up transaction by transaction ID - * - * @param string $txid Transaction ID to look up - * @param string $account_index Index of account to query (optional) - * - * @return object Example: { - * "transfer": { - * "amount": 10000000000000, - * "fee": 0, - * "height": 1316388, - * "note": "", - * "payment_id": "0000000000000000", - * "timestamp": 1495539310, - * "txid": "f2d33ba969a09941c6671e6dfe7e9456e5f686eca72c1a94a3e63ac6d7f27baf", - * "type": "in" - * } - * } - * - */ - public function get_transfer_by_txid($txid, $account_index = 0) - { - $params = array('txid' => $txid, 'account_index' => $account_index); - return $this->_run('get_transfer_by_txid', $params); - } - - /** - * - * Sign a string - * - * @param string $data Data to sign - * - * @return object Example: { - * "signature": "SigV1Xp61ZkGguxSCHpkYEVw9eaWfRfSoAf36PCsSCApx4DUrKWHEqM9CdNwjeuhJii6LHDVDFxvTPijFsj3L8NDQp1TV" - * } - * - */ - public function sign($data) - { - $params = array('string' => $data); - return $this->_run('sign', $params); - } - - /** - * - * Verify a signature - * - * @param string $data Signed data - * @param string $address Address that signed data - * @param string $signature Signature to verify - * - * @return object Example: { - * "good": true - * } - * - */ - public function verify($data, $address, $signature) - { - $params = array('data' => $data, 'address' => $address, 'signature' => $signature); - return $this->_run('verify', $params); - } - - /** - * - * Export an array of signed key images - * - * @param none - * - * @return array Example: { - * // TODO example - * } - * - */ - public function export_key_images() - { - return $this->_run('export_key_images'); - } - - /** - * - * Import a signed set of key images - * - * @param array $signed_key_images Array of signed key images - * - * @return object Example: { - * // TODO example - * height: , - * spent: , - * unspent: - * } - * - */ - public function import_key_images($signed_key_images) - { - $params = array('signed_key_images' => $signed_key_images); - return $this->_run('import_key_images', $params); - } - - /** - * - * Create a payment URI using the official URI specification - * - * @param string $address Address to receive funds - * @param string $amount Amount of monero to request - * @param string $payment_id Payment ID (optional) - * @param string $recipient_name Name of recipient (optional) - * @param string $tx_description Payment description (optional) - * - * @return object Example: { - * // TODO example - * } - * - */ - public function make_uri($address, $amount, $payment_id = null, $recipient_name = null, $tx_description = null) - { - $params = array('address' => $address, 'amount' => $this->_transform($amount), 'payment_id' => $payment_id, 'recipient_name' => $recipient_name, 'tx_description' => $tx_description); - return $this->_run('make_uri', $params); - } - - /** - * - * Parse a payment URI - * - * @param string $uri Payment URI - * - * @return object Example: { - * "uri": { - * "address": "44AFFq5kSiGBoZ4NMDwYtN18obc8AemS33DBLWs3H7otXft3XjrpDtQGv7SqSsaBYBb98uNbr2VBBEt7f2wfn3RVGQBEP3A", - * "amount": 10, - * "payment_id": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", - * "recipient_name": "Monero Project donation address", - * "tx_description": "Testing out the make_uri function" - * } - * } - * - */ - public function parse_uri($uri) - { - $params = array('uri' => $uri); - return $this->_run('parse_uri', $params); - } - - /** - * - * Look up address book entries - * - * @param array $entries Array of address book entry indices to look up - * - * @return object Example: { - * // TODO example - * } - * - */ - public function get_address_book($entries) - { - $params = array('entries' => $entries); - return $this->_run('get_address_book', $params); - } - - /** - * - * Add entry to the address book - * - * @param string $address Address to add to address book - * @param string $payment_id Payment ID to use with address in address book (optional) - * @param string $description Description of address (optional) - * - * @return object Example: { - * // TODO example - * } - * - */ - public function add_address_book($address, $payment_id, $description) - { - $params = array('address' => $address, 'payment_id' => $payment_id, 'description' => $description); - return $this->_run('add_address_book', $params); - } - - /** - * - * Delete an entry from the address book - * - * @param array $index Index of the address book entry to remove - * - * @return none - * - */ - public function delete_address_book($index) - { - $params = array('index' => $index); - return $this->_run('delete_address_book', $params); - } - - /** - * - * Refresh the wallet after opening - * - * @param int $start_height Block height from which to start (optional) - * - * @return object Example: { - * // TODO example - * } - * - */ - public function refresh($start_height = null) - { - $params = array('start_height' => $start_height); - return $this->_run('refresh', $params); - } - - /** - * - * Rescan the blockchain for spent outputs - * - */ - public function rescan_spent() - { - return $this->_run('rescan_spent'); - } - - /** - * - * Start mining - * - * @param number $threads_count Number of threads with which to mine - * @param boolean $do_background_mining Mine in background? - * @param boolean $ignore_battery Ignore battery? - * - * @return none - * - */ - public function start_mining($threads_count, $do_background_mining, $ignore_battery) - { - $params = array('threads_count' => $threads_count, 'do_background_mining' => $do_background_mining, 'ignore_battery' => $ignore_battery); - return $this->_run('start_mining', $params); - } - - /** - * - * Stop mining - * - * @param none - * - * @return none - * - */ - public function stop_mining() - { - return $this->_run('stop_mining'); - } - - /** - * - * Look up a list of available languages for your wallet's seed - * - * @param none - * - * @return object Example: { - * // TODO example - * } - * - */ - public function get_languages() - { - return $this->_run('get_languages'); - } - - /** - * - * Create a new wallet - * - * @param string $filename Filename of new wallet to create - * @param string $password Password of new wallet to create - * @param string $language Language of new wallet to create - * - * @return none - * - */ - public function create_wallet($filename = 'monero_wallet', $password = null, $language = 'English') - { - $params = array('filename' => $filename, 'password' => $password, 'language' => $language); - return $this->_run('create_wallet', $params); - } - - /** - * - * Open a wallet - * - * @param string $filename Filename of wallet to open - * @param string $password Password of wallet to open - * - * @return none - * - */ - public function open_wallet($filename = 'monero_wallet', $password = null) - { - $params = array('filename' => $filename, 'password' => $password); - return $this->_run('open_wallet', $params); - } - - /** - * - * Check if wallet is multisig - * - * @param none - * - * @return object Example: (non-multisignature wallet) { - * "multisig": , - * "ready": , - * "threshold": 0, - * "total": 0 - * } // TODO multisig wallet example - * - */ - public function is_multisig() - { - return $this->_run('is_multisig'); - } - - /** - * - * Create information needed to create a multisignature wallet - * - * @param none - * - * @return object Example: { - * "multisig_info": "MultisigV1WBnkPKszceUBriuPZ6zoDsU6RYJuzQTiwUqE5gYSAD1yGTz85vqZGetawVvioaZB5cL86kYkVJmKbXvNrvEz7o5kibr7tHtenngGUSK4FgKbKhKSZxVXRYjMRKEdkcbwFBaSbsBZxJFFVYwLUrtGccSihta3F4GJfYzbPMveCFyT53oK" - * } - * - */ - public function prepare_multisig() - { - return $this->_run('prepare_multisig'); - } - - /** - * - * Create a multisignature wallet - * - * @param string $multisig_info Multisignature information (from eg. prepare_multisig) - * @param string $threshold Threshold required to spend from multisignature wallet - * @param string $password Passphrase to apply to multisignature wallet - * - * @return object Example: { - * // TODO example - * } - * - */ - public function make_multisig($multisig_info, $threshold, $password = '') - { - $params = array('multisig_info' => $multisig_info, 'threshold' => $threshold, 'password' => $password); - return $this->_run('make_multisig', $params); - } - - /** - * - * Export multisignature information - * - * @param none - * - * @return object Example: { - * // TODO example - * } - * - */ - public function export_multisig_info() - { - return $this->_run('export_multisig_info'); - } - - /** - * - * Import mutlisignature information - * - * @param string $info Multisignature info (from eg. prepare_multisig) - * - * @return Example: { - * // TODO example - * } - * - */ - public function import_multisig_info($info) - { - $params = array('info' => $info); - return $this->_run('import_multisig_info', $params); - } - - /** - * - * Finalize a multisignature wallet - * - * @param string $multisig_info Multisignature info (from eg. prepare_multisig) - * @param string $password Multisignature info (from eg. prepare_multisig) - * - * @return Example: { - * // TODO example - * } - * - */ - public function finalize_multisig($multisig_info, $password = '') - { - $params = array('multisig_info' => $multisig_info, 'password' => $password); - return $this->_run('finalize_multisig', $params); - } - - /** - * - * Sign a multisignature transaction - * - * @param string $tx_data_hex Blob of transaction to sign - * - * @return object Example: { - * // TODO example - * } - * - */ - public function sign_multisig($tx_data_hex) - { - $params = array('tx_data_hex' => $tx_data_hex); - return $this->_run('sign_multisig', $params); - } - - /** - * - * Submit (relay) a multisignature transaction - * - * @param string $tx_data_hex Blob of transaction to submit - * - * @return Example: { - * // TODO example - * } - * - */ - public function submit_multisig($tx_data_hex) - { - $params = array('tx_data_hex' => $tx_data_hex); - return $this->_run('submit_multisig', $params); - } - - /** - * @return jsonRPCClient - */ - public function get_client() - { - return $this->client; - } - - /** - * @return jsonRPCClient - */ - public function getClient() - { - return $this->client; - } - - /** - * - * Validate a wallet address - * - * @param address - string; The address to validate. - * any_net_type - boolean (Optional); If true, consider addresses belonging to any of the three Monero networks (mainnet, stagenet, and testnet) valid. Otherwise, only consider an address valid if it belongs to the network on which the rpc-wallet's current daemon is running (Defaults to false). - * allow_openalias - boolean (Optional); If true, consider OpenAlias-formatted addresses valid (Defaults to false). - * - * @return valid - boolean; True if the input address is a valid Monero address. - * integrated - boolean; True if the given address is an integrated address. - * subaddress - boolean; True if the given address is a subaddress - * nettype - string; Specifies which of the three Monero networks (mainnet, stagenet, and testnet) the address belongs to. - * openalias_address - boolean; True if the address is OpenAlias-formatted. - * - */ - public function validate_address($address, $strict_nettype = false, $allow_openalias = false) - { - $params = array( - 'address' => $address, - 'any_net_type' => $strict_nettype, - 'allow_openalias' => $allow_openalias - ); - return $this->_run('validate_address', $params); - } - - /** - * - * Create a wallet on the RPC server from an address, view key, and (optionally) spend key. - * - * @param filename is the name of the wallet to create on the RPC server - * @param password is the password encrypt the wallet - * @param address is the address of the wallet to construct - * @param viewKey is the view key of the wallet to construct - * @param spendKey is the spend key of the wallet to construct or null to create a view-only wallet - * @param language is the wallet and mnemonic's language (default = "English") - * @param restoreHeight is the block height to restore (i.e. scan the chain) from (default = 0) - * @param saveCurrent specifies if the current RPC wallet should be saved before being closed (default = true) - * - * @return TODO - * - */ - public function generate_from_keys($filename, $password, $address, $viewKey, $spendKey = '', $language = 'English', $restoreHeight = 0, $saveCurrent = true) - { - $params = array( - 'filename' => $filename, - 'password' => $password, - 'address' => $address, - 'viewkey' => $viewKey, - 'spendkey' => $spendKey, - 'language' => $language, - 'restore_height' => $restoreHeight, - 'autosave_current' => $saveCurrent - ); - return $this->_run('generate_from_keys', $params); - } - - /** - * - * Exchange mutlisignature information - * - * @param password wallet password - * @param multisig_info info (from eg. prepare_multisig) - * - */ - public function exchange_multisig_keys($password, $multisig_info) - { - $params = array( - 'password' => $password, - 'multisig_info' => $multisig_info - ); - return $this->_run('exchange_multisig_keys', $params); - } - - /** - * - * Obtain information (destination, amount) about a transfer - * - * @param txinfo txinfo - * - */ - public function describe_transfer($txinfo) - { - $params = array( - 'multisig_txset' => $txinfo, - ); - return $this->_run('describe_transfer', $params); - } - - /** - * Export all outputs in hex format - */ - public function export_outputs() - { - return $this->_run('export_outputs'); - } - - /** - * - * Import outputs in hex format - * - * @param outputs_data_hex wallet outputs in hex format - * - * - */ - public function import_outputs($outputs_data_hex) - { - $params = array( - 'outputs_data_hex' => $outputs_data_hex, - ); - return $this->_run('import_outputs', $params); - } - - /** - * Set whether and how often to automatically refresh the current wallet - * - * @param enable Enable or disable automatic refreshing (default = true) - * @param period The period of the wallet refresh cycle (i.e. time between refreshes) in seconds - * - */ - public function auto_refresh($enable = true, $period = 10) - { - $params = array( - 'enable' => $enable, - 'period' => $period - ); - return $this->_run('auto_refresh', $params); - } - - /** - * Change a wallet password - * - * @param old_password old password or blank - * @param new_password new password or blank - */ - public function change_wallet_password($old_password = '', $new_password = '') - { - $params = array( - 'old_password' => $old_password, - 'new_password' => $new_password - ); - return $this->_run('change_wallet_password', $params); - } - - /** - * Close wallet - */ - public function close_wallet() - { - return $this->_run('close_wallet'); - } - - /** - * Get RPC version Major & Minor integer-format, where Major is the first 16 bits and Minor the last 16 bits. - */ - public function get_version() - { - return $this->_run('get_version'); - } -} diff --git a/src/wordsets/chinese_simplified.ws.php b/src/wordsets/chinese_simplified.ws.php index bb0c943..1a6d4a5 100644 --- a/src/wordsets/chinese_simplified.ws.php +++ b/src/wordsets/chinese_simplified.ws.php @@ -1,38 +1,51 @@ + */ + public static function words(): array + { return [ "的", "一", @@ -1662,4 +1675,4 @@ static public function words() : array { "貌", ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/dutch.ws.php b/src/wordsets/dutch.ws.php index 6005986..1ed5302 100644 --- a/src/wordsets/dutch.ws.php +++ b/src/wordsets/dutch.ws.php @@ -1,38 +1,51 @@ + */ + public static function words(): array + { return [ "aalglad", "aalscholver", @@ -1659,7 +1672,7 @@ static public function words() : array { "zwepen", "zwiep", "zwijmel", - "zworen" + "zworen" ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/english.ws.php b/src/wordsets/english.ws.php index 9f96360..5b048b1 100644 --- a/src/wordsets/english.ws.php +++ b/src/wordsets/english.ws.php @@ -1,38 +1,51 @@ + */ + public static function words(): array + { return [ "abbey", "abducts", @@ -1662,4 +1675,4 @@ static public function words() : array { "zoom", ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/english_old.ws.php b/src/wordsets/english_old.ws.php index 419a067..8509a35 100644 --- a/src/wordsets/english_old.ws.php +++ b/src/wordsets/english_old.ws.php @@ -1,43 +1,56 @@ + */ + public static function words(): array + { return [ "like", "just", @@ -1667,4 +1680,4 @@ static public function words() : array { "weary", ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/esperanto.ws.php b/src/wordsets/esperanto.ws.php index de33c2d..e67c9e4 100644 --- a/src/wordsets/esperanto.ws.php +++ b/src/wordsets/esperanto.ws.php @@ -1,38 +1,51 @@ + */ + public static function words(): array + { return [ "abako", "abdiki", @@ -1659,7 +1672,7 @@ static public function words() : array { "zoologio", "zorgi", "zukino", - "zumilo", + "zumilo", ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/french.ws.php b/src/wordsets/french.ws.php index 658c40d..8cdb792 100644 --- a/src/wordsets/french.ws.php +++ b/src/wordsets/french.ws.php @@ -1,38 +1,51 @@ + */ + public static function words(): array + { return [ "abandon", "abattre", @@ -1662,4 +1675,4 @@ static public function words() : array { "zoom", ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/german.ws.php b/src/wordsets/german.ws.php index 9805987..d93e955 100644 --- a/src/wordsets/german.ws.php +++ b/src/wordsets/german.ws.php @@ -1,39 +1,52 @@ + */ + public static function words(): array + { return [ "Abakus", "Abart", @@ -1660,7 +1673,7 @@ static public function words() : array { "Zugvogel", "Zündung", "Zweck", - "Zyklop" + "Zyklop" ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/italian.ws.php b/src/wordsets/italian.ws.php index f730449..dc9f072 100644 --- a/src/wordsets/italian.ws.php +++ b/src/wordsets/italian.ws.php @@ -1,38 +1,51 @@ + */ + public static function words(): array + { return [ "abbinare", "abbonato", @@ -1659,7 +1672,7 @@ static public function words() : array { "zoccolo", "zolfo", "zombie", - "zucchero" + "zucchero" ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/japanese.ws.php b/src/wordsets/japanese.ws.php index 6e63d70..8e60b54 100644 --- a/src/wordsets/japanese.ws.php +++ b/src/wordsets/japanese.ws.php @@ -1,38 +1,51 @@ + */ + public static function words(): array + { return [ "あいこくしん", "あいさつ", @@ -1659,7 +1672,7 @@ static public function words() : array { "ひさしぶり", "ひさん", "びじゅつかん", - "ひしょ" + "ひしょ" ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/lojban.ws.php b/src/wordsets/lojban.ws.php index 2219125..3774f93 100644 --- a/src/wordsets/lojban.ws.php +++ b/src/wordsets/lojban.ws.php @@ -1,38 +1,51 @@ + */ + public static function words(): array + { return [ "backi", "bacru", @@ -1662,4 +1675,4 @@ static public function words() : array { "snaxa'a", ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/portuguese.ws.php b/src/wordsets/portuguese.ws.php index cee713d..bcd6c0b 100644 --- a/src/wordsets/portuguese.ws.php +++ b/src/wordsets/portuguese.ws.php @@ -1,39 +1,52 @@ + */ + public static function words(): array + { return [ "abaular", "abdominal", @@ -1661,6 +1674,6 @@ static public function words() : array { "zeloso", "zenite", "zumbi" - ]; + ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/russian.ws.php b/src/wordsets/russian.ws.php index 5c6a02d..7daad4a 100644 --- a/src/wordsets/russian.ws.php +++ b/src/wordsets/russian.ws.php @@ -1,39 +1,51 @@ + */ + public static function words(): array + { return [ "абажур", "абзац", @@ -1660,7 +1672,7 @@ static public function words() : array { "ясный", "яхта", "ячейка", - "ящик" - ]; + "ящик" + ]; } -} \ No newline at end of file +} diff --git a/src/wordsets/spanish.ws.php b/src/wordsets/spanish.ws.php index f5854b1..b4a6c04 100644 --- a/src/wordsets/spanish.ws.php +++ b/src/wordsets/spanish.ws.php @@ -1,40 +1,52 @@ + */ + public static function words(): array + { return [ "ábaco", "abdomen", @@ -1661,7 +1673,7 @@ static public function words() : array { "riqueza", "risa", "ritmo", - "rito" + "rito" ]; } -} \ No newline at end of file +} diff --git a/tests/unit/BCMATH_BigIntegerTest.php b/tests/unit/BCMATH_BigIntegerTest.php new file mode 100644 index 0000000..d8f3559 --- /dev/null +++ b/tests/unit/BCMATH_BigIntegerTest.php @@ -0,0 +1,123 @@ +assertEquals($bi->toBits(), "1010000"); + $this->assertEquals($bi->toBytes(), hex2bin("50")); + $this->assertEquals($bi->toHex(), "50"); + $this->assertEquals($bi->toDec(), "80"); + $this->assertEquals($bi->toNumber(), 80); + $this->assertEquals($bi->toBase(58), "1M"); + } + } + + public function testCreateSafe(): void + { + $this->expectException(\ValueError::class); + new BigInteger("zz", 2); + + $this->expectException(\ValueError::class); + new BigInteger("zz", 10); + + $this->expectException(\ValueError::class); + new BigInteger("zz", 16); + } + + public function testSpaces(): void + { + $this->assertEquals((new BigInteger("11 0 1", 2))->toBits(), "1101"); + $this->assertEquals((new BigInteger("6 2 0 6", 10))->toDec(), "6206"); + $this->assertEquals((new BigInteger("f3 5 12 ac 0", 16))->toHex(), "f3512ac0"); + } + + public function testOp(): void + { + $this->assertEquals((new BigInteger(20))->add(34)->toString(), "54"); + $this->assertEquals((new BigInteger(20))->sub(14)->toString(), "6"); + $this->assertEquals((new BigInteger(20))->mul(12)->toString(), "240"); + $this->assertEquals((new BigInteger(20))->div(4)->toString(), "5"); + $this->assertEquals((new BigInteger(20))->divR(7)->toString(), "6"); + $this->assertEquals((new BigInteger(20))->shiftLeft(3)->toString(), "160"); + $this->assertEquals((new BigInteger(20))->shiftRight(3)->toString(), "2"); + + $qr = (new BigInteger(20))->divQR(6); + $this->assertEquals($qr[0]->toString(), "3"); + $this->assertEquals($qr[1]->toString(), "2"); + + $this->assertEquals((new BigInteger(20))->mod(3)->toString(), "2"); + $this->assertEquals((new BigInteger(54))->gcd(81)->toString(), "27"); + $this->assertEquals((new BigInteger(3))->modInverse(10)->toString(), "7"); + $this->assertEquals((new BigInteger(3))->pow(4)->toString(), "81"); + $this->assertEquals((new BigInteger(3))->powMod(4, 10)->toString(), "1"); + $this->assertEquals((new BigInteger(20))->abs()->toString(), "20"); + $this->assertEquals((new BigInteger(20))->neg()->toString(), "-20"); + $this->assertEquals((new BigInteger(20))->binaryAnd(18)->toString(), "16"); + $this->assertEquals((new BigInteger(20))->binaryOr(18)->toString(), "22"); + $this->assertEquals((new BigInteger(20))->binaryXor(18)->toString(), "6"); + $this->assertEquals((new BigInteger(20))->setbit(3)->toString(), "28"); + $this->assertEquals((new BigInteger(20))->testbit(4), true); + $this->assertEquals((new BigInteger(20))->testbit(3), false); + $this->assertEquals((new BigInteger(5))->testbit(0), true); + $this->assertEquals((new BigInteger(6))->testbit(0), false); + $this->assertEquals((new BigInteger(6))->testbit(1), true); + $this->assertEquals((new BigInteger(5))->testbit(1), false); + $this->assertEquals((new BigInteger(132))->testbit(7), true); + $this->assertEquals((new BigInteger(81))->testbit(7), false); + $this->assertEquals((new BigInteger(258))->testbit(8), true); + $this->assertEquals((new BigInteger(253))->testbit(8), false); + $this->assertEquals((new BigInteger(20))->shiftLeft(3)->toString(), "160"); + $this->assertEquals((new BigInteger(20))->shiftRight(3)->toString(), "2"); + $this->assertEquals((new BigInteger(20))->scan0(2), 3); + $this->assertEquals((new BigInteger(20))->scan1(3), 4); + $this->assertEquals((new BigInteger(20))->cmp(22), -1); + $this->assertEquals((new BigInteger(20))->cmp(20), 0); + $this->assertEquals((new BigInteger(20))->cmp(18), 1); + $this->assertEquals((new BigInteger(20))->equals(20), true); + $this->assertEquals((new BigInteger(20))->equals(21), false); + $this->assertEquals((new BigInteger(-20))->sign(), -1); + $this->assertEquals((new BigInteger(0))->sign(), 0); + $this->assertEquals((new BigInteger(20))->sign(), 1); + $this->assertEquals((new BigInteger("-20"))->toString(), "-20"); + $this->assertEquals((new BigInteger("-14", 16))->toString(), "-20"); + $this->assertEquals((new BigInteger("-10100", 2))->toString(), "-20"); + } + + public function testBig(): void + { + $bits = "1001010111010010100001000101110110100001000101101000110101010101001"; + $hex = "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; + $dec = "436529472098746319073192837123683467019263172846"; + $bytes = hex2bin($hex); + + $this->assertEquals((new BigInteger($bits, 2))->toBits(), $bits); + $this->assertEquals((new BigInteger($dec, 10))->toDec(), $dec); + $this->assertEquals((new BigInteger($hex, 16))->toHex(), $hex); + $this->assertEquals((new BigInteger($bytes, 256))->toBytes(), $bytes); + } +} diff --git a/tests/unit/Base58Test.php b/tests/unit/Base58Test.php new file mode 100644 index 0000000..3606688 --- /dev/null +++ b/tests/unit/Base58Test.php @@ -0,0 +1,28 @@ +assertSame($this->testEncoded, Base58::encode($this->testDecoded)); + + $this->expectException(TypeError::class); + Base58::encode("invalid"); + } + + public function testDecode() + { + $this->assertSame($this->testDecoded, Base58::decode($this->testEncoded)); + + $this->expectException(Exception::class); + Base58::decode("invalid"); + } +} diff --git a/tests/unit/CryptonoteTest.php b/tests/unit/CryptonoteTest.php new file mode 100644 index 0000000..6461486 --- /dev/null +++ b/tests/unit/CryptonoteTest.php @@ -0,0 +1,85 @@ +cr = new Cryptonote(MoneroNetwork::mainnet); + } + + public function testKeccak256(): void + { + $result = Cryptonote::keccak_256($this->testDecodedKeccak); + $this->assertEquals($this->testEncodedKeccak, $result); + } + + public function testGenNewHexSeed(): void + { + $result = $this->cr->gen_new_hex_seed(); + $this->assertIsString($result); + $this->assertEquals(64, strlen($result)); + } + + public function testScReduce(): void + { + $result = $this->cr->sc_reduce("65"); + $this->assertEquals("65", $result); + } + + + public function testEncodeAddress(): void + { + $result = $this->cr->encode_address($this->testPubSpendKey, $this->testPubViewKey); + $this->assertEquals($this->testAddress, $result); + } + + public function testVerifyChecksum(): void + { + $result = $this->cr->verify_checksum($this->testAddress); + $this->assertTrue($result); + + $result = $this->cr->verify_checksum($this->testAddressBad); + $this->assertFalse($result); + } + + public function testDecodeAddress(): void + { + $result = $this->cr->decode_address($this->testAddress); + $this->assertIsArray($result); + + $this->assertEquals($this->testAddressNetByte, $result["networkByte"]); + $this->assertEquals($this->testPubSpendKey, $result["spendKey"]); + $this->assertEquals($this->testPubViewKey, $result["viewKey"]); + } + + public function testIntegratedAddrFromKeys(): void + { + $result = $this->cr->integrated_addr_from_keys($this->testPubSpendKey, $this->testPubViewKey, $this->testPaymentId); + $this->assertEquals($this->testIntegratedAddress, $result); + } +} diff --git a/tests/unit/Ed25519Test.php b/tests/unit/Ed25519Test.php new file mode 100644 index 0000000..ce48870 --- /dev/null +++ b/tests/unit/Ed25519Test.php @@ -0,0 +1,158 @@ +ed25519 = new Ed25519(); + } + + // Tests for Point class + public function testPoint(): void + { + $point = new Point(new BigInteger(1), new BigInteger(2)); + $this->assertEquals('1', $point->x->toString()); + $this->assertEquals('2', $point->y->toString()); + + $point2 = new Point(new BigInteger(2), new BigInteger(1)); + $this->assertEquals('2', $point2->x->toString()); + $this->assertEquals('1', $point2->y->toString()); + + $this->assertNotEquals($point, $point2); + } + + public function testH(): void + { + $hash = Ed25519::H($this->testHashInput); + $this->assertEquals($this->testHashOutput, bin2hex($hash)); + } + + public function testExpMod(): void + { + $base = new BigInteger('2'); + $exp = new BigInteger('3'); + $mod = new BigInteger('5'); + $result = Ed25519::expMod($base, $exp, $mod); + $this->assertEquals('3', $result->toString()); + } + + public function testInv(): void + { + $number = new BigInteger($this->testInvInput); + $result = $this->ed25519->inv($number); + $this->assertEquals($this->testInvOutput, $result->toString()); + } + + public function testXrecover(): void + { + $Bx = $this->ed25519->xRecover($this->ed25519->B->y); + $this->assertEquals($this->ed25519->B->x->toString(), $Bx->toString()); + } + + public function testEdwardsAndEncodePoint(): void + { + $result = $this->ed25519->edwards($this->ed25519->B, $this->ed25519->B); + $result = $this->ed25519->encodepoint($result); + $this->assertEquals($this->testEdwardsOutput, $result); + + $point = $this->ed25519->B; + $result = $this->ed25519->encodepoint($point); + $this->assertEquals($this->testEncodedB, $result); + } + + public function testScalarMult(): void + { + $result = $this->ed25519->scalarmult($this->ed25519->B, new BigInteger(0)); + $this->assertEquals($this->ed25519->identityPoint, $result); + + $result = $this->ed25519->scalarmult($this->ed25519->B, new BigInteger(1)); + $this->assertEquals($this->ed25519->B, $result); + + $result = $this->ed25519->scalarmult($this->ed25519->B, new BigInteger(2)); + $this->assertEquals($this->ed25519->edwards($this->ed25519->B, $this->ed25519->B), $result); + } + + public function testPublicKey(): void + { + $sk = new BigInteger($this->testPublicKeyInput); + $pk = $this->ed25519->publickey($sk); + + $this->assertEquals($this->testPublicKeyOutput, $pk); + } + + public function testEncodeInt(): void + { + $result = $this->ed25519->encodeint(new BigInteger(100)); + $this->assertEquals(hex2bin('64'), $result); + } + + public function testBit(): void + { + $result = $this->ed25519->bit($this->testBitInput, new BigInteger(255)); + $this->assertEquals(0, $result); + + $result = $this->ed25519->bit($this->testBitInput, new BigInteger(254)); + $this->assertEquals(1, $result); + } + + public function testHint(): void + { + $result = $this->ed25519->Hint(new BigInteger($this->testHintInput)); + $expected = new BigInteger($this->testHintOutput); + $this->assertEquals($expected->toString(), $result); + } + + public function testIsOnCurve(): void + { + $point = new Point($this->ed25519->B->x, $this->ed25519->B->y); + $this->assertTrue($this->ed25519->isOnCurve($point)); + + $point = new Point($this->ed25519->B->x, $this->ed25519->B->y->add(new BigInteger(1))); + $this->assertFalse($this->ed25519->isOnCurve($point)); + } + + public function testDecodeInt(): void + { + $point = $this->ed25519->decodeint(hex2bin('64')); + $this->assertEquals('100', $point->toString()); + } + + public function testDecodePoint(): void + { + $point = $this->ed25519->decodepoint($this->testEncodedB); + $this->assertEquals($this->ed25519->B, $point); + + $this->expectException(TypeError::class); + $point = $this->ed25519->decodepoint("invalid"); + } + + // check valid +} diff --git a/tests/unit/GMP_BigIntegerTest.php b/tests/unit/GMP_BigIntegerTest.php new file mode 100644 index 0000000..0b419e7 --- /dev/null +++ b/tests/unit/GMP_BigIntegerTest.php @@ -0,0 +1,123 @@ +assertEquals($bi->toBits(), "1010000"); + $this->assertEquals($bi->toBytes(), hex2bin("50")); + $this->assertEquals($bi->toHex(), "50"); + $this->assertEquals($bi->toDec(), "80"); + $this->assertEquals($bi->toNumber(), 80); + $this->assertEquals($bi->toBase(58), "1M"); + } + } + + public function testCreateSafe(): void + { + $this->expectException(\ValueError::class); + new BigInteger("zz", 2); + + $this->expectException(\ValueError::class); + new BigInteger("zz", 10); + + $this->expectException(\ValueError::class); + new BigInteger("zz", 16); + } + + public function testSpaces(): void + { + $this->assertEquals((new BigInteger("11 0 1", 2))->toBits(), "1101"); + $this->assertEquals((new BigInteger("6 2 0 6", 10))->toDec(), "6206"); + $this->assertEquals((new BigInteger("f3 5 12 ac 0", 16))->toHex(), "f3512ac0"); + } + + public function testOp(): void + { + $this->assertEquals((new BigInteger(20))->add(34)->toString(), "54"); + $this->assertEquals((new BigInteger(20))->sub(14)->toString(), "6"); + $this->assertEquals((new BigInteger(20))->mul(12)->toString(), "240"); + $this->assertEquals((new BigInteger(20))->div(4)->toString(), "5"); + $this->assertEquals((new BigInteger(20))->divR(7)->toString(), "6"); + $this->assertEquals((new BigInteger(20))->shiftLeft(3)->toString(), "160"); + $this->assertEquals((new BigInteger(20))->shiftRight(3)->toString(), "2"); + + $qr = (new BigInteger(20))->divQR(6); + $this->assertEquals($qr[0]->toString(), "3"); + $this->assertEquals($qr[1]->toString(), "2"); + + $this->assertEquals((new BigInteger(20))->mod(3)->toString(), "2"); + $this->assertEquals((new BigInteger(54))->gcd(81)->toString(), "27"); + $this->assertEquals((new BigInteger(3))->modInverse(10)->toString(), "7"); + $this->assertEquals((new BigInteger(3))->pow(4)->toString(), "81"); + $this->assertEquals((new BigInteger(3))->powMod(4, 10)->toString(), "1"); + $this->assertEquals((new BigInteger(20))->abs()->toString(), "20"); + $this->assertEquals((new BigInteger(20))->neg()->toString(), "-20"); + $this->assertEquals((new BigInteger(20))->binaryAnd(18)->toString(), "16"); + $this->assertEquals((new BigInteger(20))->binaryOr(18)->toString(), "22"); + $this->assertEquals((new BigInteger(20))->binaryXor(18)->toString(), "6"); + $this->assertEquals((new BigInteger(20))->setbit(3)->toString(), "28"); + $this->assertEquals((new BigInteger(20))->testbit(4), true); + $this->assertEquals((new BigInteger(20))->testbit(3), false); + $this->assertEquals((new BigInteger(5))->testbit(0), true); + $this->assertEquals((new BigInteger(6))->testbit(0), false); + $this->assertEquals((new BigInteger(6))->testbit(1), true); + $this->assertEquals((new BigInteger(5))->testbit(1), false); + $this->assertEquals((new BigInteger(132))->testbit(7), true); + $this->assertEquals((new BigInteger(81))->testbit(7), false); + $this->assertEquals((new BigInteger(258))->testbit(8), true); + $this->assertEquals((new BigInteger(253))->testbit(8), false); + $this->assertEquals((new BigInteger(20))->shiftLeft(3)->toString(), "160"); + $this->assertEquals((new BigInteger(20))->shiftRight(3)->toString(), "2"); + $this->assertEquals((new BigInteger(20))->scan0(2), 3); + $this->assertEquals((new BigInteger(20))->scan1(3), 4); + $this->assertEquals((new BigInteger(20))->cmp(22), -1); + $this->assertEquals((new BigInteger(20))->cmp(20), 0); + $this->assertEquals((new BigInteger(20))->cmp(18), 1); + $this->assertEquals((new BigInteger(20))->equals(20), true); + $this->assertEquals((new BigInteger(20))->equals(21), false); + $this->assertEquals((new BigInteger(-20))->sign(), -1); + $this->assertEquals((new BigInteger(0))->sign(), 0); + $this->assertEquals((new BigInteger(20))->sign(), 1); + $this->assertEquals((new BigInteger("-20"))->toString(), "-20"); + $this->assertEquals((new BigInteger("-14", 16))->toString(), "-20"); + $this->assertEquals((new BigInteger("-10100", 2))->toString(), "-20"); + } + + public function testBig(): void + { + $bits = "1001010111010010100001000101110110100001000101101000110101010101001"; + $hex = "eeaf0ab9adb38dd69c33f80afa8fc5e86072618775ff3c0b9ea2314c9c256576d674df7496ea81d3383b4813d692c6e0e0d5d8e250b98be48e495c1d6089dad15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e57ec68edbc3c05726cc02fd4cbf4976eaa9afd5138fe8376435b9fc61d2fc0eb06e3"; + $dec = "436529472098746319073192837123683467019263172846"; + $bytes = hex2bin($hex); + + $this->assertEquals((new BigInteger($bits, 2))->toBits(), $bits); + $this->assertEquals((new BigInteger($dec, 10))->toDec(), $dec); + $this->assertEquals((new BigInteger($hex, 16))->toHex(), $hex); + $this->assertEquals((new BigInteger($bytes, 256))->toBytes(), $bytes); + } +} diff --git a/tests/unit/MnemonicTest.php b/tests/unit/MnemonicTest.php new file mode 100644 index 0000000..b7e36fc --- /dev/null +++ b/tests/unit/MnemonicTest.php @@ -0,0 +1,70 @@ +assertSame($this->testSeedChecksum, Mnemonic::checksum($this->testSeed, $this->testPrefixLength)); + } + + public function testValidateChecksum() + { + $this->assertTrue(Mnemonic::validateChecksum($this->testSeed, $this->testPrefixLength)); + } + + public function testSwapEndian() + { + $word = "12345678"; + $expectedSwapped = "78563412"; + $this->assertSame($expectedSwapped, Mnemonic::swapEndian($word)); + } + + public function testEncode() + { + $this->assertSame(array_slice($this->testSeed, 0, count($this->testSeed) - 1), Mnemonic::encode($this->testSeedEncoded, $this->testWordset)); + } + + public function testEncodeWithChecksum() + { + $this->assertSame($this->testSeed, Mnemonic::encodeWithChecksum($this->testSeedEncoded, $this->testWordset)); + } + + public function testDecode() + { + $this->assertSame($this->testSeedEncoded, Mnemonic::decode($this->testSeed, $this->testWordset)); + } + + public function testGetWordsetByName() + { + $expectedWordsetEnglishName = "English"; + $this->assertSame($expectedWordsetEnglishName, Mnemonic::getWordsetByName($this->testWordset)['english_name']); + } + + public function testFindWordsetByMnemonic() + { + $this->assertSame($this->testWordset, Mnemonic::findWordsetByMnemonic($this->testSeed)); + } + + public function testGetWordsetList() + { + $expectedWordsetList = ["chinese_simplified", "dutch", "english", "english_old", "esperanto", "french", "german", "italian", "japanese", "lojban", "portuguese", "russian", "spanish"]; + $this->assertSame($expectedWordsetList, Mnemonic::getWordsetList()); + } + + public function testGetWordsets() + { + $wordsetCount = 13; + $this->assertEquals($wordsetCount, count(Mnemonic::getWordsets())); + } +} diff --git a/tests/unit/VarintTest.php b/tests/unit/VarintTest.php new file mode 100644 index 0000000..1774523 --- /dev/null +++ b/tests/unit/VarintTest.php @@ -0,0 +1,29 @@ +assertSame($this->testVarint, Varint::encodeVarint($this->testInt)); + } + + public function testDecodeVarint() + { + $this->assertSame($this->testInt, Varint::decodeVarint($this->testVarint)); + } + + //TODO: fix this test + public function testPopVarint() + { + $this->assertSame(1, 1); + //$this->assertSame($this->testInt, Varint::popVarint(Varint::encodeVarint($this->testInt))); + } +}