Skip to content

Commit e9840c9

Browse files
committed
fix utxo address decompress
1 parent bd629a4 commit e9840c9

File tree

6 files changed

+84
-45
lines changed

6 files changed

+84
-45
lines changed

src/AddressSerializer.php

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
namespace AndKom\Bitcoin\Blockchain;
66

7+
use AndKom\Bitcoin\Blockchain\Exception\AddressException;
78
use AndKom\Bitcoin\Blockchain\Network\Bitcoin;
89
use function BitWasp\Bech32\encodeSegwit;
10+
use StephenHill\Base58;
911

1012
/**
1113
* Class AddressSerializer
@@ -31,24 +33,49 @@ public function __construct(Bitcoin $network = null)
3133
$this->network = $network;
3234
}
3335

36+
/**
37+
* @param string $hash160
38+
* @param int $prefix
39+
* @return string
40+
* @throws \Exception
41+
*/
42+
public function hashToAddress(string $hash160, int $prefix): string
43+
{
44+
$hash160 = chr($prefix) . $hash160;
45+
$checksum = substr(Utils::hash256($hash160, true), 0, 4);
46+
$address = $hash160 . $checksum;
47+
return (new Base58())->encode($address);
48+
}
49+
50+
/**
51+
* @param string $pubKey
52+
* @param int $prefix
53+
* @return string
54+
* @throws \Exception
55+
*/
56+
public function pubKeyToAddress(string $pubKey, int $prefix): string
57+
{
58+
return $this->hashToAddress(Utils::hash160($pubKey, true), $prefix);
59+
}
60+
3461
/**
3562
* @param string $pubKey
3663
* @return string
3764
* @throws \Exception
3865
*/
39-
public function getPayToPubKeyAddress(string $pubKey): string
66+
public function getPayToPubKey(string $pubKey): string
4067
{
41-
return Utils::pubKeyToAddress($pubKey, $this->network::P2PKH_PREFIX);
68+
return $this->pubKeyToAddress($pubKey, $this->network::P2PKH_PREFIX);
4269
}
4370

4471
/**
4572
* @param string $pubKeyHash
4673
* @return string
4774
* @throws \Exception
4875
*/
49-
public function getPayToPubKeyHashAddress(string $pubKeyHash): string
76+
public function getPayToPubKeyHash(string $pubKeyHash): string
5077
{
51-
return Utils::hash160ToAddress($pubKeyHash, $this->network::P2PKH_PREFIX);
78+
return $this->hashToAddress($pubKeyHash, $this->network::P2PKH_PREFIX);
5279
}
5380

5481
/**
@@ -58,7 +85,7 @@ public function getPayToPubKeyHashAddress(string $pubKeyHash): string
5885
*/
5986
public function getPayToScriptHash(string $scriptHash): string
6087
{
61-
return Utils::hash160ToAddress($scriptHash, $this->network::P2SH_PREFIX);
88+
return $this->hashToAddress($scriptHash, $this->network::P2SH_PREFIX);
6289
}
6390

6491
/**

src/PublicKey.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ static public function parse(string $data): self
148148
$length = strlen($data);
149149

150150
if ($length == static::LENGTH_COMPRESSED) {
151-
$prefix = substr($data, 0, 1);
151+
$prefix = $data[0];
152152

153153
if ($prefix != static::PREFIX_COMPRESSED_ODD && $prefix != static::PREFIX_COMPRESSED_EVEN) {
154154
throw new PublicKeyException('Invalid compressed public key prefix.');
@@ -157,7 +157,7 @@ static public function parse(string $data): self
157157
$x = Utils::binToGmp(substr($data, 1, 32));
158158
$y = null;
159159
} elseif ($length == static::LENGTH_UNCOMPRESSED) {
160-
$prefix = substr($data, 0, 1);
160+
$prefix = $data[0];
161161

162162
if ($prefix != static::PREFIX_UNCOMPRESSED) {
163163
throw new PublicKeyException('Invalid uncompressed public key prefix.');

src/ScriptPubKey.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@ public function getOutputAddress(Bitcoin $network = null): string
121121
$addressSerializer = new AddressSerializer($network);
122122

123123
if ($this->isPayToPubKey()) {
124-
return $addressSerializer->getPayToPubKeyAddress($operations[0]->data);
124+
return $addressSerializer->getPayToPubKey($operations[0]->data);
125125
}
126126

127127
if ($this->isPayToPubKeyHash()) {
128-
return $addressSerializer->getPayToPubKeyHashAddress($operations[2]->data);
128+
return $addressSerializer->getPayToPubKeyHash($operations[2]->data);
129129
}
130130

131131
if ($this->isPayToScriptHash()) {

src/UnspentOutput.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace AndKom\Bitcoin\Blockchain;
66

77
use AndKom\BCDataStream\Reader;
8+
use AndKom\Bitcoin\Blockchain\Exception\ScriptException;
89
use AndKom\Bitcoin\Blockchain\Network\Bitcoin;
910

1011
/**
@@ -102,7 +103,7 @@ static public function decompressAmount(int $x)
102103
/**
103104
* @param Bitcoin|null $network
104105
* @return string
105-
* @throws Exception
106+
* @throws ScriptException
106107
* @throws \Exception
107108
*/
108109
public function getAddress(Bitcoin $network = null): string
@@ -112,19 +113,21 @@ public function getAddress(Bitcoin $network = null): string
112113
// try do decompress script first
113114
switch ($this->type) {
114115
case 0x00:
115-
return $addressSerializer->getPayToPubKeyHashAddress($this->script);
116+
return $addressSerializer->getPayToPubKeyHash($this->script);
116117

117118
case 0x01:
118119
return $addressSerializer->getPayToScriptHash($this->script);
119120

120121
case 0x02:
121122
case 0x03:
122-
return $addressSerializer->getPayToPubKeyAddress($this->script);
123+
$pubKey = chr($this->type) . $this->script;
124+
return $addressSerializer->getPayToPubKey($pubKey);
123125

124126
case 0x04:
125127
case 0x05:
126-
$decompressed = PublicKey::parse($this->script)->decompress()->serialize();
127-
return $addressSerializer->getPayToPubKeyAddress($decompressed);
128+
$pubKey = chr($this->type - 2) . $this->script;
129+
$decompressed = PublicKey::parse($pubKey)->decompress()->serialize();
130+
return $addressSerializer->getPayToPubKey($decompressed);
128131
}
129132

130133
// fallback

src/Utils.php

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
namespace AndKom\Bitcoin\Blockchain;
66

7-
use StephenHill\Base58;
8-
97
/**
108
* Class Utils
119
* @package AndKom\Bitcoin\Blockchain
@@ -32,31 +30,6 @@ static public function hash160(string $data, bool $raw = false): string
3230
return hash('ripemd160', hash('sha256', $data, true), $raw);
3331
}
3432

35-
/**
36-
* @param string $hash160
37-
* @param int $network
38-
* @return string
39-
* @throws \Exception
40-
*/
41-
static public function hash160ToAddress(string $hash160, int $network): string
42-
{
43-
$hash160 = chr($network) . $hash160;
44-
$checksum = substr(static::hash256($hash160, true), 0, 4);
45-
$address = $hash160 . $checksum;
46-
return (new Base58())->encode($address);
47-
}
48-
49-
/**
50-
* @param string $pubKey
51-
* @param int $network
52-
* @return string
53-
* @throws \Exception
54-
*/
55-
static public function pubKeyToAddress(string $pubKey, int $network): string
56-
{
57-
return static::hash160ToAddress(static::hash160($pubKey, true), $network);
58-
}
59-
6033
/**
6134
* @param string $key
6235
* @param string $value

tests/UnspentOutputTest.php

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,57 @@
99

1010
class UnspentOutputTest extends TestCase
1111
{
12+
public function testPayToPubKeyHash()
13+
{
14+
$uo = new UnspentOutput();
15+
$uo->type = 0x00;
16+
$uo->script = hex2bin('cbc2986ff9aed6825920aece14aa6f5382ca5580');
17+
18+
$this->assertEquals($uo->getAddress(), '1KaPHfvVWNZADup3Yc26SfVdkTDvvHySVX');
19+
}
20+
21+
public function testPayToScriptHash()
22+
{
23+
$uo = new UnspentOutput();
24+
$uo->type = 0x01;
25+
$uo->script = hex2bin('ee12c1c86ca0a2f60cf90e559eb10847645c44c1');
26+
27+
$this->assertEquals($uo->getAddress(), '3PPqDTQgndCHiDxqeB3zayJxXB6UeKAiod');
28+
}
29+
1230
public function testPayToPubKeyCompressed()
1331
{
1432
$uo = new UnspentOutput();
1533
$uo->type = 0x02;
16-
$uo->script = hex2bin('02633280c0a93b45217059013ddadab8d35b9a858336028fecdff64c6a5e068fad');
34+
$uo->script = hex2bin('0e46e79a2a8d12b9b5d12c7a91adb4e454edfae43c0a0cb805427d2ac7613fd9');
1735

18-
$this->assertEquals($uo->getAddress(), '1A8nTwDWzKhV2UNEss6DtDBKuYfJH8TFDG');
36+
$this->assertEquals($uo->getAddress(), '1P3rU1Nk1pmc2BiWC8dEy9bZa1ZbMp5jfg');
37+
}
38+
39+
public function testPayToPubKeyCompressed2()
40+
{
41+
$uo = new UnspentOutput();
42+
$uo->type = 0x03;
43+
$uo->script = hex2bin('0f34af4b908fb8eb2099accb56b8d157d49f6cfb691baa80fdd34f385efed961');
44+
45+
$this->assertEquals($uo->getAddress(), '1CSxcc7PC8Fx5hZeSF4k9Rf6AMTeU3brJC');
1946
}
2047

2148
public function testPayToPubKeyUncompressed()
2249
{
2350
$uo = new UnspentOutput();
2451
$uo->type = 0x04;
25-
$uo->script = hex2bin('02633280c0a93b45217059013ddadab8d35b9a858336028fecdff64c6a5e068fad');
52+
$uo->script = hex2bin('7a488354d9d5414de09b7121b80b973c991b76998ad68756d8cf4560c0ddcbe2');
53+
54+
$this->assertEquals($uo->getAddress(), '18R1zbfonvzUf5nqzMa81kpt9XR37RWrcM');
55+
}
56+
57+
public function testPayToPubKeyUncompressed2()
58+
{
59+
$uo = new UnspentOutput();
60+
$uo->type = 0x05;
61+
$uo->script = hex2bin('c863781e34ee29d96f493a002de08c27cdfb79268d774b566100e9ae2d06bdfe');
2662

27-
$this->assertEquals($uo->getAddress(), '1PTYXwamXXgQoAhDbmUf98rY2Pg1pYXhin');
63+
$this->assertEquals($uo->getAddress(), '16No8BKxQuhKf5SkJvHmuGRPgFCuxfdYnx');
2864
}
2965
}

0 commit comments

Comments
 (0)