Skip to content

Commit c908869

Browse files
committed
taproot support
1 parent 897234e commit c908869

21 files changed

+529
-154
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ $p2wsh = $factory->p2wsh($p2ms);
9090
$address = $factory->p2sh($p2wsh)->address();
9191
```
9292

93+
Generate a P2TR address:
94+
95+
```php
96+
$taprootPubKey = Taproot::construct($pubKey);
97+
$address = OutputFactory::p2tr($taprootPubKey)->address();
98+
```
99+
93100
Generate an address from an output script:
94101

95102
```php

composer.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
],
1616
"require": {
1717
"php": ">=7.1",
18+
"ext-gmp": "*",
19+
"btccom/cashaddress": "^0.0.3",
20+
"brooksyang/bech32m": "^1.0",
1821
"stephenhill/base58": "^1.1",
19-
"bitwasp/bech32": "^0.0.1",
20-
"btccom/cashaddress": "^0.0.3"
22+
"shanecurran/phpecc": "^0.0.1"
2123
},
2224
"require-dev": {
2325
"phpunit/phpunit": ">=5.7"

src/Ecc/Point.php

+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace AndKom\Bitcoin\Address\Ecc;
6+
7+
use AndKom\Bitcoin\Address\Exception;
8+
use AndKom\Bitcoin\Address\Validate;
9+
use Mdanter\Ecc\Math\GmpMathInterface;
10+
use Mdanter\Ecc\Primitives\CurveFpInterface;
11+
12+
/**
13+
* Class Point
14+
* @package AndKom\Bitcoin\Address\Ecc
15+
*/
16+
class Point extends \Mdanter\Ecc\Primitives\Point
17+
{
18+
/**
19+
* @return $this
20+
* @throws Exception
21+
*/
22+
public function liftX(): self
23+
{
24+
$adapter = $this->getAdapter();
25+
$curve = $this->getCurve();
26+
27+
$x = $this->getX();
28+
29+
if ($adapter->cmp($x, $curve->getPrime()) > 0) {
30+
throw new Exception('Invalid point X.');
31+
}
32+
33+
$c = $adapter->mod(
34+
$adapter->add(
35+
$adapter->pow($x, 3),
36+
gmp_init(7, 10)
37+
),
38+
$curve->getPrime()
39+
);
40+
41+
$y = $adapter->powmod(
42+
$c,
43+
$adapter->div(
44+
$adapter->add(
45+
$curve->getPrime(),
46+
gmp_init(1, 10)
47+
),
48+
gmp_init(4, 10)
49+
),
50+
$curve->getPrime()
51+
);
52+
53+
if ($adapter->cmp(
54+
$c,
55+
$adapter->powmod(
56+
$y,
57+
gmp_init(2),
58+
$curve->getPrime()
59+
)
60+
) !== 0) {
61+
throw new Exception('C is not equal to point Y^2.');
62+
}
63+
64+
if ($adapter->cmp(
65+
$adapter->mod(
66+
$y,
67+
gmp_init(2, 10)
68+
),
69+
gmp_init(0, 10)
70+
) === 0) {
71+
$point = new Point($adapter, $curve, $x, $y);
72+
} else {
73+
$point = new Point($adapter, $curve, $x, $adapter->sub($curve->getPrime(), $y));
74+
}
75+
76+
return $point;
77+
}
78+
79+
static function fromPubKey(GmpMathInterface $adapter, CurveFpInterface $curve, string $pubKey): self
80+
{
81+
$pubKey = Validate::pubKey($pubKey);
82+
83+
$pubKeyHex = bin2hex($pubKey);
84+
$prefix = substr($pubKeyHex, 0, 2);
85+
86+
if ($prefix === '04') {
87+
$x = gmp_init(substr($pubKeyHex, 2, 64), 16);
88+
$y = gmp_init(substr($pubKeyHex, 66, 64), 16);
89+
} else {
90+
$x = gmp_init(substr($pubKeyHex, 2, 64), 16);
91+
$y = $curve->recoverYfromX($prefix === '03', $x);
92+
}
93+
94+
return new static($adapter, $curve, $x, $y);
95+
}
96+
}

src/Network/NetworkInterface.php

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public function getAddressP2wpkh(string $pubKeyHash): string;
3636
*/
3737
public function getAddressP2wsh(string $witnessHash): string;
3838

39+
/**
40+
* @param string $taprootPubKey
41+
* @return string
42+
*/
43+
public function getAddressP2tr(string $taprootPubKey): string;
44+
3945
/**
4046
* @param string $address
4147
* @return OutputInterface

src/Network/Networks/Bitcoin.php

+5-108
Original file line numberDiff line numberDiff line change
@@ -4,122 +4,19 @@
44

55
namespace AndKom\Bitcoin\Address\Network\Networks;
66

7-
use AndKom\Bitcoin\Address\Exception;
8-
use AndKom\Bitcoin\Address\Network\NetworkInterface;
9-
use AndKom\Bitcoin\Address\Output\OutputInterface;
10-
use AndKom\Bitcoin\Address\Output\Outputs\P2pkh;
11-
use AndKom\Bitcoin\Address\Output\Outputs\P2sh;
12-
use AndKom\Bitcoin\Address\Output\Outputs\P2wpkh;
13-
use AndKom\Bitcoin\Address\Output\Outputs\P2wsh;
14-
use AndKom\Bitcoin\Address\Utils;
15-
use AndKom\Bitcoin\Address\Validate;
16-
use BitWasp\Bech32\Exception\Bech32Exception;
17-
use function BitWasp\Bech32\decodeSegwit;
18-
use function BitWasp\Bech32\encodeSegwit;
19-
207
/**
218
* Class Bitcoin
229
* @package AndKom\Bitcoin\Address\Network\Networks
2310
*/
24-
class Bitcoin implements NetworkInterface
11+
class Bitcoin extends BitcoinAbstract
2512
{
2613
/**
27-
* @var string
28-
*/
29-
protected $prefixP2pkh = "\x00";
30-
31-
/**
32-
* @var string
33-
*/
34-
protected $prefixP2sh = "\x05";
35-
36-
/**
37-
* @var string
14+
* @var bool
3815
*/
39-
protected $prefixBech32 = 'bc';
16+
protected $hasSegwit = true;
4017

4118
/**
42-
* @param string $pubKeyHash
43-
* @return string
44-
* @throws \Exception
19+
* @var bool
4520
*/
46-
public function getAddressP2pkh(string $pubKeyHash): string
47-
{
48-
return Utils::base58encode($pubKeyHash, $this->prefixP2pkh);
49-
}
50-
51-
/**
52-
* @param string $scriptHash
53-
* @return string
54-
* @throws \Exception
55-
*/
56-
public function getAddressP2sh(string $scriptHash): string
57-
{
58-
return Utils::base58encode($scriptHash, $this->prefixP2sh);
59-
}
60-
61-
/**
62-
* @return string
63-
* @throws Exception
64-
*/
65-
public function getPrefixBech32(): string
66-
{
67-
if (!$this->prefixBech32) {
68-
throw new Exception('Empty bech32 prefix.');
69-
}
70-
71-
return $this->prefixBech32;
72-
}
73-
74-
/**
75-
* @param string $pubKeyHash
76-
* @return string
77-
* @throws Exception
78-
* @throws Bech32Exception
79-
*/
80-
public function getAddressP2wpkh(string $pubKeyHash): string
81-
{
82-
return encodeSegwit($this->getPrefixBech32(), 0, $pubKeyHash);
83-
}
84-
85-
/**
86-
* @param string $witnessHash
87-
* @return string
88-
* @throws Exception
89-
* @throws Bech32Exception
90-
*/
91-
public function getAddressP2wsh(string $witnessHash): string
92-
{
93-
return encodeSegwit($this->getPrefixBech32(), 0, $witnessHash);
94-
}
95-
96-
/**
97-
* @param string $address
98-
* @return OutputInterface
99-
* @throws \Exception
100-
* @throws Bech32Exception
101-
*/
102-
public function decodeAddress(string $address): OutputInterface
103-
{
104-
if ($this->prefixBech32 && 0 === strpos($address, $this->prefixBech32)) {
105-
list(, $hash) = decodeSegwit($this->prefixBech32, $address);
106-
$hashLen = strlen($hash);
107-
108-
if (Validate::SCRIPT_HASH_LEN == $hashLen) {
109-
return new P2wpkh($hash);
110-
} elseif (Validate::WITNESS_HASH_LEN == $hashLen) {
111-
return new P2wsh($hash);
112-
}
113-
}
114-
115-
list($hash, $prefix) = Utils::base58decode($address);
116-
117-
if ($prefix == $this->prefixP2pkh) {
118-
return new P2pkh($hash);
119-
} elseif ($prefix == $this->prefixP2sh) {
120-
return new P2sh($hash);
121-
}
122-
123-
throw new Exception('Cannot decode address.');
124-
}
21+
protected $hasTaproot = true;
12522
}

0 commit comments

Comments
 (0)