From a3fa058f258e8229f75ef1b0f8da9045bfab6f40 Mon Sep 17 00:00:00 2001 From: Sander Kruger Date: Wed, 7 Apr 2021 15:12:20 +0400 Subject: [PATCH 1/2] Support signing and verifying messages signed with a segwit address. --- src/MessageSigner/MessageSigner.php | 6 ++-- tests/SignedMessage/SignedMessageTest.php | 44 ++++++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/MessageSigner/MessageSigner.php b/src/MessageSigner/MessageSigner.php index e6cc65fb3..ab0d88742 100644 --- a/src/MessageSigner/MessageSigner.php +++ b/src/MessageSigner/MessageSigner.php @@ -4,7 +4,7 @@ namespace BitWasp\Bitcoin\MessageSigner; -use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress; +use BitWasp\Bitcoin\Address\Address; use BitWasp\Bitcoin\Bitcoin; use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface; use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface; @@ -60,11 +60,11 @@ public function calculateMessageHash(NetworkInterface $network, string $message) /** * @param SignedMessage $signedMessage - * @param PayToPubKeyHashAddress $address + * @param Address $address * @param NetworkInterface|null $network * @return bool */ - public function verify(SignedMessage $signedMessage, PayToPubKeyHashAddress $address, NetworkInterface $network = null): bool + public function verify(SignedMessage $signedMessage, Address $address, NetworkInterface $network = null): bool { $network = $network ?: Bitcoin::getNetwork(); $hash = $this->calculateMessageHash($network, $signedMessage->getMessage()); diff --git a/tests/SignedMessage/SignedMessageTest.php b/tests/SignedMessage/SignedMessageTest.php index 50fc81914..da713819e 100644 --- a/tests/SignedMessage/SignedMessageTest.php +++ b/tests/SignedMessage/SignedMessageTest.php @@ -33,6 +33,21 @@ public function sampleMessage() ]; } + public function sampleSegwitMessage() + { + return + [ + 'hi', + 'tb1q9p35ug38k0tvuj542452f3275t3uc3py5pwt82', + '-----BEGIN BITCOIN SIGNED MESSAGE----- +hi +-----BEGIN SIGNATURE----- +H6KhveLOgDCpIt13HbUxGtEGgtgVInY/bDiW9UR8TF36KgO1TZOnZ66pR1vyTS+ylvuoiwdaIGT/c3aminfCa/8= +-----END BITCOIN SIGNED MESSAGE-----', + NetworkFactory::bitcoinTestnet() + ]; + } + /** * @dataProvider getEcAdapters * @param EcAdapterInterface $ecAdapter @@ -48,7 +63,7 @@ public function testParsesMessage(EcAdapterInterface $ecAdapter) EcSerializer::getSerializer(CompactSignatureSerializerInterface::class, true, $ecAdapter) ); - $signed = $serializer->parse($content); + $signed = $serializer->parse(str_replace("\r\n", "\n", $content)); $signer = new MessageSigner($ecAdapter); $this->assertSame($message, $signed->getMessage()); @@ -143,4 +158,31 @@ public function testLitecoinFixture() $result = $signer->verify($signedMessage, $address, $network); $this->assertTrue($result); } + + /** + * @dataProvider getEcAdapters + * @param EcAdapterInterface $ecAdapter + */ + public function testParsesSegwitMessage(EcAdapterInterface $ecAdapter) + { + list ($message, $addressString, $content, $network) = $this->sampleSegwitMessage(); + + $addrCreator = new AddressCreator(); + /** @var SegwitAddress $address */ + $address = $addrCreator->fromString($addressString, $network); + $serializer = new SignedMessageSerializer( + EcSerializer::getSerializer(CompactSignatureSerializerInterface::class, true, $ecAdapter) + ); + + $signed = $serializer->parse(str_replace("\r\n", "\n", $content)); + $signer = new MessageSigner($ecAdapter); + + $this->assertSame($message, $signed->getMessage()); + $this->assertSame('73560454392673031410410110112528757574906118603913228462684770364360586190330', gmp_strval($signed->getCompactSignature()->getR(), 10)); + $this->assertSame('19003691489245959228844184723526227573581591575474947180245750135893235231743', gmp_strval($signed->getCompactSignature()->getS(), 10)); + $this->assertEquals(0, $signed->getCompactSignature()->getRecoveryId()); + $this->assertSame(true, $signed->getCompactSignature()->isCompressed()); + $this->assertTrue($signer->verify($signed, $address)); + $this->assertSame($content, $signed->getBuffer()->getBinary()); + } } From 275217b1b53c436336eb482466b6d23538f3ccb6 Mon Sep 17 00:00:00 2001 From: Sander Kruger Date: Thu, 8 Apr 2021 16:22:34 +0400 Subject: [PATCH 2/2] Add testcase for ScriptHashAddress and checks for allowed address formats --- src/MessageSigner/MessageSigner.php | 12 +++++++++ tests/SignedMessage/SignedMessageTest.php | 32 ++++++++++++++++++++--- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/MessageSigner/MessageSigner.php b/src/MessageSigner/MessageSigner.php index ab0d88742..f92c917bd 100644 --- a/src/MessageSigner/MessageSigner.php +++ b/src/MessageSigner/MessageSigner.php @@ -14,6 +14,9 @@ use BitWasp\Buffertools\Buffer; use BitWasp\Buffertools\BufferInterface; use BitWasp\Buffertools\Buffertools; +use BitWasp\Bitcoin\Address\SegwitAddress; +use BitWasp\Bitcoin\Address\PayToPubKeyHashAddress; +use BitWasp\Bitcoin\Exceptions\SignerException; class MessageSigner { @@ -63,9 +66,18 @@ public function calculateMessageHash(NetworkInterface $network, string $message) * @param Address $address * @param NetworkInterface|null $network * @return bool + * @throws SignerException */ public function verify(SignedMessage $signedMessage, Address $address, NetworkInterface $network = null): bool { + if ($address instanceof SegwitAddress) { + $version = $address->getWitnessProgram()->getVersion(); + if ($version > 0) { + throw new SignerException('Wrong segwit address version'); + } + } elseif (!$address instanceof PayToPubKeyHashAddress) { + throw new SignerException('Wrong address format'); + } $network = $network ?: Bitcoin::getNetwork(); $hash = $this->calculateMessageHash($network, $signedMessage->getMessage()); diff --git a/tests/SignedMessage/SignedMessageTest.php b/tests/SignedMessage/SignedMessageTest.php index da713819e..dcb48b7c2 100644 --- a/tests/SignedMessage/SignedMessageTest.php +++ b/tests/SignedMessage/SignedMessageTest.php @@ -172,11 +172,11 @@ public function testParsesSegwitMessage(EcAdapterInterface $ecAdapter) $address = $addrCreator->fromString($addressString, $network); $serializer = new SignedMessageSerializer( EcSerializer::getSerializer(CompactSignatureSerializerInterface::class, true, $ecAdapter) - ); - + ); + $signed = $serializer->parse(str_replace("\r\n", "\n", $content)); $signer = new MessageSigner($ecAdapter); - + $this->assertSame($message, $signed->getMessage()); $this->assertSame('73560454392673031410410110112528757574906118603913228462684770364360586190330', gmp_strval($signed->getCompactSignature()->getR(), 10)); $this->assertSame('19003691489245959228844184723526227573581591575474947180245750135893235231743', gmp_strval($signed->getCompactSignature()->getS(), 10)); @@ -185,4 +185,30 @@ public function testParsesSegwitMessage(EcAdapterInterface $ecAdapter) $this->assertTrue($signer->verify($signed, $address)); $this->assertSame($content, $signed->getBuffer()->getBinary()); } + + /** + * @dataProvider getEcAdapters + * @param EcAdapterInterface $ecAdapter + */ + public function testParsesScriptHashMessage(EcAdapterInterface $ecAdapter) + { + $this->expectException(\BitWasp\Bitcoin\Exceptions\SignerException::class); + $this->expectExceptionMessage('Wrong address format'); + $sample = $this->sampleMessage(); + $content = $sample[2]; + $network = $sample[3]; + $addressString = '2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc'; + + $addrCreator = new AddressCreator(); + /** @var ScriptHashAddress $address */ + $address = $addrCreator->fromString($addressString, $network); + $serializer = new SignedMessageSerializer( + EcSerializer::getSerializer(CompactSignatureSerializerInterface::class, true, $ecAdapter) + ); + + $signed = $serializer->parse(str_replace("\r\n", "\n", $content)); + $signer = new MessageSigner($ecAdapter); + + $signer->verify($signed, $address); + } }