btcec/schnorr/musig2: Allow infinity nonces

This commit updates the musig2 module to allow
infinity nonces, as per Musig2 0.4.0.
This commit is contained in:
sputn1ck 2022-06-29 18:48:02 +02:00
parent 4b2fe9f83e
commit 44eb8c64f8
No known key found for this signature in database
GPG Key ID: 671103D881A5F0E4
5 changed files with 113 additions and 70 deletions

View File

@ -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.Publickey and returns the
// pubkey as a JacobianPoint. 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: %v",
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)
}

View File

@ -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}
}

View File

@ -1461,7 +1461,7 @@ func TestMusig2AggregateNoncesTestVectors(t *testing.T) {
},
expectedNonce: append(
append([]byte{}, expectedNonce[0:33]...),
getGBytes()...,
getInfinityBytes()...,
),
},
}
@ -1829,6 +1829,10 @@ func getNegGBytes() []byte {
return pk
}
func getInfinityBytes() []byte {
return make([]byte, 33)
}
func mustParseHex32(str string) [32]byte {
b, err := hex.DecodeString(str)
if err != nil {

View File

@ -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

View File

@ -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()
}