diff --git a/btcec/v2/go.mod b/btcec/v2/go.mod index ce5eb3321e..7628894e6e 100644 --- a/btcec/v2/go.mod +++ b/btcec/v2/go.mod @@ -7,3 +7,9 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 ) + +require github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + +// We depend on chainhash as is, so we need to raplce to use the version of +// chainhash included in the version of btcd we're buidling in. +replace github.com/btcsuite/btcd => ../../ diff --git a/btcec/v2/go.sum b/btcec/v2/go.sum index a0f9c5b8fa..67aa2a25c6 100644 --- a/btcec/v2/go.sum +++ b/btcec/v2/go.sum @@ -1,49 +1,6 @@ -github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= -github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= -github.com/btcsuite/btcd v0.22.0-beta h1:LTDpDKUM5EeOFBPM8IXpinEcmZ6FWfNZbE3lfrfdnWo= -github.com/btcsuite/btcd v0.22.0-beta/go.mod h1:9n5ntfhhHQBIhUvlhDvD3Qg6fRUj4jkN0VB8L8svzOA= -github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= -github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= -github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= -github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= -github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= -github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= -github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= -github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= -github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/btcec/v2/pubkey.go b/btcec/v2/pubkey.go index 8bd0f5d406..c9030f5189 100644 --- a/btcec/v2/pubkey.go +++ b/btcec/v2/pubkey.go @@ -9,17 +9,55 @@ import ( secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) -// These constants define the lengths of serialized public keys. -const ( - PubKeyBytesLenCompressed = 33 -) - const ( pubkeyCompressed byte = 0x2 // y_bit + x coord pubkeyUncompressed byte = 0x4 // x coord + y coord pubkeyHybrid byte = 0x6 // y_bit + x coord + y coord ) +const ( + // PubKeyBytesLenCompressed is the number of bytes of a serialized + // compressed public key. + PubKeyBytesLenCompressed = 33 + + // PubKeyBytesLenUncompressed is the number of bytes of a serialized + // uncompressed public key. + PubKeyBytesLenUncompressed = 65 + + // PubKeyFormatCompressedEven is the identifier prefix byte for a public key + // whose Y coordinate is even when serialized in the compressed format per + // section 2.3.4 of [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4). + PubKeyFormatCompressedEven byte = 0x02 + + // PubKeyFormatCompressedOdd is the identifier prefix byte for a public key + // whose Y coordinate is odd when serialized in the compressed format per + // section 2.3.4 of [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.4). + PubKeyFormatCompressedOdd byte = 0x03 + + // PubKeyFormatUncompressed is the identifier prefix byte for a public key + // when serialized according in the uncompressed format per section 2.3.3 of + // [SEC1](https://secg.org/sec1-v2.pdf#subsubsection.2.3.3). + PubKeyFormatUncompressed byte = 0x04 + + // PubKeyFormatHybridEven is the identifier prefix byte for a public key + // whose Y coordinate is even when serialized according to the hybrid format + // per section 4.3.6 of [ANSI X9.62-1998]. + // + // NOTE: This format makes little sense in practice an therefore this + // package will not produce public keys serialized in this format. However, + // it will parse them since they exist in the wild. + PubKeyFormatHybridEven byte = 0x06 + + // PubKeyFormatHybridOdd is the identifier prefix byte for a public key + // whose Y coordingate is odd when serialized according to the hybrid format + // per section 4.3.6 of [ANSI X9.62-1998]. + // + // NOTE: This format makes little sense in practice an therefore this + // package will not produce public keys serialized in this format. However, + // it will parse them since they exist in the wild. + PubKeyFormatHybridOdd byte = 0x07 +) + // IsCompressedPubKey returns true the the passed serialized public key has // been encoded in compressed format, and false otherwise. func IsCompressedPubKey(pubKey []byte) bool { diff --git a/btcec/v2/schnorr/error.go b/btcec/v2/schnorr/error.go new file mode 100644 index 0000000000..4014339647 --- /dev/null +++ b/btcec/v2/schnorr/error.go @@ -0,0 +1,25 @@ +// 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 ( + ecdsa_schnorr "github.com/decred/dcrd/dcrec/secp256k1/v4/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 = ecdsa_schnorr.ErrorKind + +// 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 = ecdsa_schnorr.Error + +// signatureError creates an Error given a set of arguments. +func signatureError(kind ErrorKind, desc string) Error { + return Error{Err: kind, Description: desc} +} diff --git a/btcec/v2/schnorr/pubkey.go b/btcec/v2/schnorr/pubkey.go new file mode 100644 index 0000000000..8ebacf6b6f --- /dev/null +++ b/btcec/v2/schnorr/pubkey.go @@ -0,0 +1,44 @@ +// 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" + + "github.com/btcsuite/btcd/btcec/v2" +) + +// These constants define the lengths of serialized public keys. +const ( + PubKeyBytesLen = 32 + + pubkeyCompressed byte = 0x2 // y_bit + x coord +) + +// ParsePubKey parses a public key for a koblitz curve from a bytestring into a +// ecdsa.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 append the compressed so we can re-use the existing + // pubkey parsing routine of the main btcec package. + var keyCompressed [btcec.PubKeyBytesLenCompressed]byte + keyCompressed[0] = pubkeyCompressed + copy(keyCompressed[1:], pubKeyStr) + + return btcec.ParsePubKey(keyCompressed[:]) +} + +// TODO(roasbeef): new type aliases on main pubkey struct for diff serialization? diff --git a/btcec/v2/schnorr/signature.go b/btcec/v2/schnorr/signature.go new file mode 100644 index 0000000000..7bb29d6dfa --- /dev/null +++ b/btcec/v2/schnorr/signature.go @@ -0,0 +1,548 @@ +// 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" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + ecdsa_schnorr "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" +) + +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 ( + tagHashAux = []byte("BIP0340/aux") + + tagHashNonce = []byte("BIP0340/nonce") + + tagHashChallenge = []byte("BIP0340/challenge") +) + +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([]byte("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 the more strict 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(ecdsa_schnorr.ErrSigTooShort, str) + } + if sigLen > SignatureSize { + str := fmt.Sprintf("malformed signature: too long: %d > %d", sigLen, + SignatureSize) + return nil, signatureError(ecdsa_schnorr.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(ecdsa_schnorr.ErrSigRTooBig, str) + } + var s btcec.ModNScalar + if overflow := s.SetByteSlice(sig[32:64]); overflow { + str := "invalid signature: s >= group order" + return nil, signatureError(ecdsa_schnorr.ErrSigSTooBig, str) + } + + // 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, pubKey *btcec.PublicKey) 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(ecdsa_schnorr.ErrInvalidHashLen, str) + } + + // Step 2. + // + // Fail if Q is not a point on the curve + if !pubKey.IsOnCurve() { + str := "pubkey point is not on curve" + return signatureError(ecdsa_schnorr.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 := pubKey.SerializeCompressed() + + commitment := chainhash.TaggedHash( + tagHashChallenge, rBytes[:], pBytes[1:], hash, + ) + + var e btcec.ModNScalar + if overflow := e.SetBytes((*[32]byte)(commitment)); overflow != 0 { + str := "hash of (r || P || m) too big" + return signatureError(ecdsa_schnorr.ErrSchnorrHashValue, str) + } + + // 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(ecdsa_schnorr.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(ecdsa_schnorr.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(ecdsa_schnorr.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 { + return schnorrVerify(sig, hash, pubKey) == 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 private key. The produced signature is deterministic +// (same message, nonce, and key yield the same signature) and canonical. +// +// WARNING: The hash MUST be 32 bytes and both the nonce and private 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 = private 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 rfc6679. 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 := pubKey.SerializeCompressed() + + commitment := chainhash.TaggedHash( + tagHashChallenge, rBytes[:], pBytes[1:], 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(ecdsa_schnorr.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, pubKey); err != nil { + return nil, err + } + } + + // Step 15. + // + // Return (r, s) + return sig, nil +} + +// SignOption is a functional option arguemnt that allows callers to modify the +// way we generate BIP-340 schnorr signatues. +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 private key. The produced signature is deterministic (same +// message and 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 private 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.PrivateKey, 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 = private 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 cuntionalati 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 rfc6679. If + // FastSign is passed, then we skip set 14. + + // Step 1. + // + // d' = int(d) + privKeyScalar := &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(ecdsa_schnorr.ErrInvalidHashLen, str) + } + + // Step 3. + // + // Fail if d = 0 or d >= n + if privKeyScalar.IsZero() { + str := "private key is zero" + return nil, signatureError(ecdsa_schnorr.ErrPrivateKeyIsZero, str) + } + + // Step 4. + // + // P = 'd*G + pub := privKey.PubKey() + + // Step 5. + // + // Negate d if P.y is odd. + pubKeyBytes := pub.SerializeCompressed() + if pubKeyBytes[0] == btcec.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 specifid by BIP-0340. + if opts.authNonce != nil { + // Step 6. + // + // t = bytes(d) xor tagged_hash("BIP0340/aux", a) + privBytes := privKeyScalar.Bytes() + t := chainhash.TaggedHash(tagHashAux, (*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( + tagHashNonce, 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(ecdsa_schnorr.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++ { + // Step 3. + // + // Use RFC6979 to generate a deterministic nonce k in [1, n-1] + // parameterized by the private 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 + } +} diff --git a/btcec/v2/schnorr/signature_test.go b/btcec/v2/schnorr/signature_test.go new file mode 100644 index 0000000000..f510cd56bb --- /dev/null +++ b/btcec/v2/schnorr/signature_test.go @@ -0,0 +1,236 @@ +// 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 ( + "encoding/hex" + "errors" + "strings" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + secp_ecdsa "github.com/decred/dcrd/dcrec/secp256k1/v4" + ecdsa_schnorr "github.com/decred/dcrd/dcrec/secp256k1/v4/schnorr" +) + +type bip340Test struct { + secretKey string + publicKey string + auxRand string + message string + signature string + verifyResult bool + validPubKey bool + expectErr error +} + +var bip340TestVectors = []bip340Test{ + { + 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: secp_ecdsa.ErrPubKeyNotOnCurve, + }, + { + publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + signature: "FFF97BD5755EEEA420453A14355235D382F6472F8568A18B2F057A14602975563CC27944640AC607CD107AE10923D9EF7A73C643E166BE5EBEAFA34B1AC553E2", + verifyResult: false, + validPubKey: true, + expectErr: ecdsa_schnorr.ErrSigRYIsOdd, + }, + { + publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + signature: "1FA62E331EDBC21C394792D2AB1100A7B432B013DF3F6FF4F99FCB33E0E1515F28890B3EDB6E7189B630448B515CE4F8622A954CFE545735AAEA5134FCCDB2BD", + verifyResult: false, + validPubKey: true, + expectErr: ecdsa_schnorr.ErrSigRYIsOdd, + }, + { + publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + signature: "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E177769961764B3AA9B2FFCB6EF947B6887A226E8D7C93E00C5ED0C1834FF0D0C2E6DA6", + verifyResult: false, + validPubKey: true, + expectErr: ecdsa_schnorr.ErrUnequalRValues, + }, + { + publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + signature: "0000000000000000000000000000000000000000000000000000000000000000123DDA8328AF9C23A94C1FEECFD123BA4FB73476F0D594DCB65C6425BD186051", + verifyResult: false, + validPubKey: true, + expectErr: ecdsa_schnorr.ErrSigRNotOnCurve, + }, + { + publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + signature: "00000000000000000000000000000000000000000000000000000000000000017615FBAF5AE28864013C099742DEADB4DBA87F11AC6754F93780D5A1837CF197", + verifyResult: false, + validPubKey: true, + expectErr: ecdsa_schnorr.ErrSigRNotOnCurve, + }, + { + publicKey: "DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659", + message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + signature: "4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D69E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B", + verifyResult: false, + validPubKey: true, + expectErr: ecdsa_schnorr.ErrUnequalRValues, + }, + { + publicKey: "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC30", + message: "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89", + signature: "6CFF5C3BA86C69EA4B7376F31A9BCB4F74C1976089B2D9963DA2E5543E17776969E89B4C5564D00349106B8497785DD7D1D713A8AE82B32FA79D5F7FC407D39B", + verifyResult: false, + validPubKey: false, + expectErr: secp_ecdsa.ErrPubKeyXTooBig, + }, +} + +// 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.DecodeString(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 { + d := decodeHex(test.secretKey) + privKey, pubKey := btcec.PrivKeyFromBytes(d) + + var auxBytes [32]byte + aux := decodeHex(test.auxRand) + copy(auxBytes[:], aux) + + msg := decodeHex(test.message) + + sig, err := Sign(privKey, msg, CustomNonce(auxBytes)) + if err != nil { + t.Fatalf("test #%v: sig generation failed: %v", i, err) + } + + if strings.ToUpper(hex.EncodeToString(sig.Serialize())) != test.signature { + t.Fatalf("test #%v: got signature %x : "+ + "want %s", i, sig.Serialize(), test.signature) + } + + err = schnorrVerify(sig, msg, pubKey) + if err != nil { + t.Fail() + } + + verify := err == nil + if test.verifyResult != verify { + t.Fatalf("test #%v: verification mismatch: "+ + "expected %v, got %v", i, test.verifyResult, verify) + } + } + } +} + +func TestSchnorrVerify(t *testing.T) { + t.Parallel() + + for i, test := range bip340TestVectors { + pubKeyBytes := decodeHex(test.publicKey) + + pubKey, 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, pubKey) + if err != nil && test.verifyResult { + t.Fatalf("test #%v: verification shouldn't have failed: %v", i, 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) + } + } + } +}