From 9cf3d678b7fc67e00d15764ddff82bc5bbc3a7bd Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 29 Jun 2022 18:48:02 +0200 Subject: [PATCH] btcec/schnorr/musig2: Allow infinity nonces --- btcec/curve.go | 52 +++++++++++++++++++++ btcec/error.go | 5 ++ btcec/schnorr/musig2/nonces.go | 36 ++++----------- btcec/schnorr/musig2/sign.go | 84 +++++++++++++++++----------------- 4 files changed, 108 insertions(+), 69 deletions(-) diff --git a/btcec/curve.go b/btcec/curve.go index 5224e35c8ef..b033f203324 100644 --- a/btcec/curve.go +++ b/btcec/curve.go @@ -4,6 +4,8 @@ package btcec import ( + "fmt" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -11,6 +13,9 @@ import ( // Jacobian projective coordinates and thus represents a point on the curve. type JacobianPoint = secp.JacobianPoint +// infinityPoint is the jacobian representation of the point at infinity. +var infinityPoint JacobianPoint + // MakeJacobianPoint returns a Jacobian point with the provided X, Y, and Z // coordinates. func MakeJacobianPoint(x, y, z *FieldVal) JacobianPoint { @@ -61,3 +66,50 @@ func ScalarBaseMultNonConst(k *ModNScalar, result *JacobianPoint) { func ScalarMultNonConst(k *ModNScalar, point, result *JacobianPoint) { secp.ScalarMultNonConst(k, point, result) } + +// ParseJacobian parses a byte slice point as a secp.Pubkey and converts the +// pubkey to the passed result. If the nonce is a zero slice, the infinityPoint +// is returned. +func ParseJacobian(point []byte) (JacobianPoint, error) { + var result JacobianPoint + + if len(point) != 33 { + str := fmt.Sprintf("invalid nonce: invalid length: %x", + len(point)) + return JacobianPoint{}, makeError(secp.ErrPubKeyInvalidLen, str) + } + + if point[0] == 0x00 { + return infinityPoint, nil + } + + noncePk, err := secp.ParsePubKey(point) + if err != nil { + return JacobianPoint{}, err + } + noncePk.AsJacobian(&result) + + return result, nil +} + +// JacobianToByteSlice converts the passed JacobianPoint to a Pubkey +// and serializes that to a byte slice. If the JacobianPoint is the infinity +// point, a zero slice is returned. +func JacobianToByteSlice(point JacobianPoint) []byte { + if point.X == infinityPoint.X && point.Y == infinityPoint.Y { + return make([]byte, 33) + } + + point.ToAffine() + + return NewPublicKey( + &point.X, &point.Y, + ).SerializeCompressed() +} + +// GeneratorJacobian sets the passed JacobianPoint to the Generator Point. +func GeneratorJacobian(jacobian *JacobianPoint) { + var k ModNScalar + k.SetInt(1) + ScalarBaseMultNonConst(&k, jacobian) +} diff --git a/btcec/error.go b/btcec/error.go index 81ca2b044db..df6ec678a8a 100644 --- a/btcec/error.go +++ b/btcec/error.go @@ -17,3 +17,8 @@ type Error = secp.Error // errors.As, so the caller can directly check against an error kind when // determining the reason for an error. type ErrorKind = secp.ErrorKind + +// makeError creates an secp.Error given a set of arguments. +func makeError(kind ErrorKind, desc string) Error { + return Error{Err: kind, Description: desc} +} diff --git a/btcec/schnorr/musig2/nonces.go b/btcec/schnorr/musig2/nonces.go index d79aa8bdbaf..22715ce785b 100644 --- a/btcec/schnorr/musig2/nonces.go +++ b/btcec/schnorr/musig2/nonces.go @@ -331,7 +331,7 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) // function to extra 33 bytes at a time from the packed 2x public // nonces. type nonceSlicer func([PubNonceSize]byte) []byte - combineNonces := func(slicer nonceSlicer) (*btcec.PublicKey, error) { + combineNonces := func(slicer nonceSlicer) (btcec.JacobianPoint, error) { // Convert the set of nonces into jacobian coordinates we can // use to accumulate them all into each other. pubNonceJs := make([]*btcec.JacobianPoint, len(pubNonces)) @@ -339,14 +339,12 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) // Using the slicer, extract just the bytes we need to // decode. var nonceJ btcec.JacobianPoint - pubNonce, err := btcec.ParsePubKey( - slicer(pubNonceBytes), - ) + + nonceJ, err := btcec.ParseJacobian(slicer(pubNonceBytes)) if err != nil { - return nil, err + return btcec.JacobianPoint{}, err } - pubNonce.AsJacobian(&nonceJ) pubNonceJs[i] = &nonceJ } @@ -359,27 +357,8 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) ) } - // Now that we've aggregated all the points, we need to check - // if this point is the point at infinity, if so, then we'll - // just return the generator. At a later step, the malicious - // party will be detected. - if aggregateNonce == infinityPoint { - // TODO(roasbeef): better way to get the generator w/ - // the new API? -- via old curve params instead? - var generator btcec.JacobianPoint - one := new(btcec.ModNScalar).SetInt(1) - btcec.ScalarBaseMultNonConst(one, &generator) - - generator.ToAffine() - return btcec.NewPublicKey( - &generator.X, &generator.Y, - ), nil - } - aggregateNonce.ToAffine() - return btcec.NewPublicKey( - &aggregateNonce.X, &aggregateNonce.Y, - ), nil + return aggregateNonce, nil } // The final nonce public nonce is actually two nonces, one that @@ -392,6 +371,7 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) if err != nil { return finalNonce, err } + combinedNonce2, err := combineNonces(func(n [PubNonceSize]byte) []byte { return n[btcec.PubKeyBytesLenCompressed:] }) @@ -399,10 +379,10 @@ func AggregateNonces(pubNonces [][PubNonceSize]byte) ([PubNonceSize]byte, error) return finalNonce, err } - copy(finalNonce[:], combinedNonce1.SerializeCompressed()) + copy(finalNonce[:], btcec.JacobianToByteSlice(combinedNonce1)) copy( finalNonce[btcec.PubKeyBytesLenCompressed:], - combinedNonce2.SerializeCompressed(), + btcec.JacobianToByteSlice(combinedNonce2), ) return finalNonce, nil diff --git a/btcec/schnorr/musig2/sign.go b/btcec/schnorr/musig2/sign.go index 0529c53c9ae..fce61aa8a81 100644 --- a/btcec/schnorr/musig2/sign.go +++ b/btcec/schnorr/musig2/sign.go @@ -200,20 +200,6 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey, option(opts) } - // Next, we'll parse the public nonces into R1 and R2. - r1, err := btcec.ParsePubKey( - combinedNonce[:btcec.PubKeyBytesLenCompressed], - ) - if err != nil { - return nil, err - } - r2, err := btcec.ParsePubKey( - combinedNonce[btcec.PubKeyBytesLenCompressed:], - ) - if err != nil { - return nil, err - } - // Compute the hash of all the keys here as we'll need it do aggregate // the keys and also at the final step of signing. keysHash := keyHashFingerprint(pubKeys, opts.sortKeys) @@ -259,19 +245,31 @@ func Sign(secNonce [SecNonceSize]byte, privKey *btcec.PrivateKey, ) nonceBlinder.SetByteSlice(nonceBlindHash[:]) - var nonce, r1J, r2J btcec.JacobianPoint - r1.AsJacobian(&r1J) - r2.AsJacobian(&r2J) + // Next, we'll parse the public nonces into R1 and R2. + r1J, err := btcec.ParseJacobian( + combinedNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return nil, err + } + r2J, err := btcec.ParseJacobian( + combinedNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return nil, err + } // With our nonce blinding value, we'll now combine both the public // nonces, using the blinding factor to tweak the second nonce: // * R = R_1 + b*R_2 + var nonce btcec.JacobianPoint btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J) btcec.AddNonConst(&r1J, &r2J, &nonce) // If the combined nonce it eh point at infinity, then we'll bail out. if nonce == infinityPoint { - return nil, ErrNoncePointAtInfinity + G := btcec.Generator() + G.AsJacobian(&nonce) } // Next we'll parse out our two secret nonces, which we'll be using in @@ -375,6 +373,7 @@ func (p *PartialSignature) Verify(pubNonce [PubNonceSize]byte, signingKey *btcec.PublicKey, msg [32]byte, signOpts ...SignOption) bool { pubKey := schnorr.SerializePubKey(signingKey) + return verifyPartialSig( p, pubNonce, combinedNonce, keySet, pubKey, msg, signOpts..., ) == nil @@ -399,19 +398,6 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte, // Next we'll parse out the two public nonces into something we can // use. // - // TODO(roasbeef): consolidate, new method - r1, err := btcec.ParsePubKey( - combinedNonce[:btcec.PubKeyBytesLenCompressed], - ) - if err != nil { - return err - } - r2, err := btcec.ParsePubKey( - combinedNonce[btcec.PubKeyBytesLenCompressed:], - ) - if err != nil { - return err - } // Compute the hash of all the keys here as we'll need it do aggregate // the keys and also at the final step of verification. @@ -456,45 +442,61 @@ func verifyPartialSig(partialSig *PartialSignature, pubNonce [PubNonceSize]byte, nonceBlindHash := chainhash.TaggedHash(NonceBlindTag, nonceMsgBuf.Bytes()) nonceBlinder.SetByteSlice(nonceBlindHash[:]) - var nonce, r1J, r2J btcec.JacobianPoint - r1.AsJacobian(&r1J) - r2.AsJacobian(&r2J) + r1J, err := btcec.ParseJacobian( + combinedNonce[:btcec.PubKeyBytesLenCompressed], + ) + if err != nil { + return err + } + r2J, err := btcec.ParseJacobian( + combinedNonce[btcec.PubKeyBytesLenCompressed:], + ) + if err != nil { + return err + } // With our nonce blinding value, we'll now combine both the public // nonces, using the blinding factor to tweak the second nonce: // * R = R_1 + b*R_2 + + var nonce btcec.JacobianPoint btcec.ScalarMultNonConst(&nonceBlinder, &r2J, &r2J) btcec.AddNonConst(&r1J, &r2J, &nonce) // Next, we'll parse out the set of public nonces this signer used to // generate the signature. - pubNonce1, err := btcec.ParsePubKey( + pubNonce1J, err := btcec.ParseJacobian( pubNonce[:btcec.PubKeyBytesLenCompressed], ) if err != nil { return err } - pubNonce2, err := btcec.ParsePubKey( + pubNonce2J, err := btcec.ParseJacobian( pubNonce[btcec.PubKeyBytesLenCompressed:], ) if err != nil { return err } + // If the nonce is the infinity point we set it to the Generator. + if nonce == infinityPoint { + btcec.GeneratorJacobian(&nonce) + } else { + nonce.ToAffine() + } + // We'll perform a similar aggregation and blinding operator as we did // above for the combined nonces: R' = R_1' + b*R_2'. - var pubNonceJ, pubNonce1J, pubNonce2J btcec.JacobianPoint - pubNonce1.AsJacobian(&pubNonce1J) - pubNonce2.AsJacobian(&pubNonce2J) + var pubNonceJ btcec.JacobianPoint + btcec.ScalarMultNonConst(&nonceBlinder, &pubNonce2J, &pubNonce2J) btcec.AddNonConst(&pubNonce1J, &pubNonce2J, &pubNonceJ) - nonce.ToAffine() + pubNonceJ.ToAffine() // If the combined nonce used in the challenge hash has an odd y // coordinate, then we'll negate our final public nonce. if nonce.Y.IsOdd() { - pubNonceJ.ToAffine() pubNonceJ.Y.Negate(1) pubNonceJ.Y.Normalize() }