Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tss: new tss package and tss/frost threshold signature scheme #349

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
83 changes: 83 additions & 0 deletions tss/frost/combiner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package frost

import "fmt"

type Coordinator struct {
Suite
threshold uint
maxSigners uint
}

func NewCoordinator(s Suite, threshold, maxSigners uint) (*Coordinator, error) {
if threshold > maxSigners {
return nil, fmt.Errorf("frost: invalid parameters")
}

return &Coordinator{Suite: s, threshold: threshold, maxSigners: maxSigners}, nil
}

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
}
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(msg, groupPublicKey, pubKeySigners[i], coms[i], coms) {
return false
}
}

return true
}

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)
}

p := c.Suite.getParams()
bindingFactors, err := getBindingFactors(p, msg, groupPublicKey, coms)
if err != nil {
return nil, err
}

g := p.group()
groupCom, err := getGroupCommitment(g, coms, bindingFactors)
if err != nil {
return nil, err
}

gcEnc, err := groupCom.MarshalBinaryCompress()
if err != nil {
return nil, err
}

z := 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
}
128 changes: 128 additions & 0 deletions tss/frost/commit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package frost

import (
"fmt"
"sort"

"github.com/cloudflare/circl/group"
)

type Nonce struct {
id group.Scalar
hiding group.Scalar
binding group.Scalar
}

func nonceGenerate(p params, randomBytes, secretEnc []byte) group.Scalar {
return p.h3(append(append([]byte{}, randomBytes...), secretEnc...))
}

type Commitment struct {
id group.Scalar
hiding group.Element
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) (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()
})

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 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, fmt.Errorf("frost: id not found")
}

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 := p.h5(encodeComs)
rhoInputPrefix := append(append(groupPublicKeyEnc, 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 := p.h1(rhoInput)
bindingFactors[i] = bindingFactor{ID: coms[i].id, factor: bf}
}

return bindingFactors, nil
}

func getGroupCommitment(g group.Group, coms []Commitment, bindingFactors []bindingFactor) (group.Element, error) {
gc := g.NewElement()
tmp := g.NewElement()
for i := range coms {
bf, err := 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 getChallenge(p params, groupCom group.Element, msg []byte, pubKey PublicKey) (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 p.h2(chInput), nil
}
101 changes: 101 additions & 0 deletions tss/frost/frost.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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
package frost

import (
"io"

"github.com/cloudflare/circl/group"
"github.com/cloudflare/circl/secretsharing"
)

type PrivateKey struct {
Suite
key group.Scalar
publicKey *PublicKey
}

type PublicKey struct {
Suite
key group.Element
}

func GenerateKey(s Suite, rnd io.Reader) PrivateKey {
g := s.getParams().group()
return PrivateKey{s, g.RandomNonZeroScalar(rnd), nil}
}

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) (
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))
for i := range shares {
peers[i] = PeerSigner{
Suite: k.Suite,
threshold: uint16(threshold),
maxSigners: uint16(maxSigners),
keyShare: shares[i],
groupPublicKey: groupPublicKey,
myPublicKey: nil,
}
}

return peers, groupPublicKey, comm
}

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 := g.NewElement()
err := R.UnmarshalBinary(REnc)
if err != nil {
return false
}

zEnc := signature[Ne : Ne+Ns]
z := 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 := p.h2(chInput)

l := g.NewElement().MulGen(z)
r := g.NewElement().Mul(pubKey.key, c)
r.Add(r, R)

return l.IsEqual(r)
}