Skip to content

Commit

Permalink
various enhancements, updates, optimizations, serde, and dependency u…
Browse files Browse the repository at this point in the history
…pgrades

Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com>
  • Loading branch information
dbourdrez authored and bytemare committed Jul 29, 2024
1 parent 1a3c780 commit 8d030ec
Show file tree
Hide file tree
Showing 19 changed files with 776 additions and 635 deletions.
103 changes: 66 additions & 37 deletions commitment.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import (
"encoding/binary"
"errors"
"fmt"
secretsharing "github.com/bytemare/secret-sharing"
"slices"

group "github.com/bytemare/crypto"
secretsharing "github.com/bytemare/secret-sharing"

"github.com/bytemare/frost/internal"
)
Expand All @@ -24,66 +24,94 @@ var errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid

// Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces.
type Commitment struct {
Identifier uint64
PublicKey *group.Element
HidingNonce *group.Element
BindingNonce *group.Element
PublicKey *group.Element
HidingNonce *group.Element
BindingNonce *group.Element
CommitmentID uint64
ParticipantID uint64
Ciphersuite Ciphersuite
}

func commitmentEncodedSize(g group.Group) int {
return 1 + 8 + 8 + 3*g.ElementLength()
}

// Encode returns the serialized byte encoding of a participant's commitment.
func (c Commitment) Encode() []byte {
id := c.Identifier
func (c *Commitment) Encode() []byte {
hNonce := c.HidingNonce.Encode()
bNonce := c.BindingNonce.Encode()
pubKey := c.PublicKey.Encode()

out := make([]byte, 8, 8+len(hNonce)+len(bNonce))
binary.LittleEndian.PutUint64(out, id)
copy(out[8:], hNonce)
copy(out[8+len(hNonce):], bNonce)
out := make([]byte, 9, commitmentEncodedSize(group.Group(c.Ciphersuite)))
out[0] = byte(c.Ciphersuite)
binary.LittleEndian.PutUint64(out[1:], c.CommitmentID)
binary.LittleEndian.PutUint64(out[9:], c.ParticipantID)
copy(out[17:], hNonce)
copy(out[17+len(hNonce):], bNonce)
copy(out[17+len(hNonce)+len(bNonce):], pubKey)

return out
}

// DecodeCommitment attempts to deserialize the encoded commitment given as input, and to return it.
func DecodeCommitment(cs Ciphersuite, data []byte) (*Commitment, error) {
g := cs.Configuration().Ciphersuite.Group
scalarLength := g.ScalarLength()
elementLength := g.ElementLength()
// Decode attempts to deserialize the encoded commitment given as input, and to return it.
func (c *Commitment) Decode(data []byte) error {
if len(data) < 16 {
return errDecodeCommitmentLength
}

if len(data) != scalarLength+2*elementLength {
return nil, errDecodeCommitmentLength
cs := Ciphersuite(data[0])
if !cs.Available() {
return internal.ErrInvalidCiphersuite
}

c := &Commitment{
Identifier: 0,
HidingNonce: g.NewElement(),
BindingNonce: g.NewElement(),
g := cs.Group()

if len(data) != commitmentEncodedSize(g) {
return errDecodeCommitmentLength
}

c.Identifier = internal.UInt64FromLE(data[:scalarLength])
cID := binary.LittleEndian.Uint64(data[1:9])
pID := binary.LittleEndian.Uint64(data[9:17])
offset := 17 + g.ElementLength()

if err := c.HidingNonce.Decode(data[:scalarLength]); err != nil {
return nil, fmt.Errorf("failed to decode commitment hiding nonce: %w", err)
hn := g.NewElement()
if err := hn.Decode(data[17:offset]); err != nil {
return fmt.Errorf("invalid encoding of hiding nonce: %w", err)
}

if err := c.BindingNonce.Decode(data[:scalarLength]); err != nil {
return nil, fmt.Errorf("failed to decode commitment binding nonce: %w", err)
bn := g.NewElement()
if err := bn.Decode(data[offset : offset+g.ElementLength()]); err != nil {
return fmt.Errorf("invalid encoding of binding nonce: %w", err)
}

return c, nil
offset += g.ElementLength()

pk := g.NewElement()
if err := pk.Decode(data[offset : offset+g.ElementLength()]); err != nil {
return fmt.Errorf("invalid encoding of public key: %w", err)
}

c.Ciphersuite = cs
c.CommitmentID = cID
c.ParticipantID = pID
c.HidingNonce = hn
c.BindingNonce = bn
c.PublicKey = pk

return nil
}

// CommitmentList is a sortable list of commitments.
type CommitmentList []*Commitment

func cmpID(a, b *Commitment) int {
switch {
case a.Identifier != b.Identifier: // a == b
return 0
case a.Identifier <= b.Identifier: // a < b
case a.ParticipantID < b.ParticipantID: // a < b
return -1
default:
case a.ParticipantID > b.ParticipantID:
return 1
default:
return 0
}
}

Expand All @@ -98,28 +126,29 @@ func (c CommitmentList) IsSorted() bool {
}

// Encode serializes a whole commitment list.
func (c CommitmentList) Encode() []byte {
func (c CommitmentList) Encode(g group.Group) []byte {
var encoded []byte

for _, l := range c {
e := internal.Concatenate(internal.UInt64LE(l.Identifier), l.HidingNonce.Encode(), l.BindingNonce.Encode())
id := g.NewScalar().SetUInt64(l.ParticipantID).Encode()
e := internal.Concatenate(id, l.HidingNonce.Encode(), l.BindingNonce.Encode())
encoded = append(encoded, e...)
}

return encoded
}

// Participants returns the list of participants in the commitment list.
// Participants returns the list of participants in the commitment list in the form of a polynomial.
func (c CommitmentList) Participants(g group.Group) secretsharing.Polynomial {
return secretsharing.NewPolynomialFromListFunc(g, c, func(c *Commitment) *group.Scalar {
return g.NewScalar().SetUInt64(c.Identifier)
return g.NewScalar().SetUInt64(c.ParticipantID)
})
}

// Get returns the commitment of the participant with the corresponding identifier, or nil if it was not found.
func (c CommitmentList) Get(identifier uint64) *Commitment {
for _, com := range c {
if com.Identifier == identifier {
if com.ParticipantID == identifier {
return com
}
}
Expand Down
40 changes: 28 additions & 12 deletions coordinator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

package frost

import (
group "github.com/bytemare/crypto"

"github.com/bytemare/frost/internal"
)

// AggregateSignatures allows the coordinator to produce the final signature given all signature shares.
//
// Before aggregation, each signature share must be a valid, deserialized element. If that validation fails the
Expand All @@ -17,17 +23,22 @@ package frost
// The coordinator should verify this signature using the group public key before publishing or releasing the signature.
// This aggregate signature will verify if and only if all signature shares are valid. If an invalid share is identified
// a reasonable approach is to remove the participant from the set of allowed participants in future runs of FROST.
func (c Configuration) AggregateSignatures(msg []byte, sigShares []*SignatureShare, coms CommitmentList) *Signature {
func (c Configuration) AggregateSignatures(
msg []byte,
sigShares []*SignatureShare,
coms CommitmentList,
publicKey *group.Element,
) *Signature {
coms.Sort()

// Compute binding factors
bindingFactorList := c.computeBindingFactors(coms, c.GroupPublicKey.Encode(), msg)
bindingFactorList := c.computeBindingFactors(publicKey, coms, msg)

// Compute group commitment
groupCommitment := c.computeGroupCommitment(coms, bindingFactorList)
groupCommitment := computeGroupCommitment(c.Group, coms, bindingFactorList)

// Compute aggregate signature
z := c.Ciphersuite.Group.NewScalar()
z := c.Group.NewScalar()
for _, share := range sigShares {
z.Add(share.SignatureShare)
}
Expand All @@ -47,22 +58,27 @@ func (c Configuration) VerifySignatureShare(com *Commitment,
message []byte,
sigShare *SignatureShare,
commitments CommitmentList,
) bool {
bindingFactor, _, lambdaChall, err := c.do(message, commitments, com.Identifier)
if err != nil {
panic(err)
publicKey *group.Element,
) error {
if com.ParticipantID != sigShare.Identifier {
return internal.ErrWrongVerificationData
}

if com.Identifier != sigShare.Identifier {
panic(nil)
bindingFactor, lambdaChall, err := c.do(publicKey, nil, message, commitments, com.ParticipantID)
if err != nil {
return err
}

// Commitment KeyShare
commShare := com.HidingNonce.Copy().Add(com.BindingNonce.Copy().Multiply(bindingFactor))

// Compute relation values
l := c.Ciphersuite.Group.Base().Multiply(sigShare.SignatureShare)
l := c.Group.Base().Multiply(sigShare.SignatureShare)
r := commShare.Add(com.PublicKey.Multiply(lambdaChall))

return l.Equal(r) == 1
if l.Equal(r) != 1 {
return internal.ErrInvalidVerificationShare
}

return nil
}
100 changes: 100 additions & 0 deletions debug/debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: MIT
//
// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree or at
// https://spdx.org/licenses/MIT.html

// Package debug provides tools for key generation and verification for debugging purposes. They might be helpful for
// setups and investigations, but are not recommended to be used with production data (e.g. centralized key generation
// or recovery reveals the group's secret key in one spot, which goes against the principle in a decentralized setup).
package debug

import (
"fmt"

group "github.com/bytemare/crypto"
secretsharing "github.com/bytemare/secret-sharing"

"github.com/bytemare/frost"
)

// TrustedDealerKeygen uses Shamir and Verifiable Secret Sharing to create secret shares of an input group secret.
// These shares should be distributed securely to relevant participants. Note that this is centralized and combines
// the shared secret at some point. To use a decentralized dealer-less key generation, use the github.com/bytemare/dkg
// package.
func TrustedDealerKeygen(

Check failure on line 27 in debug/debug.go

View workflow job for this annotation

GitHub Actions / Lint

unnamedResult: consider giving a name to these results (gocritic)
c frost.Ciphersuite,
secret *group.Scalar,
max, min int,

Check failure on line 30 in debug/debug.go

View workflow job for this annotation

GitHub Actions / Lint

builtinShadow: shadowing of predeclared identifier: max (gocritic)
coeffs ...*group.Scalar,
) ([]*frost.KeyShare, *group.Element, []*group.Element) {
g := group.Group(c)

if secret == nil {
// If no secret provided, generated a new random secret.
g.NewScalar().Random()
}

privateKeyShares, poly, err := secretsharing.ShardReturnPolynomial(g, secret, uint(min), uint(max), coeffs...)
if err != nil {
panic(err)
}

coms := secretsharing.Commit(g, poly)

shares := make([]*frost.KeyShare, max)
for i, k := range privateKeyShares {
shares[i] = &frost.KeyShare{
Secret: k.Secret,
GroupPublicKey: coms[0],
PublicKeyShare: secretsharing.PublicKeyShare{
PublicKey: g.Base().Multiply(k.Secret),
Commitment: coms,
ID: k.ID,
Group: g,
},
}
}

return shares, coms[0], coms
}

// RecoverGroupSecret returns the groups secret from at least t-among-n (t = threshold) participant key shares. This is
// not recommended, as combining all distributed secret shares can put the group secret at risk.
func RecoverGroupSecret(g group.Group, keyShares []*frost.KeyShare) (*group.Scalar, error) {
keys := make([]secretsharing.Share, len(keyShares))
for i, v := range keyShares {
keys[i] = v
}

secret, err := secretsharing.CombineShares(g, keys)
if err != nil {
return nil, fmt.Errorf("failed to reconstruct group secret: %w", err)
}

return secret, nil
}

// RecoverPublicKeys returns the group public key as well those from all participants.
func RecoverPublicKeys(g group.Group, max int, commitment []*group.Element) (*group.Element, []*group.Element) {

Check failure on line 81 in debug/debug.go

View workflow job for this annotation

GitHub Actions / Lint

builtinShadow: shadowing of predeclared identifier: max (gocritic)
pk := commitment[0]
keys := make([]*group.Element, max)

for i := 1; i <= max; i++ {
pki, err := secretsharing.PubKeyForCommitment(g, uint64(i), commitment)
if err != nil {
panic(err)
}
keys[i-1] = pki

Check failure on line 90 in debug/debug.go

View workflow job for this annotation

GitHub Actions / Lint

assignments should only be cuddled with other assignments (wsl)
}

return pk, keys
}

// VerifyVSS allows verification of a participant's secret share given a VSS commitment to the secret polynomial.
func VerifyVSS(g group.Group, share *frost.KeyShare, commitment []*group.Element) bool {
pk := g.Base().Multiply(share.SecretKey())
return secretsharing.Verify(g, share.Identifier(), pk, commitment)
}
Loading

0 comments on commit 8d030ec

Please sign in to comment.