Skip to content

Commit

Permalink
getUnsigned impl, expose hash (#16)
Browse files Browse the repository at this point in the history
* change hash method to public (return txid)
add getUnsigned method (return unsigned transaction)

* Transaction: PSR and consistency with arguments

* EIP1559Transaction: PSR and consistency with arguments

* U-Tests: PSR, isolate and tidy

---------

Co-authored-by: Boris Momčilović <boris@firstbeatmedia.com>
  • Loading branch information
selaz and kornrunner authored Jul 23, 2024
1 parent 50051a6 commit 1780e62
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 53 deletions.
30 changes: 26 additions & 4 deletions src/Ethereum/EIP1559Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public function getRaw(string $privateKey, int $chainId = 0): string
throw new RuntimeException('Incorrect private key');
}

$this->sign($privateKey);
$this->sign($privateKey, $chainId);

return $this->serialize();
}
Expand All @@ -79,9 +79,26 @@ private function serialize(): string
return $this->txType . $this->RLPencode($this->getInput());
}

private function sign(string $privateKey): void
public function getUnsigned(int $chainId = 0): string
{
$hash = $this->hash();
if ($chainId < 0) {
throw new RuntimeException('ChainID must be positive');
}

$this->chainId = dechex($chainId);

$input = $this->getInput();

unset($input['v']);
unset($input['r']);
unset($input['s']);

return $this->txType . $this->RLPencode($input);
}

private function sign(string $privateKey, int $chainId = 0): void
{
$hash = $this->hash($chainId);

$secp256k1 = new Secp256k1();
/**
Expand All @@ -93,8 +110,13 @@ private function sign(string $privateKey): void
$this->v = dechex((int)$signed->getRecoveryParam());
}

private function hash(): string
public function hash(int $chainId = 0): string
{
if ($chainId < 0) {
throw new RuntimeException('ChainID must be positive');
}

$this->chainId = dechex($chainId);
$input = $this->getInput();

unset($input['v']);
Expand Down
41 changes: 33 additions & 8 deletions src/Ethereum/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class Transaction {
protected $s = '';
protected $v = '';

public function __construct(string $nonce = '', string $gasPrice = '', string $gasLimit = '', string $to = '', string $value = '', string $data = '') {
public function __construct(string $nonce = '', string $gasPrice = '', string $gasLimit = '', string $to = '', string $value = '', string $data = '')
{
$this->nonce = $nonce;
$this->gasPrice = $gasPrice;
$this->gasLimit = $gasLimit;
Expand All @@ -28,7 +29,8 @@ public function __construct(string $nonce = '', string $gasPrice = '', string $g
$this->data = $data;
}

public function getInput(): array {
public function getInput(): array
{
return [
'nonce' => $this->nonce,
'gasPrice' => $this->gasPrice,
Expand All @@ -42,7 +44,8 @@ public function getInput(): array {
];
}

public function getRaw(string $privateKey, int $chainId = 0): string {
public function getRaw(string $privateKey, int $chainId = 0): string
{
if ($chainId < 0) {
throw new RuntimeException('ChainID must be positive');
}
Expand All @@ -60,11 +63,30 @@ public function getRaw(string $privateKey, int $chainId = 0): string {
return $this->serialize();
}

private function serialize(): string {
private function serialize(): string
{
return $this->RLPencode($this->getInput());
}

private function sign(string $privateKey, int $chainId): void {
public function getUnsigned(int $chainId = 0): string
{
$input = $this->getInput();

if ($chainId > 0) {
$input['v'] = dechex($chainId);
$input['r'] = '';
$input['s'] = '';
} else {
unset($input['v']);
unset($input['r']);
unset($input['s']);
}

return $this->RLPencode($input);
}

private function sign(string $privateKey, int $chainId = 0): void
{
$hash = $this->hash($chainId);

$secp256k1 = new Secp256k1();
Expand All @@ -78,7 +100,8 @@ private function sign(string $privateKey, int $chainId): void {
$this->v = dechex ((int) $signed->getRecoveryParam () + 27 + ($chainId ? $chainId * 2 + 8 : 0));
}

private function hash(int $chainId): string {
public function hash(int $chainId = 0): string
{
$input = $this->getInput();

if ($chainId > 0) {
Expand All @@ -96,7 +119,8 @@ private function hash(int $chainId): string {
return Keccak::hash(hex2bin($encoded), 256);
}

private function RLPencode(array $input): string {
private function RLPencode(array $input): string
{
$rlp = new RLP;

$data = [];
Expand All @@ -108,7 +132,8 @@ private function RLPencode(array $input): string {
return $rlp->encode($data);
}

private function hexup(string $value): string {
private function hexup(string $value): string
{
return strlen ($value) % 2 === 0 ? $value : "0{$value}";
}

Expand Down
100 changes: 100 additions & 0 deletions test/EIP1559TransactionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

namespace kornrunner;

use kornrunner\Ethereum\EIP1559Transaction;
use PHPUnit\Framework\TestCase;
use RuntimeException;

class EIP1559TransactionTest extends TestCase
{

/**
* @dataProvider input
*/
public function testGetInput ($expect, $nonce, $gasPrice, $gasLimit, $to, $value, $data)
{
$transaction = new EIP1559Transaction($nonce, $gasPrice, $gasLimit, $to, $value, $data);
$this->assertSame($expect, $transaction->getInput());
}

public static function input (): array {
return [
[
['chainId' => null, 'nonce' => '', 'maxPriorityFeePerGas' => '', 'maxFeePerGas' => '', 'gasLimit' => '', 'to' => '', 'value' => '', 'data' => '', 'accessList' => [], 'v' => '', 'r' => '', 's' => ''],
'', '', '', '', '', ''
],
[
['chainId' => null, 'nonce' => '04', 'maxPriorityFeePerGas' => '03f5476a00', 'maxFeePerGas' => '027f4b', 'gasLimit' => '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', 'to' => '2a45907d1bef7c00', 'value' => '', 'data' => '', 'accessList' => [], 'v' => '', 'r' => '', 's' => ''],
'04', '03f5476a00', '027f4b', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '2a45907d1bef7c00', ''
],
];
}

/**
* @dataProvider getTransactionData
*/
public function testGetRaw($txid, $unsigned, $signed, $privateKey, $chainId, $nonce, $maxPriorityFeePerGas, $maxFeePerGas, $gasLimit, $to, $value, $data)
{
$transaction = new EIP1559Transaction($nonce, $maxPriorityFeePerGas, $maxFeePerGas, $gasLimit, $to, $value, $data);
$this->assertSame($signed, $transaction->getRaw($privateKey, $chainId));
}

/**
* @dataProvider getTransactionData
*/
public function testGetUnsigned($txid, $unsigned, $signed, $privateKey, $chainId, $nonce, $maxPriorityFeePerGas, $maxFeePerGas, $gasLimit, $to, $value, $data)
{
$transaction = new EIP1559Transaction($nonce, $maxPriorityFeePerGas, $maxFeePerGas, $gasLimit, $to, $value, $data);
$this->assertSame($unsigned, $transaction->getUnsigned($chainId));
}

/**
* @dataProvider getTransactionData
*/
public function testHash($txid, $unsigned, $signed, $privateKey, $chainId, $nonce, $maxPriorityFeePerGas, $maxFeePerGas, $gasLimit, $to, $value, $data)
{
$transaction = new EIP1559Transaction($nonce, $maxPriorityFeePerGas, $maxFeePerGas, $gasLimit, $to, $value, $data);
$this->assertSame($txid, $transaction->hash($chainId));
}

public static function getTransactionData(): array
{
return [
[
'4fa4b9bd743ee70bb1dc109cadaa5985d5e9461e793405261daeff7bb6419865', //txid
'02f0010284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c000080c0', //unsigned
'02f873010284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c000080c080a0dd32dc794af9a9085d6772c40656fc156a577570c6fd32f2a2d4126673373919a066cf1859672a9e6fdbb00ebd230bcfec66cac1c99f5c83598ecae6025d0e91f4',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 1, '02', 'b2d05e00', '6fc23ac00', '5208', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '29a2241af62c0000', ''
],
[
'f470e5422f3dc01bca43f818f03de3eea876328f8a7d1c9c4fe05410f6fc3bb6', //txid
'02f0380284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c000080c0', //unsigned
'02f873380284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c000080c080a0b8ec74b95d0d8ebe4b4c7e9d5a8af16e06a7d636d20b2c632633a2abe3a0f5cca005971b1f3ec9b498d8d7b17b312477d0b1fd2232ca4b82e9c34fc43d33e5f196',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 56, '02', 'b2d05e00', '6fc23ac00', '5208', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '29a2241af62c0000', ''
],
[
'950b552d191d26ea7b2dbaf53e59b2f82177b1924f8ac06715a0fe0f058f1c20', //txid
'02f4380284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c00008401232213c0', //unsigned
'02f877380284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c00008401232213c001a05bc0caa25dd8e23adf3f79f8dbe1237e0f22980a3b15f00bbe9f5a19dd23a70ca002e9a353c9b155b0e7e796b5f966bc6dd53deafea9990398a069d93278e50644',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 56, '02', 'b2d05e00', '6fc23ac00', '5208', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '29a2241af62c0000', '1232213'
]
];
}

public function testGetRawBadChainId()
{
$this->expectException(RuntimeException::class);
$transaction = new EIP1559Transaction('04', '03f5476a00', '027f4b', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '2a45907d1bef7c00', '');
$transaction->getRaw ('b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', -1);
}

public function testBadPrivateKey()
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Incorrect private key');
$transaction = new EIP1559Transaction();
$transaction->getRaw('');
}

}
87 changes: 46 additions & 41 deletions test/TransactionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@

namespace kornrunner;

use kornrunner\Ethereum\EIP1559Transaction;
use kornrunner\Ethereum\Transaction;
use PHPUnit\Framework\TestCase;
use RuntimeException;

class TransactionTest extends TestCase {
class TransactionTest extends TestCase
{

/**
* @dataProvider input
*/
public function testGetInput ($expect, $nonce, $gasPrice, $gasLimit, $to, $value, $data) {
$transaction = new Transaction ($nonce, $gasPrice, $gasLimit, $to, $value, $data);
public function testGetInput($expect, $nonce, $gasPrice, $gasLimit, $to, $value, $data)
{
$transaction = new Transaction($nonce, $gasPrice, $gasLimit, $to, $value, $data);
$this->assertSame($expect, $transaction->getInput());
}

public static function input (): array {
public static function input(): array
{
return [
[
['nonce' => '', 'gasPrice' => '', 'gasLimit' => '', 'to' => '', 'value' => '', 'data' => '', 'v' => '', 'r' => '', 's' => ''],
Expand All @@ -31,65 +33,68 @@ public static function input (): array {
}

/**
* @dataProvider getRaw
* @dataProvider getTransactionData
*/
public function testGetRaw ($expect, $privateKey, $chainId, $nonce, $gasPrice, $gasLimit, $to, $value, $data) {
$transaction = new Transaction ($nonce, $gasPrice, $gasLimit, $to, $value, $data);
$this->assertSame($expect, $transaction->getRaw($privateKey, $chainId));
public function testGetRaw($txid, $unsigned, $signed, $privateKey, $chainId, $nonce, $gasPrice, $gasLimit, $to, $value, $data)
{
$transaction = new Transaction($nonce, $gasPrice, $gasLimit, $to, $value, $data);
$this->assertSame($signed, $transaction->getRaw($privateKey, $chainId));
}

public function testGetRawBadChainId () {
$this->expectException(RuntimeException::class);
$transaction = new Transaction ('04', '03f5476a00', '027f4b', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '2a45907d1bef7c00', '');
$transaction->getRaw ('b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', -1);
/**
* @dataProvider getTransactionData
*/
public function getUnsigned($txid, $unsigned, $signed, $privateKey, $chainId, $nonce, $gasPrice, $gasLimit, $to, $value, $data)
{
$transaction = new Transaction($nonce, $gasPrice, $gasLimit, $to, $value, $data);
$this->assertSame($unsigned, $transaction->getUnsigned($chainId));
}

/**
* @dataProvider getTransactionData
*/
public function testHash($txid, $unsigned, $signed, $privateKey, $chainId, $nonce, $gasPrice, $gasLimit, $to, $value, $data)
{
$transaction = new Transaction($nonce, $gasPrice, $gasLimit, $to, $value, $data);
$this->assertSame($txid, $transaction->hash($chainId));
}

public static function getRaw (): array {
public static function getTransactionData(): array
{
return [
[
'a0c5887430a600aac139fc269a354b0e5d4c700c3e177ea2c227502367224e9c', //txid
'ea048503f5476a0083027f4b941a8c8adfbe1c59e8b58cc0d515f07b7225f51c72882a45907d1bef7c0080', //unsigned
'f86d048503f5476a0083027f4b941a8c8adfbe1c59e8b58cc0d515f07b7225f51c72882a45907d1bef7c00801ba0e68be766b40702e6d9c419f53d5e053c937eda36f0e973074d174027439e2b5da0790df3e4d0294f92d69104454cd96005e21095efd5f2970c2829736ca39195d8',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 0, '04', '03f5476a00', '027f4b', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '2a45907d1bef7c00', ''
],
[
'db734dc3a2da9bee8baf350f7d6e6f2dc593b06534a7721385cdb588431b6136', //txid
'ed048503f5476a0083027f4b941a8c8adfbe1c59e8b58cc0d515f07b7225f51c72882a45907d1bef7c0080018080', //unsigned
'f86d048503f5476a0083027f4b941a8c8adfbe1c59e8b58cc0d515f07b7225f51c72882a45907d1bef7c008025a0db4efcc22a7d9b2cab180ce37f81959412594798cb9af7c419abb6323763cdd5a0631a0c47d27e5b6e3906a419de2d732e290b73ead4172d8598ce4799c13bda69',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 1, '04', '03f5476a00', '027f4b', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '2a45907d1bef7c00', ''
],
[
'8e5a5404792108371948015b678cbe6a10d0cf40c9969eeb04829a6714170ff6', //txid
'ed048503f5476a0083027f4b941a8c8adfbe1c59e8b58cc0d515f07b7225f51c72882a45907d1bef7c0080388080', //unsigned
'f86e048503f5476a0083027f4b941a8c8adfbe1c59e8b58cc0d515f07b7225f51c72882a45907d1bef7c00808193a05c206269d6d902591b5b37ec821be99f78f1375191b8a8fb5a9e7bd87cb33999a027ca8262e92e3a5d7d2682d0394587bd7362250f4313767aa1a656c075d09911',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 56, '04', '03f5476a00', '027f4b', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '2a45907d1bef7c00', ''
],
];
}

public function testBadPrivateKey () {
public function testGetRawBadChainId()
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Incorrect private key');
$transaction = new Transaction ();
$transaction->getRaw('');
$transaction = new Transaction('04', '03f5476a00', '027f4b', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '2a45907d1bef7c00', '');
$transaction->getRaw('b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', -1);
}

/**
* @dataProvider getEIP1559Raw
*/
public function testGetEIP1559Raw($expect, $privateKey, $chainId, $nonce, $maxPriorityFeePerGas, $maxFeePerGas, $gasLimit, $to, $value, $data) {
$transaction = new EIP1559Transaction($nonce, $maxPriorityFeePerGas, $maxFeePerGas, $gasLimit, $to, $value, $data);
$this->assertSame($expect, $transaction->getRaw($privateKey, $chainId));
}

public static function getEIP1559Raw(): array {
return [
[
'02f873010284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c000080c080a0dd32dc794af9a9085d6772c40656fc156a577570c6fd32f2a2d4126673373919a066cf1859672a9e6fdbb00ebd230bcfec66cac1c99f5c83598ecae6025d0e91f4',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 1, '02', 'b2d05e00', '6fc23ac00', '5208', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '29a2241af62c0000', ''
],
[
'02f873380284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c000080c080a0b8ec74b95d0d8ebe4b4c7e9d5a8af16e06a7d636d20b2c632633a2abe3a0f5cca005971b1f3ec9b498d8d7b17b312477d0b1fd2232ca4b82e9c34fc43d33e5f196',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 56, '02', 'b2d05e00', '6fc23ac00', '5208', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '29a2241af62c0000', ''
],
[
'02f877380284b2d05e008506fc23ac00825208941a8c8adfbe1c59e8b58cc0d515f07b7225f51c728829a2241af62c00008401232213c001a05bc0caa25dd8e23adf3f79f8dbe1237e0f22980a3b15f00bbe9f5a19dd23a70ca002e9a353c9b155b0e7e796b5f966bc6dd53deafea9990398a069d93278e50644',
'b2f2698dd7343fa5afc96626dee139cb92e58e5d04e855f4c712727bf198e898', 56, '02', 'b2d05e00', '6fc23ac00', '5208', '1a8c8adfbe1c59e8b58cc0d515f07b7225f51c72', '29a2241af62c0000', '1232213'
]
];
public function testBadPrivateKey()
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Incorrect private key');
$transaction = new Transaction();
$transaction->getRaw('');
}
}

0 comments on commit 1780e62

Please sign in to comment.