From f84f6def3187bb39ee38ed18348a7a073fa570ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B0=D0=BC=D0=B8=D0=BB=D1=8C=20=D0=9C=D1=83=D1=85?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=82=D0=B7=D1=8F=D0=BD=D0=BE=D0=B2?= Date: Wed, 10 Jul 2019 12:50:20 +0300 Subject: [PATCH 1/2] fix: ecdsa signature --- src/Minter/Library/ECDSA.php | 6 +++--- src/Minter/Library/Helper.php | 18 ++++++++++++++++++ src/Minter/SDK/MinterCheck.php | 12 ++++++------ src/Minter/SDK/MinterTx.php | 9 ++++----- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/Minter/Library/ECDSA.php b/src/Minter/Library/ECDSA.php index cacf493..a16a0c1 100644 --- a/src/Minter/Library/ECDSA.php +++ b/src/Minter/Library/ECDSA.php @@ -106,9 +106,9 @@ protected static function encodeSign(string $r, string $s, int $recovery): array $s = Helper::padToEven($s); return [ - 'v' => $recovery + self::V_BITS, - 'r' => hex2bin($r), - 's' => hex2bin($s) + 'v' => dechex($recovery + self::V_BITS), + 'r' => $r, + 's' => $s ]; } } diff --git a/src/Minter/Library/Helper.php b/src/Minter/Library/Helper.php index efac585..9e7542b 100644 --- a/src/Minter/Library/Helper.php +++ b/src/Minter/Library/Helper.php @@ -4,6 +4,7 @@ use kornrunner\Keccak; use Minter\SDK\MinterPrefix; +use Web3p\RLP\Buffer; /** * Class Helper @@ -151,4 +152,21 @@ public static function rlpArrayToHexArray(array $rlp): array return self::rlpArrayToHexArray($item); }, $rlp); } + + /** + * Create buffer from data recursively. + * + * @param $data + * @return array|Buffer + */ + public static function hex2buffer($data) + { + if(is_array($data)) { + return array_map(function($item) { + return self::hex2buffer($item); + }, $data); + } + + return new Buffer($data, 'hex'); + } } diff --git a/src/Minter/SDK/MinterCheck.php b/src/Minter/SDK/MinterCheck.php index 10051b6..88134cc 100644 --- a/src/Minter/SDK/MinterCheck.php +++ b/src/Minter/SDK/MinterCheck.php @@ -113,11 +113,11 @@ public function sign(string $privateKey): string $this->structure['lock'] = hex2bin($this->formatLockFromSignature($signature)); // create message hash with lock field - $msgHashWithLock = $this->serialize( - array_slice($this->structure, 0, 6) - ); + $msgHashWithLock = $this->serialize(array_slice($this->structure, 0, 6)); - $this->structure = array_merge($this->structure, ECDSA::sign($msgHashWithLock, $privateKey)); + // create signature + $signature = ECDSA::sign($msgHashWithLock, $privateKey); + $this->structure = array_merge($this->structure, Helper::hex2buffer($signature)); // rlp encode data and add Minter wallet prefix return MinterPrefix::CHECK . $this->rlp->encode($this->structure)->toString('hex'); @@ -297,8 +297,8 @@ protected function validateFields(array $fields): bool */ protected function formatLockFromSignature(array $signature): string { - $recovery = $signature['v'] === ECDSA::V_BITS ? '00' : '01'; + $recovery = hexdec($signature['v']) === ECDSA::V_BITS ? '00' : '01'; - return bin2hex($signature['r']) . bin2hex($signature['s']) . $recovery; + return $signature['r'] . $signature['s'] . $recovery; } } \ No newline at end of file diff --git a/src/Minter/SDK/MinterTx.php b/src/Minter/SDK/MinterTx.php index f1245aa..3f28e07 100644 --- a/src/Minter/SDK/MinterTx.php +++ b/src/Minter/SDK/MinterTx.php @@ -145,19 +145,18 @@ public function sign(string $privateKey): string // encode data array to RPL $tx = $this->txDataRlpEncode($this->tx); - // TODO: temp fix. $tx['payload'] = new Buffer(str_split($tx['payload'], 1)); // create keccak hash from transaction - $keccak = Helper::createKeccakHash( - $this->rlp->encode($tx)->toString('hex') - ); + $keccak = Helper::createKeccakHash($this->rlp->encode($tx)->toString('hex')); // prepare special [V, R, S] signature bytes and add them to transaction + $signature = ECDSA::sign($keccak, $privateKey); $tx['signatureData'] = $this->rlp->encode( - ECDSA::sign($keccak, $privateKey) + Helper::hex2buffer($signature) ); + // pack transaction to hex string $this->txSigned = $this->rlp->encode($tx)->toString('hex'); return MinterPrefix::TRANSACTION . $this->txSigned; From 2cfc2f289c41d44964caef022fa2dd898d8c6098 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B0=D0=BC=D0=B8=D0=BB=D1=8C=20=D0=9C=D1=83=D1=85?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=82=D0=B7=D1=8F=D0=BD=D0=BE=D0=B2?= Date: Wed, 10 Jul 2019 18:08:40 +0300 Subject: [PATCH 2/2] fix check proof, nonce --- src/Minter/Library/Helper.php | 13 ++++- src/Minter/SDK/MinterCheck.php | 47 ++++++++++--------- .../SDK/MinterCoins/MinterBuyCoinTx.php | 4 +- .../SDK/MinterCoins/MinterCreateCoinTx.php | 4 +- .../MinterCoins/MinterDeclareCandidacyTx.php | 2 +- .../SDK/MinterCoins/MinterDelegateTx.php | 2 +- .../SDK/MinterCoins/MinterSellAllCoinTx.php | 4 +- .../SDK/MinterCoins/MinterSellCoinTx.php | 4 +- .../SDK/MinterCoins/MinterSendCoinTx.php | 2 +- src/Minter/SDK/MinterCoins/MinterUnbondTx.php | 2 +- src/Minter/SDK/MinterTx.php | 10 ++-- tests/MinterCheckTest.php | 17 +++---- 12 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/Minter/Library/Helper.php b/src/Minter/Library/Helper.php index 9e7542b..3bbbb99 100644 --- a/src/Minter/Library/Helper.php +++ b/src/Minter/Library/Helper.php @@ -42,7 +42,7 @@ public static function dechex(string $number): string * @param $data * @return string */ - public static function pack2hex(string $data): string + public static function hex2str(string $data): string { return str_replace(chr(0), '', pack('H*', $data)); } @@ -169,4 +169,15 @@ public static function hex2buffer($data) return new Buffer($data, 'hex'); } + + /** + * @param string $str + * @return string + */ + public static function str2hex(string $str): string + { + $str = unpack('H*', $str); + + return array_shift($str); + } } diff --git a/src/Minter/SDK/MinterCheck.php b/src/Minter/SDK/MinterCheck.php index 88134cc..02d993c 100644 --- a/src/Minter/SDK/MinterCheck.php +++ b/src/Minter/SDK/MinterCheck.php @@ -4,6 +4,7 @@ use Minter\Library\ECDSA; use Minter\Library\Helper; +use Web3p\RLP\Buffer; use Web3p\RLP\RLP; /** @@ -100,9 +101,7 @@ public function getOwnerAddress(): string public function sign(string $privateKey): string { // create message hash and passphrase by first 4 fields - $msgHash = $this->serialize( - array_slice($this->structure, 0, 5) - ); + $msgHash = $this->serialize(array_slice($this->structure, 0, 5)); $passphrase = hash('sha256', $this->passphrase); @@ -159,15 +158,16 @@ protected function decode(string $check): array $check = Helper::removePrefix($check, MinterPrefix::CHECK); $check = $this->rlp->decode('0x' . $check); $check = Helper::rlpArrayToHexArray($check); - + // prepare decoded data $data = []; foreach ($check as $key => $value) { $field = $this->structure[$key]; switch ($field) { + case 'nonce': case 'coin': - $data[$field] = Helper::pack2hex($value); + $data[$field] = Helper::hex2str($value); break; case 'value': @@ -176,20 +176,18 @@ protected function decode(string $check): array default: $data[$field] = $value; - if(in_array($field, ['dueBlock', 'nonce', 'v', 'chainId'])) { + if(in_array($field, ['dueBlock', 'v', 'chainId'])) { $data[$field] = hexdec($value); } break; } } - $structure = array_flip($this->structure); - // set owner address - list($body, $signature) = array_chunk($check, 6); + list($body, $signature) = array_chunk($data, 6, true); $this->setOwnerAddress($body, $signature); - return array_merge($structure, $data); + return $data; } /** @@ -200,19 +198,16 @@ protected function decode(string $check): array */ protected function setOwnerAddress(array $body, array $signature): void { - // convert to binary - $data = Helper::hex2binRecursive($body); - - // create keccak hash from transaction - $msg = Helper::createKeccakHash( - $this->rlp->encode($data)->toString('hex') - ); + // encode check to rlp + $lock = array_pop($body); + $check = $this->encode($body); + $check['lock'] = hex2bin($lock); - list($v, $r, $s) = $signature; - $v = hexdec($v); + // create keccak hash from check + $msg = $this->serialize($check); // recover public key - $publicKey = ECDSA::recover($msg, $r, $s, $v); + $publicKey = ECDSA::recover($msg, $signature['r'], $signature['s'], $signature['v']); $publicKey = MinterPrefix::PUBLIC_KEY . $publicKey; $this->minterAddress = MinterWallet::getAddressFromPublicKey($publicKey); @@ -245,7 +240,9 @@ protected function defineProperties(array $check): array protected function encode(array $check): array { return [ - 'nonce' => dechex($check['nonce']), + 'nonce' => Helper::hexDecode( + Helper::str2hex($check['nonce']) + ), 'chainId' => dechex($check['chainId']), @@ -284,6 +281,10 @@ protected function validateFields(array $fields): bool if(!isset($structure[$field])) { return false; } + + if($field === 'nonce' && strlen($fieldValue) > 32) { + return false; + } } return true; @@ -297,8 +298,10 @@ protected function validateFields(array $fields): bool */ protected function formatLockFromSignature(array $signature): string { + $r = str_pad($signature['r'], 64, '0', STR_PAD_LEFT); + $s = str_pad($signature['s'], 64, '0', STR_PAD_LEFT); $recovery = hexdec($signature['v']) === ECDSA::V_BITS ? '00' : '01'; - return $signature['r'] . $signature['s'] . $recovery; + return $r . $s. $recovery; } } \ No newline at end of file diff --git a/src/Minter/SDK/MinterCoins/MinterBuyCoinTx.php b/src/Minter/SDK/MinterCoins/MinterBuyCoinTx.php index 2bedbf4..9d93b66 100644 --- a/src/Minter/SDK/MinterCoins/MinterBuyCoinTx.php +++ b/src/Minter/SDK/MinterCoins/MinterBuyCoinTx.php @@ -66,13 +66,13 @@ public function decode(array $txData): array { return [ // Pack symbol - 'coinToBuy' => Helper::pack2hex($txData[0]), + 'coinToBuy' => Helper::hex2str($txData[0]), // Convert field from PIP to BIP 'valueToBuy' => MinterConverter::convertValue(Helper::hexDecode($txData[1]), 'bip'), // Pack symbol - 'coinToSell' => Helper::pack2hex($txData[2]), + 'coinToSell' => Helper::hex2str($txData[2]), // Convert field from PIP to BIP 'maximumValueToSell' => MinterConverter::convertValue(Helper::hexDecode($txData[3]), 'bip') diff --git a/src/Minter/SDK/MinterCoins/MinterCreateCoinTx.php b/src/Minter/SDK/MinterCoins/MinterCreateCoinTx.php index 86a25ed..2e7a7e4 100644 --- a/src/Minter/SDK/MinterCoins/MinterCreateCoinTx.php +++ b/src/Minter/SDK/MinterCoins/MinterCreateCoinTx.php @@ -70,10 +70,10 @@ public function decode(array $txData): array { return [ // Pack name - 'name' => Helper::pack2hex($txData[0]), + 'name' => Helper::hex2str($txData[0]), // Pack symbol - 'symbol' => Helper::pack2hex($txData[1]), + 'symbol' => Helper::hex2str($txData[1]), // Convert field from PIP to BIP 'initialAmount' => MinterConverter::convertValue(Helper::hexDecode($txData[2]), 'bip'), diff --git a/src/Minter/SDK/MinterCoins/MinterDeclareCandidacyTx.php b/src/Minter/SDK/MinterCoins/MinterDeclareCandidacyTx.php index a418847..5e5163b 100644 --- a/src/Minter/SDK/MinterCoins/MinterDeclareCandidacyTx.php +++ b/src/Minter/SDK/MinterCoins/MinterDeclareCandidacyTx.php @@ -84,7 +84,7 @@ public function decode(array $txData): array 'commission' => Helper::hexDecode($txData[2]), // Pack coin name - 'coin' => Helper::pack2hex($txData[3]), + 'coin' => Helper::hex2str($txData[3]), // Convert stake from PIP to BIP 'stake' => MinterConverter::convertValue(Helper::hexDecode($txData[4]), 'bip') diff --git a/src/Minter/SDK/MinterCoins/MinterDelegateTx.php b/src/Minter/SDK/MinterCoins/MinterDelegateTx.php index 9fa21e8..adb174f 100644 --- a/src/Minter/SDK/MinterCoins/MinterDelegateTx.php +++ b/src/Minter/SDK/MinterCoins/MinterDelegateTx.php @@ -68,7 +68,7 @@ public function decode(array $txData): array 'pubkey' => MinterPrefix::PUBLIC_KEY . $txData[0], // Pack coin name - 'coin' => Helper::pack2hex($txData[1]), + 'coin' => Helper::hex2str($txData[1]), // Convert stake from PIP to BIP 'stake' => MinterConverter::convertValue(Helper::hexDecode($txData[2]), 'bip') diff --git a/src/Minter/SDK/MinterCoins/MinterSellAllCoinTx.php b/src/Minter/SDK/MinterCoins/MinterSellAllCoinTx.php index 245f1d9..e912dab 100644 --- a/src/Minter/SDK/MinterCoins/MinterSellAllCoinTx.php +++ b/src/Minter/SDK/MinterCoins/MinterSellAllCoinTx.php @@ -62,10 +62,10 @@ public function decode(array $txData): array { return [ // Pack symbol - 'coinToSell' => Helper::pack2hex($txData[0]), + 'coinToSell' => Helper::hex2str($txData[0]), // Pack symbol - 'coinToBuy' => Helper::pack2hex($txData[1]), + 'coinToBuy' => Helper::hex2str($txData[1]), // Convert field from PIP to BIP 'minimumValueToBuy' => MinterConverter::convertValue(Helper::hexDecode($txData[2]), 'bip') diff --git a/src/Minter/SDK/MinterCoins/MinterSellCoinTx.php b/src/Minter/SDK/MinterCoins/MinterSellCoinTx.php index 7d785a8..38c6d1a 100644 --- a/src/Minter/SDK/MinterCoins/MinterSellCoinTx.php +++ b/src/Minter/SDK/MinterCoins/MinterSellCoinTx.php @@ -66,13 +66,13 @@ public function decode(array $txData): array { return [ // Pack symbol - 'coinToSell' => Helper::pack2hex($txData[0]), + 'coinToSell' => Helper::hex2str($txData[0]), // Convert field from PIP to BIP 'valueToSell' => MinterConverter::convertValue(Helper::hexDecode($txData[1]), 'bip'), // Pack symbol - 'coinToBuy' => Helper::pack2hex($txData[2]), + 'coinToBuy' => Helper::hex2str($txData[2]), // Convert field from PIP to BIP 'minimumValueToBuy' => MinterConverter::convertValue(Helper::hexDecode($txData[3]), 'bip') diff --git a/src/Minter/SDK/MinterCoins/MinterSendCoinTx.php b/src/Minter/SDK/MinterCoins/MinterSendCoinTx.php index 822acc6..399bc30 100644 --- a/src/Minter/SDK/MinterCoins/MinterSendCoinTx.php +++ b/src/Minter/SDK/MinterCoins/MinterSendCoinTx.php @@ -64,7 +64,7 @@ public function decode(array $txData): array { return [ // Pack binary to string - 'coin' => Helper::pack2hex($txData[0]), + 'coin' => Helper::hex2str($txData[0]), // Add Minter wallet prefix to string 'to' => Helper::addWalletPrefix($txData[1]), diff --git a/src/Minter/SDK/MinterCoins/MinterUnbondTx.php b/src/Minter/SDK/MinterCoins/MinterUnbondTx.php index 37f726a..849d130 100644 --- a/src/Minter/SDK/MinterCoins/MinterUnbondTx.php +++ b/src/Minter/SDK/MinterCoins/MinterUnbondTx.php @@ -68,7 +68,7 @@ public function decode(array $txData): array 'pubkey' => MinterPrefix::PUBLIC_KEY . $txData[0], // Pack binary to string - 'coin' => Helper::pack2hex($txData[1]), + 'coin' => Helper::hex2str($txData[1]), // Convert value from PIP to BIP 'value' => MinterConverter::convertValue(Helper::hexDecode($txData[2]), 'bip') diff --git a/src/Minter/SDK/MinterTx.php b/src/Minter/SDK/MinterTx.php index 3f28e07..e560a56 100644 --- a/src/Minter/SDK/MinterTx.php +++ b/src/Minter/SDK/MinterTx.php @@ -148,7 +148,9 @@ public function sign(string $privateKey): string $tx['payload'] = new Buffer(str_split($tx['payload'], 1)); // create keccak hash from transaction - $keccak = Helper::createKeccakHash($this->rlp->encode($tx)->toString('hex')); + $keccak = Helper::createKeccakHash( + $this->rlp->encode($tx)->toString('hex') + ); // prepare special [V, R, S] signature bytes and add them to transaction $signature = ECDSA::sign($keccak, $privateKey); @@ -348,16 +350,16 @@ protected function prepareResult(array $tx): array break; case 'payload': - $result[$field] = Helper::pack2hex($tx[$key]); + $result[$field] = Helper::hex2str($tx[$key]); break; case 'serviceData': - $result[$field] = Helper::pack2hex($tx[$key]); + $result[$field] = Helper::hex2str($tx[$key]); break; case 'gasCoin': $result[$field] = MinterConverter::convertCoinName( - Helper::pack2hex($tx[$key]) + Helper::hex2str($tx[$key]) ); break; diff --git a/tests/MinterCheckTest.php b/tests/MinterCheckTest.php index 2df23e0..a2de894 100644 --- a/tests/MinterCheckTest.php +++ b/tests/MinterCheckTest.php @@ -28,7 +28,7 @@ final class MinterCheckTest extends TestCase /** * Predefined valid check string */ - CONST VALID_CHECK = 'Mcf8a00102830f423f8a4d4e5400000000000000888ac7230489e80000b8419200e3c947484ced3268eebd1810d640ac0d6c6a099e4d87e074bab6a5751a324540e1e53907a10c9fb73f944490a737034de4a8bae96e707b5acbf8015dd8cb001ba0cbbc87bc7018f2c3bcaea67968713389addc3bf72f698b8b44ffddc384fca230a07ff35524aaca365fdac2eb25d29e9ba8431484fcb2b890d6d940d2527daeca22'; + CONST VALID_CHECK = 'Mcf8a38334383002830f423f8a4d4e5400000000000000888ac7230489e80000b841d184caa333fe636288fc68d99dea2c8af5f7db4569a0bb91e03214e7e238f89d2b21f4d2b730ef590fd8de72bd43eb5c6265664df5aa3610ef6c71538d9295ee001ba08bd966fc5a093024a243e62cdc8131969152d21ee9220bc0d95044f54e3dd485a033bc4e03da3ea8a2cd2bd149d16c022ee604298575380db8548b4fd6672a9195'; /** * Predefined valid proof @@ -41,7 +41,7 @@ final class MinterCheckTest extends TestCase public function testSignCheck() { $check = new MinterCheck([ - 'nonce' => 1, + 'nonce' => 480, 'chainId' => MinterTx::TESTNET_CHAIN_ID, 'dueBlock' => 999999, 'coin' => 'MNT', @@ -59,10 +59,11 @@ public function testSignCheck() public function testCreateProof() { $check = new MinterCheck(self::ADDRESS, self::PASSPHRASE); - $proof = $check->createProof(); - $this->assertSame(self::VALID_PROOF, $proof); + + $proof = (new MinterCheck('Mx41f3e5c369c8c874181b119637f1330acd08fa9d', 'Hello moto'))->createProof(); + $this->assertSame('ebe0562d0896e7ef4d0afd6d6fe80919a449dddf7f2c3fdd2a714bb39071ba68004f52ae2b4f995d24fd8be9808783330ed94bf02871f80eccb88ab3f3095b5d00', $proof); } /** @@ -73,15 +74,15 @@ public function testDecodeCheck() $check = new MinterCheck(self::VALID_CHECK); $this->assertSame([ - 'nonce' => 1, + 'nonce' => '480', 'chainId' => MinterTx::TESTNET_CHAIN_ID, 'dueBlock' => 999999, 'coin' => 'MNT', 'value' => '10', - 'lock' => '9200e3c947484ced3268eebd1810d640ac0d6c6a099e4d87e074bab6a5751a324540e1e53907a10c9fb73f944490a737034de4a8bae96e707b5acbf8015dd8cb00', + 'lock' => 'd184caa333fe636288fc68d99dea2c8af5f7db4569a0bb91e03214e7e238f89d2b21f4d2b730ef590fd8de72bd43eb5c6265664df5aa3610ef6c71538d9295ee00', 'v' => 27, - 'r' => 'cbbc87bc7018f2c3bcaea67968713389addc3bf72f698b8b44ffddc384fca230', - 's' => '7ff35524aaca365fdac2eb25d29e9ba8431484fcb2b890d6d940d2527daeca22' + 'r' => '8bd966fc5a093024a243e62cdc8131969152d21ee9220bc0d95044f54e3dd485', + 's' => '33bc4e03da3ea8a2cd2bd149d16c022ee604298575380db8548b4fd6672a9195' ], $check->getBody()); $this->assertSame('Mxce931863b9c94a526d94acd8090c1c5955a6eb4b', $check->getOwnerAddress());