Skip to content

Commit

Permalink
Merge pull request #9 from threshold-network/aggregate
Browse files Browse the repository at this point in the history
Aggregate signature shares
  • Loading branch information
eth-r authored Jan 8, 2024
2 parents bb69649 + f6af9a5 commit e8b5c8a
Show file tree
Hide file tree
Showing 9 changed files with 923 additions and 567 deletions.
22 changes: 22 additions & 0 deletions frost/bip340.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ func (bc *Bip340Curve) EcAdd(a *Point, b *Point) *Point {
return &Point{x, y}
}

// EcSub returns the subtraction of two elliptic curve points.
func (bc *Bip340Curve) EcSub(a *Point, b *Point) *Point {
bNeg := &Point{b.X, new(big.Int).Neg(b.Y)}
return bc.EcAdd(a, bNeg)
}

// Identity returns elliptic curve identity element.
func (bc *Bip340Curve) Identity() *Point {
// For elliptic curves, the identity is the point at infinity.
Expand Down Expand Up @@ -191,6 +197,22 @@ func (b *Bip340Ciphersuite) hash(tag, msg []byte) [32]byte {
return hashed
}

// EncodePoint encodes the given elliptic curve point to a byte slice in a way
// that is *specific* to [BIP-340] needs.
//
// This function yields a different result than SerializePoint function from the
// Curve interface. The SerializePoint serializes both X and Y coordinates,
// while EncodePoint serializes just X coordinate, as it is expected by
// [BIP-340] for the challenge computation. The way SerializePoint works allows
// to avoid computing Y coordinate manually when exchanging data. The way
// EncodePoint works is dictated by [BIP-340] specification.
func (b *Bip340Ciphersuite) EncodePoint(point *Point) []byte {
xMod := new(big.Int).Mod(point.X, b.curve.P)
xbs := make([]byte, 32)
xMod.FillBytes(xbs)
return xbs
}

// concat performs a concatenation of byte slices without the modification of
// the slices passed as parameters. A brand new slice instance is always
// returned from the function.
Expand Down
13 changes: 13 additions & 0 deletions frost/bip340_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,19 @@ func TestBip340CurveEcAdd(t *testing.T) {
testutils.AssertStringsEqual(t, "Y coordinate", expectedY, result.Y.String())
}

func TestBip340CurveEcSub(t *testing.T) {
curve := NewBip340Ciphersuite().Curve()
point1 := curve.EcBaseMul(big.NewInt(30))
point2 := curve.EcBaseMul(big.NewInt(5))
result := curve.EcSub(point1, point2)

expectedX := "66165162229742397718677620062386824252848999675912518712054484685772795754260"
expectedY := "52018513869565587577673992057861898728543589604141463438466108080111932355586"

testutils.AssertStringsEqual(t, "X coordinate", expectedX, result.X.String())
testutils.AssertStringsEqual(t, "Y coordinate", expectedY, result.Y.String())
}

func TestBip340CurveEcAdd_Identity(t *testing.T) {
curve := NewBip340Ciphersuite().Curve()
point := curve.EcBaseMul(big.NewInt(10))
Expand Down
28 changes: 28 additions & 0 deletions frost/ciphersuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ import (
type Ciphersuite interface {
Hashing
Curve() Curve

// EncodePoint encodes the given elliptic curve point to a byte slice in
// a way that is *specific* to the given ciphersuite needs. This is
// especially important when calculating a signature challenge in [FROST].
//
// This function may yield a different result than SerializePoint function
// from the Curve interface. While the SerializePoint result should be
// considered an internal serialization that may be optimized for speed or
// data consistency, the EncodePoint result should be considered an external
// serialization, always reflecting the given ciphersuite's specification
// requirements.
EncodePoint(point *Point) []byte
}

// Hashing interface abstracts out hash functions implementations specific to the
Expand All @@ -22,6 +34,10 @@ type Ciphersuite interface {
// generically written as H. Using H, [FROST] introduces distinct domain-separated
// hashes, H1, H2, H3, H4, and H5. The details of H1, H2, H3, H4, and H5 vary
// based on ciphersuite.
//
// Note that for some of those functions it may be important to use a specific
// encoding of elliptic curve points depending on the ciphersuite being
// implemented.
type Hashing interface {
H1(m []byte) *big.Int
H2(m []byte, ms ...[]byte) *big.Int
Expand All @@ -43,6 +59,9 @@ type Curve interface {
// EcAdd returns the sum of two elliptic curve points.
EcAdd(*Point, *Point) *Point

// EcSub returns the subtraction of two elliptic curve points.
EcSub(*Point, *Point) *Point

// Identity returns elliptic curve identity element.
Identity() *Point

Expand Down Expand Up @@ -91,3 +110,12 @@ type Point struct {
func (p *Point) String() string {
return fmt.Sprintf("Point[X=0x%v, Y=0x%v]", p.X.Text(16), p.Y.Text(16))
}

// Signature represents a Schnorr signature produced by [FROST] protocol as
// a result of the signature share aggregation. Note that the signature produced
// by the signature share aggregation in [FROST] may not be valid if there are
// malicious signers present.
type Signature struct {
R *Point // R in [FROST] appendix C
Z *big.Int // z in [FROST] appendix C
}
81 changes: 81 additions & 0 deletions frost/coordinator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package frost

import (
"errors"
"math/big"
)

// Coordinator represents a coordinator of the [FROST] signing protocol.
type Coordinator struct {
Participant
}

// Aggregate implements Signature Share Aggregation from [FROST], section
// 5.3. Signature Share Aggregation.
//
// Note that the signature produced by the signature share aggregation in
// [FROST] may not be valid if there are malicious signers present.
func (c *Coordinator) Aggregate(
message []byte,
commitments []*NonceCommitment,
signatureShares []*big.Int,
) (*Signature, error) {
// From [FROST]:
//
// 5.3. Signature Share Aggregation
//
// After participants perform round two and send their signature shares
// to the Coordinator, the Coordinator aggregates each share to produce
// a final signature. Before aggregating, the Coordinator MUST validate
// each signature share using DeserializeScalar. If validation fails,
// the Coordinator MUST abort the protocol as the resulting signature
// will be invalid. If all signature shares are valid, the Coordinator
// aggregates them to produce the final signature using the following
// procedure.
//
// Inputs:
// - commitment_list = [(i, hiding_nonce_commitment_i,
// binding_nonce_commitment_i), ...], a list of commitments issued by
// each participant, where each element in the list indicates a
// NonZeroScalar identifier i and two commitment Element values
// (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list
// MUST be sorted in ascending order by identifier.
// - msg, the message to be signed, a byte string.
// - group_public_key, public key corresponding to the group signing
// key, an Element.
// - sig_shares, a set of signature shares z_i, Scalar values, for each
// participant, of length NUM_PARTICIPANTS, where
// MIN_PARTICIPANTS <= NUM_PARTICIPANTS <= MAX_PARTICIPANTS.
//
// Outputs:
// - (R, z), a Schnorr signature consisting of an Element R and
// Scalar z.

// TODO: validate the number of signature shares

validationErrors, _ := c.validateGroupCommitmentsBase(commitments)
if len(validationErrors) != 0 {
return nil, errors.Join(validationErrors...)
}

// binding_factor_list = compute_binding_factors(group_public_key, commitment_list, msg)
bindingFactors := c.computeBindingFactors(message, commitments)

// group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
groupCommitment := c.computeGroupCommitment(commitments, bindingFactors)

curve := c.ciphersuite.Curve()
curveOrder := curve.Order()

// z = Scalar(0)
z := big.NewInt(0)
// for z_i in sig_shares:
// z = z + z_i
for _, zi := range signatureShares {
z.Add(z, zi)
z.Mod(z, curveOrder)
}

// return (group_commitment, z)
return &Signature{groupCommitment, z}, nil
}
1 change: 0 additions & 1 deletion frost/frost.go

This file was deleted.

Loading

0 comments on commit e8b5c8a

Please sign in to comment.