mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
Both protocol adapters shipped a Verify that returned false unconditionally — cggmp21 with "For now, return false", frost with "doesn't support ECDSA". That is not a safe default dressed up as one. A verifier that always fails is indistinguishable from a verifier that works and was handed a bad signature, so a caller using it as a signing-path health check sees a permanent, unexplained failure, and a caller treating "unsupported" as skippable gets no verification at all. Both now verify. FROST is Schnorr rather than ECDSA, but its group key is still a secp256k1 point and *ecdsa.PublicKey is only how this package's interface carries one, so the point is recovered and handed to the FROST verifier. pkg/protocol/curvekey.go holds the conversion both adapters need, replacing two hand-rolled decompressions (a Tonelli-Shanks square root in cggmp21, a separate helper in frost). CurvePoint goes the other way, and it validates rather than assumes: an (X, Y) that is not on the curve is REJECTED, not silently corrected. Compression keeps only the parity of Y, so a conversion that trusts X and parity alone will happily turn an inconsistent key into a valid point and then verify signatures against a key the caller never named. X=1 is a real X coordinate on secp256k1, which is how the test that found this was written. X is left-padded with FillBytes; big.Int drops leading zeros, so about one key in 256 would otherwise encode short and fail to convert at all. Bumps luxfi/threshold to v1.12.5 for pkg/quorum. Co-authored-by: Hanzo Dev <[email protected]>
86 lines
2.5 KiB
Go
86 lines
2.5 KiB
Go
package cggmp21
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"testing"
|
|
|
|
tecdsa "github.com/luxfi/threshold/pkg/ecdsa"
|
|
"github.com/luxfi/threshold/pkg/math/curve"
|
|
"github.com/luxfi/threshold/pkg/math/sample"
|
|
|
|
"github.com/luxfi/mpc/pkg/protocol"
|
|
)
|
|
|
|
// newTestSignature produces a genuine ECDSA signature over hash under secret
|
|
// x, using the same curve arithmetic the threshold protocol outputs.
|
|
func newTestSignature(t *testing.T, x curve.Scalar, hash []byte) *tecdsa.Signature {
|
|
t.Helper()
|
|
group := x.Curve()
|
|
|
|
k := sample.Scalar(rand.Reader, group)
|
|
m := curve.FromHash(group, hash)
|
|
kInv := group.NewScalar().Set(k).Invert()
|
|
R := kInv.ActOnBase()
|
|
r := R.XScalar()
|
|
s := r.Mul(x).Add(m).Mul(k)
|
|
return &tecdsa.Signature{R: R, S: s}
|
|
}
|
|
|
|
// signatureAdapter.Verify used to `return false` unconditionally with the
|
|
// comment "For now". Any caller using it as a signing-path health check saw a
|
|
// permanent, inexplicable failure; any caller treating an unverifiable
|
|
// signature as acceptable got no verification at all. This pins that it now
|
|
// actually verifies.
|
|
func TestSignatureAdapterVerifiesRealSignature(t *testing.T) {
|
|
group := curve.Secp256k1{}
|
|
|
|
for i := 0; i < 64; i++ {
|
|
x := sample.Scalar(rand.Reader, group)
|
|
pub := protocol.PublicKey(x.ActOnBase())
|
|
if pub == nil {
|
|
t.Fatalf("iteration %d: could not render public key", i)
|
|
}
|
|
|
|
hash := make([]byte, 32)
|
|
if _, err := rand.Read(hash); err != nil {
|
|
t.Fatalf("rand: %v", err)
|
|
}
|
|
|
|
adapter := &signatureAdapter{sig: newTestSignature(t, x, hash)}
|
|
if !adapter.Verify(pub, hash) {
|
|
t.Fatalf("iteration %d: Verify rejected a valid signature", i)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSignatureAdapterRejectsBadInput(t *testing.T) {
|
|
group := curve.Secp256k1{}
|
|
x := sample.Scalar(rand.Reader, group)
|
|
pub := protocol.PublicKey(x.ActOnBase())
|
|
|
|
hash := make([]byte, 32)
|
|
copy(hash, "the message that was signed-----")
|
|
adapter := &signatureAdapter{sig: newTestSignature(t, x, hash)}
|
|
|
|
otherHash := make([]byte, 32)
|
|
copy(otherHash, "a different message entirely----")
|
|
|
|
otherPub := protocol.PublicKey(sample.Scalar(rand.Reader, group).ActOnBase())
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
run func() bool
|
|
}{
|
|
{"wrong message", func() bool { return adapter.Verify(pub, otherHash) }},
|
|
{"wrong public key", func() bool { return adapter.Verify(otherPub, hash) }},
|
|
{"nil public key", func() bool { return adapter.Verify(nil, hash) }},
|
|
{"nil signature", func() bool { return (&signatureAdapter{}).Verify(pub, hash) }},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if tc.run() {
|
|
t.Fatal("Verify accepted something it must reject")
|
|
}
|
|
})
|
|
}
|
|
}
|