291 lines
7.3 KiB
Go
291 lines
7.3 KiB
Go
package ethdss
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"fmt"
|
|
"math/big"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/certusone/wormhole/bridge/pkg/signatures/cryptotest"
|
|
"github.com/certusone/wormhole/bridge/pkg/signatures/ethschnorr"
|
|
"github.com/certusone/wormhole/bridge/pkg/signatures/secp256k1"
|
|
|
|
"go.dedis.ch/kyber/v3"
|
|
dkg "go.dedis.ch/kyber/v3/share/dkg/rabin"
|
|
)
|
|
|
|
var suite = secp256k1.NewBlakeKeccackSecp256k1()
|
|
|
|
var nbParticipants = 7
|
|
var t = nbParticipants/2 + 1
|
|
|
|
var partPubs []kyber.Point
|
|
var partSec []kyber.Scalar
|
|
|
|
var longterms []*dkg.DistKeyShare
|
|
var randoms []*dkg.DistKeyShare
|
|
|
|
var msg *big.Int
|
|
|
|
var randomStream = cryptotest.NewStream(&testing.T{}, 0)
|
|
|
|
func init() {
|
|
partPubs = make([]kyber.Point, nbParticipants)
|
|
partSec = make([]kyber.Scalar, nbParticipants)
|
|
for i := 0; i < nbParticipants; i++ {
|
|
kp := secp256k1.Generate(randomStream)
|
|
partPubs[i] = kp.Public
|
|
partSec[i] = kp.Private
|
|
}
|
|
// Corresponds to section 4.2, step 1 of Stinson, 2001 paper
|
|
longterms = genDistSecret(true) // Keep trying until valid public key
|
|
randoms = genDistSecret(false)
|
|
|
|
var err error
|
|
msg, err = rand.Int(rand.Reader, big.NewInt(0).Lsh(big.NewInt(1), 256))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func TestDSSNew(t *testing.T) {
|
|
dssArgs := DSSArgs{secret: partSec[0], participants: partPubs,
|
|
long: longterms[0], random: randoms[0], msg: msg, T: 4}
|
|
dss, err := NewDSS(dssArgs)
|
|
assert.NotNil(t, dss)
|
|
assert.Nil(t, err)
|
|
dssArgs.secret = suite.Scalar().Zero()
|
|
dss, err = NewDSS(dssArgs)
|
|
assert.Nil(t, dss)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestDSSPartialSigs(t *testing.T) {
|
|
dss0 := getDSS(0)
|
|
dss1 := getDSS(1)
|
|
ps0, err := dss0.PartialSig()
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, ps0)
|
|
assert.Len(t, dss0.partials, 1)
|
|
// second time should not affect list
|
|
ps0, err = dss0.PartialSig()
|
|
assert.Nil(t, err)
|
|
assert.NotNil(t, ps0)
|
|
assert.Len(t, dss0.partials, 1)
|
|
|
|
// wrong index
|
|
goodI := ps0.Partial.I
|
|
ps0.Partial.I = 100
|
|
err = dss1.ProcessPartialSig(ps0)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "invalid index")
|
|
ps0.Partial.I = goodI
|
|
|
|
// wrong sessionID
|
|
goodSessionID := ps0.SessionID
|
|
ps0.SessionID = []byte("ahhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh")
|
|
err = dss1.ProcessPartialSig(ps0)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "dss: session id")
|
|
ps0.SessionID = goodSessionID
|
|
|
|
// wrong Signature
|
|
goodSig := ps0.Signature
|
|
ps0.Signature = ethschnorr.NewSignature()
|
|
copy(ps0.Signature.CommitmentPublicAddress[:], randomBytes(20))
|
|
badSig := secp256k1.ToInt(suite.Scalar().Pick(randomStream))
|
|
ps0.Signature.Signature.Set(badSig)
|
|
assert.Error(t, dss1.ProcessPartialSig(ps0))
|
|
ps0.Signature = goodSig
|
|
|
|
// invalid partial sig
|
|
goodV := ps0.Partial.V
|
|
ps0.Partial.V = suite.Scalar().Zero()
|
|
ps0.Signature, err = ethschnorr.Sign(dss0.secret, ps0.Hash())
|
|
require.Nil(t, err)
|
|
err = dss1.ProcessPartialSig(ps0)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not valid")
|
|
ps0.Partial.V = goodV
|
|
ps0.Signature = goodSig
|
|
|
|
// fine
|
|
err = dss1.ProcessPartialSig(ps0)
|
|
assert.Nil(t, err)
|
|
|
|
// already received
|
|
assert.Error(t, dss1.ProcessPartialSig(ps0))
|
|
|
|
// if not enough partial signatures, can't generate signature
|
|
sig, err := dss1.Signature()
|
|
assert.Nil(t, sig) // XXX: Should also check err is nil?
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "not enough")
|
|
|
|
// enough partial sigs ?
|
|
for i := 2; i < nbParticipants; i++ {
|
|
dss := getDSS(i)
|
|
ps, e := dss.PartialSig()
|
|
require.Nil(t, e)
|
|
require.Nil(t, dss1.ProcessPartialSig(ps))
|
|
}
|
|
assert.True(t, dss1.EnoughPartialSig())
|
|
sig, err = dss1.Signature()
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, Verify(dss1.long.Commitments()[0], msg, sig))
|
|
}
|
|
|
|
var printTests = false
|
|
|
|
func printTest(t *testing.T, msg *big.Int, public kyber.Point,
|
|
signature ethschnorr.Signature) {
|
|
pX, pY := secp256k1.Coordinates(public)
|
|
fmt.Printf(" ['%064x',\n '%064x',\n '%064x',\n '%064x',\n '%040x'],\n",
|
|
msg, pX, pY, signature.Signature,
|
|
signature.CommitmentPublicAddress)
|
|
}
|
|
|
|
func TestDSSSignature(t *testing.T) {
|
|
dsss := make([]*DSS, nbParticipants)
|
|
pss := make([]*PartialSig, nbParticipants)
|
|
for i := 0; i < nbParticipants; i++ {
|
|
dsss[i] = getDSS(i)
|
|
ps, err := dsss[i].PartialSig()
|
|
require.Nil(t, err)
|
|
require.NotNil(t, ps)
|
|
pss[i] = ps
|
|
}
|
|
for i, dss := range dsss {
|
|
for j, ps := range pss {
|
|
if i == j {
|
|
continue
|
|
}
|
|
require.Nil(t, dss.ProcessPartialSig(ps))
|
|
}
|
|
}
|
|
// issue and verify signature
|
|
dss0 := dsss[0]
|
|
sig, err := dss0.Signature()
|
|
assert.NotNil(t, sig)
|
|
assert.Nil(t, err)
|
|
assert.NoError(t, ethschnorr.Verify(longterms[0].Public(), dss0.msg, sig))
|
|
// Original contains this second check. Unclear why.
|
|
assert.NoError(t, ethschnorr.Verify(longterms[0].Public(), dss0.msg, sig))
|
|
if printTests {
|
|
printTest(t, dss0.msg, dss0.long.Commitments()[0], sig)
|
|
}
|
|
}
|
|
|
|
func TestPartialSig_Hash(t *testing.T) {
|
|
observedHashes := make(map[*big.Int]bool)
|
|
for i := 0; i < nbParticipants; i++ {
|
|
psig, err := getDSS(i).PartialSig()
|
|
require.NoError(t, err)
|
|
hash := psig.Hash()
|
|
require.False(t, observedHashes[hash])
|
|
observedHashes[hash] = true
|
|
}
|
|
}
|
|
|
|
func getDSS(i int) *DSS {
|
|
dss, err := NewDSS(DSSArgs{secret: partSec[i], participants: partPubs,
|
|
long: longterms[i], random: randoms[i], msg: msg, T: t})
|
|
if dss == nil || err != nil {
|
|
panic("nil dss")
|
|
}
|
|
return dss
|
|
}
|
|
|
|
func _genDistSecret() []*dkg.DistKeyShare {
|
|
dkgs := make([]*dkg.DistKeyGenerator, nbParticipants)
|
|
for i := 0; i < nbParticipants; i++ {
|
|
dkg, err := dkg.NewDistKeyGenerator(suite, partSec[i], partPubs, nbParticipants/2+1)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dkgs[i] = dkg
|
|
}
|
|
// full secret sharing exchange
|
|
// 1. broadcast deals
|
|
resps := make([]*dkg.Response, 0, nbParticipants*nbParticipants)
|
|
for _, dkg := range dkgs {
|
|
deals, err := dkg.Deals()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
for i, d := range deals {
|
|
resp, err := dkgs[i].ProcessDeal(d)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if !resp.Response.Approved {
|
|
panic("wrong approval")
|
|
}
|
|
resps = append(resps, resp)
|
|
}
|
|
}
|
|
// 2. Broadcast responses
|
|
for _, resp := range resps {
|
|
for h, dkg := range dkgs {
|
|
// ignore all messages from ourself
|
|
if resp.Response.Index == uint32(h) {
|
|
continue
|
|
}
|
|
j, err := dkg.ProcessResponse(resp)
|
|
if err != nil || j != nil {
|
|
panic("wrongProcessResponse")
|
|
}
|
|
}
|
|
}
|
|
// 4. Broadcast secret commitment
|
|
for i, dkg := range dkgs {
|
|
scs, err := dkg.SecretCommits()
|
|
if err != nil {
|
|
panic("wrong SecretCommits")
|
|
}
|
|
for j, dkg2 := range dkgs {
|
|
if i == j {
|
|
continue
|
|
}
|
|
cc, err := dkg2.ProcessSecretCommits(scs)
|
|
if err != nil || cc != nil {
|
|
panic("wrong ProcessSecretCommits")
|
|
}
|
|
}
|
|
}
|
|
|
|
// 5. reveal shares
|
|
dkss := make([]*dkg.DistKeyShare, len(dkgs))
|
|
for i, dkg := range dkgs {
|
|
dks, err := dkg.DistKeyShare()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
dkss[i] = dks
|
|
}
|
|
return dkss
|
|
|
|
}
|
|
|
|
func genDistSecret(checkValidPublicKey bool) []*dkg.DistKeyShare {
|
|
rv := _genDistSecret()
|
|
if checkValidPublicKey {
|
|
// Because of the trick we're using to verify the signatures on-chain, we
|
|
// need to make sure that the ordinate of this distributed public key is
|
|
// in the lower half of {0,...,}
|
|
for !secp256k1.ValidPublicKey(rv[0].Public()) {
|
|
rv = _genDistSecret() // Keep trying until valid distributed public key.
|
|
}
|
|
}
|
|
return rv
|
|
}
|
|
|
|
func randomBytes(n int) []byte {
|
|
var buff = make([]byte, n)
|
|
_, _ = rand.Read(buff[:])
|
|
return buff
|
|
}
|