diff --git a/src/Transaction.php b/src/Transaction.php index a15a923..a3832b9 100644 --- a/src/Transaction.php +++ b/src/Transaction.php @@ -16,6 +16,7 @@ use kornrunner\Keccak; use Web3p\RLP\RLP; use Elliptic\EC; +use Elliptic\EC\KeyPair; use ArrayAccess; class Transaction implements ArrayAccess @@ -68,6 +69,13 @@ class Transaction implements ArrayAccess */ protected $secp256k1; + /** + * privateKey + * + * @var \Elliptic\EC\KeyPair + */ + protected $privateKey; + /** * construct * @@ -253,8 +261,8 @@ public function serialize() public function sign(string $privateKey) { $txHash = $this->hash(false); - $key = $this->secp256k1->keyFromPrivate($privateKey, 'hex'); - $signature = $key->sign($txHash); + $privateKey = $this->secp256k1->keyFromPrivate($privateKey, 'hex'); + $signature = $privateKey->sign($txHash); $r = $signature->r; $s = $signature->s; $v = $signature->recoveryParam + 35; @@ -268,6 +276,7 @@ public function sign(string $privateKey) $this->offsetSet('r', '0x' . $r->toString(16)); $this->offsetSet('s', '0x' . $s->toString(16)); $this->offsetSet('v', $v); + $this->privateKey = $privateKey; return $this->serialize()->toString('hex'); } @@ -309,4 +318,47 @@ public function hash($includeSignature=false) return $this->sha3($serializedTx); } + + /** + * getFromAddress + * + * @return string + */ + public function getFromAddress() + { + $from = $this->offsetGet('from'); + + if ($from) { + var_dump(1); + return $from; + } + if (!isset($this->privateKey) || !($this->privateKey instanceof KeyPair)) { + // recover from hash + $r = $this->offsetGet('r'); + $s = $this->offsetGet('s'); + $v = $this->offsetGet('v'); + $chainId = $this->offsetGet('chainId'); + + if (!$r || !$s) { + throw new RuntimeException('Invalid signature r and s.'); + } + $txHash = $this->hash(false); + + if ($chainId && $chainId > 0) { + $v -= ($chainId * 2); + } + $v -= 35; + $publicKey = $this->secp256k1->recoverPubKey($txHash, [ + 'r' => $r, + 's' => $s + ], $v); + $publicKey = $publicKey->encode('hex'); + } else { + $publicKey = $this->privateKey->getPublic(false, 'hex'); + } + $from = '0x' . substr($this->sha3(substr(hex2bin($publicKey), 1)), 24); + + $this->offsetSet('from', $from); + return $from; + } } \ No newline at end of file diff --git a/test/unit/TransactionTest.php b/test/unit/TransactionTest.php index f6f704f..9ad21f6 100644 --- a/test/unit/TransactionTest.php +++ b/test/unit/TransactionTest.php @@ -218,4 +218,65 @@ public function testEIP155() $this->assertEquals('daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53', $transaction->hash(false)); $this->assertEquals('f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83', $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646')); } + + /** + * testGetFromAddress + * 0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f + * + * @return void + */ + public function testGetFromAddress() + { + $transaction = new Transaction([ + 'nonce' => '0x09', + 'to' => '0x3535353535353535353535353535353535353535', + 'gas' => '0x5208', + 'gasPrice' => '0x4a817c800', + 'value' => '0xde0b6b3a7640000', + 'chainId' => 1, + 'data' => '' + ]); + // sign tx + $transaction->sign('0x4646464646464646464646464646464646464646464646464646464646464646'); + $r = $transaction['r']; + $s = $transaction['s']; + $v = $transaction['v']; + + // get from privatekey + $fromA = $transaction->getFromAddress(); + + $transaction = new Transaction([ + 'nonce' => '0x09', + 'to' => '0x3535353535353535353535353535353535353535', + 'gas' => '0x5208', + 'gasPrice' => '0x4a817c800', + 'value' => '0xde0b6b3a7640000', + 'chainId' => 1, + 'data' => '' + ]); + $transaction['r'] = $r; + $transaction['s'] = $s; + $transaction['v'] = $v; + + // get from r, s, v + $fromB = $transaction->getFromAddress(); + + $transaction = new Transaction([ + 'from' => '0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f', + 'nonce' => '0x09', + 'to' => '0x3535353535353535353535353535353535353535', + 'gas' => '0x5208', + 'gasPrice' => '0x4a817c800', + 'value' => '0xde0b6b3a7640000', + 'chainId' => 1, + 'data' => '' + ]); + + // get from transaction + $fromC = $transaction->getFromAddress(); + + $this->assertEquals('0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f', $fromA); + $this->assertEquals($fromA, $fromB); + $this->assertEquals($fromB, $fromC); + } } \ No newline at end of file