88use BitWasp \Bitcoin \Crypto \EcAdapter \EcSerializer ;
99use BitWasp \Bitcoin \Crypto \EcAdapter \Impl \PhpEcc \Key \PublicKey ;
1010use BitWasp \Bitcoin \Crypto \EcAdapter \Serializer \Key \PublicKeySerializerInterface ;
11+ use BitWasp \Bitcoin \Crypto \EcAdapter \Serializer \Key \XOnlyPublicKeySerializerInterface ;
1112use BitWasp \Bitcoin \Crypto \EcAdapter \Serializer \Signature \DerSignatureSerializerInterface ;
13+ use BitWasp \Bitcoin \Crypto \EcAdapter \Serializer \Signature \SchnorrSignatureSerializerInterface ;
1214use BitWasp \Bitcoin \Exceptions \ScriptRuntimeException ;
1315use BitWasp \Bitcoin \Exceptions \SignatureNotCanonical ;
1416use BitWasp \Bitcoin \Locktime ;
1517use BitWasp \Bitcoin \Script \ScriptInterface ;
1618use BitWasp \Bitcoin \Serializer \Signature \TransactionSignatureSerializer ;
1719use BitWasp \Bitcoin \Signature \TransactionSignature ;
1820use BitWasp \Bitcoin \Transaction \SignatureHash \SigHash ;
21+ use BitWasp \Bitcoin \Transaction \SignatureHash \TaprootHasher ;
1922use BitWasp \Bitcoin \Transaction \TransactionInput ;
2023use BitWasp \Bitcoin \Transaction \TransactionInputInterface ;
2124use BitWasp \Bitcoin \Transaction \TransactionInterface ;
25+ use BitWasp \Bitcoin \Transaction \TransactionOutputInterface ;
26+ use BitWasp \Buffertools \Buffer ;
2227use BitWasp \Buffertools \BufferInterface ;
2328
2429abstract 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
0 commit comments