initial addition of essential crypto, encoders, workflows and LLM instructions

This commit is contained in:
2025-08-20 05:47:06 +01:00
parent f449a9d415
commit b8db587d7b
159 changed files with 36993 additions and 10 deletions

View File

@@ -0,0 +1,173 @@
// Copyright 2013-2016 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package schnorr
import (
"math/big"
"testing"
"next.orly.dev/pkg/crypto/ec"
"next.orly.dev/pkg/crypto/ec/secp256k1"
"next.orly.dev/pkg/crypto/sha256"
"next.orly.dev/pkg/encoders/hex"
)
// hexToBytes converts the passed hex string into bytes and will panic if there
// is an error. This is only provided for the hard-coded constants, so errors in
// the source code can be detected. It will only (and must only) be called with
// hard-coded values.
func hexToBytes(s string) []byte {
b, err := hex.Dec(s)
if err != nil {
panic("invalid hex in source file: " + s)
}
return b
}
// hexToModNScalar converts the passed hex string into a ModNScalar and will
// panic if there is an error. This is only provided for the hard-coded
//
// constants, so errors in the source code can be detected. It will only (and
//
// must only) be called with hard-coded values.
func hexToModNScalar(s string) *btcec.ModNScalar {
b, err := hex.Dec(s)
if err != nil {
panic("invalid hex in source file: " + s)
}
var scalar btcec.ModNScalar
if overflow := scalar.SetByteSlice(b); overflow {
panic("hex in source file overflows mod N scalar: " + s)
}
return &scalar
}
// hexToFieldVal converts the passed hex string into a FieldVal and will panic
// if there is an error. This is only provided for the hard-coded constants, so
// errors in the source code can be detected. It will only (and must only) be
// called with hard-coded values.
func hexToFieldVal(s string) *btcec.FieldVal {
b, err := hex.Dec(s)
if err != nil {
panic("invalid hex in source file: " + s)
}
var f btcec.FieldVal
if overflow := f.SetByteSlice(b); overflow {
panic("hex in source file overflows mod P: " + s)
}
return &f
}
// fromHex converts the passed hex string into a big integer pointer and will
// panic if there is an error. This is only provided for the hard-coded
// constants, so errors in the source code can be detected. It will only (and
// must only) be called for initialization purposes.
func fromHex(s string) *big.Int {
if s == "" {
return big.NewInt(0)
}
r, ok := new(big.Int).SetString(s, 16)
if !ok {
panic("invalid hex in source file: " + s)
}
return r
}
var testOk bool
// BenchmarkSign benchmarks how long it takes to sign a message.
func BenchmarkSign(b *testing.B) {
// Randomly generated keypair.
d := hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d")
privKey := secp256k1.NewSecretKey(d)
// blake256 of by{0x01, 0x02, 0x03, 0x04}.
msgHash := hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
var auxBytes [32]byte
copy(auxBytes[:], msgHash)
auxBytes[0] ^= 1
var (
sig *Signature
err error
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
sig, err = Sign(
privKey, msgHash, CustomNonce(auxBytes), FastSign(),
)
}
testSig = sig
testErr = err
}
// BenchmarkSigVerify benchmarks how long it takes the secp256k1 curve to
// verify signatures.
func BenchmarkSigVerify(b *testing.B) {
// Randomly generated keypair.
d := hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d")
privKey := secp256k1.NewSecretKey(d)
pubKey := privKey.PubKey()
// Double sha256 of by{0x01, 0x02, 0x03, 0x04}
msgHash := sha256.Sum256([]byte("benchmark"))
sig, err := Sign(privKey, msgHash[:])
if err != nil {
b.Fatalf("unable to sign: %v", err)
}
if !sig.Verify(msgHash[:], pubKey) {
b.Errorf("Signature failed to verify")
return
}
var ok bool
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ok = sig.Verify(msgHash[:], pubKey)
}
testOk = ok
}
// Used to ensure the compiler doesn't optimize away the benchmark.
var (
testSig *Signature
testErr error
)
// BenchmarkSignRfc6979 benchmarks how long it takes to sign a message.
func BenchmarkSignRfc6979(b *testing.B) {
// Randomly generated keypair.
d := hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d")
privKey := secp256k1.NewSecretKey(d)
// blake256 of by{0x01, 0x02, 0x03, 0x04}.
msgHash := hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
var (
sig *Signature
err error
)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
sig, err = Sign(privKey, msgHash, FastSign())
}
testSig = sig
testErr = err
}
// BenchmarkSigSerialize benchmarks how long it takes to serialize Schnorr
// signatures.
func BenchmarkSigSerialize(b *testing.B) {
// From randomly generated keypair.
d := hexToModNScalar("9e0699c91ca1e3b7e3c9ba71eb71c89890872be97576010fe593fbf3fd57e66d")
secKey := secp256k1.NewSecretKey(d)
// blake256 of by{0x01, 0x02, 0x03, 0x04}.
msgHash := hexToBytes("c301ba9de5d6053caad9f5eb46523f007702add2c62fa39de03146a36b8026b7")
// Generate the signature.
sig, _ := Sign(secKey, msgHash)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
sig.Serialize()
}
}

View File

@@ -0,0 +1,303 @@
<pre>
BIP: 340
Title: Schnorr Signatures for secp256k1
Author: Pieter Wuille <pieter.wuille@gmail.com>
Jonas Nick <jonasd.nick@gmail.com>
Tim Ruffing <crypto@timruffing.de>
Comments-Summary: No comments yet.
Comments-URI: https://github.com/bitcoin/bips/wiki/Comments:BIP-0340
Status: Final
Type: Standards Track
License: BSD-2-Clause
Created: 2020-01-19
Post-History: 2018-07-06: https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-July/016203.html [bitcoin-dev] Schnorr signatures BIP
</pre>
== Introduction ==
=== Abstract ===
This document proposes a standard for 64-byte Schnorr signatures over the elliptic curve ''secp256k1''.
=== Copyright ===
This document is licensed under the 2-clause BSD license.
=== Motivation ===
Bitcoin has traditionally used
[https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm ECDSA] signatures over the [https://www.secg.org/sec2-v2.pdf secp256k1 curve] with [https://en.wikipedia.org/wiki/SHA-2 SHA256] hashes for authenticating
transactions. These are [https://www.secg.org/sec1-v2.pdf standardized], but have a number of downsides
compared to [http://publikationen.ub.uni-frankfurt.de/opus4/files/4280/schnorr.pdf Schnorr signatures] over the same curve:
* '''Provable security''': Schnorr signatures are provably secure. In more detail, they are ''strongly unforgeable under chosen message attack (SUF-CMA)''<ref>Informally, this means that without knowledge of the secret key but given valid signatures of arbitrary messages, it is not possible to come up with further valid signatures.</ref> [https://www.di.ens.fr/~pointche/Documents/Papers/2000_joc.pdf in the random oracle model assuming the hardness of the elliptic curve discrete logarithm problem (ECDLP)] and [http://www.neven.org/papers/schnorr.pdf in the generic group model assuming variants of preimage and second preimage resistance of the used hash function]<ref>A detailed security proof in the random oracle model, which essentially restates [https://www.di.ens.fr/~pointche/Documents/Papers/2000_joc.pdf the original security proof by Pointcheval and Stern] more explicitly, can be found in [https://eprint.iacr.org/2016/191 a paper by Kiltz, Masny and Pan]. All these security proofs assume a variant of Schnorr signatures that use ''(e,s)'' instead of ''(R,s)'' (see Design above). Since we use a unique encoding of ''R'', there is an efficiently computable bijection that maps ''(R,s)'' to ''(e,s)'', which allows to convert a successful SUF-CMA attacker for the ''(e,s)'' variant to a successful SUF-CMA attacker for the ''(R,s)'' variant (and vice-versa). Furthermore, the proofs consider a variant of Schnorr signatures without key prefixing (see Design above), but it can be verified that the proofs are also correct for the variant with key prefixing. As a result, all the aforementioned security proofs apply to the variant of Schnorr signatures proposed in this document.</ref>. In contrast, the [https://nbn-resolving.de/urn:nbn:de:hbz:294-60803 best known results for the provable security of ECDSA] rely on stronger assumptions.
* '''Non-malleability''': The SUF-CMA security of Schnorr signatures implies that they are non-malleable. On the other hand, ECDSA signatures are inherently malleable<ref>If ''(r,s)'' is a valid ECDSA signature for a given message and key, then ''(r,n-s)'' is also valid for the same message and key. If ECDSA is restricted to only permit one of the two variants (as Bitcoin does through a policy rule on the network), it can be [https://nbn-resolving.de/urn:nbn:de:hbz:294-60803 proven] non-malleable under stronger than usual assumptions.</ref>; a third party without access to the secret key can alter an existing valid signature for a given public key and message into another signature that is valid for the same key and message. This issue is discussed in [[bip-0062.mediawiki|BIP62]] and [[bip-0146.mediawiki|BIP146]].
* '''Linearity''': Schnorr signatures provide a simple and efficient method that enables multiple collaborating parties to produce a signature that is valid for the sum of their public keys. This is the building block for various higher-level constructions that improve efficiency and privacy, such as multisignatures and others (see Applications below).
For all these advantages, there are virtually no disadvantages, apart
from not being standardized. This document seeks to change that. As we
propose a new standard, a number of improvements not specific to Schnorr signatures can be
made:
* '''Signature encoding''': Instead of using [https://en.wikipedia.org/wiki/X.690#DER_encoding DER]-encoding for signatures (which are variable size, and up to 72 bytes), we can use a simple fixed 64-byte format.
* '''Public key encoding''': Instead of using [https://www.secg.org/sec1-v2.pdf ''compressed''] 33-byte encodings of elliptic curve points which are common in Bitcoin today, public keys in this proposal are encoded as 32 bytes.
* '''Batch verification''': The specific formulation of ECDSA signatures that is standardized cannot be verified more efficiently in batch compared to individually, unless additional witness data is added. Changing the signature scheme offers an opportunity to address this.
* '''Completely specified''': To be safe for usage in consensus systems, the verification algorithm must be completely specified at the byte level. This guarantees that nobody can construct a signature that is valid to some verifiers but not all. This is traditionally not a requirement for digital signature schemes, and the lack of exact specification for the DER parsing of ECDSA signatures has caused problems for Bitcoin [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2015-July/009697.html in the past], needing [[bip-0066.mediawiki|BIP66]] to address it. In this document we aim to meet this property by design. For batch verification, which is inherently non-deterministic as the verifier can choose their batches, this property implies that the outcome of verification may only differ from individual verifications with negligible probability, even to an attacker who intentionally tries to make batch- and non-batch verification differ.
By reusing the same curve and hash function as Bitcoin uses for ECDSA, we are able to retain existing mechanisms for choosing secret and public keys, and we avoid introducing new assumptions about the security of elliptic curves and hash functions.
== Description ==
We first build up the algebraic formulation of the signature scheme by
going through the design choices. Afterwards, we specify the exact
encodings and operations.
=== Design ===
'''Schnorr signature variant''' Elliptic Curve Schnorr signatures for message ''m'' and public key ''P'' generally involve a point ''R'', integers ''e'' and ''s'' picked by the signer, and the base point ''G'' which satisfy ''e = hash(R || m)'' and ''s⋅G = R + e⋅P''. Two formulations exist, depending on whether the signer reveals ''e'' or ''R'':
# Signatures are pairs ''(e, s)'' that satisfy ''e = hash(s⋅G - e⋅P || m)''. This variant avoids minor complexity introduced by the encoding of the point ''R'' in the signature (see paragraphs "Encoding R and public key point P" and "Implicit Y coordinates" further below in this subsection). Moreover, revealing ''e'' instead of ''R'' allows for potentially shorter signatures: Whereas an encoding of ''R'' inherently needs about 32 bytes, the hash ''e'' can be tuned to be shorter than 32 bytes, and [http://www.neven.org/papers/schnorr.pdf a short hash of only 16 bytes suffices to provide SUF-CMA security at the target security level of 128 bits]. However, a major drawback of this optimization is that finding collisions in a short hash function is easy. This complicates the implementation of secure signing protocols in scenarios in which a group of mutually distrusting signers work together to produce a single joint signature (see Applications below). In these scenarios, which are not captured by the SUF-CMA model due its assumption of a single honest signer, a promising attack strategy for malicious co-signers is to find a collision in the hash function in order to obtain a valid signature on a message that an honest co-signer did not intend to sign.
# Signatures are pairs ''(R, s)'' that satisfy ''s⋅G = R + hash(R || m)⋅P''. This supports batch verification, as there are no elliptic curve operations inside the hashes. Batch verification enables significant speedups.<ref>The speedup that results from batch verification can be demonstrated with the cryptography library [https://github.com/jonasnick/secp256k1/blob/schnorrsig-batch-verify/doc/speedup-batch.md libsecp256k1].</ref>
Since we would like to avoid the fragility that comes with short hashes, the ''e'' variant does not provide significant advantages. We choose the ''R''-option, which supports batch verification.
'''Key prefixing''' Using the verification rule above directly makes Schnorr signatures vulnerable to "related-key attacks" in which a third party can convert a signature ''(R, s)'' for public key ''P'' into a signature ''(R, s + a⋅hash(R || m))'' for public key ''P + a⋅G'' and the same message ''m'', for any given additive tweak ''a'' to the signing key. This would render signatures insecure when keys are generated using [[bip-0032.mediawiki#public-parent-key--public-child-key|BIP32's unhardened derivation]] and other methods that rely on additive tweaks to existing keys such as Taproot.
To protect against these attacks, we choose ''key prefixed''<ref>A limitation of committing to the public key (rather than to a short hash of it, or not at all) is that it removes the ability for public key recovery or verifying signatures against a short public key hash. These constructions are generally incompatible with batch verification.</ref> Schnorr signatures which means that the public key is prefixed to the message in the challenge hash input. This changes the equation to ''s⋅G = R + hash(R || P || m)⋅P''. [https://eprint.iacr.org/2015/1135.pdf It can be shown] that key prefixing protects against related-key attacks with additive tweaks. In general, key prefixing increases robustness in multi-user settings, e.g., it seems to be a requirement for proving the MuSig multisignature scheme secure (see Applications below).
We note that key prefixing is not strictly necessary for transaction signatures as used in Bitcoin currently, because signed transactions indirectly commit to the public keys already, i.e., ''m'' contains a commitment to ''pk''. However, this indirect commitment should not be relied upon because it may change with proposals such as SIGHASH_NOINPUT ([[bip-0118.mediawiki|BIP118]]), and would render the signature scheme unsuitable for other purposes than signing transactions, e.g., [https://bitcoin.org/en/developer-reference#signmessage signing ordinary messages].
'''Encoding R and public key point P''' There exist several possibilities for encoding elliptic curve points:
# Encoding the full X and Y coordinates of ''P'' and ''R'', resulting in a 64-byte public key and a 96-byte signature.
# Encoding the full X coordinate and one bit of the Y coordinate to determine one of the two possible Y coordinates. This would result in 33-byte public keys and 65-byte signatures.
# Encoding only the X coordinate, resulting in 32-byte public keys and 64-byte signatures.
Using the first option would be slightly more efficient for verification (around 10%), but we prioritize compactness, and therefore choose option 3.
'''Implicit Y coordinates''' In order to support efficient verification and batch verification, the Y coordinate of ''P'' and of ''R'' cannot be ambiguous (every valid X coordinate has two possible Y coordinates). We have a choice between several options for symmetry breaking:
# Implicitly choosing the Y coordinate that is in the lower half.
# Implicitly choosing the Y coordinate that is even<ref>Since ''p'' is odd, negation modulo ''p'' will map even numbers to odd numbers and the other way around. This means that for a valid X coordinate, one of the corresponding Y coordinates will be even, and the other will be odd.</ref>.
# Implicitly choosing the Y coordinate that is a quadratic residue (i.e. has a square root modulo ''p'').
The second option offers the greatest compatibility with existing key generation systems, where the standard 33-byte compressed public key format consists of a byte indicating the oddness of the Y coordinate, plus the full X coordinate. To avoid gratuitous incompatibilities, we pick that option for ''P'', and thus our X-only public keys become equivalent to a compressed public key that is the X-only key prefixed by the byte 0x02. For consistency, the same is done for ''R''<ref>An earlier version of this draft used the third option instead, based on a belief that this would in general trade signing efficiency for verification efficiency. When using Jacobian coordinates, a common optimization in ECC implementations, it is possible to determine if a Y coordinate is a quadratic residue by computing the Legendre symbol, without converting to affine coordinates first (which needs a modular inversion). As modular inverses and Legendre symbols have similar [https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-August/018081.html performance] in practice, this trade-off is not worth it.</ref>.
Despite halving the size of the set of valid public keys, implicit Y coordinates are not a reduction in security. Informally, if a fast algorithm existed to compute the discrete logarithm of an X-only public key, then it could also be used to compute the discrete logarithm of a full public key: apply it to the X coordinate, and then optionally negate the result. This shows that breaking an X-only public key can be at most a small constant term faster than breaking a full one.<ref>This can be formalized by a simple reduction that reduces an attack on Schnorr signatures with implicit Y coordinates to an attack to Schnorr signatures with explicit Y coordinates. The reduction works by reencoding public keys and negating the result of the hash function, which is modeled as random oracle, whenever the challenge public key has an explicit Y coordinate that is odd. A proof sketch can be found [https://medium.com/blockstream/reducing-bitcoin-transaction-sizes-with-x-only-pubkeys-f86476af05d7 here].</ref>.
'''Tagged Hashes''' Cryptographic hash functions are used for multiple purposes in the specification below and in Bitcoin in general. To make sure hashes used in one context can't be reinterpreted in another one, hash functions can be tweaked with a context-dependent tag name, in such a way that collisions across contexts can be assumed to be infeasible. Such collisions obviously can not be ruled out completely, but only for schemes using tagging with a unique name. As for other schemes collisions are at least less likely with tagging than without.
For example, without tagged hashing a BIP340 signature could also be valid for a signature scheme where the only difference is that the arguments to the hash function are reordered. Worse, if the BIP340 nonce derivation function was copied or independently created, then the nonce could be accidentally reused in the other scheme leaking the secret key.
This proposal suggests to include the tag by prefixing the hashed data with ''SHA256(tag) || SHA256(tag)''. Because this is a 64-byte long context-specific constant and the ''SHA256'' block size is also 64 bytes, optimized implementations are possible (identical to SHA256 itself, but with a modified initial state). Using SHA256 of the tag name itself is reasonably simple and efficient for implementations that don't choose to use the optimization. In general, tags can be arbitrary byte arrays, but are suggested to be textual descriptions in UTF-8 encoding.
'''Final scheme''' As a result, our final scheme ends up using public key ''pk'' which is the X coordinate of a point ''P'' on the curve whose Y coordinate is even and signatures ''(r,s)'' where ''r'' is the X coordinate of a point ''R'' whose Y coordinate is even. The signature satisfies ''s⋅G = R + tagged_hash(r || pk || m)⋅P''.
=== Specification ===
The following conventions are used, with constants as defined for [https://www.secg.org/sec2-v2.pdf secp256k1]. We note that adapting this specification to other elliptic curves is not straightforward and can result in an insecure scheme<ref>Among other pitfalls, using the specification with a curve whose order is not close to the size of the range of the nonce derivation function is insecure.</ref>.
* Lowercase variables represent integers or byte arrays.
** The constant ''p'' refers to the field size, ''0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F''.
** The constant ''n'' refers to the curve order, ''0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141''.
* Uppercase variables refer to points on the curve with equation ''y<sup>2</sup> = x<sup>3</sup> + 7'' over the integers modulo ''p''.
** ''is_infinite(P)'' returns whether or not ''P'' is the point at infinity.
** ''x(P)'' and ''y(P)'' are integers in the range ''0..p-1'' and refer to the X and Y coordinates of a point ''P'' (assuming it is not infinity).
** The constant ''G'' refers to the base point, for which ''x(G) = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798'' and ''y(G) = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8''.
** Addition of points refers to the usual [https://en.wikipedia.org/wiki/Elliptic_curve#The_group_law elliptic curve group operation].
** [https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication Multiplication (⋅) of an integer and a point] refers to the repeated application of the group operation.
* Functions and operations:
** ''||'' refers to byte array concatenation.
** The function ''x[i:j]'', where ''x'' is a byte array and ''i, j &ge; 0'', returns a ''(j - i)''-byte array with a copy of the ''i''-th byte (inclusive) to the ''j''-th byte (exclusive) of ''x''.
** The function ''bytes(x)'', where ''x'' is an integer, returns the 32-byte encoding of ''x'', most significant byte first.
** The function ''bytes(P)'', where ''P'' is a point, returns ''bytes(x(P))''.
** The function ''int(x)'', where ''x'' is a 32-byte array, returns the 256-bit unsigned integer whose most significant byte first encoding is ''x''.
** The function ''has_even_y(P)'', where ''P'' is a point for which ''not is_infinite(P)'', returns ''y(P) mod 2 = 0''.
** The function ''lift_x(x)'', where ''x'' is a 256-bit unsigned integer, returns the point ''P'' for which ''x(P) = x''<ref>
Given a candidate X coordinate ''x'' in the range ''0..p-1'', there exist either exactly two or exactly zero valid Y coordinates. If no valid Y coordinate exists, then ''x'' is not a valid X coordinate either, i.e., no point ''P'' exists for which ''x(P) = x''. The valid Y coordinates for a given candidate ''x'' are the square roots of ''c = x<sup>3</sup> + 7 mod p'' and they can be computed as ''y = &plusmn;c<sup>(p+1)/4</sup> mod p'' (see [https://en.wikipedia.org/wiki/Quadratic_residue#Prime_or_prime_power_modulus Quadratic residue]) if they exist, which can be checked by squaring and comparing with ''c''.</ref> and ''has_even_y(P)'', or fails if ''x'' is greater than ''p-1'' or no such point exists. The function ''lift_x(x)'' is equivalent to the following pseudocode:
*** Fail if ''x &ge; p''.
*** Let ''c = x<sup>3</sup> + 7 mod p''.
*** Let ''y = c<sup>(p+1)/4</sup> mod p''.
*** Fail if ''c &ne; y<sup>2</sup> mod p''.
*** Return the unique point ''P'' such that ''x(P) = x'' and ''y(P) = y'' if ''y mod 2 = 0'' or ''y(P) = p-y'' otherwise.
** The function ''hash<sub>name</sub>(x)'' where ''x'' is a byte array returns the 32-byte hash ''SHA256(SHA256(tag) || SHA256(tag) || x)'', where ''tag'' is the UTF-8 encoding of ''name''.
==== Public Key Generation ====
Input:
* The secret key ''sk'': a 32-byte array, freshly generated uniformly at random
The algorithm ''PubKey(sk)'' is defined as:
* Let ''d' = int(sk)''.
* Fail if ''d' = 0'' or ''d' &ge; n''.
* Return ''bytes(d'⋅G)''.
Note that we use a very different public key format (32 bytes) than the ones used by existing systems (which typically use elliptic curve points as public keys, or 33-byte or 65-byte encodings of them). A side effect is that ''PubKey(sk) = PubKey(bytes(n - int(sk))'', so every public key has two corresponding secret keys.
==== Public Key Conversion ====
As an alternative to generating keys randomly, it is also possible and safe to repurpose existing key generation algorithms for ECDSA in a compatible way. The secret keys constructed by such an algorithm can be used as ''sk'' directly. The public keys constructed by such an algorithm (assuming they use the 33-byte compressed encoding) need to be converted by dropping the first byte. Specifically, [[bip-0032.mediawiki|BIP32]] and schemes built on top of it remain usable.
==== Default Signing ====
Input:
* The secret key ''sk'': a 32-byte array
* The message ''m'': a byte array
* Auxiliary random data ''a'': a 32-byte array
The algorithm ''Sign(sk, m)'' is defined as:
* Let ''d' = int(sk)''
* Fail if ''d' = 0'' or ''d' &ge; n''
* Let ''P = d'⋅G''
* Let ''d = d' '' if ''has_even_y(P)'', otherwise let ''d = n - d' ''.
* Let ''t'' be the byte-wise xor of ''bytes(d)'' and ''hash<sub>BIP0340/aux</sub>(a)''<ref>The auxiliary random data is hashed (with a unique tag) as a precaution against situations where the randomness may be correlated with the private key itself. It is xored with the private key (rather than combined with it in a hash) to reduce the number of operations exposed to the actual secret key.</ref>.
* Let ''rand = hash<sub>BIP0340/nonce</sub>(t || bytes(P) || m)''<ref>Including the [https://moderncrypto.org/mail-archive/curves/2020/001012.html public key as input to the nonce hash] helps ensure the robustness of the signing algorithm by preventing leakage of the secret key if the calculation of the public key ''P'' is performed incorrectly or maliciously, for example if it is left to the caller for performance reasons.</ref>.
* Let ''k' = int(rand) mod n''<ref>Note that in general, taking a uniformly random 256-bit integer modulo the curve order will produce an unacceptably biased result. However, for the secp256k1 curve, the order is sufficiently close to ''2<sup>256</sup>'' that this bias is not observable (''1 - n / 2<sup>256</sup>'' is around ''1.27 * 2<sup>-128</sup>'').</ref>.
* Fail if ''k' = 0''.
* Let ''R = k'⋅G''.
* Let ''k = k' '' if ''has_even_y(R)'', otherwise let ''k = n - k' ''.
* Let ''e = int(hash<sub>BIP0340/challenge</sub>(bytes(R) || bytes(P) || m)) mod n''.
* Let ''sig = bytes(R) || bytes((k + ed) mod n)''.
* If ''Verify(bytes(P), m, sig)'' (see below) returns failure, abort<ref>Verifying the signature before leaving the signer prevents random or attacker provoked computation errors. This prevents publishing invalid signatures which may leak information about the secret key. It is recommended, but can be omitted if the computation cost is prohibitive.</ref>.
* Return the signature ''sig''.
The auxiliary random data should be set to fresh randomness generated at signing time, resulting in what is called a ''synthetic nonce''. Using 32 bytes of randomness is optimal. If obtaining randomness is expensive, 16 random bytes can be padded with 16 null bytes to obtain a 32-byte array. If randomness is not available at all at signing time, a simple counter wide enough to not repeat in practice (e.g., 64 bits or wider) and padded with null bytes to a 32 byte-array can be used, or even the constant array with 32 null bytes. Using any non-repeating value increases protection against [https://moderncrypto.org/mail-archive/curves/2017/000925.html fault injection attacks]. Using unpredictable randomness additionally increases protection against other side-channel attacks, and is '''recommended whenever available'''. Note that while this means the resulting nonce is not deterministic, the randomness is only supplemental to security. The normal security properties (excluding side-channel attacks) do not depend on the quality of the signing-time RNG.
==== Alternative Signing ====
It should be noted that various alternative signing algorithms can be used to produce equally valid signatures. The 32-byte ''rand'' value may be generated in other ways, producing a different but still valid signature (in other words, this is not a ''unique'' signature scheme). '''No matter which method is used to generate the ''rand'' value, the value must be a fresh uniformly random 32-byte string which is not even partially predictable for the attacker.''' For nonces without randomness this implies that the same inputs must not be presented in another context. This can be most reliably accomplished by not reusing the same private key across different signing schemes. For example, if the ''rand'' value was computed as per RFC6979 and the same secret key is used in deterministic ECDSA with RFC6979, the signatures can leak the secret key through nonce reuse.
'''Nonce exfiltration protection''' It is possible to strengthen the nonce generation algorithm using a second device. In this case, the second device contributes randomness which the actual signer provably incorporates into its nonce. This prevents certain attacks where the signer device is compromised and intentionally tries to leak the secret key through its nonce selection.
'''Multisignatures''' This signature scheme is compatible with various types of multisignature and threshold schemes such as [https://eprint.iacr.org/2018/068 MuSig], where a single public key requires holders of multiple secret keys to participate in signing (see Applications below).
'''It is important to note that multisignature signing schemes in general are insecure with the ''rand'' generation from the default signing algorithm above (or any other deterministic method).'''
'''Precomputed public key data''' For many uses the compressed 33-byte encoding of the public key corresponding to the secret key may already be known, making it easy to evaluate ''has_even_y(P)'' and ''bytes(P)''. As such, having signers supply this directly may be more efficient than recalculating the public key from the secret key. However, if this optimization is used and additionally the signature verification at the end of the signing algorithm is dropped for increased efficiency, signers must ensure the public key is correctly calculated and not taken from untrusted sources.
==== Verification ====
Input:
* The public key ''pk'': a 32-byte array
* The message ''m'': a byte array
* A signature ''sig'': a 64-byte array
The algorithm ''Verify(pk, m, sig)'' is defined as:
* Let ''P = lift_x(int(pk))''; fail if that fails.
* Let ''r = int(sig[0:32])''; fail if ''r &ge; p''.
* Let ''s = int(sig[32:64])''; fail if ''s &ge; n''.
* Let ''e = int(hash<sub>BIP0340/challenge</sub>(bytes(r) || bytes(P) || m)) mod n''.
* Let ''R = s⋅G - e⋅P''.
* Fail if ''is_infinite(R)''.
* Fail if ''not has_even_y(R)''.
* Fail if ''x(R) &ne; r''.
* Return success iff no failure occurred before reaching this point.
For every valid secret key ''sk'' and message ''m'', ''Verify(PubKey(sk),m,Sign(sk,m))'' will succeed.
Note that the correctness of verification relies on the fact that ''lift_x'' always returns a point with an even Y coordinate. A hypothetical verification algorithm that treats points as public keys, and takes the point ''P'' directly as input would fail any time a point with odd Y is used. While it is possible to correct for this by negating points with odd Y coordinate before further processing, this would result in a scheme where every (message, signature) pair is valid for two public keys (a type of malleability that exists for ECDSA as well, but we don't wish to retain). We avoid these problems by treating just the X coordinate as public key.
==== Batch Verification ====
Input:
* The number ''u'' of signatures
* The public keys ''pk<sub>1..u</sub>'': ''u'' 32-byte arrays
* The messages ''m<sub>1..u</sub>'': ''u'' byte arrays
* The signatures ''sig<sub>1..u</sub>'': ''u'' 64-byte arrays
The algorithm ''BatchVerify(pk<sub>1..u</sub>, m<sub>1..u</sub>, sig<sub>1..u</sub>)'' is defined as:
* Generate ''u-1'' random integers ''a<sub>2...u</sub>'' in the range ''1...n-1''. They are generated deterministically using a [https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator CSPRNG] seeded by a cryptographic hash of all inputs of the algorithm, i.e. ''seed = seed_hash(pk<sub>1</sub>..pk<sub>u</sub> || m<sub>1</sub>..m<sub>u</sub> || sig<sub>1</sub>..sig<sub>u</sub> )''. A safe choice is to instantiate ''seed_hash'' with SHA256 and use [https://tools.ietf.org/html/rfc8439 ChaCha20] with key ''seed'' as a CSPRNG to generate 256-bit integers, skipping integers not in the range ''1...n-1''.
* For ''i = 1 .. u'':
** Let ''P<sub>i</sub> = lift_x(int(pk<sub>i</sub>))''; fail if it fails.
** Let ''r<sub>i</sub> = int(sig<sub>i</sub>[0:32])''; fail if ''r<sub>i</sub> &ge; p''.
** Let ''s<sub>i</sub> = int(sig<sub>i</sub>[32:64])''; fail if ''s<sub>i</sub> &ge; n''.
** Let ''e<sub>i</sub> = int(hash<sub>BIP0340/challenge</sub>(bytes(r<sub>i</sub>) || bytes(P<sub>i</sub>) || m<sub>i</sub>)) mod n''.
** Let ''R<sub>i</sub> = lift_x(r<sub>i</sub>)''; fail if ''lift_x(r<sub>i</sub>)'' fails.
* Fail if ''(s<sub>1</sub> + a<sub>2</sub>s<sub>2</sub> + ... + a<sub>u</sub>s<sub>u</sub>)⋅G &ne; R<sub>1</sub> + a<sub>2</sub>⋅R<sub>2</sub> + ... + a<sub>u</sub>⋅R<sub>u</sub> + e<sub>1</sub>⋅P<sub>1</sub> + (a<sub>2</sub>e<sub>2</sub>)⋅P<sub>2</sub> + ... + (a<sub>u</sub>e<sub>u</sub>)⋅P<sub>u</sub>''.
* Return success iff no failure occurred before reaching this point.
If all individual signatures are valid (i.e., ''Verify'' would return success for them), ''BatchVerify'' will always return success. If at least one signature is invalid, ''BatchVerify'' will return success with at most a negligible probability.
=== Usage Considerations ===
==== Messages of Arbitrary Size ====
The signature scheme specified in this BIP accepts byte strings of arbitrary size as input messages.<ref>In theory, the message size is restricted due to the fact that SHA256 accepts byte strings only up to size of 2^61-1 bytes.</ref>
It is understood that implementations may reject messages which are too large in their environment or application context,
e.g., messages which exceed predefined buffers or would otherwise cause resource exhaustion.
Earlier revisions of this BIP required messages to be exactly 32 bytes.
This restriction puts a burden on callers
who typically need to perform pre-hashing of the actual input message by feeding it through SHA256 (or another collision-resistant cryptographic hash function)
to create a 32-byte digest which can be passed to signing or verification
(as for example done in [[bip-0341.mediawiki|BIP341]].)
Since pre-hashing may not always be desirable,
e.g., when actual messages are shorter than 32 bytes,<ref>Another reason to omit pre-hashing is to protect against certain types of cryptanalytic advances against the hash function used for pre-hashing: If pre-hashing is used, an attacker that can find collisions in the pre-hashing function can necessarily forge signatures under chosen-message attacks. If pre-hashing is not used, an attacker that can find collisions in SHA256 (as used inside the signature scheme) may not be able to forge signatures. However, this seeming advantage is mostly irrelevant in the context of Bitcoin, which already relies on collision resistance of SHA256 in other places, e.g., for transaction hashes.</ref>
the restriction to 32-byte messages has been lifted.
We note that pre-hashing is recommended for performance reasons in applications that deal with large messages.
If large messages are not pre-hashed,
the algorithms of the signature scheme will perform more hashing internally.
In particular, the signing algorithm needs two sequential hashing passes over the message,
which means that the full message must necessarily be kept in memory during signing,
and large messages entail a runtime penalty.<ref>Typically, messages of 56 bytes or longer enjoy a performance benefit from pre-hashing, assuming the speed of SHA256 inside the signing algorithm matches that of the pre-hashing done by the calling application.</ref>
==== Domain Separation ====
It is good cryptographic practice to use a key pair only for a single purpose.
Nevertheless, there may be situations in which it may be desirable to use the same key pair in multiple contexts,
i.e., to sign different types of messages within the same application
or even messages in entirely different applications
(e.g., a secret key may be used to sign Bitcoin transactions as well plain text messages).
As a consequence, applications should ensure that a signed application message intended for one context is never deemed valid in a different context
(e.g., a signed plain text message should never be misinterpreted as a signed Bitcoin transaction, because this could cause unintended loss of funds).
This is called "domain separation" and it is typically realized by partitioning the message space.
Even if key pairs are intended to be used only within a single context,
domain separation is a good idea because it makes it easy to add more contexts later.
As a best practice, we recommend applications to use exactly one of the following methods to pre-process application messages before passing it to the signature scheme:
* Either, pre-hash the application message using ''hash<sub>name</sub>'', where ''name'' identifies the context uniquely (e.g., "foo-app/signed-bar"),
* or prefix the actual message with a 33-byte string that identifies the context uniquely (e.g., the UTF-8 encoding of "foo-app/signed-bar", padded with null bytes to 33 bytes).
As the two pre-processing methods yield different message sizes (32 bytes vs. at least 33 bytes), there is no risk of collision between them.
== Applications ==
There are several interesting applications beyond simple signatures.
While recent academic papers claim that they are also possible with ECDSA, consensus support for Schnorr signature verification would significantly simplify the constructions.
=== Multisignatures and Threshold Signatures ===
By means of an interactive scheme such as [https://eprint.iacr.org/2018/068 MuSig], participants can aggregate their public keys into a single public key which they can jointly sign for. This allows ''n''-of-''n'' multisignatures which, from a verifier's perspective, are no different from ordinary signatures, giving improved privacy and efficiency versus ''CHECKMULTISIG'' or other means.
Moreover, Schnorr signatures are compatible with [https://web.archive.org/web/20031003232851/http://www.research.ibm.com/security/dkg.ps distributed key generation], which enables interactive threshold signatures schemes, e.g., the schemes described by [http://cacr.uwaterloo.ca/techreports/2001/corr2001-13.ps Stinson and Strobl (2001)] or [https://web.archive.org/web/20060911151529/http://theory.lcs.mit.edu/~stasio/Papers/gjkr03.pdf Gennaro, Jarecki and Krawczyk (2003)]. These protocols make it possible to realize ''k''-of-''n'' threshold signatures, which ensure that any subset of size ''k'' of the set of ''n'' signers can sign but no subset of size less than ''k'' can produce a valid Schnorr signature. However, the practicality of the existing schemes is limited: most schemes in the literature have been proven secure only for the case ''k-1 < n/2'', are not secure when used concurrently in multiple sessions, or require a reliable broadcast mechanism to be secure. Further research is necessary to improve this situation.
=== Adaptor Signatures ===
[https://download.wpsoftware.net/bitcoin/wizardry/mw-slides/2018-05-18-l2/slides.pdf Adaptor signatures] can be produced by a signer by offsetting his public nonce ''R'' with a known point ''T = t⋅G'', but not offsetting the signature's ''s'' value.
A correct signature (or partial signature, as individual signers' contributions to a multisignature are called) on the same message with same nonce will then be equal to the adaptor signature offset by ''t'', meaning that learning ''t'' is equivalent to learning a correct signature.
This can be used to enable atomic swaps or even [https://eprint.iacr.org/2018/472 general payment channels] in which the atomicity of disjoint transactions is ensured using the signatures themselves, rather than Bitcoin script support. The resulting transactions will appear to verifiers to be no different from ordinary single-signer transactions, except perhaps for the inclusion of locktime refund logic.
Adaptor signatures, beyond the efficiency and privacy benefits of encoding script semantics into constant-sized signatures, have additional benefits over traditional hash-based payment channels. Specifically, the secret values ''t'' may be reblinded between hops, allowing long chains of transactions to be made atomic while even the participants cannot identify which transactions are part of the chain. Also, because the secret values are chosen at signing time, rather than key generation time, existing outputs may be repurposed for different applications without recourse to the blockchain, even multiple times.
=== Blind Signatures ===
A blind signature protocol is an interactive protocol that enables a signer to sign a message at the behest of another party without learning any information about the signed message or the signature. Schnorr signatures admit a very [http://publikationen.ub.uni-frankfurt.de/files/4292/schnorr.blind_sigs_attack.2001.pdf simple blind signature scheme] which is however insecure because it's vulnerable to [https://www.iacr.org/archive/crypto2002/24420288/24420288.pdf Wagner's attack]. A known mitigation is to let the signer abort a signing session with a certain probability, and the resulting scheme can be [https://eprint.iacr.org/2019/877 proven secure under non-standard cryptographic assumptions].
Blind Schnorr signatures could for example be used in [https://github.com/ElementsProject/scriptless-scripts/blob/master/md/partially-blind-swap.md Partially Blind Atomic Swaps], a construction to enable transferring of coins, mediated by an untrusted escrow agent, without connecting the transactors in the public blockchain transaction graph.
== Test Vectors and Reference Code ==
For development and testing purposes, we provide a [[bip-0340/test-vectors.csv|collection of test vectors in CSV format]] and a naive, highly inefficient, and non-constant time [[bip-0340/reference.py|pure Python 3.7 reference implementation of the signing and verification algorithm]].
The reference implementation is for demonstration purposes only and not to be used in production environments.
== Changelog ==
To help implementors understand updates to this BIP, we keep a list of substantial changes.
* 2022-08: Fix function signature of lift_x in reference code
* 2023-04: Allow messages of arbitrary size
== Footnotes ==
<references />
== Acknowledgements ==
This document is the result of many discussions around Schnorr based signatures over the years, and had input from Johnson Lau, Greg Maxwell, Andrew Poelstra, Rusty Russell, and Anthony Towns. The authors further wish to thank all those who provided valuable feedback and reviews, including the participants of the [https://github.com/ajtowns/taproot-review structured reviews].

View File

@@ -0,0 +1,244 @@
from typing import Tuple, Optional, Any
import hashlib
import binascii
# Set DEBUG to True to get a detailed debug output including
# intermediate values during key generation, signing, and
# verification. This is implemented via calls to the
# debug_print_vars() function.
#
# If you want to print values on an individual basis, use
# the pretty() function, e.g., print(pretty(foo)).
DEBUG = False
p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
# Points are tuples of X and Y coordinates and the point at infinity is
# represented by the None keyword.
G = (0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8)
Point = Tuple[int, int]
# This implementation can be sped up by storing the midstate after hashing
# tag_hash instead of rehashing it all the time.
def tagged_hash(tag: str, msg: bytes) -> bytes:
tag_hash = hashlib.sha256(tag.encode()).digest()
return hashlib.sha256(tag_hash + tag_hash + msg).digest()
def is_infinite(P: Optional[Point]) -> bool:
return P is None
def x(P: Point) -> int:
assert not is_infinite(P)
return P[0]
def y(P: Point) -> int:
assert not is_infinite(P)
return P[1]
def point_add(P1: Optional[Point], P2: Optional[Point]) -> Optional[Point]:
if P1 is None:
return P2
if P2 is None:
return P1
if (x(P1) == x(P2)) and (y(P1) != y(P2)):
return None
if P1 == P2:
lam = (3 * x(P1) * x(P1) * pow(2 * y(P1), p - 2, p)) % p
else:
lam = ((y(P2) - y(P1)) * pow(x(P2) - x(P1), p - 2, p)) % p
x3 = (lam * lam - x(P1) - x(P2)) % p
return (x3, (lam * (x(P1) - x3) - y(P1)) % p)
def point_mul(P: Optional[Point], n: int) -> Optional[Point]:
R = None
for i in range(256):
if (n >> i) & 1:
R = point_add(R, P)
P = point_add(P, P)
return R
def bytes_from_int(x: int) -> bytes:
return x.to_bytes(32, byteorder="big")
def bytes_from_point(P: Point) -> bytes:
return bytes_from_int(x(P))
def xor_bytes(b0: bytes, b1: bytes) -> bytes:
return bytes(x ^ y for (x, y) in zip(b0, b1))
def lift_x(x: int) -> Optional[Point]:
if x >= p:
return None
y_sq = (pow(x, 3, p) + 7) % p
y = pow(y_sq, (p + 1) // 4, p)
if pow(y, 2, p) != y_sq:
return None
return (x, y if y & 1 == 0 else p - y)
def int_from_bytes(b: bytes) -> int:
return int.from_bytes(b, byteorder="big")
def hash_sha256(b: bytes) -> bytes:
return hashlib.sha256(b).digest()
def has_even_y(P: Point) -> bool:
assert not is_infinite(P)
return y(P) % 2 == 0
def pubkey_gen(seckey: bytes) -> bytes:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= n - 1):
raise ValueError('The secret key must be an integer in the range 1..n-1.')
P = point_mul(G, d0)
assert P is not None
return bytes_from_point(P)
def schnorr_sign(msg: bytes, seckey: bytes, aux_rand: bytes) -> bytes:
d0 = int_from_bytes(seckey)
if not (1 <= d0 <= n - 1):
raise ValueError('The secret key must be an integer in the range 1..n-1.')
if len(aux_rand) != 32:
raise ValueError('aux_rand must be 32 bytes instead of %i.' % len(aux_rand))
P = point_mul(G, d0)
assert P is not None
d = d0 if has_even_y(P) else n - d0
t = xor_bytes(bytes_from_int(d), tagged_hash("BIP0340/aux", aux_rand))
k0 = int_from_bytes(tagged_hash("BIP0340/nonce", t + bytes_from_point(P) + msg)) % n
if k0 == 0:
raise RuntimeError('Failure. This happens only with negligible probability.')
R = point_mul(G, k0)
assert R is not None
k = n - k0 if not has_even_y(R) else k0
e = int_from_bytes(tagged_hash("BIP0340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n
sig = bytes_from_point(R) + bytes_from_int((k + e * d) % n)
debug_print_vars()
if not schnorr_verify(msg, bytes_from_point(P), sig):
raise RuntimeError('The created signature does not pass verification.')
return sig
def schnorr_verify(msg: bytes, pubkey: bytes, sig: bytes) -> bool:
if len(pubkey) != 32:
raise ValueError('The public key must be a 32-byte array.')
if len(sig) != 64:
raise ValueError('The signature must be a 64-byte array.')
P = lift_x(int_from_bytes(pubkey))
r = int_from_bytes(sig[0:32])
s = int_from_bytes(sig[32:64])
if (P is None) or (r >= p) or (s >= n):
debug_print_vars()
return False
e = int_from_bytes(tagged_hash("BIP0340/challenge", sig[0:32] + pubkey + msg)) % n
R = point_add(point_mul(G, s), point_mul(P, n - e))
if (R is None) or (not has_even_y(R)) or (x(R) != r):
debug_print_vars()
return False
debug_print_vars()
return True
#
# The following code is only used to verify the test vectors.
#
import csv
import os
import sys
def test_vectors() -> bool:
all_passed = True
with open(os.path.join(sys.path[0], 'test-vectors.csv'), newline='') as csvfile:
reader = csv.reader(csvfile)
reader.__next__()
for row in reader:
(index, seckey_hex, pubkey_hex, aux_rand_hex, msg_hex, sig_hex, result_str, comment) = row
pubkey = bytes.fromhex(pubkey_hex)
msg = bytes.fromhex(msg_hex)
sig = bytes.fromhex(sig_hex)
result = result_str == 'TRUE'
print('\nTest vector', ('#' + index).rjust(3, ' ') + ':')
if seckey_hex != '':
seckey = bytes.fromhex(seckey_hex)
pubkey_actual = pubkey_gen(seckey)
if pubkey != pubkey_actual:
print(' * Failed key generation.')
print(' Expected key:', pubkey.hex().upper())
print(' Actual key:', pubkey_actual.hex().upper())
aux_rand = bytes.fromhex(aux_rand_hex)
try:
sig_actual = schnorr_sign(msg, seckey, aux_rand)
if sig == sig_actual:
print(' * Passed signing test.')
else:
print(' * Failed signing test.')
print(' Expected signature:', sig.hex().upper())
print(' Actual signature:', sig_actual.hex().upper())
all_passed = False
except RuntimeError as e:
print(' * Signing test raised exception:', e)
all_passed = False
result_actual = schnorr_verify(msg, pubkey, sig)
if result == result_actual:
print(' * Passed verification test.')
else:
print(' * Failed verification test.')
print(' Expected verification result:', result)
print(' Actual verification result:', result_actual)
if comment:
print(' Comment:', comment)
all_passed = False
print()
if all_passed:
print('All test vectors passed.')
else:
print('Some test vectors failed.')
return all_passed
#
# The following code is only used for debugging
#
import inspect
def pretty(v: Any) -> Any:
if isinstance(v, bytes):
return '0x' + v.hex()
if isinstance(v, int):
return pretty(bytes_from_int(v))
if isinstance(v, tuple):
return tuple(map(pretty, v))
return v
def debug_print_vars() -> None:
if DEBUG:
current_frame = inspect.currentframe()
assert current_frame is not None
frame = current_frame.f_back
assert frame is not None
print(' Variables in function ', frame.f_code.co_name, ' at line ', frame.f_lineno, ':', sep='')
for var_name, var_val in frame.f_locals.items():
print(' ' + var_name.rjust(11, ' '), '==', pretty(var_val))
if __name__ == '__main__':
test_vectors()

View File

@@ -0,0 +1,20 @@
index,secret key,public key,aux_rand,message,signature,verification result,comment
0,0000000000000000000000000000000000000000000000000000000000000003,F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9,0000000000000000000000000000000000000000000000000000000000000000,0000000000000000000000000000000000000000000000000000000000000000,E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0,TRUE,
1,B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,0000000000000000000000000000000000000000000000000000000000000001,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A,TRUE,
2,C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9,DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8,C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906,7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C,5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7,TRUE,
3,0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710,25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3,TRUE,test fails if msg is reduced modulo p or n
4,,D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9,,4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703,00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4,TRUE,
5,,EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key not on the curve
6,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2,FALSE,has_even_y(R) is false
7,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD,FALSE,negated message
8,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6,FALSE,negated s value
9,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0
10,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197,FALSE,sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1
11,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is not an X coordinate on the curve
12,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,sig[0:32] is equal to field size
13,,DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,FALSE,sig[32:64] is equal to curve order
14,,FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30,,243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89,6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B,FALSE,public key is not a valid X coordinate because it exceeds the field size
15,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,,71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63,TRUE,message of size 0 (added 2022-12)
16,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,11,08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF,TRUE,message of size 1 (added 2022-12)
17,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,0102030405060708090A0B0C0D0E0F1011,5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5,TRUE,message of size 17 (added 2022-12)
18,0340034003400340034003400340034003400340034003400340034003400340,778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117,0000000000000000000000000000000000000000000000000000000000000000,99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999,403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367,TRUE,message of size 100 (added 2022-12)
1 index secret key public key aux_rand message signature verification result comment
2 0 0000000000000000000000000000000000000000000000000000000000000003 F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0 TRUE
3 1 B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 0000000000000000000000000000000000000000000000000000000000000001 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A TRUE
4 2 C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9 DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8 C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906 7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C 5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7 TRUE
5 3 0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710 25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3 TRUE test fails if msg is reduced modulo p or n
6 4 D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9 4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703 00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4 TRUE
7 5 EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B FALSE public key not on the curve
8 6 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2 FALSE has_even_y(R) is false
9 7 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD FALSE negated message
10 8 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6 FALSE negated s value
11 9 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051 FALSE sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0
12 10 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197 FALSE sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1
13 11 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B FALSE sig[0:32] is not an X coordinate on the curve
14 12 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B FALSE sig[0:32] is equal to field size
15 13 DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 FALSE sig[32:64] is equal to curve order
16 14 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30 243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89 6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B FALSE public key is not a valid X coordinate because it exceeds the field size
17 15 0340034003400340034003400340034003400340034003400340034003400340 778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117 0000000000000000000000000000000000000000000000000000000000000000 71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63 TRUE message of size 0 (added 2022-12)
18 16 0340034003400340034003400340034003400340034003400340034003400340 778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117 0000000000000000000000000000000000000000000000000000000000000000 11 08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF TRUE message of size 1 (added 2022-12)
19 17 0340034003400340034003400340034003400340034003400340034003400340 778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117 0000000000000000000000000000000000000000000000000000000000000000 0102030405060708090A0B0C0D0E0F1011 5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5 TRUE message of size 17 (added 2022-12)
20 18 0340034003400340034003400340034003400340034003400340034003400340 778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117 0000000000000000000000000000000000000000000000000000000000000000 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367 TRUE message of size 100 (added 2022-12)

View File

@@ -0,0 +1,335 @@
import sys
from reference import *
def is_square(x):
return int(pow(x, (p - 1) // 2, p)) == 1
def has_square_y(P):
"""Determine if P has a square Y coordinate. Used in an earlier draft of BIP340."""
assert not is_infinite(P)
return is_square(P[1])
def vector0():
seckey = bytes_from_int(3)
msg = bytes_from_int(0)
aux_rand = bytes_from_int(0)
sig = schnorr_sign(msg, seckey, aux_rand)
pubkey = pubkey_gen(seckey)
# We should have at least one test vector where the seckey needs to be
# negated and one where it doesn't. In this one the seckey doesn't need to
# be negated.
x = int_from_bytes(seckey)
P = point_mul(G, x)
assert (y(P) % 2 == 0)
# For historical reasons (pubkey tiebreaker was squareness and not evenness)
# we should have at least one test vector where the the point reconstructed
# from the public key has a square and one where it has a non-square Y
# coordinate. In this one Y is non-square.
pubkey_point = lift_x(pubkey)
assert (not has_square_y(pubkey_point))
# For historical reasons (R tiebreaker was squareness and not evenness)
# we should have at least one test vector where the the point reconstructed
# from the R.x coordinate has a square and one where it has a non-square Y
# coordinate. In this one Y is non-square.
R = lift_x(sig[0:32])
assert (not has_square_y(R))
return seckey, pubkey, aux_rand, msg, sig, "TRUE", None
def vector1():
seckey = bytes_from_int(0xB7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF)
msg = bytes_from_int(0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89)
aux_rand = bytes_from_int(1)
sig = schnorr_sign(msg, seckey, aux_rand)
# The point reconstructed from the R.x coordinate has a square Y coordinate.
R = lift_x(sig[0:32])
assert (has_square_y(R))
return seckey, pubkey_gen(seckey), aux_rand, msg, sig, "TRUE", None
def vector2():
seckey = bytes_from_int(0xC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9)
msg = bytes_from_int(0x7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C)
aux_rand = bytes_from_int(0xC87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906)
sig = schnorr_sign(msg, seckey, aux_rand)
# The point reconstructed from the public key has a square Y coordinate.
pubkey = pubkey_gen(seckey)
pubkey_point = lift_x(pubkey)
assert (has_square_y(pubkey_point))
# This signature vector would not verify if the implementer checked the
# evenness of the X coordinate of R instead of the Y coordinate.
R = lift_x(sig[0:32])
assert (R[0] % 2 == 1)
return seckey, pubkey, aux_rand, msg, sig, "TRUE", None
def vector3():
seckey = bytes_from_int(0x0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710)
# Need to negate this seckey before signing
x = int_from_bytes(seckey)
P = point_mul(G, x)
assert (y(P) % 2 != 0)
msg = bytes_from_int(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
aux_rand = bytes_from_int(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
sig = schnorr_sign(msg, seckey, aux_rand)
return seckey, pubkey_gen(seckey), aux_rand, msg, sig, "TRUE", "test fails if msg is reduced modulo p or n"
# Signs with a given nonce. This can be INSECURE and is only INTENDED FOR
# GENERATING TEST VECTORS. Results in an invalid signature if y(kG) is not
# even.
def insecure_schnorr_sign_fixed_nonce(msg, seckey0, k):
if len(msg) != 32:
raise ValueError('The message must be a 32-byte array.')
seckey0 = int_from_bytes(seckey0)
if not (1 <= seckey0 <= n - 1):
raise ValueError('The secret key must be an integer in the range 1..n-1.')
P = point_mul(G, seckey0)
seckey = seckey0 if has_even_y(P) else n - seckey0
R = point_mul(G, k)
e = int_from_bytes(tagged_hash("BIP0340/challenge", bytes_from_point(R) + bytes_from_point(P) + msg)) % n
return bytes_from_point(R) + bytes_from_int((k + e * seckey) % n)
# Creates a singature with a small x(R) by using k = -1/2
def vector4():
one_half = n - 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0
seckey = bytes_from_int(0x763758E5CBEEDEE4F7D3FC86F531C36578933228998226672F13C4F0EBE855EB)
msg = bytes_from_int(0x4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703)
sig = insecure_schnorr_sign_fixed_nonce(msg, seckey, one_half)
return None, pubkey_gen(seckey), None, msg, sig, "TRUE", None
default_seckey = bytes_from_int(0xB7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF)
default_msg = bytes_from_int(0x243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89)
default_aux_rand = bytes_from_int(0xC87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906)
# Public key is not on the curve
def vector5():
# This creates a dummy signature that doesn't have anything to do with the
# public key.
seckey = default_seckey
msg = default_msg
sig = schnorr_sign(msg, seckey, default_aux_rand)
pubkey = bytes_from_int(0xEEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34)
assert (lift_x(pubkey) is None)
return None, pubkey, None, msg, sig, "FALSE", "public key not on the curve"
def vector6():
seckey = default_seckey
msg = default_msg
k = 6
sig = insecure_schnorr_sign_fixed_nonce(msg, seckey, k)
# Y coordinate of R is not even
R = point_mul(G, k)
assert (not has_even_y(R))
return None, pubkey_gen(seckey), None, msg, sig, "FALSE", "has_even_y(R) is false"
def vector7():
seckey = default_seckey
msg = int_from_bytes(default_msg)
neg_msg = bytes_from_int(n - msg)
sig = schnorr_sign(neg_msg, seckey, default_aux_rand)
return None, pubkey_gen(seckey), None, bytes_from_int(msg), sig, "FALSE", "negated message"
def vector8():
seckey = default_seckey
msg = default_msg
sig = schnorr_sign(msg, seckey, default_aux_rand)
sig = sig[0:32] + bytes_from_int(n - int_from_bytes(sig[32:64]))
return None, pubkey_gen(seckey), None, msg, sig, "FALSE", "negated s value"
def bytes_from_point_inf0(P):
if P is None:
return bytes_from_int(0)
return bytes_from_int(P[0])
def vector9():
seckey = default_seckey
msg = default_msg
# Override bytes_from_point in schnorr_sign to allow creating a signature
# with k = 0.
k = 0
bytes_from_point_tmp = bytes_from_point.__code__
bytes_from_point.__code__ = bytes_from_point_inf0.__code__
sig = insecure_schnorr_sign_fixed_nonce(msg, seckey, k)
bytes_from_point.__code__ = bytes_from_point_tmp
return (None, pubkey_gen(seckey), None, msg, sig, "FALSE",
"sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 0")
def bytes_from_point_inf1(P):
if P == None:
return bytes_from_int(1)
return bytes_from_int(P[0])
def vector10():
seckey = default_seckey
msg = default_msg
# Override bytes_from_point in schnorr_sign to allow creating a signature
# with k = 0.
k = 0
bytes_from_point_tmp = bytes_from_point.__code__
bytes_from_point.__code__ = bytes_from_point_inf1.__code__
sig = insecure_schnorr_sign_fixed_nonce(msg, seckey, k)
bytes_from_point.__code__ = bytes_from_point_tmp
return (None, pubkey_gen(seckey), None, msg, sig, "FALSE",
"sG - eP is infinite. Test fails in single verification if has_even_y(inf) is defined as true and x(inf) as 1")
# It's cryptographically impossible to create a test vector that fails if run
# in an implementation which merely misses the check that sig[0:32] is an X
# coordinate on the curve. This test vector just increases test coverage.
def vector11():
seckey = default_seckey
msg = default_msg
sig = schnorr_sign(msg, seckey, default_aux_rand)
# Replace R's X coordinate with an X coordinate that's not on the curve
x_not_on_curve = bytes_from_int(0x4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D)
assert (lift_x(x_not_on_curve) is None)
sig = x_not_on_curve + sig[32:64]
return None, pubkey_gen(seckey), None, msg, sig, "FALSE", "sig[0:32] is not an X coordinate on the curve"
# It's cryptographically impossible to create a test vector that fails if run
# in an implementation which merely misses the check that sig[0:32] is smaller
# than the field size. This test vector just increases test coverage.
def vector12():
seckey = default_seckey
msg = default_msg
sig = schnorr_sign(msg, seckey, default_aux_rand)
# Replace R's X coordinate with an X coordinate that's equal to field size
sig = bytes_from_int(p) + sig[32:64]
return None, pubkey_gen(seckey), None, msg, sig, "FALSE", "sig[0:32] is equal to field size"
# It's cryptographically impossible to create a test vector that fails if run
# in an implementation which merely misses the check that sig[32:64] is smaller
# than the curve order. This test vector just increases test coverage.
def vector13():
seckey = default_seckey
msg = default_msg
sig = schnorr_sign(msg, seckey, default_aux_rand)
# Replace s with a number that's equal to the curve order
sig = sig[0:32] + bytes_from_int(n)
return None, pubkey_gen(seckey), None, msg, sig, "FALSE", "sig[32:64] is equal to curve order"
# Test out of range pubkey
# It's cryptographically impossible to create a test vector that fails if run
# in an implementation which accepts out of range pubkeys because we can't find
# a secret key for such a public key and therefore can not create a signature.
# This test vector just increases test coverage.
def vector14():
# This creates a dummy signature that doesn't have anything to do with the
# public key.
seckey = default_seckey
msg = default_msg
sig = schnorr_sign(msg, seckey, default_aux_rand)
pubkey_int = p + 1
pubkey = bytes_from_int(pubkey_int)
assert (lift_x(pubkey) is None)
# If an implementation would reduce a given public key modulo p then the
# pubkey would be valid
assert (lift_x(bytes_from_int(pubkey_int % p)) is not None)
return (
None, pubkey, None, msg, sig, "FALSE", "public key is not a valid X coordinate because it exceeds the field size")
def varlen_vector(msg_int):
seckey = bytes_from_int(int(16 * "0340", 16))
pubkey = pubkey_gen(seckey)
aux_rand = bytes_from_int(0)
msg = msg_int.to_bytes((msg_int.bit_length() + 7) // 8, "big")
sig = schnorr_sign(msg, seckey, aux_rand)
comment = "message of size %d (added 2022-12)"
return seckey, pubkey, aux_rand, msg, sig, "TRUE", comment % len(msg)
vector15 = lambda: varlen_vector(0)
vector16 = lambda: varlen_vector(0x11)
vector17 = lambda: varlen_vector(0x0102030405060708090A0B0C0D0E0F1011)
vector18 = lambda: varlen_vector(int(100 * "99", 16))
vectors = [
vector0(),
vector1(),
vector2(),
vector3(),
vector4(),
vector5(),
vector6(),
vector7(),
vector8(),
vector9(),
vector10(),
vector11(),
vector12(),
vector13(),
vector14(),
vector15(),
vector16(),
vector17(),
vector18(),
]
# Converts the byte strings of a test vector into hex strings
def bytes_to_hex(seckey, pubkey, aux_rand, msg, sig, result, comment):
return (seckey.hex().upper() if seckey is not None else None, pubkey.hex().upper(),
aux_rand.hex().upper() if aux_rand is not None else None, msg.hex().upper(), sig.hex().upper(), result,
comment)
vectors = list(
map(lambda vector: bytes_to_hex(vector[0], vector[1], vector[2], vector[3], vector[4], vector[5], vector[6]),
vectors))
def print_csv(vectors):
writer = csv.writer(sys.stdout)
writer.writerow(
("index", "secret key", "public key", "aux_rand", "message", "signature", "verification result", "comment"))
for (i, v) in enumerate(vectors):
writer.writerow((i,) + v)
print_csv(vectors)

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2020-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// Package schnorr provides custom Schnorr signing and verification via
// secp256k1.
//
// This package provides data structures and functions necessary to produce and
// verify deterministic canonical Schnorr signatures using a custom scheme named
// EC-Schnorr-DCRv0 that is described herein. The signatures and implementation
// are optimized specifically for the secp256k1 curve. See
// https://www.secg.org/sec2-v2.pdf for details on the secp256k1 standard.
//
// It also provides functions to parse and serialize the Schnorr signatures
// according to the specification described herein.
//
// A comprehensive suite of tests is provided to ensure proper functionality.
//
// # Overview
//
// A Schnorr signature is a digital signature scheme that is known for its
// simplicity, provable security and efficient generation of short signatures.
//
// It provides many advantages over ECDSA signatures that make them ideal for
// use with the only real downside being that they are not well standardized at
// the time of this writing.
//
// Some of the advantages over ECDSA include:
//
// - They are linear which makes them easier to aggregate and use in
// protocols that build on them such as multi-party signatures, threshold
// signatures, adaptor signatures, and blind signatures
// - They are provably secure with weaker assumptions than the best known
// security proofs for ECDSA
// - Specifically Schnorr signatures are provably secure under SUF-CMA (Strong
// Existential Unforgeability under Chosen Message Attack) in the ROM
// (Random Oracle Model) which guarantees that as long as the hash
// function behaves ideally, the only way to break Schnorr signatures is
// by solving the ECDLP (Elliptic Curve Discrete Logarithm Problem).
// - Their relatively straightforward and efficient aggregation properties
// make them excellent for scalability and allow them to provide some nice
// secrecy characteristics
// - They support faster batch verification unlike the standardized version of
// ECDSA signatures
//
// # Custom Schnorr-based Signature Scheme
//
// As mentioned in the overview, the primary downside of Schnorr signatures for
// elliptic curves is that they are not standardized as well as ECDSA signatures,
// which means there are a number of variations that are not compatible with
// each other.
//
// In addition, many of the standardization attempts have had various
// disadvantages that make them unsuitable for use in Decred. Some of these
// details and some insight into the design decisions made are discussed further
// in the README.md file.
//
// Consequently, this package implements a custom Schnorr-based signature scheme
// named EC-Schnorr-DCRv0 suitable for use in Decred.
//
// The following provides a high-level overview of the key design features of
// the scheme:
//
// - Uses signatures of the form (R, s)
// - Produces 64-byte signatures by only encoding the x coordinate of R
// - Enforces even y coordinates for R to support efficient verification by
// disambiguating the two possible y coordinates
// - Canonically encodes by both components of the signature with 32-bytes
// each
// - Uses BLAKE-256 with 14 rounds for the hash function to calculate
// challenge e
// - Uses RFC6979 to obviate the need for an entropy source at signing time
// - Produces deterministic signatures for a given message and secret key pair
//
// # EC-Schnorr-DCRv0 Specification
//
// See the README.md file for the specific details of the signing and
// verification algorithm as well as the signature serialization format.
//
// # Future Design Considerations
//
// It is worth noting that there are some additional optimizations and
// modifications that have been identified since the introduction of
// EC-Schnorr-DCRv0 that can be made to further harden security for multi-party
// and threshold signature use cases as well provide the opportunity for faster
// signature verification with a sufficiently optimized implementation.
//
// However, the v0 scheme is used in the existing consensus rules and any
// changes to the signature scheme would invalidate existing uses. Therefore
// changes in this regard will need to come in the form of a v1 signature scheme
// and be accompanied by the necessary consensus updates.
//
// # Schnorr use in Decred
//
// At the time of this writing, Schnorr signatures are not yet in widespread use
// on the Decred network, largely due to the current lack of support in wallets
// and infrastructure for secure multi-party and threshold signatures.
//
// However, the consensus rules and scripting engine supports the necessary
// primitives and given many of the beneficial properties of Schnorr signatures,
// a good goal is to work towards providing the additional infrastructure to
// increase their usage.
package schnorr

View File

@@ -0,0 +1,71 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2014 Conformal Systems LLC.
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package schnorr
// ErrorKind identifies a kind of error. It has full support for errors.Is and
// errors.As, so the caller can directly check against an error kind when
// determining the reason for an error.
type ErrorKind string
// These constants are used to identify a specific RuleError.
const (
// ErrInvalidHashLen indicates that the input hash to sign or verify is not
// the required length.
ErrInvalidHashLen = ErrorKind("ErrInvalidHashLen")
// ErrSecretKeyIsZero indicates an attempt was made to sign a message with
// a secret key that is equal to zero.
ErrSecretKeyIsZero = ErrorKind("ErrSecretKeyIsZero")
ErrPrivateKeyIsZero = ErrSecretKeyIsZero
// ErrSchnorrHashValue indicates that the hash of (R || m) was too large and
// so a new nonce should be used.
ErrSchnorrHashValue = ErrorKind("ErrSchnorrHashValue")
// ErrPubKeyNotOnCurve indicates that a point was not on the given elliptic
// curve.
ErrPubKeyNotOnCurve = ErrorKind("ErrPubKeyNotOnCurve")
// ErrSigRYIsOdd indicates that the calculated Y value of R was odd.
ErrSigRYIsOdd = ErrorKind("ErrSigRYIsOdd")
// ErrSigRNotOnCurve indicates that the calculated or given point R for some
// signature was not on the curve.
ErrSigRNotOnCurve = ErrorKind("ErrSigRNotOnCurve")
// ErrUnequalRValues indicates that the calculated point R for some
// signature was not the same as the given R value for the signature.
ErrUnequalRValues = ErrorKind("ErrUnequalRValues")
// ErrSigTooShort is returned when a signature that should be a Schnorr
// signature is too short.
ErrSigTooShort = ErrorKind("ErrSigTooShort")
// ErrSigTooLong is returned when a signature that should be a Schnorr
// signature is too long.
ErrSigTooLong = ErrorKind("ErrSigTooLong")
// ErrSigRTooBig is returned when a signature has r with a value that is
// greater than or equal to the prime of the field underlying the group.
ErrSigRTooBig = ErrorKind("ErrSigRTooBig")
// ErrSigSTooBig is returned when a signature has s with a value that is
// greater than or equal to the group order.
ErrSigSTooBig = ErrorKind("ErrSigSTooBig")
)
// Error satisfies the error interface and prints human-readable errors.
func (err ErrorKind) Error() string { return string(err) }
// Error identifies an error related to a schnorr signature. It has full support
// for errors.Is and errors.As, so the caller can ascertain the specific reason
// for the error by checking the underlying error.
type Error struct {
Err error
Description string
}
// Error satisfies the error interface and prints human-readable errors.
func (err Error) Error() string { return err.Description }
// Unwrap returns the underlying wrapped error.
func (err Error) Unwrap() (ee error) { return err.Err }
// signatureError creates an Error given a set of arguments.
func signatureError(kind ErrorKind, desc string) (err error) {
return Error{Err: kind, Description: desc}
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package schnorr
import (
"fmt"
"next.orly.dev/pkg/crypto/ec"
"next.orly.dev/pkg/crypto/ec/secp256k1"
)
// These constants define the lengths of serialized public keys.
const (
PubKeyBytesLen = 32
)
// ParsePubKey parses a public key for a koblitz curve from a bytestring into a
// btcec.Publickey, verifying that it is valid. It only supports public keys in
// the BIP-340 32-byte format.
func ParsePubKey(pubKeyStr []byte) (*btcec.PublicKey, error) {
if pubKeyStr == nil {
err := fmt.Errorf("nil pubkey byte string")
return nil, err
}
if len(pubKeyStr) != PubKeyBytesLen {
err := fmt.Errorf(
"bad pubkey byte string size (want %v, have %v)",
PubKeyBytesLen, len(pubKeyStr),
)
return nil, err
}
// We'll manually prepend the compressed byte so we can re-use the existing
// pubkey parsing routine of the main btcec package.
var keyCompressed [btcec.PubKeyBytesLenCompressed]byte
keyCompressed[0] = secp256k1.PubKeyFormatCompressedEven
copy(keyCompressed[1:], pubKeyStr)
return btcec.ParsePubKey(keyCompressed[:])
}
// SerializePubKey serializes a public key as specified by BIP 340. Public keys
// in this format are 32 bytes in length and are assumed to have an even y
// coordinate.
func SerializePubKey(pub *btcec.PublicKey) []byte {
pBytes := pub.SerializeCompressed()
return pBytes[1:]
}

View File

@@ -0,0 +1,142 @@
// Copyright (c) 2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package schnorr
import (
"errors"
"testing"
)
// TestErrorKindStringer tests the stringized output for the ErrorKind type.
func TestErrorKindStringer(t *testing.T) {
tests := []struct {
in ErrorKind
want string
}{
{ErrInvalidHashLen, "ErrInvalidHashLen"},
{ErrSecretKeyIsZero, "ErrSecretKeyIsZero"},
{ErrSchnorrHashValue, "ErrSchnorrHashValue"},
{ErrPubKeyNotOnCurve, "ErrPubKeyNotOnCurve"},
{ErrSigRYIsOdd, "ErrSigRYIsOdd"},
{ErrSigRNotOnCurve, "ErrSigRNotOnCurve"},
{ErrUnequalRValues, "ErrUnequalRValues"},
{ErrSigTooShort, "ErrSigTooShort"},
{ErrSigTooLong, "ErrSigTooLong"},
{ErrSigRTooBig, "ErrSigRTooBig"},
{ErrSigSTooBig, "ErrSigSTooBig"},
}
for i, test := range tests {
result := test.in.Error()
if result != test.want {
t.Errorf("#%d: got: %s want: %s", i, result, test.want)
continue
}
}
}
// TestError tests the error output for the Error type.
func TestError(t *testing.T) {
tests := []struct {
in Error
want string
}{
{
Error{Description: "some error"},
"some error",
}, {
Error{Description: "human-readable error"},
"human-readable error",
},
}
for i, test := range tests {
result := test.in.Error()
if result != test.want {
t.Errorf("#%d: got: %s want: %s", i, result, test.want)
continue
}
}
}
// TestErrorKindIsAs ensures both ErrorKind and Error can be identified
// as being a specific error via errors.Is and unwrapped via errors.As.
func TestErrorKindIsAs(t *testing.T) {
tests := []struct {
name string
err error
target error
wantMatch bool
wantAs ErrorKind
}{
{
name: "ErrInvalidHashLen == ErrInvalidHashLen",
err: ErrInvalidHashLen,
target: ErrInvalidHashLen,
wantMatch: true,
wantAs: ErrInvalidHashLen,
}, {
name: "Error.ErrInvalidHashLen == ErrInvalidHashLen",
err: signatureError(ErrInvalidHashLen, ""),
target: ErrInvalidHashLen,
wantMatch: true,
wantAs: ErrInvalidHashLen,
}, {
name: "Error.ErrInvalidHashLen == Error.ErrInvalidHashLen",
err: signatureError(ErrInvalidHashLen, ""),
target: signatureError(ErrInvalidHashLen, ""),
wantMatch: true,
wantAs: ErrInvalidHashLen,
}, {
name: "ErrSecretKeyIsZero != ErrInvalidHashLen",
err: ErrSecretKeyIsZero,
target: ErrInvalidHashLen,
wantMatch: false,
wantAs: ErrSecretKeyIsZero,
}, {
name: "Error.ErrSecretKeyIsZero != ErrInvalidHashLen",
err: signatureError(ErrSecretKeyIsZero, ""),
target: ErrInvalidHashLen,
wantMatch: false,
wantAs: ErrSecretKeyIsZero,
}, {
name: "ErrSecretKeyIsZero != Error.ErrInvalidHashLen",
err: ErrSecretKeyIsZero,
target: signatureError(ErrInvalidHashLen, ""),
wantMatch: false,
wantAs: ErrSecretKeyIsZero,
}, {
name: "Error.ErrSecretKeyIsZero != Error.ErrInvalidHashLen",
err: signatureError(ErrSecretKeyIsZero, ""),
target: signatureError(ErrInvalidHashLen, ""),
wantMatch: false,
wantAs: ErrSecretKeyIsZero,
},
}
for _, test := range tests {
// Ensure the error matches or not depending on the expected result.
result := errors.Is(test.err, test.target)
if result != test.wantMatch {
t.Errorf(
"%s: incorrect error identification -- got %v, want %v",
test.name, result, test.wantMatch,
)
continue
}
// Ensure the underlying error kind can be unwrapped and is the
// expected code.
var code ErrorKind
if !errors.As(test.err, &code) {
t.Errorf("%s: unable to unwrap to error", test.name)
continue
}
if !errors.Is(code, test.wantAs) {
t.Errorf(
"%s: unexpected unwrapped error -- got %v, want %v",
test.name, code, test.wantAs,
)
continue
}
}
}

View File

@@ -0,0 +1,515 @@
// Copyright (c) 2013-2022 The btcsuite developers
package schnorr
import (
"fmt"
"lol.mleku.dev/chk"
"next.orly.dev/pkg/crypto/ec"
"next.orly.dev/pkg/crypto/ec/chainhash"
"next.orly.dev/pkg/crypto/ec/secp256k1"
)
const (
// SignatureSize is the size of an encoded Schnorr signature.
SignatureSize = 64
// scalarSize is the size of an encoded big endian scalar.
scalarSize = 32
)
var (
// rfc6979ExtraDataV0 is the extra data to feed to RFC6979 when generating
// the deterministic nonce for the BIP-340 scheme. This ensures the same
// nonce is not generated for the same message and key as for other signing
// algorithms such as ECDSA.
//
// It is equal to SHA-256(by("BIP-340")).
rfc6979ExtraDataV0 = [32]uint8{
0xa3, 0xeb, 0x4c, 0x18, 0x2f, 0xae, 0x7e, 0xf4,
0xe8, 0x10, 0xc6, 0xee, 0x13, 0xb0, 0xe9, 0x26,
0x68, 0x6d, 0x71, 0xe8, 0x7f, 0x39, 0x4f, 0x79,
0x9c, 0x00, 0xa5, 0x21, 0x03, 0xcb, 0x4e, 0x17,
}
)
// Signature is a type representing a Schnorr signature.
type Signature struct {
r btcec.FieldVal
s btcec.ModNScalar
}
// NewSignature instantiates a new signature given some r and s values.
func NewSignature(r *btcec.FieldVal, s *btcec.ModNScalar) *Signature {
var sig Signature
sig.r.Set(r).Normalize()
sig.s.Set(s)
return &sig
}
// Serialize returns the Schnorr signature in a stricter format.
//
// The signatures are encoded as
//
// sig[0:32]
// x coordinate of the point R, encoded as a big-endian uint256
// sig[32:64]
// s, encoded also as big-endian uint256
func (sig Signature) Serialize() []byte {
// Total length of returned signature is the length of r and s.
var b [SignatureSize]byte
sig.r.PutBytesUnchecked(b[0:32])
sig.s.PutBytesUnchecked(b[32:64])
return b[:]
}
// ParseSignature parses a signature according to the BIP-340 specification and
// enforces the following additional restrictions specific to secp256k1:
//
// - The r component must be in the valid range for secp256k1 field elements
//
// - The s component must be in the valid range for secp256k1 scalars
func ParseSignature(sig []byte) (*Signature, error) {
// The signature must be the correct length.
sigLen := len(sig)
if sigLen < SignatureSize {
str := fmt.Sprintf(
"malformed signature: too short: %d < %d", sigLen,
SignatureSize,
)
return nil, signatureError(ErrSigTooShort, str)
}
if sigLen > SignatureSize {
str := fmt.Sprintf(
"malformed signature: too long: %d > %d", sigLen,
SignatureSize,
)
return nil, signatureError(ErrSigTooLong, str)
}
// The signature is validly encoded at this point, however, enforce
// additional restrictions to ensure r is in the range [0, p-1], and s is in
// the range [0, n-1] since valid Schnorr signatures are required to be in
// that range per spec.
var r btcec.FieldVal
if overflow := r.SetByteSlice(sig[0:32]); overflow {
str := "invalid signature: r >= field prime"
return nil, signatureError(ErrSigRTooBig, str)
}
var s btcec.ModNScalar
s.SetByteSlice(sig[32:64])
// Return the signature.
return NewSignature(&r, &s), nil
}
// IsEqual compares this Signature instance to the one passed, returning true if
// both Signatures are equivalent. A signature is equivalent to another if they
// both have the same scalar value for R and S.
func (sig Signature) IsEqual(otherSig *Signature) bool {
return sig.r.Equals(&otherSig.r) && sig.s.Equals(&otherSig.s)
}
// schnorrVerify attempt to verify the signature for the provided hash and
// secp256k1 public key and either returns nil if successful or a specific error
// indicating why it failed if not successful.
//
// This differs from the exported Verify method in that it returns a specific
// error to support better testing, while the exported method simply returns a
// bool indicating success or failure.
func schnorrVerify(sig *Signature, hash []byte, pubKeyBytes []byte) error {
// The algorithm for producing a BIP-340 signature is described in
// README.md and is reproduced here for reference:
//
// 1. Fail if m is not 32 bytes
// 2. P = lift_x(int(pk)).
// 3. r = int(sig[0:32]); fail is r >= p.
// 4. s = int(sig[32:64]); fail if s >= n.
// 5. e = int(tagged_hash("BIP0340/challenge", bytes(r) || bytes(P) || M)) mod n.
// 6. R = s*G - e*P
// 7. Fail if is_infinite(R)
// 8. Fail if not hash_even_y(R)
// 9. Fail is x(R) != r.
// 10. Return success iff not failure occured before reachign this
// point.
// // Step 1.
// //
// // Fail if m is not 32 bytes
// if len(hash) != scalarSize {
// str := fmt.Sprintf("wrong size for message (got %v, want %v)",
// len(hash), scalarSize)
// return signatureError(schnorr.ErrInvalidHashLen, str)
// }
// Step 2.
//
// P = lift_x(int(pk))
//
// Fail if P is not a point on the curve
pubKey, err := ParsePubKey(pubKeyBytes)
if chk.E(err) {
return err
}
if !pubKey.IsOnCurve() {
str := "pubkey point is not on curve"
return signatureError(ErrPubKeyNotOnCurve, str)
}
// Step 3.
//
// Fail if r >= p
//
// Note this is already handled by the fact r is a field element.
//
// Step 4.
//
// Fail if s >= n
//
// Note this is already handled by the fact s is a mod n scalar.
//
// Step 5.
//
// e = int(tagged_hash("BIP0340/challenge", bytes(r) || bytes(P) || M)) mod n.
var rBytes [32]byte
sig.r.PutBytesUnchecked(rBytes[:])
pBytes := SerializePubKey(pubKey)
commitment := chainhash.TaggedHash(
chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash,
)
var e btcec.ModNScalar
e.SetBytes((*[32]byte)(commitment))
// Negate e here so we can use AddNonConst below to subtract the s*G
// point from e*P.
e.Negate()
// Step 6.
//
// R = s*G - e*P
var P, R, sG, eP btcec.JacobianPoint
pubKey.AsJacobian(&P)
btcec.ScalarBaseMultNonConst(&sig.s, &sG)
btcec.ScalarMultNonConst(&e, &P, &eP)
btcec.AddNonConst(&sG, &eP, &R)
// Step 7.
//
// Fail if R is the point at infinity
if (R.X.IsZero() && R.Y.IsZero()) || R.Z.IsZero() {
str := "calculated R point is the point at infinity"
return signatureError(ErrSigRNotOnCurve, str)
}
// Step 8.
//
// Fail if R.y is odd
//
// Note that R must be in affine coordinates for this check.
R.ToAffine()
if R.Y.IsOdd() {
str := "calculated R y-value is odd"
return signatureError(ErrSigRYIsOdd, str)
}
// Step 9.
//
// Verified if R.x == r
//
// Note that R must be in affine coordinates for this check.
if !sig.r.Equals(&R.X) {
str := "calculated R point was not given R"
return signatureError(ErrUnequalRValues, str)
}
// Step 10.
//
// Return success iff not failure occured before reachign this
return nil
}
// Verify returns whether or not the signature is valid for the provided hash
// and secp256k1 public key.
func (sig *Signature) Verify(hash []byte, pubKey *btcec.PublicKey) bool {
pubkeyBytes := SerializePubKey(pubKey)
return schnorrVerify(sig, hash, pubkeyBytes) == nil
}
// zeroArray zeroes the memory of a scalar array.
func zeroArray(a *[scalarSize]byte) {
for i := 0; i < scalarSize; i++ {
a[i] = 0x00
}
}
// schnorrSign generates an BIP-340 signature over the secp256k1 curve for the
// provided hash (which should be the result of hashing a larger message) using
// the given nonce and secret key. The produced signature is deterministic (the
// same message, nonce, and key yield the same signature) and canonical.
//
// WARNING: The hash MUST be 32 bytes, and both the nonce and secret keys must
// NOT be 0. Since this is an internal use function, these preconditions MUST be
// satisified by the caller.
func schnorrSign(
privKey, nonce *btcec.ModNScalar, pubKey *btcec.PublicKey,
hash []byte, opts *signOptions,
) (*Signature, error) {
// The algorithm for producing a BIP-340 signature is described in
// README.md and is reproduced here for reference:
//
// G = curve generator
// n = curve order
// d = secret key
// m = message
// a = input randmoness
// r, s = signature
//
// 1. d' = int(d)
// 2. Fail if m is not 32 bytes
// 3. Fail if d = 0 or d >= n
// 4. P = d'*G
// 5. Negate d if P.y is odd
// 6. t = bytes(d) xor tagged_hash("BIP0340/aux", t || bytes(P) || m)
// 7. rand = tagged_hash("BIP0340/nonce", a)
// 8. k' = int(rand) mod n
// 9. Fail if k' = 0
// 10. R = 'k*G
// 11. Negate k if R.y id odd
// 12. e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || m) mod n
// 13. sig = bytes(R) || bytes((k + e*d)) mod n
// 14. If Verify(bytes(P), m, sig) fails, abort.
// 15. return sig.
//
// Note that the set of functional options passed in may modify the
// above algorithm. Namely if CustomNonce is used, then steps 6-8 are
// replaced with a process that generates the nonce using rfc6979. If
// FastSign is passed, then we skip set 14.
// NOTE: Steps 1-9 are performed by the caller.
//
// Step 10.
//
// R = kG
var R btcec.JacobianPoint
k := *nonce
btcec.ScalarBaseMultNonConst(&k, &R)
// Step 11.
//
// Negate nonce k if R.y is odd (R.y is the y coordinate of the point R)
//
// Note that R must be in affine coordinates for this check.
R.ToAffine()
if R.Y.IsOdd() {
k.Negate()
}
// Step 12.
//
// e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || m) mod n
var rBytes [32]byte
r := &R.X
r.PutBytesUnchecked(rBytes[:])
pBytes := SerializePubKey(pubKey)
commitment := chainhash.TaggedHash(
chainhash.TagBIP0340Challenge, rBytes[:], pBytes, hash,
)
var e btcec.ModNScalar
if overflow := e.SetBytes((*[32]byte)(commitment)); overflow != 0 {
k.Zero()
str := "hash of (r || P || m) too big"
return nil, signatureError(ErrSchnorrHashValue, str)
}
// Step 13.
//
// s = k + e*d mod n
s := new(btcec.ModNScalar).Mul2(&e, privKey).Add(&k)
k.Zero()
sig := NewSignature(r, s)
// Step 14.
//
// If Verify(bytes(P), m, sig) fails, abort.
if !opts.fastSign {
if err := schnorrVerify(sig, hash, pBytes); chk.T(err) {
return nil, err
}
}
// Step 15.
//
// Return (r, s)
return sig, nil
}
// SignOption is a functional option argument that allows callers to modify the
// way we generate BIP-340 schnorr signatures.
type SignOption func(*signOptions)
// signOptions houses the set of functional options that can be used to modify
// the method used to generate the BIP-340 signature.
type signOptions struct {
// fastSign determines if we'll skip the check at the end of the routine
// where we attempt to verify the produced signature.
fastSign bool
// authNonce allows the user to pass in their own nonce information, which
// is useful for schemes like mu-sig.
authNonce *[32]byte
}
// defaultSignOptions returns the default set of signing operations.
func defaultSignOptions() *signOptions { return &signOptions{} }
// FastSign forces signing to skip the extra verification step at the end.
// Peformance sensitive applications may opt to use this option to speed up the
// signing operation.
func FastSign() SignOption {
return func(o *signOptions) { o.fastSign = true }
}
// CustomNonce allows users to pass in a custom set of auxData that's used as
// input randomness to generate the nonce used during signing. Users may want
// to specify this custom value when using multi-signatures schemes such as
// Mu-Sig2. If this option isn't set, then rfc6979 will be used to generate the
// nonce material.
func CustomNonce(auxData [32]byte) SignOption {
return func(o *signOptions) { o.authNonce = &auxData }
}
// Sign generates an BIP-340 signature over the secp256k1 curve for the provided
// hash (which should be the result of hashing a larger message) using the given
// secret key. The produced signature is deterministic (the same message and the
// same key yield the same signature) and canonical.
//
// Note that the current signing implementation has a few remaining variable
// time aspects which make use of the secret key and the generated nonce, which
// can expose the signer to constant time attacks. As a result, this function
// should not be used in situations where there is the possibility of someone
// having EM field/cache/etc access.
func Sign(
privKey *btcec.SecretKey, hash []byte,
signOpts ...SignOption,
) (*Signature, error) {
// First, parse the set of optional signing options.
opts := defaultSignOptions()
for _, option := range signOpts {
option(opts)
}
// The algorithm for producing a BIP-340 signature is described in README.md
// and is reproduced here for reference:
//
// G = curve generator
// n = curve order
// d = secret key
// m = message
// a = input randmoness
// r, s = signature
//
// 1. d' = int(d)
// 2. Fail if m is not 32 bytes
// 3. Fail if d = 0 or d >= n
// 4. P = d'*G
// 5. Negate d if P.y is odd
// 6. t = bytes(d) xor tagged_hash("BIP0340/aux", t || bytes(P) || m)
// 7. rand = tagged_hash("BIP0340/nonce", a)
// 8. k' = int(rand) mod n
// 9. Fail if k' = 0
// 10. R = 'k*G
// 11. Negate k if R.y id odd
// 12. e = tagged_hash("BIP0340/challenge", bytes(R) || bytes(P) || mod) mod n
// 13. sig = bytes(R) || bytes((k + e*d)) mod n
// 14. If Verify(bytes(P), m, sig) fails, abort.
// 15. return sig.
//
// Note that the set of functional options passed in may modify the above
// algorithm. Namely if CustomNonce is used, then steps 6-8 are replaced
// with a process that generates the nonce using rfc6979. If FastSign is
// passed, then we skip set 14.
//
// Step 1.
//
// d' = int(d)
var privKeyScalar btcec.ModNScalar
privKeyScalar.Set(&privKey.Key)
// Step 2.
//
// Fail if m is not 32 bytes
// if len(hash) != scalarSize {
// str := fmt.Sprintf("wrong size for message hash (got %v, want %v)",
// len(hash), scalarSize)
// return nil, signatureError(schnorr.ErrInvalidHashLen, str)
// }
//
// Step 3.
//
// Fail if d = 0 or d >= n
if privKeyScalar.IsZero() {
str := "secret key is zero"
return nil, signatureError(ErrSecretKeyIsZero, str)
}
// Step 4.
//
// P = 'd*G
pub := privKey.PubKey()
// Step 5.
//
// Negate d if P.y is odd.
pubKeyBytes := pub.SerializeCompressed()
if pubKeyBytes[0] == secp256k1.PubKeyFormatCompressedOdd {
privKeyScalar.Negate()
}
// At this point, we check to see if a CustomNonce has been passed in, and
// if so, then we'll deviate from the main routine here by generating the
// nonce value as specified by BIP-0340.
if opts.authNonce != nil {
// Step 6.
//
// t = bytes(d) xor tagged_hash("BIP0340/aux", a)
privBytes := privKeyScalar.Bytes()
t := chainhash.TaggedHash(
chainhash.TagBIP0340Aux, (*opts.authNonce)[:],
)
for i := 0; i < len(t); i++ {
t[i] ^= privBytes[i]
}
// Step 7.
//
// rand = tagged_hash("BIP0340/nonce", t || bytes(P) || m)
//
// We snip off the first byte of the serialized pubkey, as we only need
// the x coordinate and not the market byte.
rand := chainhash.TaggedHash(
chainhash.TagBIP0340Nonce, t[:], pubKeyBytes[1:], hash,
)
// Step 8.
//
// k'= int(rand) mod n
var kPrime btcec.ModNScalar
kPrime.SetBytes((*[32]byte)(rand))
// Step 9.
//
// Fail if k' = 0
if kPrime.IsZero() {
str := fmt.Sprintf("generated nonce is zero")
return nil, signatureError(ErrSchnorrHashValue, str)
}
sig, err := schnorrSign(&privKeyScalar, &kPrime, pub, hash, opts)
kPrime.Zero()
if err != nil {
return nil, err
}
return sig, nil
}
var privKeyBytes [scalarSize]byte
privKeyScalar.PutBytes(&privKeyBytes)
defer zeroArray(&privKeyBytes)
for iteration := uint32(0); ; iteration++ {
var k *secp256k1.ModNScalar
// Step 6-9.
//
// Use RFC6979 to generate a deterministic nonce k in [1, n-1]
// parameterized by the secret key, message being signed, extra data
// that identifies the scheme, and an iteration count
k = btcec.NonceRFC6979(
privKeyBytes[:], hash, rfc6979ExtraDataV0[:], nil, iteration,
)
// Steps 10-15.
sig, err := schnorrSign(&privKeyScalar, k, pub, hash, opts)
k.Zero()
if err != nil {
// Try again with a new nonce.
continue
}
return sig, nil
}
}

View File

@@ -0,0 +1,323 @@
// Copyright (c) 2013-2017 The btcsuite developers
// Copyright (c) 2015-2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package schnorr
import (
"errors"
"strings"
"testing"
"testing/quick"
"lol.mleku.dev/chk"
"next.orly.dev/pkg/crypto/ec"
"next.orly.dev/pkg/crypto/ec/secp256k1"
"next.orly.dev/pkg/encoders/hex"
"github.com/davecgh/go-spew/spew"
)
type bip340Test struct {
secretKey string
publicKey string
auxRand string
message string
signature string
verifyResult bool
validPubKey bool
expectErr error
rfc6979 bool
}
var bip340TestVectors = []bip340Test{
{
secretKey: "0000000000000000000000000000000000000000000000000000000000000003",
publicKey: "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
auxRand: "0000000000000000000000000000000000000000000000000000000000000000",
message: "0000000000000000000000000000000000000000000000000000000000000000",
signature: "04E7F9037658A92AFEB4F25BAE5339E3DDCA81A353493827D26F16D92308E49E2A25E92208678A2DF86970DA91B03A8AF8815A8A60498B358DAF560B347AA557",
verifyResult: true,
validPubKey: true,
rfc6979: true,
},
{
secretKey: "0000000000000000000000000000000000000000000000000000000000000003",
publicKey: "F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE036F9",
auxRand: "0000000000000000000000000000000000000000000000000000000000000000",
message: "0000000000000000000000000000000000000000000000000000000000000000",
signature: "E907831F80848D1069A5371B402410364BDF1C5F8307B0084C55F1CE2DCA821525F66A4A85EA8B71E482A74F382D2CE5EBEEE8FDB2172F477DF4900D310536C0",
verifyResult: true,
validPubKey: true,
},
{
secretKey: "B7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF",
publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
auxRand: "0000000000000000000000000000000000000000000000000000000000000001",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "6896BD60EEAE296DB48A229FF71DFE071BDE413E6D43F917DC8DCF8C78DE33418906D11AC976ABCCB20B091292BFF4EA897EFCB639EA871CFA95F6DE339E4B0A",
verifyResult: true,
validPubKey: true,
},
{
secretKey: "C90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C9",
publicKey: "DD308AFEC5777E13121FA72B9CC1B7CC0139715309B086C960E18FD969774EB8",
auxRand: "C87AA53824B4D7AE2EB035A2B5BBBCCC080E76CDC6D1692C4B0B62D798E6D906",
message: "7E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C",
signature: "5831AAEED7B44BB74E5EAB94BA9D4294C49BCF2A60728D8B4C200F50DD313C1BAB745879A5AD954A72C45A91C3A51D3C7ADEA98D82F8481E0E1E03674A6F3FB7",
verifyResult: true,
validPubKey: true,
},
{
secretKey: "0B432B2677937381AEF05BB02A66ECD012773062CF3FA2549E44F58ED2401710",
publicKey: "25D1DFF95105F5253C4022F628A996AD3A0D95FBF21D468A1B33F8C160D8F517",
auxRand: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
message: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
signature: "7EB0509757E246F19449885651611CB965ECC1A187DD51B64FDA1EDC9637D5EC97582B9CB13DB3933705B32BA982AF5AF25FD78881EBB32771FC5922EFC66EA3",
verifyResult: true,
validPubKey: true,
},
{
publicKey: "D69C3509BB99E412E68B0FE8544E72837DFA30746D8BE2AA65975F29D22DC7B9",
message: "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703",
signature: "00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C6376AFB1548AF603B3EB45C9F8207DEE1060CB71C04E80F593060B07D28308D7F4",
verifyResult: true,
validPubKey: true,
},
{
publicKey: "EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
verifyResult: false,
validPubKey: false,
expectErr: secp256k1.ErrPubKeyNotOnCurve,
},
{
publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2",
verifyResult: false,
validPubKey: true,
expectErr: ErrSigRYIsOdd,
},
{
publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD",
verifyResult: false,
validPubKey: true,
expectErr: ErrSigRYIsOdd,
},
{
publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6",
verifyResult: false,
validPubKey: true,
expectErr: ErrUnequalRValues,
},
{
publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051",
verifyResult: false,
validPubKey: true,
expectErr: ErrSigRNotOnCurve,
},
{
publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197",
verifyResult: false,
validPubKey: true,
expectErr: ErrSigRNotOnCurve,
},
{
publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
verifyResult: false,
validPubKey: true,
expectErr: ErrUnequalRValues,
},
{
publicKey: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30",
message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89",
signature: "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B",
verifyResult: false,
validPubKey: false,
expectErr: secp256k1.ErrPubKeyXTooBig,
},
{
secretKey: "0340034003400340034003400340034003400340034003400340034003400340",
publicKey: "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
auxRand: "0000000000000000000000000000000000000000000000000000000000000000",
message: "",
signature: "71535DB165ECD9FBBC046E5FFAEA61186BB6AD436732FCCC25291A55895464CF6069CE26BF03466228F19A3A62DB8A649F2D560FAC652827D1AF0574E427AB63",
verifyResult: true,
validPubKey: true,
},
{
secretKey: "0340034003400340034003400340034003400340034003400340034003400340",
publicKey: "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
auxRand: "0000000000000000000000000000000000000000000000000000000000000000",
message: "11",
signature: "08A20A0AFEF64124649232E0693C583AB1B9934AE63B4C3511F3AE1134C6A303EA3173BFEA6683BD101FA5AA5DBC1996FE7CACFC5A577D33EC14564CEC2BACBF",
verifyResult: true,
validPubKey: true,
},
{
secretKey: "0340034003400340034003400340034003400340034003400340034003400340",
publicKey: "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
auxRand: "0000000000000000000000000000000000000000000000000000000000000000",
message: "0102030405060708090A0B0C0D0E0F1011",
signature: "5130F39A4059B43BC7CAC09A19ECE52B5D8699D1A71E3C52DA9AFDB6B50AC370C4A482B77BF960F8681540E25B6771ECE1E5A37FD80E5A51897C5566A97EA5A5",
verifyResult: true,
validPubKey: true,
},
{
secretKey: "0340034003400340034003400340034003400340034003400340034003400340",
publicKey: "778CAA53B4393AC467774D09497A87224BF9FAB6F6E68B23086497324D6FD117",
auxRand: "0000000000000000000000000000000000000000000000000000000000000000",
message: "99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999",
signature: "403B12B0D8555A344175EA7EC746566303321E5DBFA8BE6F091635163ECA79A8585ED3E3170807E7C03B720FC54C7B23897FCBA0E9D0B4A06894CFD249F22367",
verifyResult: true,
validPubKey: true,
},
}
// decodeHex decodes the passed hex string and returns the resulting bytes. It
// panics if an error occurs. This is only used in the tests as a helper since
// the only way it can fail is if there is an error in the test source code.
func decodeHex(hexStr string) []byte {
b, err := hex.Dec(hexStr)
if err != nil {
panic(
"invalid hex string in test source: err " + err.Error() +
", hex: " + hexStr,
)
}
return b
}
func TestSchnorrSign(t *testing.T) {
// t.Parallel()
for i, test := range bip340TestVectors {
if len(test.secretKey) == 0 {
continue
}
d := decodeHex(test.secretKey)
privKey, _ := btcec.SecKeyFromBytes(d)
var auxBytes [32]byte
aux := decodeHex(test.auxRand)
copy(auxBytes[:], aux)
msg := decodeHex(test.message)
var signOpts []SignOption
if !test.rfc6979 {
signOpts = []SignOption{CustomNonce(auxBytes)}
}
sig, err := Sign(privKey, msg, signOpts...)
if err != nil {
t.Fatalf("test #%v: sig generation failed: %v", i+1, err)
}
if strings.ToUpper(hex.Enc(sig.Serialize())) != test.signature {
t.Fatalf(
"test #%v: got signature %x : "+
"want %s", i+1, sig.Serialize(), test.signature,
)
}
pubKeyBytes := decodeHex(test.publicKey)
err = schnorrVerify(sig, msg, pubKeyBytes)
if err != nil {
t.Fail()
}
verify := err == nil
if test.verifyResult != verify {
t.Fatalf(
"test #%v: verification mismatch: "+
"expected %v, got %v", i+1, test.verifyResult, verify,
)
}
}
}
func TestSchnorrVerify(t *testing.T) {
t.Parallel()
for i, test := range bip340TestVectors {
pubKeyBytes := decodeHex(test.publicKey)
_, err := ParsePubKey(pubKeyBytes)
switch {
case !test.validPubKey && err != nil:
if !errors.Is(err, test.expectErr) {
t.Fatalf(
"test #%v: pubkey validation should "+
"have failed, expected %v, got %v", i,
test.expectErr, err,
)
}
continue
case err != nil:
t.Fatalf("test #%v: unable to parse pubkey: %v", i, err)
}
msg := decodeHex(test.message)
sig, err := ParseSignature(decodeHex(test.signature))
if err != nil {
t.Fatalf("unable to parse sig: %v", err)
}
err = schnorrVerify(sig, msg, pubKeyBytes)
if err != nil && test.verifyResult {
t.Fatalf(
"test #%v: verification shouldn't have failed: %v", i+1,
err,
)
}
verify := err == nil
if test.verifyResult != verify {
t.Fatalf(
"test #%v: verificaiton mismatch: expected "+
"%v, got %v", i, test.verifyResult, verify,
)
}
if !test.verifyResult && test.expectErr != nil {
if !errors.Is(err, test.expectErr) {
t.Fatalf(
"test #%v: expect error %v : got %v", i,
test.expectErr, err,
)
}
}
}
}
// TestSchnorrSignNoMutate tests that generating a schnorr signature doesn't
// modify/mutate the underlying secret key.
func TestSchnorrSignNoMutate(t *testing.T) {
t.Parallel()
// Assert that given a random secret key and message, we can generate
// a signature from that w/o modifying the underlying secret key.
f := func(privBytes, msg [32]byte) bool {
privBytesCopy := privBytes
privKey, _ := btcec.SecKeyFromBytes(privBytesCopy[:])
// Generate a signature for secret key with our message.
_, err := Sign(privKey, msg[:])
if err != nil {
t.Logf("unable to gen sig: %v", err)
return false
}
// We should be able to re-derive the secret key from raw
// bytes and have that match up again.
privKeyCopy, _ := btcec.SecKeyFromBytes(privBytes[:])
if *privKey != *privKeyCopy {
t.Logf(
"secret doesn't match: expected %v, got %v",
spew.Sdump(privKeyCopy), spew.Sdump(privKey),
)
return false
}
return true
}
if err := quick.Check(f, nil); chk.T(err) {
t.Fatalf("secret key modified: %v", err)
}
}