Skip to content

Commit

Permalink
FROST: Threshold Signature Scheme
Browse files Browse the repository at this point in the history
Base implementation by @armfazh taken from this PR:
cloudflare#349
  • Loading branch information
bcessa committed Jun 12, 2023
1 parent d18cc37 commit 5c22ce5
Show file tree
Hide file tree
Showing 14 changed files with 1,105 additions and 22 deletions.
3 changes: 3 additions & 0 deletions Makefile
Expand Up @@ -68,3 +68,6 @@ circl_plugin.so: circl.go

circl.go:
go run .etc/all_imports.go -out $@

updates:
@GOWORK=off go list -u -f '{{if (and (not (or .Main .Indirect)) .Update)}}{{.Path}}: [{{.Version}} -> {{.Update.Version}}]{{end}}' -mod=mod -m all 2> /dev/null
4 changes: 2 additions & 2 deletions go.mod
Expand Up @@ -4,6 +4,6 @@ go 1.19

require (
github.com/bwesterb/go-ristretto v1.2.3
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a
golang.org/x/sys v0.3.0
golang.org/x/crypto v0.9.0
golang.org/x/sys v0.9.0
)
11 changes: 7 additions & 4 deletions go.sum
@@ -1,6 +1,9 @@
github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a h1:diz9pEYuTIuLMJLs3rGDkeaTsNyRs6duYdFyPAxzE/U=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
79 changes: 79 additions & 0 deletions 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
}
134 changes: 134 additions & 0 deletions tss/frost/commit.go
@@ -0,0 +1,134 @@
package frost

import (
"errors"
"fmt"
"io"
"sort"

"go.bryk.io/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
}
11 changes: 11 additions & 0 deletions tss/frost/doc.go
@@ -0,0 +1,11 @@
/*
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
86 changes: 86 additions & 0 deletions tss/frost/frost.go
@@ -0,0 +1,86 @@
package frost

import (
"io"

"go.bryk.io/circl/group"
"go.bryk.io/circl/vss"
)

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, vss.SecretCommitment, error,
) {
ss := vss.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: vss.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)
}

0 comments on commit 5c22ce5

Please sign in to comment.