Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a41cec8
refactor: eip 155 support
alexbarnsley Jul 11, 2025
7918eab
test
alexbarnsley Jul 11, 2025
987c435
style: resolve style guide violations
alexbarnsley Jul 11, 2025
99cac43
remove constant usage
alexbarnsley Jul 11, 2025
c6bbd39
Merge branch 'refactor/eip-155-support' of github.com:arkecosystem/ph…
alexbarnsley Jul 11, 2025
4734720
Merge branch 'feat/mainsail' into refactor/eip-155-support
ItsANameToo Jul 11, 2025
7f6ccc0
wip
ItsANameToo Jul 11, 2025
e51a93a
style: resolve style guide violations
ItsANameToo Jul 11, 2025
165e735
fix: deserializer
ItsANameToo Jul 11, 2025
ad712e4
test: default to Testnet for tests and utils tweaks
ItsANameToo Jul 11, 2025
86f5175
test: update fixtures
ItsANameToo Jul 11, 2025
df61102
fix: message sign
ItsANameToo Jul 11, 2025
151226a
fix: rlp decoder
ItsANameToo Jul 11, 2025
efee52b
fix: transactionUtilsTest index
ItsANameToo Jul 11, 2025
319fd17
refactor: argumentdecoder test improvements and refactor
ItsANameToo Jul 11, 2025
dd6bccc
style: resolve style guide violations
ItsANameToo Jul 11, 2025
ad2cd0a
refactor: legacy second signature support
alexbarnsley Jul 14, 2025
98daaa0
test
alexbarnsley Jul 14, 2025
03778cf
update fixtures
alexbarnsley Jul 15, 2025
9d2e348
handle updated fixture types in tests
alexbarnsley Jul 15, 2025
2c2a655
Merge branch 'refactor/eip-155-support' into refactor/legacy-second-s…
alexbarnsley Jul 17, 2025
bb54f8e
Merge remote-tracking branch 'origin/feat/mainsail' into refactor/leg…
alexbarnsley Jul 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/Identities/PrivateKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace ArkEcosystem\Crypto\Identities;

use ArkEcosystem\Crypto\Configuration\Network;
use ArkEcosystem\Crypto\Helpers;
use BitWasp\Bitcoin\Bitcoin;
use BitWasp\Bitcoin\Crypto\EcAdapter\EcAdapterFactory;
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Signature\CompactSignature;
Expand Down Expand Up @@ -75,7 +76,7 @@ public static function fromWif(string $wif): self
}

/**
* Derive the private key for the given WIF.
* Sign a message using the private key.
*
* @param BufferInterface $message
*
Expand All @@ -86,6 +87,25 @@ public function sign(BufferInterface $message): CompactSignature
return $this->instance->signCompact($message);
}

/**
* Sign a message using the private key and return an ECDSA signature.
*
* @param BufferInterface $message
*
* @return string
*/
public function signToEcdsa(BufferInterface $message): string
{
$signature = $this->instance->signCompact($message);

return sprintf(
'%s%s%s',
Helpers::gmpToHex($signature->getR()),
Helpers::gmpToHex($signature->getS()),
str_pad((string) $signature->getRecoveryId(), 2, '0', STR_PAD_LEFT),
);
}

private static function factory(): PrivateKeyFactory
{
return new PrivateKeyFactory(
Expand Down
11 changes: 11 additions & 0 deletions src/Transactions/Builder/AbstractTransactionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ public function sign(string $passphrase): static
return $this;
}

public function legacySecondSign(string $passphrase, string $secondPassphrase): static
{
$this->sign($passphrase);

$this->transaction->legacySecondSign(
PrivateKey::fromPassphrase($secondPassphrase)
);

return $this;
}

public function verify(): bool
{
return $this->transaction->verify();
Expand Down
9 changes: 9 additions & 0 deletions src/Transactions/Types/AbstractTransaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ public function sign(PrivateKey $privateKey): static
return $this;
}

public function legacySecondSign(PrivateKey $privateKey): static
{
$hash = $this->hash(skipSignature: true);

$this->data['legacySecondSignature'] = $privateKey->signToEcdsa($hash);

return $this;
}

public function recoverSender(): void
{
$compactSignature = $this->getSignature();
Expand Down
8 changes: 5 additions & 3 deletions src/Utils/TransactionUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ public static function toBuffer(array $transaction, bool $skipSignature = false)
$fields[] = self::toBeArray($transaction['v'] + Network::get()->chainId() * 2 + 35);
$fields[] = '0x'.$transaction['r'];
$fields[] = '0x'.$transaction['s'];

if (isset($transaction['legacySecondSignature'])) {
$fields[] = '0x'.$transaction['legacySecondSignature'];
}
} else {
// Push chainId + 0s for r and s
$fields[] = self::toBeArray(Network::get()->chainId());
Expand All @@ -47,9 +51,7 @@ public static function toBuffer(array $transaction, bool $skipSignature = false)

$encoded = RlpEncoder::encode($fields);

$payload = substr($encoded, 2);

return new Buffer(hex2bin($payload));
return new Buffer(hex2bin(substr($encoded, 2)));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ abstract class TestCase extends BaseTestCase

protected $passphrase = 'found lobster oblige describe ready addict body brave live vacuum display salute lizard combine gift resemble race senior quality reunion proud tell adjust angle';

protected $secondPassphrase = 'this is a top secret second passphrase';
protected $secondPassphrase = 'gold favorite math anchor detect march purpose such sausage crucial reform novel connect misery update episode invite salute barely garbage exclude winner visa cruise';

protected $passphrases = [
'album pony urban cheap small blade cannon silent run reveal luxury glad predict excess fire beauty hollow reward solar egg exclude leaf sight degree',
Expand Down
28 changes: 28 additions & 0 deletions tests/Unit/Transactions/Builder/TransferBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,34 @@
expect($builder->verify())->toBeTrue();
});

it('should sign with a second passphrase', function () {
$fixture = $this->getTransactionFixture('evm_call', 'transfer-legacy-second-signature');

$builder = TransferBuilder::new()
->gasPrice(UnitConverter::parseUnits($fixture['data']['gasPrice'], 'wei'))
->gasLimit(UnitConverter::parseUnits($fixture['data']['gasLimit'], 'wei'))
->nonce($fixture['data']['nonce'])
->to($fixture['data']['to'])
->value(UnitConverter::parseUnits($fixture['data']['value'], 'wei'))
->legacySecondSign($this->passphrase, $this->secondPassphrase);

expect((string) $builder->transaction->data['gasPrice'])->toBe((string) $fixture['data']['gasPrice']);
expect((string) $builder->transaction->data['gasLimit'])->toBe((string) $fixture['data']['gasLimit']);
expect($builder->transaction->data['nonce'])->toBe($fixture['data']['nonce']);
expect($builder->transaction->data['to'])->toBe($fixture['data']['to']);
expect((string) $builder->transaction->data['value'])->toBe((string) $fixture['data']['value']);
expect($builder->transaction->data['v'])->toBe($fixture['data']['v']);
expect($builder->transaction->data['r'])->toBe($fixture['data']['r']);
expect($builder->transaction->data['s'])->toBe($fixture['data']['s']);
expect($builder->transaction->data['legacySecondSignature'])->toBe($fixture['data']['legacySecondSignature']);

expect($builder->transaction->serialize()->getHex())->toBe($fixture['serialized']);

expect($builder->transaction->data['hash'])->toBe($fixture['data']['hash']);

expect($builder->verify())->toBeTrue();
});

it('should handle large amounts', function () {
$fixture = $this->getTransactionFixture('evm_call', 'transfer-large-amount');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"data": {
"nonce": "1",
"gasPrice": "5000000000",
"gasLimit": "21000",
"to": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22",
"value": "100000000",
"data": "",
"network": 11812,
"v": 0,
"r": "a1f79cb40a4bb409d6cebd874002ceda3ec0ccb614c1d8155f5c2f7f798135f9",
"s": "2d2ef517aaf6feed747385e260c206f46b2ce9d6b2a585427a111685a097bd79",
"legacySecondSignature": "094b33b2d10c4d48cff9a9b10f79990c9a902a762b00d2f2a4d2ddad7823b59332b2da193265068c67cefea9ca8bc84e47acec406f3300a0a10b77a56ea8c18801",
"senderPublicKey": "0243333347c8cbf4e3cbc7a96964181d02a2b0c854faa2fef86b4b8d92afcf473d",
"from": "0x1E6747BEAa5B4076a6A98D735DF8c35a70D18Bdd",
"hash": "a39435ec5de418e77479856d06a653efc171afe43e091472af22ee359eeb83be"
},
"serialized": "f8ad0185012a05f200825208946f0182a0cc707b055322ccf6d4cb6a5aff1aeb228405f5e10080825c6ba0a1f79cb40a4bb409d6cebd874002ceda3ec0ccb614c1d8155f5c2f7f798135f9a02d2ef517aaf6feed747385e260c206f46b2ce9d6b2a585427a111685a097bd79b841094b33b2d10c4d48cff9a9b10f79990c9a902a762b00d2f2a4d2ddad7823b59332b2da193265068c67cefea9ca8bc84e47acec406f3300a0a10b77a56ea8c18801"
}
Loading