Skip to content

Commit 0664f5f

Browse files
committed
Support for Schnorr signatures and integration in SignatureCheckers (BIP 340)
This enables the schnorrsig module in libsecp256k1, adds the relevant types and functions to src/pubkey, as well as in higher-level `SignatureChecker` classes. The (verification side of the) BIP340 test vectors is also added.
1 parent 5de246c commit 0664f5f

File tree

14 files changed

+165
-13
lines changed

14 files changed

+165
-13
lines changed

build_msvc/libsecp256k1/libsecp256k1.vcxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
</ItemGroup>
1313
<ItemDefinitionGroup>
1414
<ClCompile>
15-
<PreprocessorDefinitions>ENABLE_MODULE_ECDH;ENABLE_MODULE_RECOVERY;%(PreprocessorDefinitions)</PreprocessorDefinitions>
15+
<PreprocessorDefinitions>ENABLE_MODULE_ECDH;ENABLE_MODULE_RECOVERY;ENABLE_MODULE_EXTRAKEYS;ENABLE_MODULE_SCHNORRSIG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
1616
<AdditionalIncludeDirectories>..\..\src\secp256k1;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
1717
</ClCompile>
1818
</ItemDefinitionGroup>

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1645,7 +1645,7 @@ if test x$need_bundled_univalue = xyes; then
16451645
AC_CONFIG_SUBDIRS([src/univalue])
16461646
fi
16471647

1648-
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery"
1648+
ac_configure_args="${ac_configure_args} --disable-shared --with-pic --enable-benchmark=no --with-bignum=no --enable-module-recovery --enable-module-schnorrsig --enable-experimental"
16491649
AC_CONFIG_SUBDIRS([src/secp256k1])
16501650

16511651
AC_OUTPUT

src/pubkey.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include <secp256k1.h>
99
#include <secp256k1_recovery.h>
10+
#include <secp256k1_schnorrsig.h>
1011

1112
namespace
1213
{
@@ -166,6 +167,20 @@ int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_
166167
return 1;
167168
}
168169

170+
XOnlyPubKey::XOnlyPubKey(Span<const unsigned char> bytes)
171+
{
172+
assert(bytes.size() == 32);
173+
std::copy(bytes.begin(), bytes.end(), m_keydata.begin());
174+
}
175+
176+
bool XOnlyPubKey::VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const
177+
{
178+
assert(sigbytes.size() == 64);
179+
secp256k1_xonly_pubkey pubkey;
180+
if (!secp256k1_xonly_pubkey_parse(secp256k1_context_verify, &pubkey, m_keydata.data())) return false;
181+
return secp256k1_schnorrsig_verify(secp256k1_context_verify, sigbytes.data(), msg.begin(), &pubkey);
182+
}
183+
169184
bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const {
170185
if (!IsValid())
171186
return false;

src/pubkey.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <hash.h>
1111
#include <serialize.h>
12+
#include <span.h>
1213
#include <uint256.h>
1314

1415
#include <stdexcept>
@@ -206,6 +207,25 @@ class CPubKey
206207
bool Derive(CPubKey& pubkeyChild, ChainCode &ccChild, unsigned int nChild, const ChainCode& cc) const;
207208
};
208209

210+
class XOnlyPubKey
211+
{
212+
private:
213+
uint256 m_keydata;
214+
215+
public:
216+
/** Construct an x-only pubkey from exactly 32 bytes. */
217+
XOnlyPubKey(Span<const unsigned char> bytes);
218+
219+
/** Verify a Schnorr signature against this public key.
220+
*
221+
* sigbytes must be exactly 64 bytes.
222+
*/
223+
bool VerifySchnorr(const uint256& msg, Span<const unsigned char> sigbytes) const;
224+
225+
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
226+
size_t size() const { return m_keydata.size(); }
227+
};
228+
209229
struct CExtPubKey {
210230
unsigned char nDepth;
211231
unsigned char vchFingerprint[4];

src/script/interpreter.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1521,6 +1521,12 @@ bool GenericTransactionSignatureChecker<T>::VerifyECDSASignature(const std::vect
15211521
return pubkey.Verify(sighash, vchSig);
15221522
}
15231523

1524+
template <class T>
1525+
bool GenericTransactionSignatureChecker<T>::VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const
1526+
{
1527+
return pubkey.VerifySchnorr(sighash, sig);
1528+
}
1529+
15241530
template <class T>
15251531
bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vector<unsigned char>& vchSigIn, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const
15261532
{
@@ -1543,6 +1549,30 @@ bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vecto
15431549
return true;
15441550
}
15451551

1552+
template <class T>
1553+
bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey_in, SigVersion sigversion, ScriptError* serror) const
1554+
{
1555+
assert(sigversion == SigVersion::TAPROOT);
1556+
// Schnorr signatures have 32-byte public keys. The caller is responsible for enforcing this.
1557+
assert(pubkey_in.size() == 32);
1558+
if (sig.size() != 64 && sig.size() != 65) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_SIZE);
1559+
1560+
XOnlyPubKey pubkey{pubkey_in};
1561+
1562+
uint8_t hashtype = SIGHASH_DEFAULT;
1563+
if (sig.size() == 65) {
1564+
hashtype = SpanPopBack(sig);
1565+
if (hashtype == SIGHASH_DEFAULT) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
1566+
}
1567+
uint256 sighash;
1568+
assert(this->txdata);
1569+
if (!SignatureHashSchnorr(sighash, *txTo, nIn, hashtype, sigversion, *this->txdata)) {
1570+
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
1571+
}
1572+
if (!VerifySchnorrSignature(sig, pubkey, sighash)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
1573+
return true;
1574+
}
1575+
15461576
template <class T>
15471577
bool GenericTransactionSignatureChecker<T>::CheckLockTime(const CScriptNum& nLockTime) const
15481578
{

src/script/interpreter.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
#define BITCOIN_SCRIPT_INTERPRETER_H
88

99
#include <script/script_error.h>
10+
#include <span.h>
1011
#include <primitives/transaction.h>
1112

1213
#include <vector>
1314
#include <stdint.h>
1415

1516
class CPubKey;
17+
class XOnlyPubKey;
1618
class CScript;
1719
class CTransaction;
1820
class CTxOut;
@@ -176,6 +178,11 @@ class BaseSignatureChecker
176178
return false;
177179
}
178180

181+
virtual bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptError* serror = nullptr) const
182+
{
183+
return false;
184+
}
185+
179186
virtual bool CheckLockTime(const CScriptNum& nLockTime) const
180187
{
181188
return false;
@@ -200,11 +207,13 @@ class GenericTransactionSignatureChecker : public BaseSignatureChecker
200207

201208
protected:
202209
virtual bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const;
210+
virtual bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const;
203211

204212
public:
205213
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
206214
GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
207215
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
216+
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptError* serror = nullptr) const override;
208217
bool CheckLockTime(const CScriptNum& nLockTime) const override;
209218
bool CheckSequence(const CScriptNum& nSequence) const override;
210219
};

src/script/script_error.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ std::string ScriptErrorString(const ScriptError serror)
9191
return "Witness provided for non-witness script";
9292
case SCRIPT_ERR_WITNESS_PUBKEYTYPE:
9393
return "Using non-compressed keys in segwit";
94+
case SCRIPT_ERR_SCHNORR_SIG_SIZE:
95+
return "Invalid Schnorr signature size";
96+
case SCRIPT_ERR_SCHNORR_SIG_HASHTYPE:
97+
return "Invalid Schnorr signature hash type";
98+
case SCRIPT_ERR_SCHNORR_SIG:
99+
return "Invalid Schnorr signature";
94100
case SCRIPT_ERR_OP_CODESEPARATOR:
95101
return "Using OP_CODESEPARATOR in non-witness script";
96102
case SCRIPT_ERR_SIG_FINDANDDELETE:

src/script/script_error.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ typedef enum ScriptError_t
6666
SCRIPT_ERR_WITNESS_UNEXPECTED,
6767
SCRIPT_ERR_WITNESS_PUBKEYTYPE,
6868

69+
/* Taproot */
70+
SCRIPT_ERR_SCHNORR_SIG_SIZE,
71+
SCRIPT_ERR_SCHNORR_SIG_HASHTYPE,
72+
SCRIPT_ERR_SCHNORR_SIG,
73+
6974
/* Constant scriptCode */
7075
SCRIPT_ERR_OP_CODESEPARATOR,
7176
SCRIPT_ERR_SIG_FINDANDDELETE,

src/script/sigcache.cpp

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ namespace {
2222
class CSignatureCache
2323
{
2424
private:
25-
//! Entries are SHA256(nonce || signature hash || public key || signature):
26-
CSHA256 m_salted_hasher;
25+
//! Entries are SHA256(nonce || 'E' or 'S' || 31 zero bytes || signature hash || public key || signature):
26+
CSHA256 m_salted_hasher_ecdsa;
27+
CSHA256 m_salted_hasher_schnorr;
2728
typedef CuckooCache::cache<uint256, SignatureCacheHasher> map_type;
2829
map_type setValid;
2930
boost::shared_mutex cs_sigcache;
@@ -34,18 +35,30 @@ class CSignatureCache
3435
uint256 nonce = GetRandHash();
3536
// We want the nonce to be 64 bytes long to force the hasher to process
3637
// this chunk, which makes later hash computations more efficient. We
37-
// just write our 32-byte entropy twice to fill the 64 bytes.
38-
m_salted_hasher.Write(nonce.begin(), 32);
39-
m_salted_hasher.Write(nonce.begin(), 32);
38+
// just write our 32-byte entropy, and then pad with 'E' for ECDSA and
39+
// 'S' for Schnorr (followed by 0 bytes).
40+
static constexpr unsigned char PADDING_ECDSA[32] = {'E'};
41+
static constexpr unsigned char PADDING_SCHNORR[32] = {'S'};
42+
m_salted_hasher_ecdsa.Write(nonce.begin(), 32);
43+
m_salted_hasher_ecdsa.Write(PADDING_ECDSA, 32);
44+
m_salted_hasher_schnorr.Write(nonce.begin(), 32);
45+
m_salted_hasher_schnorr.Write(PADDING_SCHNORR, 32);
4046
}
4147

4248
void
4349
ComputeEntryECDSA(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& vchSig, const CPubKey& pubkey)
4450
{
45-
CSHA256 hasher = m_salted_hasher;
51+
CSHA256 hasher = m_salted_hasher_ecdsa;
4652
hasher.Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(&vchSig[0], vchSig.size()).Finalize(entry.begin());
4753
}
4854

55+
void
56+
ComputeEntrySchnorr(uint256& entry, const uint256 &hash, Span<const unsigned char> sig, const XOnlyPubKey& pubkey)
57+
{
58+
CSHA256 hasher = m_salted_hasher_schnorr;
59+
hasher.Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(sig.data(), sig.size()).Finalize(entry.begin());
60+
}
61+
4962
bool
5063
Get(const uint256& entry, const bool erase)
5164
{
@@ -97,3 +110,13 @@ bool CachingTransactionSignatureChecker::VerifyECDSASignature(const std::vector<
97110
signatureCache.Set(entry);
98111
return true;
99112
}
113+
114+
bool CachingTransactionSignatureChecker::VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const
115+
{
116+
uint256 entry;
117+
signatureCache.ComputeEntrySchnorr(entry, sighash, sig, pubkey);
118+
if (signatureCache.Get(entry, !store)) return true;
119+
if (!TransactionSignatureChecker::VerifySchnorrSignature(sig, pubkey, sighash)) return false;
120+
if (store) signatureCache.Set(entry);
121+
return true;
122+
}

src/script/sigcache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define BITCOIN_SCRIPT_SIGCACHE_H
88

99
#include <script/interpreter.h>
10+
#include <span.h>
1011

1112
#include <vector>
1213

@@ -49,6 +50,7 @@ class CachingTransactionSignatureChecker : public TransactionSignatureChecker
4950
CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
5051

5152
bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
53+
bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override;
5254
};
5355

5456
void InitSignatureCache();

0 commit comments

Comments
 (0)