first commit

This commit is contained in:
2025-11-28 10:08:12 +08:00
commit 09a14bf0a3
1088 changed files with 145132 additions and 0 deletions
@@ -0,0 +1,3 @@
/doc export-ignore
/examples export-ignore
/tests export-ignore
+82
View File
@@ -0,0 +1,82 @@
name: Tests
on:
push:
pull_request:
jobs:
test:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: ['ubuntu-20.04', 'ubuntu-18.04']
php-versions: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0']
phpunit-versions: ['latest']
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Checkout submodules
uses: textbook/git-checkout-submodule-action@master
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: gmp
coverage: none
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist
- name: Run tests
run: make phpunit-full-ci
coverage:
runs-on: ${{ matrix.operating-system }}
strategy:
matrix:
operating-system: ['ubuntu-20.04']
php-versions: ['7.4']
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Checkout submodules
uses: textbook/git-checkout-submodule-action@master
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
extensions: gmp, xdebug
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist
- name: Run tests
run: make phpunit-ci
- name: Upload coverage to Scrutinizer
run: make scrutinizer
+4
View File
@@ -0,0 +1,4 @@
tests/output
vendor/
composer.lock
+3
View File
@@ -0,0 +1,3 @@
[submodule "tests/import/wycheproof"]
path = tests/import/wycheproof
url = https://github.com/google/wycheproof
+94
View File
@@ -0,0 +1,94 @@
build:
nodes:
php-scrutinizer-run:
project_setup:
override: true
tests:
override: [php-scrutinizer-run]
environment:
mysql: false
redis: false
postgresql: false
php:
version: 7.2
ini:
'zend.assertions': '1'
'assert.exception': '1'
dependencies:
override:
-
command: 'composer install'
tests:
override: true
project_setup:
override: true
filter:
paths:
- src/*
excluded_paths:
- examples/*
- tests/*
- vendor/*
tools:
php_cpd: true
php_pdepend: true
php_analyzer: true
php_sim: true
php_changetracking: true
php_mess_detector: true
php_code_sniffer: true
sensiolabs_security_checker: true
php_code_coverage: true
php_pdepend:
excluded_dirs: [vendor, doc, examples, build, tests]
external_code_coverage:
timeout: 1300
runs: 1
changetracking:
bug_patterns: ["\bfix(?:es|ed)?\b"]
feature_patterns: ["\badd(?:s|ed)?\b", "\bimplement(?:s|ed)?\b"]
build_failure_conditions:
# No new critical issue is introduced (existing ones are tolerated)
- 'issues.severity(>= CRITICAL).new.exists'
# Code Coverage drops below 60%
- 'project.metric("scrutinizer.test_coverage", < 0.80)'
# Code Coverage decreased from previous inspection by more than 1%
- 'project.metric_change("scrutinizer.test_coverage", < -0.01)'
checks:
php:
simplify_boolean_return: true
return_doc_comments: true
return_doc_comment_if_not_inferrable: true
remove_extra_empty_lines: true
properties_in_camelcaps: true
phpunit_assertions: true
parameters_in_camelcaps: true
parameter_doc_comments: true
param_doc_comment_if_not_inferrable: true
overriding_parameter: true
no_short_variable_names:
minimum: '3'
no_short_method_names:
minimum: '3'
no_long_variable_names:
maximum: '20'
no_goto: true
fix_use_statements:
remove_unused: true
preserve_multiple: false
preserve_blanklines: false
order_alphabetically: false
fix_line_ending: true
check_method_contracts:
verify_interface_like_constraints: true
verify_documented_constraints: true
verify_parent_constraints: true
+55
View File
@@ -0,0 +1,55 @@
test: phpunit phpcs
.PHONY: test phpunit phpcs
pretest:
if [ ! -d vendor ] || [ ! -f composer.lock ]; then composer install; else echo "Already have dependencies"; fi
phpunit: pretest
mkdir -p tests/output
vendor/bin/phpunit --coverage-text --coverage-clover=tests/output/coverage.clover --coverage-html=tests/output/Results
test-examples:
./validate_examples.sh
phpunit-ci: pretest
vendor/bin/phpunit --coverage-text --coverage-clover=tests/output/coverage.clover
phpunit-full-ci: pretest
vendor/bin/phpunit -c phpunit.full.xml --coverage-text --coverage-clover=tests/output/coverage.clover
ifndef STRICT
STRICT = 0
endif
ifeq "$(STRICT)" "1"
phpcs: pretest
vendor/bin/phpcs --standard=PSR2 src tests/unit/
else
phpcs: pretest
vendor/bin/phpcs --standard=PSR2 -n src tests/unit/
endif
phpcbf: pretest
vendor/bin/phpcbf --standard=PSR2 -n src tests/unit/
ocular:
wget https://scrutinizer-ci.com/ocular.phar
ifdef OCULAR_TOKEN
scrutinizer: ocular
@php ocular.phar code-coverage:upload --format=php-clover tests/output/coverage.clover --access-token=$(OCULAR_TOKEN);
else
scrutinizer: ocular
php ocular.phar code-coverage:upload --format=php-clover tests/output/coverage.clover;
endif
clean: clean-env clean-deps
clean-env:
rm -rf coverage.clover
rm -rf ocular.phar
rm -rf tests/output/
clean-deps:
rm -rf vendor/
+73
View File
@@ -0,0 +1,73 @@
## Pure PHP Elliptic Curve DSA and DH
[![Build Status](https://travis-ci.org/phpecc/phpecc.svg?branch=master)](https://travis-ci.org/phpecc/phpecc)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phpecc/phpecc/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phpecc/phpecc?branch=master)
[![Code Coverage](https://scrutinizer-ci.com/g/phpecc/phpecc/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/phpecc/phpecc/?branch=master)
[![Latest Stable Version](https://poser.pugx.org/mdanter/ecc/v/stable.png)](https://packagist.org/packages/mdanter/ecc)
[![Total Downloads](https://poser.pugx.org/mdanter/ecc/downloads.png)](https://packagist.org/packages/mdanter/ecc)
[![Latest Unstable Version](https://poser.pugx.org/mdanter/ecc/v/unstable.png)](https://packagist.org/packages/mdanter/ecc)
[![License](https://poser.pugx.org/mdanter/ecc/license.png)](https://packagist.org/packages/mdanter/ecc)
### Information
This library is a rewrite/update of Matyas Danter's ECC library. All credit goes to him.
For more information on Elliptic Curve Cryptography please read [this fine article](http://www.matyasdanter.com/2010/12/elliptic-curve-php-oop-dsa-and-diffie-hellman/).
The library supports the following curves:
- secp112r1
- secp256k1
- nistp192
- nistp224
- nistp256 / secp256r1
- nistp384 / secp384r1
- nistp521
During ECDSA, a random value `k` is required. It is acceptable to use a true RNG to generate this value, but
should the same `k` value ever be repeatedly used for a key, an attacker can recover that signing key.
The HMAC random generator can derive a deterministic k value from the message hash and private key, voiding
this concern.
The library uses a non-branching Montgomery ladder for scalar multiplication, as it's constant time and avoids secret
dependant branches.
### License
This package is released under the MIT license.
### Requirements
* PHP 7.0+ or PHP 8.0+
* composer
* ext-gmp
Support for older PHP versions:
* v0.4.x: php ^5.6|<7.2
* v0.5.x: php ^7.0
* v1.0.x: php ^7.0|^8.0
### Installation
You can install this library via Composer :
`composer require mdanter/ecc:^1.0`
### Contribute
When sending in pull requests, please make sure to run the `make` command.
The default target runs all PHPUnit and PHPCS tests. All tests
must validate for your contribution to be accepted.
It's also always a good idea to check the results of the [Scrutinizer analysis](https://scrutinizer-ci.com/g/phpecc/phpecc/) for your pull requests.
### Usage
Examples:
* [Key generation](./examples/key_generation.php)
* [ECDH exchange](./examples/ecdh_exchange.php)
* [Signature creation](./examples/creating_signature.php)
* [Signature verification](./examples/verify_signature.php)
+47
View File
@@ -0,0 +1,47 @@
{
"name": "mdanter/ecc",
"description": "PHP Elliptic Curve Cryptography library",
"type": "library",
"homepage": "https://github.com/phpecc/phpecc",
"keywords": ["secp256k1", "secp256r1", "nistp192", "nistp224", "nistp256", "nistp384", "nistp521", "ECDSA", "diffie", "hellman", "ECDH", "elliptic", "curve", "phpecc"],
"license": "MIT",
"authors": [
{
"name": "Matyas Danter",
"homepage": "http://matejdanter.com/",
"role": "Author"
},
{
"name": "Thibaud Fabre",
"email": "thibaud@aztech.io",
"homepage": "http://aztech.io",
"role": "Maintainer"
},
{
"name": "Thomas Kerin",
"email": "afk11@users.noreply.github.com",
"role": "Maintainer"
}
],
"require": {
"php": "^7.0||^8.0",
"ext-gmp": "*",
"fgrosse/phpasn1": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0||^8.0||^9.0",
"squizlabs/php_codesniffer": "^2.0",
"symfony/yaml": "^2.6|^3.0"
},
"autoload": {
"psr-4": {
"Mdanter\\Ecc\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Mdanter\\Ecc\\Tests\\": "tests/unit",
"Mdanter\\Ecc\\WycheProof\\": "tests/wycheproof"
}
}
}
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true"
convertErrorsToExceptions="true" convertNoticesToExceptions="true"
convertWarningsToExceptions="true" processIsolation="false"
stopOnFailure="false">
<php>
<const name="PHPUNIT_DEBUG" value="false" />
<ini name="display_errors" value="On" />
<ini name="error_reporting" value="32767" />
</php>
<testsuites>
<testsuite name="WycheProof tests">
<directory suffix="Test.php">./tests/wycheproof</directory>
</testsuite>
<testsuite name="Unit tests">
<directory suffix="Test.php">./tests/unit</directory>
</testsuite>
</testsuites>
<logging>
<log type="coverage-html" target="./tests/output/Coverage/"
charset="UTF-8" yui="true" highlight="true" />
<log type="junit" target="./tests/output/Results/Results.xml"
logIncompleteSkipped="true" />
</logging>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
</phpunit>
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php" colors="true"
convertErrorsToExceptions="true" convertNoticesToExceptions="true"
convertWarningsToExceptions="true" processIsolation="false"
stopOnFailure="false">
<php>
<const name="PHPUNIT_DEBUG" value="false" />
<ini name="display_errors" value="On" />
<ini name="error_reporting" value="32767" />
</php>
<testsuites>
<testsuite name="All">
<directory suffix="Test.php">./tests/unit</directory>
</testsuite>
</testsuites>
<logging>
<log type="coverage-html" target="./tests/output/Coverage/"
charset="UTF-8" yui="true" highlight="true" />
<log type="junit" target="./tests/output/Results/Results.xml"
logIncompleteSkipped="true" />
</logging>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
</phpunit>
+169
View File
@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\EcDH;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
use Mdanter\Ecc\Exception\ExchangeException;
use Mdanter\Ecc\Math\GmpMathInterface;
/**
* This class is the implementation of ECDH.
* EcDH is safe key exchange and achieves
* that a key is transported securely between two parties.
* The key then can be hashed and used as a basis in
* a dual encryption scheme, along with AES for faster
* two- way encryption.
*/
class EcDH implements EcDHInterface
{
/**
* Adapter used for math calculations
*
* @var GmpMathInterface
*/
private $adapter;
/**
* Secret key between the two parties
*
* @var PublicKeyInterface
*/
private $secretKey = null;
/**
*
* @var PublicKeyInterface
*/
private $recipientKey;
/**
*
* @var PrivateKeyInterface
*/
private $senderKey;
/**
* Initialize a new exchange from a generator point.
*
* @param GmpMathInterface $adapter A math adapter instance.
*/
public function __construct(GmpMathInterface $adapter)
{
$this->adapter = $adapter;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\EcDH\EcDHInterface::calculateSharedKey()
*/
public function calculateSharedKey(): \GMP
{
$this->calculateKey();
return $this->secretKey->getPoint()->getX();
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\EcDH\EcDHInterface::createMultiPartyKey()
*/
public function createMultiPartyKey(): PublicKeyInterface
{
$this->calculateKey();
return $this->secretKey;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\EcDH\EcDHInterface::setRecipientKey()
*/
public function setRecipientKey(PublicKeyInterface $key = null)
{
$this->recipientKey = $key;
return $this;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\EcDH\EcDHInterface::setSenderKey()
*/
public function setSenderKey(PrivateKeyInterface $key)
{
$this->senderKey = $key;
return $this;
}
/**
*
*/
private function calculateKey()
{
$this->checkExchangeState();
if ($this->secretKey === null) {
try {
// Multiply our secret with recipients public key
$point = $this->recipientKey->getPoint()->mul($this->senderKey->getSecret());
// Ensure we completed a valid exchange, ensure we can create a
// public key instance for the shared secret using our generator.
$this->secretKey = $this->senderKey->getPoint()->getPublicKeyFrom($point->getX(), $point->getY());
} catch (\Exception $e) {
throw new ExchangeException("Invalid ECDH exchange", 0, $e);
}
}
}
/**
* Verifies that the shared secret is known, or that the required keys are available
* to calculate the shared secret.
* @throws \RuntimeException when the exchange has not been made.
*/
private function checkExchangeState()
{
if ($this->secretKey !== null) {
return;
}
if ($this->senderKey === null) {
throw new ExchangeException('Sender key not set.');
}
if ($this->recipientKey === null) {
throw new ExchangeException('Recipient key not set.');
}
// Check the point exists on our curve.
$point = $this->recipientKey->getPoint();
if (!$this->senderKey->getPoint()->getCurve()->contains($point->getX(), $point->getY())) {
throw new ExchangeException("Invalid ECDH exchange - Point does not exist on our curve");
}
}
}
@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\EcDH;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
/**
* This is the contract for implementing EcDH (EC Diffie Hellman).
*/
interface EcDHInterface
{
/**
* Calculates and returns the shared key for the exchange.
*
* @return \GMP
*/
public function calculateSharedKey(): \GMP;
/**
* @return PublicKeyInterface
*/
public function createMultiPartyKey(): PublicKeyInterface;
/**
* Sets the sender's key.
*
* @param PrivateKeyInterface $key
*/
public function setSenderKey(PrivateKeyInterface $key);
/**
* Sets the recipient key.
*
* @param PublicKeyInterface $key
* @return void
*/
public function setRecipientKey(PublicKeyInterface $key);
}
@@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\Key;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
use Mdanter\Ecc\Crypto\EcDH\EcDH;
use Mdanter\Ecc\Crypto\EcDH\EcDHInterface;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Primitives\CurveFpInterface;
use Mdanter\Ecc\Primitives\GeneratorPoint;
use Mdanter\Ecc\Primitives\PointInterface;
/**
* This class serves as public - private key exchange for signature verification.
*/
class PrivateKey implements PrivateKeyInterface
{
/**
* @var GeneratorPoint
*/
private $generator;
/**
* @var \GMP
*/
private $secretMultiplier;
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @param GmpMathInterface $adapter
* @param GeneratorPoint $generator
* @param \GMP $secretMultiplier
*/
public function __construct(GmpMathInterface $adapter, GeneratorPoint $generator, \GMP $secretMultiplier)
{
$this->adapter = $adapter;
$this->generator = $generator;
$this->secretMultiplier = $secretMultiplier;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::getPublicKey()
*/
public function getPublicKey(): PublicKeyInterface
{
return new PublicKey($this->adapter, $this->generator, $this->generator->mul($this->secretMultiplier));
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::getPoint()
*/
public function getPoint(): GeneratorPoint
{
return $this->generator;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::getCurve()
*/
public function getCurve(): CurveFpInterface
{
return $this->generator->getCurve();
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::getSecret()
*/
public function getSecret(): \GMP
{
return $this->secretMultiplier;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Key\PrivateKeyInterface::createExchange()
*/
public function createExchange(PublicKeyInterface $recipient = null): EcDHInterface
{
$ecdh = new EcDH($this->adapter);
$ecdh
->setSenderKey($this)
->setRecipientKey($recipient);
return $ecdh;
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\Key;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
use Mdanter\Ecc\Crypto\EcDH\EcDHInterface;
use Mdanter\Ecc\Primitives\GeneratorPoint;
/**
* This is a contract for the PrivateKey portion of ECDSA.
*/
interface PrivateKeyInterface
{
/**
* @return PublicKeyInterface
*/
public function getPublicKey(): PublicKeyInterface;
/**
* @return GeneratorPoint
*/
public function getPoint(): GeneratorPoint;
/**
* @return \GMP
*/
public function getSecret(): \GMP;
/**
* @param PublicKeyInterface $recipient
* @return EcDHInterface
*/
public function createExchange(PublicKeyInterface $recipient): EcDHInterface;
}
+125
View File
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\Key;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
use Mdanter\Ecc\Exception\PublicKeyException;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Primitives\CurveFpInterface;
use Mdanter\Ecc\Primitives\GeneratorPoint;
use Mdanter\Ecc\Primitives\PointInterface;
/**
* This class serves as public- private key exchange for signature verification
*/
class PublicKey implements PublicKeyInterface
{
/**
*
* @var CurveFpInterface
*/
private $curve;
/**
*
* @var GeneratorPoint
*/
private $generator;
/**
*
* @var PointInterface
*/
private $point;
/**
*
* @var GmpMathInterface
*/
private $adapter;
/**
* Initialize a new PublicKey instance.
*
* @param GmpMathInterface $adapter
* @param GeneratorPoint $generator
* @param PointInterface $point
*/
public function __construct(GmpMathInterface $adapter, GeneratorPoint $generator, PointInterface $point)
{
$this->curve = $generator->getCurve();
$this->generator = $generator;
$this->point = $point;
$this->adapter = $adapter;
// step 1: not point at infinity.
if ($point->isInfinity()) {
throw new PublicKeyException($generator, $point, "Cannot use point at infinity for public key");
}
// step 2 full & partial public key validation routine
if ($adapter->cmp($point->getX(), gmp_init(0, 10)) < 0 || $adapter->cmp($this->curve->getPrime(), $point->getX()) < 0
|| $adapter->cmp($point->getY(), gmp_init(0, 10)) < 0 || $adapter->cmp($this->curve->getPrime(), $point->getY()) < 0
) {
throw new PublicKeyException($generator, $point, "Point has x and y out of range.");
}
// Sanity check. Point (x,y) values are qualified against it's
// generator and curve. Here we ensure the Point and Generator
// are the same.
if (!$generator->getCurve()->equals($point->getCurve())) {
throw new PublicKeyException($generator, $point, "Curve for given point not in common with GeneratorPoint");
}
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Key\PublicKeyInterface::getCurve()
*/
public function getCurve(): CurveFpInterface
{
return $this->curve;
}
/**
* {$inheritDoc}
* @see \Mdanter\Ecc\Crypto\Key\PublicKeyInterface::getGenerator()
*/
public function getGenerator(): GeneratorPoint
{
return $this->generator;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Key\PublicKeyInterface::getPoint()
*/
public function getPoint(): PointInterface
{
return $this->point;
}
}
@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\Key;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
use Mdanter\Ecc\Primitives\CurveFpInterface;
use Mdanter\Ecc\Primitives\PointInterface;
use Mdanter\Ecc\Primitives\GeneratorPoint;
/**
* This is the contract for the PublicKey portion of ECDSA.
*/
interface PublicKeyInterface
{
/**
* @return CurveFpInterface
*/
public function getCurve(): CurveFpInterface;
/**
* @return PointInterface
*/
public function getPoint(): PointInterface;
/**
* @return GeneratorPoint
*/
public function getGenerator(): GeneratorPoint;
}
@@ -0,0 +1,31 @@
<?php
namespace Mdanter\Ecc\Crypto\Signature;
use Mdanter\Ecc\Primitives\GeneratorPoint;
interface HasherInterface
{
/**
* @return string
*/
public function getAlgorithm(): string;
/**
* @return int
*/
public function getLengthInBytes(): int;
/**
* @param string $data
* @return string
*/
public function makeRawHash(string $data): string;
/**
* @param string $data
* @param GeneratorPoint $G
* @return \GMP
*/
public function makeHash(string $data, GeneratorPoint $G): \GMP;
}
@@ -0,0 +1,99 @@
<?php
namespace Mdanter\Ecc\Crypto\Signature;
use Mdanter\Ecc\EccFactory;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Primitives\GeneratorPoint;
use Mdanter\Ecc\Util\BinaryString;
use Mdanter\Ecc\Util\NumberSize;
class SignHasher implements HasherInterface
{
/**
* @var int[]
*/
protected static $sizeMap = [
'sha1' => 20,
'sha224' => 28,
'sha256' => 32,
'sha384' => 48,
'sha512' => 64,
];
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @var string
*/
private $algorithm;
/**
* SignHasher constructor.
* @param string $algorithm
* @param GmpMathInterface|null $math
*/
public function __construct(string $algorithm, GmpMathInterface $math = null)
{
if (!array_key_exists($algorithm, self::$sizeMap)) {
throw new \InvalidArgumentException("Unsupported hashing algorithm");
}
$this->algorithm = $algorithm;
$this->adapter = $math ?: EccFactory::getAdapter();
}
/**
* @return string
*/
public function getAlgorithm(): string
{
return $this->algorithm;
}
/**
* @return int
*/
public function getLengthInBytes(): int
{
return self::$sizeMap[$this->algorithm];
}
/**
* @param string $data
* @return string
*/
public function makeRawHash(string $data): string
{
return hash($this->algorithm, $data, false);
}
/**
* @param \GMP $hash
* @param GeneratorPoint $G
* @return \GMP
*/
public function truncateForECDSA(\GMP $hash, GeneratorPoint $G)
{
$hashBits = gmp_strval($hash, 2);
if (BinaryString::length($hashBits) < self::$sizeMap[$this->algorithm] * 8) {
$hashBits = str_pad($hashBits, self::$sizeMap[$this->algorithm] * 8, '0', STR_PAD_LEFT);
}
return gmp_init(BinaryString::substring($hashBits, 0, NumberSize::bnNumBits($this->adapter, $G->getOrder())), 2);
}
/**
* @param string $data
* @param GeneratorPoint $G
* @return \GMP
*/
public function makeHash(string $data, GeneratorPoint $G): \GMP
{
$hash = gmp_init($this->makeRawHash($data), 16);
return $this->truncateForECDSA($hash, $G);
}
}
@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\Signature;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
/**
* Plain Old PHP Object that stores the signature r,s for ECDSA
*/
class Signature implements SignatureInterface
{
/**
* @var \GMP
*/
private $r;
/**
*
* @var \GMP
*/
private $s;
/**
* Initialize a new instance with values
*
* @param \GMP $r
* @param \GMP $s
*/
public function __construct(\GMP $r, \GMP $s)
{
$this->r = $r;
$this->s = $s;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Signature\SignatureInterface::getR()
*/
public function getR(): \GMP
{
return $this->r;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Crypto\Signature\SignatureInterface::getS()
*/
public function getS(): \GMP
{
return $this->s;
}
}
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\Signature;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
/**
* This is the contract for describing a signature used in ECDSA.
*/
interface SignatureInterface
{
/**
* Returns the r parameter of the signature.
*
* @return \GMP
*/
public function getR(): \GMP;
/**
* Returns the s parameter of the signature.
*
* @return \GMP
*/
public function getS(): \GMP;
}
@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Crypto\Signature;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
use Mdanter\Ecc\Util\BinaryString;
class Signer
{
/**
*
* @var GmpMathInterface
*/
private $adapter;
/**
*
* @param GmpMathInterface $adapter
*/
public function __construct(GmpMathInterface $adapter)
{
$this->adapter = $adapter;
}
/**
* @param PrivateKeyInterface $key
* @param \GMP $truncatedHash - hash truncated for use in ECDSA
* @param \GMP $randomK
* @return SignatureInterface
*/
public function sign(PrivateKeyInterface $key, \GMP $truncatedHash, \GMP $randomK): SignatureInterface
{
$math = $this->adapter;
$generator = $key->getPoint();
$modMath = $math->getModularArithmetic($generator->getOrder());
$k = $math->mod($randomK, $generator->getOrder());
$p1 = $generator->mul($k);
$r = $p1->getX();
$zero = gmp_init(0, 10);
if ($math->equals($r, $zero)) {
throw new \RuntimeException("Error: random number R = 0");
}
$s = $modMath->div($modMath->add($truncatedHash, $math->mul($key->getSecret(), $r)), $k);
if ($math->equals($s, $zero)) {
throw new \RuntimeException("Error: random number S = 0");
}
return new Signature($r, $s);
}
/**
* @param PublicKeyInterface $key
* @param SignatureInterface $signature
* @param \GMP $hash
* @return bool
*/
public function verify(PublicKeyInterface $key, SignatureInterface $signature, \GMP $hash): bool
{
$generator = $key->getGenerator();
$n = $generator->getOrder();
$r = $signature->getR();
$s = $signature->getS();
$math = $this->adapter;
$one = gmp_init(1, 10);
if ($math->cmp($r, $one) < 0 || $math->cmp($r, $math->sub($n, $one)) > 0) {
return false;
}
if ($math->cmp($s, $one) < 0 || $math->cmp($s, $math->sub($n, $one)) > 0) {
return false;
}
$modMath = $math->getModularArithmetic($n);
$c = $math->inverseMod($s, $n);
$u1 = $modMath->mul($hash, $c);
$u2 = $modMath->mul($r, $c);
$xy = $generator->mul($u1)->add($key->getPoint()->mul($u2));
$v = $math->mod($xy->getX(), $n);
return BinaryString::constantTimeCompare($math->toString($v), $math->toString($r));
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Curves;
use Mdanter\Ecc\Exception\UnknownCurveException;
use Mdanter\Ecc\Exception\UnsupportedCurveException;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Math\MathAdapterFactory;
use Mdanter\Ecc\Primitives\GeneratorPoint;
class CurveFactory
{
/**
* @param string $name
* @return NamedCurveFp
*/
public static function getCurveByName(string $name): NamedCurveFp
{
$adapter = MathAdapterFactory::getAdapter();
$nistFactory = self::getNistFactory($adapter);
$secpFactory = self::getSecpFactory($adapter);
switch ($name) {
case NistCurve::NAME_P192:
return $nistFactory->curve192();
case NistCurve::NAME_P224:
return $nistFactory->curve224();
case NistCurve::NAME_P256:
return $nistFactory->curve256();
case NistCurve::NAME_P384:
return $nistFactory->curve384();
case NistCurve::NAME_P521:
return $nistFactory->curve521();
case SecgCurve::NAME_SECP_112R1:
return $secpFactory->curve112r1();
case SecgCurve::NAME_SECP_192K1:
return $secpFactory->curve192k1();
case SecgCurve::NAME_SECP_256K1:
return $secpFactory->curve256k1();
case SecgCurve::NAME_SECP_256R1:
return $secpFactory->curve256r1();
case SecgCurve::NAME_SECP_384R1:
return $secpFactory->curve384r1();
default:
$error = new UnsupportedCurveException('Unknown curve.');
$error->setCurveName($name);
throw $error;
}
}
/**
* @param string $name
* @return GeneratorPoint
*/
public static function getGeneratorByName(string $name): GeneratorPoint
{
$adapter = MathAdapterFactory::getAdapter();
$nistFactory = self::getNistFactory($adapter);
$secpFactory = self::getSecpFactory($adapter);
switch ($name) {
case NistCurve::NAME_P192:
return $nistFactory->generator192();
case NistCurve::NAME_P224:
return $nistFactory->generator224();
case NistCurve::NAME_P256:
return $nistFactory->generator256();
case NistCurve::NAME_P384:
return $nistFactory->generator384();
case NistCurve::NAME_P521:
return $nistFactory->generator521();
case SecgCurve::NAME_SECP_112R1:
return $secpFactory->generator112r1();
case SecgCurve::NAME_SECP_192K1:
return $secpFactory->generator192k1();
case SecgCurve::NAME_SECP_256K1:
return $secpFactory->generator256k1();
case SecgCurve::NAME_SECP_256R1:
return $secpFactory->generator256r1();
case SecgCurve::NAME_SECP_384R1:
return $secpFactory->generator384r1();
default:
$error = new UnsupportedCurveException('Unknown generator.');
$error->setCurveName($name);
throw $error;
}
}
/**
* @param GmpMathInterface $math
* @return NistCurve
*/
private static function getNistFactory(GmpMathInterface $math): NistCurve
{
return new NistCurve($math);
}
/**
* @param GmpMathInterface $math
* @return SecgCurve
*/
private static function getSecpFactory(GmpMathInterface $math): SecgCurve
{
return new SecgCurve($math);
}
}
+36
View File
@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Curves;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Primitives\CurveFp;
use Mdanter\Ecc\Primitives\CurveParameters;
class NamedCurveFp extends CurveFp
{
/**
* @var string
*/
private $name;
/**
* @param string $name
* @param CurveParameters $parameters
* @param GmpMathInterface $adapter
*/
public function __construct(string $name, CurveParameters $parameters, GmpMathInterface $adapter)
{
$this->name = $name;
parent::__construct($parameters, $adapter);
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
}
+221
View File
@@ -0,0 +1,221 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Curves;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Primitives\CurveParameters;
use Mdanter\Ecc\Primitives\GeneratorPoint;
use Mdanter\Ecc\Random\RandomNumberGeneratorInterface;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
/**
* This class encapsulates the NIST recommended curves
* - fields are Mersenne primes, i.e.
* for some p, Mersenne_prine = 2^p - 1
*/
class NistCurve
{
const NAME_P192 = 'nistp192';
const NAME_P224 = 'nistp224';
const NAME_P256 = 'nistp256';
const NAME_P384 = 'nistp384';
const NAME_P521 = 'nistp521';
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @param GmpMathInterface $adapter
*/
public function __construct(GmpMathInterface $adapter)
{
$this->adapter = $adapter;
}
/**
* Returns an NIST P-192 curve.
*
* @return NamedCurveFp
*/
public function curve192(): NamedCurveFp
{
$p = gmp_init('6277101735386680763835789423207666416083908700390324961279', 10);
$b = gmp_init('64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1', 16);
$parameters = new CurveParameters(192, $p, gmp_init('-3', 10), $b);
return new NamedCurveFp(self::NAME_P192, $parameters, $this->adapter);
}
/**
* Returns an NIST P-192 generator.
*
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator192(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve192();
$order = gmp_init('6277101735386680763835789423176059013767194773182842284081', 10);
$x = gmp_init('188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012', 16);
$y = gmp_init('07192b95ffc8da78631011ed6b24cdd573f977a11e794811', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
/**
* Returns an NIST P-224 curve
*
* @return NamedCurveFp
*/
public function curve224(): NamedCurveFp
{
$p = gmp_init('26959946667150639794667015087019630673557916260026308143510066298881', 10);
$b = gmp_init('b4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4', 16);
$parameters = new CurveParameters(224, $p, gmp_init(-3, 10), $b);
return new NamedCurveFp(self::NAME_P224, $parameters, $this->adapter);
}
/**
* Returns an NIST P-224 generator.
*
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator224(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve224();
$order = gmp_init('26959946667150639794667015087019625940457807714424391721682722368061', 10);
$x = gmp_init('b70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21', 16);
$y = gmp_init('bd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
/**
* Returns an NIST P-256 curve.
*
* @return NamedCurveFp
*/
public function curve256(): NamedCurveFp
{
$p = gmp_init('115792089210356248762697446949407573530086143415290314195533631308867097853951', 10);
$b = gmp_init('0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b', 16);
$parameters = new CurveParameters(256, $p, gmp_init(-3, 10), $b);
return new NamedCurveFp(self::NAME_P256, $parameters, $this->adapter);
}
/**
* Returns an NIST P-256 generator.
*
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator256(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve256();
$order = gmp_init('115792089210356248762697446949407573529996955224135760342422259061068512044369', 10);
$x = gmp_init('0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296', 16);
$y = gmp_init('0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
/**
* Returns an NIST P-384 curve.
*
* @return NamedCurveFp
*/
public function curve384(): NamedCurveFp
{
$p = gmp_init('39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319', 10);
$b = gmp_init('0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef', 16);
$parameters = new CurveParameters(384, $p, gmp_init(-3, 10), $b);
return new NamedCurveFp(self::NAME_P384, $parameters, $this->adapter);
}
/**
* Returns an NIST P-384 generator.
*
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator384(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve384();
$order = gmp_init('39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643', 10);
$x = gmp_init('0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7', 16);
$y = gmp_init('0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5f', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
/**
* Returns an NIST P-521 curve.
*
* @return NamedCurveFp
*/
public function curve521(): NamedCurveFp
{
$p = gmp_init('6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151', 10);
$b = gmp_init('0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00', 16);
$parameters = new CurveParameters(521, $p, gmp_init(-3, 10), $b);
return new NamedCurveFp(self::NAME_P521, $parameters, $this->adapter);
}
/**
* Returns an NIST P-521 generator.
*
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator521(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve521();
$order = gmp_init('6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449', 10);
$x = gmp_init('0xc6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66', 16);
$y = gmp_init('0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
}
+203
View File
@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Curves;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Primitives\CurveParameters;
use Mdanter\Ecc\Primitives\GeneratorPoint;
use Mdanter\Ecc\Random\RandomNumberGeneratorInterface;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
/**
*
*/
class SecgCurve
{
/**
* @var GmpMathInterface
*/
private $adapter;
const NAME_SECP_112R1 = 'secp112r1';
const NAME_SECP_192K1 = 'secp192k1';
const NAME_SECP_256K1 = 'secp256k1';
const NAME_SECP_256R1 = 'secp256r1';
const NAME_SECP_384R1 = 'secp384r1';
/**
* @param GmpMathInterface $adapter
*/
public function __construct(GmpMathInterface $adapter)
{
$this->adapter = $adapter;
}
/**
* @return NamedCurveFp
*/
public function curve112r1(): NamedCurveFp
{
$p = gmp_init('0xDB7C2ABF62E35E668076BEAD208B', 16);
$a = gmp_init('0xDB7C2ABF62E35E668076BEAD2088', 16);
$b = gmp_init('0x659EF8BA043916EEDE8911702B22', 16);
$parameters = new CurveParameters(112, $p, $a, $b);
return new NamedCurveFp(self::NAME_SECP_112R1, $parameters, $this->adapter);
}
/**
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator112r1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve112r1();
$order = gmp_init('0xDB7C2ABF62E35E7628DFAC6561C5', 16);
$x = gmp_init('0x09487239995A5EE76B55F9C2F098', 16);
$y = gmp_init('0xA89CE5AF8724C0A23E0E0FF77500', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
/**
* @return NamedCurveFp
*/
public function curve192k1(): NamedCurveFp
{
$p = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37', 16);
$a = gmp_init(0, 10);
$b = gmp_init(3, 10);
$parameters = new CurveParameters(192, $p, $a, $b);
return new NamedCurveFp(self::NAME_SECP_192K1, $parameters, $this->adapter);
}
/**
* @param RandomNumberGeneratorInterface $randomGenerator
* @return \Mdanter\Ecc\Primitives\GeneratorPoint
*/
public function generator192k1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve192k1();
$order = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D', 16);
$x = gmp_init('0xDB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D', 16);
$y = gmp_init('0x9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
/**
* @return NamedCurveFp
*/
public function curve256k1(): NamedCurveFp
{
$p = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16);
$a = gmp_init(0, 10);
$b = gmp_init(7, 10);
$parameters = new CurveParameters(256, $p, $a, $b);
return new NamedCurveFp(self::NAME_SECP_256K1, $parameters, $this->adapter);
}
/**
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator256k1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve256k1();
$order = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16);
$x = gmp_init('0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 16);
$y = gmp_init('0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
/**
* @return NamedCurveFp
*/
public function curve256r1(): NamedCurveFp
{
$p = gmp_init('0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', 16);
$a = gmp_init('0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', 16);
$b = gmp_init('0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', 16);
$parameters = new CurveParameters(256, $p, $a, $b);
return new NamedCurveFp(self::NAME_SECP_256R1, $parameters, $this->adapter);
}
/**
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator256r1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve256r1();
$order = gmp_init('0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551', 16);
$x = gmp_init('0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', 16);
$y = gmp_init('0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
/**
* @return NamedCurveFp
*/
public function curve384r1(): NamedCurveFp
{
$p = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF', 16);
$a = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC', 16);
$b = gmp_init('0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF', 16);
$parameters = new CurveParameters(384, $p, $a, $b);
return new NamedCurveFp(self::NAME_SECP_384R1, $parameters, $this->adapter);
}
/**
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function generator384r1(RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
$curve = $this->curve384r1();
$order = gmp_init('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973', 16);
$x = gmp_init('0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7', 16);
$y = gmp_init('0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F', 16);
return $curve->getGenerator($x, $y, $order, $randomGenerator);
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc;
use Mdanter\Ecc\Crypto\Signature\Signer;
use Mdanter\Ecc\Curves\NistCurve;
use Mdanter\Ecc\Curves\SecgCurve;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Math\MathAdapterFactory;
use Mdanter\Ecc\Primitives\CurveFp;
use Mdanter\Ecc\Primitives\CurveFpInterface;
use Mdanter\Ecc\Primitives\CurveParameters;
/**
* Static factory class providing factory methods to work with NIST and SECG recommended curves.
*/
class EccFactory
{
/**
* Selects and creates the most appropriate adapter for the running environment.
*
* @param bool $debug [optional] Set to true to get a trace of all mathematical operations
*
* @throws \RuntimeException
* @return GmpMathInterface
*/
public static function getAdapter(bool $debug = false): GmpMathInterface
{
return MathAdapterFactory::getAdapter($debug);
}
/**
* Returns a factory to create NIST Recommended curves and generators.
*
* @param GmpMathInterface $adapter [optional] Defaults to the return value of EccFactory::getAdapter().
* @return NistCurve
*/
public static function getNistCurves(GmpMathInterface $adapter = null): NistCurve
{
return new NistCurve($adapter ?: self::getAdapter());
}
/**
* Returns a factory to return SECG Recommended curves and generators.
*
* @param GmpMathInterface $adapter [optional] Defaults to the return value of EccFactory::getAdapter().
* @return SecgCurve
*/
public static function getSecgCurves(GmpMathInterface $adapter = null): SecgCurve
{
return new SecgCurve($adapter ?: self::getAdapter());
}
/**
* Creates a new curve from arbitrary parameters.
*
* @param int $bitSize
* @param \GMP $prime
* @param \GMP $a
* @param \GMP $b
* @param GmpMathInterface $adapter [optional] Defaults to the return value of EccFactory::getAdapter().
* @return CurveFpInterface
*/
public static function createCurve(int $bitSize, \GMP $prime, \GMP $a, \GMP $b, GmpMathInterface $adapter = null): CurveFpInterface
{
return new CurveFp(new CurveParameters($bitSize, $prime, $a, $b), $adapter ?: self::getAdapter());
}
/**
* @param GmpMathInterface $adapter [optional] Defaults to the return value of EccFactory::getAdapteR()
* @return Signer
*/
public static function getSigner(GmpMathInterface $adapter = null): Signer
{
return new Signer($adapter ?: self::getAdapter());
}
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
class ExchangeException extends \RuntimeException
{
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
class NumberTheoryException extends \RuntimeException
{
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
class PointException extends \RuntimeException
{
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
use Mdanter\Ecc\Primitives\CurveFpInterface;
class PointNotOnCurveException extends PointException
{
public function __construct(\GMP $x, \GMP $y, CurveFpInterface $curve)
{
parent::__construct("Curve " . $curve . " does not contain point (" . gmp_strval($x, 10) . ", " . gmp_strval($y, 10) . ")");
}
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
class PointRecoveryException extends PointException
{
}
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
use Mdanter\Ecc\Primitives\GeneratorPoint;
use Mdanter\Ecc\Primitives\PointInterface;
use Throwable;
class PublicKeyException extends \RuntimeException
{
/**
* @var GeneratorPoint
*/
private $G;
/**
* @var PointInterface
*/
private $point;
public function __construct(GeneratorPoint $G, PointInterface $point, string $message = "", int $code = 0, Throwable $previous = null)
{
$this->G = $G;
$this->point = $point;
parent::__construct($message, $code, $previous);
}
public function getGenerator(): GeneratorPoint
{
return $this->G;
}
public function getPoint(): PointInterface
{
return $this->point;
}
}
@@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
class SignatureDecodeException extends \RuntimeException
{
}
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
class SquareRootException extends NumberTheoryException
{
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Exception;
use Throwable;
class UnsupportedCurveException extends \RuntimeException
{
/**
* @var null|string
*/
private $oid;
/**
* @var null|string
*/
private $curveName;
public function __construct(string $message = "", int $code = 0, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}
public function setCurveName(string $curveName)
{
$this->curveName = $curveName;
return $this;
}
public function setOid(string $oid)
{
$this->oid = $oid;
return $this;
}
public function hasCurveName(): bool
{
return is_string($this->curveName);
}
public function hasOid(): bool
{
return is_string($this->oid);
}
public function getCurveName(): string
{
return $this->curveName;
}
public function getOid(): string
{
return $this->oid;
}
}
+620
View File
@@ -0,0 +1,620 @@
<?php
namespace Mdanter\Ecc\Math;
use Mdanter\Ecc\Primitives\GeneratorPoint;
/**
* Debug helper class to trace all calls to math functions along with the provided params and result.
*/
class DebugDecorator implements GmpMathInterface
{
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @var callable
*/
private $writer;
/**
* @param GmpMathInterface $adapter
* @param callable|null $callback
*/
public function __construct(GmpMathInterface $adapter, callable $callback = null)
{
$this->adapter = $adapter;
$this->writer = $callback ?: function ($message) {
echo $message;
};
}
/**
*
* @param string $message
*/
private function write($message)
{
call_user_func($this->writer, $message);
}
/**
*
* @param string $func
* @param array $args
* @return mixed
*/
private function call($func, $args)
{
$strArgs = array_map(
function ($arg) {
if ($arg instanceof \GMP) {
return var_export($this->adapter->toString($arg), true);
} else {
return var_export($arg, true);
}
},
$args
);
if (strpos($func, '::')) {
list(, $func) = explode('::', $func);
}
$this->write($func.'('.implode(', ', $strArgs).')');
$res = call_user_func_array([ $this->adapter, $func ], $args);
if ($res instanceof \GMP) {
$this->write(' => ' . var_export($this->adapter->toString($res), true) . PHP_EOL);
} else {
$this->write(' => ' . var_export($res, true) . PHP_EOL);
}
return $res;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::cmp()
*/
public function cmp(\GMP $first, \GMP $other): int
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::cmp()
*/
public function equals(\GMP $first, \GMP $other): bool
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::mod()
*/
public function mod(\GMP $number, \GMP $modulus): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::add()
*/
public function add(\GMP $augend, \GMP $addend): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::sub()
*/
public function sub(\GMP $minuend, \GMP $subtrahend): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::mul()
*/
public function mul(\GMP $multiplier, \GMP $multiplicand): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::div()
*/
public function div(\GMP $dividend, \GMP $divisor): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::pow()
*/
public function pow(\GMP $base, int $exponent): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::bitwiseAnd()
*/
public function bitwiseAnd(\GMP $first, \GMP $other): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\MathAdapter::toString()
*/
public function toString(\GMP $value): string
{
return $this->adapter->toString($value);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::hexDec()
*/
public function hexDec(string $hexString): string
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::decHex()
*/
public function decHex(string $decString): string
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::powmod()
*/
public function powmod(\GMP $base, \GMP $exponent, \GMP $modulus): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::isPrime()
*/
public function isPrime(\GMP $n): bool
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::nextPrime()
*/
public function nextPrime(\GMP $currentPrime): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::inverseMod()
*/
public function inverseMod(\GMP $a, \GMP $m): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::jacobi()
*/
public function jacobi(\GMP $a, \GMP $p): int
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::intToString()
*/
public function intToFixedSizeString(\GMP $x, int $byteSize): string
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::intToString()
*/
public function intToString(\GMP $x): string
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::stringToInt()
*/
public function stringToInt(string $s): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::digestInteger()
*/
public function digestInteger(\GMP $m): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::gcd2()
*/
public function gcd2(\GMP $a, \GMP $m): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::rightShift()
*/
public function rightShift(\GMP $number, int $positions): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::leftShift()
*/
public function leftShift(\GMP $number, int $positions): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call',
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::bitwiseXor()
*/
public function bitwiseXor(\GMP $first, \GMP $other): \GMP
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call'
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::baseConvert()
*/
public function baseConvert(string $value, int $fromBase, int $toBase): string
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call'
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::getEcMath()
*/
public function getEcMath(GeneratorPoint $generatorPoint, $input)
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call'
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::getModularArithmetic()
*/
public function getModularArithmetic(\GMP $modulus): ModularArithmetic
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call'
),
$func,
$args
);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Math\GmpMathInterface::getNumberTheory()
*/
public function getNumberTheory(): NumberTheory
{
$func = __METHOD__;
$args = func_get_args();
return call_user_func(
array(
$this,
'call'
),
$func,
$args
);
}
}
+333
View File
@@ -0,0 +1,333 @@
<?php
namespace Mdanter\Ecc\Math;
use Mdanter\Ecc\Util\BinaryString;
use Mdanter\Ecc\Util\NumberSize;
class GmpMath implements GmpMathInterface
{
/**
* {@inheritDoc}
* @see GmpMathInterface::cmp()
*/
public function cmp(\GMP $first, \GMP $other): int
{
return gmp_cmp($first, $other);
}
/**
* @param \GMP $first
* @param \GMP $other
* @return bool
*/
public function equals(\GMP $first, \GMP $other): bool
{
return gmp_cmp($first, $other) === 0;
}
/**
* {@inheritDoc}
* @see GmpMathInterface::mod()
*/
public function mod(\GMP $number, \GMP $modulus): \GMP
{
return gmp_mod($number, $modulus);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::add()
*/
public function add(\GMP $augend, \GMP $addend): \GMP
{
return gmp_add($augend, $addend);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::sub()
*/
public function sub(\GMP $minuend, \GMP $subtrahend): \GMP
{
return gmp_sub($minuend, $subtrahend);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::mul()
*/
public function mul(\GMP $multiplier, \GMP $multiplicand): \GMP
{
return gmp_mul($multiplier, $multiplicand);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::div()
*/
public function div(\GMP $dividend, \GMP $divisor): \GMP
{
return gmp_div($dividend, $divisor);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::pow()
*/
public function pow(\GMP $base, int $exponent): \GMP
{
return gmp_pow($base, $exponent);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::bitwiseAnd()
*/
public function bitwiseAnd(\GMP $first, \GMP $other): \GMP
{
return gmp_and($first, $other);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::rightShift()
*/
public function rightShift(\GMP $number, int $positions): \GMP
{
// Shift 1 right = div / 2
return gmp_div($number, gmp_pow(gmp_init(2, 10), $positions));
}
/**
* {@inheritDoc}
* @see GmpMathInterface::bitwiseXor()
*/
public function bitwiseXor(\GMP $first, \GMP $other): \GMP
{
return gmp_xor($first, $other);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::leftShift()
*/
public function leftShift(\GMP $number, int $positions): \GMP
{
// Shift 1 left = mul by 2
return gmp_mul($number, gmp_pow(2, $positions));
}
/**
* {@inheritDoc}
* @see GmpMathInterface::toString()
*/
public function toString(\GMP $value): string
{
return gmp_strval($value);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::hexDec()
*/
public function hexDec(string $hex): string
{
return gmp_strval(gmp_init($hex, 16), 10);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::decHex()
*/
public function decHex(string $dec): string
{
$dec = gmp_init($dec, 10);
if (gmp_cmp($dec, 0) < 0) {
throw new \InvalidArgumentException('Unable to convert negative integer to string');
}
$hex = gmp_strval($dec, 16);
if (BinaryString::length($hex) % 2 != 0) {
$hex = '0'.$hex;
}
return $hex;
}
/**
* {@inheritDoc}
* @see GmpMathInterface::powmod()
*/
public function powmod(\GMP $base, \GMP $exponent, \GMP $modulus): \GMP
{
if ($this->cmp($exponent, gmp_init(0, 10)) < 0) {
throw new \InvalidArgumentException("Negative exponents (" . $this->toString($exponent) . ") not allowed.");
}
return gmp_powm($base, $exponent, $modulus);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::isPrime()
*/
public function isPrime(\GMP $n): bool
{
$prob = gmp_prob_prime($n);
if ($prob > 0) {
return true;
}
return false;
}
/**
* {@inheritDoc}
* @see GmpMathInterface::nextPrime()
*/
public function nextPrime(\GMP $starting_value): \GMP
{
return gmp_nextprime($starting_value);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::inverseMod()
*/
public function inverseMod(\GMP $a, \GMP $m): \GMP
{
return gmp_invert($a, $m);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::jacobi()
*/
public function jacobi(\GMP $a, \GMP $n): int
{
return gmp_jacobi($a, $n);
}
/**
* @param \GMP $x
* @param int $byteSize
* @return string
*/
public function intToFixedSizeString(\GMP $x, int $byteSize): string
{
if ($byteSize < 0) {
throw new \RuntimeException("Byte size cannot be negative");
}
if (gmp_cmp($x, 0) < 0) {
throw new \RuntimeException("x was negative - not yet supported");
}
$two = gmp_init(2);
$range = gmp_pow($two, $byteSize * 8);
if (NumberSize::bnNumBits($this, $x) >= NumberSize::bnNumBits($this, $range)) {
throw new \RuntimeException("Number overflows byte size");
}
$maskShift = gmp_pow($two, 8);
$mask = gmp_mul(gmp_init(255), $range);
$binary = '';
for ($i = $byteSize - 1; $i >= 0; $i--) {
$mask = gmp_div($mask, $maskShift);
$binary .= pack('C', gmp_strval(gmp_div(gmp_and($x, $mask), gmp_pow($two, $i * 8)), 10));
}
return $binary;
}
/**
* {@inheritDoc}
* @see GmpMathInterface::intToString()
*/
public function intToString(\GMP $x): string
{
if (gmp_cmp($x, 0) < 0) {
throw new \InvalidArgumentException('Unable to convert negative integer to string');
}
$hex = gmp_strval($x, 16);
if (BinaryString::length($hex) % 2 != 0) {
$hex = '0'.$hex;
}
return pack('H*', $hex);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::stringToInt()
*/
public function stringToInt(string $s): \GMP
{
$result = gmp_init(0, 10);
$sLen = BinaryString::length($s);
for ($c = 0; $c < $sLen; $c ++) {
$result = gmp_add(gmp_mul(256, $result), gmp_init(ord($s[$c]), 10));
}
return $result;
}
/**
* {@inheritDoc}
* @see GmpMathInterface::digestInteger()
*/
public function digestInteger(\GMP $m): \GMP
{
return $this->stringToInt(hash('sha1', $this->intToString($m), true));
}
/**
* {@inheritDoc}
* @see GmpMathInterface::gcd2()
*/
public function gcd2(\GMP $a, \GMP $b): \GMP
{
while ($this->cmp($a, gmp_init(0)) > 0) {
$temp = $a;
$a = $this->mod($b, $a);
$b = $temp;
}
return $b;
}
/**
* {@inheritDoc}
* @see GmpMathInterface::baseConvert()
*/
public function baseConvert(string $number, int $from, int $to): string
{
return gmp_strval(gmp_init($number, $from), $to);
}
/**
* {@inheritDoc}
* @see GmpMathInterface::getNumberTheory()
*/
public function getNumberTheory(): NumberTheory
{
return new NumberTheory($this);
}
/**
* @param \GMP $modulus
* @return ModularArithmetic
*/
public function getModularArithmetic(\GMP $modulus): ModularArithmetic
{
return new ModularArithmetic($this, $modulus);
}
}
@@ -0,0 +1,227 @@
<?php
namespace Mdanter\Ecc\Math;
interface GmpMathInterface
{
/**
* Compares two numbers
*
* @param \GMP $first
* @param \GMP $other
* @return int less than 0 if first is less than second, 0 if equal, greater than 0 if greater than.
*/
public function cmp(\GMP $first, \GMP $other): int;
/**
* @param \GMP $first
* @param \GMP $other
* @return bool
*/
public function equals(\GMP $first, \GMP $other): bool;
/**
* Returns the remainder of a division
*
* @param \GMP $number
* @param \GMP $modulus
* @return \GMP
*/
public function mod(\GMP $number, \GMP $modulus): \GMP;
/**
* Adds two numbers
*
* @param \GMP $augend
* @param \GMP $addend
* @return \GMP
*/
public function add(\GMP $augend, \GMP $addend): \GMP;
/**
* Substract one number from another
*
* @param \GMP $minuend
* @param \GMP $subtrahend
* @return \GMP
*/
public function sub(\GMP $minuend, \GMP $subtrahend): \GMP;
/**
* Multiplies a number by another.
*
* @param \GMP $multiplier
* @param \GMP $multiplicand
* @return \GMP
*/
public function mul(\GMP $multiplier, \GMP $multiplicand): \GMP;
/**
* Divides a number by another.
*
* @param \GMP $dividend
* @param \GMP $divisor
* @return \GMP
*/
public function div(\GMP $dividend, \GMP $divisor): \GMP;
/**
* Raises a number to a power.
*
* @param \GMP $base The number to raise.
* @param int $exponent The power to raise the number to.
* @return \GMP
*/
public function pow(\GMP $base, int $exponent): \GMP;
/**
* Performs a logical AND between two values.
*
* @param \GMP $first
* @param \GMP $other
* @return \GMP
*/
public function bitwiseAnd(\GMP $first, \GMP $other): \GMP;
/**
* Performs a logical XOR between two values.
*
* @param \GMP $first
* @param \GMP $other
* @return \GMP
*/
public function bitwiseXor(\GMP $first, \GMP $other): \GMP;
/**
* Shifts bits to the right
* @param \GMP $number Number to shift
* @param int $positions Number of positions to shift
* @return \GMP
*/
public function rightShift(\GMP $number, int $positions): \GMP;
/**
* Shifts bits to the left
* @param \GMP $number Number to shift
* @param int $positions Number of positions to shift
* @return \GMP
*/
public function leftShift(\GMP $number, int $positions): \GMP;
/**
* Returns the string representation of a returned value.
*
* @param \GMP $value
* @return string
*/
public function toString(\GMP $value): string;
/**
* Converts an hexadecimal string to decimal.
*
* @param string $hexString
* @return int|string
*/
public function hexDec(string $hexString): string;
/**
* Converts a decimal string to hexadecimal.
*
* @param int|string $decString
* @return string
*/
public function decHex(string $decString): string;
/**
* Calculates the modular exponent of a number.
*
* @param \GMP $base
* @param \GMP $exponent
* @param \GMP $modulus
* @return \GMP
*/
public function powmod(\GMP $base, \GMP $exponent, \GMP $modulus): \GMP;
/**
* Checks whether a number is a prime.
*
* @param \GMP $n
* @return boolean
*/
public function isPrime(\GMP $n): bool;
/**
* Gets the next known prime that is greater than a given prime.
*
* @param \GMP $currentPrime
* @return \GMP
*/
public function nextPrime(\GMP $currentPrime): \GMP;
/**
* @param \GMP $a
* @param \GMP $m
* @return \GMP
*/
public function inverseMod(\GMP $a, \GMP $m): \GMP;
/**
* @param \GMP $a
* @param \GMP $p
* @return int
*/
public function jacobi(\GMP $a, \GMP $p): int;
/**
* @param \GMP $x
* @return string
*/
public function intToString(\GMP $x): string;
/**
* @param \GMP $x
* @param int $byteSize
* @return string
*/
public function intToFixedSizeString(\GMP $x, int $byteSize): string;
/**
*
* @param int|string $s
* @return \GMP
*/
public function stringToInt(string $s): \GMP;
/**
*
* @param \GMP $m
* @return \GMP
*/
public function digestInteger(\GMP $m): \GMP;
/**
* @param \GMP $a
* @param \GMP $m
* @return \GMP
*/
public function gcd2(\GMP $a, \GMP $m): \GMP;
/**
* @param string $value
* @param int $fromBase
* @param int $toBase
* @return string
*/
public function baseConvert(string $value, int $fromBase, int $toBase): string;
/**
* @return NumberTheory
*/
public function getNumberTheory(): NumberTheory;
/**
* @param \GMP $modulus
* @return ModularArithmetic
*/
public function getModularArithmetic(\GMP $modulus): ModularArithmetic;
}
@@ -0,0 +1,48 @@
<?php
namespace Mdanter\Ecc\Math;
class MathAdapterFactory
{
/**
* @var GmpMathInterface
*/
private static $forcedAdapter = null;
/**
* @param GmpMathInterface $adapter
*/
public static function forceAdapter(GmpMathInterface $adapter = null)
{
self::$forcedAdapter = $adapter;
}
/**
* @param bool $debug
* @return DebugDecorator|GmpMathInterface|null
*/
public static function getAdapter(bool $debug = false): GmpMathInterface
{
if (self::$forcedAdapter !== null) {
return self::$forcedAdapter;
}
$adapter = new GmpMath();
return self::wrapAdapter($adapter, $debug);
}
/**
* @param GmpMathInterface $adapter
* @param bool $debug
* @return DebugDecorator|GmpMathInterface
*/
private static function wrapAdapter(GmpMathInterface $adapter, bool $debug): GmpMathInterface
{
if ($debug === true) {
return new DebugDecorator($adapter);
}
return $adapter;
}
}
@@ -0,0 +1,76 @@
<?php
namespace Mdanter\Ecc\Math;
class ModularArithmetic
{
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @var \GMP
*/
private $modulus;
/**
* @param GmpMathInterface $adapter
* @param \GMP $modulus
*/
public function __construct(GmpMathInterface $adapter, \GMP $modulus)
{
$this->adapter = $adapter;
$this->modulus = $modulus;
}
/**
* @param \GMP $augend
* @param \GMP $addend
* @return \GMP
*/
public function add(\GMP $augend, \GMP $addend): \GMP
{
return $this->adapter->mod($this->adapter->add($augend, $addend), $this->modulus);
}
/**
* @param \GMP $minuend
* @param \GMP $subtrahend
* @return \GMP
*/
public function sub(\GMP $minuend, \GMP $subtrahend): \GMP
{
return $this->adapter->mod($this->adapter->sub($minuend, $subtrahend), $this->modulus);
}
/**
* @param \GMP $multiplier
* @param \GMP $muliplicand
* @return \GMP
*/
public function mul(\GMP $multiplier, \GMP $muliplicand): \GMP
{
return $this->adapter->mod($this->adapter->mul($multiplier, $muliplicand), $this->modulus);
}
/**
* @param \GMP $dividend
* @param \GMP $divisor
* @return \GMP
*/
public function div(\GMP $dividend, \GMP $divisor): \GMP
{
return $this->mul($dividend, $this->adapter->inverseMod($divisor, $this->modulus));
}
/**
* @param \GMP $base
* @param \GMP $exponent
* @return \GMP
*/
public function pow(\GMP $base, \GMP $exponent): \GMP
{
return $this->adapter->powmod($base, $exponent, $this->modulus);
}
}
+267
View File
@@ -0,0 +1,267 @@
<?php
namespace Mdanter\Ecc\Math;
/***********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
************************************************************************/
/**
* Implementation of some number theoretic algorithms
*
* @author Matyas Danter
*/
use Mdanter\Ecc\Exception\NumberTheoryException;
use Mdanter\Ecc\Exception\SquareRootException;
/**
* Rewritten to take a MathAdaptor to handle different environments. Has
* some desireable functions for public key compression/recovery.
*/
class NumberTheory
{
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @param GmpMathInterface $adapter
*/
public function __construct(GmpMathInterface $adapter)
{
$this->adapter = $adapter;
$this->zero = gmp_init(0, 10);
$this->one = gmp_init(1, 10);
$this->two = gmp_init(2, 10);
}
/**
* @param \GMP[] $poly
* @param \GMP[] $polymod
* @param \GMP $p
* @return \GMP[]
*/
public function polynomialReduceMod(array $poly, array $polymod, \GMP $p): array
{
$adapter = $this->adapter;
// Only enter if last value is set, implying count > 0
if ((($last = end($polymod)) instanceof \GMP) && $adapter->equals($last, $this->one)) {
$count_polymod = count($polymod);
while (count($poly) >= $count_polymod) {
if (!$adapter->equals(end($poly), $this->zero)) {
for ($i = 2; $i < $count_polymod + 1; $i++) {
$poly[count($poly) - $i] =
$adapter->mod(
$adapter->sub(
$poly[count($poly) - $i],
$adapter->mul(
end($poly),
$polymod[$count_polymod - $i]
)
),
$p
);
}
}
$poly = array_slice($poly, 0, count($poly) - 1);
}
return $poly;
}
throw new NumberTheoryException('Unable to calculate polynomialReduceMod');
}
/**
* @param \GMP[] $m1
* @param \GMP[] $m2
* @param \GMP[] $polymod
* @param \GMP $p
* @return \GMP[]
*/
public function polynomialMultiplyMod(array $m1, array $m2, array $polymod, \GMP $p): array
{
$prod = array();
$cm1 = count($m1);
$cm2 = count($m2);
for ($i = 0; $i < $cm1; $i++) {
for ($j = 0; $j < $cm2; $j++) {
$index = $i + $j;
if (!isset($prod[$index])) {
$prod[$index] = $this->zero;
}
$prod[$index] =
$this->adapter->mod(
$this->adapter->add(
$prod[$index],
$this->adapter->mul(
$m1[$i],
$m2[$j]
)
),
$p
);
}
}
return $this->polynomialReduceMod($prod, $polymod, $p);
}
/**
* @param \GMP[] $base
* @param \GMP $exponent
* @param \GMP[] $polymod
* @param \GMP $p
* @return \GMP[]
*/
public function polynomialPowMod(array $base, \GMP $exponent, array $polymod, \GMP $p): array
{
$adapter = $this->adapter;
if ($adapter->cmp($exponent, $p) < 0) {
if ($adapter->equals($exponent, $this->zero)) {
return $this->one;
}
$G = $base;
$k = $exponent;
if ($adapter->equals($adapter->mod($k, $this->two), $this->one)) {
$s = $G;
} else {
$s = array($this->one);
}
while ($adapter->cmp($k, $this->one) > 0) {
$k = $adapter->div($k, $this->two);
$G = $this->polynomialMultiplyMod($G, $G, $polymod, $p);
if ($adapter->equals($adapter->mod($k, $this->two), $this->one)) {
$s = $this->polynomialMultiplyMod($G, $s, $polymod, $p);
}
}
return $s;
}
throw new NumberTheoryException('Unable to calculate polynomialPowMod');
}
/**
* @param \GMP $a
* @param \GMP $p
* @return \GMP
*/
public function squareRootModP(\GMP $a, \GMP $p): \GMP
{
$math = $this->adapter;
$four = gmp_init(4, 10);
$eight = gmp_init(8, 10);
$modMath = $math->getModularArithmetic($p);
if ($math->cmp($this->one, $p) < 0) {
if ($math->equals($a, $this->zero)) {
return $this->zero;
}
if ($math->equals($p, $this->two)) {
return $a;
}
$jac = $math->jacobi($a, $p);
if ($jac === -1) {
throw new SquareRootException("{$math->toString($a)} has no square root modulo {$math->toString($p)}");
}
if ($math->equals($math->mod($p, $four), gmp_init(3, 10))) {
return $modMath->pow($a, $math->div($math->add($p, $this->one), $four));
}
if ($math->equals($math->mod($p, $eight), gmp_init(5, 10))) {
$d = $modMath->pow($a, $math->div($math->sub($p, $this->one), $four));
if ($math->equals($d, $this->one)) {
return $modMath->pow($a, $math->div($math->add($p, gmp_init(3, 10)), $eight));
}
if ($math->equals($d, $math->sub($p, $this->one))) {
return $modMath->mul(
$math->mul(
$this->two,
$a
),
$modMath->pow(
$math->mul(
$four,
$a
),
$math->div(
$math->sub(
$p,
gmp_init(5, 10)
),
$eight
)
)
);
}
//shouldn't get here
}
for ($b = $this->two; $math->cmp($b, $p) < 0; $b = gmp_add($b, $this->one)) {
if ($math->jacobi(
$math->sub(
$math->mul($b, $b),
$math->mul($four, $a)
),
$p
) == -1
) {
$f = array($a, $math->sub($this->zero, $b), $this->one);
$ff = $this->polynomialPowMod(
array($this->zero, $this->one),
$math->div(
$math->add(
$p,
$this->one
),
$this->two
),
$f,
$p
);
if ($math->equals($ff[1], $this->zero)) {
return $ff[0];
}
// if we got here no b was found
}
}
}
throw new SquareRootException('Unable to calculate square root mod p!');
}
}
+244
View File
@@ -0,0 +1,244 @@
<?php
declare(strict_types=1);
/***********************************************************************
Copyright (C) 2012 Matyas Danter
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*************************************************************************/
namespace Mdanter\Ecc\Primitives;
use Mdanter\Ecc\Exception\PointRecoveryException;
use Mdanter\Ecc\Exception\SquareRootException;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Math\ModularArithmetic;
use Mdanter\Ecc\Random\RandomNumberGeneratorInterface;
/**
* This class is a representation of an EC over a field modulo a prime number
*
* Important objectives for this class are:
* - Does the curve contain a point?
* - Comparison of two curves.
*/
class CurveFp implements CurveFpInterface
{
/**
* @var CurveParameters
*/
protected $parameters;
/**
*
* @var GmpMathInterface
*/
protected $adapter = null;
/**
*
* @var ModularArithmetic
*/
protected $modAdapter = null;
/**
* Constructor that sets up the instance variables.
*
* @param CurveParameters $parameters
* @param GmpMathInterface $adapter
*/
public function __construct(CurveParameters $parameters, GmpMathInterface $adapter)
{
$this->parameters = $parameters;
$this->adapter = $adapter;
$this->modAdapter = new ModularArithmetic($this->adapter, $this->parameters->getPrime());
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::getModAdapter()
*/
public function getModAdapter(): ModularArithmetic
{
return $this->modAdapter;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::getPoint()
*/
public function getPoint(\GMP $x, \GMP $y, \GMP $order = null): PointInterface
{
return new Point($this->adapter, $this, $x, $y, $order);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::getInfinity()
*/
public function getInfinity(): PointInterface
{
return new Point($this->adapter, $this, gmp_init(0, 10), gmp_init(0, 10), null, true);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::getGenerator()
*/
public function getGenerator(\GMP $x, \GMP $y, \GMP $order, RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint
{
return new GeneratorPoint($this->adapter, $this, $x, $y, $order, $randomGenerator);
}
/**
* @param bool $wasOdd
* @param \GMP $xCoord
* @return \GMP
*/
public function recoverYfromX(bool $wasOdd, \GMP $xCoord): \GMP
{
$math = $this->adapter;
$prime = $this->getPrime();
try {
$root = $this->adapter->getNumberTheory()->squareRootModP(
$math->add(
$math->add(
$this->modAdapter->pow($xCoord, gmp_init(3, 10)),
$math->mul($this->getA(), $xCoord)
),
$this->getB()
),
$prime
);
} catch (SquareRootException $e) {
throw new PointRecoveryException("Failed to recover y coordinate for point", 0, $e);
}
if ($math->equals($math->mod($root, gmp_init(2, 10)), gmp_init(1)) === $wasOdd) {
return $root;
} else {
return $math->sub($prime, $root);
}
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::contains()
*/
public function contains(\GMP $x, \GMP $y): bool
{
$math = $this->adapter;
$eq_zero = $math->equals(
$this->modAdapter->sub(
$math->pow($y, 2),
$math->add(
$math->add(
$math->pow($x, 3),
$math->mul($this->getA(), $x)
),
$this->getB()
)
),
gmp_init(0, 10)
);
return $eq_zero;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::getA()
*/
public function getA(): \GMP
{
return $this->parameters->getA();
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::getB()
*/
public function getB(): \GMP
{
return $this->parameters->getB();
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::getPrime()
*/
public function getPrime(): \GMP
{
return $this->parameters->getPrime();
}
/**
* @return int
*/
public function getSize(): int
{
return $this->parameters->getSize();
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::cmp()
*/
public function cmp(CurveFpInterface $other): int
{
$math = $this->adapter;
$equal = $math->equals($this->getA(), $other->getA());
$equal &= $math->equals($this->getB(), $other->getB());
$equal &= $math->equals($this->getPrime(), $other->getPrime());
return ($equal) ? 0 : 1;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::equals()
*/
public function equals(CurveFpInterface $other): bool
{
return $this->cmp($other) == 0;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\CurveFpInterface::__toString()
*/
public function __toString(): string
{
return 'curve(' . $this->adapter->toString($this->getA()) . ', ' . $this->adapter->toString($this->getB()) . ', ' . $this->adapter->toString($this->getPrime()) . ')';
}
/**
* @return array
*/
public function __debugInfo()
{
return [
'a' => $this->adapter->toString($this->getA()),
'b' => $this->adapter->toString($this->getB()),
'prime' => $this->adapter->toString($this->getPrime())
];
}
}
@@ -0,0 +1,137 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Primitives;
use Mdanter\Ecc\Math\ModularArithmetic;
use Mdanter\Ecc\Random\RandomNumberGeneratorInterface;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
/**
* This is the contract for implementing CurveFp (EC prime finite-field).
*/
interface CurveFpInterface
{
/**
* Returns a modular arithmetic adapter.
*
* @return ModularArithmetic
*/
public function getModAdapter(): ModularArithmetic;
/**
* Returns the point identified by given coordinates.
*
* @param \GMP $x
* @param \GMP $y
* @param \GMP $order
* @return PointInterface
*/
public function getPoint(\GMP $x, \GMP $y, \GMP $order = null): PointInterface;
/**
* @param bool $wasOdd
* @param \GMP $x
* @return \GMP
*/
public function recoverYfromX(bool $wasOdd, \GMP $x): \GMP;
/**
* Returns a point representing infinity on the curve.
*
* @return PointInterface
*/
public function getInfinity(): PointInterface;
/**
*
* @param \GMP $x
* @param \GMP $y
* @param \GMP $order
* @param RandomNumberGeneratorInterface $randomGenerator
* @return GeneratorPoint
*/
public function getGenerator(\GMP $x, \GMP $y, \GMP $order, RandomNumberGeneratorInterface $randomGenerator = null): GeneratorPoint;
/**
* Checks whether the curve contains the given coordinates.
*
* @param \GMP $x
* @param \GMP $y
* @return bool
*/
public function contains(\GMP $x, \GMP $y): bool;
/**
* Returns the a parameter of the curve.
*
* @return \GMP
*/
public function getA(): \GMP;
/**
* Returns the b parameter of the curve.
*
* @return \GMP
*/
public function getB(): \GMP;
/**
* Returns the prime associated with the curve.
*
* @return \GMP
*/
public function getPrime(): \GMP;
/**
* @return int
*/
public function getSize(): int;
/**
* Compares the curve to another.
*
* @param CurveFpInterface $other
* @return int < 0 if $this < $other, 0 if $other == $this, > 0 if $this > $other
*/
public function cmp(CurveFpInterface $other): int;
/**
* Checks whether the curve is equal to another.
*
* @param CurveFpInterface $other
* @return bool
*/
public function equals(CurveFpInterface $other): bool;
/**
* Return string representation of curve for debugging
*
* @return string
*/
public function __toString(): string;
}
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Primitives;
class CurveParameters
{
/**
* Elliptic curve over the field of integers modulo a prime.
*
* @var \GMP
*/
protected $a;
/**
*
* @var \GMP
*/
protected $b;
/**
*
* @var \GMP
*/
protected $prime;
/**
* Binary length of keys associated with these curve parameters
*
* @var int
*/
protected $size;
/**
* @param int $size
* @param \GMP $prime
* @param \GMP $a
* @param \GMP $b
*/
public function __construct(int $size, \GMP $prime, \GMP $a, \GMP $b)
{
$this->size = $size;
$this->prime = $prime;
$this->a = $a;
$this->b = $b;
}
/**
* @return \GMP
*/
public function getA(): \GMP
{
return $this->a;
}
/**
* @return \GMP
*/
public function getB(): \GMP
{
return $this->b;
}
/**
* @return \GMP
*/
public function getPrime(): \GMP
{
return $this->prime;
}
/**
* @return int
*/
public function getSize(): int
{
return $this->size;
}
}
@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Primitives;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Crypto\Key\PrivateKey;
use Mdanter\Ecc\Crypto\Key\PublicKey;
use Mdanter\Ecc\Random\RandomGeneratorFactory;
use Mdanter\Ecc\Random\RandomNumberGeneratorInterface;
/**
* Curve point from which public and private keys can be derived.
*/
class GeneratorPoint extends Point
{
/**
* @var RandomNumberGeneratorInterface
*/
private $generator;
/**
* @param GmpMathInterface $adapter
* @param CurveFpInterface $curve
* @param \GMP $x
* @param \GMP $y
* @param \GMP $order
* @param RandomNumberGeneratorInterface $generator
*/
public function __construct(
GmpMathInterface $adapter,
CurveFpInterface $curve,
\GMP $x,
\GMP $y,
\GMP $order,
RandomNumberGeneratorInterface $generator = null
) {
$this->generator = $generator ?: RandomGeneratorFactory::getRandomGenerator();
parent::__construct($adapter, $curve, $x, $y, $order);
}
/**
* Verifies validity of given coordinates against the current point and its point.
*
* @todo Check if really necessary here (only used for testing in lib)
* @param \GMP $x
* @param \GMP $y
* @return bool
*/
public function isValid(\GMP $x, \GMP $y): bool
{
$math = $this->getAdapter();
$n = $this->getOrder();
$zero = gmp_init(0, 10);
$curve = $this->getCurve();
if ($math->cmp($x, $zero) < 0 || $math->cmp($n, $x) <= 0 || $math->cmp($y, $zero) < 0 || $math->cmp($n, $y) <= 0) {
return false;
}
if (! $curve->contains($x, $y)) {
return false;
}
$point = $curve->getPoint($x, $y)->mul($n);
if (! $point->isInfinity()) {
return false;
}
return true;
}
/**
* @return PrivateKeyInterface
*/
public function createPrivateKey(): PrivateKeyInterface
{
$secret = $this->generator->generate($this->getOrder());
return new PrivateKey($this->getAdapter(), $this, $secret);
}
/**
* @param \GMP $x
* @param \GMP $y
* @return PublicKeyInterface
*/
public function getPublicKeyFrom(\GMP $x, \GMP $y): PublicKeyInterface
{
$pubPoint = $this->getCurve()->getPoint($x, $y, $this->getOrder());
return new PublicKey($this->getAdapter(), $this, $pubPoint);
}
/**
* @param \GMP $secretMultiplier
* @return PrivateKeyInterface
*/
public function getPrivateKeyFrom(\GMP $secretMultiplier): PrivateKeyInterface
{
return new PrivateKey($this->getAdapter(), $this, $secretMultiplier);
}
}
+413
View File
@@ -0,0 +1,413 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Primitives;
use Mdanter\Ecc\Exception\PointException;
use Mdanter\Ecc\Exception\PointNotOnCurveException;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Math\ModularArithmetic;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
/**
* This class is where the elliptic curve arithmetic takes place.
* The important methods are:
* - add: adds two points according to ec arithmetic
* - double: doubles a point on the ec field mod p
* - mul: uses double and add to achieve multiplication The rest of the methods are there for supporting the ones above.
*/
class Point implements PointInterface
{
/**
* @var CurveFpInterface
*/
private $curve;
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @var ModularArithmetic
*/
private $modAdapter;
/**
* @var \GMP
*/
private $x;
/**
* @var \GMP
*/
private $y;
/**
* @var \GMP
*/
private $order;
/**
* @var bool
*/
private $infinity = false;
/**
* Initialize a new instance
*
* @param GmpMathInterface $adapter
* @param CurveFpInterface $curve
* @param \GMP $x
* @param \GMP $y
* @param \GMP $order
* @param bool $infinity
*
* @throws \RuntimeException when either the curve does not contain the given coordinates or
* when order is not null and P(x, y) * order is not equal to infinity.
*/
public function __construct(GmpMathInterface $adapter, CurveFpInterface $curve, \GMP $x, \GMP $y, \GMP $order = null, bool $infinity = false)
{
$this->adapter = $adapter;
$this->modAdapter = $curve->getModAdapter();
$this->curve = $curve;
$this->x = $x;
$this->y = $y;
$this->order = $order !== null ? $order : gmp_init(0, 10);
$this->infinity = (bool) $infinity;
if (! $infinity && ! $curve->contains($x, $y)) {
throw new PointNotOnCurveException($x, $y, $curve);
}
if (!is_null($order)) {
$mul = $this->mul($order);
if (!$mul->isInfinity()) {
throw new PointException("SELF * ORDER MUST EQUAL INFINITY. (" . (string)$mul . " found instead)");
}
}
}
/**
* @return GmpMathInterface
*/
public function getAdapter(): GmpMathInterface
{
return $this->adapter;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::isInfinity()
*/
public function isInfinity(): bool
{
return (bool) $this->infinity;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::getCurve()
*/
public function getCurve(): CurveFpInterface
{
return $this->curve;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::getOrder()
*/
public function getOrder(): \GMP
{
return $this->order;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::getX()
*/
public function getX(): \GMP
{
return $this->x;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::getY()
*/
public function getY(): \GMP
{
return $this->y;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::add()
* @return self
*/
public function add(PointInterface $addend): PointInterface
{
if (! $this->curve->equals($addend->getCurve())) {
throw new \RuntimeException("The Elliptic Curves do not match.");
}
if ($addend->isInfinity()) {
return clone $this;
}
if ($this->isInfinity()) {
return clone $addend;
}
$math = $this->adapter;
$modMath = $this->modAdapter;
if ($math->equals($addend->getX(), $this->x)) {
if ($math->equals($addend->getY(), $this->y)) {
return $this->getDouble();
} else {
return $this->curve->getInfinity();
}
}
$slope = $modMath->div(
$math->sub($addend->getY(), $this->y),
$math->sub($addend->getX(), $this->x)
);
$xR = $modMath->sub(
$math->sub($math->pow($slope, 2), $this->x),
$addend->getX()
);
$yR = $modMath->sub(
$math->mul($slope, $math->sub($this->x, $xR)),
$this->y
);
return $this->curve->getPoint($xR, $yR, $this->order);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::cmp()
*/
public function cmp(PointInterface $other): int
{
if ($other->isInfinity() && $this->isInfinity()) {
return 0;
}
if ($other->isInfinity() || $this->isInfinity()) {
return 1;
}
$math = $this->adapter;
$equal = ($math->equals($this->x, $other->getX()));
$equal &= ($math->equals($this->y, $other->getY()));
$equal &= $this->isInfinity() == $other->isInfinity();
$equal &= $this->curve->equals($other->getCurve());
if ($equal) {
return 0;
}
return 1;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::equals()
*/
public function equals(PointInterface $other): bool
{
return $this->cmp($other) == 0;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::mul()
*/
public function mul(\GMP $n): PointInterface
{
if ($this->isInfinity()) {
return $this->curve->getInfinity();
}
$zero = gmp_init(0, 10);
if ($this->adapter->cmp($this->order, $zero) > 0) {
$n = $this->adapter->mod($n, $this->order);
}
if ($this->adapter->equals($n, $zero)) {
return $this->curve->getInfinity();
}
/** @var Point[] $r */
$r = [
$this->curve->getInfinity(),
clone $this
];
$k = $this->curve->getSize();
$n = str_pad($this->adapter->baseConvert($this->adapter->toString($n), 10, 2), $k, '0', STR_PAD_LEFT);
for ($i = 0; $i < $k; $i++) {
$j = $n[$i];
$this->cswap($r[0], $r[1], $j ^ 1, $k);
$r[0] = $r[0]->add($r[1]);
$r[1] = $r[1]->getDouble();
$this->cswap($r[0], $r[1], $j ^ 1, $k);
}
$r[0]->validate();
return $r[0];
}
/**
* @param Point $a
* @param Point $b
* @param int $cond
* @param int $curveSize
*/
private function cswap(self $a, self $b, int $cond, int $curveSize)
{
$this->cswapValue($a->x, $b->x, $cond, $curveSize);
$this->cswapValue($a->y, $b->y, $cond, $curveSize);
$this->cswapValue($a->order, $b->order, $cond, $curveSize);
$this->cswapValue($a->infinity, $b->infinity, $cond, 8);
}
/**
* @param bool|\GMP $a
* @param bool|\GMP $b
* @param int $cond
* @param int $maskBitSize
*/
public function cswapValue(& $a, & $b, int $cond, int $maskBitSize)
{
$isGMP = is_object($a) && $a instanceof \GMP;
$sa = $isGMP ? $a : gmp_init(intval($a), 10);
$sb = $isGMP ? $b : gmp_init(intval($b), 10);
$mask = str_pad('', $maskBitSize, (string) (1 - intval($cond)), STR_PAD_LEFT);
$mask = gmp_init($mask, 2);
$taA = $this->adapter->bitwiseAnd($sa, $mask);
$taB = $this->adapter->bitwiseAnd($sb, $mask);
$sa = $this->adapter->bitwiseXor($this->adapter->bitwiseXor($sa, $sb), $taB);
$sb = $this->adapter->bitwiseXor($this->adapter->bitwiseXor($sa, $sb), $taA);
$sa = $this->adapter->bitwiseXor($this->adapter->bitwiseXor($sa, $sb), $taB);
$a = $isGMP ? $sa : (bool) gmp_strval($sa, 10);
$b = $isGMP ? $sb : (bool) gmp_strval($sb, 10);
}
/**
*
*/
private function validate()
{
if (! $this->infinity && ! $this->curve->contains($this->x, $this->y)) {
throw new \RuntimeException('Invalid point');
}
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::getDouble()
* @return self
*/
public function getDouble(): PointInterface
{
if ($this->isInfinity()) {
return $this->curve->getInfinity();
}
$math = $this->adapter;
$modMath = $this->modAdapter;
$a = $this->curve->getA();
$threeX2 = $math->mul(gmp_init(3, 10), $math->pow($this->x, 2));
$tangent = $modMath->div(
$math->add($threeX2, $a),
$math->mul(gmp_init(2, 10), $this->y)
);
$x3 = $modMath->sub(
$math->pow($tangent, 2),
$math->mul(gmp_init(2, 10), $this->x)
);
$y3 = $modMath->sub(
$math->mul($tangent, $math->sub($this->x, $x3)),
$this->y
);
return $this->curve->getPoint($x3, $y3, $this->order);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Primitives\PointInterface::__toString()
*/
public function __toString(): string
{
if ($this->infinity) {
return '[ (infinity) on ' . (string) $this->curve . ' ]';
}
return "[ (" . $this->adapter->toString($this->x) . "," . $this->adapter->toString($this->y) . ') on ' . (string) $this->curve . ' ]';
}
/**
* @return array
*/
public function __debugInfo(): array
{
$info = [
'x' => $this->adapter->toString($this->x),
'y' => $this->adapter->toString($this->y),
'z' => $this->adapter->toString($this->order),
'curve' => $this->curve
];
if ($this->infinity) {
$info['x'] = 'inf (' . $info['x'] . ')';
$info['y'] = 'inf (' . $info['y'] . ')';
$info['z'] = 'inf (' . $info['z'] . ')';
}
return $info;
}
}
@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Primitives;
/**
* *********************************************************************
* Copyright (C) 2012 Matyas Danter
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
* ***********************************************************************
*/
/**
* This is the contract for implementing Point, which encapsulates entities
* and operations over the points on the Elliptic Curve. Implementations must be immutable.
*
* Implementors must be wary of the special "Infinity" implementation, which breaks LSP, and should always
* be checked against (and properly handled) when receiving a PointInterface as an argument or when a method indicates it can
* return infinity.
*
* @todo Fix LSP break (possibly derive an extra interface, FinitePointInterface from current one, and move
* coordinate-related ops to sub-interface).
*/
interface PointInterface
{
/**
* Returns true if instance is an non-finite point.
*/
public function isInfinity(): bool;
/**
* Adds another point to the current one and returns the resulting point.
*
* @param PointInterface $addend
* @return PointInterface
*/
public function add(PointInterface $addend): PointInterface;
/**
* Compares the current instance to another point.
*
* @param PointInterface $other
* @return int A number different than 0 when current instance is less than the given point, 0 when they are equal.
*/
public function cmp(PointInterface $other): int;
/**
* Checks whether the current instance is equal to the given point.
*
* @param PointInterface $other
* @return bool true when points are equal, false otherwise.
*/
public function equals(PointInterface $other): bool;
/**
* Multiplies the point by a scalar value and returns the resulting point.
*
* @param \GMP $multiplier
* @return PointInterface
*/
public function mul(\GMP $multiplier): PointInterface;
/**
* Returns the curve to which the point belongs.
*
* @return CurveFpInterface
*/
public function getCurve(): CurveFpInterface;
/**
* Doubles the current point and returns the resulting point.
*
* @return PointInterface
*/
public function getDouble(): PointInterface;
/**
* Returns the order of the point.
*
* @return \GMP
*/
public function getOrder(): \GMP;
/**
* Returns the X coordinate of the point.
*
* @return \GMP
*/
public function getX(): \GMP;
/**
* Returns the Y coordinate of the point.
*
* @return \GMP
*/
public function getY(): \GMP;
/**
* Returns the string representation of the point.
*
* @return string
*/
public function __toString(): string;
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Random;
class DebugDecorator implements RandomNumberGeneratorInterface
{
/**
* @var RandomNumberGeneratorInterface
*/
private $generator;
/**
* @var string
*/
private $generatorName;
/**
* @param RandomNumberGeneratorInterface $generator
* @param string $name
*/
public function __construct(RandomNumberGeneratorInterface $generator, string $name)
{
$this->generator = $generator;
$this->generatorName = $name;
}
/**
* @param \GMP $max
* @return \GMP
*/
public function generate(\GMP $max): \GMP
{
echo $this->generatorName.'::rand() = ';
$result = $this->generator->generate($max);
echo gmp_strval($result, 10).PHP_EOL;
return $result;
}
}
@@ -0,0 +1,150 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Random;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Util\BinaryString;
use Mdanter\Ecc\Util\NumberSize;
class HmacRandomNumberGenerator implements RandomNumberGeneratorInterface
{
/**
* @var GmpMathInterface
*/
private $math;
/**
* @var string
*/
private $algorithm;
/**
* @var PrivateKeyInterface
*/
private $privateKey;
/**
* @var \GMP
*/
private $messageHash;
/**
* @var array
*/
private $algSize = array(
'sha1' => 160,
'sha224' => 224,
'sha256' => 256,
'sha384' => 384,
'sha512' => 512
);
/**
* Hmac constructor.
* @param GmpMathInterface $math
* @param PrivateKeyInterface $privateKey
* @param \GMP $messageHash - decimal hash of the message (*may* be truncated)
* @param string $algorithm - hashing algorithm
*/
public function __construct(GmpMathInterface $math, PrivateKeyInterface $privateKey, \GMP $messageHash, string $algorithm)
{
if (!isset($this->algSize[$algorithm])) {
throw new \InvalidArgumentException('Unsupported hashing algorithm');
}
$this->math = $math;
$this->algorithm = $algorithm;
$this->privateKey = $privateKey;
$this->messageHash = $messageHash;
}
/**
* @param string $bits - binary string of bits
* @param \GMP $qlen - length of q in bits
* @return \GMP
*/
public function bits2int(string $bits, \GMP $qlen): \GMP
{
$vlen = gmp_init(BinaryString::length($bits) * 8, 10);
$hex = bin2hex($bits);
$v = gmp_init($hex, 16);
if ($this->math->cmp($vlen, $qlen) > 0) {
$v = $this->math->rightShift($v, (int) $this->math->toString($this->math->sub($vlen, $qlen)));
}
return $v;
}
/**
* @param \GMP $int
* @param \GMP $rlen - rounded octet length
* @return string
*/
public function int2octets(\GMP $int, \GMP $rlen): string
{
$out = pack("H*", $this->math->decHex(gmp_strval($int, 10)));
$length = gmp_init(BinaryString::length($out), 10);
if ($this->math->cmp($length, $rlen) < 0) {
return str_pad('', (int) $this->math->toString($this->math->sub($rlen, $length)), "\x00") . $out;
}
if ($this->math->cmp($length, $rlen) > 0) {
return BinaryString::substring($out, 0, (int) $this->math->toString($rlen));
}
return $out;
}
/**
* @param string $algorithm
* @return int
*/
private function getHashLength(string $algorithm): int
{
return $this->algSize[$algorithm];
}
/**
* @param \GMP $q
* @return \GMP
*/
public function generate(\GMP $q): \GMP
{
$qlen = gmp_init(NumberSize::bnNumBits($this->math, $q), 10);
$rlen = $this->math->rightShift($this->math->add($qlen, gmp_init(7, 10)), 3);
$hlen = $this->getHashLength($this->algorithm);
$bx = $this->int2octets($this->privateKey->getSecret(), $rlen) . $this->int2octets($this->messageHash, $rlen);
$v = str_pad('', $hlen >> 3, "\x01", STR_PAD_LEFT);
$k = str_pad('', $hlen >> 3, "\x00", STR_PAD_LEFT);
$k = hash_hmac($this->algorithm, $v . "\x00" . $bx, $k, true);
$v = hash_hmac($this->algorithm, $v, $k, true);
$k = hash_hmac($this->algorithm, $v . "\x01" . $bx, $k, true);
$v = hash_hmac($this->algorithm, $v, $k, true);
$t = '';
for (;;) {
$toff = gmp_init(0, 10);
while ($this->math->cmp($toff, $rlen) < 0) {
$v = hash_hmac($this->algorithm, $v, $k, true);
$cc = min(BinaryString::length($v), (int) gmp_strval(gmp_sub($rlen, $toff), 10));
$t .= BinaryString::substring($v, 0, $cc);
$toff = gmp_add($toff, $cc);
}
$k = $this->bits2int($t, $qlen);
if ($this->math->cmp($k, gmp_init(0, 10)) > 0 && $this->math->cmp($k, $q) < 0) {
return $k;
}
$k = hash_hmac($this->algorithm, $v . "\x00", $k, true);
$v = hash_hmac($this->algorithm, $v, $k, true);
}
}
}
@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Random;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
use Mdanter\Ecc\Math\MathAdapterFactory;
class RandomGeneratorFactory
{
/**
* @param bool $debug
* @return DebugDecorator|RandomNumberGeneratorInterface
*/
public static function getRandomGenerator(bool $debug = false): RandomNumberGeneratorInterface
{
return self::wrapAdapter(
new RandomNumberGenerator(
MathAdapterFactory::getAdapter($debug)
),
'random_bytes',
$debug
);
}
/**
* @param PrivateKeyInterface $privateKey
* @param \GMP $messageHash
* @param string $algorithm
* @param bool $debug
* @return DebugDecorator|RandomNumberGeneratorInterface
*/
public static function getHmacRandomGenerator(PrivateKeyInterface $privateKey, \GMP $messageHash, string $algorithm, bool $debug = false): RandomNumberGeneratorInterface
{
return self::wrapAdapter(
new HmacRandomNumberGenerator(
MathAdapterFactory::getAdapter($debug),
$privateKey,
$messageHash,
$algorithm
),
'rfc6979',
$debug
);
}
/**
* @param RandomNumberGeneratorInterface $generator
* @param string $name
* @param bool $debug
* @return DebugDecorator|RandomNumberGeneratorInterface
*/
private static function wrapAdapter(RandomNumberGeneratorInterface $generator, string $name, bool $debug = false): RandomNumberGeneratorInterface
{
if ($debug === true) {
return new DebugDecorator($generator, $name);
}
return $generator;
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Random;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Util\NumberSize;
class RandomNumberGenerator implements RandomNumberGeneratorInterface
{
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* RandomNumberGenerator constructor.
* @param GmpMathInterface $adapter
*/
public function __construct(GmpMathInterface $adapter)
{
$this->adapter = $adapter;
}
/**
* @param \GMP $max
* @return \GMP
*/
public function generate(\GMP $max): \GMP
{
$numBits = NumberSize::bnNumBits($this->adapter, $max);
$numBytes = (int) ceil($numBits / 8);
// Generate an integer of size >= $numBits
$bytes = random_bytes($numBytes);
$value = $this->adapter->stringToInt($bytes);
$mask = gmp_sub(gmp_pow(2, $numBits), 1);
$integer = gmp_and($value, $mask);
return $integer;
}
}
@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Random;
interface RandomNumberGeneratorInterface
{
/**
* Generate a random number between 0 and the specified upper boundary.
* @param \GMP $max - Upper boundary, inclusive
* @return \GMP
*/
public function generate(\GMP $max): \GMP;
}
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\Point;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Primitives\CurveFpInterface;
use Mdanter\Ecc\Primitives\PointInterface;
use Mdanter\Ecc\Serializer\Util\CurveOidMapper;
class CompressedPointSerializer implements PointSerializerInterface
{
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @var \Mdanter\Ecc\Math\NumberTheory
*/
private $theory;
/**
* CompressedPointSerializer constructor.
* @param GmpMathInterface $adapter
*/
public function __construct(GmpMathInterface $adapter)
{
$this->adapter = $adapter;
$this->theory = $adapter->getNumberTheory();
}
/**
* @param PointInterface $point
* @return string
*/
public function getPrefix(PointInterface $point): string
{
if ($this->adapter->equals($this->adapter->mod($point->getY(), gmp_init(2, 10)), gmp_init(0))) {
return '02';
} else {
return '03';
}
}
/**
* @param PointInterface $point
* @return string
*/
public function serialize(PointInterface $point): string
{
$length = CurveOidMapper::getByteSize($point->getCurve()) * 2;
$hexString = $this->getPrefix($point);
$hexString .= str_pad(gmp_strval($point->getX(), 16), $length, '0', STR_PAD_LEFT);
return $hexString;
}
/**
* @param CurveFpInterface $curve
* @param string $data - hex serialized compressed point
* @return PointInterface
*/
public function unserialize(CurveFpInterface $curve, string $data): PointInterface
{
$prefix = substr($data, 0, 2);
if ($prefix !== '03' && $prefix !== '02') {
throw new \InvalidArgumentException('Invalid data: only compressed keys are supported.');
}
$x = gmp_init(substr($data, 2), 16);
$y = $curve->recoverYfromX($prefix === '03', $x);
return $curve->getPoint($x, $y);
}
}
@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\Point;
use Mdanter\Ecc\Primitives\PointInterface;
use Mdanter\Ecc\Primitives\CurveFpInterface;
interface PointSerializerInterface
{
/**
*
* @param PointInterface $point
* @return string
*/
public function serialize(PointInterface $point): string;
/**
* @param CurveFpInterface $curve Curve that contains the serialized point
* @param string $string
* @return PointInterface
*/
public function unserialize(CurveFpInterface $curve, string $string): PointInterface;
}
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\Point;
use Mdanter\Ecc\Primitives\PointInterface;
use Mdanter\Ecc\Primitives\CurveFpInterface;
use Mdanter\Ecc\Serializer\Util\CurveOidMapper;
use Mdanter\Ecc\Util\BinaryString;
class UncompressedPointSerializer implements PointSerializerInterface
{
/**
* @param PointInterface $point
* @return string
*/
public function serialize(PointInterface $point): string
{
$length = CurveOidMapper::getByteSize($point->getCurve()) * 2;
$hexString = '04';
$hexString .= str_pad(gmp_strval($point->getX(), 16), $length, '0', STR_PAD_LEFT);
$hexString .= str_pad(gmp_strval($point->getY(), 16), $length, '0', STR_PAD_LEFT);
return $hexString;
}
/**
* @param CurveFpInterface $curve
* @param string $data
* @return PointInterface
*/
public function unserialize(CurveFpInterface $curve, string $data): PointInterface
{
if (BinaryString::substring($data, 0, 2) != '04') {
throw new \InvalidArgumentException('Invalid data: only uncompressed keys are supported.');
}
$data = BinaryString::substring($data, 2);
$dataLength = BinaryString::length($data);
$x = gmp_init(BinaryString::substring($data, 0, $dataLength / 2), 16);
$y = gmp_init(BinaryString::substring($data, $dataLength / 2), 16);
return $curve->getPoint($x, $y);
}
}
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\PrivateKey;
use FG\ASN1\ASNObject;
use FG\ASN1\Universal\Sequence;
use FG\ASN1\Universal\Integer;
use FG\ASN1\Universal\BitString;
use FG\ASN1\Universal\OctetString;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Math\MathAdapterFactory;
use Mdanter\Ecc\Serializer\Util\CurveOidMapper;
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
use FG\ASN1\ExplicitlyTaggedObject;
/**
* PEM Private key formatter
*
* @link https://tools.ietf.org/html/rfc5915
*/
class DerPrivateKeySerializer implements PrivateKeySerializerInterface
{
const VERSION = 1;
/**
* @var GmpMathInterface|null
*/
private $adapter;
/**
* @var DerPublicKeySerializer
*/
private $pubKeySerializer;
/**
* @param GmpMathInterface $adapter
* @param DerPublicKeySerializer $pubKeySerializer
*/
public function __construct(GmpMathInterface $adapter = null, DerPublicKeySerializer $pubKeySerializer = null)
{
$this->adapter = $adapter ?: MathAdapterFactory::getAdapter();
$this->pubKeySerializer = $pubKeySerializer ?: new DerPublicKeySerializer($this->adapter);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Serializer\PrivateKey\PrivateKeySerializerInterface::serialize()
*/
public function serialize(PrivateKeyInterface $key): string
{
$privateKeyInfo = new Sequence(
new Integer(self::VERSION),
new OctetString($this->formatKey($key)),
new ExplicitlyTaggedObject(0, CurveOidMapper::getCurveOid($key->getPoint()->getCurve())),
new ExplicitlyTaggedObject(1, $this->encodePubKey($key))
);
return $privateKeyInfo->getBinary();
}
/**
* @param PrivateKeyInterface $key
* @return BitString
*/
private function encodePubKey(PrivateKeyInterface $key): BitString
{
return new BitString(
$this->pubKeySerializer->getUncompressedKey($key->getPublicKey())
);
}
/**
* @param PrivateKeyInterface $key
* @return string
*/
private function formatKey(PrivateKeyInterface $key): string
{
return gmp_strval($key->getSecret(), 16);
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Serializer\PrivateKey\PrivateKeySerializerInterface::parse()
* @throws \FG\ASN1\Exception\ParserException
*/
public function parse(string $data): PrivateKeyInterface
{
$asnObject = ASNObject::fromBinary($data);
if (! ($asnObject instanceof Sequence) || $asnObject->getNumberofChildren() !== 4) {
throw new \RuntimeException('Invalid data.');
}
$children = $asnObject->getChildren();
$version = $children[0];
if ($version->getContent() != 1) {
throw new \RuntimeException('Invalid data: only version 1 (RFC5915) keys are supported.');
}
$key = gmp_init($children[1]->getContent(), 16);
$oid = $children[2]->getContent()[0];
$generator = CurveOidMapper::getGeneratorFromOid($oid);
return $generator->getPrivateKeyFrom($key);
}
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\PrivateKey;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
/**
* PEM Private key formatter
*
* @link https://tools.ietf.org/html/rfc5915
*/
class PemPrivateKeySerializer implements PrivateKeySerializerInterface
{
/**
* @var DerPrivateKeySerializer
*/
private $derSerializer;
/**
* @param DerPrivateKeySerializer $derSerializer
*/
public function __construct(DerPrivateKeySerializer $derSerializer)
{
$this->derSerializer = $derSerializer;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Serializer\PrivateKey\PrivateKeySerializerInterface::serialize()
*/
public function serialize(PrivateKeyInterface $key): string
{
$privateKeyInfo = $this->derSerializer->serialize($key);
$content = '-----BEGIN EC PRIVATE KEY-----'.PHP_EOL;
$content .= trim(chunk_split(base64_encode($privateKeyInfo), 64, PHP_EOL)).PHP_EOL;
$content .= '-----END EC PRIVATE KEY-----';
return $content;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Serializer\PrivateKey\PrivateKeySerializerInterface::parse()
*/
public function parse(string $formattedKey): PrivateKeyInterface
{
$formattedKey = str_replace('-----BEGIN EC PRIVATE KEY-----', '', $formattedKey);
$formattedKey = str_replace('-----END EC PRIVATE KEY-----', '', $formattedKey);
$data = base64_decode($formattedKey);
return $this->derSerializer->parse($data);
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\PrivateKey;
use Mdanter\Ecc\Crypto\Key\PrivateKeyInterface;
interface PrivateKeySerializerInterface
{
/**
*
* @param PrivateKeyInterface $key
* @return string
*/
public function serialize(PrivateKeyInterface $key): string;
/**
*
* @param string $formattedKey
* @return PrivateKeyInterface
*/
public function parse(string $formattedKey): PrivateKeyInterface;
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\PublicKey\Der;
use FG\ASN1\Universal\Sequence;
use FG\ASN1\Universal\ObjectIdentifier;
use FG\ASN1\Universal\BitString;
use Mdanter\Ecc\Primitives\PointInterface;
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
use Mdanter\Ecc\Curves\NamedCurveFp;
use Mdanter\Ecc\Serializer\Util\CurveOidMapper;
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
use Mdanter\Ecc\Serializer\Point\PointSerializerInterface;
use Mdanter\Ecc\Serializer\Point\UncompressedPointSerializer;
class Formatter
{
/**
* @var UncompressedPointSerializer
*/
private $pointSerializer;
/**
* Formatter constructor.
* @param PointSerializerInterface|null $pointSerializer
*/
public function __construct(PointSerializerInterface $pointSerializer = null)
{
$this->pointSerializer = $pointSerializer ?: new UncompressedPointSerializer();
}
/**
* @param PublicKeyInterface $key
* @return string
*/
public function format(PublicKeyInterface $key): string
{
if (! ($key->getCurve() instanceof NamedCurveFp)) {
throw new \RuntimeException('Not implemented for unnamed curves');
}
$sequence = new Sequence(
new Sequence(
new ObjectIdentifier(DerPublicKeySerializer::X509_ECDSA_OID),
CurveOidMapper::getCurveOid($key->getCurve())
),
new BitString($this->encodePoint($key->getPoint()))
);
return $sequence->getBinary();
}
/**
* @param PointInterface $point
* @return string
*/
public function encodePoint(PointInterface $point): string
{
return $this->pointSerializer->serialize($point);
}
}
@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\PublicKey\Der;
use FG\ASN1\ASNObject;
use FG\ASN1\Identifier;
use FG\ASN1\Universal\Sequence;
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Serializer\Util\CurveOidMapper;
use Mdanter\Ecc\Primitives\GeneratorPoint;
use Mdanter\Ecc\Serializer\PublicKey\DerPublicKeySerializer;
use Mdanter\Ecc\Serializer\Point\PointSerializerInterface;
use Mdanter\Ecc\Serializer\Point\UncompressedPointSerializer;
use Mdanter\Ecc\Crypto\Key\PublicKey;
class Parser
{
/**
* @var GmpMathInterface
*/
private $adapter;
/**
* @var UncompressedPointSerializer
*/
private $pointSerializer;
/**
* Parser constructor.
* @param GmpMathInterface $adapter
* @param PointSerializerInterface|null $pointSerializer
*/
public function __construct(GmpMathInterface $adapter, PointSerializerInterface $pointSerializer = null)
{
$this->adapter = $adapter;
$this->pointSerializer = $pointSerializer ?: new UncompressedPointSerializer();
}
/**
* @param string $binaryData
* @return PublicKeyInterface
* @throws \FG\ASN1\Exception\ParserException
*/
public function parse(string $binaryData): PublicKeyInterface
{
$asnObject = ASNObject::fromBinary($binaryData);
if ($asnObject->getType() !== Identifier::SEQUENCE) {
throw new \RuntimeException('Invalid data.');
}
/** @var Sequence $asnObject */
if ($asnObject->getNumberofChildren() != 2) {
throw new \RuntimeException('Invalid data.');
}
$children = $asnObject->getChildren();
if (count($children) != 2) {
throw new \RuntimeException('Invalid data.');
}
if (count($children) != 2) {
throw new \RuntimeException('Invalid data.');
}
if ($children[0]->getType() !== Identifier::SEQUENCE) {
throw new \RuntimeException('Invalid data.');
}
if (count($children[0]->getChildren()) != 2) {
throw new \RuntimeException('Invalid data.');
}
if ($children[0]->getChildren()[0]->getType() !== Identifier::OBJECT_IDENTIFIER) {
throw new \RuntimeException('Invalid data.');
}
if ($children[0]->getChildren()[1]->getType() !== Identifier::OBJECT_IDENTIFIER) {
throw new \RuntimeException('Invalid data.');
}
if ($children[1]->getType() !== Identifier::BITSTRING) {
throw new \RuntimeException('Invalid data.');
}
$oid = $children[0]->getChildren()[0];
$curveOid = $children[0]->getChildren()[1];
$encodedKey = $children[1];
if ($oid->getContent() !== DerPublicKeySerializer::X509_ECDSA_OID) {
throw new \RuntimeException('Invalid data: non X509 data.');
}
$generator = CurveOidMapper::getGeneratorFromOid($curveOid);
return $this->parseKey($generator, $encodedKey->getContent());
}
/**
* @param GeneratorPoint $generator
* @param string $data
* @return PublicKeyInterface
*/
public function parseKey(GeneratorPoint $generator, string $data): PublicKeyInterface
{
$point = $this->pointSerializer->unserialize($generator->getCurve(), $data);
return new PublicKey($this->adapter, $generator, $point);
}
}
@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\PublicKey;
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Math\MathAdapterFactory;
use Mdanter\Ecc\Serializer\Point\PointSerializerInterface;
use Mdanter\Ecc\Serializer\Point\UncompressedPointSerializer;
use Mdanter\Ecc\Serializer\PublicKey\Der\Formatter;
use Mdanter\Ecc\Serializer\PublicKey\Der\Parser;
/**
*
* @link https://tools.ietf.org/html/rfc5480#page-3
* @todo: review for full spec, should we support all prefixes here?
*/
class DerPublicKeySerializer implements PublicKeySerializerInterface
{
const X509_ECDSA_OID = '1.2.840.10045.2.1';
/**
*
* @var GmpMathInterface
*/
private $adapter;
/**
*
* @var Formatter
*/
private $formatter;
/**
*
* @var Parser
*/
private $parser;
/**
* @param GmpMathInterface|null $adapter
* @param PointSerializerInterface|null $pointSerializer
*/
public function __construct(GmpMathInterface $adapter = null, PointSerializerInterface $pointSerializer = null)
{
$this->adapter = $adapter ?: MathAdapterFactory::getAdapter();
$this->formatter = new Formatter();
$this->parser = new Parser($this->adapter, $pointSerializer ?: new UncompressedPointSerializer());
}
/**
*
* @param PublicKeyInterface $key
* @return string
*/
public function serialize(PublicKeyInterface $key): string
{
return $this->formatter->format($key);
}
/**
* @param PublicKeyInterface $key
* @return string
*/
public function getUncompressedKey(PublicKeyInterface $key): string
{
return $this->formatter->encodePoint($key->getPoint());
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Serializer\PublicKey\PublicKeySerializerInterface::parse()
*/
public function parse(string $string): PublicKeyInterface
{
return $this->parser->parse($string);
}
}
@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\PublicKey;
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
/**
*
* @link https://tools.ietf.org/html/rfc5480#page-3
*/
class PemPublicKeySerializer implements PublicKeySerializerInterface
{
/**
* @var DerPublicKeySerializer
*/
private $derSerializer;
/**
* @param DerPublicKeySerializer $serializer
*/
public function __construct(DerPublicKeySerializer $serializer)
{
$this->derSerializer = $serializer;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Serializer\PublicKey\PublicKeySerializerInterface::serialize()
*/
public function serialize(PublicKeyInterface $key): string
{
$publicKeyInfo = $this->derSerializer->serialize($key);
$content = '-----BEGIN PUBLIC KEY-----'.PHP_EOL;
$content .= trim(chunk_split(base64_encode($publicKeyInfo), 64, PHP_EOL)).PHP_EOL;
$content .= '-----END PUBLIC KEY-----';
return $content;
}
/**
* {@inheritDoc}
* @see \Mdanter\Ecc\Serializer\PublicKey\PublicKeySerializerInterface::parse()
*/
public function parse(string $formattedKey): PublicKeyInterface
{
$formattedKey = str_replace('-----BEGIN PUBLIC KEY-----', '', $formattedKey);
$formattedKey = str_replace('-----END PUBLIC KEY-----', '', $formattedKey);
$data = base64_decode($formattedKey);
return $this->derSerializer->parse($data);
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\PublicKey;
use Mdanter\Ecc\Crypto\Key\PublicKeyInterface;
interface PublicKeySerializerInterface
{
/**
* @param PublicKeyInterface $key
* @return string
*/
public function serialize(PublicKeyInterface $key): string;
/**
* @param string $formattedKey
* @return PublicKeyInterface
*/
public function parse(string $formattedKey): PublicKeyInterface;
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\Signature\Der;
use FG\ASN1\Universal\Integer;
use FG\ASN1\Universal\Sequence;
use Mdanter\Ecc\Crypto\Signature\SignatureInterface;
class Formatter
{
/**
* @param SignatureInterface $signature
* @return Sequence
*/
public function toAsn(SignatureInterface $signature): Sequence
{
return new Sequence(
new Integer(gmp_strval($signature->getR(), 10)),
new Integer(gmp_strval($signature->getS(), 10))
);
}
/**
* @param SignatureInterface $signature
* @return string
*/
public function serialize(SignatureInterface $signature): string
{
return $this->toAsn($signature)->getBinary();
}
}
@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\Signature\Der;
use FG\ASN1\ASNObject;
use FG\ASN1\Identifier;
use FG\ASN1\Universal\Integer;
use Mdanter\Ecc\Crypto\Signature\Signature;
use Mdanter\Ecc\Crypto\Signature\SignatureInterface;
use Mdanter\Ecc\Exception\SignatureDecodeException;
class Parser
{
/**
* @param string $binary
* @return SignatureInterface
* @throws \FG\ASN1\Exception\ParserException
*/
public function parse(string $binary): SignatureInterface
{
$offsetIndex = 0;
$asnObject = ASNObject::fromBinary($binary, $offsetIndex);
if ($offsetIndex != strlen($binary)) {
throw new SignatureDecodeException('Invalid data.');
}
// Set inherits from Sequence, so use getType!
if ($asnObject->getType() !== Identifier::SEQUENCE) {
throw new SignatureDecodeException('Invalid tag for sequence.');
}
if ($asnObject->getNumberofChildren() !== 2) {
throw new SignatureDecodeException('Invalid data.');
}
if (!($asnObject[0] instanceof Integer && $asnObject[1] instanceof Integer)) {
throw new SignatureDecodeException('Invalid data.');
}
return new Signature(
gmp_init($asnObject[0]->getContent(), 10),
gmp_init($asnObject[1]->getContent(), 10)
);
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\Signature;
use Mdanter\Ecc\Crypto\Signature\SignatureInterface;
class DerSignatureSerializer implements DerSignatureSerializerInterface
{
/**
* @var Der\Parser
*/
private $parser;
/**
* @var Der\Formatter
*/
private $formatter;
public function __construct()
{
$this->parser = new Der\Parser();
$this->formatter = new Der\Formatter();
}
/**
* @param SignatureInterface $signature
* @return string
*/
public function serialize(SignatureInterface $signature): string
{
return $this->formatter->serialize($signature);
}
/**
* @param string $binary
* @return SignatureInterface
* @throws \FG\ASN1\Exception\ParserException
*/
public function parse(string $binary): SignatureInterface
{
return $this->parser->parse($binary);
}
}
@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\Signature;
use Mdanter\Ecc\Crypto\Signature\SignatureInterface;
interface DerSignatureSerializerInterface
{
/**
* @param SignatureInterface $signature
* @return string
*/
public function serialize(SignatureInterface $signature): string;
/**
* @param string $binary
* @return SignatureInterface
* @throws \FG\ASN1\Exception\ParserException
*/
public function parse(string $binary): SignatureInterface;
}
@@ -0,0 +1,141 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Serializer\Util;
use FG\ASN1\Universal\ObjectIdentifier;
use Mdanter\Ecc\Curves\NamedCurveFp;
use Mdanter\Ecc\Curves\CurveFactory;
use Mdanter\Ecc\Curves\NistCurve;
use Mdanter\Ecc\Curves\SecgCurve;
use Mdanter\Ecc\Exception\UnsupportedCurveException;
use Mdanter\Ecc\Primitives\CurveFpInterface;
use Mdanter\Ecc\Primitives\GeneratorPoint;
class CurveOidMapper
{
const NIST_P192_OID = '1.2.840.10045.3.1.1';
const NIST_P224_OID = '1.3.132.0.33';
const NIST_P256_OID = '1.2.840.10045.3.1.7';
const NIST_P384_OID = '1.3.132.0.34';
const NIST_P521_OID = '1.3.132.0.35';
const SECP_112R1_OID = '1.3.132.0.6';
const SECP_192K1_OID = '1.3.132.0.31';
const SECP_256K1_OID = '1.3.132.0.10';
const SECP_256R1_OID = '1.2.840.10045.3.1.7';
const SECP_384R1_OID = '1.3.132.0.34';
/**
* @var array
*/
private static $oidMap = array(
NistCurve::NAME_P192 => self::NIST_P192_OID,
NistCurve::NAME_P224 => self::NIST_P224_OID,
NistCurve::NAME_P256 => self::NIST_P256_OID,
NistCurve::NAME_P384 => self::NIST_P384_OID,
NistCurve::NAME_P521 => self::NIST_P521_OID,
SecgCurve::NAME_SECP_112R1 => self::SECP_112R1_OID,
SecgCurve::NAME_SECP_192K1 => self::SECP_192K1_OID,
SecgCurve::NAME_SECP_256K1 => self::SECP_256K1_OID,
SecgCurve::NAME_SECP_256R1 => self::SECP_256R1_OID,
SecgCurve::NAME_SECP_384R1 => self::SECP_384R1_OID,
);
/**
* @var array
*/
private static $sizeMap = array(
NistCurve::NAME_P192 => 24,
NistCurve::NAME_P224 => 28,
NistCurve::NAME_P256 => 32,
NistCurve::NAME_P384 => 48,
NistCurve::NAME_P521 => 66,
SecgCurve::NAME_SECP_112R1 => 14,
SecgCurve::NAME_SECP_192K1 => 24,
SecgCurve::NAME_SECP_256K1 => 32,
SecgCurve::NAME_SECP_256R1 => 32,
SecgCurve::NAME_SECP_384R1 => 48,
);
/**
* @return array
*/
public static function getNames(): array
{
return array_keys(self::$oidMap);
}
/**
* @param CurveFpInterface $curve
* @return int
*/
public static function getByteSize(CurveFpInterface $curve): int
{
if ($curve instanceof NamedCurveFp && array_key_exists($curve->getName(), self::$sizeMap)) {
return self::$sizeMap[$curve->getName()];
}
throw new UnsupportedCurveException('Unsupported curve type');
}
/**
* @param NamedCurveFp $curve
* @return ObjectIdentifier
*/
public static function getCurveOid(NamedCurveFp $curve): ObjectIdentifier
{
if (array_key_exists($curve->getName(), self::$oidMap)) {
$oidString = self::$oidMap[$curve->getName()];
return new ObjectIdentifier($oidString);
}
throw new UnsupportedCurveException('Unsupported curve type');
}
/**
* @param ObjectIdentifier $oid
* @return NamedCurveFp
*/
public static function getCurveFromOid(ObjectIdentifier $oid): NamedCurveFp
{
$oidString = $oid->getContent();
$invertedMap = array_flip(self::$oidMap);
if (array_key_exists($oidString, $invertedMap)) {
return CurveFactory::getCurveByName($invertedMap[$oidString]);
}
$error = new UnsupportedCurveException('Invalid data: unsupported curve.');
$error->setOid($oidString);
throw $error;
}
/**
* @param ObjectIdentifier $oid
* @return GeneratorPoint
*/
public static function getGeneratorFromOid(ObjectIdentifier $oid): GeneratorPoint
{
$oidString = $oid->getContent();
$invertedMap = array_flip(self::$oidMap);
if (array_key_exists($oidString, $invertedMap)) {
return CurveFactory::getGeneratorByName($invertedMap[$oidString]);
}
$error = new UnsupportedCurveException('Invalid data: unsupported generator.');
$error->setOid($oidString);
throw $error;
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Util;
class BinaryString
{
/**
* Multi-byte-safe string length calculation
*
* @param string $str
* @return int
*/
public static function length(string $str): int
{
// Premature optimization: cache the function_exists() result
static $exists = null;
if ($exists === null) {
$exists = function_exists('mb_strlen');
}
// If it exists, we need to make sure we're using 8bit mode
if ($exists) {
return mb_strlen($str, '8bit');
}
return strlen($str);
}
/**
* Multi-byte-safe substring calculation
*
* @param string $str
* @param int $start
* @param int $length (optional)
* @return string
*/
public static function substring(string $str, int $start = 0, int $length = null): string
{
// Premature optimization: cache the function_exists() result
static $exists = null;
if ($exists === null) {
$exists = function_exists('mb_substr');
}
// If it exists, we need to make sure we're using 8bit mode
if ($exists) {
return mb_substr($str, $start, $length, '8bit');
} elseif ($length !== null) {
return substr($str, $start, $length);
}
return substr($str, $start);
}
/**
* Equivalent to hash_equals() in PHP 5.6
*
* @param string $knownString
* @param string $userString
*
* @return bool
*/
public static function constantTimeCompare(string $knownString, string $userString): bool
{
return hash_equals($knownString, $userString);
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Mdanter\Ecc\Util;
use Mdanter\Ecc\Math\GmpMathInterface;
class NumberSize
{
/**
* @param GmpMathInterface $adapter
* @param \GMP $x
* @return float
*/
public static function getCeiledByteSize(GmpMathInterface $adapter, \GMP $x): float
{
$log2 = self::bnNumBits($adapter, $x);
return ceil($log2 / 8);
}
/**
* @param GmpMathInterface $adapter
* @param \GMP $x
* @return float
*/
public static function getFlooredByteSize(GmpMathInterface $adapter, \GMP $x): float
{
$log2 = self::bnNumBits($adapter, $x);
return floor($log2 / 8) + 1;
}
/**
* Returns the number of minimum required bytes to store a given number. Non-significant upper bits are not counted.
*
* @param GmpMathInterface $adapter
* @param \GMP $x
* @return int
*
* @link https://www.openssl.org/docs/crypto/BN_num_bytes.html
*/
public static function bnNumBytes(GmpMathInterface $adapter, \GMP $x): int
{
// https://github.com/luvit/openssl/blob/master/openssl/crypto/bn/bn.h#L402
return (int) floor((self::bnNumBits($adapter, $x) + 7) / 8);
}
/**
* Returns the number of bits used to store this number. Non-significant upper bits are not counted.
*
* @param GmpMathInterface $adapter
* @param \GMP $x
* @return int
*
* @link https://www.openssl.org/docs/crypto/BN_num_bytes.html
*/
public static function bnNumBits(GmpMathInterface $adapter, \GMP $x): int
{
$zero = gmp_init(0, 10);
if ($adapter->equals($x, $zero)) {
return 0;
}
$log2 = 0;
while (false === $adapter->equals($x, $zero)) {
$x = $adapter->rightShift($x, 1);
$log2++;
}
return $log2 ;
}
}
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
for i in examples/*.php; do
php $i
if [ $? != 0 ]; then
echo "Error running example code";
exit -1
fi;
done