Skip to content

Commit

Permalink
btcec/schnorr/musig2: Allow infinity nonces
Browse files Browse the repository at this point in the history
This commit updates the musig2 module to allow
infinity nonces, as per Musig2 0.4.0.
  • Loading branch information
sputn1ck committed Aug 9, 2022
1 parent 4b2fe9f commit 44eb8c6
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 70 deletions.
52 changes: 52 additions & 0 deletions btcec/curve.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@
package btcec

import (
"fmt"

secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
)

// JacobianPoint is an element of the group formed by the secp256k1 curve in
// 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 {
Expand Down Expand Up @@ -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)
}
5 changes: 5 additions & 0 deletions btcec/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
6 changes: 5 additions & 1 deletion btcec/schnorr/musig2/musig2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1461,7 +1461,7 @@ func TestMusig2AggregateNoncesTestVectors(t *testing.T) {
},
expectedNonce: append(
append([]byte{}, expectedNonce[0:33]...),
getGBytes()...,
getInfinityBytes()...,
),
},
}
Expand Down Expand Up @@ -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 {
Expand Down
36 changes: 8 additions & 28 deletions btcec/schnorr/musig2/nonces.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,22 +331,20 @@ 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))
for i, pubNonceBytes := range pubNonces {
// 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
}

Expand All @@ -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
Expand All @@ -392,17 +371,18 @@ 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:]
})
if err != nil {
return finalNonce, err
}

copy(finalNonce[:], combinedNonce1.SerializeCompressed())
copy(finalNonce[:], btcec.JacobianToByteSlice(combinedNonce1))
copy(
finalNonce[btcec.PubKeyBytesLenCompressed:],
combinedNonce2.SerializeCompressed(),
btcec.JacobianToByteSlice(combinedNonce2),
)

return finalNonce, nil
Expand Down
84 changes: 43 additions & 41 deletions btcec/schnorr/musig2/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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()
}
Expand Down

0 comments on commit 44eb8c6

Please sign in to comment.