diff --git a/README.md b/README.md
index 7712a94..4cc852f 100644
--- a/README.md
+++ b/README.md
@@ -306,7 +306,7 @@ $tx = new MinterTx([
$tx->sign('your private key')
```
-At all type of transactions you can also specify:
+At all type of transactions you can also pass:
gasPrice, gasCoin, payload, serviceData
```php
@@ -625,7 +625,7 @@ Returns a signed tx.
###### Example
* To sign transaction with multisignatures, you need to call signMultisig method
-and specify multisig Minter address and his private keys (in any order).
+and pass multisig Minter address and his private keys (in any order).
```php
use Minter\SDK\MinterTx;
@@ -649,6 +649,58 @@ $signedTx = $tx->signMultisig('Mxdb4f4b6942cb927e8d7e3a1f602d0f1fb43b5bd2', [
])
```
+###### Example
+
+* To get the signature of transaction (not signed transaction)
+you need to call createSignature
+
+```php
+use Minter\SDK\MinterTx;
+use Minter\SDK\MinterCoins\MinterSendCoinTx;
+
+$tx = new MinterTx([
+ 'nonce' => $nonce,
+ 'chainId' => MinterTx::MAINNET_CHAIN_ID, // or MinterTx::TESTNET_CHAIN_ID
+ 'type' => MinterSendCoinTx::TYPE,
+ 'data' => [
+ 'coin' => 'MNT',
+ 'to' => 'Mxfe60014a6e9ac91618f5d1cab3fd58cded61ee99',
+ 'value' => '10'
+ ]
+]);
+
+$txSignature = $tx->createSignature($privateKey);
+```
+
+###### Example
+
+* To sign transaction with ready signatures, you need to call signMultisigBySigns method
+and pass multisig Minter address and your signatures (in any order).
+
+```php
+use Minter\SDK\MinterTx;
+use Minter\SDK\MinterCoins\MinterSendCoinTx;
+
+$tx = new MinterTx([
+ 'nonce' => $nonce,
+ 'chainId' => MinterTx::MAINNET_CHAIN_ID, // or MinterTx::TESTNET_CHAIN_ID
+ 'type' => MinterSendCoinTx::TYPE,
+ 'data' => [
+ 'coin' => 'MNT',
+ 'to' => 'Mxfe60014a6e9ac91618f5d1cab3fd58cded61ee99',
+ 'value' => '10'
+ ]
+]);
+
+$signature1 = $tx->createSignature($privateKey1);
+$signature2 = $tx->createSignature($privateKey2);
+$signature3 = $tx->createSignature($privateKey3);
+
+$signedTx = $tx->signMultisigBySigns('Mxdb4f4b6942cb927e8d7e3a1f602d0f1fb43b5bd2', [
+ $signature1, $signature2, $signature3
+])
+```
+
### Get fee of transaction
* Calculate fee of transaction. You can get fee AFTER signing or decoding transaction.
diff --git a/src/Minter/Library/Helper.php b/src/Minter/Library/Helper.php
index 66944ad..1fd9707 100644
--- a/src/Minter/Library/Helper.php
+++ b/src/Minter/Library/Helper.php
@@ -127,7 +127,6 @@ public static function padToEven(string $hexString): string
*
* @param array $tx
* @return string
- * @throws \Exception
*/
public static function createKeccakHash(string $dataString): string
{
diff --git a/src/Minter/SDK/MinterTx.php b/src/Minter/SDK/MinterTx.php
index d175e18..ad7eb26 100644
--- a/src/Minter/SDK/MinterTx.php
+++ b/src/Minter/SDK/MinterTx.php
@@ -100,7 +100,6 @@ class MinterTx
/**
* MinterTx constructor.
* @param $tx
- * @throws \Exception
*/
public function __construct($tx)
{
@@ -139,7 +138,6 @@ public function __get($name)
*
* @param array $tx
* @return string
- * @throws \Exception
*/
public function getSenderAddress(array $tx): string
{
@@ -153,27 +151,18 @@ public function getSenderAddress(array $tx): string
*
* @param string $privateKey
* @return string
- * @throws \Exception
*/
public function sign(string $privateKey): string
{
- // encode data array to RPL
$this->tx['signatureType'] = self::SIGNATURE_SINGLE_TYPE;
- $tx = $this->txDataRlpEncode($this->tx);
- $tx['payload'] = Helper::str2buffer($tx['payload']);
- // create keccak hash from transaction
- $keccak = Helper::createKeccakHash(
- $this->rlp->encode($tx)->toString('hex')
- );
+ $tx = $this->encodeTxToRlp($this->tx);
+ $hash = $this->createTxHash($tx);
- // prepare special [V, R, S] signature bytes and add them to transaction
- $signature = ECDSA::sign($keccak, $privateKey);
- $tx['signatureData'] = $this->rlp->encode(
- Helper::hex2buffer($signature)
- );
+ $signature = ECDSA::sign($hash, $privateKey);
+ $signature = Helper::hex2buffer($signature);
- // pack transaction to hex string
+ $tx['signatureData'] = $this->rlp->encode($signature);
$this->txSigned = $this->rlp->encode($tx)->toString('hex');
return MinterPrefix::TRANSACTION . $this->txSigned;
@@ -185,48 +174,92 @@ public function sign(string $privateKey): string
* @param string $multisigAddress
* @param array $privateKeys
* @return string
- * @throws Exception
*/
public function signMultisig(string $multisigAddress, array $privateKeys): string
{
- // encode data array to RPL
$this->tx['signatureType'] = self::SIGNATURE_MULTI_TYPE;
- $tx = $this->txDataRlpEncode($this->tx);
- $tx['payload'] = Helper::str2buffer($tx['payload']);
- // create keccak hash from transaction
- $keccak = Helper::createKeccakHash(
- $this->rlp->encode($tx)->toString('hex')
- );
+ $tx = $this->encodeTxToRlp($this->tx);
+ $hash = $this->createTxHash($tx);
$signatures = [];
foreach ($privateKeys as $privateKey) {
- $signature = ECDSA::sign($keccak, $privateKey);
+ $signature = ECDSA::sign($hash, $privateKey);
$signatures[] = Helper::hex2buffer($signature);
}
- $multisigAddress = hex2bin(Helper::removeWalletPrefix($multisigAddress));
+ $multisigAddress = Helper::removeWalletPrefix($multisigAddress);
+ $multisigAddress = hex2bin($multisigAddress);
+
$tx['signatureData'] = $this->rlp->encode([$multisigAddress, $signatures]);
+ $this->txSigned = $this->rlp->encode($tx)->toString('hex');
+
+ return MinterPrefix::TRANSACTION . $this->txSigned;
+ }
+
+ /**
+ * @param string $multisigAddress
+ * @param array $signatures
+ * @return string
+ */
+ public function signMultisigBySigns(string $multisigAddress, array $signatures): string
+ {
+ $this->tx['signatureType'] = self::SIGNATURE_MULTI_TYPE;
+ $tx = $this->encodeTxToRlp($this->tx);
+
+ foreach ($signatures as $key => $signature) {
+ $signatures[$key] = $this->rlp->decode('0x' . $signature);
+ }
+
+ $multisigAddress = Helper::removeWalletPrefix($multisigAddress);
+ $multisigAddress = hex2bin($multisigAddress);
- // pack transaction to hex string
+ $tx['signatureData'] = $this->rlp->encode([$multisigAddress, $signatures]);
$this->txSigned = $this->rlp->encode($tx)->toString('hex');
return MinterPrefix::TRANSACTION . $this->txSigned;
}
+ /**
+ * @param array $tx
+ * @return string
+ */
+ protected function createTxHash(array $tx): string
+ {
+ return Helper::createKeccakHash(
+ $this->rlp->encode($tx)->toString('hex')
+ );
+ }
+
+ /**
+ * @param string $privateKey
+ * @return string
+ */
+ public function createSignature(string $privateKey): string
+ {
+ $tx = $this->encodeTxToRlp($this->tx);
+ $tx['signatureType'] = $this->tx['signatureType'] ?? self::SIGNATURE_MULTI_TYPE;
+
+ $hash = $this->createTxHash($tx);
+ $signature = ECDSA::sign($hash, $privateKey);
+ $signature = Helper::hex2buffer($signature);
+ $signature = $this->rlp->encode($signature)->toString('hex');
+
+ return $signature;
+ }
+
/**
* Recover public key
*
* @param array $tx
* @return string
- * @throws \Exception
*/
public function recoverPublicKey(array $tx): string
{
// prepare short transaction
$shortTx = array_diff_key($tx, ['signatureData' => '']);
$shortTx = Helper::hex2binRecursive($shortTx);
- $shortTx = $this->txDataRlpEncode($shortTx);
+ $shortTx = $this->encodeTxToRlp($shortTx);
// create kessak hash from transaction
$msg = Helper::createKeccakHash(
@@ -244,7 +277,6 @@ public function recoverPublicKey(array $tx): string
* Get hash of transaction
*
* @return string
- * @throws Exception
*/
public function getHash(): string
{
@@ -263,7 +295,6 @@ public function getHash(): string
* Get fee of transaction in PIP
*
* @return string
- * @throws \Exception
*/
public function getFee(): string
{
@@ -291,7 +322,6 @@ public function getFee(): string
*
* @param string $tx
* @return array
- * @throws \Exception
*/
protected function decode(string $tx): array
{
@@ -315,7 +345,6 @@ protected function decode(string $tx): array
* @param bool $isHexFormat
* @return array
* @throws InvalidArgumentException
- * @throws Exception
*/
protected function encode(array $tx, bool $isHexFormat = false): array
{
@@ -402,7 +431,6 @@ protected function encode(array $tx, bool $isHexFormat = false): array
*
* @param array $tx
* @return array
- * @throws \Exception
*/
protected function prepareResult(array $tx): array
{
@@ -468,8 +496,9 @@ protected function rlpToHex(string $data): array
* @param array $tx
* @return array
*/
- protected function txDataRlpEncode(array $tx): array
+ protected function encodeTxToRlp(array $tx): array
{
+ $tx['payload'] = Helper::str2buffer($tx['payload']);
$tx['gasCoin'] = MinterConverter::convertCoinName($tx['gasCoin']);
$tx['data'] = $this->rlp->encode($tx['data']);
diff --git a/tests/MinterMultisigTxTest.php b/tests/MinterMultisigTxTest.php
index 61a2796..92508a7 100644
--- a/tests/MinterMultisigTxTest.php
+++ b/tests/MinterMultisigTxTest.php
@@ -53,8 +53,8 @@ final class MinterMultisigTxTest extends TestCase
public function testSign()
{
$tx = new MinterTx(self::TX);
- $signature = $tx->signMultisig(self::SENDER_ADDRESS, self::PRIVATE_KEYS);
- $this->assertEquals(self::VALID_TX, $signature);
+ $signedTx = $tx->signMultisig(self::SENDER_ADDRESS, self::PRIVATE_KEYS);
+ $this->assertEquals(self::VALID_TX, $signedTx);
}
/**
@@ -66,4 +66,20 @@ public function testDecode()
$this->assertEquals(self::TX['data'], $tx->data);
$this->assertEquals(self::SENDER_ADDRESS, $tx->from);
}
+
+ /**
+ * Test sign multisig with ready signatures.
+ */
+ public function testSignBySignatures()
+ {
+ $tx = new MinterTx(self::TX);
+
+ $signatures = [];
+ foreach (self::PRIVATE_KEYS as $privateKey) {
+ $signatures[] = $tx->createSignature($privateKey);
+ }
+
+ $signedTx = $tx->signMultisigBySigns(self::SENDER_ADDRESS, $signatures);
+ $this->assertEquals(self::VALID_TX, $signedTx);
+ }
}