From a3fa058f258e8229f75ef1b0f8da9045bfab6f40 Mon Sep 17 00:00:00 2001
From: Sander Kruger <s.kruger@invessel.com>
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 <s.kruger@invessel.com>
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);
+    }
 }