Skip to content

Commit 3c95742

Browse files
committed
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. 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
1 parent 44925f7 commit 3c95742

11 files changed

+1034
-0
lines changed

tss/frost/combiner.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package frost
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
type Combiner struct {
9+
Suite
10+
threshold uint
11+
maxSigners uint
12+
}
13+
14+
func NewCombiner(s Suite, threshold, maxSigners uint) (*Combiner, error) {
15+
if threshold > maxSigners {
16+
return nil, errors.New("frost: invalid parameters")
17+
}
18+
19+
return &Combiner{Suite: s, threshold: threshold, maxSigners: maxSigners}, nil
20+
}
21+
22+
func (c Combiner) CheckSignShares(
23+
signShares []*SignShare,
24+
pubKeySigners []*PublicKey,
25+
coms []*Commitment,
26+
pubKeyGroup *PublicKey,
27+
msg []byte,
28+
) bool {
29+
if l := len(signShares); !(int(c.threshold) < l && l <= int(c.maxSigners)) {
30+
return false
31+
}
32+
if l := len(pubKeySigners); !(int(c.threshold) < l && l <= int(c.maxSigners)) {
33+
return false
34+
}
35+
if l := len(coms); !(int(c.threshold) < l && l <= int(c.maxSigners)) {
36+
return false
37+
}
38+
39+
for i := range signShares {
40+
if !signShares[i].Verify(c.Suite, pubKeySigners[i], coms[i], coms, pubKeyGroup, msg) {
41+
return false
42+
}
43+
}
44+
45+
return true
46+
}
47+
48+
func (c Combiner) Sign(msg []byte, coms []*Commitment, signShares []*SignShare) ([]byte, error) {
49+
if l := len(coms); l <= int(c.threshold) {
50+
return nil, fmt.Errorf("frost: only %v shares of %v required", l, c.threshold)
51+
}
52+
53+
bindingFactors, err := c.Suite.getBindingFactors(coms, msg)
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
groupCom, err := c.Suite.getGroupCommitment(coms, bindingFactors)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
gcEnc, err := groupCom.MarshalBinaryCompress()
64+
if err != nil {
65+
return nil, err
66+
}
67+
68+
z := c.Suite.g.NewScalar()
69+
for i := range signShares {
70+
z.Add(z, signShares[i].share)
71+
}
72+
73+
zEnc, err := z.MarshalBinary()
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
return append(append([]byte{}, gcEnc...), zEnc...), nil
79+
}

tss/frost/commit.go

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package frost
2+
3+
import (
4+
"encoding/binary"
5+
"errors"
6+
"io"
7+
"sort"
8+
9+
"github.com/cloudflare/circl/group"
10+
)
11+
12+
type Nonce struct {
13+
ID uint16
14+
hiding, binding group.Scalar
15+
}
16+
17+
func (s Suite) nonceGenerate(rnd io.Reader, secret group.Scalar) (group.Scalar, error) {
18+
k := make([]byte, 32)
19+
_, err := io.ReadFull(rnd, k)
20+
if err != nil {
21+
return nil, err
22+
}
23+
secretEnc, err := secret.MarshalBinary()
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
return s.hasher.h4(append(append([]byte{}, k...), secretEnc...)), nil
29+
}
30+
31+
type Commitment struct {
32+
ID uint16
33+
hiding, binding group.Element
34+
}
35+
36+
func (c Commitment) MarshalBinary() ([]byte, error) {
37+
bytes := (&[2]byte{})[:]
38+
binary.BigEndian.PutUint16(bytes, c.ID)
39+
40+
h, err := c.hiding.MarshalBinaryCompress()
41+
if err != nil {
42+
return nil, err
43+
}
44+
b, err := c.binding.MarshalBinaryCompress()
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
return append(append(bytes, h...), b...), nil
50+
}
51+
52+
func encodeComs(coms []*Commitment) ([]byte, error) {
53+
sort.SliceStable(coms, func(i, j int) bool { return coms[i].ID < coms[j].ID })
54+
55+
var out []byte
56+
for i := range coms {
57+
cEnc, err := coms[i].MarshalBinary()
58+
if err != nil {
59+
return nil, err
60+
}
61+
out = append(out, cEnc...)
62+
}
63+
return out, nil
64+
}
65+
66+
type bindingFactor struct {
67+
ID uint16
68+
factor group.Scalar
69+
}
70+
71+
func (s Suite) getBindingFactorFromID(bindingFactors []bindingFactor, id uint) (group.Scalar, error) {
72+
for i := range bindingFactors {
73+
if uint(bindingFactors[i].ID) == id {
74+
return bindingFactors[i].factor, nil
75+
}
76+
}
77+
return nil, errors.New("frost: id not found")
78+
}
79+
80+
func (s Suite) getBindingFactors(coms []*Commitment, msg []byte) ([]bindingFactor, error) {
81+
msgHash := s.hasher.h3(msg)
82+
commitEncoded, err := encodeComs(coms)
83+
if err != nil {
84+
return nil, err
85+
}
86+
commitEncodedHash := s.hasher.h3(commitEncoded)
87+
rhoInputPrefix := append(append([]byte{}, msgHash...), commitEncodedHash...)
88+
89+
bindingFactors := make([]bindingFactor, len(coms))
90+
id := (&[2]byte{})[:]
91+
for i := range coms {
92+
binary.BigEndian.PutUint16(id, coms[i].ID)
93+
bf := s.hasher.h1(append(append([]byte{}, rhoInputPrefix...), id...))
94+
bindingFactors[i] = bindingFactor{ID: coms[i].ID, factor: bf}
95+
}
96+
97+
return bindingFactors, nil
98+
}
99+
100+
func (s Suite) getGroupCommitment(coms []*Commitment, bindingFactors []bindingFactor) (group.Element, error) {
101+
gc := s.g.NewElement()
102+
tmp := s.g.NewElement()
103+
for i := range coms {
104+
bf, err := s.getBindingFactorFromID(bindingFactors, uint(coms[i].ID))
105+
if err != nil {
106+
return nil, err
107+
}
108+
tmp.Mul(coms[i].binding, bf)
109+
tmp.Add(tmp, coms[i].hiding)
110+
gc.Add(gc, tmp)
111+
}
112+
113+
return gc, nil
114+
}
115+
116+
func (s Suite) getChallenge(groupCom group.Element, pubKey *PublicKey, msg []byte) (group.Scalar, error) {
117+
gcEnc, err := groupCom.MarshalBinaryCompress()
118+
if err != nil {
119+
return nil, err
120+
}
121+
pkEnc, err := pubKey.key.MarshalBinaryCompress()
122+
if err != nil {
123+
return nil, err
124+
}
125+
chInput := append(append(append([]byte{}, gcEnc...), pkEnc...), msg...)
126+
127+
return s.hasher.h2(chInput), nil
128+
}

tss/frost/dealer.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package frost
2+
3+
import (
4+
"errors"
5+
"io"
6+
7+
"github.com/cloudflare/circl/group"
8+
"github.com/cloudflare/circl/group/secretsharing"
9+
)
10+
11+
type Dealer struct {
12+
Suite
13+
threshold uint
14+
maxSigners uint
15+
vss *secretsharing.FeldmanSS
16+
}
17+
18+
func NewDealer(s Suite, threshold, maxSigners uint) (*Dealer, error) {
19+
if threshold > maxSigners {
20+
return nil, errors.New("frost: invalid parameters")
21+
}
22+
23+
vss, err := secretsharing.NewVerifiable(s.g, threshold, maxSigners)
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
return &Dealer{Suite: s, threshold: threshold, maxSigners: maxSigners, vss: vss}, nil
29+
}
30+
31+
type KeyShareCommitment = group.Element
32+
33+
func (d Dealer) Deal(rnd io.Reader, privKey *PrivateKey) ([]PeerSigner, []KeyShareCommitment) {
34+
shares, coms := d.vss.ShardSecret(rnd, privKey.key)
35+
36+
peers := make([]PeerSigner, d.maxSigners)
37+
for i := range shares {
38+
peers[i] = PeerSigner{
39+
Suite: d.Suite,
40+
ID: uint16(shares[i].ID),
41+
keyShare: shares[i].Share,
42+
myPubKey: nil,
43+
}
44+
}
45+
46+
shareComs := make([]KeyShareCommitment, d.threshold+1)
47+
copy(shareComs, coms)
48+
49+
return peers, shareComs
50+
}

tss/frost/frost.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Package frost provides the frost threshold signature scheme for Schnorr signatures.
2+
//
3+
// References:
4+
// frost paper: https://eprint.iacr.org/2020/852
5+
// draft-irtf-cfrg-frost: https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost
6+
package frost
7+
8+
import (
9+
"io"
10+
11+
"github.com/cloudflare/circl/group"
12+
)
13+
14+
type PrivateKey struct {
15+
Suite
16+
key group.Scalar
17+
pubKey *PublicKey
18+
}
19+
20+
type PublicKey struct {
21+
Suite
22+
key group.Element
23+
}
24+
25+
func GenerateKey(s Suite, rnd io.Reader) *PrivateKey {
26+
return &PrivateKey{s, s.g.RandomNonZeroScalar(rnd), nil}
27+
}
28+
29+
func (k *PrivateKey) Public() *PublicKey {
30+
return &PublicKey{k.Suite, k.Suite.g.NewElement().MulGen(k.key)}
31+
}
32+
33+
func Verify(s Suite, pubKey *PublicKey, msg, signature []byte) bool {
34+
params := s.g.Params()
35+
Ne, Ns := params.CompressedElementLength, params.ScalarLength
36+
if len(signature) < int(Ne+Ns) {
37+
return false
38+
}
39+
40+
REnc := signature[:Ne]
41+
R := s.g.NewElement()
42+
err := R.UnmarshalBinary(REnc)
43+
if err != nil {
44+
return false
45+
}
46+
47+
zEnc := signature[Ne : Ne+Ns]
48+
z := s.g.NewScalar()
49+
err = z.UnmarshalBinary(zEnc)
50+
if err != nil {
51+
return false
52+
}
53+
54+
pubKeyEnc, err := pubKey.key.MarshalBinaryCompress()
55+
if err != nil {
56+
return false
57+
}
58+
59+
chInput := append(append(append([]byte{}, REnc...), pubKeyEnc...), msg...)
60+
c := s.hasher.h2(chInput)
61+
62+
l := s.g.NewElement().MulGen(z)
63+
r := s.g.NewElement().Mul(pubKey.key, c)
64+
r.Add(r, R)
65+
66+
return l.IsEqual(r)
67+
}

0 commit comments

Comments
 (0)