Skip to content

Commit

Permalink
Add schnorr signature verification methods to Checker
Browse files Browse the repository at this point in the history
  • Loading branch information
afk11 committed Oct 29, 2019
1 parent 7909c9e commit 3971a4b
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 4 deletions.
8 changes: 7 additions & 1 deletion src/Crypto/EcAdapter/EcSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PrivateKeySerializerInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\XOnlyPublicKeySerializerInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\CompactSignatureSerializerInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\SchnorrSignatureSerializerInterface;

class EcSerializer
{
Expand All @@ -22,8 +24,10 @@ class EcSerializer
private static $serializerInterface = [
PrivateKeySerializerInterface::class,
PublicKeySerializerInterface::class,
XOnlyPublicKeySerializerInterface::class,
CompactSignatureSerializerInterface::class,
DerSignatureSerializerInterface::class,
SchnorrSignatureSerializerInterface::class,
];

/**
Expand All @@ -32,8 +36,10 @@ class EcSerializer
private static $serializerImpl = [
'Serializer\Key\PrivateKeySerializer',
'Serializer\Key\PublicKeySerializer',
'Serializer\Key\XOnlyPublicKeySerializer',
'Serializer\Signature\CompactSignatureSerializer',
'Serializer\Signature\DerSignatureSerializer'
'Serializer\Signature\DerSignatureSerializer',
'Serializer\Signature\SchnorrSignatureSerializer',
];

/**
Expand Down
98 changes: 95 additions & 3 deletions src/Script/Interpreter/CheckerBase.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,22 @@
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\XOnlyPublicKeySerializerInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\SchnorrSignatureSerializerInterface;
use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException;
use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical;
use BitWasp\Bitcoin\Locktime;
use BitWasp\Bitcoin\Script\ScriptInterface;
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
use BitWasp\Bitcoin\Signature\TransactionSignature;
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
use BitWasp\Bitcoin\Transaction\SignatureHash\TaprootHasher;
use BitWasp\Bitcoin\Transaction\TransactionInput;
use BitWasp\Bitcoin\Transaction\TransactionInputInterface;
use BitWasp\Bitcoin\Transaction\TransactionInterface;
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
use BitWasp\Buffertools\Buffer;
use BitWasp\Buffertools\BufferInterface;

abstract class CheckerBase
Expand Down Expand Up @@ -48,6 +53,16 @@ abstract class CheckerBase
*/
protected $sigCache = [];

/**
* @var array
*/
protected $schnorrSigHashCache = [];

/**
* @var TransactionOutputInterface[]
*/
protected $spentOutputs = [];

/**
* @var TransactionSignatureSerializer
*/
Expand All @@ -58,30 +73,60 @@ abstract class CheckerBase
*/
private $pubKeySerializer;

/**
* @var XOnlyPublicKeySerializerInterface
*/
private $xonlyKeySerializer;

/**
* @var SchnorrSignatureSerializerInterface
*/
private $schnorrSigSerializer;

/**
* @var int
*/
protected $sigHashOptionalBits = SigHash::ANYONECANPAY;

/**
* Checker constructor.
* CheckerBase constructor.
* @param EcAdapterInterface $ecAdapter
* @param TransactionInterface $transaction
* @param int $nInput
* @param int $amount
* @param TransactionSignatureSerializer|null $sigSerializer
* @param PublicKeySerializerInterface|null $pubKeySerializer
* @param XOnlyPublicKeySerializerInterface|null $xonlyKeySerializer
* @param SchnorrSignatureSerializerInterface|null $schnorrSigSerializer
*/
public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $transaction, int $nInput, int $amount, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
{
public function __construct(
EcAdapterInterface $ecAdapter,
TransactionInterface $transaction,
int $nInput,
int $amount,
TransactionSignatureSerializer $sigSerializer = null,
PublicKeySerializerInterface $pubKeySerializer = null,
XOnlyPublicKeySerializerInterface $xonlyKeySerializer = null,
SchnorrSignatureSerializerInterface $schnorrSigSerializer = null
) {
$this->sigSerializer = $sigSerializer ?: new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, true, $ecAdapter));
$this->pubKeySerializer = $pubKeySerializer ?: EcSerializer::getSerializer(PublicKeySerializerInterface::class, true, $ecAdapter);
$this->xonlyKeySerializer = $xonlyKeySerializer ?: EcSerializer::getSerializer(XOnlyPublicKeySerializerInterface::class, true, $ecAdapter);
$this->schnorrSigSerializer = $schnorrSigSerializer ?: EcSerializer::getSerializer(SchnorrSignatureSerializerInterface::class, true, $ecAdapter);
$this->adapter = $ecAdapter;
$this->transaction = $transaction;
$this->nInput = $nInput;
$this->amount = $amount;
}

public function setSpentOutputs(array $txOuts)
{
if (count($txOuts) !== count($this->transaction->getInputs())) {
throw new \RuntimeException("number of spent txouts should equal number of inputs");
}
$this->spentOutputs = $txOuts;
}

/**
* @param ScriptInterface $script
* @param int $hashType
Expand Down Expand Up @@ -225,6 +270,53 @@ public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, Buffe
}
}

public function getTaprootSigHash(int $sigHashType, int $sigVersion): BufferInterface
{
$cacheCheck = $sigVersion . $sigHashType;
if (!isset($this->schnorrSigHashCache[$cacheCheck])) {
$hasher = new TaprootHasher($this->transaction, $this->amount, $this->spentOutputs);

$hash = $hasher->calculate($this->spentOutputs[$this->nInput]->getScript(), $this->nInput, $sigHashType);
$this->schnorrSigHashCache[$cacheCheck] = $hash->getBinary();
} else {
$hash = new Buffer($this->schnorrSigHashCache[$cacheCheck], 32);
}

return $hash;
}

public function checkSigSchnorr(BufferInterface $sig64, BufferInterface $key32, int $sigVersion): bool
{
if ($sig64->getSize() === 0) {
return false;
}
if ($key32->getSize() !== 32) {
return false;
}

$hashType = SigHash::TAPDEFAULT;
if ($sig64->getSize() === 65) {
$hashType = $sig64->slice(64, 1);
if ($hashType == SigHash::TAPDEFAULT) {
return false;
}
$sig64 = $sig64->slice(0, 64);
}

if ($sig64->getSize() !== 64) {
return false;
}

try {
$sig = $this->schnorrSigSerializer->parse($sig64);
$pubKey = $this->xonlyKeySerializer->parse($key32);
$sigHash = $this->getTaprootSigHash($hashType, $sigVersion);
return $pubKey->verifySchnorr($sigHash, $sig);
} catch (\Exception $e) {
return false;
}
}

/**
* @param \BitWasp\Bitcoin\Script\Interpreter\Number $scriptLockTime
* @return bool
Expand Down
3 changes: 3 additions & 0 deletions src/Transaction/SignatureHash/TaprootHasher.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ public function hashSpentAmountsHash(array $txOuts): BufferInterface
* spend $txOut, and are signing $inputToSign. The SigHashType defaults to
* SIGHASH_ALL
*
* Note: this function doesn't use txOutScript, as we have access to it via
* spentOutputs.
*
* @param ScriptInterface $txOutScript
* @param int $inputToSign
* @param int $sighashType
Expand Down

0 comments on commit 3971a4b

Please sign in to comment.