Skip to content

Commit 3971a4b

Browse files
committed
Add schnorr signature verification methods to Checker
1 parent 7909c9e commit 3971a4b

File tree

3 files changed

+105
-4
lines changed

3 files changed

+105
-4
lines changed

src/Crypto/EcAdapter/EcSerializer.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
99
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PrivateKeySerializerInterface;
1010
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
11+
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\XOnlyPublicKeySerializerInterface;
1112
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\CompactSignatureSerializerInterface;
1213
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
14+
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\SchnorrSignatureSerializerInterface;
1315

1416
class EcSerializer
1517
{
@@ -22,8 +24,10 @@ class EcSerializer
2224
private static $serializerInterface = [
2325
PrivateKeySerializerInterface::class,
2426
PublicKeySerializerInterface::class,
27+
XOnlyPublicKeySerializerInterface::class,
2528
CompactSignatureSerializerInterface::class,
2629
DerSignatureSerializerInterface::class,
30+
SchnorrSignatureSerializerInterface::class,
2731
];
2832

2933
/**
@@ -32,8 +36,10 @@ class EcSerializer
3236
private static $serializerImpl = [
3337
'Serializer\Key\PrivateKeySerializer',
3438
'Serializer\Key\PublicKeySerializer',
39+
'Serializer\Key\XOnlyPublicKeySerializer',
3540
'Serializer\Signature\CompactSignatureSerializer',
36-
'Serializer\Signature\DerSignatureSerializer'
41+
'Serializer\Signature\DerSignatureSerializer',
42+
'Serializer\Signature\SchnorrSignatureSerializer',
3743
];
3844

3945
/**

src/Script/Interpreter/CheckerBase.php

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,22 @@
88
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
99
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
1010
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
11+
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\XOnlyPublicKeySerializerInterface;
1112
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
13+
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\SchnorrSignatureSerializerInterface;
1214
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
1315
use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical;
1416
use BitWasp\Bitcoin\Locktime;
1517
use BitWasp\Bitcoin\Script\ScriptInterface;
1618
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
1719
use BitWasp\Bitcoin\Signature\TransactionSignature;
1820
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
21+
use BitWasp\Bitcoin\Transaction\SignatureHash\TaprootHasher;
1922
use BitWasp\Bitcoin\Transaction\TransactionInput;
2023
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
2124
use BitWasp\Bitcoin\Transaction\TransactionInterface;
25+
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
26+
use BitWasp\Buffertools\Buffer;
2227
use BitWasp\Buffertools\BufferInterface;
2328

2429
abstract class CheckerBase
@@ -48,6 +53,16 @@ abstract class CheckerBase
4853
*/
4954
protected $sigCache = [];
5055

56+
/**
57+
* @var array
58+
*/
59+
protected $schnorrSigHashCache = [];
60+
61+
/**
62+
* @var TransactionOutputInterface[]
63+
*/
64+
protected $spentOutputs = [];
65+
5166
/**
5267
* @var TransactionSignatureSerializer
5368
*/
@@ -58,30 +73,60 @@ abstract class CheckerBase
5873
*/
5974
private $pubKeySerializer;
6075

76+
/**
77+
* @var XOnlyPublicKeySerializerInterface
78+
*/
79+
private $xonlyKeySerializer;
80+
81+
/**
82+
* @var SchnorrSignatureSerializerInterface
83+
*/
84+
private $schnorrSigSerializer;
85+
6186
/**
6287
* @var int
6388
*/
6489
protected $sigHashOptionalBits = SigHash::ANYONECANPAY;
6590

6691
/**
67-
* Checker constructor.
92+
* CheckerBase constructor.
6893
* @param EcAdapterInterface $ecAdapter
6994
* @param TransactionInterface $transaction
7095
* @param int $nInput
7196
* @param int $amount
7297
* @param TransactionSignatureSerializer|null $sigSerializer
7398
* @param PublicKeySerializerInterface|null $pubKeySerializer
99+
* @param XOnlyPublicKeySerializerInterface|null $xonlyKeySerializer
100+
* @param SchnorrSignatureSerializerInterface|null $schnorrSigSerializer
74101
*/
75-
public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, int $nInput, int $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
76-
{
102+
public function __construct(
103+
EcAdapterInterface $ecAdapter,
104+
TransactionInterface $transaction,
105+
int $nInput,
106+
int $amount,
107+
TransactionSignatureSerializer $sigSerializer = null,
108+
PublicKeySerializerInterface $pubKeySerializer = null,
109+
XOnlyPublicKeySerializerInterface $xonlyKeySerializer = null,
110+
SchnorrSignatureSerializerInterface $schnorrSigSerializer = null
111+
) {
77112
$this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
78113
$this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
114+
$this->xonlyKeySerializer = $xonlyKeySerializer ?: EcSerializer::getSerializer(XOnlyPublicKeySerializerInterface::class, true, $ecAdapter);
115+
$this->schnorrSigSerializer = $schnorrSigSerializer ?: EcSerializer::getSerializer(SchnorrSignatureSerializerInterface::class, true, $ecAdapter);
79116
$this->adapter = $ecAdapter;
80117
$this->transaction = $transaction;
81118
$this->nInput = $nInput;
82119
$this->amount = $amount;
83120
}
84121

122+
public function setSpentOutputs(array $txOuts)
123+
{
124+
if (count($txOuts) !== count($this->transaction->getInputs())) {
125+
throw new \RuntimeException("number of spent txouts should equal number of inputs");
126+
}
127+
$this->spentOutputs = $txOuts;
128+
}
129+
85130
/**
86131
* @param ScriptInterface $script
87132
* @param int $hashType
@@ -225,6 +270,53 @@ public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, Buffe
225270
}
226271
}
227272

273+
public function getTaprootSigHash(int $sigHashType, int $sigVersion): BufferInterface
274+
{
275+
$cacheCheck = $sigVersion . $sigHashType;
276+
if (!isset($this->schnorrSigHashCache[$cacheCheck])) {
277+
$hasher = new TaprootHasher($this->transaction, $this->amount, $this->spentOutputs);
278+
279+
$hash = $hasher->calculate($this->spentOutputs[$this->nInput]->getScript(), $this->nInput, $sigHashType);
280+
$this->schnorrSigHashCache[$cacheCheck] = $hash->getBinary();
281+
} else {
282+
$hash = new Buffer($this->schnorrSigHashCache[$cacheCheck], 32);
283+
}
284+
285+
return $hash;
286+
}
287+
288+
public function checkSigSchnorr(BufferInterface $sig64, BufferInterface $key32, int $sigVersion): bool
289+
{
290+
if ($sig64->getSize() === 0) {
291+
return false;
292+
}
293+
if ($key32->getSize() !== 32) {
294+
return false;
295+
}
296+
297+
$hashType = SigHash::TAPDEFAULT;
298+
if ($sig64->getSize() === 65) {
299+
$hashType = $sig64->slice(64, 1);
300+
if ($hashType == SigHash::TAPDEFAULT) {
301+
return false;
302+
}
303+
$sig64 = $sig64->slice(0, 64);
304+
}
305+
306+
if ($sig64->getSize() !== 64) {
307+
return false;
308+
}
309+
310+
try {
311+
$sig = $this->schnorrSigSerializer->parse($sig64);
312+
$pubKey = $this->xonlyKeySerializer->parse($key32);
313+
$sigHash = $this->getTaprootSigHash($hashType, $sigVersion);
314+
return $pubKey->verifySchnorr($sigHash, $sig);
315+
} catch (\Exception $e) {
316+
return false;
317+
}
318+
}
319+
228320
/**
229321
* @param \BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime
230322
* @return bool

src/Transaction/SignatureHash/TaprootHasher.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ public function hashSpentAmountsHash(array $txOuts): BufferInterface
141141
* spend $txOut, and are signing $inputToSign. The SigHashType defaults to
142142
* SIGHASH_ALL
143143
*
144+
* Note: this function doesn't use txOutScript, as we have access to it via
145+
* spentOutputs.
146+
*
144147
* @param ScriptInterface $txOutScript
145148
* @param int $inputToSign
146149
* @param int $sighashType

0 commit comments

Comments
 (0)