From cd6a30778ee62dbae733c1f9eb270a4d89c4fdc3 Mon Sep 17 00:00:00 2001 From: armfazh Date: Mon, 11 Jul 2022 23:45:54 -0700 Subject: [PATCH 1/3] New tss package, and includes tss/frost threshold signature scheme Changes: - The package tss will provide the threshold signature schemes. - The package tss/frost implements the FROST threshold signature scheme for Schnorr signatures. This scheme is under standardization process at IETF/CFRG [2]. Test vectors from [3] are passing for P256 and Ristretto255 groups. - Version supported: v11 References: [1] frost paper: https://eprint.iacr.org/2020/852 [2] draft-irtf-cfrg-frost: https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost [3] test-vectors: https://github.com/cfrg/draft-irtf-cfrg-frost --- tss/frost/combiner.go | 79 ++++++ tss/frost/commit.go | 134 ++++++++++ tss/frost/frost.go | 94 +++++++ tss/frost/frost_test.go | 153 +++++++++++ tss/frost/peer.go | 161 ++++++++++++ tss/frost/suites.go | 77 ++++++ tss/frost/testdata/frost_p256_sha256.json | 68 +++++ .../testdata/frost_ristretto255_sha512.json | 68 +++++ tss/frost/vectors_test.go | 237 ++++++++++++++++++ tss/tss.go | 2 + 10 files changed, 1073 insertions(+) create mode 100644 tss/frost/combiner.go create mode 100644 tss/frost/commit.go create mode 100644 tss/frost/frost.go create mode 100644 tss/frost/frost_test.go create mode 100644 tss/frost/peer.go create mode 100644 tss/frost/suites.go create mode 100644 tss/frost/testdata/frost_p256_sha256.json create mode 100644 tss/frost/testdata/frost_ristretto255_sha512.json create mode 100644 tss/frost/vectors_test.go create mode 100644 tss/tss.go diff --git a/tss/frost/combiner.go b/tss/frost/combiner.go new file mode 100644 index 000000000..488f5f9a5 --- /dev/null +++ b/tss/frost/combiner.go @@ -0,0 +1,79 @@ +package frost + +import ( + "errors" + "fmt" +) + +type Combiner struct { + Suite + threshold uint + maxSigners uint +} + +func NewCombiner(s Suite, threshold, maxSigners uint) (*Combiner, error) { + if threshold > maxSigners { + return nil, errors.New("frost: invalid parameters") + } + + return &Combiner{Suite: s, threshold: threshold, maxSigners: maxSigners}, nil +} + +func (c Combiner) CheckSignShares( + signShares []*SignShare, + pubKeySigners []*PublicKey, + coms []*Commitment, + pubKeyGroup *PublicKey, + msg []byte, +) bool { + if l := len(signShares); !(int(c.threshold) < l && l <= int(c.maxSigners)) { + return false + } + if l := len(pubKeySigners); !(int(c.threshold) < l && l <= int(c.maxSigners)) { + return false + } + if l := len(coms); !(int(c.threshold) < l && l <= int(c.maxSigners)) { + return false + } + + for i := range signShares { + if !signShares[i].Verify(c.Suite, pubKeySigners[i], coms[i], coms, pubKeyGroup, msg) { + return false + } + } + + return true +} + +func (c Combiner) Sign(msg []byte, coms []*Commitment, signShares []*SignShare) ([]byte, error) { + if l := len(coms); l <= int(c.threshold) { + return nil, fmt.Errorf("frost: only %v shares of %v required", l, c.threshold) + } + + bindingFactors, err := c.Suite.getBindingFactors(coms, msg) + if err != nil { + return nil, err + } + + groupCom, err := c.Suite.getGroupCommitment(coms, bindingFactors) + if err != nil { + return nil, err + } + + gcEnc, err := groupCom.MarshalBinaryCompress() + if err != nil { + return nil, err + } + + z := c.Suite.g.NewScalar() + for i := range signShares { + z.Add(z, signShares[i].s.Value) + } + + zEnc, err := z.MarshalBinary() + if err != nil { + return nil, err + } + + return append(append([]byte{}, gcEnc...), zEnc...), nil +} diff --git a/tss/frost/commit.go b/tss/frost/commit.go new file mode 100644 index 000000000..77ed1b91d --- /dev/null +++ b/tss/frost/commit.go @@ -0,0 +1,134 @@ +package frost + +import ( + "errors" + "fmt" + "io" + "sort" + + "github.com/cloudflare/circl/group" +) + +type Nonce struct { + ID group.Scalar + hiding, binding group.Scalar +} + +func (s Suite) nonceGenerate(rnd io.Reader, secret group.Scalar) (group.Scalar, error) { + randomBytes := make([]byte, 32) + _, err := io.ReadFull(rnd, randomBytes) + if err != nil { + return nil, err + } + secretEnc, err := secret.MarshalBinary() + if err != nil { + return nil, err + } + + return s.hasher.h3(append(randomBytes, secretEnc...)), nil +} + +type Commitment struct { + ID group.Scalar + hiding, binding group.Element +} + +func (c Commitment) MarshalBinary() ([]byte, error) { + id, err := c.ID.MarshalBinary() + if err != nil { + return nil, err + } + h, err := c.hiding.MarshalBinaryCompress() + if err != nil { + return nil, err + } + b, err := c.binding.MarshalBinaryCompress() + if err != nil { + return nil, err + } + + return append(append(id, h...), b...), nil +} + +func encodeCommitments(coms []*Commitment) ([]byte, error) { + sort.SliceStable(coms, func(i, j int) bool { + return coms[i].ID.(fmt.Stringer).String() < coms[j].ID.(fmt.Stringer).String() + }) + + var out []byte + for i := range coms { + cEnc, err := coms[i].MarshalBinary() + if err != nil { + return nil, err + } + out = append(out, cEnc...) + } + return out, nil +} + +type bindingFactor struct { + ID group.Scalar + factor group.Scalar +} + +func (s Suite) getBindingFactorFromID(bindingFactors []bindingFactor, id group.Scalar) (group.Scalar, error) { + for i := range bindingFactors { + if bindingFactors[i].ID.IsEqual(id) { + return bindingFactors[i].factor, nil + } + } + return nil, errors.New("frost: id not found") +} + +func (s Suite) getBindingFactors(coms []*Commitment, msg []byte) ([]bindingFactor, error) { + msgHash := s.hasher.h4(msg) + encodeComs, err := encodeCommitments(coms) + if err != nil { + return nil, err + } + encodeComsHash := s.hasher.h5(encodeComs) + rhoInputPrefix := append(msgHash, encodeComsHash...) + + bindingFactors := make([]bindingFactor, len(coms)) + for i := range coms { + id, err := coms[i].ID.MarshalBinary() + if err != nil { + return nil, err + } + rhoInput := append(append([]byte{}, rhoInputPrefix...), id...) + bf := s.hasher.h1(rhoInput) + bindingFactors[i] = bindingFactor{ID: coms[i].ID, factor: bf} + } + + return bindingFactors, nil +} + +func (s Suite) getGroupCommitment(coms []*Commitment, bindingFactors []bindingFactor) (group.Element, error) { + gc := s.g.NewElement() + tmp := s.g.NewElement() + for i := range coms { + bf, err := s.getBindingFactorFromID(bindingFactors, coms[i].ID) + if err != nil { + return nil, err + } + tmp.Mul(coms[i].binding, bf) + tmp.Add(tmp, coms[i].hiding) + gc.Add(gc, tmp) + } + + return gc, nil +} + +func (s Suite) getChallenge(groupCom group.Element, pubKey *PublicKey, msg []byte) (group.Scalar, error) { + gcEnc, err := groupCom.MarshalBinaryCompress() + if err != nil { + return nil, err + } + pkEnc, err := pubKey.key.MarshalBinaryCompress() + if err != nil { + return nil, err + } + chInput := append(append(append([]byte{}, gcEnc...), pkEnc...), msg...) + + return s.hasher.h2(chInput), nil +} diff --git a/tss/frost/frost.go b/tss/frost/frost.go new file mode 100644 index 000000000..d3d99680c --- /dev/null +++ b/tss/frost/frost.go @@ -0,0 +1,94 @@ +// Package frost provides the FROST threshold signature scheme for Schnorr signatures. +// +// References +// +// FROST paper: https://eprint.iacr.org/2020/852 +// draft-irtf-cfrg-frost: https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost +// +// Version supported: v11 +package frost + +import ( + "io" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +type PrivateKey struct { + Suite + key group.Scalar + pubKey *PublicKey +} + +type PublicKey struct { + Suite + key group.Element +} + +func GenerateKey(s Suite, rnd io.Reader) *PrivateKey { + return &PrivateKey{s, s.g.RandomNonZeroScalar(rnd), nil} +} + +func (k *PrivateKey) Public() *PublicKey { + return &PublicKey{k.Suite, k.Suite.g.NewElement().MulGen(k.key)} +} + +func (k *PrivateKey) Split(rnd io.Reader, threshold, maxSigners uint) ( + []PeerSigner, secretsharing.SecretCommitment, error, +) { + ss := secretsharing.New(rnd, threshold, k.key) + shares := ss.Share(maxSigners) + + peers := make([]PeerSigner, len(shares)) + for i := range shares { + peers[i] = PeerSigner{ + Suite: k.Suite, + threshold: uint16(threshold), + maxSigners: uint16(maxSigners), + keyShare: secretsharing.Share{ + ID: shares[i].ID, + Value: shares[i].Value, + }, + myPubKey: nil, + } + } + + return peers, ss.CommitSecret(), nil +} + +func Verify(s Suite, pubKey *PublicKey, msg, signature []byte) bool { + params := s.g.Params() + Ne, Ns := params.CompressedElementLength, params.ScalarLength + if len(signature) < int(Ne+Ns) { + return false + } + + REnc := signature[:Ne] + R := s.g.NewElement() + err := R.UnmarshalBinary(REnc) + if err != nil { + return false + } + + zEnc := signature[Ne : Ne+Ns] + z := s.g.NewScalar() + err = z.UnmarshalBinary(zEnc) + if err != nil { + return false + } + + pubKeyEnc, err := pubKey.key.MarshalBinaryCompress() + if err != nil { + return false + } + + chInput := append(append(append([]byte{}, REnc...), pubKeyEnc...), msg...) + c := s.hasher.h2(chInput) + + l := s.g.NewElement().MulGen(z) + r := s.g.NewElement().Mul(pubKey.key, c) + r.Add(r, R) + + return l.IsEqual(r) +} diff --git a/tss/frost/frost_test.go b/tss/frost/frost_test.go new file mode 100644 index 000000000..4ca8b7f63 --- /dev/null +++ b/tss/frost/frost_test.go @@ -0,0 +1,153 @@ +package frost_test + +import ( + "crypto/rand" + "fmt" + "testing" + + "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/tss/frost" +) + +func TestFrost(t *testing.T) { + for _, si := range []frost.Suite{frost.Ristretto255, frost.P256} { + t.Run(fmt.Sprintf("%v", si), func(tt *testing.T) { testFrost(tt, si) }) + } +} + +func testFrost(tt *testing.T, suite frost.Suite) { + t, n := uint(3), uint(5) + + privKey := frost.GenerateKey(suite, rand.Reader) + pubKeyGroup := privKey.Public() + peers, keyShareCommits, err := privKey.Split(rand.Reader, t, n) + test.CheckNoErr(tt, err, "failed to split secret") + + // every peer can validate its own keyShare. + for i := range peers { + valid := peers[i].CheckKeyShare(keyShareCommits) + test.CheckOk(valid == true, "invalid key share", tt) + } + + // Only k peers try to generate a signature. + for k := uint(0); k < n; k++ { + // round 1 + nonces := make([]*frost.Nonce, k) + commits := make([]*frost.Commitment, k) + pkSigners := make([]*frost.PublicKey, k) + for i := range peers[:k] { + nonces[i], commits[i], err = peers[i].Commit(rand.Reader) + test.CheckNoErr(tt, err, "failed to commit") + pkSigners[i] = peers[i].Public() + } + + // round 2 + msg := []byte("it's cold here") + signShares := make([]*frost.SignShare, k) + for i := range peers[:k] { + signShares[i], err = peers[i].Sign(msg, pubKeyGroup, nonces[i], commits) + test.CheckNoErr(tt, err, "failed to create a sign share") + } + + // Combiner + combiner, err := frost.NewCombiner(suite, t, n) + test.CheckNoErr(tt, err, "failed to create combiner") + + valid := combiner.CheckSignShares(signShares, pkSigners, commits, pubKeyGroup, msg) + if k > t { + test.CheckOk(valid == true, "invalid sign shares", tt) + } else { + test.CheckOk(valid == false, "must be invalid sign shares", tt) + } + + signature, err := combiner.Sign(msg, commits, signShares) + if k > t { + test.CheckNoErr(tt, err, "failed to produce signature") + // anyone can verify + valid := frost.Verify(suite, pubKeyGroup, msg, signature) + test.CheckOk(valid == true, "invalid signature", tt) + } else { + test.CheckIsErr(tt, err, "should not produce a signature") + test.CheckOk(signature == nil, "not nil signature", tt) + } + } +} + +func BenchmarkFrost(b *testing.B) { + for _, si := range []frost.Suite{frost.Ristretto255, frost.P256} { + b.Run(fmt.Sprintf("%v", si), func(bb *testing.B) { benchmarkFrost(bb, si) }) + } +} + +func benchmarkFrost(b *testing.B, suite frost.Suite) { + t, n := uint(3), uint(5) + + privKey := frost.GenerateKey(suite, rand.Reader) + peers, keyShareCommits, err := privKey.Split(rand.Reader, t, n) + test.CheckNoErr(b, err, "failed to split secret") + + b.Run("SplitKey", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _ = privKey.Split(rand.Reader, t, n) + } + }) + + pubKeyGroup := privKey.Public() + msg := []byte("it's cold here") + + nonces := make([]*frost.Nonce, len(peers)) + commits := make([]*frost.Commitment, len(peers)) + pkSigners := make([]*frost.PublicKey, len(peers)) + for i := range peers { + nonces[i], commits[i], err = peers[i].Commit(rand.Reader) + test.CheckNoErr(b, err, "failed to commit") + pkSigners[i] = peers[i].Public() + } + + b.Run("CheckKeyShare", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = peers[0].CheckKeyShare(keyShareCommits) + } + }) + + b.Run("Commit", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _, _ = peers[0].Commit(rand.Reader) + } + }) + + b.Run("SignShare", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = peers[0].Sign(msg, pubKeyGroup, nonces[0], commits) + } + }) + + signShares := make([]*frost.SignShare, len(peers)) + for i := range peers { + signShares[i], err = peers[i].Sign(msg, pubKeyGroup, nonces[i], commits) + test.CheckNoErr(b, err, "failed to create a sign share") + } + + combiner, err := frost.NewCombiner(suite, t, n) + test.CheckNoErr(b, err, "failed to create combiner") + + b.Run("CheckSignShares", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _ = combiner.CheckSignShares(signShares, pkSigners, commits, pubKeyGroup, msg) + } + }) + + b.Run("Sign", func(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = combiner.Sign(msg, commits, signShares) + } + }) + + b.Run("Verify", func(b *testing.B) { + signature, _ := combiner.Sign(msg, commits, signShares) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = frost.Verify(suite, pubKeyGroup, msg, signature) + } + }) +} diff --git a/tss/frost/peer.go b/tss/frost/peer.go new file mode 100644 index 000000000..b80c9d4b1 --- /dev/null +++ b/tss/frost/peer.go @@ -0,0 +1,161 @@ +package frost + +import ( + "errors" + "fmt" + "io" + "sort" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/math/polynomial" + "github.com/cloudflare/circl/secretsharing" +) + +type PeerSigner struct { + Suite + threshold uint16 + maxSigners uint16 + keyShare secretsharing.Share + myPubKey *PublicKey +} + +func (p PeerSigner) Commit(rnd io.Reader) (*Nonce, *Commitment, error) { + hidingNonce, err := p.Suite.nonceGenerate(rnd, p.keyShare.Value) + if err != nil { + return nil, nil, err + } + bindingNonce, err := p.Suite.nonceGenerate(rnd, p.keyShare.Value) + if err != nil { + return nil, nil, err + } + + return p.commitWithNonce(hidingNonce, bindingNonce) +} + +func (p PeerSigner) commitWithNonce(hidingNonce, bindingNonce group.Scalar) (*Nonce, *Commitment, error) { + hidingNonceCom := p.Suite.g.NewElement().MulGen(hidingNonce) + bindingNonceCom := p.Suite.g.NewElement().MulGen(bindingNonce) + return &Nonce{p.keyShare.ID, hidingNonce, bindingNonce}, &Commitment{p.keyShare.ID, hidingNonceCom, bindingNonceCom}, nil +} + +func (p PeerSigner) CheckKeyShare(c secretsharing.SecretCommitment) bool { + return secretsharing.Verify(uint(p.threshold), p.keyShare, c) +} + +func (p PeerSigner) Public() *PublicKey { + if p.myPubKey == nil { + p.myPubKey = &PublicKey{p.Suite, p.Suite.g.NewElement().MulGen(p.keyShare.Value)} + } + return p.myPubKey +} + +func (p PeerSigner) Sign(msg []byte, pubKey *PublicKey, nonce *Nonce, coms []*Commitment) (*SignShare, error) { + if !p.keyShare.ID.IsEqual(nonce.ID) { + return nil, errors.New("frost: bad id") + } + aux, err := p.Suite.common(p.keyShare.ID, msg, pubKey, coms) + if err != nil { + return nil, err + } + + tmp := p.Suite.g.NewScalar().Mul(nonce.binding, aux.bindingFactor) + signShare := p.Suite.g.NewScalar().Add(nonce.hiding, tmp) + tmp.Mul(aux.lambdaID, p.keyShare.Value) + tmp.Mul(tmp, aux.challenge) + signShare.Add(signShare, tmp) + + return &SignShare{s: secretsharing.Share{ + ID: p.keyShare.ID, + Value: signShare, + }}, nil +} + +type SignShare struct { + s secretsharing.Share +} + +func (s *SignShare) Verify( + suite Suite, + pubKeySigner *PublicKey, + comSigner *Commitment, + coms []*Commitment, + pubKeyGroup *PublicKey, + msg []byte, +) bool { + if s.s.ID != comSigner.ID || s.s.ID.IsZero() { + return false + } + + aux, err := suite.common(s.s.ID, msg, pubKeyGroup, coms) + if err != nil { + return false + } + + comShare := suite.g.NewElement().Mul(coms[aux.idx].binding, aux.bindingFactor) + comShare.Add(comShare, coms[aux.idx].hiding) + + l := suite.g.NewElement().MulGen(s.s.Value) + r := suite.g.NewElement().Mul(pubKeySigner.key, suite.g.NewScalar().Mul(aux.challenge, aux.lambdaID)) + r.Add(r, comShare) + + return l.IsEqual(r) +} + +type commonAux struct { + idx uint + lambdaID group.Scalar + challenge group.Scalar + bindingFactor group.Scalar +} + +func (s Suite) common(id group.Scalar, msg []byte, pubKey *PublicKey, coms []*Commitment) (aux *commonAux, err error) { + if !sort.SliceIsSorted(coms, + func(i, j int) bool { + return coms[i].ID.(fmt.Stringer).String() < coms[j].ID.(fmt.Stringer).String() + }, + ) { + return nil, errors.New("frost: commitments must be sorted") + } + + idx := sort.Search(len(coms), func(j int) bool { + return coms[j].ID.(fmt.Stringer).String() >= id.(fmt.Stringer).String() + }) + if !(idx < len(coms) && coms[idx].ID.IsEqual(id)) { + return nil, errors.New("frost: commitment not present") + } + + bindingFactors, err := s.getBindingFactors(coms, msg) + if err != nil { + return nil, err + } + + bindingFactor, err := s.getBindingFactorFromID(bindingFactors, id) + if err != nil { + return nil, err + } + + groupCom, err := s.getGroupCommitment(coms, bindingFactors) + if err != nil { + return nil, err + } + + challenge, err := s.getChallenge(groupCom, pubKey, msg) + if err != nil { + return nil, err + } + + peers := make([]group.Scalar, len(coms)) + for i := range coms { + peers[i] = coms[i].ID.Copy() + } + + zero := s.g.NewScalar() + lambdaID := polynomial.LagrangeBase(uint(idx), peers, zero) + + return &commonAux{ + idx: uint(idx), + lambdaID: lambdaID, + challenge: challenge, + bindingFactor: bindingFactor, + }, nil +} diff --git a/tss/frost/suites.go b/tss/frost/suites.go new file mode 100644 index 000000000..f4f917179 --- /dev/null +++ b/tss/frost/suites.go @@ -0,0 +1,77 @@ +package frost + +import ( + "crypto" + _ "crypto/sha256" // added to link library. + _ "crypto/sha512" // added to link library. + "fmt" + + r255 "github.com/bwesterb/go-ristretto" + "github.com/cloudflare/circl/group" +) + +var ( + P256 = Suite{group.P256, suiteP{group.P256, suiteCommon{crypto.SHA256, "FROST-P256-SHA256-v11"}}} + Ristretto255 = Suite{group.Ristretto255, suiteRis255{suiteCommon{crypto.SHA512, "FROST-RISTRETTO255-SHA512-v11"}}} +) + +type Suite struct { + g group.Group + hasher interface { + h1(m []byte) group.Scalar + h2(m []byte) group.Scalar + h3(m []byte) group.Scalar + h4(m []byte) []byte + h5(m []byte) []byte + } +} + +func (s Suite) String() string { return s.hasher.(fmt.Stringer).String() } + +const ( + labelRho = "rho" + labelChal = "chal" + labelNonce = "nonce" + labelMsg = "msg" + labelCom = "com" +) + +type suiteCommon struct { + hash crypto.Hash + context string +} + +func (s suiteCommon) String() string { return s.context[:len(s.context)-4] } +func (s suiteCommon) h4(m []byte) []byte { return s.hashLabeled(labelMsg, m) } +func (s suiteCommon) h5(m []byte) []byte { return s.hashLabeled(labelCom, m) } +func (s suiteCommon) hashLabeled(label string, m []byte) []byte { + H := s.hash.New() + _, _ = H.Write([]byte(s.context + label)) + _, _ = H.Write(m) + return H.Sum(nil) +} + +type suiteP struct { + g group.Group + suiteCommon +} + +func (s suiteP) h1(m []byte) group.Scalar { return s.g.HashToScalar(m, []byte(s.context+labelRho)) } +func (s suiteP) h2(m []byte) group.Scalar { return s.g.HashToScalar(m, []byte(s.context+labelChal)) } +func (s suiteP) h3(m []byte) group.Scalar { return s.g.HashToScalar(m, []byte(s.context+labelNonce)) } + +type suiteRis255 struct{ suiteCommon } + +func (s suiteRis255) getScalar(input []byte) group.Scalar { + var data [64]byte + copy(data[:], input[:64]) + y := new(r255.Scalar).SetReduced(&data) + bytes, _ := y.MarshalBinary() + z := group.Ristretto255.NewScalar() + _ = z.UnmarshalBinary(bytes) + return z +} + +func (s suiteRis255) h1(m []byte) group.Scalar { return s.getScalar(s.hashLabeled(labelRho, m)) } +func (s suiteRis255) h2(m []byte) group.Scalar { return s.getScalar(s.hashLabeled(labelChal, m)) } +func (s suiteRis255) h3(m []byte) group.Scalar { return s.getScalar(s.hashLabeled(labelNonce, m)) } diff --git a/tss/frost/testdata/frost_p256_sha256.json b/tss/frost/testdata/frost_p256_sha256.json new file mode 100644 index 000000000..5b6106523 --- /dev/null +++ b/tss/frost/testdata/frost_p256_sha256.json @@ -0,0 +1,68 @@ +{ + "config": { + "MAX_PARTICIPANTS": "3", + "NUM_PARTICIPANTS": "2", + "MIN_PARTICIPANTS": "2", + "name": "FROST(P-256, SHA-256)", + "group": "P-256", + "hash": "SHA-256" + }, + "inputs": { + "group_secret_key": "8ba9bba2e0fd8c4767154d35a0b7562244a4aaf6f36c8fb8735fa48b301bd8de", + "group_public_key": "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70", + "message": "74657374", + "share_polynomial_coefficients": [ + "80f25e6c0709353e46bfbe882a11bdbb1f8097e46340eb8673b7e14556e6c3a4" + ], + "participants": { + "1": { + "participant_share": "0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731" + }, + "2": { + "participant_share": "8d8e787bef0ff6c2f494ca45f4dad198c6bee01212d6c84067159c52e1863ad5" + }, + "3": { + "participant_share": "0e80d6e8f6192c003b5488ce1eec8f5429587d48cf001541e713b2d53c09d928" + } + } + }, + "round_one_outputs": { + "participant_list": "1,3", + "participants": { + "1": { + "hiding_nonce_randomness": "96be177390d40df347f7e0c5fceab4724dc41bc595ad822366be90f15f983a7b", + "binding_nonce_randomness": "74e2bf636456d87ce76a7429088998d81ae2faa5c8ce04e9bac5197d893adc06", + "hiding_nonce": "2e60c16b89c17d1cefef5e7f3a8c8f6ae821b57d1e040da647f8280cce739198", + "binding_nonce": "2c07e805df65c86ba4ede8edcc222e2a5d7b1ee3ba4bc11c68ed8d1307de40e6", + "hiding_nonce_commitment": "03893a951d667965ea784f8a715a6b6337e3a434871101502f6dc66066ab37b97c", + "binding_nonce_commitment": "0272dc56a3c0fe1454ea8355c6b6c38d061c166426ff095aab25fecd32af9961a8", + "binding_factor_input": "350c8b523feea9bb35720e9fbe0405ed48d78caa4fb60869f34367e144c68bb02da00e7af1d696811fd38187745717e96978f97e4ec287818a7afede5435b0280000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "38ac5d4ce49ea2a403279278fabf3b3275eef91c86f3b83355b6975777d3acd2" + }, + "3": { + "hiding_nonce_randomness": "65f081e0ae1f83ca6bb792d050bc0ac23728f617341f9532fcbd90ee5b3bd3e5", + "binding_nonce_randomness": "3613b6b5ac6833848c52e2e3c27ba777b4b79160af667670315e87ed5df47364", + "hiding_nonce": "8c56d9e1bbb2792a8761d9c9e108e12957acd3840b69ad3307a24ac5e9618875", + "binding_nonce": "2df2d0828c9a7fe9f338b3bb885c3a6aa87fda81cd75b11c4b1275c741bd68e6", + "hiding_nonce_commitment": "02ef00cb2527c5f363b5d90c15b7dc6b3fd3c38decca715d8feea6c0642c38d366", + "binding_nonce_commitment": "0244f86440396661bd1d9cab34e72543e1cccb6768a7f445099f3d6d8719d84a66", + "binding_factor_input": "350c8b523feea9bb35720e9fbe0405ed48d78caa4fb60869f34367e144c68bb02da00e7af1d696811fd38187745717e96978f97e4ec287818a7afede5435b0280000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "11d9b0267c04e79f44615ed7d1fe7117f3d5b802351ace82f51454443a1efca9" + } + } + }, + "round_two_outputs": { + "participant_list": "1,3", + "participants": { + "1": { + "sig_share": "dd18c4b0e3cc1ed70a8274bafa6b6b65cd46d87e52070078e3d684bbe0946fca" + }, + "3": { + "sig_share": "0644c5aaa8f27b4bf39dda8a9dcbd90dbcbb358c15c165c762997c3dd51a3b09" + } + } + }, + "final_output": { + "sig": "03289283518ddc78ad2d53418245e4f83c1eaeb523e0abd3823655f32997990177e35d8a5b8cbe9a22fe204f45983744738a020e0a67c86640467000f9b5aeaad3" + } +} \ No newline at end of file diff --git a/tss/frost/testdata/frost_ristretto255_sha512.json b/tss/frost/testdata/frost_ristretto255_sha512.json new file mode 100644 index 000000000..979d4ff93 --- /dev/null +++ b/tss/frost/testdata/frost_ristretto255_sha512.json @@ -0,0 +1,68 @@ +{ + "config": { + "MAX_PARTICIPANTS": "3", + "NUM_PARTICIPANTS": "2", + "MIN_PARTICIPANTS": "2", + "name": "FROST(ristretto255, SHA-512)", + "group": "ristretto255", + "hash": "SHA-512" + }, + "inputs": { + "group_secret_key": "1b25a55e463cfd15cf14a5d3acc3d15053f08da49c8afcf3ab265f2ebc4f970b", + "group_public_key": "e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d05083c7254f57", + "message": "74657374", + "share_polynomial_coefficients": [ + "410f8b744b19325891d73736923525a4f596c805d060dfb9c98009d34e3fec02" + ], + "participants": { + "1": { + "participant_share": "5c3430d391552f6e60ecdc093ff9f6f4488756aa6cebdbad75a768010b8f830e" + }, + "2": { + "participant_share": "b06fc5eac20b4f6e1b271d9df2343d843e1e1fb03c4cbb673f2872d459ce6f01" + }, + "3": { + "participant_share": "f17e505f0e2581c6acfe54d3846a622834b5e7b50cad9a2109a97ba7a80d5c04" + } + } + }, + "round_one_outputs": { + "participant_list": "1,3", + "participants": { + "1": { + "hiding_nonce_randomness": "705bef89bc7fb7b9fdd6ccf475643c37638aa2a36b87676c9045d0bce36711a7", + "binding_nonce_randomness": "7fe0a8455ccbcd1fc58598e04ba08759e1aed04e46b79c02d0f7dc259ff42db4", + "hiding_nonce": "c1e56ae9bfceb570c4ef2dd10cdb79fd887b326093da3fa7eba7320caf284a03", + "binding_nonce": "8159b8b194083d315fdbeac3cfbc3c46adcf5963e72b5bb5b1956e228b20030d", + "hiding_nonce_commitment": "ce43378a940f4f576ffaea183ba7815ace0d91a50e2b80b05766072a22c26702", + "binding_nonce_commitment": "805bd85f1eefad8d248cf98a149491c0a6bac3f4c13e9709c547e9b704711a52", + "binding_factor_input": "9c245d5fc2e451c5c5a617cc6f2a20629fb317d9b1c1915ab4bfa319d4ebf922c54dd1a5b3b754550c72734ac9255db8107a2b01f361754d9f13f428c2f6de9ebad59fa100eebaaeb77f179d097b36ade3bd13f02214f4394ead67be3d69bc031b1ca7332b1e5dcef9aae4cc80af30872a9c48ad92ecbee52b4bb76759c4794d0100000000000000000000000000000000000000000000000000000000000000", + "binding_factor": "3f02ce577740f60ea7bf30f6b1c891738e2b810b3754a756b15d94858ea5aa0f" + }, + "3": { + "hiding_nonce_randomness": "812e3907ec1748e5663192ba1b8bcef999534c465914dd8239c3162330d3b0b9", + "binding_nonce_randomness": "a6dc55712d9dd4b3ef5d22aed36169841a6758aea7cb707e7929d8c8cda3e80b", + "hiding_nonce": "c645ac5632f4658bd560f60e7c3aeb40a868267d436037cb2ef35efef6d0a809", + "binding_nonce": "54c933a872de06fe50933596f99e019b2d8aeaec087f9d9459d8583acccec101", + "hiding_nonce_commitment": "088d7e22d0900358043480f60be82f06538798b6fe63c958c308996199c02e19", + "binding_nonce_commitment": "faf53f8c449fafd3d2d37c81fed72b59bed56d090674ebbc629aab14cedc0a2b", + "binding_factor_input": "9c245d5fc2e451c5c5a617cc6f2a20629fb317d9b1c1915ab4bfa319d4ebf922c54dd1a5b3b754550c72734ac9255db8107a2b01f361754d9f13f428c2f6de9ebad59fa100eebaaeb77f179d097b36ade3bd13f02214f4394ead67be3d69bc031b1ca7332b1e5dcef9aae4cc80af30872a9c48ad92ecbee52b4bb76759c4794d0300000000000000000000000000000000000000000000000000000000000000", + "binding_factor": "4d73c0a3a763931becb5bbd2a977cb0c9341166f1693b5c057433ae877ed2008" + } + } + }, + "round_two_outputs": { + "participant_list": "1,3", + "participants": { + "1": { + "sig_share": "735083b8fdd2aee8a892c26ce8985e25bb0666fa8e4234827801fe4cc7be1f0c" + }, + "3": { + "sig_share": "1b44ebcc5807967156035d5e21b6b0dbbf2f23b72dea9d69f4b6576a67e6000d" + } + } + }, + "final_output": { + "sig": "ee4e7905c52e72b8d6acb2f015fd499066fead78c7f696e817212d16c134fd66a1c078283c77320229f927282b5530ec7a3689b1bc2cd2eb6cb855b72ea52009" + } +} \ No newline at end of file diff --git a/tss/frost/vectors_test.go b/tss/frost/vectors_test.go new file mode 100644 index 000000000..62b5f02c6 --- /dev/null +++ b/tss/frost/vectors_test.go @@ -0,0 +1,237 @@ +package frost + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "os" + "testing" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/internal/test" + "github.com/cloudflare/circl/secretsharing" +) + +type vector struct { + Config struct { + MAXPARTICIPANTS uint16 `json:"MAX_PARTICIPANTS,string"` + NUMPARTICIPANTS uint16 `json:"NUM_PARTICIPANTS,string"` + MINPARTICIPANTS uint16 `json:"MIN_PARTICIPANTS,string"` + Name string `json:"name"` + Group string `json:"group"` + Hash string `json:"hash"` + } `json:"config"` + Inputs struct { + GroupSecretKey string `json:"group_secret_key"` + GroupPublicKey string `json:"group_public_key"` + Message string `json:"message"` + SharePolynomialCoefficients []string `json:"share_polynomial_coefficients"` + Participants struct { + Num1 struct { + ParticipantShare string `json:"participant_share"` + } `json:"1"` + Num2 struct { + ParticipantShare string `json:"participant_share"` + } `json:"2"` + Num3 struct { + ParticipantShare string `json:"participant_share"` + } `json:"3"` + } `json:"participants"` + } `json:"inputs"` + RoundOneOutputs struct { + ParticipantList string `json:"participant_list"` + Participants struct { + Num1 struct { + HidingNonceRandomness string `json:"hiding_nonce_randomness"` + BindingNonceRandomness string `json:"binding_nonce_randomness"` + HidingNonce string `json:"hiding_nonce"` + BindingNonce string `json:"binding_nonce"` + HidingNonceCommitment string `json:"hiding_nonce_commitment"` + BindingNonceCommitment string `json:"binding_nonce_commitment"` + BindingFactorInput string `json:"binding_factor_input"` + BindingFactor string `json:"binding_factor"` + } `json:"1"` + Num3 struct { + HidingNonceRandomness string `json:"hiding_nonce_randomness"` + BindingNonceRandomness string `json:"binding_nonce_randomness"` + HidingNonce string `json:"hiding_nonce"` + BindingNonce string `json:"binding_nonce"` + HidingNonceCommitment string `json:"hiding_nonce_commitment"` + BindingNonceCommitment string `json:"binding_nonce_commitment"` + BindingFactorInput string `json:"binding_factor_input"` + BindingFactor string `json:"binding_factor"` + } `json:"3"` + } `json:"participants"` + } `json:"round_one_outputs"` + RoundTwoOutputs struct { + ParticipantList string `json:"participant_list"` + Participants struct { + Num1 struct { + SigShare string `json:"sig_share"` + } `json:"1"` + Num3 struct { + SigShare string `json:"sig_share"` + } `json:"3"` + } `json:"participants"` + } `json:"round_two_outputs"` + FinalOutput struct { + Sig string `json:"sig"` + } `json:"final_output"` +} + +func fromHex(t *testing.T, s, errMsg string) []byte { + t.Helper() + bytes, err := hex.DecodeString(s) + test.CheckNoErr(t, err, "decoding "+errMsg) + + return bytes +} + +func toBytesScalar(t *testing.T, s group.Scalar) []byte { + t.Helper() + bytes, err := s.MarshalBinary() + test.CheckNoErr(t, err, "decoding scalar") + + return bytes +} + +func toBytesElt(t *testing.T, e group.Element) []byte { + t.Helper() + bytes, err := e.MarshalBinaryCompress() + test.CheckNoErr(t, err, "decoding element") + + return bytes +} + +func toScalar(t *testing.T, g group.Group, s, errMsg string) group.Scalar { + t.Helper() + r := g.NewScalar() + rBytes := fromHex(t, s, errMsg) + err := r.UnmarshalBinary(rBytes) + test.CheckNoErr(t, err, errMsg) + + return r +} + +func compareBytes(t *testing.T, got, want []byte) { + t.Helper() + if !bytes.Equal(got, want) { + test.ReportError(t, got, want) + } +} + +func (v *vector) test(t *testing.T, suite Suite) { + privKey := &PrivateKey{suite, toScalar(t, suite.g, v.Inputs.GroupSecretKey, "bad private key"), nil} + pubKeyGroup := privKey.Public() + compareBytes(t, toBytesElt(t, pubKeyGroup.key), fromHex(t, v.Inputs.GroupPublicKey, "bad public key")) + + p1 := PeerSigner{ + Suite: suite, + threshold: v.Config.NUMPARTICIPANTS, + maxSigners: v.Config.MAXPARTICIPANTS, + keyShare: secretsharing.Share{ + ID: suite.g.NewScalar().SetUint64(1), + Value: toScalar(t, suite.g, v.Inputs.Participants.Num1.ParticipantShare, "signer share value"), + }, + myPubKey: nil, + } + + /*p2 := PeerSigner{ + Suite: suite, + threshold: v.Config.NUMPARTICIPANTS, + maxSigners: v.Config.MAXPARTICIPANTS, + keyShare: secretsharing.Share{ + ID: suite.g.NewScalar().SetUint64(2), + Value: toScalar(t, suite.g, v.Inputs.Participants.Num2.ParticipantShare, "signer share value"), + }, + myPubKey: nil, + }*/ + + p3 := PeerSigner{ + Suite: suite, + threshold: v.Config.NUMPARTICIPANTS, + maxSigners: v.Config.MAXPARTICIPANTS, + keyShare: secretsharing.Share{ + ID: suite.g.NewScalar().SetUint64(3), + Value: toScalar(t, suite.g, v.Inputs.Participants.Num3.ParticipantShare, "signer share value"), + }, + myPubKey: nil, + } + + hn1 := toScalar(t, suite.g, v.RoundOneOutputs.Participants.Num1.HidingNonce, "hiding nonce") + bn1 := toScalar(t, suite.g, v.RoundOneOutputs.Participants.Num1.BindingNonce, "binding nonce") + nonce1, commit1, err := p1.commitWithNonce(hn1, bn1) + test.CheckNoErr(t, err, "failed to commit") + + compareBytes(t, toBytesElt(t, commit1.hiding), fromHex(t, v.RoundOneOutputs.Participants.Num1.HidingNonceCommitment, "hiding nonce commit")) + compareBytes(t, toBytesElt(t, commit1.binding), fromHex(t, v.RoundOneOutputs.Participants.Num1.BindingNonceCommitment, "binding nonce commit")) + + hn3 := toScalar(t, suite.g, v.RoundOneOutputs.Participants.Num3.HidingNonce, "hiding nonce") + bn3 := toScalar(t, suite.g, v.RoundOneOutputs.Participants.Num3.BindingNonce, "binding nonce") + nonce3, commit3, err := p3.commitWithNonce(hn3, bn3) + test.CheckNoErr(t, err, "failed to commit") + + compareBytes(t, toBytesElt(t, commit3.hiding), fromHex(t, v.RoundOneOutputs.Participants.Num3.HidingNonceCommitment, "hiding nonce commit")) + compareBytes(t, toBytesElt(t, commit3.binding), fromHex(t, v.RoundOneOutputs.Participants.Num3.BindingNonceCommitment, "binding nonce commit")) + + msg := fromHex(t, v.Inputs.Message, "bad msg") + commits := []*Commitment{commit1, commit3} + bindingFactors, err := suite.getBindingFactors(commits, msg) + test.CheckNoErr(t, err, "failed to get binding factors") + + compareBytes(t, toBytesScalar(t, bindingFactors[0].factor), fromHex(t, v.RoundOneOutputs.Participants.Num1.BindingFactor, "binding factor")) + compareBytes(t, toBytesScalar(t, bindingFactors[1].factor), fromHex(t, v.RoundOneOutputs.Participants.Num3.BindingFactor, "binding factor")) + + signShares1, err := p1.Sign(msg, pubKeyGroup, nonce1, commits) + test.CheckNoErr(t, err, "failed to sign share") + compareBytes(t, toBytesScalar(t, signShares1.s.Value), fromHex(t, v.RoundTwoOutputs.Participants.Num1.SigShare, "sign share")) + + signShares3, err := p3.Sign(msg, pubKeyGroup, nonce3, commits) + test.CheckNoErr(t, err, "failed to sign share") + compareBytes(t, toBytesScalar(t, signShares3.s.Value), fromHex(t, v.RoundTwoOutputs.Participants.Num3.SigShare, "sign share")) + + combiner, err := NewCombiner(suite, uint(v.Config.MINPARTICIPANTS-1), uint(v.Config.MAXPARTICIPANTS)) + test.CheckNoErr(t, err, "failed to create combiner") + + signShares := []*SignShare{signShares1, signShares3} + signature, err := combiner.Sign(msg, commits, signShares) + test.CheckNoErr(t, err, "failed to create signature") + compareBytes(t, signature, fromHex(t, v.FinalOutput.Sig, "signature")) + + valid := Verify(suite, pubKeyGroup, msg, signature) + test.CheckOk(valid == true, "invalid signature", t) +} + +func readFile(t *testing.T, fileName string) *vector { + t.Helper() + jsonFile, err := os.Open(fileName) + if err != nil { + t.Fatalf("File %v can not be opened. Error: %v", fileName, err) + } + defer jsonFile.Close() + input, err := io.ReadAll(jsonFile) + if err != nil { + t.Fatalf("File %v can not be read. Error: %v", fileName, err) + } + + var v vector + err = json.Unmarshal(input, &v) + if err != nil { + t.Fatalf("File %v can not be loaded. Error: %v", fileName, err) + } + + return &v +} + +func TestVectors(t *testing.T) { + // Draft published at https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-frost-11 + // Test vectors at https://github.com/cfrg/draft-irtf-cfrg-frost + // Version supported: v11 + suite, vector := P256, readFile(t, "testdata/frost_p256_sha256.json") + t.Run(fmt.Sprintf("%v", suite), func(tt *testing.T) { vector.test(tt, suite) }) + + suite, vector = Ristretto255, readFile(t, "testdata/frost_ristretto255_sha512.json") + t.Run(fmt.Sprintf("%v", suite), func(tt *testing.T) { vector.test(tt, suite) }) +} diff --git a/tss/tss.go b/tss/tss.go new file mode 100644 index 000000000..f3e650b43 --- /dev/null +++ b/tss/tss.go @@ -0,0 +1,2 @@ +// Package tss provides threshold signature schemes. +package tss From 72293a55a95a54d356f192a6aea3d22d3d6bb98f Mon Sep 17 00:00:00 2001 From: armfazh Date: Fri, 16 Feb 2024 19:09:19 -0800 Subject: [PATCH 2/3] Updating frost be compliant with draft v15. --- tss/frost/combiner.go | 40 ++-- tss/frost/commit.go | 68 +++--- tss/frost/frost.go | 63 ++--- tss/frost/frost_test.go | 93 ++++---- tss/frost/peer.go | 126 ++++++---- tss/frost/suites.go | 57 +++-- tss/frost/testdata/frost_p256_sha256.json | 79 +++--- .../testdata/frost_ristretto255_sha512.json | 79 +++--- tss/frost/vectors_test.go | 225 ++++++++---------- 9 files changed, 439 insertions(+), 391 deletions(-) diff --git a/tss/frost/combiner.go b/tss/frost/combiner.go index 488f5f9a5..a5059751a 100644 --- a/tss/frost/combiner.go +++ b/tss/frost/combiner.go @@ -1,30 +1,27 @@ package frost -import ( - "errors" - "fmt" -) +import "fmt" -type Combiner struct { +type Coordinator struct { Suite threshold uint maxSigners uint } -func NewCombiner(s Suite, threshold, maxSigners uint) (*Combiner, error) { +func NewCoordinator(s Suite, threshold, maxSigners uint) (*Coordinator, error) { if threshold > maxSigners { - return nil, errors.New("frost: invalid parameters") + return nil, fmt.Errorf("frost: invalid parameters") } - return &Combiner{Suite: s, threshold: threshold, maxSigners: maxSigners}, nil + return &Coordinator{Suite: s, threshold: threshold, maxSigners: maxSigners}, nil } -func (c Combiner) CheckSignShares( - signShares []*SignShare, - pubKeySigners []*PublicKey, - coms []*Commitment, - pubKeyGroup *PublicKey, +func (c Coordinator) CheckSignShares( msg []byte, + groupPublicKey PublicKey, + signShares []SignShare, + coms []Commitment, + pubKeySigners []PublicKey, ) bool { if l := len(signShares); !(int(c.threshold) < l && l <= int(c.maxSigners)) { return false @@ -37,7 +34,7 @@ func (c Combiner) CheckSignShares( } for i := range signShares { - if !signShares[i].Verify(c.Suite, pubKeySigners[i], coms[i], coms, pubKeyGroup, msg) { + if !signShares[i].Verify(msg, groupPublicKey, pubKeySigners[i], coms[i], coms) { return false } } @@ -45,17 +42,24 @@ func (c Combiner) CheckSignShares( return true } -func (c Combiner) Sign(msg []byte, coms []*Commitment, signShares []*SignShare) ([]byte, error) { +func (c Coordinator) Aggregate( + msg []byte, + groupPublicKey PublicKey, + signShares []SignShare, + coms []Commitment, +) ([]byte, error) { if l := len(coms); l <= int(c.threshold) { return nil, fmt.Errorf("frost: only %v shares of %v required", l, c.threshold) } - bindingFactors, err := c.Suite.getBindingFactors(coms, msg) + p := c.Suite.getParams() + bindingFactors, err := getBindingFactors(p, msg, groupPublicKey, coms) if err != nil { return nil, err } - groupCom, err := c.Suite.getGroupCommitment(coms, bindingFactors) + g := p.group() + groupCom, err := getGroupCommitment(g, coms, bindingFactors) if err != nil { return nil, err } @@ -65,7 +69,7 @@ func (c Combiner) Sign(msg []byte, coms []*Commitment, signShares []*SignShare) return nil, err } - z := c.Suite.g.NewScalar() + z := g.NewScalar() for i := range signShares { z.Add(z, signShares[i].s.Value) } diff --git a/tss/frost/commit.go b/tss/frost/commit.go index 77ed1b91d..b899f7849 100644 --- a/tss/frost/commit.go +++ b/tss/frost/commit.go @@ -1,40 +1,30 @@ package frost import ( - "errors" "fmt" - "io" "sort" "github.com/cloudflare/circl/group" ) type Nonce struct { - ID group.Scalar - hiding, binding group.Scalar + id group.Scalar + hiding group.Scalar + binding group.Scalar } -func (s Suite) nonceGenerate(rnd io.Reader, secret group.Scalar) (group.Scalar, error) { - randomBytes := make([]byte, 32) - _, err := io.ReadFull(rnd, randomBytes) - if err != nil { - return nil, err - } - secretEnc, err := secret.MarshalBinary() - if err != nil { - return nil, err - } - - return s.hasher.h3(append(randomBytes, secretEnc...)), nil +func nonceGenerate(p params, randomBytes, secretEnc []byte) group.Scalar { + return p.h3(append(append([]byte{}, randomBytes...), secretEnc...)) } type Commitment struct { - ID group.Scalar - hiding, binding group.Element + id group.Scalar + hiding group.Element + binding group.Element } func (c Commitment) MarshalBinary() ([]byte, error) { - id, err := c.ID.MarshalBinary() + id, err := c.id.MarshalBinary() if err != nil { return nil, err } @@ -50,12 +40,11 @@ func (c Commitment) MarshalBinary() ([]byte, error) { return append(append(id, h...), b...), nil } -func encodeCommitments(coms []*Commitment) ([]byte, error) { +func encodeCommitments(coms []Commitment) (out []byte, err error) { sort.SliceStable(coms, func(i, j int) bool { - return coms[i].ID.(fmt.Stringer).String() < coms[j].ID.(fmt.Stringer).String() + return coms[i].id.(fmt.Stringer).String() < coms[j].id.(fmt.Stringer).String() }) - var out []byte for i := range coms { cEnc, err := coms[i].MarshalBinary() if err != nil { @@ -71,43 +60,48 @@ type bindingFactor struct { factor group.Scalar } -func (s Suite) getBindingFactorFromID(bindingFactors []bindingFactor, id group.Scalar) (group.Scalar, error) { +func getBindingFactorFromID(bindingFactors []bindingFactor, id group.Scalar) (group.Scalar, error) { for i := range bindingFactors { if bindingFactors[i].ID.IsEqual(id) { return bindingFactors[i].factor, nil } } - return nil, errors.New("frost: id not found") + return nil, fmt.Errorf("frost: id not found") } -func (s Suite) getBindingFactors(coms []*Commitment, msg []byte) ([]bindingFactor, error) { - msgHash := s.hasher.h4(msg) +func getBindingFactors(p params, msg []byte, groupPublicKey PublicKey, coms []Commitment) ([]bindingFactor, error) { + groupPublicKeyEnc, err := groupPublicKey.key.MarshalBinaryCompress() + if err != nil { + return nil, err + } + + msgHash := p.h4(msg) encodeComs, err := encodeCommitments(coms) if err != nil { return nil, err } - encodeComsHash := s.hasher.h5(encodeComs) - rhoInputPrefix := append(msgHash, encodeComsHash...) + encodeComsHash := p.h5(encodeComs) + rhoInputPrefix := append(append(groupPublicKeyEnc, msgHash...), encodeComsHash...) bindingFactors := make([]bindingFactor, len(coms)) for i := range coms { - id, err := coms[i].ID.MarshalBinary() + id, err := coms[i].id.MarshalBinary() if err != nil { return nil, err } rhoInput := append(append([]byte{}, rhoInputPrefix...), id...) - bf := s.hasher.h1(rhoInput) - bindingFactors[i] = bindingFactor{ID: coms[i].ID, factor: bf} + bf := p.h1(rhoInput) + bindingFactors[i] = bindingFactor{ID: coms[i].id, factor: bf} } return bindingFactors, nil } -func (s Suite) getGroupCommitment(coms []*Commitment, bindingFactors []bindingFactor) (group.Element, error) { - gc := s.g.NewElement() - tmp := s.g.NewElement() +func getGroupCommitment(g group.Group, coms []Commitment, bindingFactors []bindingFactor) (group.Element, error) { + gc := g.NewElement() + tmp := g.NewElement() for i := range coms { - bf, err := s.getBindingFactorFromID(bindingFactors, coms[i].ID) + bf, err := getBindingFactorFromID(bindingFactors, coms[i].id) if err != nil { return nil, err } @@ -119,7 +113,7 @@ func (s Suite) getGroupCommitment(coms []*Commitment, bindingFactors []bindingFa return gc, nil } -func (s Suite) getChallenge(groupCom group.Element, pubKey *PublicKey, msg []byte) (group.Scalar, error) { +func getChallenge(p params, groupCom group.Element, msg []byte, pubKey PublicKey) (group.Scalar, error) { gcEnc, err := groupCom.MarshalBinaryCompress() if err != nil { return nil, err @@ -130,5 +124,5 @@ func (s Suite) getChallenge(groupCom group.Element, pubKey *PublicKey, msg []byt } chInput := append(append(append([]byte{}, gcEnc...), pkEnc...), msg...) - return s.hasher.h2(chInput), nil + return p.h2(chInput), nil } diff --git a/tss/frost/frost.go b/tss/frost/frost.go index d3d99680c..7edd244dd 100644 --- a/tss/frost/frost.go +++ b/tss/frost/frost.go @@ -1,11 +1,10 @@ // Package frost provides the FROST threshold signature scheme for Schnorr signatures. // -// References +// FROST paper: https://eprint.iacr.org/2020/852 // -// FROST paper: https://eprint.iacr.org/2020/852 -// draft-irtf-cfrg-frost: https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost +// draft-irtf-cfrg-frost: https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost // -// Version supported: v11 +// Version supported: v15 package frost import ( @@ -17,8 +16,8 @@ import ( type PrivateKey struct { Suite - key group.Scalar - pubKey *PublicKey + key group.Scalar + publicKey *PublicKey } type PublicKey struct { @@ -26,53 +25,61 @@ type PublicKey struct { key group.Element } -func GenerateKey(s Suite, rnd io.Reader) *PrivateKey { - return &PrivateKey{s, s.g.RandomNonZeroScalar(rnd), nil} +func GenerateKey(s Suite, rnd io.Reader) PrivateKey { + g := s.getParams().group() + return PrivateKey{s, g.RandomNonZeroScalar(rnd), nil} } -func (k *PrivateKey) Public() *PublicKey { - return &PublicKey{k.Suite, k.Suite.g.NewElement().MulGen(k.key)} +func (k *PrivateKey) PublicKey() PublicKey { + if k.publicKey == nil { + g := k.Suite.getParams().group() + k.publicKey = &PublicKey{k.Suite, g.NewElement().MulGen(k.key)} + } + + return *k.publicKey } func (k *PrivateKey) Split(rnd io.Reader, threshold, maxSigners uint) ( - []PeerSigner, secretsharing.SecretCommitment, error, + peers []PeerSigner, groupPublicKey PublicKey, comm secretsharing.SecretCommitment, ) { ss := secretsharing.New(rnd, threshold, k.key) shares := ss.Share(maxSigners) + comm = ss.CommitSecret() + groupPublicKey = PublicKey{k.Suite, comm[0]} - peers := make([]PeerSigner, len(shares)) + peers = make([]PeerSigner, len(shares)) for i := range shares { peers[i] = PeerSigner{ - Suite: k.Suite, - threshold: uint16(threshold), - maxSigners: uint16(maxSigners), - keyShare: secretsharing.Share{ - ID: shares[i].ID, - Value: shares[i].Value, - }, - myPubKey: nil, + Suite: k.Suite, + threshold: uint16(threshold), + maxSigners: uint16(maxSigners), + keyShare: shares[i], + groupPublicKey: groupPublicKey, + myPublicKey: nil, } } - return peers, ss.CommitSecret(), nil + return peers, groupPublicKey, comm } -func Verify(s Suite, pubKey *PublicKey, msg, signature []byte) bool { - params := s.g.Params() +func Verify(msg []byte, pubKey PublicKey, signature []byte) bool { + p := pubKey.Suite.getParams() + g := p.group() + params := g.Params() Ne, Ns := params.CompressedElementLength, params.ScalarLength if len(signature) < int(Ne+Ns) { return false } REnc := signature[:Ne] - R := s.g.NewElement() + R := g.NewElement() err := R.UnmarshalBinary(REnc) if err != nil { return false } zEnc := signature[Ne : Ne+Ns] - z := s.g.NewScalar() + z := g.NewScalar() err = z.UnmarshalBinary(zEnc) if err != nil { return false @@ -84,10 +91,10 @@ func Verify(s Suite, pubKey *PublicKey, msg, signature []byte) bool { } chInput := append(append(append([]byte{}, REnc...), pubKeyEnc...), msg...) - c := s.hasher.h2(chInput) + c := p.h2(chInput) - l := s.g.NewElement().MulGen(z) - r := s.g.NewElement().Mul(pubKey.key, c) + l := g.NewElement().MulGen(z) + r := g.NewElement().Mul(pubKey.key, c) r.Add(r, R) return l.IsEqual(r) diff --git a/tss/frost/frost_test.go b/tss/frost/frost_test.go index 4ca8b7f63..fcb21976e 100644 --- a/tss/frost/frost_test.go +++ b/tss/frost/frost_test.go @@ -19,9 +19,7 @@ func testFrost(tt *testing.T, suite frost.Suite) { t, n := uint(3), uint(5) privKey := frost.GenerateKey(suite, rand.Reader) - pubKeyGroup := privKey.Public() - peers, keyShareCommits, err := privKey.Split(rand.Reader, t, n) - test.CheckNoErr(tt, err, "failed to split secret") + peers, groupPublicKey, keyShareCommits := privKey.Split(rand.Reader, t, n) // every peer can validate its own keyShare. for i := range peers { @@ -32,39 +30,42 @@ func testFrost(tt *testing.T, suite frost.Suite) { // Only k peers try to generate a signature. for k := uint(0); k < n; k++ { // round 1 - nonces := make([]*frost.Nonce, k) - commits := make([]*frost.Commitment, k) - pkSigners := make([]*frost.PublicKey, k) + nonces := make([]frost.Nonce, k) + commits := make([]frost.Commitment, k) + pkSigners := make([]frost.PublicKey, k) for i := range peers[:k] { - nonces[i], commits[i], err = peers[i].Commit(rand.Reader) + nonce, commit, err := peers[i].Commit(rand.Reader) test.CheckNoErr(tt, err, "failed to commit") - pkSigners[i] = peers[i].Public() + pkSigners[i] = peers[i].PublicKey() + nonces[i] = *nonce + commits[i] = *commit } // round 2 msg := []byte("it's cold here") - signShares := make([]*frost.SignShare, k) + signShares := make([]frost.SignShare, k) for i := range peers[:k] { - signShares[i], err = peers[i].Sign(msg, pubKeyGroup, nonces[i], commits) + sigShare, err := peers[i].Sign(msg, groupPublicKey, nonces[i], commits) test.CheckNoErr(tt, err, "failed to create a sign share") + signShares[i] = *sigShare } - // Combiner - combiner, err := frost.NewCombiner(suite, t, n) + // Coordinator + coordinator, err := frost.NewCoordinator(suite, t, n) test.CheckNoErr(tt, err, "failed to create combiner") - valid := combiner.CheckSignShares(signShares, pkSigners, commits, pubKeyGroup, msg) + valid := coordinator.CheckSignShares(msg, groupPublicKey, signShares, commits, pkSigners) if k > t { test.CheckOk(valid == true, "invalid sign shares", tt) } else { test.CheckOk(valid == false, "must be invalid sign shares", tt) } - signature, err := combiner.Sign(msg, commits, signShares) + signature, err := coordinator.Aggregate(msg, groupPublicKey, signShares, commits) if k > t { test.CheckNoErr(tt, err, "failed to produce signature") // anyone can verify - valid := frost.Verify(suite, pubKeyGroup, msg, signature) + valid := frost.Verify(msg, groupPublicKey, signature) test.CheckOk(valid == true, "invalid signature", tt) } else { test.CheckIsErr(tt, err, "should not produce a signature") @@ -81,10 +82,33 @@ func BenchmarkFrost(b *testing.B) { func benchmarkFrost(b *testing.B, suite frost.Suite) { t, n := uint(3), uint(5) - privKey := frost.GenerateKey(suite, rand.Reader) - peers, keyShareCommits, err := privKey.Split(rand.Reader, t, n) - test.CheckNoErr(b, err, "failed to split secret") + peers, groupPublicKey, keyShareCommits := privKey.Split(rand.Reader, t, n) + + msg := []byte("it's cold here") + nonces := make([]frost.Nonce, len(peers)) + commits := make([]frost.Commitment, len(peers)) + pkSigners := make([]frost.PublicKey, len(peers)) + for i := range peers { + nonce, commit, err := peers[i].Commit(rand.Reader) + test.CheckNoErr(b, err, "failed to commit") + pkSigners[i] = peers[i].PublicKey() + nonces[i] = *nonce + commits[i] = *commit + } + + signShares := make([]frost.SignShare, len(peers)) + for i := range peers { + sigShare, err := peers[i].Sign(msg, groupPublicKey, nonces[i], commits) + test.CheckNoErr(b, err, "failed to create a sign share") + signShares[i] = *sigShare + } + coordinator, err := frost.NewCoordinator(suite, t, n) + test.CheckNoErr(b, err, "failed to create combiner") + signature, err := coordinator.Aggregate(msg, groupPublicKey, signShares, commits) + test.CheckNoErr(b, err, "failed to aggregate") + valid := frost.Verify(msg, groupPublicKey, signature) + test.CheckOk(valid, "failed to verify", b) b.Run("SplitKey", func(b *testing.B) { for i := 0; i < b.N; i++ { @@ -92,18 +116,6 @@ func benchmarkFrost(b *testing.B, suite frost.Suite) { } }) - pubKeyGroup := privKey.Public() - msg := []byte("it's cold here") - - nonces := make([]*frost.Nonce, len(peers)) - commits := make([]*frost.Commitment, len(peers)) - pkSigners := make([]*frost.PublicKey, len(peers)) - for i := range peers { - nonces[i], commits[i], err = peers[i].Commit(rand.Reader) - test.CheckNoErr(b, err, "failed to commit") - pkSigners[i] = peers[i].Public() - } - b.Run("CheckKeyShare", func(b *testing.B) { for i := 0; i < b.N; i++ { _ = peers[0].CheckKeyShare(keyShareCommits) @@ -118,36 +130,25 @@ func benchmarkFrost(b *testing.B, suite frost.Suite) { b.Run("SignShare", func(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = peers[0].Sign(msg, pubKeyGroup, nonces[0], commits) + _, _ = peers[0].Sign(msg, groupPublicKey, nonces[0], commits) } }) - signShares := make([]*frost.SignShare, len(peers)) - for i := range peers { - signShares[i], err = peers[i].Sign(msg, pubKeyGroup, nonces[i], commits) - test.CheckNoErr(b, err, "failed to create a sign share") - } - - combiner, err := frost.NewCombiner(suite, t, n) - test.CheckNoErr(b, err, "failed to create combiner") - b.Run("CheckSignShares", func(b *testing.B) { for i := 0; i < b.N; i++ { - _ = combiner.CheckSignShares(signShares, pkSigners, commits, pubKeyGroup, msg) + _ = coordinator.CheckSignShares(msg, groupPublicKey, signShares, commits, pkSigners) } }) - b.Run("Sign", func(b *testing.B) { + b.Run("Aggregate", func(b *testing.B) { for i := 0; i < b.N; i++ { - _, _ = combiner.Sign(msg, commits, signShares) + _, _ = coordinator.Aggregate(msg, groupPublicKey, signShares, commits) } }) b.Run("Verify", func(b *testing.B) { - signature, _ := combiner.Sign(msg, commits, signShares) - b.ResetTimer() for i := 0; i < b.N; i++ { - _ = frost.Verify(suite, pubKeyGroup, msg, signature) + _ = frost.Verify(msg, groupPublicKey, signature) } }) } diff --git a/tss/frost/peer.go b/tss/frost/peer.go index b80c9d4b1..1a18a17bf 100644 --- a/tss/frost/peer.go +++ b/tss/frost/peer.go @@ -1,7 +1,6 @@ package frost import ( - "errors" "fmt" "io" "sort" @@ -13,89 +12,113 @@ import ( type PeerSigner struct { Suite - threshold uint16 - maxSigners uint16 - keyShare secretsharing.Share - myPubKey *PublicKey + threshold uint16 + maxSigners uint16 + keyShare secretsharing.Share + myPublicKey *PublicKey + groupPublicKey PublicKey } -func (p PeerSigner) Commit(rnd io.Reader) (*Nonce, *Commitment, error) { - hidingNonce, err := p.Suite.nonceGenerate(rnd, p.keyShare.Value) +func (p *PeerSigner) Commit(rnd io.Reader) (*Nonce, *Commitment, error) { + var hidingNonceRandomness [32]byte + _, err := io.ReadFull(rnd, hidingNonceRandomness[:]) if err != nil { return nil, nil, err } - bindingNonce, err := p.Suite.nonceGenerate(rnd, p.keyShare.Value) + + var bindingNonceRandomness [32]byte + _, err = io.ReadFull(rnd, bindingNonceRandomness[:]) if err != nil { return nil, nil, err } - return p.commitWithNonce(hidingNonce, bindingNonce) + return p.commitWithRandomness(hidingNonceRandomness[:], bindingNonceRandomness[:]) } -func (p PeerSigner) commitWithNonce(hidingNonce, bindingNonce group.Scalar) (*Nonce, *Commitment, error) { - hidingNonceCom := p.Suite.g.NewElement().MulGen(hidingNonce) - bindingNonceCom := p.Suite.g.NewElement().MulGen(bindingNonce) - return &Nonce{p.keyShare.ID, hidingNonce, bindingNonce}, &Commitment{p.keyShare.ID, hidingNonceCom, bindingNonceCom}, nil +func (p *PeerSigner) commitWithRandomness(hidingNonceRnd, bindingNonceRnd []byte) (*Nonce, *Commitment, error) { + secretEnc, err := p.keyShare.Value.MarshalBinary() + if err != nil { + return nil, nil, err + } + + pp := p.Suite.getParams() + g := pp.group() + hidingNonce := nonceGenerate(pp, hidingNonceRnd, secretEnc) + hidingNonceCom := g.NewElement().MulGen(hidingNonce) + + bindingNonce := nonceGenerate(pp, bindingNonceRnd, secretEnc) + bindingNonceCom := g.NewElement().MulGen(bindingNonce) + + return &Nonce{p.keyShare.ID, hidingNonce, bindingNonce}, + &Commitment{p.keyShare.ID, hidingNonceCom, bindingNonceCom}, + nil } -func (p PeerSigner) CheckKeyShare(c secretsharing.SecretCommitment) bool { +func (p *PeerSigner) CheckKeyShare(c secretsharing.SecretCommitment) bool { return secretsharing.Verify(uint(p.threshold), p.keyShare, c) } -func (p PeerSigner) Public() *PublicKey { - if p.myPubKey == nil { - p.myPubKey = &PublicKey{p.Suite, p.Suite.g.NewElement().MulGen(p.keyShare.Value)} +func (p *PeerSigner) PublicKey() PublicKey { + if p.myPublicKey == nil { + g := p.Suite.getParams().group() + p.myPublicKey = &PublicKey{p.Suite, g.NewElement().MulGen(p.keyShare.Value)} } - return p.myPubKey + + return *p.myPublicKey } -func (p PeerSigner) Sign(msg []byte, pubKey *PublicKey, nonce *Nonce, coms []*Commitment) (*SignShare, error) { - if !p.keyShare.ID.IsEqual(nonce.ID) { - return nil, errors.New("frost: bad id") +func (p *PeerSigner) Sign(msg []byte, pubKey PublicKey, nonce Nonce, coms []Commitment) (*SignShare, error) { + if !p.keyShare.ID.IsEqual(nonce.id) { + return nil, fmt.Errorf("frost: bad id") } - aux, err := p.Suite.common(p.keyShare.ID, msg, pubKey, coms) + + pp := p.Suite.getParams() + aux, err := common(pp, p.keyShare.ID, msg, pubKey, coms) if err != nil { return nil, err } - tmp := p.Suite.g.NewScalar().Mul(nonce.binding, aux.bindingFactor) - signShare := p.Suite.g.NewScalar().Add(nonce.hiding, tmp) + g := pp.group() + tmp := g.NewScalar().Mul(nonce.binding, aux.bindingFactor) + signShare := g.NewScalar().Add(nonce.hiding, tmp) tmp.Mul(aux.lambdaID, p.keyShare.Value) tmp.Mul(tmp, aux.challenge) signShare.Add(signShare, tmp) - return &SignShare{s: secretsharing.Share{ - ID: p.keyShare.ID, - Value: signShare, - }}, nil + return &SignShare{ + Suite: p.Suite, + s: secretsharing.Share{ID: p.keyShare.ID, Value: signShare}, + }, nil } type SignShare struct { + Suite s secretsharing.Share } -func (s *SignShare) Verify( - suite Suite, - pubKeySigner *PublicKey, - comSigner *Commitment, - coms []*Commitment, - pubKeyGroup *PublicKey, +func (s SignShare) Verify( msg []byte, + groupPublicKey PublicKey, + pubKeySigner PublicKey, + comSigner Commitment, + coms []Commitment, ) bool { - if s.s.ID != comSigner.ID || s.s.ID.IsZero() { + if s.s.ID != comSigner.id || s.s.ID.IsZero() { return false } - aux, err := suite.common(s.s.ID, msg, pubKeyGroup, coms) + pp := s.Suite.getParams() + aux, err := common(pp, s.s.ID, msg, groupPublicKey, coms) if err != nil { return false } - comShare := suite.g.NewElement().Mul(coms[aux.idx].binding, aux.bindingFactor) + g := pp.group() + comShare := g.NewElement().Mul(coms[aux.idx].binding, aux.bindingFactor) comShare.Add(comShare, coms[aux.idx].hiding) - l := suite.g.NewElement().MulGen(s.s.Value) - r := suite.g.NewElement().Mul(pubKeySigner.key, suite.g.NewScalar().Mul(aux.challenge, aux.lambdaID)) + l := g.NewElement().MulGen(s.s.Value) + r := g.NewElement().Mul(pubKeySigner.key, g.NewScalar().Mul(aux.challenge, aux.lambdaID)) r.Add(r, comShare) return l.IsEqual(r) @@ -108,48 +131,49 @@ type commonAux struct { bindingFactor group.Scalar } -func (s Suite) common(id group.Scalar, msg []byte, pubKey *PublicKey, coms []*Commitment) (aux *commonAux, err error) { +func common(p params, id group.Scalar, msg []byte, groupPublicKey PublicKey, coms []Commitment) (aux *commonAux, err error) { if !sort.SliceIsSorted(coms, func(i, j int) bool { - return coms[i].ID.(fmt.Stringer).String() < coms[j].ID.(fmt.Stringer).String() + return coms[i].id.(fmt.Stringer).String() < coms[j].id.(fmt.Stringer).String() }, ) { - return nil, errors.New("frost: commitments must be sorted") + return nil, fmt.Errorf("frost: commitments must be sorted") } idx := sort.Search(len(coms), func(j int) bool { - return coms[j].ID.(fmt.Stringer).String() >= id.(fmt.Stringer).String() + return coms[j].id.(fmt.Stringer).String() >= id.(fmt.Stringer).String() }) - if !(idx < len(coms) && coms[idx].ID.IsEqual(id)) { - return nil, errors.New("frost: commitment not present") + if !(idx < len(coms) && coms[idx].id.IsEqual(id)) { + return nil, fmt.Errorf("frost: commitment not present") } - bindingFactors, err := s.getBindingFactors(coms, msg) + bindingFactors, err := getBindingFactors(p, msg, groupPublicKey, coms) if err != nil { return nil, err } - bindingFactor, err := s.getBindingFactorFromID(bindingFactors, id) + bindingFactor, err := getBindingFactorFromID(bindingFactors, id) if err != nil { return nil, err } - groupCom, err := s.getGroupCommitment(coms, bindingFactors) + g := p.group() + groupCom, err := getGroupCommitment(g, coms, bindingFactors) if err != nil { return nil, err } - challenge, err := s.getChallenge(groupCom, pubKey, msg) + challenge, err := getChallenge(p, groupCom, msg, groupPublicKey) if err != nil { return nil, err } peers := make([]group.Scalar, len(coms)) for i := range coms { - peers[i] = coms[i].ID.Copy() + peers[i] = coms[i].id.Copy() } - zero := s.g.NewScalar() + zero := g.NewScalar() lambdaID := polynomial.LagrangeBase(uint(idx), peers, zero) return &commonAux{ diff --git a/tss/frost/suites.go b/tss/frost/suites.go index f4f917179..54b4bb6cc 100644 --- a/tss/frost/suites.go +++ b/tss/frost/suites.go @@ -4,29 +4,53 @@ import ( "crypto" _ "crypto/sha256" // added to link library. _ "crypto/sha512" // added to link library. - "fmt" r255 "github.com/bwesterb/go-ristretto" "github.com/cloudflare/circl/group" ) +type Suite uint8 + +const ( + Ristretto255 Suite = iota + P256 +) + +func (s Suite) String() string { + switch s { + case Ristretto255: + return paramsRis.String() + case P256: + return paramsP256.String() + default: + return "frost: undefined suite" + } +} + var ( - P256 = Suite{group.P256, suiteP{group.P256, suiteCommon{crypto.SHA256, "FROST-P256-SHA256-v11"}}} - Ristretto255 = Suite{group.Ristretto255, suiteRis255{suiteCommon{crypto.SHA512, "FROST-RISTRETTO255-SHA512-v11"}}} + paramsRis = &suiteRis255{suiteCommon{group.Ristretto255, crypto.SHA512, "FROST-RISTRETTO255-SHA512-v1"}} + paramsP256 = &suiteP{suiteCommon{group.P256, crypto.SHA256, "FROST-P256-SHA256-v1"}} ) -type Suite struct { - g group.Group - hasher interface { - h1(m []byte) group.Scalar - h2(m []byte) group.Scalar - h3(m []byte) group.Scalar - h4(m []byte) []byte - h5(m []byte) []byte +func (s Suite) getParams() params { + switch s { + case Ristretto255: + return paramsRis + case P256: + return paramsP256 + default: + panic("frost: undefined suite") } } -func (s Suite) String() string { return s.hasher.(fmt.Stringer).String() } +type params interface { + group() group.Group + h1(m []byte) group.Scalar + h2(m []byte) group.Scalar + h3(m []byte) group.Scalar + h4(m []byte) []byte + h5(m []byte) []byte +} const ( labelRho = "rho" @@ -37,11 +61,13 @@ const ( ) type suiteCommon struct { + g group.Group hash crypto.Hash context string } -func (s suiteCommon) String() string { return s.context[:len(s.context)-4] } +func (s suiteCommon) String() string { return s.context[:len(s.context)-3] } +func (s suiteCommon) group() group.Group { return s.g } func (s suiteCommon) h4(m []byte) []byte { return s.hashLabeled(labelMsg, m) } func (s suiteCommon) h5(m []byte) []byte { return s.hashLabeled(labelCom, m) } func (s suiteCommon) hashLabeled(label string, m []byte) []byte { @@ -51,10 +77,7 @@ func (s suiteCommon) hashLabeled(label string, m []byte) []byte { return H.Sum(nil) } -type suiteP struct { - g group.Group - suiteCommon -} +type suiteP struct{ suiteCommon } func (s suiteP) h1(m []byte) group.Scalar { return s.g.HashToScalar(m, []byte(s.context+labelRho)) } func (s suiteP) h2(m []byte) group.Scalar { return s.g.HashToScalar(m, []byte(s.context+labelChal)) } diff --git a/tss/frost/testdata/frost_p256_sha256.json b/tss/frost/testdata/frost_p256_sha256.json index 5b6106523..5227e78cd 100644 --- a/tss/frost/testdata/frost_p256_sha256.json +++ b/tss/frost/testdata/frost_p256_sha256.json @@ -8,61 +8,70 @@ "hash": "SHA-256" }, "inputs": { + "participant_list": [ + 1, + 3 + ], "group_secret_key": "8ba9bba2e0fd8c4767154d35a0b7562244a4aaf6f36c8fb8735fa48b301bd8de", "group_public_key": "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70", "message": "74657374", "share_polynomial_coefficients": [ "80f25e6c0709353e46bfbe882a11bdbb1f8097e46340eb8673b7e14556e6c3a4" ], - "participants": { - "1": { + "participant_shares": [ + { + "identifier": 1, "participant_share": "0c9c1a0fe806c184add50bbdcac913dda73e482daf95dcb9f35dbb0d8a9f7731" }, - "2": { + { + "identifier": 2, "participant_share": "8d8e787bef0ff6c2f494ca45f4dad198c6bee01212d6c84067159c52e1863ad5" }, - "3": { + { + "identifier": 3, "participant_share": "0e80d6e8f6192c003b5488ce1eec8f5429587d48cf001541e713b2d53c09d928" } - } + ] }, "round_one_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "hiding_nonce_randomness": "96be177390d40df347f7e0c5fceab4724dc41bc595ad822366be90f15f983a7b", - "binding_nonce_randomness": "74e2bf636456d87ce76a7429088998d81ae2faa5c8ce04e9bac5197d893adc06", - "hiding_nonce": "2e60c16b89c17d1cefef5e7f3a8c8f6ae821b57d1e040da647f8280cce739198", - "binding_nonce": "2c07e805df65c86ba4ede8edcc222e2a5d7b1ee3ba4bc11c68ed8d1307de40e6", - "hiding_nonce_commitment": "03893a951d667965ea784f8a715a6b6337e3a434871101502f6dc66066ab37b97c", - "binding_nonce_commitment": "0272dc56a3c0fe1454ea8355c6b6c38d061c166426ff095aab25fecd32af9961a8", - "binding_factor_input": "350c8b523feea9bb35720e9fbe0405ed48d78caa4fb60869f34367e144c68bb02da00e7af1d696811fd38187745717e96978f97e4ec287818a7afede5435b0280000000000000000000000000000000000000000000000000000000000000001", - "binding_factor": "38ac5d4ce49ea2a403279278fabf3b3275eef91c86f3b83355b6975777d3acd2" + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "ec4c891c85fee802a9d757a67d1252e7f4e5efb8a538991ac18fbd0e06fb6fd3", + "binding_nonce_randomness": "9334e29d09061223f69a09421715a347e4e6deba77444c8f42b0c833f80f4ef9", + "hiding_nonce": "9f0542a5ba879a58f255c09f06da7102ef6a2dec6279700c656d58394d8facd4", + "binding_nonce": "6513dfe7429aa2fc972c69bb495b27118c45bbc6e654bb9dc9be55385b55c0d7", + "hiding_nonce_commitment": "0213b3e6298bf8ad46fd5e9389519a8665d63d98f4ec6a1fcca434e809d2d8070e", + "binding_nonce_commitment": "02188ff1390bf69374d7b272e454b1878ef10a6b6ea3ff36f114b300b4dbd5233b", + "binding_factor_input": "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70825371853e974bc30ac5b947b216d70461919666584c70c51f9f56f117736c5d178dd0b521ad9c1abe98048419cbdec81504c85e12eb40e3bcb6ec73d3fc4afd0000000000000000000000000000000000000000000000000000000000000001", + "binding_factor": "7925f0d4693f204e6e59233e92227c7124664a99739d2c06b81cf64ddf90559e" }, - "3": { - "hiding_nonce_randomness": "65f081e0ae1f83ca6bb792d050bc0ac23728f617341f9532fcbd90ee5b3bd3e5", - "binding_nonce_randomness": "3613b6b5ac6833848c52e2e3c27ba777b4b79160af667670315e87ed5df47364", - "hiding_nonce": "8c56d9e1bbb2792a8761d9c9e108e12957acd3840b69ad3307a24ac5e9618875", - "binding_nonce": "2df2d0828c9a7fe9f338b3bb885c3a6aa87fda81cd75b11c4b1275c741bd68e6", - "hiding_nonce_commitment": "02ef00cb2527c5f363b5d90c15b7dc6b3fd3c38decca715d8feea6c0642c38d366", - "binding_nonce_commitment": "0244f86440396661bd1d9cab34e72543e1cccb6768a7f445099f3d6d8719d84a66", - "binding_factor_input": "350c8b523feea9bb35720e9fbe0405ed48d78caa4fb60869f34367e144c68bb02da00e7af1d696811fd38187745717e96978f97e4ec287818a7afede5435b0280000000000000000000000000000000000000000000000000000000000000003", - "binding_factor": "11d9b0267c04e79f44615ed7d1fe7117f3d5b802351ace82f51454443a1efca9" + { + "identifier": 3, + "hiding_nonce_randomness": "c0451c5a0a5480d6c1f860e5db7d655233dca2669fd90ff048454b8ce983367b", + "binding_nonce_randomness": "2ba5f7793ae700e40e78937a82f407dd35e847e33d1e607b5c7eb6ed2a8ed799", + "hiding_nonce": "f73444a8972bcda9e506bbca3d2b1c083c10facdf4bb5d47fef7c2dc1d9f2a0d", + "binding_nonce": "44c6a29075d6e7e4f8b97796205f9e22062e7835141470afe9417fd317c1c303", + "hiding_nonce_commitment": "033ac9a5fe4a8b57316ba1c34e8a6de453033b750e8984924a984eb67a11e73a3f", + "binding_nonce_commitment": "03a7a2480ee16199262e648aea3acab628a53e9b8c1945078f2ddfbdc98b7df369", + "binding_factor_input": "023a309ad94e9fe8a7ba45dfc58f38bf091959d3c99cfbd02b4dc00585ec45ab70825371853e974bc30ac5b947b216d70461919666584c70c51f9f56f117736c5d178dd0b521ad9c1abe98048419cbdec81504c85e12eb40e3bcb6ec73d3fc4afd0000000000000000000000000000000000000000000000000000000000000003", + "binding_factor": "e10d24a8a403723bcb6f9bb4c537f316593683b472f7a89f166630dde11822c4" } - } + ] }, "round_two_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "sig_share": "dd18c4b0e3cc1ed70a8274bafa6b6b65cd46d87e52070078e3d684bbe0946fca" + "outputs": [ + { + "identifier": 1, + "sig_share": "400308eaed7a2ddee02a265abe6a1cfe04d946ee8720768899619cfabe7a3aeb" }, - "3": { - "sig_share": "0644c5aaa8f27b4bf39dda8a9dcbd90dbcbb358c15c165c762997c3dd51a3b09" + { + "identifier": 3, + "sig_share": "561da3c179edbb0502d941bb3e3ace3c37d122aaa46fb54499f15f3a3331de44" } - } + ] }, "final_output": { - "sig": "03289283518ddc78ad2d53418245e4f83c1eaeb523e0abd3823655f32997990177e35d8a5b8cbe9a22fe204f45983744738a020e0a67c86640467000f9b5aeaad3" + "sig": "026d8d434874f87bdb7bc0dfd239b2c00639044f9dcb195e9a04426f70bfa4b70d9620acac6767e8e3e3036815fca4eb3a3caa69992b902bcd3352fc34f1ac192f" } -} \ No newline at end of file +} diff --git a/tss/frost/testdata/frost_ristretto255_sha512.json b/tss/frost/testdata/frost_ristretto255_sha512.json index 979d4ff93..91aedb1c8 100644 --- a/tss/frost/testdata/frost_ristretto255_sha512.json +++ b/tss/frost/testdata/frost_ristretto255_sha512.json @@ -8,61 +8,70 @@ "hash": "SHA-512" }, "inputs": { + "participant_list": [ + 1, + 3 + ], "group_secret_key": "1b25a55e463cfd15cf14a5d3acc3d15053f08da49c8afcf3ab265f2ebc4f970b", "group_public_key": "e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d05083c7254f57", "message": "74657374", "share_polynomial_coefficients": [ "410f8b744b19325891d73736923525a4f596c805d060dfb9c98009d34e3fec02" ], - "participants": { - "1": { + "participant_shares": [ + { + "identifier": 1, "participant_share": "5c3430d391552f6e60ecdc093ff9f6f4488756aa6cebdbad75a768010b8f830e" }, - "2": { + { + "identifier": 2, "participant_share": "b06fc5eac20b4f6e1b271d9df2343d843e1e1fb03c4cbb673f2872d459ce6f01" }, - "3": { + { + "identifier": 3, "participant_share": "f17e505f0e2581c6acfe54d3846a622834b5e7b50cad9a2109a97ba7a80d5c04" } - } + ] }, "round_one_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "hiding_nonce_randomness": "705bef89bc7fb7b9fdd6ccf475643c37638aa2a36b87676c9045d0bce36711a7", - "binding_nonce_randomness": "7fe0a8455ccbcd1fc58598e04ba08759e1aed04e46b79c02d0f7dc259ff42db4", - "hiding_nonce": "c1e56ae9bfceb570c4ef2dd10cdb79fd887b326093da3fa7eba7320caf284a03", - "binding_nonce": "8159b8b194083d315fdbeac3cfbc3c46adcf5963e72b5bb5b1956e228b20030d", - "hiding_nonce_commitment": "ce43378a940f4f576ffaea183ba7815ace0d91a50e2b80b05766072a22c26702", - "binding_nonce_commitment": "805bd85f1eefad8d248cf98a149491c0a6bac3f4c13e9709c547e9b704711a52", - "binding_factor_input": "9c245d5fc2e451c5c5a617cc6f2a20629fb317d9b1c1915ab4bfa319d4ebf922c54dd1a5b3b754550c72734ac9255db8107a2b01f361754d9f13f428c2f6de9ebad59fa100eebaaeb77f179d097b36ade3bd13f02214f4394ead67be3d69bc031b1ca7332b1e5dcef9aae4cc80af30872a9c48ad92ecbee52b4bb76759c4794d0100000000000000000000000000000000000000000000000000000000000000", - "binding_factor": "3f02ce577740f60ea7bf30f6b1c891738e2b810b3754a756b15d94858ea5aa0f" + "outputs": [ + { + "identifier": 1, + "hiding_nonce_randomness": "f595a133b4d95c6e1f79887220c8b275ce6277e7f68a6640e1e7140f9be2fb5c", + "binding_nonce_randomness": "34dd1001360e3513cb37bebfabe7be4a32c5bb91ba19fbd4360d039111f0fbdc", + "hiding_nonce": "214f2cabb86ed71427ea7ad4283b0fae26b6746c801ce824b83ceb2b99278c03", + "binding_nonce": "c9b8f5e16770d15603f744f8694c44e335e8faef00dad182b8d7a34a62552f0c", + "hiding_nonce_commitment": "965def4d0958398391fc06d8c2d72932608b1e6255226de4fb8d972dac15fd57", + "binding_nonce_commitment": "ec5170920660820007ae9e1d363936659ef622f99879898db86e5bf1d5bf2a14", + "binding_factor_input": "e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d05083c7254f572889dde2854e26377a16caf77dfee5f6be8fe5b4c80318da84698a4161021b033911db5ef8205362701bc9ecd983027814abee94f46d094943a2f4b79a6e4d4603e52c435d8344554942a0a472d8ad84320585b8da3ae5b9ce31cd1903f795c1af66de22af1a45f652cd05ee446b1b4091aaccc91e2471cd18a85a659cecd11f0100000000000000000000000000000000000000000000000000000000000000", + "binding_factor": "8967fd70fa06a58e5912603317fa94c77626395a695a0e4e4efc4476662eba0c" }, - "3": { - "hiding_nonce_randomness": "812e3907ec1748e5663192ba1b8bcef999534c465914dd8239c3162330d3b0b9", - "binding_nonce_randomness": "a6dc55712d9dd4b3ef5d22aed36169841a6758aea7cb707e7929d8c8cda3e80b", - "hiding_nonce": "c645ac5632f4658bd560f60e7c3aeb40a868267d436037cb2ef35efef6d0a809", - "binding_nonce": "54c933a872de06fe50933596f99e019b2d8aeaec087f9d9459d8583acccec101", - "hiding_nonce_commitment": "088d7e22d0900358043480f60be82f06538798b6fe63c958c308996199c02e19", - "binding_nonce_commitment": "faf53f8c449fafd3d2d37c81fed72b59bed56d090674ebbc629aab14cedc0a2b", - "binding_factor_input": "9c245d5fc2e451c5c5a617cc6f2a20629fb317d9b1c1915ab4bfa319d4ebf922c54dd1a5b3b754550c72734ac9255db8107a2b01f361754d9f13f428c2f6de9ebad59fa100eebaaeb77f179d097b36ade3bd13f02214f4394ead67be3d69bc031b1ca7332b1e5dcef9aae4cc80af30872a9c48ad92ecbee52b4bb76759c4794d0300000000000000000000000000000000000000000000000000000000000000", - "binding_factor": "4d73c0a3a763931becb5bbd2a977cb0c9341166f1693b5c057433ae877ed2008" + { + "identifier": 3, + "hiding_nonce_randomness": "daa0cf42a32617786d390e0c7edfbf2efbd428037069357b5173ae61d6dd5d5e", + "binding_nonce_randomness": "b4387e72b2e4108ce4168931cc2c7fcce5f345a5297368952c18b5fc8473f050", + "hiding_nonce": "3f7927872b0f9051dd98dd73eb2b91494173bbe0feb65a3e7e58d3e2318fa40f", + "binding_nonce": "ffd79445fb8030f0a3ddd3861aa4b42b618759282bfe24f1f9304c7009728305", + "hiding_nonce_commitment": "480e06e3de182bf83489c45d7441879932fd7b434a26af41455756264fbd5d6e", + "binding_nonce_commitment": "3064746dfd3c1862ef58fc68c706da287dd925066865ceacc816b3a28c7b363b", + "binding_factor_input": "e2a62f39eede11269e3bd5a7d97554f5ca384f9f6d3dd9c3c0d05083c7254f572889dde2854e26377a16caf77dfee5f6be8fe5b4c80318da84698a4161021b033911db5ef8205362701bc9ecd983027814abee94f46d094943a2f4b79a6e4d4603e52c435d8344554942a0a472d8ad84320585b8da3ae5b9ce31cd1903f795c1af66de22af1a45f652cd05ee446b1b4091aaccc91e2471cd18a85a659cecd11f0300000000000000000000000000000000000000000000000000000000000000", + "binding_factor": "f2c1bb7c33a10511158c2f1766a4a5fadf9f86f2a92692ed333128277cc31006" } - } + ] }, "round_two_outputs": { - "participant_list": "1,3", - "participants": { - "1": { - "sig_share": "735083b8fdd2aee8a892c26ce8985e25bb0666fa8e4234827801fe4cc7be1f0c" + "outputs": [ + { + "identifier": 1, + "sig_share": "9285f875923ce7e0c491a592e9ea1865ec1b823ead4854b48c8a46287749ee09" }, - "3": { - "sig_share": "1b44ebcc5807967156035d5e21b6b0dbbf2f23b72dea9d69f4b6576a67e6000d" + { + "identifier": 3, + "sig_share": "7cb211fe0e3d59d25db6e36b3fb32344794139602a7b24f1ae0dc4e26ad7b908" } - } + ] }, "final_output": { - "sig": "ee4e7905c52e72b8d6acb2f015fd499066fead78c7f696e817212d16c134fd66a1c078283c77320229f927282b5530ec7a3689b1bc2cd2eb6cb855b72ea52009" + "sig": "fc45655fbc66bbffad654ea4ce5fdae253a49a64ace25d9adb62010dd9fb25552164141787162e5b4cab915b4aa45d94655dbb9ed7c378a53b980a0be220a802" } -} \ No newline at end of file +} diff --git a/tss/frost/vectors_test.go b/tss/frost/vectors_test.go index 62b5f02c6..6f1072bbb 100644 --- a/tss/frost/vectors_test.go +++ b/tss/frost/vectors_test.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "io" "os" "testing" @@ -16,65 +15,42 @@ import ( type vector struct { Config struct { - MAXPARTICIPANTS uint16 `json:"MAX_PARTICIPANTS,string"` - NUMPARTICIPANTS uint16 `json:"NUM_PARTICIPANTS,string"` - MINPARTICIPANTS uint16 `json:"MIN_PARTICIPANTS,string"` - Name string `json:"name"` - Group string `json:"group"` - Hash string `json:"hash"` + MAXSIGNERS int `json:"MAX_PARTICIPANTS,string"` + NUMSIGNERS int `json:"NUM_PARTICIPANTS,string"` + MINSIGNERS int `json:"MIN_PARTICIPANTS,string"` + Name string `json:"name"` + Group string `json:"group"` + Hash string `json:"hash"` } `json:"config"` Inputs struct { - GroupSecretKey string `json:"group_secret_key"` - GroupPublicKey string `json:"group_public_key"` - Message string `json:"message"` - SharePolynomialCoefficients []string `json:"share_polynomial_coefficients"` - Participants struct { - Num1 struct { - ParticipantShare string `json:"participant_share"` - } `json:"1"` - Num2 struct { - ParticipantShare string `json:"participant_share"` - } `json:"2"` - Num3 struct { - ParticipantShare string `json:"participant_share"` - } `json:"3"` - } `json:"participants"` + GroupSecretKey string `json:"group_secret_key"` + GroupPublicKey string `json:"group_public_key"` + Message string `json:"message"` + PolyCoeffs []string `json:"share_polynomial_coefficients"` + Signers []int `json:"participant_list"` + Shares []struct { + ID int `json:"identifier"` + SignerShare string `json:"participant_share"` + } `json:"participant_shares"` } `json:"inputs"` RoundOneOutputs struct { - ParticipantList string `json:"participant_list"` - Participants struct { - Num1 struct { - HidingNonceRandomness string `json:"hiding_nonce_randomness"` - BindingNonceRandomness string `json:"binding_nonce_randomness"` - HidingNonce string `json:"hiding_nonce"` - BindingNonce string `json:"binding_nonce"` - HidingNonceCommitment string `json:"hiding_nonce_commitment"` - BindingNonceCommitment string `json:"binding_nonce_commitment"` - BindingFactorInput string `json:"binding_factor_input"` - BindingFactor string `json:"binding_factor"` - } `json:"1"` - Num3 struct { - HidingNonceRandomness string `json:"hiding_nonce_randomness"` - BindingNonceRandomness string `json:"binding_nonce_randomness"` - HidingNonce string `json:"hiding_nonce"` - BindingNonce string `json:"binding_nonce"` - HidingNonceCommitment string `json:"hiding_nonce_commitment"` - BindingNonceCommitment string `json:"binding_nonce_commitment"` - BindingFactorInput string `json:"binding_factor_input"` - BindingFactor string `json:"binding_factor"` - } `json:"3"` - } `json:"participants"` + Outputs []struct { + ID int `json:"identifier"` + HidingNonceRnd string `json:"hiding_nonce_randomness"` + BindingNonceRnd string `json:"binding_nonce_randomness"` + HidingNonce string `json:"hiding_nonce"` + BindingNonce string `json:"binding_nonce"` + HidingNonceCommitment string `json:"hiding_nonce_commitment"` + BindingNonceCommitment string `json:"binding_nonce_commitment"` + BindingFactorInput string `json:"binding_factor_input"` + BindingFactor string `json:"binding_factor"` + } `json:"outputs"` } `json:"round_one_outputs"` RoundTwoOutputs struct { - ParticipantList string `json:"participant_list"` - Participants struct { - Num1 struct { - SigShare string `json:"sig_share"` - } `json:"1"` - Num3 struct { - SigShare string `json:"sig_share"` - } `json:"3"` - } `json:"participants"` + Outputs []struct { + ID int `json:"identifier"` + SigShare string `json:"sig_share"` + } `json:"outputs"` } `json:"round_two_outputs"` FinalOutput struct { Sig string `json:"sig"` @@ -118,103 +94,104 @@ func toScalar(t *testing.T, g group.Group, s, errMsg string) group.Scalar { func compareBytes(t *testing.T, got, want []byte) { t.Helper() if !bytes.Equal(got, want) { - test.ReportError(t, got, want) + test.ReportError(t, fmt.Sprintf("%x", got), fmt.Sprintf("%x", want)) } } -func (v *vector) test(t *testing.T, suite Suite) { - privKey := &PrivateKey{suite, toScalar(t, suite.g, v.Inputs.GroupSecretKey, "bad private key"), nil} - pubKeyGroup := privKey.Public() - compareBytes(t, toBytesElt(t, pubKeyGroup.key), fromHex(t, v.Inputs.GroupPublicKey, "bad public key")) - - p1 := PeerSigner{ - Suite: suite, - threshold: v.Config.NUMPARTICIPANTS, - maxSigners: v.Config.MAXPARTICIPANTS, - keyShare: secretsharing.Share{ - ID: suite.g.NewScalar().SetUint64(1), - Value: toScalar(t, suite.g, v.Inputs.Participants.Num1.ParticipantShare, "signer share value"), - }, - myPubKey: nil, +func (v *vector) test(t *testing.T, s Suite) { + Threshold := v.Config.MINSIGNERS - 1 + NumPeers := v.Config.NUMSIGNERS + MaxPeers := v.Config.MAXSIGNERS + + test.CheckOk(MaxPeers == len(v.Inputs.Shares), "bad number of shares", t) + test.CheckOk(NumPeers == len(v.Inputs.Signers), "bad number of signers", t) + test.CheckOk(NumPeers == len(v.RoundOneOutputs.Outputs), "bad number of outputs round one", t) + test.CheckOk(NumPeers == len(v.RoundTwoOutputs.Outputs), "bad number of outputs round two", t) + + params := s.getParams() + g := params.group() + privKey := PrivateKey{s, toScalar(t, g, v.Inputs.GroupSecretKey, "bad private key"), nil} + groupPublicKey := privKey.PublicKey() + compareBytes(t, toBytesElt(t, groupPublicKey.key), fromHex(t, v.Inputs.GroupPublicKey, "bad public key")) + + peers := make(map[int]PeerSigner) + for _, inputs := range v.Inputs.Shares { + keyShare := secretsharing.Share{ + ID: g.NewScalar().SetUint64(uint64(inputs.ID)), + Value: toScalar(t, g, inputs.SignerShare, "peer share"), + } + peers[inputs.ID] = PeerSigner{ + Suite: s, + threshold: uint16(Threshold), + maxSigners: uint16(MaxPeers), + keyShare: keyShare, + groupPublicKey: groupPublicKey, + myPublicKey: nil, + } } - /*p2 := PeerSigner{ - Suite: suite, - threshold: v.Config.NUMPARTICIPANTS, - maxSigners: v.Config.MAXPARTICIPANTS, - keyShare: secretsharing.Share{ - ID: suite.g.NewScalar().SetUint64(2), - Value: toScalar(t, suite.g, v.Inputs.Participants.Num2.ParticipantShare, "signer share value"), - }, - myPubKey: nil, - }*/ - - p3 := PeerSigner{ - Suite: suite, - threshold: v.Config.NUMPARTICIPANTS, - maxSigners: v.Config.MAXPARTICIPANTS, - keyShare: secretsharing.Share{ - ID: suite.g.NewScalar().SetUint64(3), - Value: toScalar(t, suite.g, v.Inputs.Participants.Num3.ParticipantShare, "signer share value"), - }, - myPubKey: nil, - } + var commitList []Commitment + var pkSigners []PublicKey + nonces := make(map[int]Nonce) - hn1 := toScalar(t, suite.g, v.RoundOneOutputs.Participants.Num1.HidingNonce, "hiding nonce") - bn1 := toScalar(t, suite.g, v.RoundOneOutputs.Participants.Num1.BindingNonce, "binding nonce") - nonce1, commit1, err := p1.commitWithNonce(hn1, bn1) - test.CheckNoErr(t, err, "failed to commit") + for _, roundOne := range v.RoundOneOutputs.Outputs { + peer := peers[roundOne.ID] + hnr := fromHex(t, roundOne.HidingNonceRnd, "hiding nonce rand") + bnr := fromHex(t, roundOne.BindingNonceRnd, "binding nonce rand") - compareBytes(t, toBytesElt(t, commit1.hiding), fromHex(t, v.RoundOneOutputs.Participants.Num1.HidingNonceCommitment, "hiding nonce commit")) - compareBytes(t, toBytesElt(t, commit1.binding), fromHex(t, v.RoundOneOutputs.Participants.Num1.BindingNonceCommitment, "binding nonce commit")) + nonce, commit, err := peer.commitWithRandomness(hnr, bnr) + test.CheckNoErr(t, err, "failed to commit") - hn3 := toScalar(t, suite.g, v.RoundOneOutputs.Participants.Num3.HidingNonce, "hiding nonce") - bn3 := toScalar(t, suite.g, v.RoundOneOutputs.Participants.Num3.BindingNonce, "binding nonce") - nonce3, commit3, err := p3.commitWithNonce(hn3, bn3) - test.CheckNoErr(t, err, "failed to commit") + compareBytes(t, toBytesScalar(t, nonce.hiding), fromHex(t, roundOne.HidingNonce, "hiding nonce")) + compareBytes(t, toBytesScalar(t, nonce.binding), fromHex(t, roundOne.BindingNonce, "binding nonce")) + compareBytes(t, toBytesElt(t, commit.hiding), fromHex(t, roundOne.HidingNonceCommitment, "hiding nonce commit")) + compareBytes(t, toBytesElt(t, commit.binding), fromHex(t, roundOne.BindingNonceCommitment, "binding nonce commit")) - compareBytes(t, toBytesElt(t, commit3.hiding), fromHex(t, v.RoundOneOutputs.Participants.Num3.HidingNonceCommitment, "hiding nonce commit")) - compareBytes(t, toBytesElt(t, commit3.binding), fromHex(t, v.RoundOneOutputs.Participants.Num3.BindingNonceCommitment, "binding nonce commit")) + nonces[roundOne.ID] = *nonce + commitList = append(commitList, *commit) + pkSigners = append(pkSigners, peer.PublicKey()) + } msg := fromHex(t, v.Inputs.Message, "bad msg") - commits := []*Commitment{commit1, commit3} - bindingFactors, err := suite.getBindingFactors(commits, msg) + bindingFactors, err := getBindingFactors(params, msg, groupPublicKey, commitList) test.CheckNoErr(t, err, "failed to get binding factors") - compareBytes(t, toBytesScalar(t, bindingFactors[0].factor), fromHex(t, v.RoundOneOutputs.Participants.Num1.BindingFactor, "binding factor")) - compareBytes(t, toBytesScalar(t, bindingFactors[1].factor), fromHex(t, v.RoundOneOutputs.Participants.Num3.BindingFactor, "binding factor")) + for i := range bindingFactors { + compareBytes(t, toBytesScalar(t, bindingFactors[i].factor), fromHex(t, v.RoundOneOutputs.Outputs[i].BindingFactor, "binding factor")) + } + + var signShareList []SignShare + for _, roundTwo := range v.RoundTwoOutputs.Outputs { + peer := peers[roundTwo.ID] + signShare, errr := peer.Sign(msg, groupPublicKey, nonces[roundTwo.ID], commitList) + test.CheckNoErr(t, errr, "failed to sign share") - signShares1, err := p1.Sign(msg, pubKeyGroup, nonce1, commits) - test.CheckNoErr(t, err, "failed to sign share") - compareBytes(t, toBytesScalar(t, signShares1.s.Value), fromHex(t, v.RoundTwoOutputs.Participants.Num1.SigShare, "sign share")) + compareBytes(t, toBytesScalar(t, signShare.s.ID), toBytesScalar(t, g.NewScalar().SetUint64(uint64(roundTwo.ID)))) + compareBytes(t, toBytesScalar(t, signShare.s.Value), fromHex(t, roundTwo.SigShare, "sign share")) - signShares3, err := p3.Sign(msg, pubKeyGroup, nonce3, commits) - test.CheckNoErr(t, err, "failed to sign share") - compareBytes(t, toBytesScalar(t, signShares3.s.Value), fromHex(t, v.RoundTwoOutputs.Participants.Num3.SigShare, "sign share")) + signShareList = append(signShareList, *signShare) + } - combiner, err := NewCombiner(suite, uint(v.Config.MINPARTICIPANTS-1), uint(v.Config.MAXPARTICIPANTS)) + coordinator, err := NewCoordinator(s, uint(Threshold), uint(MaxPeers)) test.CheckNoErr(t, err, "failed to create combiner") - signShares := []*SignShare{signShares1, signShares3} - signature, err := combiner.Sign(msg, commits, signShares) + ok := coordinator.CheckSignShares(msg, groupPublicKey, signShareList, commitList, pkSigners) + test.CheckOk(ok == true, "invalid signature shares", t) + + signature, err := coordinator.Aggregate(msg, groupPublicKey, signShareList, commitList) test.CheckNoErr(t, err, "failed to create signature") compareBytes(t, signature, fromHex(t, v.FinalOutput.Sig, "signature")) - valid := Verify(suite, pubKeyGroup, msg, signature) + valid := Verify(msg, groupPublicKey, signature) test.CheckOk(valid == true, "invalid signature", t) } func readFile(t *testing.T, fileName string) *vector { t.Helper() - jsonFile, err := os.Open(fileName) + input, err := os.ReadFile(fileName) if err != nil { t.Fatalf("File %v can not be opened. Error: %v", fileName, err) } - defer jsonFile.Close() - input, err := io.ReadAll(jsonFile) - if err != nil { - t.Fatalf("File %v can not be read. Error: %v", fileName, err) - } var v vector err = json.Unmarshal(input, &v) @@ -226,9 +203,9 @@ func readFile(t *testing.T, fileName string) *vector { } func TestVectors(t *testing.T) { - // Draft published at https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-frost-11 + // Draft published at https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-frost-15 // Test vectors at https://github.com/cfrg/draft-irtf-cfrg-frost - // Version supported: v11 + // Version supported: v15 suite, vector := P256, readFile(t, "testdata/frost_p256_sha256.json") t.Run(fmt.Sprintf("%v", suite), func(tt *testing.T) { vector.test(tt, suite) }) From bc52b060eef3df3288b49bc0237ab4ddab422ee6 Mon Sep 17 00:00:00 2001 From: armfazh Date: Mon, 8 Jul 2024 12:20:11 -0700 Subject: [PATCH 3/3] Updating documentation to point to RFC9591. --- tss/frost/frost.go | 5 +---- ...ost_p256_sha256.json => rfc9591_frost_p256_sha256.json} | 2 +- ..._sha512.json => rfc9591_frost_ristretto255_sha512.json} | 2 +- tss/frost/vectors_test.go | 7 +++---- 4 files changed, 6 insertions(+), 10 deletions(-) rename tss/frost/testdata/{frost_p256_sha256.json => rfc9591_frost_p256_sha256.json} (99%) rename tss/frost/testdata/{frost_ristretto255_sha512.json => rfc9591_frost_ristretto255_sha512.json} (99%) diff --git a/tss/frost/frost.go b/tss/frost/frost.go index 7edd244dd..1c701225b 100644 --- a/tss/frost/frost.go +++ b/tss/frost/frost.go @@ -1,10 +1,7 @@ // Package frost provides the FROST threshold signature scheme for Schnorr signatures. // // FROST paper: https://eprint.iacr.org/2020/852 -// -// draft-irtf-cfrg-frost: https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost -// -// Version supported: v15 +// RFC 9519: https://www.rfc-editor.org/rfc/rfc9591 package frost import ( diff --git a/tss/frost/testdata/frost_p256_sha256.json b/tss/frost/testdata/rfc9591_frost_p256_sha256.json similarity index 99% rename from tss/frost/testdata/frost_p256_sha256.json rename to tss/frost/testdata/rfc9591_frost_p256_sha256.json index 5227e78cd..fdad9410d 100644 --- a/tss/frost/testdata/frost_p256_sha256.json +++ b/tss/frost/testdata/rfc9591_frost_p256_sha256.json @@ -74,4 +74,4 @@ "final_output": { "sig": "026d8d434874f87bdb7bc0dfd239b2c00639044f9dcb195e9a04426f70bfa4b70d9620acac6767e8e3e3036815fca4eb3a3caa69992b902bcd3352fc34f1ac192f" } -} +} \ No newline at end of file diff --git a/tss/frost/testdata/frost_ristretto255_sha512.json b/tss/frost/testdata/rfc9591_frost_ristretto255_sha512.json similarity index 99% rename from tss/frost/testdata/frost_ristretto255_sha512.json rename to tss/frost/testdata/rfc9591_frost_ristretto255_sha512.json index 91aedb1c8..5e39a553d 100644 --- a/tss/frost/testdata/frost_ristretto255_sha512.json +++ b/tss/frost/testdata/rfc9591_frost_ristretto255_sha512.json @@ -74,4 +74,4 @@ "final_output": { "sig": "fc45655fbc66bbffad654ea4ce5fdae253a49a64ace25d9adb62010dd9fb25552164141787162e5b4cab915b4aa45d94655dbb9ed7c378a53b980a0be220a802" } -} +} \ No newline at end of file diff --git a/tss/frost/vectors_test.go b/tss/frost/vectors_test.go index 6f1072bbb..ff3648b1a 100644 --- a/tss/frost/vectors_test.go +++ b/tss/frost/vectors_test.go @@ -203,12 +203,11 @@ func readFile(t *testing.T, fileName string) *vector { } func TestVectors(t *testing.T) { - // Draft published at https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-frost-15 + // RFC 9519: https://www.rfc-editor.org/rfc/rfc9591 // Test vectors at https://github.com/cfrg/draft-irtf-cfrg-frost - // Version supported: v15 - suite, vector := P256, readFile(t, "testdata/frost_p256_sha256.json") + suite, vector := P256, readFile(t, "testdata/rfc9591_frost_p256_sha256.json") t.Run(fmt.Sprintf("%v", suite), func(tt *testing.T) { vector.test(tt, suite) }) - suite, vector = Ristretto255, readFile(t, "testdata/frost_ristretto255_sha512.json") + suite, vector = Ristretto255, readFile(t, "testdata/rfc9591_frost_ristretto255_sha512.json") t.Run(fmt.Sprintf("%v", suite), func(tt *testing.T) { vector.test(tt, suite) }) }