diff --git a/src/MessageSigner/MessageSigner.php b/src/MessageSigner/MessageSigner.php index e6cc65fb3..f92c917bd 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; @@ -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 { @@ -60,12 +63,21 @@ public function calculateMessageHash(NetworkInterface $network, string $message) /** * @param SignedMessage $signedMessage - * @param PayToPubKeyHashAddress $address + * @param Address $address * @param NetworkInterface|null $network * @return bool + * @throws SignerException */ - public function verify(SignedMessage $signedMessage, PayToPubKeyHashAddress $address, NetworkInterface $network = null): bool + 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 50fc81914..dcb48b7c2 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,57 @@ 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()); + } + + /** + * @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); + } }