diff --git a/composer.json b/composer.json index 6410214..fce710d 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "google/protobuf": "^3.13", "ext-curl": "*", "ext-gmp": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "deemru/cloaked": "^1.0" }, "suggest": { "ext-sodium": "Up to ~2000x faster sign/verify" diff --git a/docs/WavesKit.md b/docs/WavesKit.md index 8a26614..e4f283f 100644 --- a/docs/WavesKit.md +++ b/docs/WavesKit.md @@ -97,7 +97,7 @@ **Description** ```php -public __construct (string $chainId, mixed|null $logFunction) +public __construct (string $chainId, mixed|null $logFunction, bool $keyCaching) ``` Creates WavesKit instance @@ -110,6 +110,8 @@ Creates WavesKit instance : Blockchain identifier (default: 'W') * `(mixed|null) $logFunction` : Log functionality (default: null) +* `(bool) $keyCaching` +: Cache key flag (default: false) **Return Values** @@ -573,9 +575,9 @@ Gets address **Return Values** -`string` +`string|false` -> Address +> Address or FALSE on failure
@@ -834,7 +836,7 @@ Gets order history for your account **Description** ```php -public getPrivateKey (bool $raw, string|null $seed, string|null $prefix) +public getPrivateKey (bool $raw, string|null $seed, string|null $prefix, bool|false $noret) ``` Gets private key @@ -849,12 +851,14 @@ Gets private key : Seed string in binary format (default: null) * `(string|null) $prefix` : Prefix string in binary format (default: "\0\0\0\0") +* `(bool|false) $noret` +: Do not return the key (default: false) **Return Values** -`string` +`string|bool` -> Private key +> Private key or FALSE on failure or TRUE on noret
@@ -879,9 +883,9 @@ Gets public Key **Return Values** -`string` +`string|false` -> Public key +> Public key or FALSE on failure
@@ -1379,7 +1383,7 @@ Sets an order fee based on matcher settings **Return Values** -`array|bool` +`array|false` > Order as an array or FALSE on failure diff --git a/src/WavesKit.php b/src/WavesKit.php index 7a4fced..0755a03 100644 --- a/src/WavesKit.php +++ b/src/WavesKit.php @@ -9,8 +9,6 @@ use deemru\Pairs; use Composer\CaBundle\CaBundle; -use function deemru\curve25519\rnd; - class WavesKit { private $chainId; @@ -18,18 +16,18 @@ class WavesKit public $logFilter; public $lastLog; - private $sodiumSign; - private $sodiumZero; + private $c25519; /** * Creates WavesKit instance * * @param string $chainId Blockchain identifier (default: 'W') * @param mixed|null $logFunction Log functionality (default: null) + * @param bool $keyCaching Cache key flag (default: false) * * @return void */ - public function __construct( $chainId = 'W', $logFunction = null ) + public function __construct( $chainId = 'W', $logFunction = null, $keyCaching = false ) { $this->chainId = $chainId; if( isset( $logFunction ) ) @@ -42,25 +40,7 @@ public function __construct( $chainId = 'W', $logFunction = null ) $tz = true; } - static $sodiumSign; - if( !isset( $sodiumSign ) ) - { - $sodiumSign = false; - if( function_exists( 'sodium_crypto_sign_seed_keypair' ) ) - { - $seed = hex2bin( '3030303030303030303030303030303030303030303030303030303030303030' ); - $pubkey = hex2bin( '0a8907a1ec72d1b80373cd41e8e4eb5a6a25fdda4ef82be7b635a54700b42289' ); - $pubkey_sodium = substr( sodium_crypto_sign_seed_keypair( $seed ), 32, 32 ); - if( $pubkey === $pubkey_sodium ) - $sodiumSign = true; - } - } - $this->sodiumSign = $sodiumSign; - - static $sodiumZero; - if( !isset( $sodiumZero ) ) - $sodiumZero = function_exists( 'sodium_memzero' ); - $this->sodiumZero = $sodiumZero; + $this->c25519 = new Curve25519( $keyCaching ); } /** @@ -256,42 +236,67 @@ public function blake2b256( $data ) */ public function sign( $data, $key = null ) { - if( isset( $this->rseed ) ) + if( $key === null ) { - $rseed = $this->rseed; - unset( $this->rseed ); - return $this->sign_rseed( $data, $rseed, $key ); - } + if( !isset( $this->rseed ) ) + { + static $sodiumSign; + if( !isset( $sodiumSign ) ) + { + $sodiumSign = false; + if( function_exists( 'sodium_crypto_sign_seed_keypair' ) ) + { + $seed = hex2bin( '3030303030303030303030303030303030303030303030303030303030303030' ); + $pubkey = hex2bin( '0a8907a1ec72d1b80373cd41e8e4eb5a6a25fdda4ef82be7b635a54700b42289' ); + $pubkey_sodium = substr( sodium_crypto_sign_seed_keypair( $seed ), 32, 32 ); + if( $pubkey === $pubkey_sodium ) + $sodiumSign = true; + } + } - if( $this->sodiumSign && $key === null ) - { - if( !isset( $this->privateKeyPair ) ) + if( $sodiumSign ) + { + if( !isset( $this->privateKeyPairCloaked ) ) + { + $this->privateKeyCloaked->uncloak( function( $key ) + { + $keypair = substr( sodium_crypto_sign_seed_keypair( $this->getSodium() ? substr( $this->sha512( $key ), 0, 32 ) : $key ), 0, 64 ); + $this->privateKeyPairBit = ord( $keypair[63] ) & 128; + $this->privateKeyPairCloaked = new Cloaked; + $this->privateKeyPairCloaked->cloak( $keypair ); + } ); + } + + return $this->privateKeyPairCloaked->uncloak( function( $keypair ) use ( $data ) + { + $sig = sodium_crypto_sign_detached( $data, $keypair ); + if( $this->privateKeyPairBit !== 0 ) + $sig[63] = chr( ord( $sig[63] ) | $this->privateKeyPairBit ); + return $sig; + } ); + } + + return $this->privateKeyCloaked->uncloak( function( $key ) use ( $data ) + { + if( $this->getSodium() ) + return $this->c25519->sign_sodium( $data, $key ); + return $this->c25519->sign( $data, $key ); + } ); + } + else // rseed { - $key = $this->getPrivateKey(); - $keypair = substr( sodium_crypto_sign_seed_keypair( $this->getSodium() ? substr( $this->sha512( $key ), 0, 32 ) : $key ), 0, 64 ); - $this->cloakUsed( $key ); - $this->privateKeyPairBit = ord( $keypair[63] ) & 128; - $this->privateKeyPair = $this->cloak( $keypair ); + return $this->privateKeyCloaked->uncloak( function( $key ) use ( $data ) + { + $rseed = $this->rseed; + unset( $this->rseed ); + return $this->c25519->sign( $data, $key, $rseed ); + } ); } - - $keypair = $this->uncloak( $this->privateKeyPair ); - $sig = sodium_crypto_sign_detached( $data, $keypair ); - $this->cloakUsed( $keypair ); - if( $this->privateKeyPairBit !== 0 ) - $sig[63] = chr( ord( $sig[63] ) | $this->privateKeyPairBit ); - return $sig; } - if( $this->getSodium() ) - return $this->sign_sodium( $data, $key ); - - return $this->sign_php( $data, $key ); + return $this->c25519->sign( $data, $key ); } - private function sign_php( $data, $key = null ){ return $this->c25519()->sign( $data, isset( $key ) ? $key : $this->getPrivateKey() ); } - private function sign_sodium( $data, $key = null ){ return $this->c25519()->sign_sodium( $data, isset( $key ) ? $key : $this->getPrivateKey() ); } - private function sign_rseed( $data, $rseed, $key = null ){ return $this->c25519()->sign( $data, isset( $key ) ? $key : $this->getPrivateKey(), $rseed ); } - /** * Verifies a signature of a message by a public key * @@ -301,17 +306,7 @@ private function sign_rseed( $data, $rseed, $key = null ){ return $this->c25519( * * @return bool Returns TRUE if the signature is valid or FALSE on failure */ - public function verify( $sig, $data, $key = null ){ return $this->c25519()->verify( $sig, $data, isset( $key ) ? $key : $this->getPublicKey( true ) ); } - - private function c25519() - { - static $c25519; - - if( !isset( $c25519 ) ) - $c25519 = new Curve25519(); - - return $c25519; - } + public function verify( $sig, $data, $key = null ){ return $this->c25519->verify( $sig, $data, isset( $key ) ? $key : $this->getPublicKey( true ) ); } /** * Generates random seed string @@ -376,8 +371,8 @@ public function isAddressValid( $address, $raw = false ) return true; } - private $privateKey; - private $privateKeyPair; + private $privateKeyCloaked; + private $privateKeyPairCloaked; private $privateKeyPairBit; private $publicKey; private $publicKey58; @@ -388,8 +383,9 @@ private function cleanup( $full = true ) { if( $full ) { - unset( $this->privateKey ); - unset( $this->privateKeyPair ); + unset( $this->privateKeyCloaked ); + unset( $this->privateKeyPairCloaked ); + unset( $this->privateKeyPairBit ); } unset( $this->publicKey ); @@ -398,60 +394,6 @@ private function cleanup( $full = true ) unset( $this->address58 ); } - private $cloakBytes; - private $cloakMap; - - private function cloakInit() - { - if( !isset( $this->cloakMap ) ) - { - for( ;; ) - { - $n = 65536; - $bytes = Cryptash::rnd( $n ); - $map = []; - for( $i = 0; $i < $n; ++$i ) - $map[ord($bytes[$i])][] = $i; - if( count( $map ) === 256 ) - break; - } - - $this->cloakBytes = $bytes; - $this->cloakMap = $map; - } - } - - private function cloak( &$data ) - { - $this->cloakInit(); - - $cloaked = []; - $n = strlen( $data ); - for( $i = 0; $i < $n; ++$i ) - { - $pointers = $this->cloakMap[ord( $data[$i] )]; - $cloaked[] = $pointers[array_rand( $pointers )]; - } - - $this->cloakUsed( $data ); - return $cloaked; - } - - private function uncloak( $cloaked ) - { - $n = count( $cloaked ); - $data = str_pad( '', $n, ' ' ); - for( $i = 0; $i < $n; ++$i ) - $data[$i] = $this->cloakBytes[$cloaked[$i]]; - return $data; - } - - private function cloakUsed( &$data ) - { - if( $this->sodiumZero ) - sodium_memzero( $data ); - } - /** * Sets user seed string * @@ -464,8 +406,7 @@ private function cloakUsed( &$data ) public function setSeed( $seed, $raw = true, $prefix = "\0\0\0\0" ) { $this->cleanup(); - $key = $this->getPrivateKey( true, $raw ? $seed : $this->base58Decode( $seed, false ), $prefix ); - $this->cloakUsed( $key ); + $this->getPrivateKey( true, $raw ? $seed : $this->base58Decode( $seed, false ), $prefix, true ); } /** @@ -480,7 +421,8 @@ public function setPrivateKey( $privateKey, $raw = false ) { $this->cleanup(); $key = $raw ? $privateKey : $this->base58Decode( $privateKey, false ); - $this->privateKey = $this->cloak( $key ); + $this->privateKeyCloaked = new Cloaked; + $this->privateKeyCloaked->cloak( $key ); } /** @@ -489,24 +431,28 @@ public function setPrivateKey( $privateKey, $raw = false ) * @param bool $raw String format is binary or base58 (default: binary) * @param string|null $seed Seed string in binary format (default: null) * @param string|null $prefix Prefix string in binary format (default: "\0\0\0\0") + * @param bool|false $noret Do not return the key (default: false) * - * @return string Private key + * @return string|bool Private key or FALSE on failure or TRUE on noret */ - public function getPrivateKey( $raw = true, $seed = null, $prefix = "\0\0\0\0" ) + public function getPrivateKey( $raw = true, $seed = null, $prefix = "\0\0\0\0", $noret = false ) { - if( !isset( $this->privateKey ) ) + if( !isset( $this->privateKeyCloaked ) ) { if( !isset( $seed ) ) return false; $temp = $prefix . $seed; $temp = $this->secureHash( $temp ); $temp = $this->sha256( $temp ); - $this->privateKey = $this->cloak( $temp ); + $this->privateKeyCloaked = new Cloaked; + $this->privateKeyCloaked->cloak( $temp ); } - if( $raw ) - return $this->uncloak( $this->privateKey ); - return $this->base58Encode( $this->uncloak( $this->privateKey ) ); + if( $noret ) + return true; + + $key = $this->privateKeyCloaked->uncloak( function( $key ){ return $key; } ); + return $raw ? $key : $this->base58Encode( $key ); } /** @@ -528,19 +474,25 @@ public function setPublicKey( $publicKey, $raw = false ) * * @param bool $raw String format is binary or base58 (default: base58) * - * @return string Public key + * @return string|false Public key or FALSE on failure */ public function getPublicKey( $raw = false ) { if( !isset( $this->publicKey ) ) { - $temp = $this->getPrivateKey(); - if( $temp === false || strlen( $temp ) !== 32 ) + if( !isset( $this->privateKeyCloaked ) ) return false; - if( $this->getSodium() ) - $temp = $this->c25519()->getSodiumPrivateKeyFromPrivateKey( $temp ); - $temp = $this->c25519()->getPublicKeyFromPrivateKey( $temp, $this->getLastBitFlip() ); - $this->publicKey = $temp; + + $this->publicKey = $this->privateKeyCloaked->uncloak( function( $key ) + { + $temp = $key; + if( $temp === false || strlen( $temp ) !== 32 ) + return false; + if( $this->getSodium() ) + $temp = $this->c25519->getSodiumPrivateKeyFromPrivateKey( $temp ); + $temp = $this->c25519->getPublicKeyFromPrivateKey( $temp, $this->getLastBitFlip() ); + return $temp; + } ); } if( $raw ) @@ -574,7 +526,7 @@ public function setAddress( $address, $raw = false ) * * @param bool $raw String format is binary or base58 (default: base58) * - * @return string Address + * @return string|false Address or FALSE on failure */ public function getAddress( $raw = false ) { @@ -959,7 +911,7 @@ private function decimalize( $n ) * @param array $order Order as an array * @param bool $discount Use dicount asset (default: true) * - * @return array|bool Order as an array or FALSE on failure + * @return array|false Order as an array or FALSE on failure */ public function setMatcherFee( $order, $discount = true ) {