From 1a3c780f8851a06e9baff11086a71f8d370c3231 Mon Sep 17 00:00:00 2001 From: Daniel Bourdrez Date: Mon, 8 Jul 2024 22:22:56 +0200 Subject: [PATCH 01/31] rewrite Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- .gitignore | 2 + commitment.go | 42 +++--- coordinator.go | 66 +++----- dkg/README.md | 55 ------- dkg/dkg.go | 254 ------------------------------- examples_test.go | 302 +++++++++++++++++++++++-------------- frost.go | 152 +++++++++---------- go.mod | 21 +-- go.sum | 38 ++--- internal/binding.go | 10 +- internal/ciphersuite.go | 9 +- internal/utils.go | 21 +-- participant.go | 159 +++++++++++-------- schnorr.go | 33 ++-- tests/dkg_test.go | 250 ++++++++---------------------- tests/frost_test.go | 84 +++++------ tests/vector_utils_test.go | 45 +++--- tests/vectors_test.go | 48 +++--- 18 files changed, 607 insertions(+), 984 deletions(-) delete mode 100644 dkg/README.md delete mode 100644 dkg/dkg.go diff --git a/.gitignore b/.gitignore index 66fd13c..bbfd2cb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +.idea \ No newline at end of file diff --git a/commitment.go b/commitment.go index 0d16bb8..9a7980d 100644 --- a/commitment.go +++ b/commitment.go @@ -9,8 +9,10 @@ package frost import ( + "encoding/binary" "errors" "fmt" + secretsharing "github.com/bytemare/secret-sharing" "slices" group "github.com/bytemare/crypto" @@ -22,21 +24,22 @@ 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 *group.Scalar + Identifier uint64 + PublicKey *group.Element HidingNonce *group.Element BindingNonce *group.Element } // Encode returns the serialized byte encoding of a participant's commitment. func (c Commitment) Encode() []byte { - id := c.Identifier.Encode() + id := c.Identifier hNonce := c.HidingNonce.Encode() bNonce := c.BindingNonce.Encode() - out := make([]byte, len(id)+len(hNonce)+len(bNonce)) - copy(out, id) - copy(out[len(id):], hNonce) - copy(out[len(id)+len(hNonce):], bNonce) + out := make([]byte, 8, 8+len(hNonce)+len(bNonce)) + binary.LittleEndian.PutUint64(out, id) + copy(out[8:], hNonce) + copy(out[8+len(hNonce):], bNonce) return out } @@ -52,14 +55,12 @@ func DecodeCommitment(cs Ciphersuite, data []byte) (*Commitment, error) { } c := &Commitment{ - Identifier: g.NewScalar(), + Identifier: 0, HidingNonce: g.NewElement(), BindingNonce: g.NewElement(), } - if err := c.Identifier.Decode(data[:scalarLength]); err != nil { - return nil, fmt.Errorf("failed to decode commitment identifier: %w", err) - } + c.Identifier = internal.UInt64FromLE(data[:scalarLength]) if err := c.HidingNonce.Decode(data[:scalarLength]); err != nil { return nil, fmt.Errorf("failed to decode commitment hiding nonce: %w", err) @@ -77,9 +78,9 @@ type CommitmentList []*Commitment func cmpID(a, b *Commitment) int { switch { - case a.Identifier.Equal(b.Identifier) == 1: // a == b + case a.Identifier != b.Identifier: // a == b return 0 - case a.Identifier.LessOrEqual(b.Identifier) == 1: // a < b + case a.Identifier <= b.Identifier: // a < b return -1 default: return 1 @@ -101,7 +102,7 @@ func (c CommitmentList) Encode() []byte { var encoded []byte for _, l := range c { - e := internal.Concatenate(l.Identifier.Encode(), l.HidingNonce.Encode(), l.BindingNonce.Encode()) + e := internal.Concatenate(internal.UInt64LE(l.Identifier), l.HidingNonce.Encode(), l.BindingNonce.Encode()) encoded = append(encoded, e...) } @@ -109,19 +110,16 @@ func (c CommitmentList) Encode() []byte { } // Participants returns the list of participants in the commitment list. -func (c CommitmentList) Participants() []*group.Scalar { - identifiers := make([]*group.Scalar, len(c)) - for i, l := range c { - identifiers[i] = l.Identifier - } - - return identifiers +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) + }) } // Get returns the commitment of the participant with the corresponding identifier, or nil if it was not found. -func (c CommitmentList) Get(identifier *group.Scalar) *Commitment { +func (c CommitmentList) Get(identifier uint64) *Commitment { for _, com := range c { - if com.Identifier.Equal(identifier) == 1 { + if com.Identifier == identifier { return com } } diff --git a/coordinator.go b/coordinator.go index 06f4260..50d6746 100644 --- a/coordinator.go +++ b/coordinator.go @@ -8,12 +8,7 @@ package frost -import ( - group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" -) - -// Aggregate allows the coordinator to produce the final signature given all signature shares. +// 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 // coordinator must abort the protocol, as the resulting signature will be invalid. @@ -22,23 +17,17 @@ import ( // 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 (p *Participant) Aggregate( - list CommitmentList, - msg []byte, - sigShares []*SignatureShare, -) *Signature { - if !list.IsSorted() { - panic("list not sorted") - } +func (c Configuration) AggregateSignatures(msg []byte, sigShares []*SignatureShare, coms CommitmentList) *Signature { + coms.Sort() // Compute binding factors - bindingFactorList := p.computeBindingFactors(list, msg) + bindingFactorList := c.computeBindingFactors(coms, c.GroupPublicKey.Encode(), msg) // Compute group commitment - groupCommitment := p.computeGroupCommitment(list, bindingFactorList) + groupCommitment := c.computeGroupCommitment(coms, bindingFactorList) // Compute aggregate signature - z := p.Ciphersuite.Group.NewScalar() + z := c.Ciphersuite.Group.NewScalar() for _, share := range sigShares { z.Add(share.SignatureShare) } @@ -50,45 +39,30 @@ func (p *Participant) Aggregate( } // VerifySignatureShare verifies a signature share. -// id, pki, commi, and sigShareI are, respectively, the identifier, public key, commitment, and signature share of +// commitment, pki, and sigShareI are, respectively, commitment, public key, and signature share of // the participant whose share is to be verified. // // The CommitmentList must be sorted in ascending order by identifier. -func (p *Participant) VerifySignatureShare( - commitment *Commitment, - pki *group.Element, - sigShareI *group.Scalar, - coms CommitmentList, - msg []byte, +func (c Configuration) VerifySignatureShare(com *Commitment, + message []byte, + sigShare *SignatureShare, + commitments CommitmentList, ) bool { - if !coms.IsSorted() { - panic("list not sorted") + bindingFactor, _, lambdaChall, err := c.do(message, commitments, com.Identifier) + if err != nil { + panic(err) } - // Compute Binding Factor(s) - bindingFactorList := p.computeBindingFactors(coms, msg) - bindingFactor := bindingFactorList.BindingFactorForParticipant(commitment.Identifier) - - // Compute Group Commitment - groupCommitment := p.computeGroupCommitment(coms, bindingFactorList) + if com.Identifier != sigShare.Identifier { + panic(nil) + } // Commitment KeyShare - commShare := commitment.HidingNonce.Copy().Add(commitment.BindingNonce.Copy().Multiply(bindingFactor)) - - // Compute the challenge - challenge := challenge(p.Ciphersuite, groupCommitment, p.Configuration.GroupPublicKey, msg) - - // Compute the interpolating value - participantList := secretsharing.Polynomial(coms.Participants()) - - lambdaI, err := participantList.DeriveInterpolatingValue(p.Ciphersuite.Group, commitment.Identifier) - if err != nil { - panic(err) - } + commShare := com.HidingNonce.Copy().Add(com.BindingNonce.Copy().Multiply(bindingFactor)) // Compute relation values - l := p.Ciphersuite.Group.Base().Multiply(sigShareI) - r := commShare.Add(pki.Multiply(challenge.Multiply(lambdaI))) + l := c.Ciphersuite.Group.Base().Multiply(sigShare.SignatureShare) + r := commShare.Add(com.PublicKey.Multiply(lambdaChall)) return l.Equal(r) == 1 } diff --git a/dkg/README.md b/dkg/README.md deleted file mode 100644 index 116a709..0000000 --- a/dkg/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Distributed Key Generation - -This package implements a Distributed Key Generation. It builds on the 2-round Pederson DGK and extends it with -zero-knowledge proofs to protect against rogue-key attacks, as defined in [FROST](https://eprint.iacr.org/2020/852.pdf). - -This effectively generates keys among participants without the need of a trusted dealer or third-party. These keys are -compatible for use in FROST. - -### References - -- Pederson introduced the [first DKG protocol](https://link.springer.com/chapter/10.1007/3-540-46416-6_47), based on Feldman's Verifiable Secret Sharing. -- Komlo & Goldberg [add zero-knowledge proofs](https://eprint.iacr.org/2020/852.pdf) to the Ped-DKG. - -## Usage - -### Assumptions - -- All parties are identified with unique IDs. -- Communicate over confidential, authenticated, and secure channels. -- All participants honestly follow the protocol (they can, nevertheless, identify the misbehaving participant). - -### Setup - -Use the same ciphersuite for the DKG and FROST. - -### Error handling - -In case of an identified misbehaving participant, abort the protocol immediately. If this happens there might be a serious -problem that must be investigated. One may re-run the protocol after excluding that participant and solving the problem. - -### Protocol - -The following steps describe how to run the DKG among participants. For each participant: -1. Run Init() - - this returns a round 1 package - - send/broadcast this package to every participant - (this might include the very same participant, in which case it should discard it) -2. Collect all the r1 packages from other participants -3. Run Continue() with the collection of r1 packages - - this returns round 2 packages, one destined to each other participant - - send these packages to their destined participant -4. Collect all round 2 packages destined to the participant -5. Run Finalize() with the collected round 1 and round 2 packages - - returns the participant's own secret signing share, - the corresponding verification share, and the group's public key -6. Erase all intermediary values received and computed by the participants (including in their states) -7. Optionally, compute the verification keys for each other participant and store them - -## Possible extensions - -- Laing and Stinson [refine Repairable Threshold Schemes](https://eprint.iacr.org/2017/1155.pdf) to enable a participant to securely reconstruct a lost share with help from their peers. -- Herzberg et al. propose [Proactive Secret Sharing](https://www.researchgate.net/profile/Amir-Herzberg/publication/221355399_Proactive_Secret_Sharing_Or_How_to_Cope_With_Perpetual_Leakage/links/02e7e52e0ecf4dbae1000000/Proactive-Secret-Sharing-Or-How-to-Cope-With-Perpetual-Leakage.pdf), allowing for shares to be rotated without impact on the secret key. -- Gennaro et al. improve on the Ped-DKG and propose a [more robust version called New-DKG](https://link.springer.com/article/10.1007/s00145-006-0347-3). -- Canetti et al. extend New-DKG to make it [secure against adaptive adversaries](https://link.springer.com/content/pdf/10.1007/3-540-48405-1_7.pdf). -- Jarecki and Lysyanskaya present the [erasure-free model](https://www.iacr.org/archive/eurocrypt2000/1807/18070223-new.pdf) for threshold schemes secure against adaptive adversaries. diff --git a/dkg/dkg.go b/dkg/dkg.go deleted file mode 100644 index a9e64cb..0000000 --- a/dkg/dkg.go +++ /dev/null @@ -1,254 +0,0 @@ -// 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 dkg implements the Distributed Key Generation described in FROST, -// using zero-knowledge proofs in Schnorr signatures. -package dkg - -import ( - "encoding/hex" - "errors" - "fmt" - - group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" - - "github.com/bytemare/frost" - "github.com/bytemare/frost/internal" -) - -var ( - errRound1DataElements = errors.New("invalid number of expected round 1 data packets") - errRound2DataElements = errors.New("invalid number of expected round 2 data packets") - errRound2InvalidReceiver = errors.New("invalid receiver in round 2 package") - errInvalidSignature = errors.New("invalid signature") - - errCommitmentNotFound = errors.New("commitment not found for participant") - errInvalidSecretShare = errors.New("invalid secret share received from peer") - errVerificationShareFailed = errors.New("failed to compute correct verification share") -) - -// Round1Data is the output data of the Init() function, to be broadcast to all participants. -type Round1Data struct { - ProofOfKnowledge frost.Signature - SenderIdentifier *group.Scalar - Commitment []*group.Element -} - -// Round2Data is an output of the Continue() function, to be sent to the Receiver. -type Round2Data struct { - SenderIdentifier *group.Scalar - - ReceiverIdentifier *group.Scalar - SecretShare *group.Scalar -} - -// Participant represent a party in the Distributed Key Generation. Once the DKG completed, all values must be erased. -type Participant struct { - Identifier *group.Scalar - publicShare *group.Element - secretShare *group.Scalar - coefficients secretsharing.Polynomial - ciphersuite internal.Ciphersuite - maxSigners int - threshold int -} - -// NewParticipant instantiates a new participant with identifier id. -func NewParticipant(c internal.Ciphersuite, id *group.Scalar, maxSigners, threshold int) *Participant { - return &Participant{ - maxSigners: maxSigners, - threshold: threshold, - ciphersuite: c, - Identifier: id, - } -} - -func (p *Participant) challenge(id *group.Scalar, pubkey, r *group.Element) *group.Scalar { - // The paper actually hashes (id || dst || φ0 || r) - return p.ciphersuite.HDKG(internal.Concatenate( - id.Encode(), - pubkey.Encode(), - r.Encode(), - )) -} - -// Init returns a participant's output for the first round, and stores intermediate values internally. -func (p *Participant) Init() *Round1Data { - // Step 1 - secretCoefficients := secretsharing.NewPolynomial(uint(p.threshold)) - for i := 0; i < p.threshold; i++ { - secretCoefficients[i] = p.ciphersuite.Group.NewScalar().Random() - } - - // step 3 - we do this before step 2, so we can reuse the calculation of the commitment com[0] - com := secretsharing.Commit(p.ciphersuite.Group, secretCoefficients) - - // step 2 - k := p.ciphersuite.Group.NewScalar().Random() - r := p.ciphersuite.Group.Base().Multiply(k) - c := p.challenge(p.Identifier, com[0], r) - mu := k.Add(secretCoefficients[0].Copy().Multiply(c)) - - p.coefficients = secretCoefficients - p.publicShare = com[0] - - package1 := &Round1Data{ - SenderIdentifier: p.Identifier, - Commitment: com, - ProofOfKnowledge: frost.Signature{ - R: r, - Z: mu, - }, - } - - // step 4, broadcast package 1 to all other participants - return package1 -} - -// Continue ingests the broadcast data from other peers and returns a dedicated Round2Data structure for each peer. -func (p *Participant) Continue(r1data []*Round1Data) ([]*Round2Data, error) { - if len(r1data) != p.maxSigners { - return nil, errRound1DataElements - } - - r2data := make([]*Round2Data, 0, len(r1data)-1) - - for _, r1package := range r1data { - if r1package == nil { - continue - } - - peer := r1package.SenderIdentifier - if peer.Equal(p.Identifier) == 1 { - continue - } - - // round1, step 5 - c := p.challenge(peer, r1package.Commitment[0], r1package.ProofOfKnowledge.R) - rc := p.ciphersuite.Group.Base(). - Multiply(r1package.ProofOfKnowledge.Z). - Subtract(r1package.Commitment[0].Copy().Multiply(c)) - - if r1package.ProofOfKnowledge.R.Equal(rc) != 1 { - return nil, fmt.Errorf( - "%w: participant %v", - errInvalidSignature, - hex.EncodeToString(r1package.SenderIdentifier.Encode()), - ) - } - - // round 2, step 1 - fil := p.coefficients.Evaluate(p.ciphersuite.Group, peer) - r2data = append(r2data, &Round2Data{ - SenderIdentifier: p.Identifier.Copy(), - ReceiverIdentifier: peer.Copy(), - SecretShare: fil, - }) - } - - p.secretShare = p.coefficients.Evaluate(p.ciphersuite.Group, p.Identifier) - - return r2data, nil -} - -// Finalize ingests the broadcast data from round 1 and the round 2 data destined for the participant, -// and returns the participant's secret share and verification key, and the group's public key. -func (p *Participant) Finalize( - r1data []*Round1Data, - r2data []*Round2Data, -) (signingShare *group.Scalar, verificationShare, groupPublic *group.Element, err error) { - if len(r1data) != p.maxSigners { - return nil, nil, nil, errRound1DataElements - } - - if len(r1data) != len(r2data)+1 { - return nil, nil, nil, errRound2DataElements - } - - signingShare = p.ciphersuite.Group.NewScalar().Zero() - groupPublic = p.ciphersuite.Group.NewElement().Identity() - - for _, r2package := range r2data { - if r2package.ReceiverIdentifier.Equal(p.Identifier) != 1 { - return nil, nil, nil, errRound2InvalidReceiver - } - - // round 2, step 2 - - // Find the commitment from the participant. - var com []*group.Element - - for _, r1d := range r1data { - if r1d.SenderIdentifier.Equal(r2package.SenderIdentifier) == 1 { - com = r1d.Commitment - } - } - - if len(com) == 0 { - return nil, nil, nil, - fmt.Errorf("%w: %v", - errCommitmentNotFound, - hex.EncodeToString(r2package.SenderIdentifier.Encode())) - } - - // Verify the secret share is valid with regard to the commitment. - pk := p.ciphersuite.Group.Base().Multiply(r2package.SecretShare) - if !secretsharing.Verify(p.ciphersuite.Group, p.Identifier, pk, com) { - return nil, nil, nil, fmt.Errorf( - "%w: %v", - errInvalidSecretShare, - hex.EncodeToString(r2package.SenderIdentifier.Encode()), - ) - } - - // Round 2, step 3 - signingShare.Add(r2package.SecretShare) - - // Round 2, step 4 - groupPublic.Add(com[0]) - } - - signingShare.Add(p.secretShare) - groupPublic.Add(p.publicShare) - - // round 2, step 4 - verificationShare = p.ciphersuite.Group.Base().Multiply(signingShare) - - yi := ComputeVerificationShare(p.ciphersuite.Group, p.Identifier, r1data) - if verificationShare.Equal(yi) != 1 { - return nil, nil, nil, - fmt.Errorf("%w: want %q got %q", - errVerificationShareFailed, - hex.EncodeToString(yi.Encode()), - hex.EncodeToString(verificationShare.Encode()), - ) - } - - return signingShare, verificationShare, groupPublic, nil -} - -// ComputeVerificationShare computes the verification share for participant id given the commitments of round 1. -func ComputeVerificationShare(g group.Group, id *group.Scalar, r1data []*Round1Data) *group.Element { - yi := g.NewElement().Identity() - - for _, p := range r1data { - prime := g.NewElement().Identity() - one := g.NewScalar().One() - j := g.NewScalar().Zero() - - for _, com := range p.Commitment { - prime.Add(com.Copy().Multiply(id.Copy().Pow(j))) - j.Add(one) - } - - yi.Add(prime) - } - - return yi -} diff --git a/examples_test.go b/examples_test.go index b3b2807..4acde9d 100644 --- a/examples_test.go +++ b/examples_test.go @@ -9,131 +9,221 @@ package frost_test import ( - "encoding/hex" "fmt" - group "github.com/bytemare/crypto" - + "github.com/bytemare/dkg" "github.com/bytemare/frost" - "github.com/bytemare/frost/dkg" ) var ( - participantGeneratedInDKG *frost.Participant + maxParticipants uint + threshold uint + ciphersuite frost.Ciphersuite + participantsGeneratedInDKG []*frost.Participant commitment *frost.Commitment groupPublicKeyGeneratedInDKG *group.Element ) -// Example_dkg shows the distributed key generation procedure that must be executed by each participant to build the secret key. func Example_dkg() { // Each participant must be set to use the same configuration. - maximumAmountOfParticipants := 1 - threshold := 1 - configuration := frost.Ristretto255.Configuration() - - // Step 1: Initialise your participant. Each participant must be given an identifier that MUST be unique among - // all participants. For this example, this participant will have id = 1. - participantIdentifier := configuration.IDFromInt(1) - dkgParticipant := dkg.NewParticipant( - configuration.Ciphersuite, - participantIdentifier, - maximumAmountOfParticipants, - threshold, - ) - - // Step 2: Call Init() on each participant. This will return data that must be broadcast to all other participants + if threshold == 0 || maxParticipants == 0 { + maxParticipants = 5 + threshold = 3 + } + + if ciphersuite == 0 { + ciphersuite = frost.Ristretto255 + } + + dkgCiphersuite := dkg.Ciphersuite(ciphersuite) + + // Step 1: Initialise each participant. Each participant must be given an identifier that MUST be unique among + // all participants. + participants := make([]*dkg.Participant, 0, maxParticipants) + for i := range maxParticipants { + p, err := dkgCiphersuite.NewParticipant(uint64(i+1), maxParticipants, threshold) + if err != nil { + panic(err) + } + + participants = append(participants, p) + } + + // Step 2: Call Start() on each participant. This will return data that must be broadcast to all other participants // over a secure channel. - round1Data := dkgParticipant.Init() - if round1Data.SenderIdentifier.Equal(participantIdentifier) != 1 { - panic("this is just a test, and it failed") + r1 := make([][]byte, 0, maxParticipants) + for i := range maxParticipants { + r1 = append(r1, participants[i].Start().Encode()) } - // Step 3: First, collect all round1Data from all other participants. Then call Continue() on each participant - // providing them with the compiled data. - accumulatedRound1Data := make([]*dkg.Round1Data, 0, maximumAmountOfParticipants) - accumulatedRound1Data = append(accumulatedRound1Data, round1Data) + // Step 3: First, each participant collects all round1Data from all other participants, and decodes them using + // NewRound1Data(). + // Then call Continue() on each participant providing them with the compiled data. + accumulatedRound1Data := make([]*dkg.Round1Data, 0, maxParticipants) + for i, r := range r1 { + decodedRound1 := participants[i].NewRound1Data() + if err := decodedRound1.Decode(r); err != nil { + panic(err) + } - // This will return a dedicated package for each other participant that must be sent to them over a secure channel. - // The intended receiver is specified in the returned data. - // We ignore the error for the demo, but execution MUST be aborted upon errors. - round2Data, err := dkgParticipant.Continue(accumulatedRound1Data) - if err != nil { - panic(err) - } else if len(round2Data) != len(accumulatedRound1Data)-1 { - panic("this is just a test, and it failed") + accumulatedRound1Data = append(accumulatedRound1Data, decodedRound1) } - // Step 3: First, collect all round2Data from all other participants. Then call Finalize() on each participant - // providing the same input as for Continue() and the collected data from the second round2. - accumulatedRound2Data := round2Data + // This will return a dedicated package round2Data for each other participant that must be sent to them over a secure channel. + // The intended receiver is specified in round2Data. + // Execution MUST be aborted upon errors, and not rewound. If this fails you should probably investigate this. + // Since we centrally simulate the setup here, we use a map to keep the messages for participant together. + r2 := make(map[uint64][][]byte, maxParticipants) + for _, participant := range participants { + r, err := participant.Continue(accumulatedRound1Data) + if err != nil { + panic(err) + } - // This will, for each participant, return their secret key (which is a share of the global secret signing key), - // the corresponding verification key, and the global public key. - // We ignore the error for the demo, but execution MUST be aborted upon errors. - var participantsSecretKey *group.Scalar - participantsSecretKey, _, groupPublicKeyGeneratedInDKG, err = dkgParticipant.Finalize( - accumulatedRound1Data, - accumulatedRound2Data, - ) - if err != nil { - panic(err) + for id, data := range r { + if r2[id] == nil { + r2[id] = make([][]byte, 0, maxParticipants-1) + } + r2[id] = append(r2[id], data.Encode()) + } } - // It is important to set the group's public key. - configuration.GroupPublicKey = groupPublicKeyGeneratedInDKG + // Step 3: First, collect all round2Data from all other participants intended to this participant, and decode them + // using NewRound2Data(). + // Then call Finalize() on each participant providing the same input as for Continue() and the collected data from the second round. - // Now you can build a Signing Participant for the FROST protocol with this ID and key. - participantGeneratedInDKG = configuration.Participant(participantIdentifier, participantsSecretKey) + // This will, for each participant, return their secret key (which is a share of the global secret signing key), + // the corresponding verification/public key, and the global public key. + // In case of errors, execution MUST be aborted. + + keyShares := make([]*frost.KeyShare, maxParticipants) + participantsGeneratedInDKG = make([]*frost.Participant, maxParticipants) + + for i, participant := range participants { + accumulatedRound2Data := make([]*dkg.Round2Data, 0, maxParticipants) + for _, r := range r2[participant.Identifier] { + d := participant.NewRound2Data() + if err := d.Decode(r); err != nil { + panic(err) + } - fmt.Printf("Signing keys for participant set up. ID: %s\n", hex.EncodeToString(participantIdentifier.Encode())) + accumulatedRound2Data = append(accumulatedRound2Data, d) + } - // Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000 + participantKeys, gpk, err := participant.Finalize(accumulatedRound1Data, accumulatedRound2Data) + if err != nil { + panic(err) + } + + if groupPublicKeyGeneratedInDKG == nil { + groupPublicKeyGeneratedInDKG = gpk + } + + keyShare := &frost.KeyShare{ + ID: participantKeys.Identifier, + Secret: participantKeys.SecretKey, + PublicKey: participantKeys.PublicKey, + } + + keyShares[i] = keyShare + participantsGeneratedInDKG[i] = ciphersuite.Configuration(groupPublicKeyGeneratedInDKG).Participant(keyShare) + } + + fmt.Println("Signing keys set up in DKG.") + // Output: Signing keys set up in DKG. } // Example_signer shows the execution steps of a FROST participant. func Example_signer() { - // The following are your setup variables and configuration. - numberOfParticipants := 1 + maxParticipants = 5 + threshold = 3 + message := []byte("example message") + ciphersuite = frost.Ristretto255 // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. // See Example_dkg() on how to do generate these shares. Example_dkg() - participant := participantGeneratedInDKG + participant := participantsGeneratedInDKG[1] // Step 1: call Commit() on each participant. This will return the participant's single-use commitment. // Send this to the coordinator or all other participants over an authenticated // channel (confidentiality is not required). // A participant keeps an internal state during the protocol run across the two rounds. commitment = participant.Commit() - if commitment.Identifier.Equal(participant.KeyShare.Identifier) != 1 { + if commitment.Identifier != participant.KeyShare.ID { panic("this is just a test and it failed") } // Step 2: collect the commitments from the other participants and coordinator-chosen the message to sign, - // and finalize by signing the message. - message := []byte("example") - commitments := make(frost.CommitmentList, 0, numberOfParticipants) + // and finalize by signing the message. This is a dummy list since we have only one signer. + commitments := make(frost.CommitmentList, 0, threshold) commitments = append(commitments, commitment) - // This will produce a signature share to be sent back to the coordinator. + // Step 3: The participant receives the commitments from the other signer and the message to sign. + // Sign produce a signature share to be sent back to the coordinator. // We ignore the error for the demo, but execution MUST be aborted upon errors. - signatureShare, _ := participant.Sign(message, commitments) + signatureShare, err := participant.Sign(message, commitments) + if err != nil { + panic(err) + } + + // This shows how to verify a single signature share if !participant.VerifySignatureShare( commitment, - participant.GroupPublicKey, - signatureShare.SignatureShare, - commitments, message, + signatureShare, + commitments, ) { - panic("this is a test and it failed") + panic("signature share verification failed") } fmt.Println("Signing successful.") - // Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000 + // Output: Signing keys set up in DKG. // Signing successful. } +// Example_verification shows how to verify a FROST signature produced by multiple signers. +func Example_verification() { + maxParticipants = 5 + threshold = 3 + + message := []byte("example message") + ciphersuite = frost.Ristretto255 + + // The following sets up the signer pool and produces a signature to verify. + Example_dkg() + participants := participantsGeneratedInDKG[:threshold] + commitments := make(frost.CommitmentList, threshold) + signatureShares := make([]*frost.SignatureShare, threshold) + for i, p := range participants { + commitments[i] = p.Commit() + } + for i, p := range participants { + var err error + signatureShares[i], err = p.Sign(message, commitments) + if err != nil { + panic(err) + } + } + + configuration := ciphersuite.Configuration(groupPublicKeyGeneratedInDKG) + signature := configuration.AggregateSignatures(message, signatureShares, commitments) + + // Verify the signature + conf := ciphersuite.Configuration(groupPublicKeyGeneratedInDKG) + success := conf.VerifySignature(message, signature) + + if success { + fmt.Println("Signature is valid.") + } else { + fmt.Println("Signature is not valid.") + } + + // Output: Signing keys set up in DKG. + // Signature is valid. +} + // Example_coordinator shows the execution steps of a FROST coordinator. func Example_coordinator() { /* @@ -145,78 +235,62 @@ func Example_coordinator() { Note that it is possible to deploy the protocol without a distinguished Coordinator. */ + maxParticipants = 5 + threshold = 3 - // The following are your setup variables and configuration. - const ( - maxParticipants = 1 - numberOfParticipants = 1 // Must be >= to the threshold, and <= to the total number of participants. - ) + message := []byte("example message") + ciphersuite = frost.Ristretto255 - // 0. We suppose a previous run of DKG with a setup of participants. Here we will only use 1 participant. + // 0. We suppose a previous run of a DKG with a setup of participants. Example_dkg() - participant := participantGeneratedInDKG + participants := participantsGeneratedInDKG[:threshold] groupPublicKey := groupPublicKeyGeneratedInDKG - // A coordinator CAN be a participant. In this instance, we chose it not to be one. + // Set up a coordinator. configuration := frost.Ristretto255.Configuration(groupPublicKey) - coordinator := configuration.Participant(nil, nil) - // 1. Determine which participants will participate (at least MIN_PARTICIPANTS in number). - //participantIdentifiers := [numberOfParticipants]*group.Scalar{ - // participant.KeyShare.Identifier, - //} - participantPublicKeys := []*group.Element{ - participant.ParticipantInfo.PublicKey, + // 1. Each participant generates its commitment and sends it to the aggregator. + // Then send the message to be signed and the sorted received commitment list to each participant. + commitments := make(frost.CommitmentList, threshold) + for i, p := range participants { + commitments[i] = p.Commit() } - // 2. Receive the participant's commitments and sort the list. Then send the message to be signed and the sorted - // received commitment list to each participant. - commitments := frost.CommitmentList{ - participant.Commit(), - } - message := []byte("example") - commitments.Sort() - // 3. Collect the participants signature shares, and aggregate them to produce the final signature. This signature - // SHOULD be verified. - p1SignatureShare, _ := participant.Sign(message, commitments) - signatureShares := [numberOfParticipants]*frost.SignatureShare{ - p1SignatureShare, + // 2. Each participant signs the message and sends the resulting signature shares to the aggregator/coordinator, + // which aggregates them to produce the final signature. This signature SHOULD be verified. + var err error + signatureShares := make([]*frost.SignatureShare, threshold) + for i, participant := range participants { + signatureShares[i], err = participant.Sign(message, commitments) + if err != nil { + panic(err) + } } - signature := coordinator.Aggregate(commitments, message, signatureShares[:]) + signature := configuration.AggregateSignatures(message, signatureShares[:], commitments) - if !frost.Verify(configuration.Ciphersuite, message, signature, groupPublicKey) { - fmt.Println("invalid signature") + if !configuration.VerifySignature(message, signature) { // At this point one should try to identify which participant's signature share is invalid and act on it. // This verification is done as follows: - for i, signatureShare := range signatureShares { + for _, signatureShare := range signatureShares { // Verify whether we have the participants commitment commitmentI := commitments.Get(signatureShare.Identifier) if commitmentI == nil { panic("commitment not found") } - // Get the public key corresponding to the signature share's participant - pki := participantPublicKeys[i-1] - - if !coordinator.VerifySignatureShare( - commitmentI, - pki, - signatureShare.SignatureShare, - commitments, - message, - ) { - fmt.Printf("participant %v produced an invalid signature share", signatureShare.Identifier.Encode()) + if !configuration.VerifySignatureShare(commitmentI, message, signatureShare, commitments) { + panic(fmt.Sprintf("participant %v produced an invalid signature share", signatureShare.Identifier)) } } - panic("Failed.") + panic("Signature verification failed.") } fmt.Printf("Valid signature for %q.", message) - // Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000 - // Valid signature for "example". + // Output: Signing keys set up in DKG. + // Valid signature for "example message". } diff --git a/frost.go b/frost.go index ad4b635..f66193e 100644 --- a/frost.go +++ b/frost.go @@ -10,6 +10,7 @@ package frost import ( + "fmt" group "github.com/bytemare/crypto" "github.com/bytemare/hash" secretsharing "github.com/bytemare/secret-sharing" @@ -17,24 +18,38 @@ import ( "github.com/bytemare/frost/internal" ) +/* +- check RFC +- update description + - more buzz + - show supported ciphersuites +- Check for + - FROST2-BTZ + - FROST3 (ROAST): https://eprint.iacr.org/2022/550 + - wrapper increasing robustness and apparently reducing some calculations? + - Chu + - re-randomize keys: https://eprint.iacr.org/2024/436.pdf + +*/ + // Ciphersuite identifies the group and hash to use for FROST. type Ciphersuite byte const ( // Ed25519 uses Edwards25519 and SHA-512, producing Ed25519-compliant signatures as specified in RFC8032. - Ed25519 Ciphersuite = 1 + iota + Ed25519 = Ciphersuite(group.Edwards25519Sha512) // Ristretto255 uses Ristretto255 and SHA-512. - Ristretto255 + Ristretto255 = Ciphersuite(group.Ristretto255Sha512) // Ed448 uses Edwards448 and SHAKE256, producing Ed448-compliant signatures as specified in RFC8032. - ed448 + ed448 = Ciphersuite(2) // P256 uses P-256 and SHA-256. - P256 + P256 = Ciphersuite(group.P256Sha256) // Secp256k1 uses Secp256k1 and SHA-256. - Secp256k1 + Secp256k1 = Ciphersuite(group.Secp256k1) ed25519ContextString = "FROST-ED25519-SHA512-v1" ristretto255ContextString = "FROST-RISTRETTO255-SHA512-v1" @@ -42,7 +57,6 @@ const ( secp256k1ContextString = "FROST-secp256k1-SHA256-v1" /* - ed448ContextString = "FROST-ED448-SHAKE256-v1" */ ) @@ -59,8 +73,20 @@ func (c Ciphersuite) Available() bool { } } +func makeConf(pk *group.Element, context string, h hash.Hash, g group.Group) *Configuration { + return &Configuration{ + GroupPublicKey: pk, + Ciphersuite: internal.Ciphersuite{ + ContextString: []byte(context), + Hash: h, + Group: g, + }, + } +} + // Configuration returns a configuration created for the ciphersuite. func (c Ciphersuite) Configuration(groupPublicKey ...*group.Element) *Configuration { + //todo: pubkey as byte slice so that we can check it's a valid point in the group, must be non-nil if !c.Available() { return nil } @@ -72,43 +98,13 @@ func (c Ciphersuite) Configuration(groupPublicKey ...*group.Element) *Configurat switch c { case Ed25519: - return &Configuration{ - GroupPublicKey: pk, - Ciphersuite: internal.Ciphersuite{ - ContextString: []byte(ed25519ContextString), - Hash: hash.SHA512, - Group: group.Edwards25519Sha512, - }, - } + return makeConf(pk, ed25519ContextString, hash.SHA512, group.Edwards25519Sha512) case Ristretto255: - return &Configuration{ - GroupPublicKey: pk, - Ciphersuite: internal.Ciphersuite{ - Group: group.Ristretto255Sha512, - Hash: hash.SHA512, - ContextString: []byte(ristretto255ContextString), - }, - } + return makeConf(pk, ristretto255ContextString, hash.SHA512, group.Ristretto255Sha512) case P256: - return &Configuration{ - GroupPublicKey: pk, - Ciphersuite: internal.Ciphersuite{ - Group: group.P256Sha256, - Hash: hash.SHA256, - ContextString: []byte(p256ContextString), - }, - } + return makeConf(pk, p256ContextString, hash.SHA256, group.P256Sha256) case Secp256k1: - return &Configuration{ - GroupPublicKey: pk, - Ciphersuite: internal.Ciphersuite{ - ContextString: []byte(secp256k1ContextString), - Hash: hash.SHA256, - Group: group.Secp256k1, - }, - } - case ed448: - return nil + return makeConf(pk, secp256k1ContextString, hash.SHA256, group.Secp256k1) default: return nil } @@ -120,33 +116,27 @@ type Configuration struct { Ciphersuite internal.Ciphersuite } -// IDFromInt returns a valid ID from and integer given the configuration. -func (c Configuration) IDFromInt(id int) *group.Scalar { - s := c.Ciphersuite.Group.NewScalar() +// 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 (c Configuration) RecoverGroupSecret(keyShares []*KeyShare) (*group.Scalar, error) { + keys := make([]secretsharing.KeyShare, len(keyShares)) + for i, v := range keyShares { + keys[i] = secretsharing.KeyShare(v) + } - switch id { - case 0: - s.Zero() - case 1: - s.One() - default: - s = internal.IntegerToScalar(c.Ciphersuite.Group, id) + secret, err := secretsharing.Combine(c.Ciphersuite.Group, keys) + if err != nil { + return nil, fmt.Errorf("failed to reconstruct group secret: %w", err) } - return s + return secret, nil } // Participant returns a new participant of the protocol instantiated from the configuration an input. -func (c Configuration) Participant(id, keyShare *group.Scalar) *Participant { +func (c Configuration) Participant(keyShare *KeyShare) *Participant { return &Participant{ - ParticipantInfo: ParticipantInfo{ - KeyShare: &secretsharing.KeyShare{ - Identifier: id, - SecretKey: keyShare, - }, - Lambda: nil, - PublicKey: c.Ciphersuite.Group.Base().Multiply(keyShare), - }, + KeyShare: keyShare, + Lambda: nil, Nonce: [2]*group.Scalar{}, HidingRandom: nil, BindingRandom: nil, @@ -154,18 +144,15 @@ func (c Configuration) Participant(id, keyShare *group.Scalar) *Participant { } } -// PublicKeys is the tuple defining a commitment. -type PublicKeys []*group.Element - // DeriveGroupInfo returns the group public key as well those from all participants. -func DeriveGroupInfo(g group.Group, max int, coms secretsharing.Commitment) (*group.Element, PublicKeys) { +func DeriveGroupInfo(g group.Group, max int, coms secretsharing.Commitment) (*group.Element, []*group.Element) { pk := coms[0] - keys := make(PublicKeys, max) + keys := make([]*group.Element, max) - for i := 0; i < max; i++ { - id := internal.IntegerToScalar(g, i) + for i := 1; i <= max; i++ { + id := g.NewScalar().SetUInt64(uint64(i)) pki := derivePublicPoint(g, coms, id) - keys[i] = pki + keys[i-1] = pki } return pk, keys @@ -173,26 +160,31 @@ func DeriveGroupInfo(g group.Group, max int, coms secretsharing.Commitment) (*gr // 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 dkg package. +// the shared secret at some point. To use a decentralized dealer-less key generation, use the github.com/bytemare/dkg +// package. func TrustedDealerKeygen( g group.Group, secret *group.Scalar, max, min int, coeffs ...*group.Scalar, -) ([]*secretsharing.KeyShare, *group.Element, secretsharing.Commitment, error) { - ss, err := secretsharing.New(g, uint(min)-1, coeffs...) +) ([]*KeyShare, *group.Element, secretsharing.Commitment, error) { + privateKeyShares, poly, err := secretsharing.ShardReturnPolynomial(g, secret, uint(min), uint(max), coeffs...) if err != nil { return nil, nil, nil, err } - privateKeyShares, coeffs, err := ss.Shard(secret, uint(max)) - if err != nil { - return nil, nil, nil, err - } + coms := secretsharing.Commit(g, poly) - coms := secretsharing.Commit(g, coeffs) + shares := make([]*KeyShare, max) + for i, k := range privateKeyShares { + shares[i] = &KeyShare{ + Secret: k.Secret, + PublicKey: g.Base().Multiply(k.Secret), + ID: k.ID, + } + } - return privateKeyShares, coms[0], coms, nil + return shares, coms[0], coms, nil } func derivePublicPoint(g group.Group, coms secretsharing.Commitment, i *group.Scalar) *group.Element { @@ -209,7 +201,7 @@ func derivePublicPoint(g group.Group, coms secretsharing.Commitment, i *group.Sc } // VerifyVSS allows verification of a participant's secret share given a VSS commitment to the secret polynomial. -func VerifyVSS(g group.Group, share *secretsharing.KeyShare, coms secretsharing.Commitment) bool { - pk := g.Base().Multiply(share.SecretKey) - return secretsharing.Verify(g, share.Identifier, pk, coms) +func VerifyVSS(g group.Group, share secretsharing.KeyShare, coms secretsharing.Commitment) bool { + pk := g.Base().Multiply(share.SecretKey()) + return secretsharing.Verify(g, share.Identifier(), pk, coms) } diff --git a/go.mod b/go.mod index 4580564..8ea98c6 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,20 @@ module github.com/bytemare/frost -go 1.21 +go 1.22.3 require ( - filippo.io/edwards25519 v1.0.0 - github.com/bytemare/crypto v0.5.2 - github.com/bytemare/hash v0.1.5 - github.com/bytemare/secret-sharing v0.1.0 + filippo.io/edwards25519 v1.1.0 + github.com/bytemare/crypto v0.7.5 + github.com/bytemare/dkg v0.0.0-20240630203912-c5098d51de1b + github.com/bytemare/hash v0.3.0 + github.com/bytemare/secret-sharing v0.1.4 github.com/gtank/ristretto255 v0.1.2 ) require ( - filippo.io/nistec v0.0.2 // indirect - github.com/bytemare/hash2curve v0.2.2 // indirect - github.com/bytemare/secp256k1 v0.1.0 // indirect - golang.org/x/crypto v0.8.0 // indirect - golang.org/x/sys v0.7.0 // indirect + filippo.io/nistec v0.0.3 // indirect + github.com/bytemare/hash2curve v0.3.0 // indirect + github.com/bytemare/secp256k1 v0.1.4 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/sys v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index 696a2d3..bb573ce 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,22 @@ -filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= -filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= -filippo.io/nistec v0.0.2 h1:/NIXTUimcHIh0E2DsYucHlICvUisgj28/XEnKSEptUs= -filippo.io/nistec v0.0.2/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw= -github.com/bytemare/crypto v0.5.2 h1:ogvfY5mmtrPc5Uhwq4mUEUDnTVig+UEF8gwnNAPaNbU= -github.com/bytemare/crypto v0.5.2/go.mod h1:kkx4ciRQFWcjMauezZo9SHw4YmqSTolWkfOVVTOXgAY= -github.com/bytemare/hash v0.1.5 h1:VW+X1YQ2b3chjRFHkRUnO42uclsQjXimdBCPOgIobR4= -github.com/bytemare/hash v0.1.5/go.mod h1:+QmWXTky/2b63ngqM5IYezGydn9UTFDhpX7mLYwYxCA= -github.com/bytemare/hash2curve v0.2.2 h1:zaGx6Z4/N4Pl9B7aGNtpbZ09vu1NNJGoJRRtHHl8oTw= -github.com/bytemare/hash2curve v0.2.2/go.mod h1:Wma3DmJdn8kqiK9j120hkWvC3tQVKS1PyA8ZzyG23BI= -github.com/bytemare/secp256k1 v0.1.0 h1:kjVJ06GAHSa+EJ7Rz1LdVgE0DQWdvUT77tmcGf7epXQ= -github.com/bytemare/secp256k1 v0.1.0/go.mod h1:hzquMsr3GXhVcqL9qFX7GGjmcT5dlQldKrArd7tcXHE= -github.com/bytemare/secret-sharing v0.1.0 h1:p+5TQw40/JnSMUgSe0OdvUACo9xZuiQDL/eLRPjPd50= -github.com/bytemare/secret-sharing v0.1.0/go.mod h1:NKTAXEhsxrVi2Yr5BCKFP9uhPWDtBStAPbyqLghFG4I= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +filippo.io/nistec v0.0.3 h1:h336Je2jRDZdBCLy2fLDUd9E2unG32JLwcJi0JQE9Cw= +filippo.io/nistec v0.0.3/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw= +github.com/bytemare/crypto v0.7.5 h1:aRZzSmRZFlPt4ydpI5KKr+UQbzNd1559mNnRj9tNjRw= +github.com/bytemare/crypto v0.7.5/go.mod h1:qRA6Tdg0Q9zMTuxeKkyVtEyDAgIuwM0YN5tffzFQJQw= +github.com/bytemare/dkg v0.0.0-20240630203912-c5098d51de1b h1:k6kOdWLiWH8xRf1m1a8vdwF7TynVJf1eyxeIliv0ko4= +github.com/bytemare/dkg v0.0.0-20240630203912-c5098d51de1b/go.mod h1:Lp2TnIUKKHtzzjhbq3xK8/svOmgM+Tbn2WluC9uc85U= +github.com/bytemare/hash v0.3.0 h1:RqFMt3mqpF7UxLdjBrsOZm/2cz0cQiAOnYc9gDLopWE= +github.com/bytemare/hash v0.3.0/go.mod h1:YKOBchL0l8hRLFinVCL8YUKokGNIMhrWEHPHo3EV7/M= +github.com/bytemare/hash2curve v0.3.0 h1:41Npcbc+u/E252A5aCMtxDcz7JPkkX1QzShneTFm4eg= +github.com/bytemare/hash2curve v0.3.0/go.mod h1:itj45U8uqvCtWC0eCswIHVHswXcEHkpFui7gfJdPSfQ= +github.com/bytemare/secp256k1 v0.1.4 h1:6F1yP6RiUiWwH7AsGHsHktmHm24QcetdDcc39roBd2M= +github.com/bytemare/secp256k1 v0.1.4/go.mod h1:Pxb9miDs8PTt5mOktvvXiRflvLxI1wdxbXrc6IYsaho= +github.com/bytemare/secret-sharing v0.1.4 h1:+qEmeOgKq16P8SX1oWGkTit0WBa+5uDpG46EA/8+kXw= +github.com/bytemare/secret-sharing v0.1.4/go.mod h1:kZ8Ty314nPP1LLd9ZsAAoc77625CEvXzRtimtEE1M9I= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= -golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/binding.go b/internal/binding.go index bf51bcb..2fd1246 100644 --- a/internal/binding.go +++ b/internal/binding.go @@ -8,11 +8,13 @@ package internal -import group "github.com/bytemare/crypto" +import ( + group "github.com/bytemare/crypto" +) // BindingFactor holds the binding factor scalar for the given identifier. type BindingFactor struct { - Identifier *group.Scalar + Identifier uint64 BindingFactor *group.Scalar } @@ -20,9 +22,9 @@ type BindingFactor struct { type BindingFactorList []*BindingFactor // BindingFactorForParticipant returns the binding factor for a given participant identifier in the list. -func (b BindingFactorList) BindingFactorForParticipant(id *group.Scalar) *group.Scalar { +func (b BindingFactorList) BindingFactorForParticipant(id uint64) *group.Scalar { for _, bf := range b { - if id.Equal(bf.Identifier) == 1 { + if id == bf.Identifier { return bf.BindingFactor } } diff --git a/internal/ciphersuite.go b/internal/ciphersuite.go index 7784e5d..dc50ee3 100644 --- a/internal/ciphersuite.go +++ b/internal/ciphersuite.go @@ -18,7 +18,7 @@ import ( // Ciphersuite combines the group and hashing routines. type Ciphersuite struct { ContextString []byte - Hash hash.Hashing + Hash hash.Hash Group group.Group } @@ -69,7 +69,7 @@ func (c Ciphersuite) H1(input []byte) *group.Scalar { // H2 hashes the input and proves the "chal" DST. func (c Ciphersuite) H2(input []byte) *group.Scalar { if c.Group == group.Edwards25519Sha512 { - // For compatibility with RFC8032 H2 doesn't use a domain separator. + // For compatibility with RFC8032 H2 doesn't use a domain separator for Edwards25519. return c.h1Ed25519(input) } @@ -90,8 +90,3 @@ func (c Ciphersuite) H4(msg []byte) []byte { func (c Ciphersuite) H5(msg []byte) []byte { return c.Hash.Hash(c.ContextString, []byte("com"), msg) } - -// HDKG hashes the input to the "dkg" DST. -func (c Ciphersuite) HDKG(msg []byte) *group.Scalar { - return c.hx(msg, []byte("dkg")) -} diff --git a/internal/utils.go b/internal/utils.go index 13e08c8..89f38f9 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -11,11 +11,9 @@ package internal import ( cryptorand "crypto/rand" + "encoding/binary" "errors" "fmt" - "math/big" - - group "github.com/bytemare/crypto" ) var ( @@ -64,12 +62,15 @@ func RandomBytes(length int) []byte { return r } -// IntegerToScalar creates a group.Scalar given an int. -func IntegerToScalar(g group.Group, i int) *group.Scalar { - s := g.NewScalar() - if err := s.SetInt(big.NewInt(int64(i))); err != nil { - panic(err) - } +// UInt64LE returns the 8 byte little endian byte encoding of i. +func UInt64LE(i uint64) []byte { + out := [8]byte{} + binary.LittleEndian.PutUint64(out[:], i) + + return out[:] +} - return s +// UInt64FromLE returns the uint64 representation of the first 8 input bytes +func UInt64FromLE(b []byte) uint64 { + return binary.LittleEndian.Uint64(b) } diff --git a/participant.go b/participant.go index d1851e1..46d69a7 100644 --- a/participant.go +++ b/participant.go @@ -13,14 +13,35 @@ import ( "fmt" group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" - "github.com/bytemare/frost/internal" ) +// KeyShare identifies the sharded key share for a given participant. +type KeyShare struct { + // The Secret of a participant (or secret share). + Secret *group.Scalar + + // The PublicKey of Secret belonging to the participant. + PublicKey *group.Element + + // ID of the participant. + ID uint64 +} + +// Identifier returns the identity for this share. +func (s KeyShare) Identifier() uint64 { + return s.ID +} + +// SecretKey returns the participant's secret share. +func (s KeyShare) SecretKey() *group.Scalar { + return s.Secret +} + // Participant is a signer of a group. type Participant struct { - ParticipantInfo + KeyShare *KeyShare + Lambda *group.Scalar // lamba can be computed once and reused across FROST signing operations Nonce [2]*group.Scalar HidingRandom []byte BindingRandom []byte @@ -29,13 +50,6 @@ type Participant struct { var errDecodeSignatureShare = errors.New("failed to decode signature share: invalid length") -// ParticipantInfo holds the participant specific long-term values. -type ParticipantInfo struct { - KeyShare *secretsharing.KeyShare - Lambda *group.Scalar // lamba can be computed once and reused across FROST signing operations - PublicKey *group.Element -} - func (p *Participant) generateNonce(s *group.Scalar, random []byte) *group.Scalar { if random == nil { random = internal.RandomBytes(32) @@ -48,9 +62,9 @@ func (p *Participant) generateNonce(s *group.Scalar, random []byte) *group.Scala // Backup serializes the client with its long term values, containing its secret share. func (p *Participant) Backup() []byte { - return internal.Concatenate(p.ParticipantInfo.KeyShare.Identifier.Encode(), - p.ParticipantInfo.KeyShare.SecretKey.Encode(), - p.ParticipantInfo.Lambda.Encode()) + return internal.Concatenate(internal.UInt64LE(p.KeyShare.ID), + p.KeyShare.Secret.Encode(), + p.Lambda.Encode()) } // RecoverParticipant attempts to deserialize the encoded backup into a Participant. @@ -62,17 +76,14 @@ func RecoverParticipant(c Ciphersuite, backup []byte) (*Participant, error) { conf := c.Configuration() sLen := conf.Ciphersuite.Group.ScalarLength() - if len(backup) != 3*sLen { + if len(backup) != 8+2*sLen { return nil, internal.ErrInvalidParticipantBackup } - id := conf.Ciphersuite.Group.NewScalar() - if err := id.Decode(backup[:sLen]); err != nil { - return nil, fmt.Errorf("decoding identity: %w", err) - } + id := internal.UInt64FromLE(backup[:8]) - share := conf.Ciphersuite.Group.NewScalar() - if err := share.Decode(backup[sLen : 2*sLen]); err != nil { + secret := conf.Ciphersuite.Group.NewScalar() + if err := secret.Decode(backup[sLen : 2*sLen]); err != nil { return nil, fmt.Errorf("decoding key share: %w", err) } @@ -81,56 +92,78 @@ func RecoverParticipant(c Ciphersuite, backup []byte) (*Participant, error) { return nil, fmt.Errorf("decoding lambda: %w", err) } - p := conf.Participant(id, share) + keyShare := &KeyShare{ + Secret: secret, + PublicKey: conf.Ciphersuite.Group.Base().Multiply(secret), + ID: id, + } + + p := conf.Participant(keyShare) p.Lambda = lambda - p.PublicKey = conf.Ciphersuite.Group.Base().Multiply(share) return p, nil } -// Commit generates a participants nonce and commitment, to be used in the second FROST round. The nonce must be kept -// secret, and the commitment sent to the coordinator. +// Commit generates a participants nonce and commitment, to be used in the second FROST round. The internal nonce must +// be kept secret, and the returned commitment sent to the signature aggregator. func (p *Participant) Commit() *Commitment { - p.Nonce[0] = p.generateNonce(p.ParticipantInfo.KeyShare.SecretKey, p.HidingRandom) - p.Nonce[1] = p.generateNonce(p.ParticipantInfo.KeyShare.SecretKey, p.BindingRandom) + p.Nonce[0] = p.generateNonce(p.KeyShare.Secret, p.HidingRandom) + p.Nonce[1] = p.generateNonce(p.KeyShare.Secret, p.BindingRandom) return &Commitment{ - Identifier: p.ParticipantInfo.KeyShare.Identifier.Copy(), + Identifier: p.KeyShare.ID, + PublicKey: p.KeyShare.PublicKey, HidingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonce[0]), BindingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonce[1]), } } +func computeLambda(g group.Group, commitments CommitmentList, id uint64) (*group.Scalar, error) { + participantList := commitments.Participants(g) + return participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) +} + +func (c Configuration) do(message []byte, commitments CommitmentList, id uint64) (*group.Scalar, *group.Scalar, *group.Scalar, error) { + if !commitments.IsSorted() { + commitments.Sort() + } + + // Compute the interpolating value + lambda, err := computeLambda(c.Ciphersuite.Group, commitments, id) + if err != nil { + return nil, nil, nil, err + } + + // Compute the binding factor(s) + bindingFactorList := c.computeBindingFactors(commitments, c.GroupPublicKey.Encode(), message) + bindingFactor := bindingFactorList.BindingFactorForParticipant(id) + + // Compute group commitment + groupCommitment := c.computeGroupCommitment(commitments, bindingFactorList) + + // Compute per message challenge + chall := challenge(c.Ciphersuite, groupCommitment, c.GroupPublicKey, message) + + return bindingFactor, lambda, chall.Multiply(lambda), nil +} + // Sign produces a participant's signature share of the message msg. // // Each participant MUST validate the inputs before processing the Coordinator's request. // In particular, the Signer MUST validate commitment_list, deserializing each group Element in the list using // DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, // each participant MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. -func (p *Participant) Sign(msg []byte, list CommitmentList) (*SignatureShare, error) { - // Compute the binding factor(s) - bindingFactorList := p.computeBindingFactors(list, msg) - bindingFactor := bindingFactorList.BindingFactorForParticipant(p.KeyShare.Identifier) - - // Compute group commitment - groupCommitment := p.computeGroupCommitment(list, bindingFactorList) - - // Compute the interpolating value - participantList := secretsharing.Polynomial(list.Participants()) - - lambdaID, err := participantList.DeriveInterpolatingValue(p.Ciphersuite.Group, p.KeyShare.Identifier) +func (p *Participant) Sign(msg []byte, coms CommitmentList) (*SignatureShare, error) { + bindingFactor, lambda, lambdaChall, err := p.do(msg, coms, p.KeyShare.ID) if err != nil { return nil, err } - p.Lambda = lambdaID.Copy() - - // Compute per message challenge - challenge := challenge(p.Ciphersuite, groupCommitment, p.Configuration.GroupPublicKey, msg) + p.Lambda = lambda.Copy() // Compute the signature share sigShare := p.Nonce[0].Add( - p.Nonce[1].Multiply(bindingFactor).Add(lambdaID.Multiply(p.KeyShare.SecretKey).Multiply(challenge)), + p.Nonce[1].Multiply(bindingFactor).Add(lambdaChall.Multiply(p.KeyShare.Secret)), ).Copy() // Clean up values @@ -138,26 +171,26 @@ func (p *Participant) Sign(msg []byte, list CommitmentList) (*SignatureShare, er p.Nonce[1].Zero() return &SignatureShare{ - Identifier: p.ParticipantInfo.KeyShare.Identifier.Copy(), + Identifier: p.KeyShare.ID, SignatureShare: sigShare, }, nil } // computeBindingFactors computes binding factors based on the participant commitment list and the message to be signed. -func (p *Participant) computeBindingFactors(l CommitmentList, message []byte) internal.BindingFactorList { +func (c Configuration) computeBindingFactors(l CommitmentList, pubkey, message []byte) internal.BindingFactorList { if !l.IsSorted() { panic(nil) } - h := p.Configuration.Ciphersuite.H4(message) - encodedCommitHash := p.Configuration.Ciphersuite.H5(l.Encode()) - rhoInputPrefix := internal.Concatenate(p.GroupPublicKey.Encode(), h, encodedCommitHash) + h := c.Ciphersuite.H4(message) + encodedCommitHash := c.Ciphersuite.H5(l.Encode()) + rhoInputPrefix := internal.Concatenate(pubkey, h, encodedCommitHash) bindingFactorList := make(internal.BindingFactorList, len(l)) for i, commitment := range l { - rhoInput := internal.Concatenate(rhoInputPrefix, commitment.Identifier.Encode()) - bindingFactor := p.Configuration.Ciphersuite.H1(rhoInput) + rhoInput := internal.Concatenate(rhoInputPrefix, internal.UInt64LE(commitment.Identifier)) + bindingFactor := c.Ciphersuite.H1(rhoInput) bindingFactorList[i] = &internal.BindingFactor{ Identifier: commitment.Identifier, @@ -169,12 +202,12 @@ func (p *Participant) computeBindingFactors(l CommitmentList, message []byte) in } // computeGroupCommitment creates the group commitment from a commitment list. -func (p *Participant) computeGroupCommitment(l CommitmentList, list internal.BindingFactorList) *group.Element { +func (c Configuration) computeGroupCommitment(l CommitmentList, list internal.BindingFactorList) *group.Element { if !l.IsSorted() { panic(nil) } - gc := p.Configuration.Ciphersuite.Group.NewElement().Identity() + gc := c.Ciphersuite.Group.NewElement().Identity() for _, commitment := range l { if commitment.HidingNonce.IsIdentity() || commitment.BindingNonce.IsIdentity() { @@ -191,18 +224,17 @@ func (p *Participant) computeGroupCommitment(l CommitmentList, list internal.Bin // SignatureShare represents a participants signature share, specifying which participant it was produced by. type SignatureShare struct { - Identifier *group.Scalar + Identifier uint64 SignatureShare *group.Scalar } // Encode returns a compact byte encoding of the signature share. func (s SignatureShare) Encode() []byte { - id := s.Identifier.Encode() share := s.SignatureShare.Encode() - out := make([]byte, len(id)+len(share)) - copy(out, id) - copy(out[len(id):], share) + out := make([]byte, 8+len(share)) + copy(out, internal.UInt64LE(s.Identifier)) + copy(out[8:], share) return out } @@ -210,22 +242,17 @@ func (s SignatureShare) Encode() []byte { // DecodeSignatureShare takes a byte string and attempts to decode it to return the signature share. func (c Configuration) DecodeSignatureShare(data []byte) (*SignatureShare, error) { g := c.Ciphersuite.Group - scalarLength := g.ScalarLength() - if len(data) != 2*scalarLength { + if len(data) != 8+g.ScalarLength() { return nil, errDecodeSignatureShare } s := &SignatureShare{ - Identifier: g.NewScalar(), + Identifier: internal.UInt64FromLE(data[:8]), SignatureShare: g.NewScalar(), } - if err := s.Identifier.Decode(data[:scalarLength]); err != nil { - return nil, fmt.Errorf("failed to decode signature share identifier: %w", err) - } - - if err := s.SignatureShare.Decode(data[scalarLength:]); err != nil { + if err := s.SignatureShare.Decode(data[8:]); err != nil { return nil, fmt.Errorf("failed to decode signature share: %w", err) } diff --git a/schnorr.go b/schnorr.go index 5a51af3..3e8f5cc 100644 --- a/schnorr.go +++ b/schnorr.go @@ -10,8 +10,6 @@ package frost import ( "fmt" - "math/big" - group "github.com/bytemare/crypto" "github.com/bytemare/frost/internal" @@ -58,12 +56,13 @@ func computeZ(r, challenge, key *group.Scalar) *group.Scalar { } // Sign returns a Schnorr signature over the message msg with the full secret signing key (as opposed to a key share). -func Sign(cs internal.Ciphersuite, msg []byte, key *group.Scalar) *Signature { - r := cs.Group.NewScalar().Random() - R := cs.Group.Base().Multiply(r) - pk := cs.Group.Base().Multiply(key) - c := challenge(cs, R, pk, msg) - z := computeZ(r, c, key) +func (c Configuration) Sign(msg []byte, key *group.Scalar) *Signature { + g := c.Ciphersuite.Group + r := g.NewScalar().Random() + R := g.Base().Multiply(r) + pk := g.Base().Multiply(key) + ch := challenge(c.Ciphersuite, R, pk, msg) + z := computeZ(r, ch, key) return &Signature{ R: R, @@ -71,18 +70,14 @@ func Sign(cs internal.Ciphersuite, msg []byte, key *group.Scalar) *Signature { } } -// Verify returns whether the signature of the message msg is valid under the public key pk. -func Verify(cs internal.Ciphersuite, msg []byte, signature *Signature, pk *group.Element) bool { - c := challenge(cs, signature.R, pk, msg) - l := cs.Group.Base().Multiply(signature.Z) - r := signature.R.Add(pk.Copy().Multiply(c)) - - if cs.Group == group.Edwards25519Sha512 { - cofactor := group.Edwards25519Sha512.NewScalar() - if err := cofactor.SetInt(big.NewInt(8)); err != nil { - panic(err) - } +// VerifySignature returns whether the signature of the message msg is valid under the public key pk. +func (c Configuration) VerifySignature(msg []byte, signature *Signature) bool { + ch := challenge(c.Ciphersuite, signature.R, c.GroupPublicKey, msg) + l := c.Ciphersuite.Group.Base().Multiply(signature.Z) + r := signature.R.Add(c.GroupPublicKey.Copy().Multiply(ch)) + if c.Ciphersuite.Group == group.Edwards25519Sha512 { + cofactor := group.Edwards25519Sha512.NewScalar().SetUInt64(8) return l.Multiply(cofactor).Equal(r.Multiply(cofactor)) == 1 } diff --git a/tests/dkg_test.go b/tests/dkg_test.go index 2a97fb9..452a010 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -1,230 +1,102 @@ -// 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 frost_test import ( - "testing" - + "fmt" group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" - + "github.com/bytemare/dkg" "github.com/bytemare/frost" - "github.com/bytemare/frost/dkg" - "github.com/bytemare/frost/internal" + "testing" ) -// testUnit holds a participant and its return and input values during the protocol. -type testUnit struct { - participant *dkg.Participant - r1Data *dkg.Round1Data - secret *group.Scalar - verificationShare *group.Element - publicKey *group.Element - r2OutputData []*dkg.Round2Data - r2InputData []*dkg.Round2Data -} - -// TestDKG verifies -// - execution of the protocol with any number of participants and threshold, and no errors. -// - the correctness of each verification share. -// - the correctness of the group public key. -// - the correctness of the secret key recovery with regard to the public key. -func TestDKG(t *testing.T) { - conf := frost.Ristretto255.Configuration() - g := conf.Ciphersuite.Group - maxSigners := 5 - quals := []int{1, 3, 5} // = threshold - - var err error - - // Vector of participant units. - units := make([]*testUnit, maxSigners) - for i := 0; i < maxSigners; i++ { - id := internal.IntegerToScalar(conf.Ciphersuite.Group, i+1) - units[i] = &testUnit{ - participant: dkg.NewParticipant(conf.Ciphersuite, id, maxSigners, len(quals)), - r2InputData: make([]*dkg.Round2Data, 0, maxSigners-1), - } - } - - // Step 1: Init. - for _, unit := range units { - unit.r1Data = unit.participant.Init() - } - - // Step 2: assemble packages. - r1Data := make([]*dkg.Round1Data, maxSigners) - for i, unit := range units { - r1Data[i] = unit.r1Data - } - - // Step 3: Continue. - for _, unit := range units { - unit.r2OutputData, err = unit.participant.Continue(r1Data) +func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, threshold int) []*dkg.Participant { + ps := make([]*dkg.Participant, 0, maxSigners) + for i := range uint64(maxSigners) { + p, err := ciphersuite.NewParticipant(i+1, uint(maxSigners), uint(threshold)) if err != nil { t.Fatal(err) } - } - // Step 4: assemble packages. - for i, uniti := range units { - for j, unitj := range units { - if i == j { - continue - } - - for _, p := range unitj.r2OutputData { - if p.ReceiverIdentifier.Equal(uniti.participant.Identifier) == 1 { - uniti.r2InputData = append(uniti.r2InputData, p) - break - } - } - } + ps = append(ps, p) } - // Step 5: Finalize. - for _, unit := range units { - unit.secret, unit.verificationShare, unit.publicKey, err = unit.participant.Finalize( - r1Data, - unit.r2InputData, - ) - if err != nil { - t.Fatal(err) - } - } + return ps +} - // Verify individual verification shares. - for _, unit := range units { - verifPk := dkg.ComputeVerificationShare(g, unit.participant.Identifier, r1Data) - if verifPk.Equal(unit.verificationShare) != 1 { - t.Fatal("invalid verification key") - } - } +func simulateDKG(t *testing.T, g group.Group, maxSigners, threshold int) ([]*frost.KeyShare, *group.Element) { + c := dkg.Ciphersuite(g) - // Compare group public keys. - p1g := units[0].publicKey - for _, unit := range units[1:] { - if p1g.Equal(unit.publicKey) != 1 { - t.Fatal("expected equality") - } - } + // valid r1DataSet set with and without own package + participants := dkgMakeParticipants(t, c, maxSigners, threshold) + r1 := make([]*dkg.Round1Data, maxSigners) - // Verify the individual secret shares by combining a subset of them. - keyShares := make([]*secretsharing.KeyShare, len(quals)) - for i, ii := range quals { - id := internal.IntegerToScalar(conf.Ciphersuite.Group, ii) - - for _, unit := range units { - if id.Equal(unit.participant.Identifier) == 1 { - keyShares[i] = &secretsharing.KeyShare{ - Identifier: unit.participant.Identifier, - SecretKey: unit.secret, - } - } - } + // Step 1: Start and assemble packages. + for i := range maxSigners { + r1[i] = participants[i].Start() } - secret, err := secretsharing.Combine(g, uint(len(quals)), keyShares) + pubKey, err := dkg.GroupPublicKey(c, r1) if err != nil { t.Fatal(err) } - pk := g.Base().Multiply(secret) - if pk.Equal(p1g) != 1 { - t.Fatal("expected recovered secret to be compatible with public key") - } -} - -// TestDKG_InvalidPOK verifies whether an invalid signature is detected and an error is returned. -func TestDKG_InvalidPOK(t *testing.T) { - conf := frost.Ristretto255.Configuration() - g := conf.Ciphersuite.Group - - maxSigners := 2 - threshold := 1 - - one := g.NewScalar().One() - p1 := dkg.NewParticipant(conf.Ciphersuite, one, maxSigners, threshold) - - two := g.NewScalar().One().Add(g.NewScalar().One()) - p2 := dkg.NewParticipant(conf.Ciphersuite, two, maxSigners, threshold) - - r1P1 := p1.Init() - r1P2 := p2.Init() - - r1P2.ProofOfKnowledge.Z = g.NewScalar().Random() - - r1Data := []*dkg.Round1Data{r1P1, r1P2} - - if _, err := p1.Continue(r1Data); err == nil { - t.Fatal("expected error on invalid signature") - } -} + // Step 2: Continue and assemble + triage packages. + r2 := make(map[uint64][]*dkg.Round2Data, maxSigners) + for i := range maxSigners { + r, err := participants[i].Continue(r1) + if err != nil { + t.Fatal(err) + } -// SimulateDKG generates sharded keys for maxSigners participant without a trusted dealer, and returns these shares -// and the group's public key. This function is used in tests and examples. -func SimulateDKG( - conf *frost.Configuration, - maxSigners, threshold int, -) ([]*secretsharing.KeyShare, []*group.Element, *group.Element) { - g := conf.Ciphersuite.Group - - // Create participants. - participants := make([]*dkg.Participant, maxSigners) - for i := 0; i < maxSigners; i++ { - id := internal.IntegerToScalar(conf.Ciphersuite.Group, i+1) - participants[i] = dkg.NewParticipant(conf.Ciphersuite, id, maxSigners, threshold) + for id, data := range r { + if r2[id] == nil { + r2[id] = make([]*dkg.Round2Data, 0, maxSigners-1) + } + r2[id] = append(r2[id], data) + } } - // Step 1 & 2. - r1Data := make([]*dkg.Round1Data, maxSigners) - for i, p := range participants { - r1Data[i] = p.Init() + // Step 3: Clean the proofs. + // This must be called by each participant on their copy of the r1DataSet. + for _, d := range r1 { + d.ProofOfKnowledge.Clear() } - // Step 3 & 4. - r2Data := make(map[string][]*dkg.Round2Data) - for _, p := range participants { - id := string(p.Identifier.Encode()) - r2Data[id] = make([]*dkg.Round2Data, 0, maxSigners-1) - } + // Step 4: Finalize and test outputs. + keyShares := make([]*frost.KeyShare, 0, maxSigners) for _, p := range participants { - r2DataI, err := p.Continue(r1Data) + keyShare, gpk, err := p.Finalize(r1, r2[p.Identifier]) if err != nil { - panic(err) + t.Fatal() + } + + if gpk.Equal(pubKey) != 1 { + t.Fatalf("expected same public key") } - for _, r2d := range r2DataI { - id := string(r2d.ReceiverIdentifier.Encode()) - r2Data[id] = append(r2Data[id], r2d) + if keyShare.PublicKey.Equal(g.Base().Multiply(keyShare.SecretKey)) != 1 { + t.Fatal("expected equality") + } + + if err := dkg.VerifyPublicKey(c, p.Identifier, keyShare.PublicKey, r1); err != nil { + t.Fatal(err) } + + keyShares = append(keyShares, &frost.KeyShare{ + ID: keyShare.Identifier, + Secret: keyShare.SecretKey, + PublicKey: keyShare.PublicKey, + }) } - // Step 5. - secretShares := make([]*secretsharing.KeyShare, maxSigners) - publicShares := make([]*group.Element, maxSigners) - groupPublicKey := g.NewElement() - for i, p := range participants { - id := string(p.Identifier.Encode()) - secret, public, pk, err := p.Finalize(r1Data, r2Data[id]) + { + groupSecretKey, err := frost.Ciphersuite(g).Configuration(pubKey).RecoverGroupSecret(keyShares) if err != nil { - panic(err) + t.Fatal(err) } - secretShares[i] = &secretsharing.KeyShare{ - Identifier: p.Identifier, - SecretKey: secret, - } - publicShares[i] = public - groupPublicKey = pk + fmt.Println(groupSecretKey.Hex()) } - return secretShares, publicShares, groupPublicKey + return keyShares, pubKey } diff --git a/tests/frost_test.go b/tests/frost_test.go index 9887495..0549c0f 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -9,14 +9,13 @@ package frost_test import ( + "fmt" "testing" group "github.com/bytemare/crypto" - "github.com/bytemare/hash" - secretsharing "github.com/bytemare/secret-sharing" - "github.com/bytemare/frost" "github.com/bytemare/frost/internal" + "github.com/bytemare/hash" ) var configurationTable = []frost.Configuration{ @@ -55,29 +54,29 @@ var configurationTable = []frost.Configuration{ } func TestTrustedDealerKeygen(t *testing.T) { - min := 2 - max := 3 + threshold := 3 + maxSigners := 5 testAll(t, func(t2 *testing.T, configuration *frost.Configuration) { g := configuration.Ciphersuite.Group groupSecretKey := g.NewScalar().Random() - privateKeyShares, dealerGroupPubKey, secretsharingCommitment, err := frost.TrustedDealerKeygen( + keyShares, dealerGroupPubKey, secretsharingCommitment, err := frost.TrustedDealerKeygen( g, groupSecretKey, - max, - min, + maxSigners, + threshold, ) if err != nil { t.Fatal(err) } - if len(secretsharingCommitment) != min { - t2.Fatalf("%d / %d", len(secretsharingCommitment), min) + if len(secretsharingCommitment) != threshold { + t2.Fatalf("%d / %d", len(secretsharingCommitment), threshold) } - recoveredKey, err := secretsharing.Combine(g, uint(min), privateKeyShares) + recoveredKey, err := configuration.RecoverGroupSecret(keyShares[:threshold]) if err != nil { t.Fatal(err) } @@ -86,8 +85,8 @@ func TestTrustedDealerKeygen(t *testing.T) { t.Fatal() } - groupPublicKey, participantPublicKeys := frost.DeriveGroupInfo(g, max, secretsharingCommitment) - if len(participantPublicKeys) != max { + groupPublicKey, participantPublicKeys := frost.DeriveGroupInfo(g, maxSigners, secretsharingCommitment) + if len(participantPublicKeys) != maxSigners { t2.Fatal() } @@ -97,7 +96,7 @@ func TestTrustedDealerKeygen(t *testing.T) { configuration.GroupPublicKey = dealerGroupPubKey - for i, shareI := range privateKeyShares { + for i, shareI := range keyShares { if !frost.VerifyVSS(g, shareI, secretsharingCommitment) { t2.Fatal(i) } @@ -117,65 +116,56 @@ func TestTrustedDealerKeygen(t *testing.T) { } func TestFrost(t *testing.T) { - max := 3 + maxSigner := 3 threshold := 2 - participantListInt := []int{1, 3} message := []byte("test") testAll(t, func(t2 *testing.T, configuration *frost.Configuration) { g := configuration.Ciphersuite.Group - privateKeyShares, _, groupPublicKey := SimulateDKG(configuration, max, threshold) + keyShares, groupPublicKey := simulateDKG(t, g, maxSigner, threshold) configuration.GroupPublicKey = groupPublicKey // Create Participants - participants := make(ParticipantList, max) - for i, share := range privateKeyShares { - participants[i] = configuration.Participant(share.Identifier, share.SecretKey) - } - - signatureAggregator := &frost.Participant{ - Configuration: *configuration, + participants := make(ParticipantList, threshold) + for i, share := range keyShares[:threshold] { + participants[i] = configuration.Participant(share) } // Round One: Commitment - participantList := make([]*group.Scalar, len(participantListInt)) - for i, p := range participantListInt { - participantList[i] = internal.IntegerToScalar(g, p) - } - - comList := make(frost.CommitmentList, len(participantList)) - for i, id := range participantList { - p := participants.Get(id) - comList[i] = p.Commit() + commitments := make(frost.CommitmentList, threshold) + for i, p := range participants { + commitments[i] = p.Commit() } - comList.Sort() + commitments.Sort() // Round Two: Sign - sigShares := make([]*frost.SignatureShare, len(participantList)) - for i, id := range participantList { - p := participants.Get(id) - - sigShare, err := p.Sign(message, comList) + sigShares := make([]*frost.SignatureShare, threshold) + for i, p := range participants { + var err error + sigShares[i], err = p.Sign(message, commitments) if err != nil { t.Fatal(err) } - - sigShares[i] = sigShare } // Final step: aggregate - _ = signatureAggregator.Aggregate(comList, message, sigShares) + signature := configuration.AggregateSignatures(message, sigShares, commitments) + if !configuration.VerifySignature(message, signature) { + t2.Fatal() + } // Sanity Check - groupSecretKey, err := secretsharing.Combine(g, uint(threshold), privateKeyShares) + groupSecretKey, err := configuration.RecoverGroupSecret(keyShares) if err != nil { t.Fatal(err) } - singleSig := frost.Sign(configuration.Ciphersuite, message, groupSecretKey) - if !frost.Verify(configuration.Ciphersuite, message, singleSig, groupPublicKey) { + fmt.Println(groupSecretKey.Hex()) + + singleSig := configuration.Sign(message, groupSecretKey) + if !configuration.VerifySignature(message, singleSig) { t2.Fatal() } }) @@ -188,3 +178,7 @@ func testAll(t *testing.T, f func(*testing.T, *frost.Configuration)) { }) } } + +func TestMaliciousSigner(t *testing.T) { + +} diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index e6d3ebd..ac3de42 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -16,17 +16,14 @@ import ( "testing" group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" - "github.com/bytemare/frost" - "github.com/bytemare/frost/internal" ) type ParticipantList []*frost.Participant -func (p ParticipantList) Get(id *group.Scalar) *frost.Participant { +func (p ParticipantList) Get(id uint64) *frost.Participant { for _, i := range p { - if i.ParticipantInfo.KeyShare.Identifier.Equal(id) == 1 { + if i.KeyShare.ID == id { return i } } @@ -84,7 +81,7 @@ func (j *ByteToHex) UnmarshalJSON(b []byte) error { */ type testVectorInput struct { - ParticipantList []int `json:"participant_list"` + ParticipantList []uint64 `json:"participant_list"` GroupSecretKey ByteToHex `json:"group_secret_key"` GroupPublicKey ByteToHex `json:"group_public_key"` Message ByteToHex `json:"message"` @@ -123,7 +120,7 @@ func (c testVectorConfig) decode(t *testing.T) *testConfig { type testVectorParticipantShare struct { ParticipantShare ByteToHex `json:"participant_share"` - Identifier int `json:"identifier"` + Identifier uint64 `json:"identifier"` } type testParticipant struct { @@ -135,7 +132,7 @@ type testParticipant struct { BindingNonceCommitment ByteToHex `json:"binding_nonce_commitment"` BindingFactorInput ByteToHex `json:"binding_factor_input"` BindingFactor ByteToHex `json:"binding_factor"` - Identifier int `json:"identifier"` + Identifier uint64 `json:"identifier"` } type testVectorRoundOneOutputs struct { @@ -144,7 +141,7 @@ type testVectorRoundOneOutputs struct { type testVectorSigShares struct { SigShare ByteToHex `json:"sig_share"` - Identifier int `json:"identifier"` + Identifier uint64 `json:"identifier"` } type testVectorRoundTwoOutputs struct { @@ -165,12 +162,12 @@ type testConfig struct { } type testInput struct { - ParticipantList []*group.Scalar + ParticipantList []uint64 GroupSecretKey *group.Scalar GroupPublicKey *group.Element Message []byte SharePolynomialCoefficients []*group.Scalar - Participants []*secretsharing.KeyShare + Participants []*frost.KeyShare } type test struct { @@ -182,7 +179,7 @@ type test struct { } type participant struct { - ID *group.Scalar + ID uint64 HidingNonce *group.Scalar BindingNonce *group.Scalar HidingNonceCommitment *group.Element @@ -198,7 +195,7 @@ type testRoundOneOutputs struct { } type testRoundTwoOutputs struct { - Outputs []*secretsharing.KeyShare + Outputs []*frost.SignatureShare } /* @@ -224,7 +221,7 @@ func configToConfiguration(t *testing.T, c *testVectorConfig) *frost.Configurati func decodeParticipant(t *testing.T, g group.Group, tp *testParticipant) *participant { return &participant{ - ID: internal.IntegerToScalar(g, tp.Identifier), + ID: tp.Identifier, HidingNonceRandomness: tp.HidingNonceRandomness, BindingNonceRandomness: tp.BindingNonceRandomness, HidingNonce: decodeScalar(t, g, tp.HidingNonce), @@ -242,12 +239,12 @@ func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { GroupPublicKey: decodeElement(t, g, i.GroupPublicKey), Message: i.Message, SharePolynomialCoefficients: make([]*group.Scalar, len(i.SharePolynomialCoefficients)), - Participants: make([]*secretsharing.KeyShare, len(i.ParticipantShares)), - ParticipantList: make([]*group.Scalar, len(i.ParticipantList)), + Participants: make([]*frost.KeyShare, len(i.ParticipantShares)), + ParticipantList: make([]uint64, len(i.ParticipantList)), } for j, id := range i.ParticipantList { - input.ParticipantList[j] = internal.IntegerToScalar(g, id) + input.ParticipantList[j] = id } for j, coeff := range i.SharePolynomialCoefficients { @@ -255,9 +252,9 @@ func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { } for j, p := range i.ParticipantShares { - input.Participants[j] = &secretsharing.KeyShare{ - Identifier: internal.IntegerToScalar(g, p.Identifier), - SecretKey: decodeScalar(t, g, p.ParticipantShare), + input.Participants[j] = &frost.KeyShare{ + ID: p.Identifier, + Secret: decodeScalar(t, g, p.ParticipantShare), } } @@ -278,13 +275,13 @@ func (o testVectorRoundOneOutputs) decode(t *testing.T, g group.Group) *testRoun func (o testVectorRoundTwoOutputs) decode(t *testing.T, g group.Group) *testRoundTwoOutputs { r := &testRoundTwoOutputs{ - Outputs: make([]*secretsharing.KeyShare, len(o.Outputs)), + Outputs: make([]*frost.SignatureShare, len(o.Outputs)), } for i, p := range o.Outputs { - r.Outputs[i] = &secretsharing.KeyShare{ - Identifier: internal.IntegerToScalar(g, p.Identifier), - SecretKey: decodeScalar(t, g, p.SigShare), + r.Outputs[i] = &frost.SignatureShare{ + Identifier: p.Identifier, + SignatureShare: decodeScalar(t, g, p.SigShare), } } diff --git a/tests/vectors_test.go b/tests/vectors_test.go index 76b3d5f..c78a557 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -14,10 +14,9 @@ import ( "fmt" "os" "path/filepath" + "slices" "testing" - secretsharing "github.com/bytemare/secret-sharing" - "github.com/bytemare/frost" ) @@ -26,7 +25,7 @@ func (v test) test(t *testing.T) { coeffs := v.Inputs.SharePolynomialCoefficients - privateKeyShares, dealerGroupPubKey, secretsharingCommitment, err := frost.TrustedDealerKeygen( + keyShares, dealerGroupPubKey, secretsharingCommitment, err := frost.TrustedDealerKeygen( g, v.Inputs.GroupSecretKey, v.Config.MaxParticipants, @@ -42,10 +41,10 @@ func (v test) test(t *testing.T) { } // Check whether key shares are the same - cpt := len(privateKeyShares) - for _, p := range privateKeyShares { + cpt := len(keyShares) + for _, p := range keyShares { for _, p2 := range v.Inputs.Participants { - if p2.Identifier.Equal(p.Identifier) == 1 { + if p2.Identifier() == p.Identifier() && p2.SecretKey().Equal(p.Secret) == 1 { cpt-- } } @@ -56,7 +55,7 @@ func (v test) test(t *testing.T) { } // Test recovery of the full secret signing key. - recoveredKey, err := secretsharing.Combine(g, uint(v.Config.MinParticipants), privateKeyShares) + recoveredKey, err := v.Config.Configuration.RecoverGroupSecret(keyShares) if err != nil { t.Fatal(err) } @@ -74,18 +73,18 @@ func (v test) test(t *testing.T) { t.Fatal() } - for i, shareI := range privateKeyShares { + for i, shareI := range keyShares { if !frost.VerifyVSS(g, shareI, secretsharingCommitment) { t.Fatal(i) } } // Create participants - participants := make(ParticipantList, len(privateKeyShares)) + participants := make(ParticipantList, len(keyShares)) conf := v.Config conf.GroupPublicKey = groupPublicKey - for i, pks := range privateKeyShares { - participants[i] = conf.Participant(pks.Identifier, pks.SecretKey) + for i, keyShare := range keyShares { + participants[i] = conf.Participant(keyShare) } // Round One: Commitment @@ -98,7 +97,7 @@ func (v test) test(t *testing.T) { var pv *participant for _, pp := range v.RoundOneOutputs.Outputs { - if pp.ID.Equal(pid.ID) == 1 { + if pp.ID == pid.ID { pv = pp } } @@ -139,8 +138,8 @@ func (v test) test(t *testing.T) { // Round two: sign sigShares := make([]*frost.SignatureShare, len(v.RoundTwoOutputs.Outputs)) - for i, pid := range v.RoundTwoOutputs.Outputs { - p := participants.Get(pid.Identifier) + for i, share := range v.RoundTwoOutputs.Outputs { + p := participants.Get(share.Identifier) if p == nil { t.Fatal(i) } @@ -149,22 +148,29 @@ func (v test) test(t *testing.T) { if err != nil { t.Fatal(err) } - } - for i, ks := range v.RoundTwoOutputs.Outputs { - if ks.SecretKey.Equal(sigShares[i].SignatureShare) != 1 { - t.Fatal(i) + j := slices.IndexFunc(commitmentList, func(commitment *frost.Commitment) bool { + return commitment.Identifier == p.KeyShare.Identifier() + }) + + if !conf.Configuration.VerifySignatureShare(commitmentList[j], v.Inputs.Message, sigShares[i], commitmentList) { + t.Fatal() + } + + // Check against vector + if share.SignatureShare.Equal(sigShares[i].SignatureShare) != 1 { + t.Fatalf("%s\n%s\n", share.SignatureShare.Hex(), sigShares[i].SignatureShare.Hex()) } } - // Aggregate - sig := participants[1].Aggregate(commitmentList, v.Inputs.Message, sigShares) + // AggregateSignatures + sig := v.Config.AggregateSignatures(v.Inputs.Message, sigShares, commitmentList) if !bytes.Equal(sig.Encode(), v.FinalOutput) { t.Fatal() } // Sanity Check - if !frost.Verify(conf.Ciphersuite, v.Inputs.Message, sig, groupPublicKey) { + if !conf.VerifySignature(v.Inputs.Message, sig) { t.Fatal() } } From 8d030ec38ec2e6c5f8b1acd25180bc1e3b8f91ee Mon Sep 17 00:00:00 2001 From: Daniel Bourdrez Date: Tue, 30 Jul 2024 00:57:32 +0200 Subject: [PATCH 02/31] various enhancements, updates, optimizations, serde, and dependency upgrades Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 103 +++++++++----- coordinator.go | 40 ++++-- debug/debug.go | 100 ++++++++++++++ encoding.go | 119 ++++++++++++++++ errors.go | 5 + examples_test.go | 271 +++++++++++++------------------------ frost.go | 152 +++++---------------- go.mod | 4 +- go.sum | 8 +- internal/binding.go | 2 +- internal/errors.go | 21 +++ internal/utils.go | 12 -- keys.go | 81 +++++++++++ participant.go | 207 ++++++++++------------------ schnorr.go | 7 +- tests/dkg_test.go | 32 ++--- tests/frost_test.go | 124 +++++++++-------- tests/vector_utils_test.go | 21 ++- tests/vectors_test.go | 102 +++++++------- 19 files changed, 776 insertions(+), 635 deletions(-) create mode 100644 debug/debug.go create mode 100644 encoding.go create mode 100644 errors.go create mode 100644 internal/errors.go create mode 100644 keys.go diff --git a/commitment.go b/commitment.go index 9a7980d..1a0412f 100644 --- a/commitment.go +++ b/commitment.go @@ -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" ) @@ -24,53 +24,81 @@ 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. @@ -78,12 +106,12 @@ 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 } } @@ -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 } } diff --git a/coordinator.go b/coordinator.go index 50d6746..b5b70bb 100644 --- a/coordinator.go +++ b/coordinator.go @@ -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 @@ -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) } @@ -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 } diff --git a/debug/debug.go b/debug/debug.go new file mode 100644 index 0000000..068d8cf --- /dev/null +++ b/debug/debug.go @@ -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( + c frost.Ciphersuite, + secret *group.Scalar, + max, min int, + 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) { + 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 + } + + 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) +} diff --git a/encoding.go b/encoding.go new file mode 100644 index 0000000..b69269a --- /dev/null +++ b/encoding.go @@ -0,0 +1,119 @@ +package frost + +import ( + "encoding/binary" + "fmt" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost/internal" +) + +func noncesEncodedLength(g group.Group, n map[uint64][2]*group.Scalar) int { + nbNonces := len(n) + return nbNonces + nbNonces*2*g.ScalarLength() +} + +// Backup serializes the client with its long term values, containing its secret share. +func (p *Participant) Backup() []byte { + g := p.KeyShare.Group + ks := p.KeyShare.Encode() + nLen := noncesEncodedLength(g, p.Nonces) + out := make([]byte, 1, 1+2+2+g.ScalarLength()+len(ks)+nLen) + out[0] = byte(g) + binary.LittleEndian.PutUint16(out[1:3], uint16(len(ks))) + binary.LittleEndian.PutUint16(out[3:5], uint16(len(p.Nonces))) + out = append(out, ks...) + for id, nonces := range p.Nonces { + out = append(out, internal.Concatenate(internal.UInt64LE(id), nonces[0].Encode(), nonces[1].Encode())...) + } + + return out +} + +// Recover attempts to deserialize the encoded backup data into a Participant. +func (p *Participant) Recover(data []byte) error { + if len(data) < 5 { + return internal.ErrInvalidLength + } + + g := group.Group(data[0]) + if !Ciphersuite(g).Available() { + return internal.ErrInvalidCiphersuite + } + + ksLen := int(binary.LittleEndian.Uint16(data[1:3])) + nN := int(binary.LittleEndian.Uint16(data[3:5])) + nLen := nN + nN*2*g.ScalarLength() + + if len(data) != 1+2+2+g.ScalarLength()+ksLen+nLen { + return internal.ErrInvalidLength + } + + lambda := g.NewScalar() + if err := lambda.Decode(data[5 : 5+g.ScalarLength()]); err != nil { + return fmt.Errorf("failed to decode key share: %w", err) + } + + keyShare := new(KeyShare) + if err := keyShare.Decode(data[5+g.ScalarLength() : 5+g.ScalarLength()+ksLen]); err != nil { + return fmt.Errorf("failed to decode key share: %w", err) + } + + step := 8 + 2*g.ScalarLength() + + nonces := make(map[uint64][2]*group.Scalar) + for i := 5 + g.ScalarLength() + ksLen; i < len(data); i += step { + id := binary.LittleEndian.Uint64(data[i : i+8]) + + n0 := g.NewScalar() + if err := n0.Decode(data[i+8 : i+8+g.ScalarLength()]); err != nil { + return err + } + + n1 := g.NewScalar() + if err := n1.Decode(data[i+8 : i+8+g.ScalarLength()]); err != nil { + return err + } + + nonces[id] = [2]*group.Scalar{n0, n1} + } + + p.KeyShare = keyShare + p.Lambda = lambda + p.Nonces = nonces + p.Configuration = *Ciphersuite(g).Configuration() + + return nil +} + +// Encode returns a compact byte encoding of the signature share. +func (s SignatureShare) Encode() []byte { + share := s.SignatureShare.Encode() + + out := make([]byte, 8+len(share)) + copy(out, internal.UInt64LE(s.Identifier)) + copy(out[8:], share) + + return out +} + +// DecodeSignatureShare takes a byte string and attempts to decode it to return the signature share. +func (c Configuration) DecodeSignatureShare(data []byte) (*SignatureShare, error) { + g := c.Ciphersuite.Group + + if len(data) != 8+g.ScalarLength() { + return nil, errDecodeSignatureShare + } + + s := &SignatureShare{ + Identifier: internal.UInt64FromLE(data[:8]), + SignatureShare: g.NewScalar(), + } + + if err := s.SignatureShare.Decode(data[8:]); err != nil { + return nil, fmt.Errorf("failed to decode signature share: %w", err) + } + + return s, nil +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..a5b294b --- /dev/null +++ b/errors.go @@ -0,0 +1,5 @@ +package frost + +import "errors" + +var errDecodeSignatureShare = errors.New("failed to decode signature share: invalid length") diff --git a/examples_test.go b/examples_test.go index 4acde9d..84e746f 100644 --- a/examples_test.go +++ b/examples_test.go @@ -10,209 +10,116 @@ package frost_test import ( "fmt" - group "github.com/bytemare/crypto" - "github.com/bytemare/dkg" + "github.com/bytemare/frost" + "github.com/bytemare/frost/debug" ) var ( - maxParticipants uint - threshold uint - ciphersuite frost.Ciphersuite - participantsGeneratedInDKG []*frost.Participant - commitment *frost.Commitment - groupPublicKeyGeneratedInDKG *group.Element +// participantsGeneratedInDKG []*frost.Participant +// commitment *frost.Commitment +// groupPublicKeyGeneratedInDKG *group.Element ) -func Example_dkg() { - // Each participant must be set to use the same configuration. - if threshold == 0 || maxParticipants == 0 { - maxParticipants = 5 - threshold = 3 - } - - if ciphersuite == 0 { - ciphersuite = frost.Ristretto255 - } - - dkgCiphersuite := dkg.Ciphersuite(ciphersuite) - - // Step 1: Initialise each participant. Each participant must be given an identifier that MUST be unique among - // all participants. - participants := make([]*dkg.Participant, 0, maxParticipants) - for i := range maxParticipants { - p, err := dkgCiphersuite.NewParticipant(uint64(i+1), maxParticipants, threshold) - if err != nil { - panic(err) - } - - participants = append(participants, p) - } - - // Step 2: Call Start() on each participant. This will return data that must be broadcast to all other participants - // over a secure channel. - r1 := make([][]byte, 0, maxParticipants) - for i := range maxParticipants { - r1 = append(r1, participants[i].Start().Encode()) - } - - // Step 3: First, each participant collects all round1Data from all other participants, and decodes them using - // NewRound1Data(). - // Then call Continue() on each participant providing them with the compiled data. - accumulatedRound1Data := make([]*dkg.Round1Data, 0, maxParticipants) - for i, r := range r1 { - decodedRound1 := participants[i].NewRound1Data() - if err := decodedRound1.Decode(r); err != nil { - panic(err) - } - - accumulatedRound1Data = append(accumulatedRound1Data, decodedRound1) - } - - // This will return a dedicated package round2Data for each other participant that must be sent to them over a secure channel. - // The intended receiver is specified in round2Data. - // Execution MUST be aborted upon errors, and not rewound. If this fails you should probably investigate this. - // Since we centrally simulate the setup here, we use a map to keep the messages for participant together. - r2 := make(map[uint64][][]byte, maxParticipants) - for _, participant := range participants { - r, err := participant.Continue(accumulatedRound1Data) - if err != nil { - panic(err) - } - - for id, data := range r { - if r2[id] == nil { - r2[id] = make([][]byte, 0, maxParticipants-1) - } - r2[id] = append(r2[id], data.Encode()) - } - } - - // Step 3: First, collect all round2Data from all other participants intended to this participant, and decode them - // using NewRound2Data(). - // Then call Finalize() on each participant providing the same input as for Continue() and the collected data from the second round. - - // This will, for each participant, return their secret key (which is a share of the global secret signing key), - // the corresponding verification/public key, and the global public key. - // In case of errors, execution MUST be aborted. - - keyShares := make([]*frost.KeyShare, maxParticipants) - participantsGeneratedInDKG = make([]*frost.Participant, maxParticipants) - - for i, participant := range participants { - accumulatedRound2Data := make([]*dkg.Round2Data, 0, maxParticipants) - for _, r := range r2[participant.Identifier] { - d := participant.NewRound2Data() - if err := d.Decode(r); err != nil { - panic(err) - } - - accumulatedRound2Data = append(accumulatedRound2Data, d) - } - - participantKeys, gpk, err := participant.Finalize(accumulatedRound1Data, accumulatedRound2Data) - if err != nil { - panic(err) - } - - if groupPublicKeyGeneratedInDKG == nil { - groupPublicKeyGeneratedInDKG = gpk - } - - keyShare := &frost.KeyShare{ - ID: participantKeys.Identifier, - Secret: participantKeys.SecretKey, - PublicKey: participantKeys.PublicKey, - } - - keyShares[i] = keyShare - participantsGeneratedInDKG[i] = ciphersuite.Configuration(groupPublicKeyGeneratedInDKG).Participant(keyShare) - } - - fmt.Println("Signing keys set up in DKG.") - // Output: Signing keys set up in DKG. -} - // Example_signer shows the execution steps of a FROST participant. func Example_signer() { - maxParticipants = 5 - threshold = 3 + maxParticipants := 5 + threshold := 3 message := []byte("example message") - ciphersuite = frost.Ristretto255 + ciphersuite := frost.Ristretto255 // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. - // See Example_dkg() on how to do generate these shares. - Example_dkg() - participant := participantsGeneratedInDKG[1] + // This example uses a centralised trusted dealer, but it is strongly recommended to use distributed key generation, + // e.g. from github.com/bytemare/dkg, which is compatible with FROST. + secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, maxParticipants, threshold) + + // Since we used a centralised key generation, we only take the first key share for our participant. + participantSecretKeyShare := secretKeyShares[0] + participant := ciphersuite.Participant(participantSecretKeyShare) + commitments := make(frost.CommitmentList, threshold) // Step 1: call Commit() on each participant. This will return the participant's single-use commitment. // Send this to the coordinator or all other participants over an authenticated // channel (confidentiality is not required). // A participant keeps an internal state during the protocol run across the two rounds. - commitment = participant.Commit() - if commitment.Identifier != participant.KeyShare.ID { - panic("this is just a test and it failed") - } + // A participant can pre-compute multiple commitments in advance: these commitments can be shared, but the + // participant keeps an internal state of corresponding values, so it must the same instance or a backup of it using + commitment := participant.Commit() // Step 2: collect the commitments from the other participants and coordinator-chosen the message to sign, - // and finalize by signing the message. This is a dummy list since we have only one signer. - commitments := make(frost.CommitmentList, 0, threshold) - commitments = append(commitments, commitment) + // and finalize by signing the message. + commitments[0] = commitment + + // This is not part of a participant's flow, but we need to collect the commitments of the other participants. + { + for i := 1; i < threshold; i++ { + commitments[i] = ciphersuite.Participant(secretKeyShares[i]).Commit() + } + } // Step 3: The participant receives the commitments from the other signer and the message to sign. // Sign produce a signature share to be sent back to the coordinator. // We ignore the error for the demo, but execution MUST be aborted upon errors. - signatureShare, err := participant.Sign(message, commitments) + signatureShare, err := participant.Sign(commitment.CommitmentID, message, commitments) if err != nil { panic(err) } // This shows how to verify a single signature share - if !participant.VerifySignatureShare( - commitment, - message, - signatureShare, - commitments, - ) { - panic("signature share verification failed") + conf := ciphersuite.Configuration() + if err = conf.VerifySignatureShare(commitment, message, signatureShare, commitments, groupPublicKey); err != nil { + panic(fmt.Sprintf("signature share verification failed: %s", err)) } fmt.Println("Signing successful.") - // Output: Signing keys set up in DKG. - // Signing successful. + // Output: Signing successful. } // Example_verification shows how to verify a FROST signature produced by multiple signers. func Example_verification() { - maxParticipants = 5 - threshold = 3 - + maxParticipants := 5 + threshold := 3 message := []byte("example message") - ciphersuite = frost.Ristretto255 + ciphersuite := frost.Ristretto255 + + // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. + // This example uses a centralised trusted dealer, but it is strongly recommended to use distributed key generation, + // e.g. from github.com/bytemare/dkg, which is compatible with FROST. + secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, maxParticipants, threshold) + participantSecretKeyShares := secretKeyShares[:threshold] + participants := make([]*frost.Participant, threshold) - // The following sets up the signer pool and produces a signature to verify. - Example_dkg() - participants := participantsGeneratedInDKG[:threshold] + // Create a participant on each instance + for i, ks := range participantSecretKeyShares { + participants[i] = ciphersuite.Participant(ks) + } + + // Pre-commit commitments := make(frost.CommitmentList, threshold) - signatureShares := make([]*frost.SignatureShare, threshold) for i, p := range participants { commitments[i] = p.Commit() } + + commitments.Sort() + + // Sign + signatureShares := make([]*frost.SignatureShare, threshold) for i, p := range participants { var err error - signatureShares[i], err = p.Sign(message, commitments) + signatureShares[i], err = p.Sign(commitments[i].CommitmentID, message, commitments) if err != nil { panic(err) } } - configuration := ciphersuite.Configuration(groupPublicKeyGeneratedInDKG) - signature := configuration.AggregateSignatures(message, signatureShares, commitments) + // A coordinator, proxy, assembles the shares + configuration := ciphersuite.Configuration() + signature := configuration.AggregateSignatures(message, signatureShares, commitments, groupPublicKey) // Verify the signature - conf := ciphersuite.Configuration(groupPublicKeyGeneratedInDKG) - success := conf.VerifySignature(message, signature) + conf := ciphersuite.Configuration() + success := conf.VerifySignature(message, signature, groupPublicKey) if success { fmt.Println("Signature is valid.") @@ -220,8 +127,7 @@ func Example_verification() { fmt.Println("Signature is not valid.") } - // Output: Signing keys set up in DKG. - // Signature is valid. + // Output: Signature is valid. } // Example_coordinator shows the execution steps of a FROST coordinator. @@ -235,22 +141,24 @@ func Example_coordinator() { Note that it is possible to deploy the protocol without a distinguished Coordinator. */ - maxParticipants = 5 - threshold = 3 - + maxParticipants := 5 + threshold := 3 message := []byte("example message") - ciphersuite = frost.Ristretto255 + ciphersuite := frost.Ristretto255 - // 0. We suppose a previous run of a DKG with a setup of participants. - Example_dkg() - participants := participantsGeneratedInDKG[:threshold] - groupPublicKey := groupPublicKeyGeneratedInDKG + // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. + // This example uses a centralised trusted dealer, but it is strongly recommended to use distributed key generation, + // e.g. from github.com/bytemare/dkg, which is compatible with FROST. + secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, maxParticipants, threshold) + participantSecretKeyShares := secretKeyShares[:threshold] + participants := make([]*frost.Participant, threshold) - // Set up a coordinator. - configuration := frost.Ristretto255.Configuration(groupPublicKey) + // Create a participant on each instance + for i, ks := range participantSecretKeyShares { + participants[i] = ciphersuite.Participant(ks) + } - // 1. Each participant generates its commitment and sends it to the aggregator. - // Then send the message to be signed and the sorted received commitment list to each participant. + // Pre-commit commitments := make(frost.CommitmentList, threshold) for i, p := range participants { commitments[i] = p.Commit() @@ -258,20 +166,22 @@ func Example_coordinator() { commitments.Sort() - // 2. Each participant signs the message and sends the resulting signature shares to the aggregator/coordinator, - // which aggregates them to produce the final signature. This signature SHOULD be verified. - var err error + // Sign signatureShares := make([]*frost.SignatureShare, threshold) - for i, participant := range participants { - signatureShares[i], err = participant.Sign(message, commitments) + for i, p := range participants { + var err error + signatureShares[i], err = p.Sign(commitments[i].CommitmentID, message, commitments) if err != nil { panic(err) } } - signature := configuration.AggregateSignatures(message, signatureShares[:], commitments) + // Set up a coordinator, and assemble the shares. + configuration := ciphersuite.Configuration() + signature := configuration.AggregateSignatures(message, signatureShares, commitments, groupPublicKey) - if !configuration.VerifySignature(message, signature) { + // Verify the signature and identify potential foul players. + if !configuration.VerifySignature(message, signature, groupPublicKey) { // At this point one should try to identify which participant's signature share is invalid and act on it. // This verification is done as follows: for _, signatureShare := range signatureShares { @@ -281,8 +191,14 @@ func Example_coordinator() { panic("commitment not found") } - if !configuration.VerifySignatureShare(commitmentI, message, signatureShare, commitments) { - panic(fmt.Sprintf("participant %v produced an invalid signature share", signatureShare.Identifier)) + if err := configuration.VerifySignatureShare(commitmentI, message, signatureShare, commitments, groupPublicKey); err != nil { + panic( + fmt.Sprintf( + "participant %v produced an invalid signature share: %s", + signatureShare.Identifier, + err, + ), + ) } } @@ -291,6 +207,5 @@ func Example_coordinator() { fmt.Printf("Valid signature for %q.", message) - // Output: Signing keys set up in DKG. - // Valid signature for "example message". + // Output: Valid signature for "example message". } diff --git a/frost.go b/frost.go index f66193e..4ac8d41 100644 --- a/frost.go +++ b/frost.go @@ -10,10 +10,8 @@ package frost import ( - "fmt" group "github.com/bytemare/crypto" "github.com/bytemare/hash" - secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/frost/internal" ) @@ -24,10 +22,11 @@ import ( - more buzz - show supported ciphersuites - Check for - - FROST2-BTZ - - FROST3 (ROAST): https://eprint.iacr.org/2022/550 + - FROST2-CKM: https://eprint.iacr.org/2021/1375 (has duplicate checks) + - FROST2-BTZ: https://eprint.iacr.org/2022/833 + - FROST3 (ROAST): https://eprint.iacr.org/2022/550 (most efficient variant of FROST) - wrapper increasing robustness and apparently reducing some calculations? - - Chu + - Chu: https://eprint.iacr.org/2023/899 - re-randomize keys: https://eprint.iacr.org/2024/436.pdf */ @@ -66,16 +65,39 @@ func (c Ciphersuite) Available() bool { switch c { case Ed25519, Ristretto255, P256, Secp256k1: return true - case ed448: - return false default: return false } } -func makeConf(pk *group.Element, context string, h hash.Hash, g group.Group) *Configuration { +// Group returns the elliptic curve group used in the ciphersuite. +func (c Ciphersuite) Group() group.Group { + if !c.Available() { + return 0 + } + + return group.Group(c) +} + +// Participant returns a new participant of the protocol instantiated from the configuration an input. +func (c Ciphersuite) Participant(keyShare *KeyShare) *Participant { + return &Participant{ + KeyShare: keyShare, + Lambda: nil, + Nonces: make(map[uint64][2]*group.Scalar), + HidingRandom: nil, + BindingRandom: nil, + Configuration: *c.Configuration(), + } +} + +// Configuration holds long term configuration information. +type Configuration struct { + internal.Ciphersuite +} + +func makeConf(context string, h hash.Hash, g group.Group) *Configuration { return &Configuration{ - GroupPublicKey: pk, Ciphersuite: internal.Ciphersuite{ ContextString: []byte(context), Hash: h, @@ -85,123 +107,21 @@ func makeConf(pk *group.Element, context string, h hash.Hash, g group.Group) *Co } // Configuration returns a configuration created for the ciphersuite. -func (c Ciphersuite) Configuration(groupPublicKey ...*group.Element) *Configuration { - //todo: pubkey as byte slice so that we can check it's a valid point in the group, must be non-nil +func (c Ciphersuite) Configuration() *Configuration { if !c.Available() { return nil } - var pk *group.Element - if len(groupPublicKey) != 0 { - pk = groupPublicKey[0] - } - switch c { case Ed25519: - return makeConf(pk, ed25519ContextString, hash.SHA512, group.Edwards25519Sha512) + return makeConf(ed25519ContextString, hash.SHA512, group.Edwards25519Sha512) case Ristretto255: - return makeConf(pk, ristretto255ContextString, hash.SHA512, group.Ristretto255Sha512) + return makeConf(ristretto255ContextString, hash.SHA512, group.Ristretto255Sha512) case P256: - return makeConf(pk, p256ContextString, hash.SHA256, group.P256Sha256) + return makeConf(p256ContextString, hash.SHA256, group.P256Sha256) case Secp256k1: - return makeConf(pk, secp256k1ContextString, hash.SHA256, group.Secp256k1) + return makeConf(secp256k1ContextString, hash.SHA256, group.Secp256k1) default: return nil } } - -// Configuration holds long term configuration information. -type Configuration struct { - GroupPublicKey *group.Element - Ciphersuite internal.Ciphersuite -} - -// 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 (c Configuration) RecoverGroupSecret(keyShares []*KeyShare) (*group.Scalar, error) { - keys := make([]secretsharing.KeyShare, len(keyShares)) - for i, v := range keyShares { - keys[i] = secretsharing.KeyShare(v) - } - - secret, err := secretsharing.Combine(c.Ciphersuite.Group, keys) - if err != nil { - return nil, fmt.Errorf("failed to reconstruct group secret: %w", err) - } - - return secret, nil -} - -// Participant returns a new participant of the protocol instantiated from the configuration an input. -func (c Configuration) Participant(keyShare *KeyShare) *Participant { - return &Participant{ - KeyShare: keyShare, - Lambda: nil, - Nonce: [2]*group.Scalar{}, - HidingRandom: nil, - BindingRandom: nil, - Configuration: c, - } -} - -// DeriveGroupInfo returns the group public key as well those from all participants. -func DeriveGroupInfo(g group.Group, max int, coms secretsharing.Commitment) (*group.Element, []*group.Element) { - pk := coms[0] - keys := make([]*group.Element, max) - - for i := 1; i <= max; i++ { - id := g.NewScalar().SetUInt64(uint64(i)) - pki := derivePublicPoint(g, coms, id) - keys[i-1] = pki - } - - return pk, keys -} - -// 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( - g group.Group, - secret *group.Scalar, - max, min int, - coeffs ...*group.Scalar, -) ([]*KeyShare, *group.Element, secretsharing.Commitment, error) { - privateKeyShares, poly, err := secretsharing.ShardReturnPolynomial(g, secret, uint(min), uint(max), coeffs...) - if err != nil { - return nil, nil, nil, err - } - - coms := secretsharing.Commit(g, poly) - - shares := make([]*KeyShare, max) - for i, k := range privateKeyShares { - shares[i] = &KeyShare{ - Secret: k.Secret, - PublicKey: g.Base().Multiply(k.Secret), - ID: k.ID, - } - } - - return shares, coms[0], coms, nil -} - -func derivePublicPoint(g group.Group, coms secretsharing.Commitment, i *group.Scalar) *group.Element { - publicPoint := g.NewElement().Identity() - one := g.NewScalar().One() - - j := g.NewScalar().Zero() - for _, com := range coms { - publicPoint.Add(com.Copy().Multiply(i.Copy().Pow(j))) - j.Add(one) - } - - return publicPoint -} - -// VerifyVSS allows verification of a participant's secret share given a VSS commitment to the secret polynomial. -func VerifyVSS(g group.Group, share secretsharing.KeyShare, coms secretsharing.Commitment) bool { - pk := g.Base().Multiply(share.SecretKey()) - return secretsharing.Verify(g, share.Identifier(), pk, coms) -} diff --git a/go.mod b/go.mod index 8ea98c6..5d35820 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.22.3 require ( filippo.io/edwards25519 v1.1.0 github.com/bytemare/crypto v0.7.5 - github.com/bytemare/dkg v0.0.0-20240630203912-c5098d51de1b + github.com/bytemare/dkg v0.0.0-20240724114445-f93f2b4fc5d5 github.com/bytemare/hash v0.3.0 - github.com/bytemare/secret-sharing v0.1.4 + github.com/bytemare/secret-sharing v0.3.0 github.com/gtank/ristretto255 v0.1.2 ) diff --git a/go.sum b/go.sum index bb573ce..4898bff 100644 --- a/go.sum +++ b/go.sum @@ -4,16 +4,16 @@ filippo.io/nistec v0.0.3 h1:h336Je2jRDZdBCLy2fLDUd9E2unG32JLwcJi0JQE9Cw= filippo.io/nistec v0.0.3/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw= github.com/bytemare/crypto v0.7.5 h1:aRZzSmRZFlPt4ydpI5KKr+UQbzNd1559mNnRj9tNjRw= github.com/bytemare/crypto v0.7.5/go.mod h1:qRA6Tdg0Q9zMTuxeKkyVtEyDAgIuwM0YN5tffzFQJQw= -github.com/bytemare/dkg v0.0.0-20240630203912-c5098d51de1b h1:k6kOdWLiWH8xRf1m1a8vdwF7TynVJf1eyxeIliv0ko4= -github.com/bytemare/dkg v0.0.0-20240630203912-c5098d51de1b/go.mod h1:Lp2TnIUKKHtzzjhbq3xK8/svOmgM+Tbn2WluC9uc85U= +github.com/bytemare/dkg v0.0.0-20240724114445-f93f2b4fc5d5 h1:A19axQ2U11TMSRBYHsWUhRrudV8zR9HWQYcFAJozcaU= +github.com/bytemare/dkg v0.0.0-20240724114445-f93f2b4fc5d5/go.mod h1:jXow/Ycil51++OmvmBJ25ksb93RtSEnMxGxadZjWRVk= github.com/bytemare/hash v0.3.0 h1:RqFMt3mqpF7UxLdjBrsOZm/2cz0cQiAOnYc9gDLopWE= github.com/bytemare/hash v0.3.0/go.mod h1:YKOBchL0l8hRLFinVCL8YUKokGNIMhrWEHPHo3EV7/M= github.com/bytemare/hash2curve v0.3.0 h1:41Npcbc+u/E252A5aCMtxDcz7JPkkX1QzShneTFm4eg= github.com/bytemare/hash2curve v0.3.0/go.mod h1:itj45U8uqvCtWC0eCswIHVHswXcEHkpFui7gfJdPSfQ= github.com/bytemare/secp256k1 v0.1.4 h1:6F1yP6RiUiWwH7AsGHsHktmHm24QcetdDcc39roBd2M= github.com/bytemare/secp256k1 v0.1.4/go.mod h1:Pxb9miDs8PTt5mOktvvXiRflvLxI1wdxbXrc6IYsaho= -github.com/bytemare/secret-sharing v0.1.4 h1:+qEmeOgKq16P8SX1oWGkTit0WBa+5uDpG46EA/8+kXw= -github.com/bytemare/secret-sharing v0.1.4/go.mod h1:kZ8Ty314nPP1LLd9ZsAAoc77625CEvXzRtimtEE1M9I= +github.com/bytemare/secret-sharing v0.3.0 h1:IK+wi3dhh+s8amN4xqdpgd8Byi36jZJQ9oAX3bowto0= +github.com/bytemare/secret-sharing v0.3.0/go.mod h1:kZ8Ty314nPP1LLd9ZsAAoc77625CEvXzRtimtEE1M9I= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= diff --git a/internal/binding.go b/internal/binding.go index 2fd1246..c0837b1 100644 --- a/internal/binding.go +++ b/internal/binding.go @@ -14,8 +14,8 @@ import ( // BindingFactor holds the binding factor scalar for the given identifier. type BindingFactor struct { - Identifier uint64 BindingFactor *group.Scalar + Identifier uint64 } // BindingFactorList a list of BindingFactor. diff --git a/internal/errors.go b/internal/errors.go new file mode 100644 index 0000000..ea302e6 --- /dev/null +++ b/internal/errors.go @@ -0,0 +1,21 @@ +package internal + +import "errors" + +var ( + // ErrInvalidParameters indicates that wrong input has been provided. + ErrInvalidParameters = errors.New("invalid parameters") + + // ErrInvalidCiphersuite indicates a non-supported ciphersuite is being used. + ErrInvalidCiphersuite = errors.New("ciphersuite not available") + + // ErrInvalidParticipantBackup indicates the participant's encoded backup is not valid. + ErrInvalidParticipantBackup = errors.New("invalid backup") + + // ErrInvalidLength indicates that a provided encoded data piece is not of the expected length. + ErrInvalidLength = errors.New("invalid encoding length") + + ErrWrongVerificationData = errors.New("the commitment and signature share don't belong the same participant") + + ErrInvalidVerificationShare = errors.New("signature share does not not match") +) diff --git a/internal/utils.go b/internal/utils.go index 89f38f9..db5c8b9 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -12,21 +12,9 @@ package internal import ( cryptorand "crypto/rand" "encoding/binary" - "errors" "fmt" ) -var ( - // ErrInvalidParameters indicates that wrong input has been provided. - ErrInvalidParameters = errors.New("invalid parameters") - - // ErrInvalidCiphersuite indicates a non-supported ciphersuite is being used. - ErrInvalidCiphersuite = errors.New("ciphersuite not available") - - // ErrInvalidParticipantBackup indicates the participant's encoded backup is not valid. - ErrInvalidParticipantBackup = errors.New("invalid backup") -) - // Concatenate returns the concatenation of all bytes composing the input elements. func Concatenate(input ...[]byte) []byte { if len(input) == 1 { diff --git a/keys.go b/keys.go new file mode 100644 index 0000000..0982620 --- /dev/null +++ b/keys.go @@ -0,0 +1,81 @@ +package frost + +import ( + "fmt" + + group "github.com/bytemare/crypto" + "github.com/bytemare/dkg" + secretsharing "github.com/bytemare/secret-sharing" +) + +// KeyShare identifies the sharded key share for a given participant. +type KeyShare secretsharing.KeyShare + +// Identifier returns the identity for this share. +func (k *KeyShare) Identifier() uint64 { + return (*secretsharing.KeyShare)(k).Identifier() +} + +// SecretKey returns the participant's secret share. +func (k *KeyShare) SecretKey() *group.Scalar { + return (*secretsharing.KeyShare)(k).SecretKey() +} + +// Public returns the public key share and identifier corresponding to the secret key share. +func (k *KeyShare) Public() *PublicKeyShare { + return (*PublicKeyShare)(&k.PublicKeyShare) +} + +// Encode serializes k into a compact byte string. +func (k *KeyShare) Encode() []byte { + return (*secretsharing.KeyShare)(k).Encode() +} + +// Decode deserializes the compact encoding obtained from Encode(), or returns an error. +func (k *KeyShare) Decode(data []byte) error { + if err := (*secretsharing.KeyShare)(k).Decode(data); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} + +// UnmarshalJSON decodes data into k, or returns an error. +func (k *KeyShare) UnmarshalJSON(data []byte) error { + if err := (*secretsharing.KeyShare)(k).UnmarshalJSON(data); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} + +// PublicKeyShare specifies the public key of a participant identified with ID. +type PublicKeyShare secretsharing.PublicKeyShare + +// Verify returns whether the PublicKeyShare's public key is valid given its VSS commitment to the secret polynomial. +func (p *PublicKeyShare) Verify(commitments [][]*group.Element) bool { + return dkg.VerifyPublicKey(dkg.Ciphersuite(p.Group), p.ID, p.PublicKey, commitments) == nil +} + +// Encode serializes p into a compact byte string. +func (p *PublicKeyShare) Encode() []byte { + return (*secretsharing.PublicKeyShare)(p).Encode() +} + +// Decode deserializes the compact encoding obtained from Encode(), or returns an error. +func (p *PublicKeyShare) Decode(data []byte) error { + if err := (*secretsharing.PublicKeyShare)(p).Decode(data); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} + +// UnmarshalJSON decodes data into p, or returns an error. +func (p *PublicKeyShare) UnmarshalJSON(data []byte) error { + if err := (*secretsharing.PublicKeyShare)(p).UnmarshalJSON(data); err != nil { + return fmt.Errorf("%w", err) + } + + return nil +} diff --git a/participant.go b/participant.go index 46d69a7..170b103 100644 --- a/participant.go +++ b/participant.go @@ -9,47 +9,30 @@ package frost import ( - "errors" - "fmt" + "crypto/rand" + "encoding/binary" group "github.com/bytemare/crypto" + "github.com/bytemare/frost/internal" ) -// KeyShare identifies the sharded key share for a given participant. -type KeyShare struct { - // The Secret of a participant (or secret share). - Secret *group.Scalar - - // The PublicKey of Secret belonging to the participant. - PublicKey *group.Element - - // ID of the participant. - ID uint64 -} - -// Identifier returns the identity for this share. -func (s KeyShare) Identifier() uint64 { - return s.ID -} - -// SecretKey returns the participant's secret share. -func (s KeyShare) SecretKey() *group.Scalar { - return s.Secret +// SignatureShare represents a participants signature share and its identifier. +type SignatureShare struct { + SignatureShare *group.Scalar + Identifier uint64 } // Participant is a signer of a group. type Participant struct { KeyShare *KeyShare - Lambda *group.Scalar // lamba can be computed once and reused across FROST signing operations - Nonce [2]*group.Scalar + Lambda *group.Scalar // lambda can be computed once and reused across FROST signing operations + Nonces map[uint64][2]*group.Scalar HidingRandom []byte BindingRandom []byte Configuration } -var errDecodeSignatureShare = errors.New("failed to decode signature share: invalid length") - func (p *Participant) generateNonce(s *group.Scalar, random []byte) *group.Scalar { if random == nil { random = internal.RandomBytes(32) @@ -60,61 +43,43 @@ func (p *Participant) generateNonce(s *group.Scalar, random []byte) *group.Scala return p.Ciphersuite.H3(internal.Concatenate(random, enc)) } -// Backup serializes the client with its long term values, containing its secret share. -func (p *Participant) Backup() []byte { - return internal.Concatenate(internal.UInt64LE(p.KeyShare.ID), - p.KeyShare.Secret.Encode(), - p.Lambda.Encode()) -} +func randomCommitmentID() uint64 { + buf := make([]byte, 8) -// RecoverParticipant attempts to deserialize the encoded backup into a Participant. -func RecoverParticipant(c Ciphersuite, backup []byte) (*Participant, error) { - if !c.Available() { - return nil, internal.ErrInvalidCiphersuite - } - - conf := c.Configuration() - - sLen := conf.Ciphersuite.Group.ScalarLength() - if len(backup) != 8+2*sLen { - return nil, internal.ErrInvalidParticipantBackup - } - - id := internal.UInt64FromLE(backup[:8]) - - secret := conf.Ciphersuite.Group.NewScalar() - if err := secret.Decode(backup[sLen : 2*sLen]); err != nil { - return nil, fmt.Errorf("decoding key share: %w", err) - } - - lambda := conf.Ciphersuite.Group.NewScalar() - if err := lambda.Decode(backup[2*sLen:]); err != nil { - return nil, fmt.Errorf("decoding lambda: %w", err) - } - - keyShare := &KeyShare{ - Secret: secret, - PublicKey: conf.Ciphersuite.Group.Base().Multiply(secret), - ID: id, + _, err := rand.Read(buf) + if err != nil { + panic(err) } - p := conf.Participant(keyShare) - p.Lambda = lambda + return binary.LittleEndian.Uint64(buf) +} - return p, nil +func (p *Participant) Identifier() uint64 { + return p.KeyShare.ID } // Commit generates a participants nonce and commitment, to be used in the second FROST round. The internal nonce must // be kept secret, and the returned commitment sent to the signature aggregator. func (p *Participant) Commit() *Commitment { - p.Nonce[0] = p.generateNonce(p.KeyShare.Secret, p.HidingRandom) - p.Nonce[1] = p.generateNonce(p.KeyShare.Secret, p.BindingRandom) + cid := randomCommitmentID() + for { + if _, ok := p.Nonces[cid]; !ok { + break + } + } + + p.Nonces[cid] = [2]*group.Scalar{ + p.generateNonce(p.KeyShare.Secret, p.HidingRandom), + p.generateNonce(p.KeyShare.Secret, p.BindingRandom), + } return &Commitment{ - Identifier: p.KeyShare.ID, - PublicKey: p.KeyShare.PublicKey, - HidingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonce[0]), - BindingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonce[1]), + Ciphersuite: Ciphersuite(p.Group), + ParticipantID: p.KeyShare.ID, + CommitmentID: cid, + PublicKey: p.KeyShare.PublicKey, + HidingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonces[cid][0]), + BindingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonces[cid][1]), } } @@ -123,28 +88,38 @@ func computeLambda(g group.Group, commitments CommitmentList, id uint64) (*group return participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) } -func (c Configuration) do(message []byte, commitments CommitmentList, id uint64) (*group.Scalar, *group.Scalar, *group.Scalar, error) { +func (c Configuration) do( + publicKey *group.Element, + lambda *group.Scalar, + message []byte, + commitments CommitmentList, + id uint64, +) (*group.Scalar, *group.Scalar, error) { if !commitments.IsSorted() { commitments.Sort() } // Compute the interpolating value - lambda, err := computeLambda(c.Ciphersuite.Group, commitments, id) - if err != nil { - return nil, nil, nil, err + if lambda == nil || lambda.IsZero() { + l, err := computeLambda(c.Ciphersuite.Group, commitments, id) + if err != nil { + return nil, nil, err + } + + lambda = l } // Compute the binding factor(s) - bindingFactorList := c.computeBindingFactors(commitments, c.GroupPublicKey.Encode(), message) + bindingFactorList := c.computeBindingFactors(publicKey, commitments, message) bindingFactor := bindingFactorList.BindingFactorForParticipant(id) // Compute group commitment - groupCommitment := c.computeGroupCommitment(commitments, bindingFactorList) + groupCommitment := computeGroupCommitment(c.Group, commitments, bindingFactorList) // Compute per message challenge - chall := challenge(c.Ciphersuite, groupCommitment, c.GroupPublicKey, message) + chall := challenge(c.Ciphersuite, groupCommitment, publicKey, message) - return bindingFactor, lambda, chall.Multiply(lambda), nil + return bindingFactor, chall.Multiply(lambda), nil } // Sign produces a participant's signature share of the message msg. @@ -153,22 +128,20 @@ func (c Configuration) do(message []byte, commitments CommitmentList, id uint64) // In particular, the Signer MUST validate commitment_list, deserializing each group Element in the list using // DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, // each participant MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. -func (p *Participant) Sign(msg []byte, coms CommitmentList) (*SignatureShare, error) { - bindingFactor, lambda, lambdaChall, err := p.do(msg, coms, p.KeyShare.ID) +func (p *Participant) Sign(commitmentID uint64, msg []byte, coms CommitmentList) (*SignatureShare, error) { + bindingFactor, lambdaChall, err := p.do(p.KeyShare.GroupPublicKey, p.Lambda, msg, coms, p.KeyShare.ID) if err != nil { return nil, err } - p.Lambda = lambda.Copy() - // Compute the signature share - sigShare := p.Nonce[0].Add( - p.Nonce[1].Multiply(bindingFactor).Add(lambdaChall.Multiply(p.KeyShare.Secret)), + sigShare := p.Nonces[commitmentID][0].Copy().Add( + p.Nonces[commitmentID][1].Copy().Multiply(bindingFactor).Add(lambdaChall.Multiply(p.KeyShare.Secret)), ).Copy() // Clean up values - p.Nonce[0].Zero() - p.Nonce[1].Zero() + p.Nonces[commitmentID][0].Zero() + p.Nonces[commitmentID][1].Zero() return &SignatureShare{ Identifier: p.KeyShare.ID, @@ -177,23 +150,28 @@ func (p *Participant) Sign(msg []byte, coms CommitmentList) (*SignatureShare, er } // computeBindingFactors computes binding factors based on the participant commitment list and the message to be signed. -func (c Configuration) computeBindingFactors(l CommitmentList, pubkey, message []byte) internal.BindingFactorList { +func (c Configuration) computeBindingFactors( + publicKey *group.Element, + l CommitmentList, + message []byte, +) internal.BindingFactorList { if !l.IsSorted() { panic(nil) } - h := c.Ciphersuite.H4(message) - encodedCommitHash := c.Ciphersuite.H5(l.Encode()) - rhoInputPrefix := internal.Concatenate(pubkey, h, encodedCommitHash) + h := c.H4(message) + encodedCommitHash := c.H5(l.Encode(c.Ciphersuite.Group)) + rhoInputPrefix := internal.Concatenate(publicKey.Encode(), h, encodedCommitHash) bindingFactorList := make(internal.BindingFactorList, len(l)) for i, commitment := range l { - rhoInput := internal.Concatenate(rhoInputPrefix, internal.UInt64LE(commitment.Identifier)) - bindingFactor := c.Ciphersuite.H1(rhoInput) + id := c.Group.NewScalar().SetUInt64(commitment.ParticipantID).Encode() + rhoInput := internal.Concatenate(rhoInputPrefix, id) + bindingFactor := c.H1(rhoInput) bindingFactorList[i] = &internal.BindingFactor{ - Identifier: commitment.Identifier, + Identifier: commitment.ParticipantID, BindingFactor: bindingFactor, } } @@ -202,59 +180,22 @@ func (c Configuration) computeBindingFactors(l CommitmentList, pubkey, message [ } // computeGroupCommitment creates the group commitment from a commitment list. -func (c Configuration) computeGroupCommitment(l CommitmentList, list internal.BindingFactorList) *group.Element { +func computeGroupCommitment(g group.Group, l CommitmentList, list internal.BindingFactorList) *group.Element { if !l.IsSorted() { panic(nil) } - gc := c.Ciphersuite.Group.NewElement().Identity() + gc := g.NewElement() for _, commitment := range l { if commitment.HidingNonce.IsIdentity() || commitment.BindingNonce.IsIdentity() { panic("identity commitment") } - factor := list.BindingFactorForParticipant(commitment.Identifier) + factor := list.BindingFactorForParticipant(commitment.ParticipantID) bindingNonce := commitment.BindingNonce.Copy().Multiply(factor) gc.Add(commitment.HidingNonce).Add(bindingNonce) } return gc } - -// SignatureShare represents a participants signature share, specifying which participant it was produced by. -type SignatureShare struct { - Identifier uint64 - SignatureShare *group.Scalar -} - -// Encode returns a compact byte encoding of the signature share. -func (s SignatureShare) Encode() []byte { - share := s.SignatureShare.Encode() - - out := make([]byte, 8+len(share)) - copy(out, internal.UInt64LE(s.Identifier)) - copy(out[8:], share) - - return out -} - -// DecodeSignatureShare takes a byte string and attempts to decode it to return the signature share. -func (c Configuration) DecodeSignatureShare(data []byte) (*SignatureShare, error) { - g := c.Ciphersuite.Group - - if len(data) != 8+g.ScalarLength() { - return nil, errDecodeSignatureShare - } - - s := &SignatureShare{ - Identifier: internal.UInt64FromLE(data[:8]), - SignatureShare: g.NewScalar(), - } - - if err := s.SignatureShare.Decode(data[8:]); err != nil { - return nil, fmt.Errorf("failed to decode signature share: %w", err) - } - - return s, nil -} diff --git a/schnorr.go b/schnorr.go index 3e8f5cc..2b948ef 100644 --- a/schnorr.go +++ b/schnorr.go @@ -10,6 +10,7 @@ package frost import ( "fmt" + group "github.com/bytemare/crypto" "github.com/bytemare/frost/internal" @@ -71,10 +72,10 @@ func (c Configuration) Sign(msg []byte, key *group.Scalar) *Signature { } // VerifySignature returns whether the signature of the message msg is valid under the public key pk. -func (c Configuration) VerifySignature(msg []byte, signature *Signature) bool { - ch := challenge(c.Ciphersuite, signature.R, c.GroupPublicKey, msg) +func (c Configuration) VerifySignature(msg []byte, signature *Signature, publicKey *group.Element) bool { + ch := challenge(c.Ciphersuite, signature.R, publicKey, msg) l := c.Ciphersuite.Group.Base().Multiply(signature.Z) - r := signature.R.Add(c.GroupPublicKey.Copy().Multiply(ch)) + r := signature.R.Add(publicKey.Copy().Multiply(ch)) if c.Ciphersuite.Group == group.Edwards25519Sha512 { cofactor := group.Edwards25519Sha512.NewScalar().SetUInt64(8) diff --git a/tests/dkg_test.go b/tests/dkg_test.go index 452a010..c77c67c 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -1,11 +1,12 @@ package frost_test import ( - "fmt" + "testing" + group "github.com/bytemare/crypto" "github.com/bytemare/dkg" + "github.com/bytemare/frost" - "testing" ) func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, threshold int) []*dkg.Participant { @@ -28,13 +29,15 @@ func simulateDKG(t *testing.T, g group.Group, maxSigners, threshold int) ([]*fro // valid r1DataSet set with and without own package participants := dkgMakeParticipants(t, c, maxSigners, threshold) r1 := make([]*dkg.Round1Data, maxSigners) + commitments := make([][]*group.Element, maxSigners) // Step 1: Start and assemble packages. for i := range maxSigners { r1[i] = participants[i].Start() + commitments[i] = r1[i].Commitment } - pubKey, err := dkg.GroupPublicKey(c, r1) + pubKey, err := dkg.GroupPublicKeyFromRound1(c, r1) if err != nil { t.Fatal(err) } @@ -65,37 +68,24 @@ func simulateDKG(t *testing.T, g group.Group, maxSigners, threshold int) ([]*fro keyShares := make([]*frost.KeyShare, 0, maxSigners) for _, p := range participants { - keyShare, gpk, err := p.Finalize(r1, r2[p.Identifier]) + keyShare, err := p.Finalize(r1, r2[p.Identifier]) if err != nil { t.Fatal() } - if gpk.Equal(pubKey) != 1 { + if keyShare.GroupPublicKey.Equal(pubKey) != 1 { t.Fatalf("expected same public key") } - if keyShare.PublicKey.Equal(g.Base().Multiply(keyShare.SecretKey)) != 1 { + if keyShare.PublicKey.Equal(g.Base().Multiply(keyShare.SecretKey())) != 1 { t.Fatal("expected equality") } - if err := dkg.VerifyPublicKey(c, p.Identifier, keyShare.PublicKey, r1); err != nil { - t.Fatal(err) - } - - keyShares = append(keyShares, &frost.KeyShare{ - ID: keyShare.Identifier, - Secret: keyShare.SecretKey, - PublicKey: keyShare.PublicKey, - }) - } - - { - groupSecretKey, err := frost.Ciphersuite(g).Configuration(pubKey).RecoverGroupSecret(keyShares) - if err != nil { + if err := dkg.VerifyPublicKey(c, p.Identifier, keyShare.PublicKey, commitments); err != nil { t.Fatal(err) } - fmt.Println(groupSecretKey.Hex()) + keyShares = append(keyShares, (*frost.KeyShare)(keyShare)) } return keyShares, pubKey diff --git a/tests/frost_test.go b/tests/frost_test.go index 0549c0f..64f5598 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -9,46 +9,60 @@ package frost_test import ( - "fmt" "testing" group "github.com/bytemare/crypto" + "github.com/bytemare/hash" + "github.com/bytemare/frost" + "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" - "github.com/bytemare/hash" ) -var configurationTable = []frost.Configuration{ +type tableTest struct { + frost.Configuration + frost.Ciphersuite +} + +var testTable = []tableTest{ { - GroupPublicKey: nil, - Ciphersuite: internal.Ciphersuite{ - ContextString: []byte("FROST-ED25519-SHA512-v1"), - Hash: hash.SHA512, - Group: group.Edwards25519Sha512, + Ciphersuite: frost.Ed25519, + Configuration: frost.Configuration{ + Ciphersuite: internal.Ciphersuite{ + ContextString: []byte("FROST-ED25519-SHA512-v1"), + Hash: hash.SHA512, + Group: group.Edwards25519Sha512, + }, }, }, { - Ciphersuite: internal.Ciphersuite{ - Group: group.Ristretto255Sha512, - Hash: hash.SHA512, - ContextString: []byte("FROST-RISTRETTO255-SHA512-v1"), + Ciphersuite: frost.Ristretto255, + Configuration: frost.Configuration{ + Ciphersuite: internal.Ciphersuite{ + Group: group.Ristretto255Sha512, + Hash: hash.SHA512, + ContextString: []byte("FROST-RISTRETTO255-SHA512-v1"), + }, }, - GroupPublicKey: nil, }, { - Ciphersuite: internal.Ciphersuite{ - Group: group.P256Sha256, - Hash: hash.SHA256, - ContextString: []byte("FROST-P256-SHA256-v1"), + Ciphersuite: frost.P256, + Configuration: frost.Configuration{ + Ciphersuite: internal.Ciphersuite{ + Group: group.P256Sha256, + Hash: hash.SHA256, + ContextString: []byte("FROST-P256-SHA256-v1"), + }, }, - GroupPublicKey: nil, }, { - GroupPublicKey: nil, - Ciphersuite: internal.Ciphersuite{ - ContextString: []byte("FROST-secp256k1-SHA256-v1"), - Hash: hash.SHA256, - Group: group.Secp256k1, + Ciphersuite: frost.Secp256k1, + Configuration: frost.Configuration{ + Ciphersuite: internal.Ciphersuite{ + ContextString: []byte("FROST-secp256k1-SHA256-v1"), + Hash: hash.SHA256, + Group: group.Secp256k1, + }, }, }, } @@ -57,26 +71,23 @@ func TestTrustedDealerKeygen(t *testing.T) { threshold := 3 maxSigners := 5 - testAll(t, func(t2 *testing.T, configuration *frost.Configuration) { - g := configuration.Ciphersuite.Group + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Ciphersuite.Group() groupSecretKey := g.NewScalar().Random() - keyShares, dealerGroupPubKey, secretsharingCommitment, err := frost.TrustedDealerKeygen( - g, + keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( + frost.Ciphersuite(g), groupSecretKey, maxSigners, threshold, ) - if err != nil { - t.Fatal(err) - } if len(secretsharingCommitment) != threshold { - t2.Fatalf("%d / %d", len(secretsharingCommitment), threshold) + t.Fatalf("%d / %d", len(secretsharingCommitment), threshold) } - recoveredKey, err := configuration.RecoverGroupSecret(keyShares[:threshold]) + recoveredKey, err := debug.RecoverGroupSecret(g, keyShares[:threshold]) if err != nil { t.Fatal(err) } @@ -85,20 +96,18 @@ func TestTrustedDealerKeygen(t *testing.T) { t.Fatal() } - groupPublicKey, participantPublicKeys := frost.DeriveGroupInfo(g, maxSigners, secretsharingCommitment) + groupPublicKey, participantPublicKeys := debug.RecoverPublicKeys(g, maxSigners, secretsharingCommitment) if len(participantPublicKeys) != maxSigners { - t2.Fatal() + t.Fatal() } if groupPublicKey.Equal(dealerGroupPubKey) != 1 { - t2.Fatal() + t.Fatal() } - configuration.GroupPublicKey = dealerGroupPubKey - for i, shareI := range keyShares { - if !frost.VerifyVSS(g, shareI, secretsharingCommitment) { - t2.Fatal(i) + if !debug.VerifyVSS(g, shareI, secretsharingCommitment) { + t.Fatal(i) } } @@ -106,11 +115,11 @@ func TestTrustedDealerKeygen(t *testing.T) { recoveredPK := g.NewElement() if err := recoveredPK.Decode(pkEnc); err != nil { - t2.Fatal(err) + t.Fatal(err) } if recoveredPK.Equal(groupPublicKey) != 1 { - t2.Fatal() + t.Fatal() } }) } @@ -120,16 +129,15 @@ func TestFrost(t *testing.T) { threshold := 2 message := []byte("test") - testAll(t, func(t2 *testing.T, configuration *frost.Configuration) { - g := configuration.Ciphersuite.Group + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Ciphersuite.Group() keyShares, groupPublicKey := simulateDKG(t, g, maxSigner, threshold) - configuration.GroupPublicKey = groupPublicKey // Create Participants participants := make(ParticipantList, threshold) for i, share := range keyShares[:threshold] { - participants[i] = configuration.Participant(share) + participants[i] = test.Ciphersuite.Participant(share) } // Round One: Commitment @@ -144,41 +152,39 @@ func TestFrost(t *testing.T) { sigShares := make([]*frost.SignatureShare, threshold) for i, p := range participants { var err error - sigShares[i], err = p.Sign(message, commitments) + commitmentID := commitments.Get(p.Identifier()).CommitmentID + sigShares[i], err = p.Sign(commitmentID, message, commitments) if err != nil { t.Fatal(err) } } // Final step: aggregate - signature := configuration.AggregateSignatures(message, sigShares, commitments) - if !configuration.VerifySignature(message, signature) { - t2.Fatal() + signature := test.AggregateSignatures(message, sigShares, commitments, groupPublicKey) + if !test.VerifySignature(message, signature, groupPublicKey) { + t.Fatal() } // Sanity Check - groupSecretKey, err := configuration.RecoverGroupSecret(keyShares) + groupSecretKey, err := debug.RecoverGroupSecret(g, keyShares) if err != nil { t.Fatal(err) } - fmt.Println(groupSecretKey.Hex()) - - singleSig := configuration.Sign(message, groupSecretKey) - if !configuration.VerifySignature(message, singleSig) { - t2.Fatal() + singleSig := test.Sign(message, groupSecretKey) + if !test.VerifySignature(message, singleSig, groupPublicKey) { + t.Fatal() } }) } -func testAll(t *testing.T, f func(*testing.T, *frost.Configuration)) { - for _, test := range configurationTable { - t.Run(string(test.Ciphersuite.ContextString), func(t *testing.T) { +func testAll(t *testing.T, f func(*testing.T, *tableTest)) { + for _, test := range testTable { + t.Run(string(test.Configuration.ContextString), func(t *testing.T) { f(t, &test) }) } } func TestMaliciousSigner(t *testing.T) { - } diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index ac3de42..989b14a 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -16,6 +16,8 @@ import ( "testing" group "github.com/bytemare/crypto" + secretsharing "github.com/bytemare/secret-sharing" + "github.com/bytemare/frost" ) @@ -179,7 +181,6 @@ type test struct { } type participant struct { - ID uint64 HidingNonce *group.Scalar BindingNonce *group.Scalar HidingNonceCommitment *group.Element @@ -188,6 +189,7 @@ type participant struct { HidingNonceRandomness []byte BindingNonceRandomness []byte BindingFactorInput []byte + ID uint64 } type testRoundOneOutputs struct { @@ -238,7 +240,7 @@ func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { GroupSecretKey: decodeScalar(t, g, i.GroupSecretKey), GroupPublicKey: decodeElement(t, g, i.GroupPublicKey), Message: i.Message, - SharePolynomialCoefficients: make([]*group.Scalar, len(i.SharePolynomialCoefficients)), + SharePolynomialCoefficients: make([]*group.Scalar, len(i.SharePolynomialCoefficients)+1), Participants: make([]*frost.KeyShare, len(i.ParticipantShares)), ParticipantList: make([]uint64, len(i.ParticipantList)), } @@ -247,14 +249,23 @@ func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { input.ParticipantList[j] = id } + input.SharePolynomialCoefficients[0] = input.GroupSecretKey for j, coeff := range i.SharePolynomialCoefficients { - input.SharePolynomialCoefficients[j] = decodeScalar(t, g, coeff) + input.SharePolynomialCoefficients[j+1] = decodeScalar(t, g, coeff) } for j, p := range i.ParticipantShares { + secret := decodeScalar(t, g, p.ParticipantShare) + public := g.Base().Multiply(secret) input.Participants[j] = &frost.KeyShare{ - ID: p.Identifier, - Secret: decodeScalar(t, g, p.ParticipantShare), + Secret: secret, + GroupPublicKey: input.GroupPublicKey, + PublicKeyShare: secretsharing.PublicKeyShare{ + PublicKey: public, + Commitment: nil, + ID: p.Identifier, + Group: g, + }, } } diff --git a/tests/vectors_test.go b/tests/vectors_test.go index c78a557..b59fdec 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -14,48 +14,31 @@ import ( "fmt" "os" "path/filepath" - "slices" "testing" + group "github.com/bytemare/crypto" + "github.com/bytemare/frost" + "github.com/bytemare/frost/debug" ) -func (v test) test(t *testing.T) { +func (v test) testTrustedDealer(t *testing.T) ([]*frost.KeyShare, *group.Element) { g := v.Config.Ciphersuite.Group - coeffs := v.Inputs.SharePolynomialCoefficients - - keyShares, dealerGroupPubKey, secretsharingCommitment, err := frost.TrustedDealerKeygen( - g, + keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( + frost.Ciphersuite(g), v.Inputs.GroupSecretKey, v.Config.MaxParticipants, v.Config.MinParticipants, - coeffs...) - if err != nil { - t.Fatal(err) - } + v.Inputs.SharePolynomialCoefficients...) if len(secretsharingCommitment) != v.Config.MinParticipants { t.Fatalf( "%d / %d", len(secretsharingCommitment), v.Config.MinParticipants) } - // Check whether key shares are the same - cpt := len(keyShares) - for _, p := range keyShares { - for _, p2 := range v.Inputs.Participants { - if p2.Identifier() == p.Identifier() && p2.SecretKey().Equal(p.Secret) == 1 { - cpt-- - } - } - } - - if cpt != 0 { - t.Fatal("Some key shares do not match.") - } - // Test recovery of the full secret signing key. - recoveredKey, err := v.Config.Configuration.RecoverGroupSecret(keyShares) + recoveredKey, err := debug.RecoverGroupSecret(g, keyShares) if err != nil { t.Fatal(err) } @@ -64,7 +47,11 @@ func (v test) test(t *testing.T) { t.Fatal() } - groupPublicKey, participantPublicKey := frost.DeriveGroupInfo(g, v.Config.MaxParticipants, secretsharingCommitment) + groupPublicKey, participantPublicKey := debug.RecoverPublicKeys( + g, + v.Config.MaxParticipants, + secretsharingCommitment, + ) if len(participantPublicKey) != v.Config.MaxParticipants { t.Fatal() } @@ -74,17 +61,38 @@ func (v test) test(t *testing.T) { } for i, shareI := range keyShares { - if !frost.VerifyVSS(g, shareI, secretsharingCommitment) { + if !debug.VerifyVSS(g, shareI, secretsharingCommitment) { t.Fatal(i) } } + return keyShares, dealerGroupPubKey +} + +func (v test) test(t *testing.T) { + keyShares, groupPublicKey := v.testTrustedDealer(t) + + // Check whether key shares are the same + cpt := len(keyShares) + for _, p := range keyShares { + for _, p2 := range v.Inputs.Participants { + if p2.Identifier() == p.Identifier() && p2.SecretKey().Equal(p.Secret) == 1 { + cpt-- + } + } + } + + if cpt != 0 { + t.Fatal("Some key shares do not match.") + } + + c := frost.Ciphersuite(v.Config.Group) + // Create participants participants := make(ParticipantList, len(keyShares)) conf := v.Config - conf.GroupPublicKey = groupPublicKey for i, keyShare := range keyShares { - participants[i] = conf.Participant(keyShare) + participants[i] = c.Participant(keyShare) } // Round One: Commitment @@ -110,10 +118,10 @@ func (v test) test(t *testing.T) { commitment := p.Commit() - if p.Nonce[0].Equal(pv.HidingNonce) != 1 { + if p.Nonces[commitment.CommitmentID][0].Equal(pv.HidingNonce) != 1 { t.Fatal(i) } - if p.Nonce[1].Equal(pv.BindingNonce) != 1 { + if p.Nonces[commitment.CommitmentID][1].Equal(pv.BindingNonce) != 1 { t.Fatal(i) } if commitment.HidingNonce.Equal(pv.HidingNonceCommitment) != 1 { @@ -126,16 +134,6 @@ func (v test) test(t *testing.T) { commitmentList[i] = commitment } - //_, rhoInputs := commitmentList.ComputeBindingFactors( - // v.Config.Ciphersuite, - // v.Inputs.Message, - //) - //for i, rho := range rhoInputs { - // if !bytes.Equal(rho, v.RoundOneOutputs.Outputs[i].BindingFactorInput) { - // t.Fatal() - // } - //} - // Round two: sign sigShares := make([]*frost.SignatureShare, len(v.RoundTwoOutputs.Outputs)) for i, share := range v.RoundTwoOutputs.Outputs { @@ -144,33 +142,33 @@ func (v test) test(t *testing.T) { t.Fatal(i) } - sigShares[i], err = p.Sign(v.Inputs.Message, commitmentList) + commitment := commitmentList.Get(p.Identifier()) + commitmentID := commitment.CommitmentID + + var err error + sigShares[i], err = p.Sign(commitmentID, v.Inputs.Message, commitmentList) if err != nil { t.Fatal(err) } - j := slices.IndexFunc(commitmentList, func(commitment *frost.Commitment) bool { - return commitment.Identifier == p.KeyShare.Identifier() - }) - - if !conf.Configuration.VerifySignatureShare(commitmentList[j], v.Inputs.Message, sigShares[i], commitmentList) { - t.Fatal() - } - // Check against vector if share.SignatureShare.Equal(sigShares[i].SignatureShare) != 1 { t.Fatalf("%s\n%s\n", share.SignatureShare.Hex(), sigShares[i].SignatureShare.Hex()) } + + if err := v.Config.VerifySignatureShare(commitment, v.Inputs.Message, sigShares[i], commitmentList, groupPublicKey); err != nil { + t.Fatalf("signature share matched but verification failed: %s", err) + } } // AggregateSignatures - sig := v.Config.AggregateSignatures(v.Inputs.Message, sigShares, commitmentList) + sig := v.Config.AggregateSignatures(v.Inputs.Message, sigShares, commitmentList, groupPublicKey) if !bytes.Equal(sig.Encode(), v.FinalOutput) { t.Fatal() } // Sanity Check - if !conf.VerifySignature(v.Inputs.Message, sig) { + if !conf.VerifySignature(v.Inputs.Message, sig, groupPublicKey) { t.Fatal() } } From 301f23f847cad2540116e7b958346940619c57ed Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 30 Jul 2024 01:09:18 +0200 Subject: [PATCH 03/31] test with dkg and trusted dealer Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- tests/dkg_test.go | 4 +-- tests/frost_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/tests/dkg_test.go b/tests/dkg_test.go index c77c67c..187741f 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -23,7 +23,7 @@ func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, return ps } -func simulateDKG(t *testing.T, g group.Group, maxSigners, threshold int) ([]*frost.KeyShare, *group.Element) { +func runDKG(t *testing.T, g group.Group, maxSigners, threshold int) ([]*frost.KeyShare, *group.Element, []*group.Element) { c := dkg.Ciphersuite(g) // valid r1DataSet set with and without own package @@ -88,5 +88,5 @@ func simulateDKG(t *testing.T, g group.Group, maxSigners, threshold int) ([]*fro keyShares = append(keyShares, (*frost.KeyShare)(keyShare)) } - return keyShares, pubKey + return keyShares, pubKey, nil } diff --git a/tests/frost_test.go b/tests/frost_test.go index 64f5598..adf4b1f 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -124,15 +124,74 @@ func TestTrustedDealerKeygen(t *testing.T) { }) } -func TestFrost(t *testing.T) { +func TestFrost_WithTrustedDealer(t *testing.T) { maxSigner := 3 threshold := 2 message := []byte("test") testAll(t, func(t *testing.T, test *tableTest) { g := test.Ciphersuite.Group() + sk := g.NewScalar().Random() - keyShares, groupPublicKey := simulateDKG(t, g, maxSigner, threshold) + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, sk, maxSigner, threshold) + + // Create Participants + participants := make(ParticipantList, threshold) + for i, share := range keyShares[:threshold] { + participants[i] = test.Ciphersuite.Participant(share) + } + + // Round One: Commitment + commitments := make(frost.CommitmentList, threshold) + for i, p := range participants { + commitments[i] = p.Commit() + } + + commitments.Sort() + + // Round Two: Sign + sigShares := make([]*frost.SignatureShare, threshold) + for i, p := range participants { + var err error + commitmentID := commitments.Get(p.Identifier()).CommitmentID + sigShares[i], err = p.Sign(commitmentID, message, commitments) + if err != nil { + t.Fatal(err) + } + } + + // Final step: aggregate + signature := test.AggregateSignatures(message, sigShares, commitments, groupPublicKey) + if !test.VerifySignature(message, signature, groupPublicKey) { + t.Fatal() + } + + // Sanity Check + groupSecretKey, err := debug.RecoverGroupSecret(g, keyShares) + if err != nil { + t.Fatal(err) + } + + if groupSecretKey.Equal(sk) != 1 { + t.Fatal("expected equality in group secret key") + } + + singleSig := test.Sign(message, groupSecretKey) + if !test.VerifySignature(message, singleSig, groupPublicKey) { + t.Fatal() + } + }) +} + +func TestFrost_WithDKG(t *testing.T) { + maxSigner := 3 + threshold := 2 + message := []byte("test") + + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Ciphersuite.Group() + + keyShares, groupPublicKey, _ := runDKG(t, g, maxSigner, threshold) // Create Participants participants := make(ParticipantList, threshold) From a5ded8bc36469e44086df12ccdd0904e2b5f9e7e Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:43:47 +0200 Subject: [PATCH 04/31] update-ci Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- .github/.golangci.yml | 203 ------------------ .github/CODE_OF_CONDUCT.md | 57 ----- .github/CONTRIBUTING.md | 34 --- .github/ISSUE_TEMPLATE/bug-report.md | 45 ---- .github/ISSUE_TEMPLATE/config.yml | 5 - .github/Makefile | 53 ----- .../pull_request_template.md | 34 --- .github/SECURITY.md | 10 - .github/licence-header.tmpl | 7 - .github/workflows/ci.yml | 84 -------- .github/workflows/codeql.yml | 39 ---- .github/workflows/scorecards.yml | 55 ----- 12 files changed, 626 deletions(-) delete mode 100644 .github/.golangci.yml delete mode 100644 .github/CODE_OF_CONDUCT.md delete mode 100644 .github/CONTRIBUTING.md delete mode 100644 .github/ISSUE_TEMPLATE/bug-report.md delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/Makefile delete mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md delete mode 100644 .github/SECURITY.md delete mode 100644 .github/licence-header.tmpl delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/codeql.yml delete mode 100644 .github/workflows/scorecards.yml diff --git a/.github/.golangci.yml b/.github/.golangci.yml deleted file mode 100644 index 03900c6..0000000 --- a/.github/.golangci.yml +++ /dev/null @@ -1,203 +0,0 @@ -linters: - disable-all: true - enable: - - asciicheck - - bidichk - - bodyclose - - containedctx - - contextcheck - - cyclop - - deadcode - #- depguard - - dogsled - - dupl - - durationcheck - - errcheck - - errname - - errorlint - - exportloopref - - forbidigo - - forcetypeassert - - funlen - - gocognit - - goconst - - gocritic - - gocyclo - - godot - - godox - - goerr113 - - gofmt - - gofumpt - - goheader - - goimports - - gomodguard - - gomoddirectives - - goprintffuncname - - gosec - - gosimple - - govet - - ifshort - - importas - - ineffassign - - lll - - makezero - #- maligned - - megacheck - - misspell - - nakedret - - nestif - - nilerr - - nilnil - - noctx - - nolintlint - - paralleltest - - prealloc - - predeclared - - revive - - rowserrcheck - - sqlclosecheck - - staticcheck - - structcheck - - stylecheck - #- tagliatelle - - tenv - - testpackage - - thelper - - tparallel - - typecheck - - unconvert - - unparam - - unused - - varcheck - #- varnamelen - - wastedassign - - whitespace - #- wrapcheck - - wsl - #- exhaustive - #- exhaustivestruct - #- gci - #- gochecknoglobals - #- gochecknoinits - #- gomnd - #- nlreturn - presets: - - bugs - - unused - fast: false - -linters-settings: - dupl: - threshold: 100 - errcheck: - check-type-assertions: true - check-blank: true - funlen: - lines: 100 - statements: 50 - gocognit: - min-complexity: 15 - goconst: - min-len: 2 - min-occurrences: 2 - gocritic: - enabled-tags: - - diagnostic - - experimental - - opinionated - - performance - - style - gocyclo: - min-complexity: 15 - godox: - keywords: - - NOTE - - OPTIMIZE - - HACK - - TODO - - todo - gofmt: - simplify: true - goimports: - local-prefixes: github.com/bytemare/frost - gomnd: - settings: - mnd: - checks: - - argument - - case - - condition - - operation - - return - - assign - govet: - check-shadowing: true - - # settings per analyzer - settings: - printf: # analyzer name, run `go tool vet help` to see all analyzers - funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - - # enable or disable analyzers by name - enable: - - atomicalign - disable-all: false - lll: - line-length: 120 - # tab width ('\t') in spaces. Default to 1. - tab-width: 4 - maligned: - suggest-new: true - misspell: - locale: US - prealloc: - simple: false - for-loops: true - unused: - check-exported: false - whitespace: - multi-if: false - multi-func: false - wsl: - # Allow declarations (var) to be cuddled. - allow-cuddle-declarations: true - # Allow trailing comments in ending of blocks - allow-trailing-comment: false - # Force newlines in end of case at this limit (0 = never). - force-case-trailing-whitespace: 0 - # Force cuddling of err checks with err var assignment - force-err-cuddling: true - # Allow leading comments to be separated with empty liens - allow-separated-leading-comment: false - -issues: - # List of regexps of issue texts to exclude, empty list by default. - # But independently from this option we use default exclude patterns, - # it can be disabled by `exclude-use-default: false`. To list all - # excluded by default patterns execute `golangci-lint run --help` - exclude: - - "should have a package comment, unless it's in another file for this package" - - exclude-rules: - - path: ./* - linters: - - exhaustive - - max-issues-per-linter: 0 - max-same-issues: 0 - - # Independently from option `exclude` we use default exclude patterns, - # it can be disabled by this option. To list all - # excluded by default patterns execute `golangci-lint run --help`. - # Default value for this option is true. - exclude-use-default: false - -run: - tests: false - -output: - format: github-actions \ No newline at end of file diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 9e07407..0000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,57 +0,0 @@ - -# Contributor Covenant Code of Conduct [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](code_of_conduct.md) - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual identity -and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the - overall community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or - advances of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email - address, without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.0, available at -[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available -at [https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index 8e40d2f..0000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,34 +0,0 @@ -# How to contribute - -### Did you find a bug? 🐞 - -* 🔎 Please ensure your findings have not already been reported by searching on the project repository under [Issues](https://github.com/bytemare/frost). -* If you think your findings can be complementary to an existing issue, don't hesitate to join the conversation 😃☕ -* If there's no issue addressing the problem, [open a new one](https://github.com/bytemare/frost/issues/new). Please be clear in the title and description, and add relevant information. Bonus points if you provide a **code sample** and everything needed to reproduce the issue when expected behaviour is not occurring. -* If possible, use the relevant issue templates. - -### Do you have a fix? - -🎉 That's awesome! Pull requests are welcome! - -* Please [open an issue](https://github.com/bytemare/frost) beforehand, so we can discuss this. -* Fork this repo from `main`, and ensure your fork is up-to-date with it when submitting the PR. -* If your changes impact the documentation, please update it accordingly. -* If you added code that impact tests, please add tests with relevant coverage and test cases. Bonus points for fuzzing. -* 🛠️ Make sure the test suite passes. - -If your changes might have an impact on performance, please benchmark your code and measure the impact, share the results and the tests that lead to these results. - -Please note that changes that are purely cosmetic and do not add anything substantial to the stability, functionality, or testability of the project may not be accepted. - -### Coding Convention - -This project tries to be as Go idiomatic as possible. Conventions from [Effective Go](https://golang.org/doc/effective_go) apply here. Tests use a very opinionated linting configuration that you should use before committing to your changes. - -### Licence - -By contributing to this project, you agree that your contributions will be licensed under the project's [License](https://github.com/bytemare/template/blob/main/LICENSE). - -All contributions (including pull requests) must agree to the [Developer Certificate of Origin (DCO) version 1.1](http://developercertificate.org). It states that the contributor has the right to submit the patch for inclusion into the project. Simply submitting a contribution implies this agreement, however, please include the "Signed-off-by" git tag in every commit (this tag is a conventional way to confirm that you agree to the DCO). - -Thanks! :heart: \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index 915c281..0000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -name: "\U0001F41E Bug report" -about: Create a report to help us improve -title: "[BUG]" -labels: bug -assignees: bytemare - ---- - - - -### Describe the bug -A clear and concise description of what the bug is. - -### Your setup - -**What version/commit of the project are you using?** - -**What version of go are you using?** -
-$ go version
-
-
- -**What does the go environment look like?** -
go env Output
-$ go env
-
-
- -**If relevant, what parameters or arguments are you using?** - -### Reproducing - -**What did you do?** -Steps to reproduce the behavior: -1. '....' - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Additional context** -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index f84c297..0000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: -- name: Questions, feature requests, and more 💬 - url: https://github.com/bytemare/frost/discussions - about: Do you need help? Did you make something with frost? Do you have an idea? Tell us about it! \ No newline at end of file diff --git a/.github/Makefile b/.github/Makefile deleted file mode 100644 index 9829570..0000000 --- a/.github/Makefile +++ /dev/null @@ -1,53 +0,0 @@ -GH_ACTIONS = workflows - -.PHONY: update -update: - @cd ../ - @echo "Updating dependencies..." - @go mod tidy - @echo "Updating Github Actions pins..." - @$(foreach file, $(wildcard workflows/*.yml), pin-github-action $(file);) - -.PHONY: update-linters -update-linters: - @echo "Updating linters..." - @go install mvdan.cc/gofumpt@latest - @go install github.com/daixiang0/gci@latest - @go install github.com/segmentio/golines@latest - @go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest - @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin - -.PHONY: fmt -fmt: - @echo "Formatting ..." - @go mod tidy - @go fmt ../... - @golines -m 120 -t 4 -w ../ - @gofumpt -w -extra ../ - @gci write -s Standard -s Default -s "Prefix($(shell go list -m))" ../ - @fieldalignment -fix ../... - -.PHONY: license -license: - @echo "Checking License headers ..." - @if addlicense -check -v -skip yml -skip yaml -f licence-header.tmpl ../*; then echo "License headers OK"; else return 1; fi; - -.PHONY: lint -lint: fmt license - @echo "Linting ..." - @if golangci-lint run --config=.golangci.yml ../...; then echo "Linting OK"; else return 1; fi; - -.PHONY: test -test: - @echo "Running all tests ..." - @go test -v -vet=all ../tests - -.PHONY: vectors -vectors: - @echo "Testing vectors ..." - @go test -v ../tests/vectors_test.go - -.PHONY: cover -cover: - @echo "Testing with coverage ..." - @go test -v -race -covermode=atomic -coverpkg=../... -coverprofile=../coverage.out ../tests diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md deleted file mode 100644 index 0c4ba8a..0000000 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ /dev/null @@ -1,34 +0,0 @@ - - -### Description - - -### Related Issue - - - - - -### Motivation and Context - - -### How Has This Been Tested? - - - - -### Types of changes - -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to change) - -### Checklist: - - -- [ ] My code follows the code style of this project. -- [ ] My change requires a change to the documentation. -- [ ] I have updated the documentation accordingly. -- [ ] I have read the **CONTRIBUTING** document. -- [ ] I have added tests to cover my changes. -- [ ] All new and existing tests passed. diff --git a/.github/SECURITY.md b/.github/SECURITY.md deleted file mode 100644 index ac9af76..0000000 --- a/.github/SECURITY.md +++ /dev/null @@ -1,10 +0,0 @@ -# Security Policy - -## Supported Versions - -Only the latest version will be benefit from security fixes. Maintainers of projects using this package are invited to update their dependency. - -## Reporting a Vulnerability - -Vulnerabilities can be reported through GitHub issues, here: https://github.com/bytemare/frost/security/advisories -If the issue is sensitive enough that the reporter thinks the discussion needs more confidentiality, we can discuss options there (e.g. On a Security Advisory or per e-mail). diff --git a/.github/licence-header.tmpl b/.github/licence-header.tmpl deleted file mode 100644 index 12c483f..0000000 --- a/.github/licence-header.tmpl +++ /dev/null @@ -1,7 +0,0 @@ -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 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 068bd26..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: FROST -on: - pull_request: - branches: - - main - -permissions: - contents: read - -jobs: - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@master - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@master - with: - go-version-file: ./go.mod - - # Lint - - name: Linting - uses: golangci/golangci-lint-action@5c56cd6c9dc07901af25baab6f2b0d9f3b7c3018 # pin@master - with: - version: latest - args: --config=./.github/.golangci.yml ./... - only-new-issues: true - - test: - name: Test on ${{ matrix.go }} - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - go: [ '1.21' ] - steps: - - name: Checkout repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@master - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@master - with: - go-version: ${{ matrix.go }} - - # Test - - name: Run Tests - run: cd .github && make test - - analyze: - name: Analyze - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # pin@master - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@6edd4406fa81c3da01a34fa6f6343087c207a568 # pin@master - with: - go-version-file: ./go.mod - - # Coverage - - name: Run coverage - run: cd .github && make cover - - # Codecov - - name: Codecov - uses: codecov/codecov-action@29386c70ef20e286228c72b668a06fd0e8399192 # pin@master - with: - file: .github/coverage.out - - # Sonar - - name: SonarCloud Scan - uses: SonarSource/sonarcloud-github-action@5ee47de3c96f0c1c51b09d2ff1fec0cfeefcf67c # pin@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - with: - args: > - -Dsonar.projectKey=bytemare_frost -Dsonar.organization=bytemare-github -Dsonar.sources=. -Dsonar.go.coverage.reportPaths=.github/coverage.out -Dsonar.test.exclusions=examples_test.go,tests/** -Dsonar.tests=tests/ -Dsonar.verbose=true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 1efceab..0000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: "CodeQL" - -on: - pull_request: - branches: - - main - schedule: - - cron: '31 10 * * 0' - -permissions: - contents: read - -jobs: - codeql: - name: CodeQL - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - - strategy: - fail-fast: false - - steps: - - name: Checkout repository - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # pin@master - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@231aa2c8a89117b126725a0e11897209b7118144 # pin@master - with: - languages: go - - - name: Autobuild - uses: github/codeql-action/autobuild@231aa2c8a89117b126725a0e11897209b7118144 # pin@master - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@231aa2c8a89117b126725a0e11897209b7118144 # pin@master diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml deleted file mode 100644 index 239e26e..0000000 --- a/.github/workflows/scorecards.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Scorecards supply-chain security -on: - # Only the default branch is supported. - branch_protection_rule: - schedule: - - cron: '44 9 * * 0' - push: - branches: [ main ] - -# Declare default permissions as read only. -permissions: read-all - -jobs: - analysis: - name: Scorecards analysis - runs-on: ubuntu-latest - permissions: - # Needed to upload the results to code-scanning dashboard. - security-events: write - actions: read - contents: read - - steps: - - name: "Checkout code" - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # pin@master - with: - persist-credentials: false - - - name: "Run analysis" - uses: ossf/scorecard-action@c1aec4ac820532bab364f02a81873c555a0ba3a1 # pin@master - with: - results_file: results.sarif - results_format: sarif - # Read-only PAT token. To create it, - # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation. - repo_token: ${{ secrets.SCORECARD_TOKEN }} - # Publish the results to enable scorecard badges. For more details, see - # https://github.com/ossf/scorecard-action#publishing-results. - # For private repositories, `publish_results` will automatically be set to `false`, - # regardless of the value entered here. - publish_results: true - - # Upload the results as artifacts (optional). - - name: "Upload artifact" - uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # pin@master - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard. - - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # pin@master - with: - sarif_file: results.sarif From f5b849ff5ae38901d95a9c7f8b593a83d2cbbdb0 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:45:32 +0200 Subject: [PATCH 05/31] update CI Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- .github/.golangci.yml | 280 ++++++++++++++++++ .github/CODEOWNERS | 1 + .github/CODE_OF_CONDUCT.md | 58 ++++ .github/CONTRIBUTING.md | 38 +++ .github/ISSUE_TEMPLATE/bug-report.md | 45 +++ .github/ISSUE_TEMPLATE/config.yml | 5 + .github/ISSUE_TEMPLATE/enhancement.md | 24 ++ .github/Makefile | 51 ++++ .../pull_request_template.md | 36 +++ .github/SECURITY.md | 10 + .github/dependabot.yml | 15 + .github/dependency-review.yml | 27 ++ .github/licence-header.tmpl | 7 + .github/renovate.json | 6 + .github/sonar-project.properties | 8 + .github/workflows/code-scan.yml | 36 +++ .github/workflows/codeql.yml | 23 ++ .github/workflows/scorecards.yml | 39 +++ .github/workflows/tests.yml | 22 ++ 19 files changed, 731 insertions(+) create mode 100644 .github/.golangci.yml create mode 100644 .github/CODEOWNERS create mode 100644 .github/CODE_OF_CONDUCT.md create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/enhancement.md create mode 100644 .github/Makefile create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md create mode 100644 .github/SECURITY.md create mode 100644 .github/dependabot.yml create mode 100644 .github/dependency-review.yml create mode 100644 .github/licence-header.tmpl create mode 100644 .github/renovate.json create mode 100644 .github/sonar-project.properties create mode 100644 .github/workflows/code-scan.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/scorecards.yml create mode 100644 .github/workflows/tests.yml diff --git a/.github/.golangci.yml b/.github/.golangci.yml new file mode 100644 index 0000000..e824874 --- /dev/null +++ b/.github/.golangci.yml @@ -0,0 +1,280 @@ +linters: + disable-all: true + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - containedctx + - contextcheck + - copyloopvar + - cyclop + - decorder + #- depguard + - dogsled + - dupl + - dupword + - durationcheck + - err113 + - errcheck + - errchkjson + - errname + - errorlint + - exhaustive + - exhaustruct + - exportloopref + - fatcontext + - forbidigo + - forcetypeassert + - funlen + - gci + - ginkgolinter + - gocheckcompilerdirectives + - gochecknoglobals + - gochecknoinits + - gochecksumtype + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - gofmt + - gofumpt + - goheader + - goimports + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - gosimple + - gosmopolitan + - govet + - grouper + - importas + - inamedparam + - ineffassign + - interfacebloat + - intrange + - ireturn + - lll + - loggercheck + - maintidx + - makezero + - mirror + - misspell + #- mnd + - musttag + - nakedret + - nestif + - nilerr + - nilnil + - nlreturn + - noctx + - nolintlint + #- nonamedreturns + - nosprintfhostport + - paralleltest + - perfsprint + - prealloc + - predeclared + - promlinter + - protogetter + - reassign + - revive + - rowserrcheck + - sloglint + - spancheck + - sqlclosecheck + - staticcheck + - stylecheck + - tagalign + - tagliatelle + - tenv + - testableexamples + - testifylint + - testpackage + - thelper + - tparallel + - typecheck + - unconvert + - unparam + - unused + - usestdlibvars + #- varnamelen + - wastedassign + - whitespace + - wrapcheck + - wsl + - zerologlint + +linters-settings: + cyclop: + max-complexity: 11 + skip-tests: true + dupl: + threshold: 100 + errcheck: + check-type-assertions: true + check-blank: true + #exclude-functions: + # - io/ioutil.ReadFile + # - io.Copy(*bytes.Buffer) + # - io.Copy(os.Stdout) + funlen: + lines: 100 + statements: 50 + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix(github.com/bytemare/frost) # Custom section: groups all imports with the specified Prefix. + skip-generated: true + # Enable custom order of sections. + # If `true`, make the section order the same as the order of `sections`. + # Default: false + custom-order: true + gocognit: + min-complexity: 16 + goconst: + min-len: 2 + min-occurrences: 2 + gocritic: + enabled-tags: + - diagnostic + - experimental + - opinionated + - performance + - style + disabled-checks: + - unnamedResult + gocyclo: + min-complexity: 15 + godox: + keywords: + - NOTE + - OPTIMIZE + - HACK + - TODO + - todo + gofmt: + simplify: true + goimports: + local-prefixes: github.com/bytemare/frost + gosimple: + checks: [ "all" ] + govet: + settings: + shadow: + strict: true + disable-all: true + enable: + - asmdecl + - assign + - atomic + - atomicalign + - bools + - buildtag + - cgocall + - composites + - copylocks + - deepequalerrors + - errorsas + - fieldalignment + - findcall + - framepointer + - httpresponse + - ifaceassert + - loopclosure + - lostcancel + - nilfunc + - nilness + - printf + - reflectvaluecompare + - shadow + - shift + - sigchanyzer + - sortslice + - stdmethods + - stringintconv + - structtag + - testinggoroutine + - tests + - unmarshal + - unreachable + - unsafeptr + - unusedresult + - unusedwrite + lll: + line-length: 120 + # tab width ('\t') in spaces. Default to 1. + tab-width: 4 + misspell: + locale: US + mnd: + checks: + - argument + - case + - condition + - operation + - return + - assign + #ignored-functions: + # - 'nist.setMapping' + # - 'big.NewInt' + # - 'hash2curve.HashToFieldXMD' + nlreturn: + block-size: 2 + prealloc: + simple: false + for-loops: true + whitespace: + multi-if: false + multi-func: false + wsl: + # Allow declarations (var) to be cuddled. + allow-cuddle-declarations: true + # Allow trailing comments in ending of blocks + allow-trailing-comment: false + # Force newlines in end of case at this limit (0 = never). + force-case-trailing-whitespace: 0 + # Force cuddling of err checks with err var assignment + force-err-cuddling: true + # Allow leading comments to be separated with empty liens + allow-separated-leading-comment: false + +issues: + # List of regexps of issue texts to exclude, empty list by default. + # But independently from this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. To list all + # excluded by default patterns execute `golangci-lint run --help` + #exclude: + #- "should have a package comment, unless it's in another file for this package" + #- "do not define dynamic errors, use wrapped static errors instead" + #- "missing cases in switch of type Group: maxID" + + #exclude-rules: + # - path: internal/hash.go + # linters: + # - errcheck + # - path: internal/tag/strings.go + # linters: + # - gosec + + max-issues-per-linter: 0 + max-same-issues: 0 + + # Independently of option `exclude` we use default exclude patterns, + # it can be disabled by this option. To list all + # excluded by default patterns execute `golangci-lint run --help`. + # Default value for this option is true. + exclude-use-default: false + +run: + tests: false + +#output: +# formats: +# - format: github-actions +# show-stats: true \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..3b1e198 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @bytemare \ No newline at end of file diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7ddb17b --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,58 @@ + + +# Contributor Covenant Code of Conduct [![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg)](code_of_conduct.md) + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available +at [https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..02aa318 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# How to contribute + +### Did you find a bug? 🐞 + +* 🔎 Please ensure your findings have not already been reported by searching on the project repository under [Issues](https://github.com/bytemare/frost). +* If you think your findings can be complementary to an existing issue, don't hesitate to join the conversation 😃☕ +* If there's no issue addressing the problem, [open a new one](https://github.com/bytemare/frost/issues/new). Please be clear in the title and description, and add relevant information. Bonus points if you provide a **code sample** and everything needed to reproduce the issue when expected behaviour is not occurring. +* If possible, use the relevant issue templates. + +### Do you have a fix? + +🎉 That's awesome! Pull requests are welcome! + +* Please [open an issue](https://github.com/bytemare/frost) beforehand, so we can discuss this. +* Fork this repo from `main`, and ensure your fork is up-to-date with it when submitting the PR. +* If your changes impact the documentation, please update it accordingly. +* If you added code that impact tests, please add tests with relevant coverage and test cases. Bonus points for fuzzing. +* 🛠️ Make sure the test suite passes. + +If your changes might have an impact on performance, please benchmark your code and measure the impact, share the results and the tests that lead to these results. + +Please note that changes that are purely cosmetic and do not add anything substantial to the stability, functionality, or testability of the project may not be accepted. + +### Coding Convention + +This project tries to be as Go idiomatic as possible. Conventions from [Effective Go](https://golang.org/doc/effective_go) apply here. Tests use a very opinionated linting configuration that you should use before committing to your changes. + +## Governance Model + +This project follows the [Benevolent Dictator Governance Model](http://oss-watch.ac.uk/resources/benevolentdictatorgovernancemodel) where the project owner and lead makes all final decisions. + +### Licence + +By contributing to this project, you agree that your contributions will be licensed under the project's [License](https://github.com/bytemare/template/blob/main/LICENSE). + +All contributions (including pull requests) must agree to the [Developer Certificate of Origin (DCO) version 1.1](https://developercertificate.org). It states that the contributor has the right to submit the patch for inclusion into the project. Simply submitting a contribution implies this agreement, however, please include the "Signed-off-by" git tag in every commit (this tag is a conventional way to confirm that you agree to the DCO). + +Thanks! :heart: \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..915c281 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,45 @@ +--- +name: "\U0001F41E Bug report" +about: Create a report to help us improve +title: "[BUG]" +labels: bug +assignees: bytemare + +--- + + + +### Describe the bug +A clear and concise description of what the bug is. + +### Your setup + +**What version/commit of the project are you using?** + +**What version of go are you using?** +
+$ go version
+
+
+ +**What does the go environment look like?** +
go env Output
+$ go env
+
+
+ +**If relevant, what parameters or arguments are you using?** + +### Reproducing + +**What did you do?** +Steps to reproduce the behavior: +1. '....' + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..eaddbc7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: +- name: Questions, feature requests, and more 💬 + url: https://github.com/bytemare/frost/discussions + about: Do you need help? Did you make something with FROST? Do you have an idea? Tell us about it! \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 0000000..8300dba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,24 @@ +--- +name: "📈 Enhancement" +about: Request or discuss improvements +title: "[Enhancement]" +labels: enhancement +assignees: bytemare + +--- + + + +### Describe the feature + +A clear and concise description of what the enhancement is and what problem it solves. + +**Expected behaviour** + +A clear and concise description of what you expected to happen. + +**Additional context** + +Add any other context about the problem here. diff --git a/.github/Makefile b/.github/Makefile new file mode 100644 index 0000000..e1a5c5b --- /dev/null +++ b/.github/Makefile @@ -0,0 +1,51 @@ +.PHONY: update +update: + @echo "Updating dependencies..." + @cd ../ && go get -u ./... + @go mod tidy + +.PHONY: update-linters +update-linters: + @echo "Updating linters..." + @go install mvdan.cc/gofumpt@latest + @go install github.com/daixiang0/gci@latest + @go install github.com/segmentio/golines@latest + @go install github.com/google/addlicense@latest + @go install golang.org/x/tools/cmd/goimports@latest + @go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin + +.PHONY: fmt +fmt: + @echo "Formatting ..." + @go mod tidy + @go fmt ../... + @golines -m 120 -t 4 -w ../ + @gofumpt -w -extra ../ + @gci write -s Standard -s Default -s "Prefix($(shell go list -m))" ../ + @fieldalignment -fix ../... + +.PHONY: license +license: + @echo "Checking License headers ..." + @if addlicense -check -v -skip yaml -f licence-header.tmpl ../*; then echo "License headers OK"; else return 1; fi; + +.PHONY: lint +lint: fmt license + @echo "Linting ..." + @if golangci-lint run --config=.golangci.yml ../...; then echo "Linting OK"; else return 1; fi; + +.PHONY: test +test: + @echo "Running all tests ..." + @go test -v ../... + +.PHONY: vectors +vectors: + @echo "Testing vectors ..." + @go test -v ../tests/vectors_test.go + +.PHONY: cover +cover: + @echo "Testing with coverage ..." + @go test -v -race -covermode=atomic -coverpkg=../... -coverprofile=./coverage.out ../tests diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..3898f62 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,36 @@ + + +### Description + + +### Related Issue + + + + + + + +### Motivation and Context + + +### How Has This Been Tested? + + + + +### Types of changes + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to change) + +### Checklist: + + +- [ ] I have read the **CONTRIBUTING** document. +- [ ] My code follows the code style of this project. +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. +- [ ] I have added tests to cover my changes. +- [ ] All new and existing tests passed. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..ac9af76 --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,10 @@ +# Security Policy + +## Supported Versions + +Only the latest version will be benefit from security fixes. Maintainers of projects using this package are invited to update their dependency. + +## Reporting a Vulnerability + +Vulnerabilities can be reported through GitHub issues, here: https://github.com/bytemare/frost/security/advisories +If the issue is sensitive enough that the reporter thinks the discussion needs more confidentiality, we can discuss options there (e.g. On a Security Advisory or per e-mail). diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e50a88a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" # Location of package manifests + schedule: + interval: "daily" + assignees: + - "bytemare" + reviewers: + - "bytemare" diff --git a/.github/dependency-review.yml b/.github/dependency-review.yml new file mode 100644 index 0000000..81ba9c1 --- /dev/null +++ b/.github/dependency-review.yml @@ -0,0 +1,27 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, +# PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +name: 'Dependency Review' +on: [pull_request] + +permissions: + contents: read + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 + with: + egress-policy: block + + - name: 'Checkout Repository' + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + - name: 'Dependency Review' + uses: actions/dependency-review-action@0efb1d1d84fc9633afcdaad14c485cbbc90ef46c diff --git a/.github/licence-header.tmpl b/.github/licence-header.tmpl new file mode 100644 index 0000000..12c483f --- /dev/null +++ b/.github/licence-header.tmpl @@ -0,0 +1,7 @@ +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 \ No newline at end of file diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..d466a32 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>bytemare/renovate-config" + ] +} diff --git a/.github/sonar-project.properties b/.github/sonar-project.properties new file mode 100644 index 0000000..1dc64cf --- /dev/null +++ b/.github/sonar-project.properties @@ -0,0 +1,8 @@ +sonar.organization=bytemare +sonar.projectKey=frost +sonar.sources=. +sonar.tests=tests/ +sonar.test.exclusions=examples_test.go,tests/** +sonar.verbose=true +sonar.coverage.exclusions=examples_test.go,tests/** +sonar.go.coverage.reportPaths=coverage.out \ No newline at end of file diff --git a/.github/workflows/code-scan.yml b/.github/workflows/code-scan.yml new file mode 100644 index 0000000..344fd9e --- /dev/null +++ b/.github/workflows/code-scan.yml @@ -0,0 +1,36 @@ +name: Code Scan + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + # random HH:MM to avoid a load spike on GitHub Actions at 00:00 + - cron: '4 1 * * *' + +permissions: {} + +jobs: + Lint: + permissions: + contents: read + uses: bytemare/workflows/.github/workflows/golangci-lint.yml@232148ec449718765bacb8bd4684de41f15b8258 + with: + config-path: ./.github/.golangci.yml + scope: ./... + + Analyze: + permissions: + contents: read + uses: bytemare/workflows/.github/workflows/scan-go.yml@232148ec449718765bacb8bd4684de41f15b8258 + with: + sonar-configuration: .github/sonar-project.properties + coverage-output-file: coverage.out + secrets: + github: ${{ secrets.GITHUB_TOKEN }} + sonar: ${{ secrets.SONAR_TOKEN }} + codecov: ${{ secrets.CODECOV_TOKEN }} + semgrep: ${{ secrets.SEMGREP_APP_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..45e7530 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,23 @@ +name: "CodeQL" + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: '31 10 * * 0' + +permissions: {} + +jobs: + CodeQL: + permissions: + actions: read + contents: read + security-events: write + uses: bytemare/workflows/.github/workflows/codeql.yml@232148ec449718765bacb8bd4684de41f15b8258 + with: + language: go diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml new file mode 100644 index 0000000..d22d1b4 --- /dev/null +++ b/.github/workflows/scorecards.yml @@ -0,0 +1,39 @@ +name: Scorecard Analysis Workflow + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + # Weekly on Saturdays. + - cron: '30 1 * * 6' + +permissions: {} + +jobs: + analysis: + permissions: + # Needed if using Code scanning alerts + security-events: write + # Needed for GitHub OIDC token if publish_results is true + id-token: write + # Needed for nested workflow + actions: read + attestations: read + checks: read + contents: read + deployments: read + issues: read + discussions: read + packages: read + pages: read + pull-requests: read + repository-projects: read + statuses: read + + uses: bytemare/workflows/.github/workflows/scorecard.yml@232148ec449718765bacb8bd4684de41f15b8258 + secrets: + token: ${{ secrets.SCORECARD_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..e408132 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,22 @@ +name: Run Tests + +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: {} + +jobs: + Test: + strategy: + fail-fast: false + matrix: + go: [ '1.22', '1.21' ] + uses: bytemare/workflows/.github/workflows/test-go.yml@232148ec449718765bacb8bd4684de41f15b8258 + with: + command: cd .github && make test + version: ${{ matrix.go }} From e023d90ad863c82f99688561241427a19bcd65a3 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:41:54 +0200 Subject: [PATCH 06/31] clean up, optimizations, refactoring Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- .github/.golangci.yml | 3 +- commitment.go => commitment/commitment.go | 100 +++--- coordinator.go | 184 +++++++++-- debug/debug.go | 67 +++- encoding.go | 352 ++++++++++++++++++---- errors.go | 5 - examples_test.go | 200 ++++++------ frost.go | 193 +++++++++--- go.mod | 4 +- go.sum | 8 +- internal/binding.go | 33 -- internal/ciphersuite.go | 92 ------ internal/configuration.go | 134 ++++++++ internal/core.go | 191 ++++++++++++ internal/errors.go | 15 +- internal/utils.go | 5 - keys.go | 8 + participant.go | 201 ------------ schnorr.go | 86 ------ signer.go | 210 +++++++++++++ tests/dkg_test.go | 18 +- tests/frost_test.go | 236 +++++++-------- tests/vector_utils_test.go | 66 ++-- tests/vectors_test.go | 68 +++-- 24 files changed, 1553 insertions(+), 926 deletions(-) rename commitment.go => commitment/commitment.go (55%) delete mode 100644 errors.go delete mode 100644 internal/binding.go delete mode 100644 internal/ciphersuite.go create mode 100644 internal/configuration.go create mode 100644 internal/core.go delete mode 100644 participant.go delete mode 100644 schnorr.go create mode 100644 signer.go diff --git a/.github/.golangci.yml b/.github/.golangci.yml index e824874..f64a5cd 100644 --- a/.github/.golangci.yml +++ b/.github/.golangci.yml @@ -16,7 +16,7 @@ linters: - dupl - dupword - durationcheck - - err113 + #- err113 - errcheck - errchkjson - errname @@ -149,6 +149,7 @@ linters-settings: - style disabled-checks: - unnamedResult + - sloppyReassign gocyclo: min-complexity: 15 godox: diff --git a/commitment.go b/commitment/commitment.go similarity index 55% rename from commitment.go rename to commitment/commitment.go index 1a0412f..77ef874 100644 --- a/commitment.go +++ b/commitment/commitment.go @@ -6,7 +6,8 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package frost +// Package commitment defines the FROST Signer commitment. +package commitment import ( "encoding/binary" @@ -16,39 +17,49 @@ import ( group "github.com/bytemare/crypto" secretsharing "github.com/bytemare/secret-sharing" - - "github.com/bytemare/frost/internal" ) -var errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") +var ( + errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") + errInvalidCiphersuite = errors.New("ciphersuite not available") +) // Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. type Commitment struct { - PublicKey *group.Element - HidingNonce *group.Element - BindingNonce *group.Element - CommitmentID uint64 - ParticipantID uint64 - Ciphersuite Ciphersuite + HidingNonce *group.Element + BindingNonce *group.Element + CommitmentID uint64 + SignerID uint64 + Group group.Group } -func commitmentEncodedSize(g group.Group) int { - return 1 + 8 + 8 + 3*g.ElementLength() +// Copy returns a new Commitment struct populated with the same values as the receiver. +func (c *Commitment) Copy() *Commitment { + return &Commitment{ + HidingNonce: c.HidingNonce.Copy(), + BindingNonce: c.BindingNonce.Copy(), + CommitmentID: c.CommitmentID, + SignerID: c.SignerID, + Group: c.Group, + } +} + +// EncodedSize returns the byte size of the output of Encode(). +func EncodedSize(g group.Group) uint64 { + return 1 + 8 + 8 + 2*uint64(g.ElementLength()) } // Encode returns the serialized byte encoding of a participant's commitment. func (c *Commitment) Encode() []byte { hNonce := c.HidingNonce.Encode() bNonce := c.BindingNonce.Encode() - pubKey := c.PublicKey.Encode() - out := make([]byte, 9, commitmentEncodedSize(group.Group(c.Ciphersuite))) - out[0] = byte(c.Ciphersuite) + out := make([]byte, 9, EncodedSize(c.Group)) + out[0] = byte(c.Group) binary.LittleEndian.PutUint64(out[1:], c.CommitmentID) - binary.LittleEndian.PutUint64(out[9:], c.ParticipantID) + binary.LittleEndian.PutUint64(out[9:], c.SignerID) copy(out[17:], hNonce) copy(out[17+len(hNonce):], bNonce) - copy(out[17+len(hNonce)+len(bNonce):], pubKey) return out } @@ -59,14 +70,12 @@ func (c *Commitment) Decode(data []byte) error { return errDecodeCommitmentLength } - cs := Ciphersuite(data[0]) - if !cs.Available() { - return internal.ErrInvalidCiphersuite + g := group.Group(data[0]) + if !g.Available() { + return errInvalidCiphersuite } - g := cs.Group() - - if len(data) != commitmentEncodedSize(g) { + if uint64(len(data)) != EncodedSize(g) { return errDecodeCommitmentLength } @@ -84,31 +93,23 @@ func (c *Commitment) Decode(data []byte) error { return fmt.Errorf("invalid encoding of binding nonce: %w", err) } - 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.Group = g c.CommitmentID = cID - c.ParticipantID = pID + c.SignerID = pID c.HidingNonce = hn c.BindingNonce = bn - c.PublicKey = pk return nil } -// CommitmentList is a sortable list of commitments. -type CommitmentList []*Commitment +// List is a sortable list of commitments. +type List []*Commitment func cmpID(a, b *Commitment) int { switch { - case a.ParticipantID < b.ParticipantID: // a < b + case a.SignerID < b.SignerID: // a < b return -1 - case a.ParticipantID > b.ParticipantID: + case a.SignerID > b.SignerID: return 1 default: return 0 @@ -116,39 +117,26 @@ func cmpID(a, b *Commitment) int { } // Sort sorts the list the ascending order of identifiers. -func (c CommitmentList) Sort() { +func (c List) Sort() { slices.SortFunc(c, cmpID) } // IsSorted returns whether the list is sorted in ascending order by identifier. -func (c CommitmentList) IsSorted() bool { +func (c List) IsSorted() bool { return slices.IsSortedFunc(c, cmpID) } -// Encode serializes a whole commitment list. -func (c CommitmentList) Encode(g group.Group) []byte { - var encoded []byte - - for _, l := range c { - 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 in the form of a polynomial. -func (c CommitmentList) Participants(g group.Group) secretsharing.Polynomial { +func (c List) Participants(g group.Group) secretsharing.Polynomial { return secretsharing.NewPolynomialFromListFunc(g, c, func(c *Commitment) *group.Scalar { - return g.NewScalar().SetUInt64(c.ParticipantID) + return g.NewScalar().SetUInt64(c.SignerID) }) } // 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 { +func (c List) Get(identifier uint64) *Commitment { for _, com := range c { - if com.ParticipantID == identifier { + if com.SignerID == identifier { return com } } diff --git a/coordinator.go b/coordinator.go index b5b70bb..0000e36 100644 --- a/coordinator.go +++ b/coordinator.go @@ -9,12 +9,24 @@ package frost import ( + "errors" + "fmt" + group "github.com/bytemare/crypto" + "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/internal" ) -// AggregateSignatures allows the coordinator to produce the final signature given all signature shares. +var errInvalidSignature = errors.New("invalid Signature") + +// Signature represent a Schnorr signature. +type Signature struct { + R *group.Element + Z *group.Scalar +} + +// AggregateSignatures allows a 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 // coordinator must abort the protocol, as the resulting signature will be invalid. @@ -22,62 +34,174 @@ import ( // // 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, +// a reasonable approach is to remove the signer from the set of allowed participants in future runs of FROST. If verify +// is set to true, AggregateSignatures will automatically verify the signature shares and produced signatures, and will +// return an error with the first encountered invalid signature share. +func (c *Configuration) AggregateSignatures( + message []byte, sigShares []*SignatureShare, - coms CommitmentList, - publicKey *group.Element, -) *Signature { - coms.Sort() + commitments commitment.List, + verify bool, +) (*Signature, error) { + if !c.verified { + if err := c.verify(); err != nil { + return nil, err + } + } - // Compute binding factors - bindingFactorList := c.computeBindingFactors(publicKey, coms, msg) + groupCommitment, bindingFactors, err := c.prepSigShareCheck(message, commitments, c.GroupPublicKey) + if err != nil { + return nil, err + } - // Compute group commitment - groupCommitment := computeGroupCommitment(c.Group, coms, bindingFactorList) + if verify { + for _, share := range sigShares { + if err = c.verifySignatureShare(share, message, commitments, + groupCommitment, bindingFactors); err != nil { + return nil, err + } + } + } - // Compute aggregate signature - z := c.Group.NewScalar() + // Aggregate signatures + z := group.Group(c.Ciphersuite).NewScalar() for _, share := range sigShares { z.Add(share.SignatureShare) } - return &Signature{ + signature := &Signature{ R: groupCommitment, Z: z, } + + if verify { + if err = VerifySignature(c.Ciphersuite, message, signature, c.GroupPublicKey); err != nil { + return nil, err + } + } + + return signature, nil } -// VerifySignatureShare verifies a signature share. -// commitment, pki, and sigShareI are, respectively, commitment, public key, and signature share of -// the participant whose share is to be verified. +// VerifySignatureShare verifies a signature share. sigShare is the signer's signature share to be verified. // // The CommitmentList must be sorted in ascending order by identifier. -func (c Configuration) VerifySignatureShare(com *Commitment, - message []byte, +func (c *Configuration) VerifySignatureShare( sigShare *SignatureShare, - commitments CommitmentList, - publicKey *group.Element, + message []byte, + commitments commitment.List, ) error { - if com.ParticipantID != sigShare.Identifier { - return internal.ErrWrongVerificationData + if !c.verified { + if err := c.verify(); err != nil { + return err + } } - bindingFactor, lambdaChall, err := c.do(publicKey, nil, message, commitments, com.ParticipantID) + groupCommitment, bindingFactors, err := c.prepSigShareCheck(message, commitments, c.GroupPublicKey) if err != nil { return err } - // Commitment KeyShare + return c.verifySignatureShare(sigShare, message, commitments, groupCommitment, bindingFactors) +} + +func (c *Configuration) prepSigShareCheck(message []byte, + commitments commitment.List, + groupPublicKey *group.Element, +) (*group.Element, internal.BindingFactors, error) { + if !c.verified { + if err := c.verify(); err != nil { + return nil, nil, err + } + } + + if err := internal.VerifyCommitmentList(c.group, commitments, c.Threshold); err != nil { + return nil, nil, fmt.Errorf("invalid list of commitments: %w", err) + } + + groupCommitment, bindingFactors := internal.GroupCommitmentAndBindingFactors( + c.group, + message, + commitments, + groupPublicKey, + ) + + return groupCommitment, bindingFactors, nil +} + +func (c *Configuration) getSignerPubKey(id uint64) *group.Element { + for _, pks := range c.SignerPublicKeys { + if pks.ID == id { + return pks.PublicKey + } + } + + return nil +} + +func (c *Configuration) verifySignatureShare( + sigShare *SignatureShare, + message []byte, + commitments commitment.List, + groupCommitment *group.Element, + bindingFactors internal.BindingFactors, +) error { + com := commitments.Get(sigShare.SignerIdentifier) + if com == nil { + return fmt.Errorf("commitment not registered for signer %q", sigShare.SignerIdentifier) + } + + pk := c.getSignerPubKey(sigShare.SignerIdentifier) + if pk == nil { + return fmt.Errorf("public key not registered for signer %q", sigShare.SignerIdentifier) + } + + lambdaChall, err := internal.ComputeChallengeFactor( + c.group, + groupCommitment, + nil, + sigShare.SignerIdentifier, + message, + commitments, + c.GroupPublicKey, + ) + if err != nil { + return fmt.Errorf("can't compute challenge: %w", err) + } + + // Commitment KeyShare: r = g(h + b*f + l*s) + bindingFactor := bindingFactors[sigShare.SignerIdentifier] commShare := com.HidingNonce.Copy().Add(com.BindingNonce.Copy().Multiply(bindingFactor)) + r := commShare.Add(pk.Copy().Multiply(lambdaChall)) + l := c.group.Base().Multiply(sigShare.SignatureShare) + + if l.Equal(r) != 1 { + return fmt.Errorf("invalid signature share for signer %d", sigShare.SignerIdentifier) + } + + return nil +} + +// VerifySignature returns whether the signature of the message is valid under publicKey. +func VerifySignature(c Ciphersuite, message []byte, signature *Signature, publicKey *group.Element) error { + g := c.ECGroup() + if g == 0 { + return internal.ErrInvalidCiphersuite + } + + ch := internal.SchnorrChallenge(g, message, signature.R, publicKey) + r := signature.R.Copy().Add(publicKey.Copy().Multiply(ch)) + l := g.Base().Multiply(signature.Z) - // Compute relation values - l := c.Group.Base().Multiply(sigShare.SignatureShare) - r := commShare.Add(com.PublicKey.Multiply(lambdaChall)) + // Clear the cofactor for Edwards25519. + if g == group.Edwards25519Sha512 { + cofactor := group.Edwards25519Sha512.NewScalar().SetUInt64(8) + l.Multiply(cofactor) + r.Multiply(cofactor) + } if l.Equal(r) != 1 { - return internal.ErrInvalidVerificationShare + return errInvalidSignature } return nil diff --git a/debug/debug.go b/debug/debug.go index 068d8cf..94a79e8 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -18,16 +18,18 @@ import ( secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/frost" + "github.com/bytemare/frost/internal" ) -// TrustedDealerKeygen uses Shamir and Verifiable Secret Sharing to create secret shares of an input group secret. +// TrustedDealerKeygen uses Shamir and Verifiable Secret Sharing to create secret shares of an input group secret. If +// secret is not set, a new random secret will be generated. // 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( c frost.Ciphersuite, secret *group.Scalar, - max, min int, + threshold, maxSigners uint64, coeffs ...*group.Scalar, ) ([]*frost.KeyShare, *group.Element, []*group.Element) { g := group.Group(c) @@ -37,14 +39,19 @@ func TrustedDealerKeygen( g.NewScalar().Random() } - privateKeyShares, poly, err := secretsharing.ShardReturnPolynomial(g, secret, uint(min), uint(max), coeffs...) + privateKeyShares, poly, err := secretsharing.ShardReturnPolynomial( + g, + secret, + uint(threshold), + uint(maxSigners), + coeffs...) if err != nil { panic(err) } coms := secretsharing.Commit(g, poly) - shares := make([]*frost.KeyShare, max) + shares := make([]*frost.KeyShare, maxSigners) for i, k := range privateKeyShares { shares[i] = &frost.KeyShare{ Secret: k.Secret, @@ -63,7 +70,13 @@ func TrustedDealerKeygen( // 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) { +func RecoverGroupSecret(c frost.Ciphersuite, keyShares []*frost.KeyShare) (*group.Scalar, error) { + if !c.Available() { + return nil, internal.ErrInvalidCiphersuite + } + + g := group.Group(c) + keys := make([]secretsharing.Share, len(keyShares)) for i, v := range keyShares { keys[i] = v @@ -77,20 +90,50 @@ func RecoverGroupSecret(g group.Group, keyShares []*frost.KeyShare) (*group.Scal 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) { +// Sign returns a Schnorr signature over the message msg with the full secret signing key (as opposed to a key share). +func Sign(c frost.Ciphersuite, msg []byte, key *group.Scalar) (*frost.Signature, error) { + g := c.ECGroup() + if g == 0 { + return nil, internal.ErrInvalidCiphersuite + } + + r := g.NewScalar().Random() + R := g.Base().Multiply(r) + pk := g.Base().Multiply(key) + challenge := internal.SchnorrChallenge(g, msg, R, pk) + z := r.Add(challenge.Multiply(key)) + + return &frost.Signature{ + R: R, + Z: z, + }, nil +} + +// RecoverPublicKeys returns the group public key as well those from all participants, +// if the identifiers are 1, 2, ..., maxSigners. +func RecoverPublicKeys( + c frost.Ciphersuite, + maxSigners uint64, + commitment []*group.Element, +) (*group.Element, []*group.Element, error) { + if !c.Available() { + return nil, nil, internal.ErrInvalidCiphersuite + } + + g := group.Group(c) pk := commitment[0] - keys := make([]*group.Element, max) + keys := make([]*group.Element, maxSigners) - for i := 1; i <= max; i++ { - pki, err := secretsharing.PubKeyForCommitment(g, uint64(i), commitment) + for i := uint64(1); i <= maxSigners; i++ { + pki, err := secretsharing.PubKeyForCommitment(g, i, commitment) if err != nil { - panic(err) + return nil, nil, err } + keys[i-1] = pki } - return pk, keys + return pk, keys, nil } // VerifyVSS allows verification of a participant's secret share given a VSS commitment to the secret polynomial. diff --git a/encoding.go b/encoding.go index b69269a..aef7648 100644 --- a/encoding.go +++ b/encoding.go @@ -1,119 +1,359 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 frost import ( "encoding/binary" + "errors" "fmt" + "math" + "slices" group "github.com/bytemare/crypto" + "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/internal" ) -func noncesEncodedLength(g group.Group, n map[uint64][2]*group.Scalar) int { - nbNonces := len(n) - return nbNonces + nbNonces*2*g.ScalarLength() +const ( + encConf = byte(1) + encSigner = byte(2) + encSigShare = byte(3) + encSig = byte(4) + encPubKeyShare = byte(5) + encNonceCommitment = byte(6) +) + +var errInvalidConfigEncoding = errors.New("invalid values in configuration encoding") + +func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { + eLen := uint64(g.ElementLength()) + sLen := uint64(g.ScalarLength()) + + switch encID { + case encConf: + return 1 + 3*8 + eLen + other[0] + case encSigner: + return other[0] + 2 + 2 + sLen + other[1] + other[2] + case encSigShare: + return 1 + 8 + uint64(g.ScalarLength()) + case encSig: + return eLen + uint64(g.ScalarLength()) + case encPubKeyShare: + return 1 + 8 + 4 + eLen + other[0] + case encNonceCommitment: + return other[0] * (2*sLen + commitment.EncodedSize(g)) + default: + panic("encoded id not recognized") + } } -// Backup serializes the client with its long term values, containing its secret share. -func (p *Participant) Backup() []byte { - g := p.KeyShare.Group - ks := p.KeyShare.Encode() - nLen := noncesEncodedLength(g, p.Nonces) - out := make([]byte, 1, 1+2+2+g.ScalarLength()+len(ks)+nLen) - out[0] = byte(g) - binary.LittleEndian.PutUint16(out[1:3], uint16(len(ks))) - binary.LittleEndian.PutUint16(out[3:5], uint16(len(p.Nonces))) - out = append(out, ks...) - for id, nonces := range p.Nonces { - out = append(out, internal.Concatenate(internal.UInt64LE(id), nonces[0].Encode(), nonces[1].Encode())...) +// Encode serializes the Configuration into a compact byte slice. +func (c *Configuration) Encode() []byte { + g := group.Group(c.Ciphersuite) + pksLen := encodedLength(encPubKeyShare, g, c.Threshold*uint64(g.ElementLength())) + out := make([]byte, 25, encodedLength(encConf, g, uint64(len(c.SignerPublicKeys))*pksLen)) + out[0] = byte(c.group) + binary.LittleEndian.PutUint64(out[1:9], c.Threshold) + binary.LittleEndian.PutUint64(out[9:17], c.MaxSigners) + binary.LittleEndian.PutUint64(out[17:25], uint64(len(c.SignerPublicKeys))) + + out = append(out, c.GroupPublicKey.Encode()...) + + for _, pk := range c.SignerPublicKeys { + out = append(out, pk.Encode()...) } return out } -// Recover attempts to deserialize the encoded backup data into a Participant. -func (p *Participant) Recover(data []byte) error { - if len(data) < 5 { - return internal.ErrInvalidLength +type confHeader struct { + g group.Group + h, t, m, n, length uint64 +} + +func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { + if len(data) <= 25 { + return nil, internal.ErrInvalidLength + } + + cs := Ciphersuite(data[0]) + if !cs.Available() { + return nil, internal.ErrInvalidCiphersuite } g := group.Group(data[0]) - if !Ciphersuite(g).Available() { - return internal.ErrInvalidCiphersuite + n := binary.LittleEndian.Uint64(data[17:25]) + pksLen := encodedLength(encPubKeyShare, g, c.Threshold*uint64(g.ElementLength())) + length := encodedLength(encConf, g, n*(8+pksLen)) + + return &confHeader{ + g: g, + h: 25, + t: binary.LittleEndian.Uint64(data[1:9]), + m: binary.LittleEndian.Uint64(data[9:17]), + n: n, + length: length, + }, nil +} + +func (c *Configuration) decode(header *confHeader, data []byte) error { + if uint64(len(data)) != header.length { + return internal.ErrInvalidLength + } + + if header.t > math.MaxUint || header.m > math.MaxUint { + return errInvalidConfigEncoding + } + + gpk := header.g.NewElement() + if err := gpk.Decode(data[header.h : header.h+uint64(header.g.ElementLength())]); err != nil { + return fmt.Errorf("could not decode group public key: %w", err) + } + + offset := header.h + uint64(header.g.ElementLength()) + pksLen := encodedLength(encPubKeyShare, header.g, header.t*uint64(header.g.ElementLength())) + pks := make([]*PublicKeyShare, header.n) + + for j := range header.n { + pk := new(PublicKeyShare) + if err := pk.Decode(data[offset : offset+pksLen]); err != nil { + return fmt.Errorf("could not decode signer public key share for signer %d: %w", j, err) + } + + offset += pksLen + pks[j] = pk + } + + conf := &Configuration{ + Ciphersuite: Ciphersuite(header.g), + Threshold: header.t, + MaxSigners: header.m, + GroupPublicKey: gpk, + SignerPublicKeys: pks, + } + + if err := conf.verify(); err != nil { + return err + } + + c.Ciphersuite = conf.Ciphersuite + c.Threshold = conf.Threshold + c.MaxSigners = conf.MaxSigners + c.GroupPublicKey = gpk + c.SignerPublicKeys = pks + c.group = group.Group(conf.Ciphersuite) + c.verified = true + + return nil +} + +// Decode deserializes the input data into the configuration, or returns an error. +func (c *Configuration) Decode(data []byte) error { + header, err := c.decodeHeader(data) + if err != nil { + return err + } + + return c.decode(header, data) +} + +// Encode serializes the client with its long term values, containing its secret share. This is useful for saving state +// and backup. +func (s *Signer) Encode() []byte { + g := s.KeyShare.Group + ks := s.KeyShare.Encode() + nbCommitments := len(s.Commitments) + conf := s.configuration.Encode() + out := make( + []byte, + len(conf)+4, + encodedLength( + encSigner, + g, + uint64(len(conf)), + uint64(len(ks)), + uint64(nbCommitments)*encodedLength(encNonceCommitment, g), + ), + ) + copy(out, conf) + binary.LittleEndian.PutUint16(out[len(conf):len(conf)+2], uint16(len(ks))) // key share length + binary.LittleEndian.PutUint16(out[len(conf)+2:len(conf)+4], uint16(nbCommitments)) // number of commitments + out = append(out, s.Lambda.Encode()...) + out = append(out, ks...) // key share + + for id, com := range s.Commitments { + out = append(out, internal.Concatenate(internal.UInt64LE(id), + com.HidingNonceS.Encode(), + com.BindingNonceS.Encode(), + com.Commitment.Encode())...) + } + + return out +} + +// Decode attempts to deserialize the encoded backup data into the Signer. +func (s *Signer) Decode(data []byte) error { + conf := new(Configuration) + + header, err := conf.decodeHeader(data) + if err != nil { + return err } - ksLen := int(binary.LittleEndian.Uint16(data[1:3])) - nN := int(binary.LittleEndian.Uint16(data[3:5])) - nLen := nN + nN*2*g.ScalarLength() + if err = conf.decode(header, data[:header.length]); err != nil { + return err + } - if len(data) != 1+2+2+g.ScalarLength()+ksLen+nLen { + if uint64(len(data)) <= header.length+4 { return internal.ErrInvalidLength } + ksLen := uint64(binary.LittleEndian.Uint16(data[header.length : header.length+2])) + nCommitments := uint64(binary.LittleEndian.Uint16(data[header.length+2 : header.length+4])) + g := conf.group + nLen := encodedLength(encNonceCommitment, g) + + length := encodedLength(encSigner, g, header.length, ksLen, nCommitments*nLen) + if uint64(len(data)) != length { + return internal.ErrInvalidLength + } + + offset := header.length + 4 + lambda := g.NewScalar() - if err := lambda.Decode(data[5 : 5+g.ScalarLength()]); err != nil { + if err := lambda.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { return fmt.Errorf("failed to decode key share: %w", err) } + offset += uint64(g.ScalarLength()) keyShare := new(KeyShare) - if err := keyShare.Decode(data[5+g.ScalarLength() : 5+g.ScalarLength()+ksLen]); err != nil { + + if err := keyShare.Decode(data[offset : offset+ksLen]); err != nil { return fmt.Errorf("failed to decode key share: %w", err) } - step := 8 + 2*g.ScalarLength() + offset += ksLen + commitments := make(map[uint64]*NonceCommitment) + + for offset < uint64(len(data)) { + id := binary.LittleEndian.Uint64(data[offset : offset+8]) + offset += 8 + + hs := g.NewScalar() + if err = hs.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { + return fmt.Errorf("can't decode hiding nonce for commitment %d: %w", id, err) + } - nonces := make(map[uint64][2]*group.Scalar) - for i := 5 + g.ScalarLength() + ksLen; i < len(data); i += step { - id := binary.LittleEndian.Uint64(data[i : i+8]) + offset += uint64(g.ScalarLength()) - n0 := g.NewScalar() - if err := n0.Decode(data[i+8 : i+8+g.ScalarLength()]); err != nil { - return err + bs := g.NewScalar() + if err = bs.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { + return fmt.Errorf("can't decode binding nonce for commitment %d: %w", id, err) } - n1 := g.NewScalar() - if err := n1.Decode(data[i+8 : i+8+g.ScalarLength()]); err != nil { - return err + offset += uint64(g.ScalarLength()) + + com := new(commitment.Commitment) + if err = com.Decode(data[offset : offset+nLen]); err != nil { + return fmt.Errorf("can't decode nonce commitment %d: %w", id, err) } - nonces[id] = [2]*group.Scalar{n0, n1} + offset += nLen + + commitments[id] = &NonceCommitment{ + HidingNonceS: hs, + BindingNonceS: bs, + Commitment: com, + } } - p.KeyShare = keyShare - p.Lambda = lambda - p.Nonces = nonces - p.Configuration = *Ciphersuite(g).Configuration() + s.KeyShare = keyShare + s.Lambda = lambda + s.Commitments = commitments + s.configuration = conf return nil } // Encode returns a compact byte encoding of the signature share. -func (s SignatureShare) Encode() []byte { +func (s *SignatureShare) Encode() []byte { share := s.SignatureShare.Encode() - out := make([]byte, 8+len(share)) - copy(out, internal.UInt64LE(s.Identifier)) + out := make([]byte, 1+8+s.group.ScalarLength()) + out[0] = byte(s.group) + binary.LittleEndian.PutUint64(out[1:9], s.SignerIdentifier) copy(out[8:], share) return out } -// DecodeSignatureShare takes a byte string and attempts to decode it to return the signature share. -func (c Configuration) DecodeSignatureShare(data []byte) (*SignatureShare, error) { - g := c.Ciphersuite.Group +// Decode takes a byte string and attempts to decode it to return the signature share. +func (s *SignatureShare) Decode(data []byte) error { + if len(data) < 1 { + return internal.ErrInvalidLength + } - if len(data) != 8+g.ScalarLength() { - return nil, errDecodeSignatureShare + c := Ciphersuite(data[0]) + + g := c.ECGroup() + if g == 0 { + return internal.ErrInvalidCiphersuite } - s := &SignatureShare{ - Identifier: internal.UInt64FromLE(data[:8]), - SignatureShare: g.NewScalar(), + if len(data) != 1+8+g.ScalarLength() { + return internal.ErrInvalidLength + } + + share := g.NewScalar() + if err := share.Decode(data[9:]); err != nil { + return fmt.Errorf("failed to decode signature share: %w", err) } - if err := s.SignatureShare.Decode(data[8:]); err != nil { - return nil, fmt.Errorf("failed to decode signature share: %w", err) + s.group = g + s.SignerIdentifier = binary.LittleEndian.Uint64(data[:1:9]) + s.SignatureShare = share + + return nil +} + +// Encode serializes the signature into a byte string. +func (s *Signature) Encode() []byte { + r := s.R.Encode() + z := s.Z.Encode() + r = slices.Grow(r, len(z)) + + return append(r, z...) +} + +// Decode attempts to deserialize the encoded input into the signature in the group. +func (s *Signature) Decode(c Ciphersuite, data []byte) error { + if !c.Available() { + return internal.ErrInvalidCiphersuite } - return s, nil + g := group.Group(data[0]) + eLen := g.ElementLength() + + if uint64(len(data)) != encodedLength(encSig, g) { + return internal.ErrInvalidLength + } + + s.R = g.NewElement() + if err := s.R.Decode(data[:eLen]); err != nil { + return fmt.Errorf("invalid signature - decoding R: %w", err) + } + + s.Z = g.NewScalar() + if err := s.Z.Decode(data[eLen:]); err != nil { + return fmt.Errorf("invalid signature - decoding Z: %w", err) + } + + return nil } diff --git a/errors.go b/errors.go deleted file mode 100644 index a5b294b..0000000 --- a/errors.go +++ /dev/null @@ -1,5 +0,0 @@ -package frost - -import "errors" - -var errDecodeSignatureShare = errors.New("failed to decode signature share: invalid length") diff --git a/examples_test.go b/examples_test.go index 84e746f..d534a7c 100644 --- a/examples_test.go +++ b/examples_test.go @@ -12,62 +12,89 @@ import ( "fmt" "github.com/bytemare/frost" + "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/debug" ) -var ( -// participantsGeneratedInDKG []*frost.Participant -// commitment *frost.Commitment -// groupPublicKeyGeneratedInDKG *group.Element -) - // Example_signer shows the execution steps of a FROST participant. func Example_signer() { - maxParticipants := 5 - threshold := 3 + maxSigners := uint64(5) + threshold := uint64(3) message := []byte("example message") ciphersuite := frost.Ristretto255 // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. // This example uses a centralised trusted dealer, but it is strongly recommended to use distributed key generation, // e.g. from github.com/bytemare/dkg, which is compatible with FROST. - secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, maxParticipants, threshold) + secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) // Since we used a centralised key generation, we only take the first key share for our participant. participantSecretKeyShare := secretKeyShares[0] - participant := ciphersuite.Participant(participantSecretKeyShare) - commitments := make(frost.CommitmentList, threshold) - // Step 1: call Commit() on each participant. This will return the participant's single-use commitment. + // At key generation, each participant must send their public key share to the coordinator, and the collection must + // be broadcast to every participant. + publicKeyShares := make([]*frost.PublicKeyShare, len(secretKeyShares)) + for i, sk := range secretKeyShares { + publicKeyShares[i] = sk.Public() + } + + // This is how to set up the configuration for FROST, the same for every signer and the coordinator. + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err != nil { + panic(err) + } + + // Instantiate the participant. + participant, err := configuration.Signer(participantSecretKeyShare) + if err != nil { + panic(err) + } + + // Step 1: call Commit() on each participant. This will return the participant's single-use commitment for a + // a signature. Every commitment has an identifier that must be provided to Sign() to use that commitment. // Send this to the coordinator or all other participants over an authenticated // channel (confidentiality is not required). // A participant keeps an internal state during the protocol run across the two rounds. // A participant can pre-compute multiple commitments in advance: these commitments can be shared, but the // participant keeps an internal state of corresponding values, so it must the same instance or a backup of it using - commitment := participant.Commit() + // the serialization functions. + com := participant.Commit() // Step 2: collect the commitments from the other participants and coordinator-chosen the message to sign, // and finalize by signing the message. - commitments[0] = commitment + commitments := make(commitment.List, threshold) + commitments[0] = com // This is not part of a participant's flow, but we need to collect the commitments of the other participants. { - for i := 1; i < threshold; i++ { - commitments[i] = ciphersuite.Participant(secretKeyShares[i]).Commit() + for i := uint64(1); i < threshold; i++ { + signer, err := configuration.Signer(secretKeyShares[i]) + if err != nil { + panic(err) + } + + commitments[i] = signer.Commit() + } } - // Step 3: The participant receives the commitments from the other signer and the message to sign. - // Sign produce a signature share to be sent back to the coordinator. - // We ignore the error for the demo, but execution MUST be aborted upon errors. - signatureShare, err := participant.Sign(commitment.CommitmentID, message, commitments) + // Step 3: The participant receives the commitments from the other signers and the message to sign. + // Sign produces a signature share to be sent back to the coordinator. + // Execution MUST be aborted upon errors. + signatureShare, err := participant.Sign(com.CommitmentID, message, commitments) if err != nil { panic(err) } // This shows how to verify a single signature share - conf := ciphersuite.Configuration() - if err = conf.VerifySignatureShare(commitment, message, signatureShare, commitments, groupPublicKey); err != nil { + if err = configuration.VerifySignatureShare(signatureShare, message, commitments); err != nil { panic(fmt.Sprintf("signature share verification failed: %s", err)) } @@ -76,90 +103,53 @@ func Example_signer() { // Output: Signing successful. } -// Example_verification shows how to verify a FROST signature produced by multiple signers. -func Example_verification() { - maxParticipants := 5 - threshold := 3 +// Example_coordinator shows how to aggregate signature shares into the final signature, and verify a FROST signature +// produced by multiple signers. +func Example_coordinator() { + maxSigners := uint64(5) + threshold := uint64(3) message := []byte("example message") ciphersuite := frost.Ristretto255 // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. - // This example uses a centralised trusted dealer, but it is strongly recommended to use distributed key generation, - // e.g. from github.com/bytemare/dkg, which is compatible with FROST. - secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, maxParticipants, threshold) + // The following block uses a centralised trusted dealer to do this, but it is strongly recommended to use + // distributed key generation, e.g. from github.com/bytemare/dkg, which is compatible with FROST. + secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) participantSecretKeyShares := secretKeyShares[:threshold] - participants := make([]*frost.Participant, threshold) + participants := make([]*frost.Signer, threshold) - // Create a participant on each instance - for i, ks := range participantSecretKeyShares { - participants[i] = ciphersuite.Participant(ks) + // At key generation, each participant must send their public key share to the coordinator, and the collection must + // be broadcast to every participant. + publicKeyShares := make([]*frost.PublicKeyShare, len(secretKeyShares)) + for i, sk := range secretKeyShares { + publicKeyShares[i] = sk.Public() } - // Pre-commit - commitments := make(frost.CommitmentList, threshold) - for i, p := range participants { - commitments[i] = p.Commit() + // This is how to set up the configuration for FROST, the same for every signer and the coordinator. + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, } - commitments.Sort() + if err := configuration.Init(); err != nil { + panic(err) + } - // Sign - signatureShares := make([]*frost.SignatureShare, threshold) - for i, p := range participants { - var err error - signatureShares[i], err = p.Sign(commitments[i].CommitmentID, message, commitments) + // Create a participant on each instance + for i, ks := range participantSecretKeyShares { + signer, err := configuration.Signer(ks) if err != nil { panic(err) } - } - // A coordinator, proxy, assembles the shares - configuration := ciphersuite.Configuration() - signature := configuration.AggregateSignatures(message, signatureShares, commitments, groupPublicKey) - - // Verify the signature - conf := ciphersuite.Configuration() - success := conf.VerifySignature(message, signature, groupPublicKey) - - if success { - fmt.Println("Signature is valid.") - } else { - fmt.Println("Signature is not valid.") - } - - // Output: Signature is valid. -} - -// Example_coordinator shows the execution steps of a FROST coordinator. -func Example_coordinator() { - /* - The Coordinator is an entity with the following responsibilities: - - 1. Determining which participants will participate (at least MIN_PARTICIPANTS in number); - 2. Coordinating rounds (receiving and forwarding inputs among participants); and - 3. Aggregating signature shares output by each participant, and publishing the resulting signature. - - Note that it is possible to deploy the protocol without a distinguished Coordinator. - */ - maxParticipants := 5 - threshold := 3 - message := []byte("example message") - ciphersuite := frost.Ristretto255 - - // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. - // This example uses a centralised trusted dealer, but it is strongly recommended to use distributed key generation, - // e.g. from github.com/bytemare/dkg, which is compatible with FROST. - secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, maxParticipants, threshold) - participantSecretKeyShares := secretKeyShares[:threshold] - participants := make([]*frost.Participant, threshold) - - // Create a participant on each instance - for i, ks := range participantSecretKeyShares { - participants[i] = ciphersuite.Participant(ks) + participants[i] = signer } // Pre-commit - commitments := make(frost.CommitmentList, threshold) + commitments := make(commitment.List, threshold) for i, p := range participants { commitments[i] = p.Commit() } @@ -176,36 +166,40 @@ func Example_coordinator() { } } - // Set up a coordinator, and assemble the shares. - configuration := ciphersuite.Configuration() - signature := configuration.AggregateSignatures(message, signatureShares, commitments, groupPublicKey) + // Everything above was a simulation of commitment and signing rounds to produce the signature shares. + // The following shows how to aggregate these shares, and if verification fails, how to identify a misbehaving signer. - // Verify the signature and identify potential foul players. - if !configuration.VerifySignature(message, signature, groupPublicKey) { + // The coordinator assembles the shares. If the verify argument is set to true, AggregateSignatures will internally + // verify each signature share and return an error on the first that is invalid. It will also verify whether the + // signature is valid. + signature, err := configuration.AggregateSignatures(message, signatureShares, commitments, true) + if err != nil { + panic(err) + } + + // Verify the signature and identify potential foul players. Note that since we set verify to true when calling + // AggregateSignatures, the following is redundant. + // Anyone can verify the signature given the ciphersuite parameter, message, and the group public key. + if err = frost.VerifySignature(ciphersuite, message, signature, groupPublicKey); err != nil { // At this point one should try to identify which participant's signature share is invalid and act on it. // This verification is done as follows: for _, signatureShare := range signatureShares { - // Verify whether we have the participants commitment - commitmentI := commitments.Get(signatureShare.Identifier) - if commitmentI == nil { - panic("commitment not found") - } - - if err := configuration.VerifySignatureShare(commitmentI, message, signatureShare, commitments, groupPublicKey); err != nil { + if err := configuration.VerifySignatureShare(signatureShare, message, commitments); err != nil { panic( fmt.Sprintf( "participant %v produced an invalid signature share: %s", - signatureShare.Identifier, + signatureShare.SignerIdentifier, err, ), ) } } + fmt.Println(err) panic("Signature verification failed.") } - fmt.Printf("Valid signature for %q.", message) + fmt.Println("Signature is valid.") - // Output: Valid signature for "example message". + // Output: Signature is valid. } diff --git a/frost.go b/frost.go index 4ac8d41..24962a0 100644 --- a/frost.go +++ b/frost.go @@ -10,8 +10,11 @@ package frost import ( + "errors" + "fmt" + "math/big" + group "github.com/bytemare/crypto" - "github.com/bytemare/hash" "github.com/bytemare/frost/internal" ) @@ -29,6 +32,40 @@ import ( - Chu: https://eprint.iacr.org/2023/899 - re-randomize keys: https://eprint.iacr.org/2024/436.pdf +Requirements: +- group MUST be of prime order +- threshold <= max +- max < order +- identifier is in [1:max] and must be distinct form other ids +- each participant MUST know the group public key +- each participant MUST know the pub key of each other +- network channels must be authenticated (confidentiality is not required) +- Signers have local secret data + - secret key and lambda are long term + - committed nonces between commitment and signature + +- When receiving the commitment list, each elements must be deserialized, and upon error, the signer MUST abort the + protocol +- A signer must check whether their id and commitment appear in the commitment list + +- A coordinator aggregates, and then should verify the signature. If signature fails, then check shares. + +TODO: +- verify serialize and deserialize functions of messages, scalars, and elements + +Notes: +- Frost is not robust, i.e. + - if aggregated signature is not valid, SHOULD abort + - misbehaving signers can DOS the protocol by providing wrong sig shares or not contributing +- Wrong shares can be identified, and with the authenticated channel associated with the signer, which can then be +denied of further contributions +- R255 is recommended +- the coordinator does not not have any secret or private information +- the coordinator is assumed to behave honestly +- the coordinator may further hedge against nonce reuse by tracking the nonce commitments used for a given group key +- for message pre-hashing, see RFC + + */ // Ciphersuite identifies the group and hash to use for FROST. @@ -42,36 +79,33 @@ const ( Ristretto255 = Ciphersuite(group.Ristretto255Sha512) // Ed448 uses Edwards448 and SHAKE256, producing Ed448-compliant signatures as specified in RFC8032. - ed448 = Ciphersuite(2) + // ed448 = Ciphersuite(2). // P256 uses P-256 and SHA-256. P256 = Ciphersuite(group.P256Sha256) - // Secp256k1 uses Secp256k1 and SHA-256. - Secp256k1 = Ciphersuite(group.Secp256k1) + // P384 uses P-384 and SHA-384. + P384 = Ciphersuite(group.P384Sha384) - ed25519ContextString = "FROST-ED25519-SHA512-v1" - ristretto255ContextString = "FROST-RISTRETTO255-SHA512-v1" - p256ContextString = "FROST-P256-SHA256-v1" - secp256k1ContextString = "FROST-secp256k1-SHA256-v1" + // P521 uses P-521 and SHA-512. + P521 = Ciphersuite(group.P521Sha512) - /* - ed448ContextString = "FROST-ED448-SHAKE256-v1" - */ + // Secp256k1 uses Secp256k1 and SHA-256. + Secp256k1 = Ciphersuite(group.Secp256k1) ) // Available returns whether the selected ciphersuite is available. func (c Ciphersuite) Available() bool { switch c { - case Ed25519, Ristretto255, P256, Secp256k1: + case Ed25519, Ristretto255, P256, P384, P521, Secp256k1: return true default: return false } } -// Group returns the elliptic curve group used in the ciphersuite. -func (c Ciphersuite) Group() group.Group { +// ECGroup returns the elliptic curve group used in the ciphersuite. +func (c Ciphersuite) ECGroup() group.Group { if !c.Available() { return 0 } @@ -79,49 +113,110 @@ func (c Ciphersuite) Group() group.Group { return group.Group(c) } -// Participant returns a new participant of the protocol instantiated from the configuration an input. -func (c Ciphersuite) Participant(keyShare *KeyShare) *Participant { - return &Participant{ - KeyShare: keyShare, - Lambda: nil, - Nonces: make(map[uint64][2]*group.Scalar), - HidingRandom: nil, - BindingRandom: nil, - Configuration: *c.Configuration(), - } -} - // Configuration holds long term configuration information. type Configuration struct { - internal.Ciphersuite + GroupPublicKey *group.Element + SignerPublicKeys []*PublicKeyShare + Threshold uint64 + MaxSigners uint64 + Ciphersuite Ciphersuite + group group.Group + verified bool } -func makeConf(context string, h hash.Hash, g group.Group) *Configuration { - return &Configuration{ - Ciphersuite: internal.Ciphersuite{ - ContextString: []byte(context), - Hash: h, - Group: g, - }, +var ( + errInvalidThresholdParameter = errors.New("threshold is 0 or higher than maxSigners") + errInvalidMaxSignersOrder = errors.New("maxSigners is higher than group order") + errInvalidGroupPublicKey = errors.New("invalid group public key (nil, identity, or generator") + errInvalidNumberOfPublicKeys = errors.New("number of public keys is lower than threshold") +) + +func (c *Configuration) verify() error { + if !c.Ciphersuite.Available() { + return internal.ErrInvalidCiphersuite + } + + if c.Threshold == 0 || c.Threshold > c.MaxSigners { + return errInvalidThresholdParameter + } + + order, _ := new(big.Int).SetString(group.Group(c.Ciphersuite).Order(), 0) + if order == nil { + panic("can't set group order number") + } + + bigMax := new(big.Int).SetUint64(c.MaxSigners) + if order.Cmp(bigMax) != 1 { + return errInvalidMaxSignersOrder + } + + g := group.Group(c.Ciphersuite) + base := g.Base() + + if c.GroupPublicKey == nil || c.GroupPublicKey.IsIdentity() || c.GroupPublicKey.Equal(base) == 1 { + return errInvalidGroupPublicKey + } + + if uint64(len(c.SignerPublicKeys)) < c.Threshold { + return errInvalidNumberOfPublicKeys + } + + // Set to detect duplicate public keys. + pkSet := make(map[string]uint64, len(c.SignerPublicKeys)) + idSet := make(map[uint64]struct{}, len(c.SignerPublicKeys)) + + for i, pks := range c.SignerPublicKeys { + if pks == nil { + return fmt.Errorf("empty public key share at index %d", i) + } + + if pks.PublicKey == nil || pks.PublicKey.IsIdentity() || pks.PublicKey.Equal(base) == 1 { + return fmt.Errorf("invalid signer public key (nil, identity, or generator) for participant %d", pks.ID) + } + + // Verify whether the ID has duplicates + if _, exists := idSet[pks.ID]; exists { + return fmt.Errorf("found duplicate identifier for signer %d", pks.ID) + } + + // Verify whether the public key has duplicates + s := string(pks.PublicKey.Encode()) + if id, exists := pkSet[s]; exists { + return fmt.Errorf("found duplicate public keys for signers %d and %d", pks.ID, id) + } + + pkSet[s] = pks.ID + idSet[pks.ID] = struct{}{} } + + return nil } -// Configuration returns a configuration created for the ciphersuite. -func (c Ciphersuite) Configuration() *Configuration { - if !c.Available() { - return nil +func (c *Configuration) Init() error { + if err := c.verify(); err != nil { + return err } - switch c { - case Ed25519: - return makeConf(ed25519ContextString, hash.SHA512, group.Edwards25519Sha512) - case Ristretto255: - return makeConf(ristretto255ContextString, hash.SHA512, group.Ristretto255Sha512) - case P256: - return makeConf(p256ContextString, hash.SHA256, group.P256Sha256) - case Secp256k1: - return makeConf(secp256k1ContextString, hash.SHA256, group.Secp256k1) - default: - return nil + c.verified = true + c.group = group.Group(c.Ciphersuite) + + return nil +} + +// Signer returns a new participant of the protocol instantiated from the configuration and the signer's key share. +func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { + if !c.verified { + if err := c.Init(); err != nil { + return nil, err + } } + + return &Signer{ + KeyShare: keyShare, + Lambda: nil, + Commitments: make(map[uint64]*NonceCommitment), + HidingRandom: nil, + BindingRandom: nil, + configuration: c, + }, nil } diff --git a/go.mod b/go.mod index 5d35820..be5695d 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,6 @@ require ( filippo.io/nistec v0.0.3 // indirect github.com/bytemare/hash2curve v0.3.0 // indirect github.com/bytemare/secp256k1 v0.1.4 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.24.0 // indirect ) diff --git a/go.sum b/go.sum index 4898bff..f6a5762 100644 --- a/go.sum +++ b/go.sum @@ -16,7 +16,7 @@ github.com/bytemare/secret-sharing v0.3.0 h1:IK+wi3dhh+s8amN4xqdpgd8Byi36jZJQ9oA github.com/bytemare/secret-sharing v0.3.0/go.mod h1:kZ8Ty314nPP1LLd9ZsAAoc77625CEvXzRtimtEE1M9I= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/binding.go b/internal/binding.go deleted file mode 100644 index c0837b1..0000000 --- a/internal/binding.go +++ /dev/null @@ -1,33 +0,0 @@ -// 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 internal - -import ( - group "github.com/bytemare/crypto" -) - -// BindingFactor holds the binding factor scalar for the given identifier. -type BindingFactor struct { - BindingFactor *group.Scalar - Identifier uint64 -} - -// BindingFactorList a list of BindingFactor. -type BindingFactorList []*BindingFactor - -// BindingFactorForParticipant returns the binding factor for a given participant identifier in the list. -func (b BindingFactorList) BindingFactorForParticipant(id uint64) *group.Scalar { - for _, bf := range b { - if id == bf.Identifier { - return bf.BindingFactor - } - } - - panic("invalid participant") -} diff --git a/internal/ciphersuite.go b/internal/ciphersuite.go deleted file mode 100644 index dc50ee3..0000000 --- a/internal/ciphersuite.go +++ /dev/null @@ -1,92 +0,0 @@ -// 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 internal - -import ( - "filippo.io/edwards25519" - group "github.com/bytemare/crypto" - "github.com/bytemare/hash" - "github.com/gtank/ristretto255" -) - -// Ciphersuite combines the group and hashing routines. -type Ciphersuite struct { - ContextString []byte - Hash hash.Hash - Group group.Group -} - -func (c Ciphersuite) h1Ed25519(input []byte) *group.Scalar { - h := c.Hash.Hash(input) - - s := edwards25519.NewScalar() - if _, err := s.SetUniformBytes(h); err != nil { - panic(err) - } - - s2 := c.Group.NewScalar() - if err := s2.Decode(s.Bytes()); err != nil { - panic(err) - } - - return s2 -} - -func (c Ciphersuite) hx(input, dst []byte) *group.Scalar { - var sc *group.Scalar - - switch c.Group { - case group.Edwards25519Sha512: - sc = c.h1Ed25519(Concatenate(c.ContextString, dst, input)) - case group.Ristretto255Sha512: - h := c.Hash.Hash(c.ContextString, dst, input) - s := ristretto255.NewScalar().FromUniformBytes(h) - - sc = c.Group.NewScalar() - if err := sc.Decode(s.Encode(nil)); err != nil { - panic(err) - } - case group.P256Sha256, group.Secp256k1: - sc = c.Group.HashToScalar(input, append(c.ContextString, dst...)) - default: - panic(ErrInvalidParameters) - } - - return sc -} - -// H1 hashes the input and proves the "rho" DST. -func (c Ciphersuite) H1(input []byte) *group.Scalar { - return c.hx(input, []byte("rho")) -} - -// H2 hashes the input and proves the "chal" DST. -func (c Ciphersuite) H2(input []byte) *group.Scalar { - if c.Group == group.Edwards25519Sha512 { - // For compatibility with RFC8032 H2 doesn't use a domain separator for Edwards25519. - return c.h1Ed25519(input) - } - - return c.hx(input, []byte("chal")) -} - -// H3 hashes the input and proves the "nonce" DST. -func (c Ciphersuite) H3(input []byte) *group.Scalar { - return c.hx(input, []byte("nonce")) -} - -// H4 hashes the input and proves the "msg" DST. -func (c Ciphersuite) H4(msg []byte) []byte { - return c.Hash.Hash(c.ContextString, []byte("msg"), msg) -} - -// H5 hashes the input and proves the "com" DST. -func (c Ciphersuite) H5(msg []byte) []byte { - return c.Hash.Hash(c.ContextString, []byte("com"), msg) -} diff --git a/internal/configuration.go b/internal/configuration.go new file mode 100644 index 0000000..83c4c37 --- /dev/null +++ b/internal/configuration.go @@ -0,0 +1,134 @@ +// 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 internal + +import ( + "filippo.io/edwards25519" + group "github.com/bytemare/crypto" + "github.com/bytemare/hash" + "github.com/gtank/ristretto255" +) + +const ( + ed25519ContextString = "FROST-ED25519-SHA512-v1" + ristretto255ContextString = "FROST-RISTRETTO255-SHA512-v1" + ed448ContextString = "FROST-ED448-SHAKE256-v1" + p256ContextString = "FROST-P256-SHA256-v1" + p384ContextString = "FROST-P384-SHA384-v1" + p521ContextString = "FROST-P521-SHA512-v1" + secp256k1ContextString = "FROST-secp256k1-SHA256-v1" +) + +type ciphersuite struct { + hash hash.Hasher + contextString []byte +} + +var ciphersuites = [group.Secp256k1 + 1]ciphersuite{ + { + hash: hash.SHA512.New(), + contextString: []byte(ristretto255ContextString), + }, + { + hash: hash.SHAKE256.New(), + contextString: []byte(ed448ContextString), + }, + { + hash: hash.SHA256.New(), + contextString: []byte(p256ContextString), + }, + { + hash: hash.SHA384.New(), + contextString: []byte(p384ContextString), + }, + { + hash: hash.SHA512.New(), + contextString: []byte(p521ContextString), + }, + { + hash: hash.SHA512.New(), + contextString: []byte(ed25519ContextString), + }, + { + hash: hash.SHA256.New(), + contextString: []byte(secp256k1ContextString), + }, +} + +func h1Ed25519(hashed []byte) *group.Scalar { + s := edwards25519.NewScalar() + if _, err := s.SetUniformBytes(hashed); err != nil { + panic(err) + } + + s2 := group.Edwards25519Sha512.NewScalar() + if err := s2.Decode(s.Bytes()); err != nil { + panic(err) + } + + return s2 +} + +func hx(g group.Group, input, dst []byte) *group.Scalar { + var sc *group.Scalar + c := ciphersuites[g-1] + + switch g { + case group.Edwards25519Sha512: + h := c.hash.Hash(0, c.contextString, dst, input) + sc = h1Ed25519(h) + case group.Ristretto255Sha512: + h := c.hash.Hash(0, c.contextString, dst, input) + s := ristretto255.NewScalar().FromUniformBytes(h) + + sc = g.NewScalar() + if err := sc.Decode(s.Encode(nil)); err != nil { + panic(err) + } + case group.P256Sha256, group.P384Sha384, group.P521Sha512, group.Secp256k1: + sc = g.HashToScalar(input, append(c.contextString, dst...)) + default: + panic(ErrInvalidParameters) + } + + return sc +} + +// H1 hashes the input and proves the "rho" DST. +func H1(g group.Group, input []byte) *group.Scalar { + return hx(g, input, []byte("rho")) +} + +// H2 hashes the input and proves the "chal" DST. +func H2(g group.Group, input []byte) *group.Scalar { + if g == group.Edwards25519Sha512 { + // For compatibility with RFC8032 H2 doesn't use a domain separator for Edwards25519. + h := ciphersuites[group.Edwards25519Sha512-1].hash.Hash(0, input) + return h1Ed25519(h) + } + + return hx(g, input, []byte("chal")) +} + +// H3 hashes the input and proves the "nonce" DST. +func H3(g group.Group, input []byte) *group.Scalar { + return hx(g, input, []byte("nonce")) +} + +// H4 hashes the input and proves the "msg" DST. +func H4(g group.Group, msg []byte) []byte { + cs := ciphersuites[g-1] + return cs.hash.Hash(0, cs.contextString, []byte("msg"), msg) +} + +// H5 hashes the input and proves the "com" DST. +func H5(g group.Group, msg []byte) []byte { + cs := ciphersuites[g-1] + return cs.hash.Hash(0, cs.contextString, []byte("com"), msg) +} diff --git a/internal/core.go b/internal/core.go new file mode 100644 index 0000000..a0de7f0 --- /dev/null +++ b/internal/core.go @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 internal + +import ( + "fmt" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost/commitment" +) + +// GroupCommitmentAndBindingFactors computes and returns the group commitment element and signers' binding factors. +func GroupCommitmentAndBindingFactors( + g group.Group, + message []byte, + commitments commitment.List, + pk *group.Element, +) (*group.Element, BindingFactors) { + bindingFactors := computeBindingFactors(g, pk, commitments, message) + groupCommitment := computeGroupCommitment(g, commitments, bindingFactors) + + return groupCommitment, bindingFactors +} + +func computeLambda(g group.Group, commitments commitment.List, id uint64) (*group.Scalar, error) { + participantList := commitments.Participants(g) + + l, err := participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) + if err != nil { + return nil, fmt.Errorf("anomaly in participant identifiers: %w", err) + } + + return l, nil +} + +// ComputeChallengeFactor computes and returns the Schnorr challenge factor used in signing and verification. +func ComputeChallengeFactor( + g group.Group, + groupCommitment *group.Element, + lambda *group.Scalar, + id uint64, + message []byte, + commitments commitment.List, + groupPublicKey *group.Element, +) (*group.Scalar, error) { + // Compute the interpolating value + if lambda == nil || lambda.IsZero() { + l, err := computeLambda(g, commitments, id) + if err != nil { + return nil, err + } + + lambda = l + } + + // Compute per message challenge + chall := SchnorrChallenge(g, message, groupCommitment, groupPublicKey) + + return chall.Multiply(lambda), nil +} + +// BindingFactors is a map of participant identifier to BindingFactors. +type BindingFactors map[uint64]*group.Scalar + +type commitmentWithEncodedID struct { + *commitment.Commitment + ParticipantID []byte +} + +func commitmentsWithEncodedID(g group.Group, commitments commitment.List) []*commitmentWithEncodedID { + r := make([]*commitmentWithEncodedID, len(commitments)) + for i, com := range commitments { + r[i] = &commitmentWithEncodedID{ + ParticipantID: g.NewScalar().SetUInt64(com.SignerID).Encode(), + Commitment: com, + } + } + + return r +} + +func encodeCommitmentList(g group.Group, commitments []*commitmentWithEncodedID) []byte { + size := len(commitments) * (g.ScalarLength() + 2*g.ElementLength()) + encoded := make([]byte, 0, size) + + for _, com := range commitments { + encoded = append(encoded, com.ParticipantID...) + encoded = append(encoded, com.HidingNonce.Encode()...) + encoded = append(encoded, com.BindingNonce.Encode()...) + } + + return encoded +} + +// computeBindingFactors computes binding factors based on the participant commitment list and the message to be signed. +func computeBindingFactors( + g group.Group, + publicKey *group.Element, + commitments commitment.List, + message []byte, +) BindingFactors { + coms := commitmentsWithEncodedID(g, commitments) + encodedCommitHash := H5(g, encodeCommitmentList(g, coms)) + h := H4(g, message) + rhoInputPrefix := Concatenate(publicKey.Encode(), h, encodedCommitHash) + bindingFactors := make(BindingFactors, len(commitments)) + + for _, com := range coms { + rhoInput := Concatenate(rhoInputPrefix, com.ParticipantID) + bindingFactors[com.Commitment.SignerID] = H1(g, rhoInput) + } + + return bindingFactors +} + +func computeGroupCommitment(g group.Group, commitments commitment.List, bf BindingFactors) *group.Element { + gc := g.NewElement() + + for _, com := range commitments { + factor := bf[com.SignerID] + bindingNonce := com.BindingNonce.Copy().Multiply(factor) + gc.Add(com.HidingNonce).Add(bindingNonce) + } + + return gc +} + +// SchnorrChallenge computes the per-message SchnorrChallenge. +func SchnorrChallenge(g group.Group, msg []byte, r, pk *group.Element) *group.Scalar { + return H2(g, Concatenate(r.Encode(), pk.Encode(), msg)) +} + +func verifyCommitment(g group.Group, com *commitment.Commitment) error { + if com.Group != g { + return fmt.Errorf( + "commitment for participant %d has an unexpected ciphersuite: expected %s, got %s", + com.SignerID, + g, + com.Group, + ) + } + + if com.HidingNonce == nil || com.HidingNonce.IsIdentity() { + return fmt.Errorf("hiding nonce for participant %d is nil or identity element", com.SignerID) + } + + if com.BindingNonce == nil || com.BindingNonce.IsIdentity() { + return fmt.Errorf("binding nonce for participant %d is nil or identity element", com.SignerID) + } + + return nil +} + +// VerifyCommitmentList checks for the Commitment list integrity. +func VerifyCommitmentList(g group.Group, coms commitment.List, threshold uint64) error { + // Verify number of commitments. + if uint64(len(coms)) < threshold { + return fmt.Errorf("too few commitments: expected at least %d but got %d", threshold, len(coms)) + } + + // Ensure the list is sorted + if !coms.IsSorted() { + coms.Sort() + } + + // set to detect duplication + set := make(map[uint64]struct{}, len(coms)) + + for _, com := range coms { + // Check for duplicate participant entries. + if _, exists := set[com.SignerID]; exists { + return fmt.Errorf("commitment list contains multiple commitments of participant %d", com.SignerID) + } + + set[com.SignerID] = struct{}{} + + // Check general consistency. + if err := verifyCommitment(g, com); err != nil { + return err + } + } + + return nil +} diff --git a/internal/errors.go b/internal/errors.go index ea302e6..422044e 100644 --- a/internal/errors.go +++ b/internal/errors.go @@ -1,3 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 internal import "errors" @@ -9,13 +17,6 @@ var ( // ErrInvalidCiphersuite indicates a non-supported ciphersuite is being used. ErrInvalidCiphersuite = errors.New("ciphersuite not available") - // ErrInvalidParticipantBackup indicates the participant's encoded backup is not valid. - ErrInvalidParticipantBackup = errors.New("invalid backup") - // ErrInvalidLength indicates that a provided encoded data piece is not of the expected length. ErrInvalidLength = errors.New("invalid encoding length") - - ErrWrongVerificationData = errors.New("the commitment and signature share don't belong the same participant") - - ErrInvalidVerificationShare = errors.New("signature share does not not match") ) diff --git a/internal/utils.go b/internal/utils.go index db5c8b9..8c28c63 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -57,8 +57,3 @@ func UInt64LE(i uint64) []byte { return out[:] } - -// UInt64FromLE returns the uint64 representation of the first 8 input bytes -func UInt64FromLE(b []byte) uint64 { - return binary.LittleEndian.Uint64(b) -} diff --git a/keys.go b/keys.go index 0982620..2c5aeff 100644 --- a/keys.go +++ b/keys.go @@ -1,3 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 frost import ( diff --git a/participant.go b/participant.go deleted file mode 100644 index 170b103..0000000 --- a/participant.go +++ /dev/null @@ -1,201 +0,0 @@ -// 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 frost - -import ( - "crypto/rand" - "encoding/binary" - - group "github.com/bytemare/crypto" - - "github.com/bytemare/frost/internal" -) - -// SignatureShare represents a participants signature share and its identifier. -type SignatureShare struct { - SignatureShare *group.Scalar - Identifier uint64 -} - -// Participant is a signer of a group. -type Participant struct { - KeyShare *KeyShare - Lambda *group.Scalar // lambda can be computed once and reused across FROST signing operations - Nonces map[uint64][2]*group.Scalar - HidingRandom []byte - BindingRandom []byte - Configuration -} - -func (p *Participant) generateNonce(s *group.Scalar, random []byte) *group.Scalar { - if random == nil { - random = internal.RandomBytes(32) - } - - enc := s.Encode() - - return p.Ciphersuite.H3(internal.Concatenate(random, enc)) -} - -func randomCommitmentID() uint64 { - buf := make([]byte, 8) - - _, err := rand.Read(buf) - if err != nil { - panic(err) - } - - return binary.LittleEndian.Uint64(buf) -} - -func (p *Participant) Identifier() uint64 { - return p.KeyShare.ID -} - -// Commit generates a participants nonce and commitment, to be used in the second FROST round. The internal nonce must -// be kept secret, and the returned commitment sent to the signature aggregator. -func (p *Participant) Commit() *Commitment { - cid := randomCommitmentID() - for { - if _, ok := p.Nonces[cid]; !ok { - break - } - } - - p.Nonces[cid] = [2]*group.Scalar{ - p.generateNonce(p.KeyShare.Secret, p.HidingRandom), - p.generateNonce(p.KeyShare.Secret, p.BindingRandom), - } - - return &Commitment{ - Ciphersuite: Ciphersuite(p.Group), - ParticipantID: p.KeyShare.ID, - CommitmentID: cid, - PublicKey: p.KeyShare.PublicKey, - HidingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonces[cid][0]), - BindingNonce: p.Ciphersuite.Group.Base().Multiply(p.Nonces[cid][1]), - } -} - -func computeLambda(g group.Group, commitments CommitmentList, id uint64) (*group.Scalar, error) { - participantList := commitments.Participants(g) - return participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) -} - -func (c Configuration) do( - publicKey *group.Element, - lambda *group.Scalar, - message []byte, - commitments CommitmentList, - id uint64, -) (*group.Scalar, *group.Scalar, error) { - if !commitments.IsSorted() { - commitments.Sort() - } - - // Compute the interpolating value - if lambda == nil || lambda.IsZero() { - l, err := computeLambda(c.Ciphersuite.Group, commitments, id) - if err != nil { - return nil, nil, err - } - - lambda = l - } - - // Compute the binding factor(s) - bindingFactorList := c.computeBindingFactors(publicKey, commitments, message) - bindingFactor := bindingFactorList.BindingFactorForParticipant(id) - - // Compute group commitment - groupCommitment := computeGroupCommitment(c.Group, commitments, bindingFactorList) - - // Compute per message challenge - chall := challenge(c.Ciphersuite, groupCommitment, publicKey, message) - - return bindingFactor, chall.Multiply(lambda), nil -} - -// Sign produces a participant's signature share of the message msg. -// -// Each participant MUST validate the inputs before processing the Coordinator's request. -// In particular, the Signer MUST validate commitment_list, deserializing each group Element in the list using -// DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, -// each participant MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. -func (p *Participant) Sign(commitmentID uint64, msg []byte, coms CommitmentList) (*SignatureShare, error) { - bindingFactor, lambdaChall, err := p.do(p.KeyShare.GroupPublicKey, p.Lambda, msg, coms, p.KeyShare.ID) - if err != nil { - return nil, err - } - - // Compute the signature share - sigShare := p.Nonces[commitmentID][0].Copy().Add( - p.Nonces[commitmentID][1].Copy().Multiply(bindingFactor).Add(lambdaChall.Multiply(p.KeyShare.Secret)), - ).Copy() - - // Clean up values - p.Nonces[commitmentID][0].Zero() - p.Nonces[commitmentID][1].Zero() - - return &SignatureShare{ - Identifier: p.KeyShare.ID, - SignatureShare: sigShare, - }, nil -} - -// computeBindingFactors computes binding factors based on the participant commitment list and the message to be signed. -func (c Configuration) computeBindingFactors( - publicKey *group.Element, - l CommitmentList, - message []byte, -) internal.BindingFactorList { - if !l.IsSorted() { - panic(nil) - } - - h := c.H4(message) - encodedCommitHash := c.H5(l.Encode(c.Ciphersuite.Group)) - rhoInputPrefix := internal.Concatenate(publicKey.Encode(), h, encodedCommitHash) - - bindingFactorList := make(internal.BindingFactorList, len(l)) - - for i, commitment := range l { - id := c.Group.NewScalar().SetUInt64(commitment.ParticipantID).Encode() - rhoInput := internal.Concatenate(rhoInputPrefix, id) - bindingFactor := c.H1(rhoInput) - - bindingFactorList[i] = &internal.BindingFactor{ - Identifier: commitment.ParticipantID, - BindingFactor: bindingFactor, - } - } - - return bindingFactorList -} - -// computeGroupCommitment creates the group commitment from a commitment list. -func computeGroupCommitment(g group.Group, l CommitmentList, list internal.BindingFactorList) *group.Element { - if !l.IsSorted() { - panic(nil) - } - - gc := g.NewElement() - - for _, commitment := range l { - if commitment.HidingNonce.IsIdentity() || commitment.BindingNonce.IsIdentity() { - panic("identity commitment") - } - - factor := list.BindingFactorForParticipant(commitment.ParticipantID) - bindingNonce := commitment.BindingNonce.Copy().Multiply(factor) - gc.Add(commitment.HidingNonce).Add(bindingNonce) - } - - return gc -} diff --git a/schnorr.go b/schnorr.go deleted file mode 100644 index 2b948ef..0000000 --- a/schnorr.go +++ /dev/null @@ -1,86 +0,0 @@ -// 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 frost - -import ( - "fmt" - - group "github.com/bytemare/crypto" - - "github.com/bytemare/frost/internal" -) - -// Signature represent a Schnorr signature. -type Signature struct { - R *group.Element - Z *group.Scalar -} - -// Encode serializes the signature into a byte string. -func (s *Signature) Encode() []byte { - return append(s.R.Encode(), s.Z.Encode()...) -} - -// Decode attempts to deserialize the encoded input into the signature in the group. -func (s *Signature) Decode(g group.Group, encoded []byte) error { - eLen := g.ElementLength() - sLen := g.ScalarLength() - - if len(encoded) != eLen+sLen { - return internal.ErrInvalidParameters - } - - if err := s.R.Decode(encoded[:eLen]); err != nil { - return fmt.Errorf("invalid signature - decoding R: %w", err) - } - - if err := s.Z.Decode(encoded[eLen:]); err != nil { - return fmt.Errorf("invalid signature - decoding Z: %w", err) - } - - return nil -} - -// challenge computes the per-message challenge. -func challenge(cs internal.Ciphersuite, r, pk *group.Element, msg []byte) *group.Scalar { - return cs.H2(internal.Concatenate(r.Encode(), pk.Encode(), msg)) -} - -func computeZ(r, challenge, key *group.Scalar) *group.Scalar { - return r.Add(challenge.Multiply(key)) -} - -// Sign returns a Schnorr signature over the message msg with the full secret signing key (as opposed to a key share). -func (c Configuration) Sign(msg []byte, key *group.Scalar) *Signature { - g := c.Ciphersuite.Group - r := g.NewScalar().Random() - R := g.Base().Multiply(r) - pk := g.Base().Multiply(key) - ch := challenge(c.Ciphersuite, R, pk, msg) - z := computeZ(r, ch, key) - - return &Signature{ - R: R, - Z: z, - } -} - -// VerifySignature returns whether the signature of the message msg is valid under the public key pk. -func (c Configuration) VerifySignature(msg []byte, signature *Signature, publicKey *group.Element) bool { - ch := challenge(c.Ciphersuite, signature.R, publicKey, msg) - l := c.Ciphersuite.Group.Base().Multiply(signature.Z) - r := signature.R.Add(publicKey.Copy().Multiply(ch)) - - if c.Ciphersuite.Group == group.Edwards25519Sha512 { - cofactor := group.Edwards25519Sha512.NewScalar().SetUInt64(8) - return l.Multiply(cofactor).Equal(r.Multiply(cofactor)) == 1 - } - - return l.Equal(r) == 1 -} diff --git a/signer.go b/signer.go new file mode 100644 index 0000000..4d043e2 --- /dev/null +++ b/signer.go @@ -0,0 +1,210 @@ +// 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 frost + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost/commitment" + "github.com/bytemare/frost/internal" +) + +// SignatureShare represents a Signer's signature share and its identifier. +type SignatureShare struct { + SignatureShare *group.Scalar + SignerIdentifier uint64 + group group.Group +} + +// Signer is a participant in a signing group. +type Signer struct { + KeyShare *KeyShare + Lambda *group.Scalar + Commitments map[uint64]*NonceCommitment + configuration *Configuration + HidingRandom []byte + BindingRandom []byte +} + +type NonceCommitment struct { + HidingNonceS *group.Scalar + BindingNonceS *group.Scalar + *commitment.Commitment +} + +func (s *Signer) ClearNonceCommitment(commitmentID uint64) { + if com := s.Commitments[commitmentID]; com != nil { + com.HidingNonceS.Zero() + com.BindingNonceS.Zero() + com.HidingNonce.Identity() + com.BindingNonce.Identity() + delete(s.Commitments, commitmentID) + } +} + +// Identifier returns the Signer's identifier. +func (s *Signer) Identifier() uint64 { + return s.KeyShare.ID +} + +func randomCommitmentID() uint64 { + buf := make([]byte, 8) + + _, err := rand.Read(buf) + if err != nil { + panic(fmt.Errorf("FATAL: %w", err)) + } + + return binary.LittleEndian.Uint64(buf) +} + +func (s *Signer) generateNonce(secret *group.Scalar, random []byte) *group.Scalar { + if random == nil { + random = internal.RandomBytes(32) + } + + enc := secret.Encode() + + return internal.H3(s.configuration.group, internal.Concatenate(random, enc)) +} + +func (s *Signer) genNonceID() uint64 { + cid := randomCommitmentID() + + for range 128 { + if _, exists := s.Commitments[cid]; !exists { + return cid + } + + cid = randomCommitmentID() + } + + panic("FATAL: CSPRNG could not generate a unique nonce over 128 iterations") +} + +// Commit generates a signer's nonces and commitment, to be used in the second FROST round. The internal nonce must +// be kept secret, and the returned commitment sent to the signature aggregator. +func (s *Signer) Commit() *commitment.Commitment { + cid := s.genNonceID() + hn := s.generateNonce(s.KeyShare.Secret, s.HidingRandom) + bn := s.generateNonce(s.KeyShare.Secret, s.BindingRandom) + com := &commitment.Commitment{ + Group: s.configuration.group, + SignerID: s.KeyShare.ID, + CommitmentID: cid, + HidingNonce: s.configuration.group.Base().Multiply(hn), + BindingNonce: s.configuration.group.Base().Multiply(bn), + } + s.Commitments[cid] = &NonceCommitment{ + HidingNonceS: hn, + BindingNonceS: bn, + Commitment: com, + } + + return com.Copy() +} + +func (s *Signer) verifyNonces(com *commitment.Commitment) error { + nonces, ok := s.Commitments[com.CommitmentID] + if !ok { + return fmt.Errorf( + "the commitment identifier %d for signer %d in the commitments is unknown to the signer", + com.CommitmentID, + s.KeyShare.ID, + ) + } + + if nonces.HidingNonce.Equal(com.HidingNonce) != 1 { + return fmt.Errorf("invalid hiding nonce in commitment list for signer %d", s.KeyShare.ID) + } + + if nonces.BindingNonce.Equal(com.BindingNonce) != 1 { + return fmt.Errorf("invalid binding nonce in commitment list for signer %d", s.KeyShare.ID) + } + + return nil +} + +// VerifyCommitmentList checks for the Commitment list integrity and the signer's commitment. +func (s *Signer) VerifyCommitmentList(commitments commitment.List) error { + if err := internal.VerifyCommitmentList(s.configuration.group, commitments, s.configuration.Threshold); err != nil { + return fmt.Errorf("invalid list of commitments: %w", err) + } + + // Check commitment values for the signer. + for _, com := range commitments { + if com.SignerID == s.KeyShare.ID { + return s.verifyNonces(com) + } + } + + return fmt.Errorf("no commitment for signer %d found in the commitment list", s.KeyShare.ID) +} + +// Sign produces a participant's signature share of the message msg. The commitmentID identifies the commitment produced +// on a previous call to Commit(). Once the signature with Sign() is produced, the internal commitment nonces are +// cleared and another call to Sign() with the same commitmentID will return an error. +// +// Each signer MUST validate the inputs before processing the Coordinator's request. +// In particular, the Signer MUST validate commitment_list, deserializing each group Element in the list using +// DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, +// each signer MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. +func (s *Signer) Sign(commitmentID uint64, message []byte, commitments commitment.List) (*SignatureShare, error) { + com, exists := s.Commitments[commitmentID] + if !exists { + return nil, fmt.Errorf("commitmentID %d not registered", commitmentID) + } + + if err := s.VerifyCommitmentList(commitments); err != nil { + return nil, err + } + + groupCommitment, bindingFactors := internal.GroupCommitmentAndBindingFactors( + s.configuration.group, + message, + commitments, + s.configuration.GroupPublicKey, + ) + + bindingFactor := bindingFactors[s.KeyShare.ID] + + lambdaChall, err := internal.ComputeChallengeFactor( + s.configuration.group, + groupCommitment, + s.Lambda, + s.KeyShare.ID, + message, + commitments, + s.configuration.GroupPublicKey, + ) + if err != nil { + return nil, fmt.Errorf("can't compute challenge: %w", err) + } + + hidingNonce := com.HidingNonceS.Copy() + bindingNonce := com.BindingNonceS + + // Compute the signature share: h + b*f + l*s + sigShare := hidingNonce. + Add(bindingFactor.Multiply(bindingNonce). + Add(lambdaChall.Multiply(s.KeyShare.Secret))) + + // Clean up values + s.ClearNonceCommitment(commitmentID) + + return &SignatureShare{ + group: s.configuration.group, + SignerIdentifier: s.KeyShare.ID, + SignatureShare: sigShare, + }, nil +} diff --git a/tests/dkg_test.go b/tests/dkg_test.go index 187741f..8701b96 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -1,3 +1,11 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 frost_test import ( @@ -9,9 +17,9 @@ import ( "github.com/bytemare/frost" ) -func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, threshold int) []*dkg.Participant { +func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, threshold uint64) []*dkg.Participant { ps := make([]*dkg.Participant, 0, maxSigners) - for i := range uint64(maxSigners) { + for i := range maxSigners { p, err := ciphersuite.NewParticipant(i+1, uint(maxSigners), uint(threshold)) if err != nil { t.Fatal(err) @@ -23,7 +31,11 @@ func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, return ps } -func runDKG(t *testing.T, g group.Group, maxSigners, threshold int) ([]*frost.KeyShare, *group.Element, []*group.Element) { +func runDKG( + t *testing.T, + g group.Group, + maxSigners, threshold uint64, +) ([]*frost.KeyShare, *group.Element, []*group.Element) { c := dkg.Ciphersuite(g) // valid r1DataSet set with and without own package diff --git a/tests/frost_test.go b/tests/frost_test.go index adf4b1f..9fd609f 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -12,82 +12,52 @@ import ( "testing" group "github.com/bytemare/crypto" - "github.com/bytemare/hash" "github.com/bytemare/frost" + "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/debug" - "github.com/bytemare/frost/internal" ) type tableTest struct { - frost.Configuration frost.Ciphersuite } var testTable = []tableTest{ { Ciphersuite: frost.Ed25519, - Configuration: frost.Configuration{ - Ciphersuite: internal.Ciphersuite{ - ContextString: []byte("FROST-ED25519-SHA512-v1"), - Hash: hash.SHA512, - Group: group.Edwards25519Sha512, - }, - }, }, { Ciphersuite: frost.Ristretto255, - Configuration: frost.Configuration{ - Ciphersuite: internal.Ciphersuite{ - Group: group.Ristretto255Sha512, - Hash: hash.SHA512, - ContextString: []byte("FROST-RISTRETTO255-SHA512-v1"), - }, - }, }, { Ciphersuite: frost.P256, - Configuration: frost.Configuration{ - Ciphersuite: internal.Ciphersuite{ - Group: group.P256Sha256, - Hash: hash.SHA256, - ContextString: []byte("FROST-P256-SHA256-v1"), - }, - }, }, { Ciphersuite: frost.Secp256k1, - Configuration: frost.Configuration{ - Ciphersuite: internal.Ciphersuite{ - ContextString: []byte("FROST-secp256k1-SHA256-v1"), - Hash: hash.SHA256, - Group: group.Secp256k1, - }, - }, }, } func TestTrustedDealerKeygen(t *testing.T) { - threshold := 3 - maxSigners := 5 + threshold := uint64(3) + maxSigners := uint64(5) testAll(t, func(t *testing.T, test *tableTest) { - g := test.Ciphersuite.Group() + g := test.Ciphersuite.ECGroup() groupSecretKey := g.NewScalar().Random() keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( - frost.Ciphersuite(g), + test.Ciphersuite, groupSecretKey, - maxSigners, threshold, + maxSigners, ) - if len(secretsharingCommitment) != threshold { + if uint64(len(secretsharingCommitment)) != threshold { t.Fatalf("%d / %d", len(secretsharingCommitment), threshold) } - recoveredKey, err := debug.RecoverGroupSecret(g, keyShares[:threshold]) + recoveredKey, err := debug.RecoverGroupSecret(test.Ciphersuite, keyShares[:threshold]) if err != nil { t.Fatal(err) } @@ -96,8 +66,16 @@ func TestTrustedDealerKeygen(t *testing.T) { t.Fatal() } - groupPublicKey, participantPublicKeys := debug.RecoverPublicKeys(g, maxSigners, secretsharingCommitment) - if len(participantPublicKeys) != maxSigners { + groupPublicKey, participantPublicKeys, err := debug.RecoverPublicKeys( + test.Ciphersuite, + maxSigners, + secretsharingCommitment, + ) + if err != nil { + t.Fatal(err) + } + + if uint64(len(participantPublicKeys)) != maxSigners { t.Fatal() } @@ -124,122 +102,118 @@ func TestTrustedDealerKeygen(t *testing.T) { }) } -func TestFrost_WithTrustedDealer(t *testing.T) { - maxSigner := 3 - threshold := 2 - message := []byte("test") - - testAll(t, func(t *testing.T, test *tableTest) { - g := test.Ciphersuite.Group() - sk := g.NewScalar().Random() +func runFrost( + t *testing.T, + test *tableTest, + threshold, maxSigners uint64, + message []byte, + keyShares []*frost.KeyShare, + groupPublicKey *group.Element, +) { + // At key generation, each participant must send their public key share to the coordinator, and the collection + // must be broadcast to every participant. + publicKeyShares := make([]*frost.PublicKeyShare, 0, len(keyShares)) + for _, ks := range keyShares { + publicKeyShares = append(publicKeyShares, ks.Public()) + } - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, sk, maxSigner, threshold) + // Set up configuration. + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } - // Create Participants - participants := make(ParticipantList, threshold) - for i, share := range keyShares[:threshold] { - participants[i] = test.Ciphersuite.Participant(share) - } + if err := configuration.Init(); err != nil { + panic(err) + } - // Round One: Commitment - commitments := make(frost.CommitmentList, threshold) - for i, p := range participants { - commitments[i] = p.Commit() + // Create Participants + participants := make(ParticipantList, threshold) + for i, ks := range keyShares[:threshold] { + signer, err := configuration.Signer(ks) + if err != nil { + panic(err) } - commitments.Sort() + participants[i] = signer + } - // Round Two: Sign - sigShares := make([]*frost.SignatureShare, threshold) - for i, p := range participants { - var err error - commitmentID := commitments.Get(p.Identifier()).CommitmentID - sigShares[i], err = p.Sign(commitmentID, message, commitments) - if err != nil { - t.Fatal(err) - } - } + // Round One: Commitment + commitments := make(commitment.List, threshold) + for i, p := range participants { + commitments[i] = p.Commit() + } - // Final step: aggregate - signature := test.AggregateSignatures(message, sigShares, commitments, groupPublicKey) - if !test.VerifySignature(message, signature, groupPublicKey) { - t.Fatal() - } + commitments.Sort() - // Sanity Check - groupSecretKey, err := debug.RecoverGroupSecret(g, keyShares) + // Round Two: Sign + sigShares := make([]*frost.SignatureShare, threshold) + for i, p := range participants { + var err error + commitmentID := commitments.Get(p.Identifier()).CommitmentID + sigShares[i], err = p.Sign(commitmentID, message, commitments) if err != nil { t.Fatal(err) } + } - if groupSecretKey.Equal(sk) != 1 { - t.Fatal("expected equality in group secret key") - } - - singleSig := test.Sign(message, groupSecretKey) - if !test.VerifySignature(message, singleSig, groupPublicKey) { - t.Fatal() - } - }) -} - -func TestFrost_WithDKG(t *testing.T) { - maxSigner := 3 - threshold := 2 - message := []byte("test") - - testAll(t, func(t *testing.T, test *tableTest) { - g := test.Ciphersuite.Group() + // Final step: aggregate + signature, err := configuration.AggregateSignatures(message, sigShares, commitments, true) + if err != nil { + t.Fatal(err) + } - keyShares, groupPublicKey, _ := runDKG(t, g, maxSigner, threshold) + if err = frost.VerifySignature(test.Ciphersuite, message, signature, groupPublicKey); err != nil { + t.Fatal(err) + } - // Create Participants - participants := make(ParticipantList, threshold) - for i, share := range keyShares[:threshold] { - participants[i] = test.Ciphersuite.Participant(share) - } + // Sanity Check + groupSecretKey, err := debug.RecoverGroupSecret(test.Ciphersuite, keyShares) + if err != nil { + t.Fatal(err) + } - // Round One: Commitment - commitments := make(frost.CommitmentList, threshold) - for i, p := range participants { - commitments[i] = p.Commit() - } + singleSig, err := debug.Sign(test.Ciphersuite, message, groupSecretKey) + if err != nil { + t.Fatal(err) + } - commitments.Sort() + if err = frost.VerifySignature(test.Ciphersuite, message, singleSig, groupPublicKey); err != nil { + t.Fatal(err) + } +} - // Round Two: Sign - sigShares := make([]*frost.SignatureShare, threshold) - for i, p := range participants { - var err error - commitmentID := commitments.Get(p.Identifier()).CommitmentID - sigShares[i], err = p.Sign(commitmentID, message, commitments) - if err != nil { - t.Fatal(err) - } - } +func TestFrost_WithTrustedDealer(t *testing.T) { + maxSigners := uint64(3) + threshold := uint64(2) + message := []byte("test") - // Final step: aggregate - signature := test.AggregateSignatures(message, sigShares, commitments, groupPublicKey) - if !test.VerifySignature(message, signature, groupPublicKey) { - t.Fatal() - } + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Ciphersuite.ECGroup() + sk := g.NewScalar().Random() + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, sk, threshold, maxSigners) + runFrost(t, test, threshold, maxSigners, message, keyShares, groupPublicKey) + }) +} - // Sanity Check - groupSecretKey, err := debug.RecoverGroupSecret(g, keyShares) - if err != nil { - t.Fatal(err) - } +func TestFrost_WithDKG(t *testing.T) { + maxSigners := uint64(3) + threshold := uint64(2) + message := []byte("test") - singleSig := test.Sign(message, groupSecretKey) - if !test.VerifySignature(message, singleSig, groupPublicKey) { - t.Fatal() - } + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Ciphersuite.ECGroup() + keyShares, groupPublicKey, _ := runDKG(t, g, maxSigners, threshold) + runFrost(t, test, threshold, maxSigners, message, keyShares, groupPublicKey) }) } func testAll(t *testing.T, f func(*testing.T, *tableTest)) { for _, test := range testTable { - t.Run(string(test.Configuration.ContextString), func(t *testing.T) { + t.Run(string(test.ECGroup()), func(t *testing.T) { f(t, &test) }) } diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index 989b14a..97bc666 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -21,9 +21,9 @@ import ( "github.com/bytemare/frost" ) -type ParticipantList []*frost.Participant +type ParticipantList []*frost.Signer -func (p ParticipantList) Get(id uint64) *frost.Participant { +func (p ParticipantList) Get(id uint64) *frost.Signer { for _, i := range p { if i.KeyShare.ID == id { return i @@ -33,13 +33,13 @@ func (p ParticipantList) Get(id uint64) *frost.Participant { return nil } -func stringToInt(t *testing.T, s string) int { - i, err := strconv.ParseInt(s, 10, 32) +func stringToUint(t *testing.T, s string) uint { + i, err := strconv.ParseUint(s, 10, 0) if err != nil { t.Fatal(err) } - return int(i) + return uint(i) } func decodeScalar(t *testing.T, g group.Group, enc []byte) *group.Scalar { @@ -111,12 +111,13 @@ type testVectorConfig struct { } func (c testVectorConfig) decode(t *testing.T) *testConfig { + threshold := stringToUint(t, c.MinParticipants) + maxSigners := stringToUint(t, c.MaxParticipants) + return &testConfig{ - MaxParticipants: stringToInt(t, c.MaxParticipants), - NumParticipants: stringToInt(t, c.NumParticipants), - MinParticipants: stringToInt(t, c.MinParticipants), Name: c.Name, - Configuration: configToConfiguration(t, &c), + NumParticipants: stringToUint(t, c.NumParticipants), + Configuration: configToConfiguration(t, &c, threshold, maxSigners), } } @@ -158,9 +159,7 @@ type testConfig struct { *frost.Configuration Name string ContextString []byte - MaxParticipants int - NumParticipants int - MinParticipants int + NumParticipants uint } type testInput struct { @@ -204,16 +203,26 @@ type testRoundTwoOutputs struct { Parsing and decoding functions. */ -func configToConfiguration(t *testing.T, c *testVectorConfig) *frost.Configuration { +func makeFrostConfig(c frost.Ciphersuite, threshold, maxSigners uint) *frost.Configuration { + return &frost.Configuration{ + Ciphersuite: c, + Threshold: uint64(threshold), + MaxSigners: uint64(maxSigners), + GroupPublicKey: nil, + SignerPublicKeys: nil, + } +} + +func configToConfiguration(t *testing.T, c *testVectorConfig, threshold, maxSigners uint) *frost.Configuration { switch c.Group { case "ed25519": - return frost.Ed25519.Configuration() + return makeFrostConfig(frost.Ed25519, threshold, maxSigners) case "ristretto255": - return frost.Ristretto255.Configuration() + return makeFrostConfig(frost.Ristretto255, threshold, maxSigners) case "P-256": - return frost.P256.Configuration() + return makeFrostConfig(frost.P256, threshold, maxSigners) case "secp256k1": - return frost.Secp256k1.Configuration() + return makeFrostConfig(frost.Secp256k1, threshold, maxSigners) default: t.Fatalf("group not supported: %s", c.Group) } @@ -291,8 +300,8 @@ func (o testVectorRoundTwoOutputs) decode(t *testing.T, g group.Group) *testRoun for i, p := range o.Outputs { r.Outputs[i] = &frost.SignatureShare{ - Identifier: p.Identifier, - SignatureShare: decodeScalar(t, g, p.SigShare), + SignerIdentifier: p.Identifier, + SignatureShare: decodeScalar(t, g, p.SigShare), } } @@ -301,11 +310,24 @@ func (o testVectorRoundTwoOutputs) decode(t *testing.T, g group.Group) *testRoun func (v testVector) decode(t *testing.T) *test { conf := v.Config.decode(t) + inputs := v.Inputs.decode(t, conf.Ciphersuite.ECGroup()) + + conf.GroupPublicKey = inputs.GroupPublicKey + conf.SignerPublicKeys = make([]*frost.PublicKeyShare, len(inputs.Participants)) + + for i, ks := range inputs.Participants { + conf.SignerPublicKeys[i] = ks.Public() + } + + if err := conf.Configuration.Init(); err != nil { + t.Fatal(err) + } + return &test{ Config: conf, - Inputs: v.Inputs.decode(t, conf.Ciphersuite.Group), - RoundOneOutputs: v.RoundOneOutputs.decode(t, conf.Ciphersuite.Group), - RoundTwoOutputs: v.RoundTwoOutputs.decode(t, conf.Ciphersuite.Group), + Inputs: inputs, + RoundOneOutputs: v.RoundOneOutputs.decode(t, conf.Ciphersuite.ECGroup()), + RoundTwoOutputs: v.RoundTwoOutputs.decode(t, conf.Ciphersuite.ECGroup()), FinalOutput: v.FinalOutput.Sig, } } diff --git a/tests/vectors_test.go b/tests/vectors_test.go index b59fdec..7bb5be5 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -19,26 +19,27 @@ import ( group "github.com/bytemare/crypto" "github.com/bytemare/frost" + "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/debug" ) func (v test) testTrustedDealer(t *testing.T) ([]*frost.KeyShare, *group.Element) { - g := v.Config.Ciphersuite.Group + g := v.Config.Ciphersuite.ECGroup() keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( - frost.Ciphersuite(g), + v.Config.Ciphersuite, v.Inputs.GroupSecretKey, - v.Config.MaxParticipants, - v.Config.MinParticipants, + v.Config.Configuration.Threshold, + v.Config.Configuration.MaxSigners, v.Inputs.SharePolynomialCoefficients...) - if len(secretsharingCommitment) != v.Config.MinParticipants { + if uint64(len(secretsharingCommitment)) != v.Config.Configuration.Threshold { t.Fatalf( - "%d / %d", len(secretsharingCommitment), v.Config.MinParticipants) + "%d / %d", len(secretsharingCommitment), v.Config.Configuration.Threshold) } // Test recovery of the full secret signing key. - recoveredKey, err := debug.RecoverGroupSecret(g, keyShares) + recoveredKey, err := debug.RecoverGroupSecret(v.Config.Ciphersuite, keyShares) if err != nil { t.Fatal(err) } @@ -47,12 +48,16 @@ func (v test) testTrustedDealer(t *testing.T) ([]*frost.KeyShare, *group.Element t.Fatal() } - groupPublicKey, participantPublicKey := debug.RecoverPublicKeys( - g, - v.Config.MaxParticipants, + groupPublicKey, participantPublicKey, err := debug.RecoverPublicKeys( + v.Config.Ciphersuite, + v.Config.Configuration.MaxSigners, secretsharingCommitment, ) - if len(participantPublicKey) != v.Config.MaxParticipants { + if err != nil { + t.Fatal(err) + } + + if uint64(len(participantPublicKey)) != v.Config.Configuration.MaxSigners { t.Fatal() } @@ -86,17 +91,20 @@ func (v test) test(t *testing.T) { t.Fatal("Some key shares do not match.") } - c := frost.Ciphersuite(v.Config.Group) - // Create participants participants := make(ParticipantList, len(keyShares)) conf := v.Config for i, keyShare := range keyShares { - participants[i] = c.Participant(keyShare) + signer, err := conf.Configuration.Signer(keyShare) + if err != nil { + t.Fatal(err) + } + + participants[i] = signer } // Round One: Commitment - commitmentList := make(frost.CommitmentList, len(v.RoundOneOutputs.Outputs)) + commitmentList := make(commitment.List, len(v.RoundOneOutputs.Outputs)) for i, pid := range v.RoundOneOutputs.Outputs { p := participants.Get(pid.ID) if p == nil { @@ -116,34 +124,34 @@ func (v test) test(t *testing.T) { p.HidingRandom = pv.HidingNonceRandomness p.BindingRandom = pv.BindingNonceRandomness - commitment := p.Commit() + com := p.Commit() - if p.Nonces[commitment.CommitmentID][0].Equal(pv.HidingNonce) != 1 { + if p.Commitments[com.CommitmentID].HidingNonceS.Equal(pv.HidingNonce) != 1 { t.Fatal(i) } - if p.Nonces[commitment.CommitmentID][1].Equal(pv.BindingNonce) != 1 { + if p.Commitments[com.CommitmentID].BindingNonceS.Equal(pv.BindingNonce) != 1 { t.Fatal(i) } - if commitment.HidingNonce.Equal(pv.HidingNonceCommitment) != 1 { + if com.HidingNonce.Equal(pv.HidingNonceCommitment) != 1 { t.Fatal(i) } - if commitment.BindingNonce.Equal(pv.BindingNonceCommitment) != 1 { + if com.BindingNonce.Equal(pv.BindingNonceCommitment) != 1 { t.Fatal(i) } - commitmentList[i] = commitment + commitmentList[i] = com } // Round two: sign sigShares := make([]*frost.SignatureShare, len(v.RoundTwoOutputs.Outputs)) for i, share := range v.RoundTwoOutputs.Outputs { - p := participants.Get(share.Identifier) + p := participants.Get(share.SignerIdentifier) if p == nil { t.Fatal(i) } - commitment := commitmentList.Get(p.Identifier()) - commitmentID := commitment.CommitmentID + com := commitmentList.Get(p.Identifier()) + commitmentID := com.CommitmentID var err error sigShares[i], err = p.Sign(commitmentID, v.Inputs.Message, commitmentList) @@ -156,19 +164,23 @@ func (v test) test(t *testing.T) { t.Fatalf("%s\n%s\n", share.SignatureShare.Hex(), sigShares[i].SignatureShare.Hex()) } - if err := v.Config.VerifySignatureShare(commitment, v.Inputs.Message, sigShares[i], commitmentList, groupPublicKey); err != nil { + if err := v.Config.VerifySignatureShare(sigShares[i], v.Inputs.Message, commitmentList); err != nil { t.Fatalf("signature share matched but verification failed: %s", err) } } // AggregateSignatures - sig := v.Config.AggregateSignatures(v.Inputs.Message, sigShares, commitmentList, groupPublicKey) + sig, err := v.Config.AggregateSignatures(v.Inputs.Message, sigShares, commitmentList, false) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(sig.Encode(), v.FinalOutput) { - t.Fatal() + t.Fatal("") } // Sanity Check - if !conf.VerifySignature(v.Inputs.Message, sig, groupPublicKey) { + if err := frost.VerifySignature(conf.Ciphersuite, v.Inputs.Message, sig, groupPublicKey); err != nil { t.Fatal() } } From 8f92a2d4b176fa8111e29a96a2027294b2270b48 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:45:16 +0200 Subject: [PATCH 07/31] clean up, optimizations, refactoring Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- internal/{configuration.go => hashing.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename internal/{configuration.go => hashing.go} (100%) diff --git a/internal/configuration.go b/internal/hashing.go similarity index 100% rename from internal/configuration.go rename to internal/hashing.go From ee263d39da6d0356e8f9eb947d91c8ca620a3bc7 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Sat, 17 Aug 2024 00:39:13 +0200 Subject: [PATCH 08/31] some refacto and tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment/commitment.go | 62 ++- encoding.go | 82 ++-- examples_test.go | 4 +- frost.go | 68 ++-- signer.go | 24 +- tests/encoding_test.go | 831 +++++++++++++++++++++++++++++++++++++++ tests/frost_test.go | 41 +- 7 files changed, 1012 insertions(+), 100 deletions(-) create mode 100644 tests/encoding_test.go diff --git a/commitment/commitment.go b/commitment/commitment.go index 77ef874..63524d6 100644 --- a/commitment/commitment.go +++ b/commitment/commitment.go @@ -22,6 +22,7 @@ import ( var ( errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") errInvalidCiphersuite = errors.New("ciphersuite not available") + errInvalidLength = errors.New("invalid encoding length") ) // Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. @@ -54,12 +55,12 @@ func (c *Commitment) Encode() []byte { hNonce := c.HidingNonce.Encode() bNonce := c.BindingNonce.Encode() - out := make([]byte, 9, EncodedSize(c.Group)) + out := make([]byte, 17, EncodedSize(c.Group)) out[0] = byte(c.Group) - binary.LittleEndian.PutUint64(out[1:], c.CommitmentID) - binary.LittleEndian.PutUint64(out[9:], c.SignerID) - copy(out[17:], hNonce) - copy(out[17+len(hNonce):], bNonce) + binary.LittleEndian.PutUint64(out[1:9], c.CommitmentID) + binary.LittleEndian.PutUint64(out[9:17], c.SignerID) + out = append(out, hNonce...) + out = append(out, bNonce...) return out } @@ -143,3 +144,54 @@ func (c List) Get(identifier uint64) *Commitment { return nil } + +func (c List) Encode() []byte { + n := len(c) + if n == 0 { + return nil + } + + g := c[0].Group + size := 8 + uint64(n)*EncodedSize(g) + out := make([]byte, 8, size) + out[0] = byte(g) + binary.LittleEndian.PutUint64(out, uint64(n)) + + for _, com := range c { + out = append(out, com.Encode()...) + } + + return out +} + +func DecodeList(data []byte) (List, error) { + if len(data) < 9 { + return nil, errInvalidLength + } + + n := binary.LittleEndian.Uint64(data[:8]) + g := group.Group(data[8]) + if !g.Available() { + return nil, errInvalidCiphersuite + } + + es := EncodedSize(g) + size := 8 + n*es + + if uint64(len(data)) != size { + return nil, errInvalidLength + } + + c := make(List, n) + + for offset := uint64(8); offset <= uint64(len(data)); offset += es { + com := new(Commitment) + if err := com.Decode(data[offset : offset+es]); err != nil { + return nil, err + } + + c = append(c, com) + } + + return c, nil +} diff --git a/encoding.go b/encoding.go index aef7648..3a3a0d6 100644 --- a/encoding.go +++ b/encoding.go @@ -30,7 +30,10 @@ const ( encNonceCommitment = byte(6) ) -var errInvalidConfigEncoding = errors.New("invalid values in configuration encoding") +var ( + errInvalidConfigEncoding = errors.New("invalid values in Configuration encoding") + errZeroIdentifier = errors.New("identifier cannot be 0") +) func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { eLen := uint64(g.ElementLength()) @@ -48,7 +51,7 @@ func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { case encPubKeyShare: return 1 + 8 + 4 + eLen + other[0] case encNonceCommitment: - return other[0] * (2*sLen + commitment.EncodedSize(g)) + return 8 + 2*sLen + commitment.EncodedSize(g) default: panic("encoded id not recognized") } @@ -58,8 +61,9 @@ func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { func (c *Configuration) Encode() []byte { g := group.Group(c.Ciphersuite) pksLen := encodedLength(encPubKeyShare, g, c.Threshold*uint64(g.ElementLength())) - out := make([]byte, 25, encodedLength(encConf, g, uint64(len(c.SignerPublicKeys))*pksLen)) - out[0] = byte(c.group) + size := encodedLength(encConf, g, uint64(len(c.SignerPublicKeys))*pksLen) + out := make([]byte, 25, size) + out[0] = byte(g) binary.LittleEndian.PutUint64(out[1:9], c.Threshold) binary.LittleEndian.PutUint64(out[9:17], c.MaxSigners) binary.LittleEndian.PutUint64(out[17:25], uint64(len(c.SignerPublicKeys))) @@ -89,15 +93,21 @@ func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { } g := group.Group(data[0]) + t := binary.LittleEndian.Uint64(data[1:9]) + m := binary.LittleEndian.Uint64(data[9:17]) n := binary.LittleEndian.Uint64(data[17:25]) - pksLen := encodedLength(encPubKeyShare, g, c.Threshold*uint64(g.ElementLength())) - length := encodedLength(encConf, g, n*(8+pksLen)) + pksLen := encodedLength(encPubKeyShare, g, t*uint64(g.ElementLength())) + length := encodedLength(encConf, g, n*pksLen) + + if t > math.MaxUint || m > math.MaxUint { + return nil, errInvalidConfigEncoding + } return &confHeader{ g: g, h: 25, - t: binary.LittleEndian.Uint64(data[1:9]), - m: binary.LittleEndian.Uint64(data[9:17]), + t: t, + m: m, n: n, length: length, }, nil @@ -108,10 +118,6 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { return internal.ErrInvalidLength } - if header.t > math.MaxUint || header.m > math.MaxUint { - return errInvalidConfigEncoding - } - gpk := header.g.NewElement() if err := gpk.Decode(data[header.h : header.h+uint64(header.g.ElementLength())]); err != nil { return fmt.Errorf("could not decode group public key: %w", err) @@ -154,7 +160,7 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { return nil } -// Decode deserializes the input data into the configuration, or returns an error. +// Decode deserializes the input data into the Configuration, or returns an error. func (c *Configuration) Decode(data []byte) error { header, err := c.decodeHeader(data) if err != nil { @@ -169,8 +175,8 @@ func (c *Configuration) Decode(data []byte) error { func (s *Signer) Encode() []byte { g := s.KeyShare.Group ks := s.KeyShare.Encode() - nbCommitments := len(s.Commitments) - conf := s.configuration.Encode() + nCommitments := len(s.Commitments) + conf := s.Configuration.Encode() out := make( []byte, len(conf)+4, @@ -179,13 +185,18 @@ func (s *Signer) Encode() []byte { g, uint64(len(conf)), uint64(len(ks)), - uint64(nbCommitments)*encodedLength(encNonceCommitment, g), + uint64(nCommitments)*encodedLength(encNonceCommitment, g), ), ) copy(out, conf) - binary.LittleEndian.PutUint16(out[len(conf):len(conf)+2], uint16(len(ks))) // key share length - binary.LittleEndian.PutUint16(out[len(conf)+2:len(conf)+4], uint16(nbCommitments)) // number of commitments - out = append(out, s.Lambda.Encode()...) + binary.LittleEndian.PutUint16(out[len(conf):len(conf)+2], uint16(len(ks))) // key share length + binary.LittleEndian.PutUint16(out[len(conf)+2:len(conf)+4], uint16(nCommitments)) // number of commitments + + if s.Lambda != nil { + out = append(out, s.Lambda.Encode()...) + } else { + out = append(out, make([]byte, g.ScalarLength())...) + } out = append(out, ks...) // key share for id, com := range s.Commitments { @@ -229,7 +240,7 @@ func (s *Signer) Decode(data []byte) error { lambda := g.NewScalar() if err := lambda.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { - return fmt.Errorf("failed to decode key share: %w", err) + return fmt.Errorf("failed to decode lambda: %w", err) } offset += uint64(g.ScalarLength()) @@ -241,9 +252,15 @@ func (s *Signer) Decode(data []byte) error { offset += ksLen commitments := make(map[uint64]*NonceCommitment) + comLen := commitment.EncodedSize(g) for offset < uint64(len(data)) { id := binary.LittleEndian.Uint64(data[offset : offset+8]) + + if _, exists := commitments[id]; exists { + return fmt.Errorf("multiple encoded commitments with the same id: %d", id) + } + offset += 8 hs := g.NewScalar() @@ -261,11 +278,11 @@ func (s *Signer) Decode(data []byte) error { offset += uint64(g.ScalarLength()) com := new(commitment.Commitment) - if err = com.Decode(data[offset : offset+nLen]); err != nil { + if err = com.Decode(data[offset : offset+comLen]); err != nil { return fmt.Errorf("can't decode nonce commitment %d: %w", id, err) } - offset += nLen + offset += comLen commitments[id] = &NonceCommitment{ HidingNonceS: hs, @@ -277,7 +294,7 @@ func (s *Signer) Decode(data []byte) error { s.KeyShare = keyShare s.Lambda = lambda s.Commitments = commitments - s.configuration = conf + s.Configuration = conf return nil } @@ -286,10 +303,10 @@ func (s *Signer) Decode(data []byte) error { func (s *SignatureShare) Encode() []byte { share := s.SignatureShare.Encode() - out := make([]byte, 1+8+s.group.ScalarLength()) - out[0] = byte(s.group) + out := make([]byte, 1+8+s.Group.ScalarLength()) + out[0] = byte(s.Group) binary.LittleEndian.PutUint64(out[1:9], s.SignerIdentifier) - copy(out[8:], share) + copy(out[9:], share) return out } @@ -311,13 +328,18 @@ func (s *SignatureShare) Decode(data []byte) error { return internal.ErrInvalidLength } + id := binary.LittleEndian.Uint64(data[1:9]) + if id == 0 { + return errZeroIdentifier + } + share := g.NewScalar() if err := share.Decode(data[9:]); err != nil { return fmt.Errorf("failed to decode signature share: %w", err) } - s.group = g - s.SignerIdentifier = binary.LittleEndian.Uint64(data[:1:9]) + s.Group = g + s.SignerIdentifier = id s.SignatureShare = share return nil @@ -334,11 +356,11 @@ func (s *Signature) Encode() []byte { // Decode attempts to deserialize the encoded input into the signature in the group. func (s *Signature) Decode(c Ciphersuite, data []byte) error { - if !c.Available() { + g := c.ECGroup() + if g == 0 { return internal.ErrInvalidCiphersuite } - g := group.Group(data[0]) eLen := g.ElementLength() if uint64(len(data)) != encodedLength(encSig, g) { diff --git a/examples_test.go b/examples_test.go index d534a7c..ed5953b 100644 --- a/examples_test.go +++ b/examples_test.go @@ -38,7 +38,7 @@ func Example_signer() { publicKeyShares[i] = sk.Public() } - // This is how to set up the configuration for FROST, the same for every signer and the coordinator. + // This is how to set up the Configuration for FROST, the same for every signer and the coordinator. configuration := &frost.Configuration{ Ciphersuite: ciphersuite, Threshold: threshold, @@ -125,7 +125,7 @@ func Example_coordinator() { publicKeyShares[i] = sk.Public() } - // This is how to set up the configuration for FROST, the same for every signer and the coordinator. + // This is how to set up the Configuration for FROST, the same for every signer and the coordinator. configuration := &frost.Configuration{ Ciphersuite: ciphersuite, Threshold: threshold, diff --git a/frost.go b/frost.go index 24962a0..b19e7a9 100644 --- a/frost.go +++ b/frost.go @@ -113,7 +113,7 @@ func (c Ciphersuite) ECGroup() group.Group { return group.Group(c) } -// Configuration holds long term configuration information. +// Configuration holds long term Configuration information. type Configuration struct { GroupPublicKey *group.Element SignerPublicKeys []*PublicKeyShare @@ -131,6 +131,40 @@ var ( errInvalidNumberOfPublicKeys = errors.New("number of public keys is lower than threshold") ) +func (c *Configuration) verifySignerPublicKeys() error { + // Sets to detect duplicates. + pkSet := make(map[string]uint64, len(c.SignerPublicKeys)) + idSet := make(map[uint64]struct{}, len(c.SignerPublicKeys)) + g := group.Group(c.Ciphersuite) + base := g.Base() + + for i, pks := range c.SignerPublicKeys { + if pks == nil { + return fmt.Errorf("empty public key share at index %d", i) + } + + if pks.PublicKey == nil || pks.PublicKey.IsIdentity() || pks.PublicKey.Equal(base) == 1 { + return fmt.Errorf("invalid signer public key (nil, identity, or generator) for participant %d", pks.ID) + } + + // Verify whether the ID has duplicates + if _, exists := idSet[pks.ID]; exists { + return fmt.Errorf("found duplicate identifier for signer %d", pks.ID) + } + + // Verify whether the public key has duplicates + s := string(pks.PublicKey.Encode()) + if id, exists := pkSet[s]; exists { + return fmt.Errorf("found duplicate public keys for signers %d and %d", pks.ID, id) + } + + pkSet[s] = pks.ID + idSet[pks.ID] = struct{}{} + } + + return nil +} + func (c *Configuration) verify() error { if !c.Ciphersuite.Available() { return internal.ErrInvalidCiphersuite @@ -161,32 +195,8 @@ func (c *Configuration) verify() error { return errInvalidNumberOfPublicKeys } - // Set to detect duplicate public keys. - pkSet := make(map[string]uint64, len(c.SignerPublicKeys)) - idSet := make(map[uint64]struct{}, len(c.SignerPublicKeys)) - - for i, pks := range c.SignerPublicKeys { - if pks == nil { - return fmt.Errorf("empty public key share at index %d", i) - } - - if pks.PublicKey == nil || pks.PublicKey.IsIdentity() || pks.PublicKey.Equal(base) == 1 { - return fmt.Errorf("invalid signer public key (nil, identity, or generator) for participant %d", pks.ID) - } - - // Verify whether the ID has duplicates - if _, exists := idSet[pks.ID]; exists { - return fmt.Errorf("found duplicate identifier for signer %d", pks.ID) - } - - // Verify whether the public key has duplicates - s := string(pks.PublicKey.Encode()) - if id, exists := pkSet[s]; exists { - return fmt.Errorf("found duplicate public keys for signers %d and %d", pks.ID, id) - } - - pkSet[s] = pks.ID - idSet[pks.ID] = struct{}{} + if err := c.verifySignerPublicKeys(); err != nil { + return err } return nil @@ -203,7 +213,7 @@ func (c *Configuration) Init() error { return nil } -// Signer returns a new participant of the protocol instantiated from the configuration and the signer's key share. +// Signer returns a new participant of the protocol instantiated from the Configuration and the signer's key share. func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { if !c.verified { if err := c.Init(); err != nil { @@ -217,6 +227,6 @@ func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { Commitments: make(map[uint64]*NonceCommitment), HidingRandom: nil, BindingRandom: nil, - configuration: c, + Configuration: c, }, nil } diff --git a/signer.go b/signer.go index 4d043e2..2f5cd17 100644 --- a/signer.go +++ b/signer.go @@ -23,7 +23,7 @@ import ( type SignatureShare struct { SignatureShare *group.Scalar SignerIdentifier uint64 - group group.Group + Group group.Group } // Signer is a participant in a signing group. @@ -31,7 +31,7 @@ type Signer struct { KeyShare *KeyShare Lambda *group.Scalar Commitments map[uint64]*NonceCommitment - configuration *Configuration + Configuration *Configuration HidingRandom []byte BindingRandom []byte } @@ -75,7 +75,7 @@ func (s *Signer) generateNonce(secret *group.Scalar, random []byte) *group.Scala enc := secret.Encode() - return internal.H3(s.configuration.group, internal.Concatenate(random, enc)) + return internal.H3(s.Configuration.group, internal.Concatenate(random, enc)) } func (s *Signer) genNonceID() uint64 { @@ -99,11 +99,11 @@ func (s *Signer) Commit() *commitment.Commitment { hn := s.generateNonce(s.KeyShare.Secret, s.HidingRandom) bn := s.generateNonce(s.KeyShare.Secret, s.BindingRandom) com := &commitment.Commitment{ - Group: s.configuration.group, + Group: s.Configuration.group, SignerID: s.KeyShare.ID, CommitmentID: cid, - HidingNonce: s.configuration.group.Base().Multiply(hn), - BindingNonce: s.configuration.group.Base().Multiply(bn), + HidingNonce: s.Configuration.group.Base().Multiply(hn), + BindingNonce: s.Configuration.group.Base().Multiply(bn), } s.Commitments[cid] = &NonceCommitment{ HidingNonceS: hn, @@ -137,7 +137,7 @@ func (s *Signer) verifyNonces(com *commitment.Commitment) error { // VerifyCommitmentList checks for the Commitment list integrity and the signer's commitment. func (s *Signer) VerifyCommitmentList(commitments commitment.List) error { - if err := internal.VerifyCommitmentList(s.configuration.group, commitments, s.configuration.Threshold); err != nil { + if err := internal.VerifyCommitmentList(s.Configuration.group, commitments, s.Configuration.Threshold); err != nil { return fmt.Errorf("invalid list of commitments: %w", err) } @@ -170,22 +170,22 @@ func (s *Signer) Sign(commitmentID uint64, message []byte, commitments commitmen } groupCommitment, bindingFactors := internal.GroupCommitmentAndBindingFactors( - s.configuration.group, + s.Configuration.group, message, commitments, - s.configuration.GroupPublicKey, + s.Configuration.GroupPublicKey, ) bindingFactor := bindingFactors[s.KeyShare.ID] lambdaChall, err := internal.ComputeChallengeFactor( - s.configuration.group, + s.Configuration.group, groupCommitment, s.Lambda, s.KeyShare.ID, message, commitments, - s.configuration.GroupPublicKey, + s.Configuration.GroupPublicKey, ) if err != nil { return nil, fmt.Errorf("can't compute challenge: %w", err) @@ -203,7 +203,7 @@ func (s *Signer) Sign(commitmentID uint64, message []byte, commitments commitmen s.ClearNonceCommitment(commitmentID) return &SignatureShare{ - group: s.configuration.group, + Group: s.Configuration.group, SignerIdentifier: s.KeyShare.ID, SignatureShare: sigShare, }, nil diff --git a/tests/encoding_test.go b/tests/encoding_test.go new file mode 100644 index 0000000..15f018a --- /dev/null +++ b/tests/encoding_test.go @@ -0,0 +1,831 @@ +// 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 frost_test + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "slices" + "strings" + "testing" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost" + "github.com/bytemare/frost/commitment" + "github.com/bytemare/frost/debug" + "github.com/bytemare/frost/internal" +) + +func badScalar(t *testing.T, g group.Group) []byte { + order, ok := new(big.Int).SetString(g.Order(), 0) + if !ok { + t.Errorf("setting int in base %d failed: %v", 0, g.Order()) + } + + encoded := make([]byte, g.ScalarLength()) + order.FillBytes(encoded) + + if g == group.Ristretto255Sha512 || g == group.Edwards25519Sha512 { + slices.Reverse(encoded) + } + + return encoded +} + +func badElement(t *testing.T, g group.Group) []byte { + order, ok := new(big.Int).SetString(g.Order(), 0) + if !ok { + t.Errorf("setting int in base %d failed: %v", 0, g.Order()) + } + + encoded := make([]byte, g.ElementLength()) + order.FillBytes(encoded) + + if g == group.Ristretto255Sha512 || g == group.Edwards25519Sha512 { + slices.Reverse(encoded) + } + + return encoded +} + +func comparePublicKeyShare(p1, p2 *frost.PublicKeyShare) error { + if p1.PublicKey.Equal(p2.PublicKey) != 1 { + return fmt.Errorf("Expected equality on PublicKey:\n\t%s\n\t%s\n", p1.PublicKey.Hex(), p2.PublicKey.Hex()) + } + + if p1.ID != p2.ID { + return fmt.Errorf("Expected equality on ID:\n\t%d\n\t%d\n", p1.ID, p2.ID) + } + + if p1.Group != p2.Group { + return fmt.Errorf("Expected equality on Group:\n\t%v\n\t%v\n", p1.Group, p2.Group) + } + + if len(p1.Commitment) != len(p2.Commitment) { + return fmt.Errorf( + "Expected equality on Commitment length:\n\t%d\n\t%d\n", + len(p1.Commitment), + len(p1.Commitment), + ) + } + + for i := range p1.Commitment { + if p1.Commitment[i].Equal(p2.Commitment[i]) != 1 { + return fmt.Errorf( + "Expected equality on Commitment %d:\n\t%s\n\t%s\n", + i, + p1.Commitment[i].Hex(), + p1.Commitment[i].Hex(), + ) + } + } + + return nil +} + +func getPublicKeyShares(keyShares []*frost.KeyShare) []*frost.PublicKeyShare { + publicKeyShares := make([]*frost.PublicKeyShare, 0, len(keyShares)) + for _, ks := range keyShares { + publicKeyShares = append(publicKeyShares, ks.Public()) + } + + return publicKeyShares +} + +func compareConfigurations(t *testing.T, c1, c2 *frost.Configuration, expectedMatch bool) { + if c1 == nil || c2 == nil { + t.Fatal("nil config") + } + + if c1.Ciphersuite != c2.Ciphersuite && expectedMatch { + t.Fatalf("expected matching ciphersuite: %q / %q", c1.Ciphersuite, c2.Ciphersuite) + } + + if c1.Threshold != c2.Threshold && expectedMatch { + t.Fatalf("expected matching threshold: %q / %q", c1.Threshold, c2.Threshold) + } + + if c1.MaxSigners != c2.MaxSigners && expectedMatch { + t.Fatalf("expected matching max signers: %q / %q", c1.MaxSigners, c2.MaxSigners) + } + + if ((c1.GroupPublicKey == nil || c2.GroupPublicKey == nil) || (c1.GroupPublicKey.Equal(c2.GroupPublicKey) != 1)) && + expectedMatch { + t.Fatalf("expected matching GroupPublicKey: %q / %q", c1.Ciphersuite, c2.Ciphersuite) + } + + if len(c1.SignerPublicKeys) != len(c2.SignerPublicKeys) { + t.Fatalf( + "expected matching SignerPublicKeys lengths: %q / %q", + len(c1.SignerPublicKeys), + len(c2.SignerPublicKeys), + ) + } + + for i, p1 := range c1.SignerPublicKeys { + p2 := c2.SignerPublicKeys[i] + if err := comparePublicKeyShare(p1, p2); !expectedMatch && err != nil { + t.Fatal(err) + } + } +} + +func makeConf(t *testing.T, test *tableTest) *frost.Configuration { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err != nil { + t.Fatal(err) + } + + return configuration +} + +func TestEncoding_Configuration(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + configuration := makeConf(t, test) + encoded := configuration.Encode() + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded); err != nil { + t.Fatal(err) + } + + compareConfigurations(t, configuration, decoded, true) + }) +} + +func TestEncoding_Configuration_InvalidHeaderLength(t *testing.T) { + expectedError := internal.ErrInvalidLength + + testAll(t, func(t *testing.T, test *tableTest) { + configuration := makeConf(t, test) + encoded := configuration.Encode() + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded[:24]); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("extected %q, got %q", expectedError, err) + } + }) +} + +func TestEncoding_Configuration_InvalidCiphersuite(t *testing.T) { + expectedError := internal.ErrInvalidCiphersuite + + testAll(t, func(t *testing.T, test *tableTest) { + configuration := makeConf(t, test) + encoded := configuration.Encode() + encoded[0] = 2 + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("extected %q, got %q", expectedError, err) + } + }) +} + +func TestEncoding_Configuration_InvalidLength(t *testing.T) { + expectedError := internal.ErrInvalidLength + + testAll(t, func(t *testing.T, test *tableTest) { + configuration := makeConf(t, test) + encoded := configuration.Encode() + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded[:len(encoded)-1]); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("extected %q, got %q", expectedError, err) + } + + encoded = append(encoded, []byte{0, 1}...) + if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("extected %q, got %q", expectedError, err) + } + }) +} + +func TestEncoding_Configuration_InvalidGroupPublicKey(t *testing.T) { + expectedErrorPrefix := "could not decode group public key: element Decode: " + + testAll(t, func(t *testing.T, test *tableTest) { + configuration := makeConf(t, test) + g := group.Group(test.Ciphersuite) + encoded := configuration.Encode() + bad := badElement(t, g) + encoded = slices.Replace(encoded, 25, 25+g.ElementLength(), bad...) + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Configuration_InvalidPublicKeyShare(t *testing.T) { + expectedErrorPrefix := "could not decode signer public key share for signer 1: " + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + g := group.Group(test.Ciphersuite) + pksSize := len(publicKeyShares[0].Encode()) + bad := badElement(t, g) + offset := 25 + g.ElementLength() + pksSize + 1 + 8 + 4 + encoded := configuration.Encode() + encoded = slices.Replace(encoded, offset, offset+g.ElementLength(), bad...) + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func compareKeyShares(s1, s2 *frost.KeyShare) error { + if s1.Secret.Equal(s2.Secret) != 1 { + return fmt.Errorf("Expected equality on Secret:\n\t%s\n\t%s\n", s1.Secret.Hex(), s2.Secret.Hex()) + } + + if s1.GroupPublicKey.Equal(s2.GroupPublicKey) != 1 { + return fmt.Errorf( + "Expected equality on GroupPublicKey:\n\t%s\n\t%s\n", + s1.GroupPublicKey.Hex(), + s2.GroupPublicKey.Hex(), + ) + } + + return comparePublicKeyShare(s1.Public(), s2.Public()) +} + +func compareCommitments(c1, c2 *commitment.Commitment) error { + if c1.Group != c2.Group { + return errors.New("different groups") + } + + if c1.SignerID != c2.SignerID { + return errors.New("different SignerID") + } + + if c1.CommitmentID != c2.CommitmentID { + return errors.New("different CommitmentID") + } + + if c1.HidingNonce.Equal(c2.HidingNonce) != 1 { + return errors.New("different HidingNonce") + } + + if c1.BindingNonce.Equal(c2.BindingNonce) != 1 { + return errors.New("different BindingNonce") + } + + return nil +} + +func compareNonceCommitments(c1, c2 *frost.NonceCommitment) error { + if c1.HidingNonceS.Equal(c2.HidingNonceS) != 1 { + return errors.New("different HidingNonceS") + } + + if c1.BindingNonceS.Equal(c2.BindingNonceS) != 1 { + return errors.New("different BindingNonceS") + } + + return compareCommitments(c1.Commitment, c2.Commitment) +} + +func makeSigners(t *testing.T, test *tableTest) []*frost.Signer { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err != nil { + t.Fatal(err) + } + + signers := make([]*frost.Signer, test.maxSigners) + + for i, keyShare := range keyShares { + s, err := configuration.Signer(keyShare) + if err != nil { + t.Fatal(err) + } + + signers[i] = s + } + + return signers +} + +func compareSigners(t *testing.T, s1, s2 *frost.Signer) { + if err := compareKeyShares(s1.KeyShare, s2.KeyShare); err != nil { + t.Fatal(err) + } + + if !((s1.Lambda == nil && (s2.Lambda == nil || s2.Lambda.IsZero())) || (s2.Lambda == nil && (s1.Lambda == nil || s1.Lambda.IsZero()))) { + t.Fatalf("expected equality: %v / %v", s1.Lambda, s2.Lambda.IsZero()) + } + + if len(s1.Commitments) != len(s2.Commitments) { + t.Fatal("expected equality") + } + + for id, com := range s1.Commitments { + if com2, exists := s2.Commitments[id]; !exists { + t.Fatalf("com id %d does not exist in s2", id) + } else { + if err := compareNonceCommitments(com, com2); err != nil { + t.Fatal(err) + } + } + } + + if bytes.Compare(s1.HidingRandom, s2.HidingRandom) != 0 { + t.Fatal("expected equality") + } + + if bytes.Compare(s1.BindingRandom, s2.BindingRandom) != 0 { + t.Fatal("expected equality") + } + + compareConfigurations(t, s1.Configuration, s2.Configuration, true) +} + +func TestEncoding_Signer(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + s.Commit() + s.Commit() + encoded := s.Encode() + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded); err != nil { + t.Fatal(err) + } + + compareSigners(t, s, decoded) + }) +} + +func TestEncoding_Signer_BadConfHeader(t *testing.T) { + expectedErr := internal.ErrInvalidLength + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + encoded := s.Encode() + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded[:20]); err == nil || err.Error() != expectedErr.Error() { + t.Fatalf("expected error %q, got %q", expectedErr, err) + } + }) +} + +func TestEncoding_Signer_BadConf(t *testing.T) { + expectedErr := internal.ErrInvalidLength + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + encoded := s.Encode() + + eLen := s.Configuration.Ciphersuite.ECGroup().ElementLength() + pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen + confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded[:confLen-2]); err == nil || err.Error() != expectedErr.Error() { + t.Fatalf("expected error %q, got %q", expectedErr, err) + } + }) +} + +func TestEncoding_Signer_InvalidLength1(t *testing.T) { + expectedErr := internal.ErrInvalidLength + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + encoded := s.Encode() + eLen := s.Configuration.Ciphersuite.ECGroup().ElementLength() + pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen + confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded[:confLen+2]); err == nil || err.Error() != expectedErr.Error() { + t.Fatalf("expected error %q, got %q", expectedErr, err) + } + }) +} + +func TestEncoding_Signer_InvalidLength2(t *testing.T) { + expectedErr := internal.ErrInvalidLength + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + encoded := s.Encode() + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded[:len(encoded)-1]); err == nil || err.Error() != expectedErr.Error() { + t.Fatalf("expected error %q, got %q", expectedErr, err) + } + + if err := decoded.Decode(append(encoded, []byte{0}...)); err == nil || err.Error() != expectedErr.Error() { + t.Fatalf("expected error %q, got %q", expectedErr, err) + } + }) +} + +func TestEncoding_Signer_InvalidLambda(t *testing.T) { + expectedErrorPrefix := "failed to decode lambda:" + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + + g := group.Group(test.Ciphersuite) + eLen := g.ElementLength() + pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen + confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + encoded := s.Encode() + bad := badScalar(t, g) + + encoded = slices.Replace(encoded, confLen+4, confLen+4+g.ScalarLength(), bad...) + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { + expectedErrorPrefix := "failed to decode key share:" + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + + g := group.Group(test.Ciphersuite) + eLen := g.ElementLength() + pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen + confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + offset := confLen + 4 + g.ScalarLength() + + // Set an invalid group in the key share encoding. + kse := s.KeyShare.Encode() + kse[0] = 2 + + encoded := s.Encode() + encoded = slices.Replace(encoded, offset, offset+len(kse), kse...) + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Signer_InvalidCommitmentNonces_DuplicateID(t *testing.T) { + expectedErrorPrefix := "multiple encoded commitments with the same id:" + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + s.Commit() + s.Commit() + s.Commit() + g := group.Group(test.Ciphersuite) + eLen := g.ElementLength() + sLen := g.ScalarLength() + pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen + confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + keyShareLen := len(s.KeyShare.Encode()) + commitmentLength := 8 + 2*sLen + int(commitment.EncodedSize(g)) + offset := confLen + 4 + sLen + keyShareLen + offset2 := offset + commitmentLength + + encoded := s.Encode() + data := slices.Replace(encoded, offset2, offset2+8, encoded[offset:offset+8]...) + + decoded := new(frost.Signer) + if err := decoded.Decode(data); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Signer_InvalidHidingNonceCommitment(t *testing.T) { + expectedErrorPrefix := "can't decode hiding nonce for commitment" + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + s.Commit() + g := group.Group(test.Ciphersuite) + eLen := g.ElementLength() + sLen := g.ScalarLength() + pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen + confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + keyShareLen := len(s.KeyShare.Encode()) + offset := confLen + 4 + sLen + keyShareLen + 8 + + encoded := s.Encode() + data := slices.Replace(encoded, offset, offset+g.ScalarLength(), badScalar(t, g)...) + + decoded := new(frost.Signer) + if err := decoded.Decode(data); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Signer_InvalidBindingNonceCommitment(t *testing.T) { + expectedErrorPrefix := "can't decode binding nonce for commitment" + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + s.Commit() + g := group.Group(test.Ciphersuite) + eLen := g.ElementLength() + sLen := g.ScalarLength() + pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen + confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + keyShareLen := len(s.KeyShare.Encode()) + offset := confLen + 4 + sLen + keyShareLen + 8 + g.ScalarLength() + + encoded := s.Encode() + data := slices.Replace(encoded, offset, offset+g.ScalarLength(), badScalar(t, g)...) + + decoded := new(frost.Signer) + if err := decoded.Decode(data); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Signer_InvalidCommitment(t *testing.T) { + expectedErrorPrefix := "can't decode nonce commitment" + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + s.Commit() + g := group.Group(test.Ciphersuite) + eLen := g.ElementLength() + sLen := g.ScalarLength() + pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen + confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + keyShareLen := len(s.KeyShare.Encode()) + offset := confLen + 4 + sLen + keyShareLen + 8 + 2*g.ScalarLength() + + encoded := s.Encode() + encoded[offset] = 0 + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func compareSignatureShares(t *testing.T, s1, s2 *frost.SignatureShare) { + if s1.Group != s2.Group { + t.Fatal("unexpected group") + } + + if s1.SignerIdentifier != s2.SignerIdentifier { + t.Fatal("expected equality") + } + + if s1.SignatureShare.Equal(s2.SignatureShare) != 1 { + t.Fatal("expected equality") + } +} + +func TestEncoding_SignatureShare(t *testing.T) { + message := []byte("message") + + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make([]*commitment.Commitment, len(signers)) + comsMap := make(map[uint64]*commitment.Commitment, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + comsMap[s.Identifier()] = coms[i] + } + + for _, s := range signers { + com := comsMap[s.Identifier()].CommitmentID + sigShare, err := s.Sign(com, message, coms) + if err != nil { + t.Fatal(err) + } + + encoded := sigShare.Encode() + + decoded := new(frost.SignatureShare) + if err = decoded.Decode(encoded); err != nil { + t.Fatalf("unexpected error %q", err) + } + + compareSignatureShares(t, sigShare, decoded) + } + }) +} + +func TestEncoding_SignatureShare_InvalidCiphersuite(t *testing.T) { + expectedError := internal.ErrInvalidCiphersuite + + encoded := make([]byte, 3) + + decoded := new(frost.SignatureShare) + if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + +func TestEncoding_SignatureShare_InvalidLength1(t *testing.T) { + expectedError := internal.ErrInvalidLength + + decoded := new(frost.SignatureShare) + if err := decoded.Decode([]byte{}); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + +func TestEncoding_SignatureShare_InvalidLength2(t *testing.T) { + expectedError := internal.ErrInvalidLength + + decoded := new(frost.SignatureShare) + if err := decoded.Decode([]byte{1, 0, 0}); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + +func TestEncoding_SignatureShare_ZeroID(t *testing.T) { + // todo: check for zero id in all decodings + expectedError := errors.New("identifier cannot be 0") + encoded := make([]byte, 41) + encoded[0] = 1 + + decoded := new(frost.SignatureShare) + if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + +func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { + expectedErrorPrefix := "failed to decode signature share: " + message := []byte("message") + + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make([]*commitment.Commitment, len(signers)) + comsMap := make(map[uint64]*commitment.Commitment, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + comsMap[s.Identifier()] = coms[i] + } + + s := signers[0] + com := comsMap[s.Identifier()].CommitmentID + + sigShare, err := s.Sign(com, message, coms) + if err != nil { + t.Fatal(err) + } + + encoded := sigShare.Encode() + slices.Replace(encoded, 9, 9+test.ECGroup().ScalarLength(), badScalar(t, test.ECGroup())...) + + decoded := new(frost.SignatureShare) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func compareSignatures(t *testing.T, s1, s2 *frost.Signature) { + if s1.R.Equal(s2.R) != 1 { + t.Fatal("expected equality") + } + + if s1.Z.Equal(s2.Z) != 1 { + t.Fatal("expected equality") + } +} + +func TestEncoding_Signature(t *testing.T) { + message := []byte("message") + + testAll(t, func(t *testing.T, test *tableTest) { + key := test.ECGroup().NewScalar().Random() + signature, err := debug.Sign(test.Ciphersuite, message, key) + if err != nil { + t.Fatal(err) + } + + encoded := signature.Encode() + + decoded := new(frost.Signature) + if err = decoded.Decode(test.Ciphersuite, encoded); err != nil { + t.Fatal(err) + } + + compareSignatures(t, signature, decoded) + }) +} + +func TestEncoding_Signature_InvalidCiphersuite(t *testing.T) { + decoded := new(frost.Signature) + if err := decoded.Decode(0, nil); err == nil || err.Error() != internal.ErrInvalidCiphersuite.Error() { + t.Fatalf("expected %q, got %q", internal.ErrInvalidCiphersuite, err) + } +} + +func TestEncoding_Signature_InvalidLength(t *testing.T) { + decoded := new(frost.Signature) + if err := decoded.Decode(1, make([]byte, 63)); err == nil || err.Error() != internal.ErrInvalidLength.Error() { + t.Fatalf("expected %q, got %q", internal.ErrInvalidLength, err) + } +} + +func TestEncoding_Signature_InvalidR(t *testing.T) { + expectedErrorPrefix := "invalid signature - decoding R:" + message := []byte("message") + + testAll(t, func(t *testing.T, test *tableTest) { + key := test.ECGroup().NewScalar().Random() + signature, err := debug.Sign(test.Ciphersuite, message, key) + if err != nil { + t.Fatal(err) + } + + encoded := signature.Encode() + slices.Replace( + encoded, + 0, + test.Ciphersuite.ECGroup().ElementLength(), + badElement(t, test.Ciphersuite.ECGroup())...) + + decoded := new(frost.Signature) + if err := decoded.Decode(test.Ciphersuite, encoded); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Signature_InvalidZ(t *testing.T) { + expectedErrorPrefix := "invalid signature - decoding Z:" + message := []byte("message") + + testAll(t, func(t *testing.T, test *tableTest) { + key := test.ECGroup().NewScalar().Random() + signature, err := debug.Sign(test.Ciphersuite, message, key) + if err != nil { + t.Fatal(err) + } + + encoded := signature.Encode() + g := test.Ciphersuite.ECGroup() + eLen := g.ElementLength() + sLen := g.ScalarLength() + slices.Replace(encoded, eLen, eLen+sLen, badScalar(t, g)...) + + decoded := new(frost.Signature) + if err := decoded.Decode(test.Ciphersuite, encoded); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} diff --git a/tests/frost_test.go b/tests/frost_test.go index 9fd609f..b8535b2 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -20,20 +20,29 @@ import ( type tableTest struct { frost.Ciphersuite + threshold, maxSigners uint64 } var testTable = []tableTest{ { Ciphersuite: frost.Ed25519, + threshold: 2, + maxSigners: 3, }, { Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, }, { Ciphersuite: frost.P256, + threshold: 2, + maxSigners: 3, }, { Ciphersuite: frost.Secp256k1, + threshold: 2, + maxSigners: 3, }, } @@ -110,12 +119,8 @@ func runFrost( keyShares []*frost.KeyShare, groupPublicKey *group.Element, ) { - // At key generation, each participant must send their public key share to the coordinator, and the collection - // must be broadcast to every participant. - publicKeyShares := make([]*frost.PublicKeyShare, 0, len(keyShares)) - for _, ks := range keyShares { - publicKeyShares = append(publicKeyShares, ks.Public()) - } + // Collect public keys. + publicKeyShares := getPublicKeyShares(keyShares) // Set up configuration. configuration := &frost.Configuration{ @@ -141,7 +146,7 @@ func runFrost( participants[i] = signer } - // Round One: Commitment + // Commit commitments := make(commitment.List, threshold) for i, p := range participants { commitments[i] = p.Commit() @@ -149,7 +154,7 @@ func runFrost( commitments.Sort() - // Round Two: Sign + // Sign sigShares := make([]*frost.SignatureShare, threshold) for i, p := range participants { var err error @@ -160,16 +165,12 @@ func runFrost( } } - // Final step: aggregate - signature, err := configuration.AggregateSignatures(message, sigShares, commitments, true) + // Aggregate + _, err := configuration.AggregateSignatures(message, sigShares, commitments, true) if err != nil { t.Fatal(err) } - if err = frost.VerifySignature(test.Ciphersuite, message, signature, groupPublicKey); err != nil { - t.Fatal(err) - } - // Sanity Check groupSecretKey, err := debug.RecoverGroupSecret(test.Ciphersuite, keyShares) if err != nil { @@ -187,27 +188,23 @@ func runFrost( } func TestFrost_WithTrustedDealer(t *testing.T) { - maxSigners := uint64(3) - threshold := uint64(2) message := []byte("test") testAll(t, func(t *testing.T, test *tableTest) { g := test.Ciphersuite.ECGroup() sk := g.NewScalar().Random() - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, sk, threshold, maxSigners) - runFrost(t, test, threshold, maxSigners, message, keyShares, groupPublicKey) + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, sk, test.threshold, test.maxSigners) + runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, groupPublicKey) }) } func TestFrost_WithDKG(t *testing.T) { - maxSigners := uint64(3) - threshold := uint64(2) message := []byte("test") testAll(t, func(t *testing.T, test *tableTest) { g := test.Ciphersuite.ECGroup() - keyShares, groupPublicKey, _ := runDKG(t, g, maxSigners, threshold) - runFrost(t, test, threshold, maxSigners, message, keyShares, groupPublicKey) + keyShares, groupPublicKey, _ := runDKG(t, g, test.maxSigners, test.threshold) + runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, groupPublicKey) }) } From 1821c6dd4118b64f2fc5f5b8c26d5e3e8c88813c Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:04:35 +0200 Subject: [PATCH 09/31] add tests, fixes Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment/commitment.go | 34 +-- debug/debug.go | 18 +- internal/core.go | 9 +- tests/dkg_test.go | 2 +- tests/encoding_test.go | 567 ++++++++++++++++++++++++++------------- tests/frost_test.go | 70 +---- tests/misc_test.go | 450 +++++++++++++++++++++++++++++++ 7 files changed, 874 insertions(+), 276 deletions(-) create mode 100644 tests/misc_test.go diff --git a/commitment/commitment.go b/commitment/commitment.go index 63524d6..40e15fc 100644 --- a/commitment/commitment.go +++ b/commitment/commitment.go @@ -16,7 +16,6 @@ import ( "slices" group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" ) var ( @@ -67,7 +66,7 @@ func (c *Commitment) Encode() []byte { // 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 { + if len(data) < 17 { return errDecodeCommitmentLength } @@ -82,13 +81,15 @@ func (c *Commitment) Decode(data []byte) error { cID := binary.LittleEndian.Uint64(data[1:9]) pID := binary.LittleEndian.Uint64(data[9:17]) - offset := 17 + g.ElementLength() + offset := 17 hn := g.NewElement() - if err := hn.Decode(data[17:offset]); err != nil { + if err := hn.Decode(data[offset : offset+g.ElementLength()]); err != nil { return fmt.Errorf("invalid encoding of hiding nonce: %w", err) } + offset += g.ElementLength() + bn := g.NewElement() if err := bn.Decode(data[offset : offset+g.ElementLength()]); err != nil { return fmt.Errorf("invalid encoding of binding nonce: %w", err) @@ -127,13 +128,6 @@ func (c List) IsSorted() bool { return slices.IsSortedFunc(c, cmpID) } -// Participants returns the list of participants in the commitment list in the form of a polynomial. -func (c List) Participants(g group.Group) secretsharing.Polynomial { - return secretsharing.NewPolynomialFromListFunc(g, c, func(c *Commitment) *group.Scalar { - return g.NewScalar().SetUInt64(c.SignerID) - }) -} - // Get returns the commitment of the participant with the corresponding identifier, or nil if it was not found. func (c List) Get(identifier uint64) *Commitment { for _, com := range c { @@ -152,10 +146,10 @@ func (c List) Encode() []byte { } g := c[0].Group - size := 8 + uint64(n)*EncodedSize(g) - out := make([]byte, 8, size) + size := 1 + 8 + uint64(n)*EncodedSize(g) + out := make([]byte, 9, size) out[0] = byte(g) - binary.LittleEndian.PutUint64(out, uint64(n)) + binary.LittleEndian.PutUint64(out[1:9], uint64(n)) for _, com := range c { out = append(out, com.Encode()...) @@ -169,25 +163,25 @@ func DecodeList(data []byte) (List, error) { return nil, errInvalidLength } - n := binary.LittleEndian.Uint64(data[:8]) - g := group.Group(data[8]) + g := group.Group(data[0]) if !g.Available() { return nil, errInvalidCiphersuite } + n := binary.LittleEndian.Uint64(data[1:9]) es := EncodedSize(g) - size := 8 + n*es + size := 1 + 8 + n*es if uint64(len(data)) != size { return nil, errInvalidLength } - c := make(List, n) + c := make(List, 0, n) - for offset := uint64(8); offset <= uint64(len(data)); offset += es { + for offset := uint64(9); offset < uint64(len(data)); offset += es { com := new(Commitment) if err := com.Decode(data[offset : offset+es]); err != nil { - return nil, err + return nil, fmt.Errorf("invalid encoding of commitment: %w", err) } c = append(c, com) diff --git a/debug/debug.go b/debug/debug.go index 94a79e8..1b7e77f 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -91,17 +91,25 @@ func RecoverGroupSecret(c frost.Ciphersuite, keyShares []*frost.KeyShare) (*grou } // Sign returns a Schnorr signature over the message msg with the full secret signing key (as opposed to a key share). -func Sign(c frost.Ciphersuite, msg []byte, key *group.Scalar) (*frost.Signature, error) { +// The optional random argument is the random k in Schnorr signatures. Setting it allows for reproducible signatures. +func Sign(c frost.Ciphersuite, msg []byte, key *group.Scalar, random ...*group.Scalar) (*frost.Signature, error) { g := c.ECGroup() if g == 0 { return nil, internal.ErrInvalidCiphersuite } - r := g.NewScalar().Random() - R := g.Base().Multiply(r) + var k *group.Scalar + + if len(random) != 0 && random[0] != nil { + k = random[0].Copy() + } else { + k = g.NewScalar().Random() + } + + R := g.Base().Multiply(k) pk := g.Base().Multiply(key) challenge := internal.SchnorrChallenge(g, msg, R, pk) - z := r.Add(challenge.Multiply(key)) + z := k.Add(challenge.Multiply(key)) return &frost.Signature{ R: R, @@ -110,7 +118,7 @@ func Sign(c frost.Ciphersuite, msg []byte, key *group.Scalar) (*frost.Signature, } // RecoverPublicKeys returns the group public key as well those from all participants, -// if the identifiers are 1, 2, ..., maxSigners. +// if the identifiers are 1, 2, ..., maxSigners, given the VSS commitment vector. func RecoverPublicKeys( c frost.Ciphersuite, maxSigners uint64, diff --git a/internal/core.go b/internal/core.go index a0de7f0..3d27126 100644 --- a/internal/core.go +++ b/internal/core.go @@ -12,6 +12,7 @@ import ( "fmt" group "github.com/bytemare/crypto" + secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/frost/commitment" ) @@ -29,8 +30,14 @@ func GroupCommitmentAndBindingFactors( return groupCommitment, bindingFactors } +func participantsFromCommitments(g group.Group, c commitment.List) secretsharing.Polynomial { + return secretsharing.NewPolynomialFromListFunc(g, c, func(c *commitment.Commitment) *group.Scalar { + return g.NewScalar().SetUInt64(c.SignerID) + }) +} + func computeLambda(g group.Group, commitments commitment.List, id uint64) (*group.Scalar, error) { - participantList := commitments.Participants(g) + participantList := participantsFromCommitments(g, commitments) l, err := participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) if err != nil { diff --git a/tests/dkg_test.go b/tests/dkg_test.go index 8701b96..1cc8d29 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -34,7 +34,7 @@ func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, func runDKG( t *testing.T, g group.Group, - maxSigners, threshold uint64, + threshold, maxSigners uint64, ) ([]*frost.KeyShare, *group.Element, []*group.Element) { c := dkg.Ciphersuite(g) diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 15f018a..cacd432 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -57,39 +57,53 @@ func badElement(t *testing.T, g group.Group) []byte { return encoded } -func comparePublicKeyShare(p1, p2 *frost.PublicKeyShare) error { - if p1.PublicKey.Equal(p2.PublicKey) != 1 { - return fmt.Errorf("Expected equality on PublicKey:\n\t%s\n\t%s\n", p1.PublicKey.Hex(), p2.PublicKey.Hex()) +func makeConf(t *testing.T, test *tableTest) *frost.Configuration { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, } - if p1.ID != p2.ID { - return fmt.Errorf("Expected equality on ID:\n\t%d\n\t%d\n", p1.ID, p2.ID) + if err := configuration.Init(); err != nil { + t.Fatal(err) } - if p1.Group != p2.Group { - return fmt.Errorf("Expected equality on Group:\n\t%v\n\t%v\n", p1.Group, p2.Group) + return configuration +} + +func makeSigners(t *testing.T, test *tableTest) []*frost.Signer { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, } - if len(p1.Commitment) != len(p2.Commitment) { - return fmt.Errorf( - "Expected equality on Commitment length:\n\t%d\n\t%d\n", - len(p1.Commitment), - len(p1.Commitment), - ) + if err := configuration.Init(); err != nil { + t.Fatal(err) } - for i := range p1.Commitment { - if p1.Commitment[i].Equal(p2.Commitment[i]) != 1 { - return fmt.Errorf( - "Expected equality on Commitment %d:\n\t%s\n\t%s\n", - i, - p1.Commitment[i].Hex(), - p1.Commitment[i].Hex(), - ) + signers := make([]*frost.Signer, test.maxSigners) + + for i, keyShare := range keyShares { + s, err := configuration.Signer(keyShare) + if err != nil { + t.Fatal(err) } + + signers[i] = s } - return nil + return signers } func getPublicKeyShares(keyShares []*frost.KeyShare) []*frost.PublicKeyShare { @@ -139,23 +153,151 @@ func compareConfigurations(t *testing.T, c1, c2 *frost.Configuration, expectedMa } } -func makeConf(t *testing.T, test *tableTest) *frost.Configuration { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) +func comparePublicKeyShare(p1, p2 *frost.PublicKeyShare) error { + if p1.PublicKey.Equal(p2.PublicKey) != 1 { + return fmt.Errorf("Expected equality on PublicKey:\n\t%s\n\t%s\n", p1.PublicKey.Hex(), p2.PublicKey.Hex()) + } - configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + if p1.ID != p2.ID { + return fmt.Errorf("Expected equality on ID:\n\t%d\n\t%d\n", p1.ID, p2.ID) } - if err := configuration.Init(); err != nil { + if p1.Group != p2.Group { + return fmt.Errorf("Expected equality on Group:\n\t%v\n\t%v\n", p1.Group, p2.Group) + } + + if len(p1.Commitment) != len(p2.Commitment) { + return fmt.Errorf( + "Expected equality on Commitment length:\n\t%d\n\t%d\n", + len(p1.Commitment), + len(p1.Commitment), + ) + } + + for i := range p1.Commitment { + if p1.Commitment[i].Equal(p2.Commitment[i]) != 1 { + return fmt.Errorf( + "Expected equality on Commitment %d:\n\t%s\n\t%s\n", + i, + p1.Commitment[i].Hex(), + p1.Commitment[i].Hex(), + ) + } + } + + return nil +} + +func compareKeyShares(s1, s2 *frost.KeyShare) error { + if s1.Secret.Equal(s2.Secret) != 1 { + return fmt.Errorf("Expected equality on Secret:\n\t%s\n\t%s\n", s1.Secret.Hex(), s2.Secret.Hex()) + } + + if s1.GroupPublicKey.Equal(s2.GroupPublicKey) != 1 { + return fmt.Errorf( + "Expected equality on GroupPublicKey:\n\t%s\n\t%s\n", + s1.GroupPublicKey.Hex(), + s2.GroupPublicKey.Hex(), + ) + } + + return comparePublicKeyShare(s1.Public(), s2.Public()) +} + +func compareCommitments(c1, c2 *commitment.Commitment) error { + if c1.Group != c2.Group { + return errors.New("different groups") + } + + if c1.SignerID != c2.SignerID { + return errors.New("different SignerID") + } + + if c1.CommitmentID != c2.CommitmentID { + return errors.New("different CommitmentID") + } + + if c1.HidingNonce.Equal(c2.HidingNonce) != 1 { + return errors.New("different HidingNonce") + } + + if c1.BindingNonce.Equal(c2.BindingNonce) != 1 { + return errors.New("different BindingNonce") + } + + return nil +} + +func compareNonceCommitments(c1, c2 *frost.NonceCommitment) error { + if c1.HidingNonceS.Equal(c2.HidingNonceS) != 1 { + return errors.New("different HidingNonceS") + } + + if c1.BindingNonceS.Equal(c2.BindingNonceS) != 1 { + return errors.New("different BindingNonceS") + } + + return compareCommitments(c1.Commitment, c2.Commitment) +} + +func compareSigners(t *testing.T, s1, s2 *frost.Signer) { + if err := compareKeyShares(s1.KeyShare, s2.KeyShare); err != nil { t.Fatal(err) } - return configuration + if !((s1.Lambda == nil && (s2.Lambda == nil || s2.Lambda.IsZero())) || (s2.Lambda == nil && (s1.Lambda == nil || s1.Lambda.IsZero()))) { + t.Fatalf("expected equality: %v / %v", s1.Lambda, s2.Lambda.IsZero()) + } + + if len(s1.Commitments) != len(s2.Commitments) { + t.Fatal("expected equality") + } + + for id, com := range s1.Commitments { + if com2, exists := s2.Commitments[id]; !exists { + t.Fatalf("com id %d does not exist in s2", id) + } else { + if err := compareNonceCommitments(com, com2); err != nil { + t.Fatal(err) + } + } + } + + if bytes.Compare(s1.HidingRandom, s2.HidingRandom) != 0 { + t.Fatal("expected equality") + } + + if bytes.Compare(s1.BindingRandom, s2.BindingRandom) != 0 { + t.Fatal("expected equality") + } + + compareConfigurations(t, s1.Configuration, s2.Configuration, true) +} + +func compareSignatureShares(t *testing.T, s1, s2 *frost.SignatureShare) { + if s1.Group != s2.Group { + t.Fatal("unexpected group") + } + + if s1.SignerIdentifier != s2.SignerIdentifier { + t.Fatal("expected equality") + } + + if s1.SignatureShare.Equal(s2.SignatureShare) != 1 { + t.Fatal("expected equality") + } +} + +func compareSignatures(s1, s2 *frost.Signature, expectEqual bool) error { + if s1.R.Equal(s2.R) == 1 != expectEqual { + return fmt.Errorf("expected %v R", expectEqual) + } + + if s1.Z.Equal(s2.Z) == 1 != expectEqual { + return fmt.Errorf("expected %v Z", expectEqual) + } + + return nil } func TestEncoding_Configuration(t *testing.T) { @@ -270,122 +412,6 @@ func TestEncoding_Configuration_InvalidPublicKeyShare(t *testing.T) { }) } -func compareKeyShares(s1, s2 *frost.KeyShare) error { - if s1.Secret.Equal(s2.Secret) != 1 { - return fmt.Errorf("Expected equality on Secret:\n\t%s\n\t%s\n", s1.Secret.Hex(), s2.Secret.Hex()) - } - - if s1.GroupPublicKey.Equal(s2.GroupPublicKey) != 1 { - return fmt.Errorf( - "Expected equality on GroupPublicKey:\n\t%s\n\t%s\n", - s1.GroupPublicKey.Hex(), - s2.GroupPublicKey.Hex(), - ) - } - - return comparePublicKeyShare(s1.Public(), s2.Public()) -} - -func compareCommitments(c1, c2 *commitment.Commitment) error { - if c1.Group != c2.Group { - return errors.New("different groups") - } - - if c1.SignerID != c2.SignerID { - return errors.New("different SignerID") - } - - if c1.CommitmentID != c2.CommitmentID { - return errors.New("different CommitmentID") - } - - if c1.HidingNonce.Equal(c2.HidingNonce) != 1 { - return errors.New("different HidingNonce") - } - - if c1.BindingNonce.Equal(c2.BindingNonce) != 1 { - return errors.New("different BindingNonce") - } - - return nil -} - -func compareNonceCommitments(c1, c2 *frost.NonceCommitment) error { - if c1.HidingNonceS.Equal(c2.HidingNonceS) != 1 { - return errors.New("different HidingNonceS") - } - - if c1.BindingNonceS.Equal(c2.BindingNonceS) != 1 { - return errors.New("different BindingNonceS") - } - - return compareCommitments(c1.Commitment, c2.Commitment) -} - -func makeSigners(t *testing.T, test *tableTest) []*frost.Signer { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.Init(); err != nil { - t.Fatal(err) - } - - signers := make([]*frost.Signer, test.maxSigners) - - for i, keyShare := range keyShares { - s, err := configuration.Signer(keyShare) - if err != nil { - t.Fatal(err) - } - - signers[i] = s - } - - return signers -} - -func compareSigners(t *testing.T, s1, s2 *frost.Signer) { - if err := compareKeyShares(s1.KeyShare, s2.KeyShare); err != nil { - t.Fatal(err) - } - - if !((s1.Lambda == nil && (s2.Lambda == nil || s2.Lambda.IsZero())) || (s2.Lambda == nil && (s1.Lambda == nil || s1.Lambda.IsZero()))) { - t.Fatalf("expected equality: %v / %v", s1.Lambda, s2.Lambda.IsZero()) - } - - if len(s1.Commitments) != len(s2.Commitments) { - t.Fatal("expected equality") - } - - for id, com := range s1.Commitments { - if com2, exists := s2.Commitments[id]; !exists { - t.Fatalf("com id %d does not exist in s2", id) - } else { - if err := compareNonceCommitments(com, com2); err != nil { - t.Fatal(err) - } - } - } - - if bytes.Compare(s1.HidingRandom, s2.HidingRandom) != 0 { - t.Fatal("expected equality") - } - - if bytes.Compare(s1.BindingRandom, s2.BindingRandom) != 0 { - t.Fatal("expected equality") - } - - compareConfigurations(t, s1.Configuration, s2.Configuration, true) -} - func TestEncoding_Signer(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] @@ -617,34 +643,18 @@ func TestEncoding_Signer_InvalidCommitment(t *testing.T) { }) } -func compareSignatureShares(t *testing.T, s1, s2 *frost.SignatureShare) { - if s1.Group != s2.Group { - t.Fatal("unexpected group") - } - - if s1.SignerIdentifier != s2.SignerIdentifier { - t.Fatal("expected equality") - } - - if s1.SignatureShare.Equal(s2.SignatureShare) != 1 { - t.Fatal("expected equality") - } -} - func TestEncoding_SignatureShare(t *testing.T) { message := []byte("message") testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make([]*commitment.Commitment, len(signers)) - comsMap := make(map[uint64]*commitment.Commitment, len(signers)) + coms := make(commitment.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() - comsMap[s.Identifier()] = coms[i] } for _, s := range signers { - com := comsMap[s.Identifier()].CommitmentID + com := coms.Get(s.Identifier()).CommitmentID sigShare, err := s.Sign(com, message, coms) if err != nil { t.Fatal(err) @@ -709,15 +719,13 @@ func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make([]*commitment.Commitment, len(signers)) - comsMap := make(map[uint64]*commitment.Commitment, len(signers)) + coms := make(commitment.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() - comsMap[s.Identifier()] = coms[i] } s := signers[0] - com := comsMap[s.Identifier()].CommitmentID + com := coms.Get(s.Identifier()).CommitmentID sigShare, err := s.Sign(com, message, coms) if err != nil { @@ -734,16 +742,6 @@ func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { }) } -func compareSignatures(t *testing.T, s1, s2 *frost.Signature) { - if s1.R.Equal(s2.R) != 1 { - t.Fatal("expected equality") - } - - if s1.Z.Equal(s2.Z) != 1 { - t.Fatal("expected equality") - } -} - func TestEncoding_Signature(t *testing.T) { message := []byte("message") @@ -761,7 +759,9 @@ func TestEncoding_Signature(t *testing.T) { t.Fatal(err) } - compareSignatures(t, signature, decoded) + if err = compareSignatures(signature, decoded, true); err != nil { + t.Fatal(err) + } }) } @@ -829,3 +829,206 @@ func TestEncoding_Signature_InvalidZ(t *testing.T) { } }) } + +func TestEncoding_Commitment(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + encoded := com.Encode() + + decoded := new(commitment.Commitment) + if err := decoded.Decode(encoded); err != nil { + t.Fatal(err) + } + + if err := compareCommitments(com, decoded); err != nil { + t.Fatal(err) + } + }) +} + +func TestEncoding_Commitment_BadCiphersuite(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite.Error() + + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + encoded := com.Encode() + encoded[0] = 0 + + decoded := new(commitment.Commitment) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Commitment_InvalidLength1(t *testing.T) { + expectedErrorPrefix := "failed to decode commitment: invalid length" + + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + encoded := com.Encode() + + decoded := new(commitment.Commitment) + if err := decoded.Decode(encoded[:16]); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Commitment_InvalidLength2(t *testing.T) { + expectedErrorPrefix := "failed to decode commitment: invalid length" + + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + encoded := com.Encode() + + decoded := new(commitment.Commitment) + if err := decoded.Decode(encoded[:35]); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Commitment_InvalidHidingNonce(t *testing.T) { + expectedErrorPrefix := "invalid encoding of hiding nonce: " + + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + encoded := com.Encode() + bad := badElement(t, test.ECGroup()) + slices.Replace(encoded, 17, 17+test.ECGroup().ElementLength(), bad...) + + decoded := new(commitment.Commitment) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_Commitment_InvalidBindingNonce(t *testing.T) { + expectedErrorPrefix := "invalid encoding of binding nonce: " + + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + encoded := com.Encode() + g := test.ECGroup() + bad := badElement(t, g) + slices.Replace(encoded, 17+g.ElementLength(), 17+2*g.ElementLength(), bad...) + + decoded := new(commitment.Commitment) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_CommitmentList(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(commitment.List, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + encoded := coms.Encode() + + list, err := commitment.DecodeList(encoded) + if err != nil { + t.Fatal(err) + } + + if len(list) != len(coms) { + t.Fatalf("want %d, got %d", len(coms), len(list)) + } + + for i, com := range coms { + if err = compareCommitments(com, list[i]); err != nil { + t.Fatal(err) + } + } + }) +} + +func TestEncoding_CommitmentList_InvalidCiphersuite(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite.Error() + + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(commitment.List, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + encoded := coms.Encode() + encoded[0] = 0 + + if _, err := commitment.DecodeList(encoded); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_CommitmentList_InvalidLength1(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidLength.Error() + + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(commitment.List, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + encoded := coms.Encode() + + if _, err := commitment.DecodeList(encoded[:8]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_CommitmentList_InvalidLength2(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidLength.Error() + + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(commitment.List, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + encoded := coms.Encode() + + if _, err := commitment.DecodeList(encoded[:9]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { + expectedErrorPrefix := "invalid encoding of commitment: " + internal.ErrInvalidCiphersuite.Error() + + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(commitment.List, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + encoded := coms.Encode() + encoded[9] = 0 + + if _, err := commitment.DecodeList(encoded); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} diff --git a/tests/frost_test.go b/tests/frost_test.go index b8535b2..65df9ae 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -9,6 +9,7 @@ package frost_test import ( + "fmt" "testing" group "github.com/bytemare/crypto" @@ -46,71 +47,6 @@ var testTable = []tableTest{ }, } -func TestTrustedDealerKeygen(t *testing.T) { - threshold := uint64(3) - maxSigners := uint64(5) - - testAll(t, func(t *testing.T, test *tableTest) { - g := test.Ciphersuite.ECGroup() - - groupSecretKey := g.NewScalar().Random() - - keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( - test.Ciphersuite, - groupSecretKey, - threshold, - maxSigners, - ) - - if uint64(len(secretsharingCommitment)) != threshold { - t.Fatalf("%d / %d", len(secretsharingCommitment), threshold) - } - - recoveredKey, err := debug.RecoverGroupSecret(test.Ciphersuite, keyShares[:threshold]) - if err != nil { - t.Fatal(err) - } - - if recoveredKey.Equal(groupSecretKey) != 1 { - t.Fatal() - } - - groupPublicKey, participantPublicKeys, err := debug.RecoverPublicKeys( - test.Ciphersuite, - maxSigners, - secretsharingCommitment, - ) - if err != nil { - t.Fatal(err) - } - - if uint64(len(participantPublicKeys)) != maxSigners { - t.Fatal() - } - - if groupPublicKey.Equal(dealerGroupPubKey) != 1 { - t.Fatal() - } - - for i, shareI := range keyShares { - if !debug.VerifyVSS(g, shareI, secretsharingCommitment) { - t.Fatal(i) - } - } - - pkEnc := groupPublicKey.Encode() - - recoveredPK := g.NewElement() - if err := recoveredPK.Decode(pkEnc); err != nil { - t.Fatal(err) - } - - if recoveredPK.Equal(groupPublicKey) != 1 { - t.Fatal() - } - }) -} - func runFrost( t *testing.T, test *tableTest, @@ -203,14 +139,14 @@ func TestFrost_WithDKG(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { g := test.Ciphersuite.ECGroup() - keyShares, groupPublicKey, _ := runDKG(t, g, test.maxSigners, test.threshold) + keyShares, groupPublicKey, _ := runDKG(t, g, test.threshold, test.maxSigners) runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, groupPublicKey) }) } func testAll(t *testing.T, f func(*testing.T, *tableTest)) { for _, test := range testTable { - t.Run(string(test.ECGroup()), func(t *testing.T) { + t.Run(fmt.Sprintf("%s", test.ECGroup()), func(t *testing.T) { f(t, &test) }) } diff --git a/tests/misc_test.go b/tests/misc_test.go new file mode 100644 index 0000000..e5efda7 --- /dev/null +++ b/tests/misc_test.go @@ -0,0 +1,450 @@ +// 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 frost_test + +import ( + "errors" + "fmt" + "strings" + "testing" + + group "github.com/bytemare/crypto" + "github.com/bytemare/dkg" + + "github.com/bytemare/frost" + "github.com/bytemare/frost/commitment" + "github.com/bytemare/frost/debug" + "github.com/bytemare/frost/internal" +) + +var ( + errNoPanic = errors.New("no panic") + errNoPanicMessage = errors.New("panic but no message") +) + +func hasPanic(f func()) (has bool, err error) { + defer func() { + var report any + if report = recover(); report != nil { + has = true + err = fmt.Errorf("%v", report) + } + }() + + f() + + return has, err +} + +// testPanic executes the function f with the expectation to recover from a panic. If no panic occurred or if the +// panic message is not the one expected, ExpectPanic returns an error. +func testPanic(s string, expectedError error, f func()) error { + hasPanic, err := hasPanic(f) + + // if there was no panic + if !hasPanic { + return errNoPanic + } + + // panic, and we don't expect a particular message + if expectedError == nil { + return nil + } + + // panic, but the panic value is empty + if err == nil { + return errNoPanicMessage + } + + // panic, but the panic value is not what we expected + if err.Error() != expectedError.Error() { + return fmt.Errorf("expected panic on %s with message %q, got %q", s, expectedError, err) + } + + return nil +} + +func TestCommitmentList_Sort(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(commitment.List, len(signers)) + + // signer A < signer B + coms[0] = signers[0].Commit() + coms[1] = signers[1].Commit() + coms[2] = signers[2].Commit() + + coms.Sort() + + if !coms.IsSorted() { + t.Fatal("expected sorted") + } + + // signer B > singer A + coms[0] = signers[1].Commit() + coms[1] = signers[0].Commit() + + coms.Sort() + + if !coms.IsSorted() { + t.Fatal("expected sorted") + } + + // signer B > singer A + coms[0] = signers[0].Commit() + coms[1] = signers[2].Commit() + coms[2] = signers[2].Commit() + + coms.Sort() + + if !coms.IsSorted() { + t.Fatal("expected sorted") + } + }) +} + +func verifyTrustedDealerKeygen( + t *testing.T, + test *tableTest, + ks []*frost.KeyShare, + pk *group.Element, + coms []*group.Element, +) { + if uint64(len(coms)) != test.threshold { + t.Fatalf("%d / %d", len(coms), test.threshold) + } + + recoveredKey, err := debug.RecoverGroupSecret(test.Ciphersuite, ks[:test.threshold]) + if err != nil { + t.Fatal(err) + } + + groupPublicKey, participantPublicKeys, err := debug.RecoverPublicKeys( + test.Ciphersuite, + test.maxSigners, + coms, + ) + if err != nil { + t.Fatal(err) + } + + if uint64(len(participantPublicKeys)) != test.maxSigners { + t.Fatal() + } + + if groupPublicKey.Equal(pk) != 1 { + t.Fatal() + } + + g := test.Ciphersuite.ECGroup() + + for i, shareI := range ks { + if !debug.VerifyVSS(g, shareI, coms) { + t.Fatal(i) + } + } + + sig, err := debug.Sign(test.Ciphersuite, []byte("message"), recoveredKey) + if err != nil { + t.Fatal(err) + } + + if err = frost.VerifySignature(test.Ciphersuite, []byte("message"), sig, groupPublicKey); err != nil { + t.Fatal(err) + } +} + +func TestTrustedDealerKeygen(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Ciphersuite.ECGroup() + groupSecretKey := g.NewScalar().Random() + keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( + test.Ciphersuite, + groupSecretKey, + test.threshold, + test.maxSigners, + ) + + verifyTrustedDealerKeygen(t, test, keyShares, dealerGroupPubKey, secretsharingCommitment) + }) +} + +func TestTrustedDealerKeygenNoSecret(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + + verifyTrustedDealerKeygen(t, test, keyShares, dealerGroupPubKey, secretsharingCommitment) + }) +} + +func TestTrustedDealerKeygenWrongParams(t *testing.T) { + errTooFewShares := errors.New("number of shares must be equal or greater than the threshold") + + testAll(t, func(t *testing.T, test *tableTest) { + if err := testPanic("wrong params", errTooFewShares, func() { + _, _, _ = debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + 5, + 3, + ) + }); err != nil { + t.Fatal(err) + } + }) +} + +func TestRecoverGroupSecretInvalidCiphersuite(t *testing.T) { + expectedError := internal.ErrInvalidCiphersuite + if _, err := debug.RecoverGroupSecret(0, nil); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + +func TestRecoverGroupSecretNoShares(t *testing.T) { + expectedError := "failed to reconstruct group secret: " + if _, err := debug.RecoverGroupSecret(frost.Ristretto255, nil); err == nil || + !strings.HasPrefix(err.Error(), expectedError) { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + +func TestSchnorrSign(t *testing.T) { + message1 := []byte("message-1") + message2 := []byte("message-2") + testAll(t, func(t *testing.T, test *tableTest) { + g := test.ECGroup() + secretKey1 := g.NewScalar().Random() + verificationKey1 := g.Base().Multiply(secretKey1) + secretKey2 := g.NewScalar().Random() + + // Signature must be valid. + signature, err := debug.Sign(test.Ciphersuite, message1, secretKey1) + if err != nil { + t.Fatal(err) + } + + if err = frost.VerifySignature(test.Ciphersuite, message1, signature, verificationKey1); err != nil { + t.Fatal(err) + } + + // Same key, different messages = different signatures + signature1, err := debug.Sign(test.Ciphersuite, message1, secretKey1) + if err != nil { + t.Fatal(err) + } + + signature2, err := debug.Sign(test.Ciphersuite, message2, secretKey1) + if err != nil { + t.Fatal(err) + } + + if err = compareSignatures(signature1, signature2, false); err != nil { + t.Fatal(err) + } + + // Same key, same message = different signatures + signature1, err = debug.Sign(test.Ciphersuite, message1, secretKey1) + if err != nil { + t.Fatal(err) + } + + signature2, err = debug.Sign(test.Ciphersuite, message1, secretKey1) + if err != nil { + t.Fatal(err) + } + + if err = compareSignatures(signature1, signature2, false); err != nil { + t.Fatal(err) + } + + // Same key, same message, same random = same signatures + k := g.NewScalar().Random() + + signature1, err = debug.Sign(test.Ciphersuite, message1, secretKey1, k) + if err != nil { + t.Fatal(err) + } + + signature2, err = debug.Sign(test.Ciphersuite, message1, secretKey1, k) + if err != nil { + t.Fatal(err) + } + + if err = compareSignatures(signature1, signature2, true); err != nil { + t.Fatal(err) + } + + // Same key, same message, explicit different random = same signatures + k1 := g.NewScalar().Random() + k2 := g.NewScalar().Random() + + if k1.Equal(k2) == 1 { + t.Fatal("unexpected equality") + } + + signature1, err = debug.Sign(test.Ciphersuite, message1, secretKey1, k1) + if err != nil { + t.Fatal(err) + } + + signature2, err = debug.Sign(test.Ciphersuite, message1, secretKey1, k2) + if err != nil { + t.Fatal(err) + } + + if err = compareSignatures(signature1, signature2, false); err != nil { + t.Fatal(err) + } + + // Different keys, same message = different signatures + signature1, err = debug.Sign(test.Ciphersuite, message1, secretKey1) + if err != nil { + t.Fatal(err) + } + + signature2, err = debug.Sign(test.Ciphersuite, message1, secretKey2) + if err != nil { + t.Fatal(err) + } + + if err = compareSignatures(signature1, signature2, false); err != nil { + t.Fatal(err) + } + + // Different keys, different messages = different signatures + signature1, err = debug.Sign(test.Ciphersuite, message1, secretKey1) + if err != nil { + t.Fatal(err) + } + + signature2, err = debug.Sign(test.Ciphersuite, message2, secretKey2) + if err != nil { + t.Fatal(err) + } + + if err = compareSignatures(signature1, signature2, false); err != nil { + t.Fatal(err) + } + }) +} + +func TestSign_InvalidCiphersuite(t *testing.T) { + expectedError := internal.ErrInvalidCiphersuite + if _, err := debug.Sign(0, nil, nil); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + +func TestRecoverPublicKeys(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + + groupPublicKey, participantPublicKeys, err := debug.RecoverPublicKeys( + test.Ciphersuite, + test.maxSigners, + secretsharingCommitment, + ) + if err != nil { + t.Fatal(err) + } + + if dealerGroupPubKey.Equal(groupPublicKey) != 1 { + t.Fatal("expected equality") + } + + if len(participantPublicKeys) != len(keyShares) { + t.Fatal("expected equality") + } + + for i, keyShare := range keyShares { + if keyShare.PublicKey.Equal(participantPublicKeys[i]) != 1 { + t.Fatal("expected equality") + } + } + }) +} + +func TestRecoverPublicKeys_InvalidCiphersuite(t *testing.T) { + expectedError := internal.ErrInvalidCiphersuite + if _, _, err := debug.RecoverPublicKeys(0, 0, nil); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + +func TestPublicKeyShareVerification(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, dealerGroupPubKey, _ := runDKG( + t, + test.Ciphersuite.ECGroup(), + test.threshold, + test.maxSigners, + ) + + vssComs := make([][]*group.Element, test.maxSigners) + pkShares := make([]*frost.PublicKeyShare, test.maxSigners) + + for i, keyShare := range keyShares { + pk := keyShare.Public() + vssComs[i] = pk.Commitment + pkShares[i] = pk + } + + if err := dkg.VerifyPublicKey(dkg.Ciphersuite(test.Ciphersuite), 0, dealerGroupPubKey, vssComs); err != nil { + t.Fatal(err) + } + + for _, pk := range pkShares { + if !pk.Verify(vssComs) { + t.Fatal("expected validity") + } + } + }) +} + +func TestPublicKeyShareVerificationFail(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, dealerGroupPubKey, _ := runDKG( + t, + test.Ciphersuite.ECGroup(), + test.threshold, + test.maxSigners, + ) + + vssComs := make([][]*group.Element, test.maxSigners) + pkShares := make([]*frost.PublicKeyShare, test.maxSigners) + + for i, keyShare := range keyShares { + pk := keyShare.Public() + vssComs[i] = pk.Commitment + pk.PublicKey = nil + pkShares[i] = pk + } + + if err := dkg.VerifyPublicKey(dkg.Ciphersuite(test.Ciphersuite), 0, dealerGroupPubKey, vssComs); err != nil { + t.Fatal(err) + } + + for _, pk := range pkShares { + if pk.Verify(vssComs) { + t.Fatal("expected invalidity") + } + } + }) +} From d0dabb1261df70893d3e63534cb20f31abe6278d Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:13:34 +0200 Subject: [PATCH 10/31] update licence headers Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- .github/licence-header.tmpl | 2 +- commitment/commitment.go | 2 +- coordinator.go | 2 +- debug/debug.go | 2 +- examples_test.go | 2 +- frost.go | 2 +- internal/hashing.go | 2 +- internal/utils.go | 2 +- signer.go | 2 +- tests/encoding_test.go | 2 +- tests/frost_error_test.go | 9 +++++++++ tests/frost_test.go | 2 +- tests/misc_test.go | 2 +- tests/vector_utils_test.go | 2 +- tests/vectors_test.go | 2 +- 15 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 tests/frost_error_test.go diff --git a/.github/licence-header.tmpl b/.github/licence-header.tmpl index 12c483f..4d3a906 100644 --- a/.github/licence-header.tmpl +++ b/.github/licence-header.tmpl @@ -1,6 +1,6 @@ SPDX-License-Identifier: MIT -Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +Copyright (C) 2024 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 diff --git a/commitment/commitment.go b/commitment/commitment.go index 40e15fc..24f4932 100644 --- a/commitment/commitment.go +++ b/commitment/commitment.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/coordinator.go b/coordinator.go index 0000e36..bf0b646 100644 --- a/coordinator.go +++ b/coordinator.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/debug/debug.go b/debug/debug.go index 1b7e77f..db23e03 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/examples_test.go b/examples_test.go index ed5953b..2f61ee0 100644 --- a/examples_test.go +++ b/examples_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/frost.go b/frost.go index b19e7a9..a9a27d4 100644 --- a/frost.go +++ b/frost.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/internal/hashing.go b/internal/hashing.go index 83c4c37..f421a42 100644 --- a/internal/hashing.go +++ b/internal/hashing.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/internal/utils.go b/internal/utils.go index 8c28c63..c8206bf 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/signer.go b/signer.go index 2f5cd17..11b1316 100644 --- a/signer.go +++ b/signer.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/tests/encoding_test.go b/tests/encoding_test.go index cacd432..77fc89b 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go new file mode 100644 index 0000000..9e8e9f0 --- /dev/null +++ b/tests/frost_error_test.go @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 frost_test diff --git a/tests/frost_test.go b/tests/frost_test.go index 65df9ae..ad9acce 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/tests/misc_test.go b/tests/misc_test.go index e5efda7..96f5454 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index 97bc666..597a1f6 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 diff --git a/tests/vectors_test.go b/tests/vectors_test.go index 7bb5be5..e743927 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // -// Copyright (C) 2023 Daniel Bourdrez. All Rights Reserved. +// Copyright (C) 2024 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 From 02cb0af277ea4bc5da42b86f6710d67c6509fe87 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Thu, 22 Aug 2024 18:03:22 +0200 Subject: [PATCH 11/31] some refactor, simplification, added tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment/commitment.go => commitment.go | 163 +++++++- coordinator.go | 25 +- encoding.go | 7 +- examples_test.go | 5 +- frost.go | 13 +- internal/challenge.go | 56 +++ internal/core.go | 198 --------- internal/utils.go | 7 +- signer.go | 24 +- tests/encoding_test.go | 74 +--- tests/frost_error_test.go | 475 ++++++++++++++++++++++ tests/frost_test.go | 3 +- tests/misc_test.go | 51 +-- tests/utils_test.go | 131 ++++++ tests/vectors_test.go | 3 +- 15 files changed, 889 insertions(+), 346 deletions(-) rename commitment/commitment.go => commitment.go (51%) create mode 100644 internal/challenge.go delete mode 100644 internal/core.go create mode 100644 tests/utils_test.go diff --git a/commitment/commitment.go b/commitment.go similarity index 51% rename from commitment/commitment.go rename to commitment.go index 24f4932..b44b4e8 100644 --- a/commitment/commitment.go +++ b/commitment.go @@ -7,7 +7,7 @@ // https://spdx.org/licenses/MIT.html // Package commitment defines the FROST Signer commitment. -package commitment +package frost import ( "encoding/binary" @@ -16,12 +16,17 @@ import ( "slices" group "github.com/bytemare/crypto" + secretsharing "github.com/bytemare/secret-sharing" + + "github.com/bytemare/frost/internal" ) var ( errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") errInvalidCiphersuite = errors.New("ciphersuite not available") errInvalidLength = errors.New("invalid encoding length") + errHidingNonce = errors.New("invalid hiding nonce (nil, identity, or generator)") + errBindingNonce = errors.New("invalid binding nonce (nil, identity, or generator)") ) // Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. @@ -33,6 +38,30 @@ type Commitment struct { Group group.Group } +// Verify returns an error if the commitment is +func (c *Commitment) Verify(g group.Group) error { + if c.Group != g { + return fmt.Errorf( + "commitment for participant %d has an unexpected ciphersuite: expected %s, got %s", + c.SignerID, + g, + c.Group, + ) + } + + generator := g.Base() + + if c.HidingNonce == nil || c.HidingNonce.IsIdentity() || c.HidingNonce.Equal(generator) == 1 { + return errHidingNonce + } + + if c.BindingNonce == nil || c.BindingNonce.IsIdentity() || c.BindingNonce.Equal(generator) == 1 { + return errBindingNonce + } + + return nil +} + // Copy returns a new Commitment struct populated with the same values as the receiver. func (c *Commitment) Copy() *Commitment { return &Commitment{ @@ -139,6 +168,66 @@ func (c List) Get(identifier uint64) *Commitment { return nil } +// ParticipantsUInt64 returns the uint64 list of participant identifiers in the list. +func (c List) ParticipantsUInt64() []uint64 { + out := make([]uint64, len(c)) + + for i, com := range c { + out[i] = com.SignerID + } + + return out +} + +// ParticipantsScalar returns the group.Scalar list of participant identifier in the list +func (c List) ParticipantsScalar() []*group.Scalar { + if len(c) == 0 { + return nil + } + + if c[0] == nil { + return nil + } + + g := c[0].Group + + return secretsharing.NewPolynomialFromListFunc(g, c, func(c *Commitment) *group.Scalar { + return g.NewScalar().SetUInt64(c.SignerID) + }) +} + +// Verify checks for the Commitment list's integrity. +func (c List) Verify(g group.Group, threshold uint64) error { + // Verify number of commitments. + if uint64(len(c)) < threshold { + return fmt.Errorf("too few commitments: expected at least %d but got %d", threshold, len(c)) + } + + // Ensure the list is sorted + if !c.IsSorted() { + c.Sort() + } + + // set to detect duplication + set := make(map[uint64]struct{}, len(c)) + + for _, com := range c { + // Check for duplicate participant entries. + if _, exists := set[com.SignerID]; exists { + return fmt.Errorf("commitment list contains multiple commitments of participant %d", com.SignerID) + } + + set[com.SignerID] = struct{}{} + + // Check general consistency. + if err := com.Verify(g); err != nil { + return err + } + } + + return nil +} + func (c List) Encode() []byte { n := len(c) if n == 0 { @@ -189,3 +278,75 @@ func DecodeList(data []byte) (List, error) { return c, nil } + +func (c List) GroupCommitmentAndBindingFactors( + publicKey *group.Element, + message []byte, +) (*group.Element, BindingFactors) { + bindingFactors := c.bindingFactors(publicKey, message) + groupCommitment := c.groupCommitment(bindingFactors) + + return groupCommitment, bindingFactors +} + +type commitmentWithEncodedID struct { + *Commitment + ParticipantID []byte +} + +func commitmentsWithEncodedID(g group.Group, commitments List) []*commitmentWithEncodedID { + r := make([]*commitmentWithEncodedID, len(commitments)) + for i, com := range commitments { + r[i] = &commitmentWithEncodedID{ + ParticipantID: g.NewScalar().SetUInt64(com.SignerID).Encode(), + Commitment: com, + } + } + + return r +} + +func encodeCommitmentList(g group.Group, commitments []*commitmentWithEncodedID) []byte { + size := len(commitments) * (g.ScalarLength() + 2*g.ElementLength()) + encoded := make([]byte, 0, size) + + for _, com := range commitments { + encoded = append(encoded, com.ParticipantID...) + encoded = append(encoded, com.HidingNonce.Encode()...) + encoded = append(encoded, com.BindingNonce.Encode()...) + } + + return encoded +} + +// BindingFactors is a map of participant identifier to BindingFactors. +type BindingFactors map[uint64]*group.Scalar + +func (c List) bindingFactors(publicKey *group.Element, message []byte) BindingFactors { + g := c[0].Group + coms := commitmentsWithEncodedID(g, c) + encodedCommitHash := internal.H5(g, encodeCommitmentList(g, coms)) + h := internal.H4(g, message) + rhoInputPrefix := internal.Concatenate(publicKey.Encode(), h, encodedCommitHash) + bindingFactors := make(BindingFactors, len(c)) + + for _, com := range coms { + rhoInput := internal.Concatenate(rhoInputPrefix, com.ParticipantID) + bindingFactors[com.Commitment.SignerID] = internal.H1(g, rhoInput) + } + + return bindingFactors +} + +func (c List) groupCommitment(bf BindingFactors) *group.Element { + g := c[0].Group + gc := g.NewElement() + + for _, com := range c { + factor := bf[com.SignerID] + bindingNonce := com.BindingNonce.Copy().Multiply(factor) + gc.Add(com.HidingNonce).Add(bindingNonce) + } + + return gc +} diff --git a/coordinator.go b/coordinator.go index bf0b646..1e4b095 100644 --- a/coordinator.go +++ b/coordinator.go @@ -14,7 +14,6 @@ import ( group "github.com/bytemare/crypto" - "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/internal" ) @@ -40,7 +39,7 @@ type Signature struct { func (c *Configuration) AggregateSignatures( message []byte, sigShares []*SignatureShare, - commitments commitment.List, + commitments List, verify bool, ) (*Signature, error) { if !c.verified { @@ -89,7 +88,7 @@ func (c *Configuration) AggregateSignatures( func (c *Configuration) VerifySignatureShare( sigShare *SignatureShare, message []byte, - commitments commitment.List, + commitments List, ) error { if !c.verified { if err := c.verify(); err != nil { @@ -106,24 +105,22 @@ func (c *Configuration) VerifySignatureShare( } func (c *Configuration) prepSigShareCheck(message []byte, - commitments commitment.List, + commitments List, groupPublicKey *group.Element, -) (*group.Element, internal.BindingFactors, error) { +) (*group.Element, BindingFactors, error) { if !c.verified { if err := c.verify(); err != nil { return nil, nil, err } } - if err := internal.VerifyCommitmentList(c.group, commitments, c.Threshold); err != nil { + if err := commitments.Verify(c.group, c.Threshold); err != nil { return nil, nil, fmt.Errorf("invalid list of commitments: %w", err) } - groupCommitment, bindingFactors := internal.GroupCommitmentAndBindingFactors( - c.group, - message, - commitments, + groupCommitment, bindingFactors := commitments.GroupCommitmentAndBindingFactors( groupPublicKey, + message, ) return groupCommitment, bindingFactors, nil @@ -142,9 +139,9 @@ func (c *Configuration) getSignerPubKey(id uint64) *group.Element { func (c *Configuration) verifySignatureShare( sigShare *SignatureShare, message []byte, - commitments commitment.List, + commitments List, groupCommitment *group.Element, - bindingFactors internal.BindingFactors, + bindingFactors BindingFactors, ) error { com := commitments.Get(sigShare.SignerIdentifier) if com == nil { @@ -156,13 +153,15 @@ func (c *Configuration) verifySignatureShare( return fmt.Errorf("public key not registered for signer %q", sigShare.SignerIdentifier) } + participants := commitments.ParticipantsScalar() + lambdaChall, err := internal.ComputeChallengeFactor( c.group, groupCommitment, nil, sigShare.SignerIdentifier, message, - commitments, + participants, c.GroupPublicKey, ) if err != nil { diff --git a/encoding.go b/encoding.go index 3a3a0d6..058ba0d 100644 --- a/encoding.go +++ b/encoding.go @@ -17,7 +17,6 @@ import ( group "github.com/bytemare/crypto" - "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/internal" ) @@ -51,7 +50,7 @@ func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { case encPubKeyShare: return 1 + 8 + 4 + eLen + other[0] case encNonceCommitment: - return 8 + 2*sLen + commitment.EncodedSize(g) + return 8 + 2*sLen + EncodedSize(g) default: panic("encoded id not recognized") } @@ -252,7 +251,7 @@ func (s *Signer) Decode(data []byte) error { offset += ksLen commitments := make(map[uint64]*NonceCommitment) - comLen := commitment.EncodedSize(g) + comLen := EncodedSize(g) for offset < uint64(len(data)) { id := binary.LittleEndian.Uint64(data[offset : offset+8]) @@ -277,7 +276,7 @@ func (s *Signer) Decode(data []byte) error { offset += uint64(g.ScalarLength()) - com := new(commitment.Commitment) + com := new(Commitment) if err = com.Decode(data[offset : offset+comLen]); err != nil { return fmt.Errorf("can't decode nonce commitment %d: %w", id, err) } diff --git a/examples_test.go b/examples_test.go index 2f61ee0..b3e4e66 100644 --- a/examples_test.go +++ b/examples_test.go @@ -12,7 +12,6 @@ import ( "fmt" "github.com/bytemare/frost" - "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/debug" ) @@ -69,7 +68,7 @@ func Example_signer() { // Step 2: collect the commitments from the other participants and coordinator-chosen the message to sign, // and finalize by signing the message. - commitments := make(commitment.List, threshold) + commitments := make(frost.List, threshold) commitments[0] = com // This is not part of a participant's flow, but we need to collect the commitments of the other participants. @@ -149,7 +148,7 @@ func Example_coordinator() { } // Pre-commit - commitments := make(commitment.List, threshold) + commitments := make(frost.List, threshold) for i, p := range participants { commitments[i] = p.Commit() } diff --git a/frost.go b/frost.go index a9a27d4..2c6a0e6 100644 --- a/frost.go +++ b/frost.go @@ -128,10 +128,15 @@ var ( errInvalidThresholdParameter = errors.New("threshold is 0 or higher than maxSigners") errInvalidMaxSignersOrder = errors.New("maxSigners is higher than group order") errInvalidGroupPublicKey = errors.New("invalid group public key (nil, identity, or generator") - errInvalidNumberOfPublicKeys = errors.New("number of public keys is lower than threshold") + errInvalidNumberOfPublicKeys = errors.New("invalid number of public keys (lower than threshold or above maximum)") ) func (c *Configuration) verifySignerPublicKeys() error { + if uint64(len(c.SignerPublicKeys)) < c.Threshold || + uint64(len(c.SignerPublicKeys)) > c.MaxSigners { + return errInvalidNumberOfPublicKeys + } + // Sets to detect duplicates. pkSet := make(map[string]uint64, len(c.SignerPublicKeys)) idSet := make(map[uint64]struct{}, len(c.SignerPublicKeys)) @@ -181,6 +186,8 @@ func (c *Configuration) verify() error { bigMax := new(big.Int).SetUint64(c.MaxSigners) if order.Cmp(bigMax) != 1 { + // This is unlikely to happen, as the usual group orders cannot be represented in a uint64. + // Only a new, unregistered group would make it fail here. return errInvalidMaxSignersOrder } @@ -191,10 +198,6 @@ func (c *Configuration) verify() error { return errInvalidGroupPublicKey } - if uint64(len(c.SignerPublicKeys)) < c.Threshold { - return errInvalidNumberOfPublicKeys - } - if err := c.verifySignerPublicKeys(); err != nil { return err } diff --git a/internal/challenge.go b/internal/challenge.go new file mode 100644 index 0000000..b7ac8ad --- /dev/null +++ b/internal/challenge.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 internal + +import ( + "fmt" + + group "github.com/bytemare/crypto" + secretsharing "github.com/bytemare/secret-sharing" +) + +func computeLambda(g group.Group, participantList secretsharing.Polynomial, id uint64) (*group.Scalar, error) { + l, err := participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) + if err != nil { + return nil, fmt.Errorf("anomaly in participant identifiers: %w", err) + } + + return l, nil +} + +// ComputeChallengeFactor computes and returns the Schnorr challenge factor used in signing and verification. +func ComputeChallengeFactor( + g group.Group, + groupCommitment *group.Element, + lambda *group.Scalar, + id uint64, + message []byte, + participants []*group.Scalar, + groupPublicKey *group.Element, +) (*group.Scalar, error) { + // Compute the interpolating value + if lambda == nil || lambda.IsZero() { + l, err := computeLambda(g, participants, id) + if err != nil { + return nil, err + } + + lambda = l + } + + // Compute per message challenge + chall := SchnorrChallenge(g, message, groupCommitment, groupPublicKey) + + return chall.Multiply(lambda), nil +} + +// SchnorrChallenge computes the per-message SchnorrChallenge. +func SchnorrChallenge(g group.Group, msg []byte, r, pk *group.Element) *group.Scalar { + return H2(g, Concatenate(r.Encode(), pk.Encode(), msg)) +} diff --git a/internal/core.go b/internal/core.go deleted file mode 100644 index 3d27126..0000000 --- a/internal/core.go +++ /dev/null @@ -1,198 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// Copyright (C) 2024 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 internal - -import ( - "fmt" - - group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" - - "github.com/bytemare/frost/commitment" -) - -// GroupCommitmentAndBindingFactors computes and returns the group commitment element and signers' binding factors. -func GroupCommitmentAndBindingFactors( - g group.Group, - message []byte, - commitments commitment.List, - pk *group.Element, -) (*group.Element, BindingFactors) { - bindingFactors := computeBindingFactors(g, pk, commitments, message) - groupCommitment := computeGroupCommitment(g, commitments, bindingFactors) - - return groupCommitment, bindingFactors -} - -func participantsFromCommitments(g group.Group, c commitment.List) secretsharing.Polynomial { - return secretsharing.NewPolynomialFromListFunc(g, c, func(c *commitment.Commitment) *group.Scalar { - return g.NewScalar().SetUInt64(c.SignerID) - }) -} - -func computeLambda(g group.Group, commitments commitment.List, id uint64) (*group.Scalar, error) { - participantList := participantsFromCommitments(g, commitments) - - l, err := participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) - if err != nil { - return nil, fmt.Errorf("anomaly in participant identifiers: %w", err) - } - - return l, nil -} - -// ComputeChallengeFactor computes and returns the Schnorr challenge factor used in signing and verification. -func ComputeChallengeFactor( - g group.Group, - groupCommitment *group.Element, - lambda *group.Scalar, - id uint64, - message []byte, - commitments commitment.List, - groupPublicKey *group.Element, -) (*group.Scalar, error) { - // Compute the interpolating value - if lambda == nil || lambda.IsZero() { - l, err := computeLambda(g, commitments, id) - if err != nil { - return nil, err - } - - lambda = l - } - - // Compute per message challenge - chall := SchnorrChallenge(g, message, groupCommitment, groupPublicKey) - - return chall.Multiply(lambda), nil -} - -// BindingFactors is a map of participant identifier to BindingFactors. -type BindingFactors map[uint64]*group.Scalar - -type commitmentWithEncodedID struct { - *commitment.Commitment - ParticipantID []byte -} - -func commitmentsWithEncodedID(g group.Group, commitments commitment.List) []*commitmentWithEncodedID { - r := make([]*commitmentWithEncodedID, len(commitments)) - for i, com := range commitments { - r[i] = &commitmentWithEncodedID{ - ParticipantID: g.NewScalar().SetUInt64(com.SignerID).Encode(), - Commitment: com, - } - } - - return r -} - -func encodeCommitmentList(g group.Group, commitments []*commitmentWithEncodedID) []byte { - size := len(commitments) * (g.ScalarLength() + 2*g.ElementLength()) - encoded := make([]byte, 0, size) - - for _, com := range commitments { - encoded = append(encoded, com.ParticipantID...) - encoded = append(encoded, com.HidingNonce.Encode()...) - encoded = append(encoded, com.BindingNonce.Encode()...) - } - - return encoded -} - -// computeBindingFactors computes binding factors based on the participant commitment list and the message to be signed. -func computeBindingFactors( - g group.Group, - publicKey *group.Element, - commitments commitment.List, - message []byte, -) BindingFactors { - coms := commitmentsWithEncodedID(g, commitments) - encodedCommitHash := H5(g, encodeCommitmentList(g, coms)) - h := H4(g, message) - rhoInputPrefix := Concatenate(publicKey.Encode(), h, encodedCommitHash) - bindingFactors := make(BindingFactors, len(commitments)) - - for _, com := range coms { - rhoInput := Concatenate(rhoInputPrefix, com.ParticipantID) - bindingFactors[com.Commitment.SignerID] = H1(g, rhoInput) - } - - return bindingFactors -} - -func computeGroupCommitment(g group.Group, commitments commitment.List, bf BindingFactors) *group.Element { - gc := g.NewElement() - - for _, com := range commitments { - factor := bf[com.SignerID] - bindingNonce := com.BindingNonce.Copy().Multiply(factor) - gc.Add(com.HidingNonce).Add(bindingNonce) - } - - return gc -} - -// SchnorrChallenge computes the per-message SchnorrChallenge. -func SchnorrChallenge(g group.Group, msg []byte, r, pk *group.Element) *group.Scalar { - return H2(g, Concatenate(r.Encode(), pk.Encode(), msg)) -} - -func verifyCommitment(g group.Group, com *commitment.Commitment) error { - if com.Group != g { - return fmt.Errorf( - "commitment for participant %d has an unexpected ciphersuite: expected %s, got %s", - com.SignerID, - g, - com.Group, - ) - } - - if com.HidingNonce == nil || com.HidingNonce.IsIdentity() { - return fmt.Errorf("hiding nonce for participant %d is nil or identity element", com.SignerID) - } - - if com.BindingNonce == nil || com.BindingNonce.IsIdentity() { - return fmt.Errorf("binding nonce for participant %d is nil or identity element", com.SignerID) - } - - return nil -} - -// VerifyCommitmentList checks for the Commitment list integrity. -func VerifyCommitmentList(g group.Group, coms commitment.List, threshold uint64) error { - // Verify number of commitments. - if uint64(len(coms)) < threshold { - return fmt.Errorf("too few commitments: expected at least %d but got %d", threshold, len(coms)) - } - - // Ensure the list is sorted - if !coms.IsSorted() { - coms.Sort() - } - - // set to detect duplication - set := make(map[uint64]struct{}, len(coms)) - - for _, com := range coms { - // Check for duplicate participant entries. - if _, exists := set[com.SignerID]; exists { - return fmt.Errorf("commitment list contains multiple commitments of participant %d", com.SignerID) - } - - set[com.SignerID] = struct{}{} - - // Check general consistency. - if err := verifyCommitment(g, com); err != nil { - return err - } - } - - return nil -} diff --git a/internal/utils.go b/internal/utils.go index c8206bf..6f1b3f4 100644 --- a/internal/utils.go +++ b/internal/utils.go @@ -17,12 +17,17 @@ import ( // Concatenate returns the concatenation of all bytes composing the input elements. func Concatenate(input ...[]byte) []byte { + if len(input) == 0 { + return []byte{} + } + if len(input) == 1 { if len(input[0]) == 0 { return nil } - return input[0] + // shallow clone + return append(input[0][:0:0], input[0]...) } length := 0 diff --git a/signer.go b/signer.go index 11b1316..b188a10 100644 --- a/signer.go +++ b/signer.go @@ -15,7 +15,6 @@ import ( group "github.com/bytemare/crypto" - "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/internal" ) @@ -39,7 +38,7 @@ type Signer struct { type NonceCommitment struct { HidingNonceS *group.Scalar BindingNonceS *group.Scalar - *commitment.Commitment + *Commitment } func (s *Signer) ClearNonceCommitment(commitmentID uint64) { @@ -94,11 +93,11 @@ func (s *Signer) genNonceID() uint64 { // Commit generates a signer's nonces and commitment, to be used in the second FROST round. The internal nonce must // be kept secret, and the returned commitment sent to the signature aggregator. -func (s *Signer) Commit() *commitment.Commitment { +func (s *Signer) Commit() *Commitment { cid := s.genNonceID() hn := s.generateNonce(s.KeyShare.Secret, s.HidingRandom) bn := s.generateNonce(s.KeyShare.Secret, s.BindingRandom) - com := &commitment.Commitment{ + com := &Commitment{ Group: s.Configuration.group, SignerID: s.KeyShare.ID, CommitmentID: cid, @@ -114,7 +113,7 @@ func (s *Signer) Commit() *commitment.Commitment { return com.Copy() } -func (s *Signer) verifyNonces(com *commitment.Commitment) error { +func (s *Signer) verifyNonces(com *Commitment) error { nonces, ok := s.Commitments[com.CommitmentID] if !ok { return fmt.Errorf( @@ -136,8 +135,8 @@ func (s *Signer) verifyNonces(com *commitment.Commitment) error { } // VerifyCommitmentList checks for the Commitment list integrity and the signer's commitment. -func (s *Signer) VerifyCommitmentList(commitments commitment.List) error { - if err := internal.VerifyCommitmentList(s.Configuration.group, commitments, s.Configuration.Threshold); err != nil { +func (s *Signer) VerifyCommitmentList(commitments List) error { + if err := commitments.Verify(s.Configuration.group, s.Configuration.Threshold); err != nil { return fmt.Errorf("invalid list of commitments: %w", err) } @@ -159,7 +158,7 @@ func (s *Signer) VerifyCommitmentList(commitments commitment.List) error { // In particular, the Signer MUST validate commitment_list, deserializing each group Element in the list using // DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, // each signer MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. -func (s *Signer) Sign(commitmentID uint64, message []byte, commitments commitment.List) (*SignatureShare, error) { +func (s *Signer) Sign(commitmentID uint64, message []byte, commitments List) (*SignatureShare, error) { com, exists := s.Commitments[commitmentID] if !exists { return nil, fmt.Errorf("commitmentID %d not registered", commitmentID) @@ -169,14 +168,13 @@ func (s *Signer) Sign(commitmentID uint64, message []byte, commitments commitmen return nil, err } - groupCommitment, bindingFactors := internal.GroupCommitmentAndBindingFactors( - s.Configuration.group, - message, - commitments, + groupCommitment, bindingFactors := commitments.GroupCommitmentAndBindingFactors( s.Configuration.GroupPublicKey, + message, ) bindingFactor := bindingFactors[s.KeyShare.ID] + participants := commitments.ParticipantsScalar() lambdaChall, err := internal.ComputeChallengeFactor( s.Configuration.group, @@ -184,7 +182,7 @@ func (s *Signer) Sign(commitmentID uint64, message []byte, commitments commitmen s.Lambda, s.KeyShare.ID, message, - commitments, + participants, s.Configuration.GroupPublicKey, ) if err != nil { diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 77fc89b..1e2e1a2 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -12,7 +12,6 @@ import ( "bytes" "errors" "fmt" - "math/big" "slices" "strings" "testing" @@ -20,43 +19,10 @@ import ( group "github.com/bytemare/crypto" "github.com/bytemare/frost" - "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" ) -func badScalar(t *testing.T, g group.Group) []byte { - order, ok := new(big.Int).SetString(g.Order(), 0) - if !ok { - t.Errorf("setting int in base %d failed: %v", 0, g.Order()) - } - - encoded := make([]byte, g.ScalarLength()) - order.FillBytes(encoded) - - if g == group.Ristretto255Sha512 || g == group.Edwards25519Sha512 { - slices.Reverse(encoded) - } - - return encoded -} - -func badElement(t *testing.T, g group.Group) []byte { - order, ok := new(big.Int).SetString(g.Order(), 0) - if !ok { - t.Errorf("setting int in base %d failed: %v", 0, g.Order()) - } - - encoded := make([]byte, g.ElementLength()) - order.FillBytes(encoded) - - if g == group.Ristretto255Sha512 || g == group.Edwards25519Sha512 { - slices.Reverse(encoded) - } - - return encoded -} - func makeConf(t *testing.T, test *tableTest) *frost.Configuration { keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -204,7 +170,7 @@ func compareKeyShares(s1, s2 *frost.KeyShare) error { return comparePublicKeyShare(s1.Public(), s2.Public()) } -func compareCommitments(c1, c2 *commitment.Commitment) error { +func compareCommitments(c1, c2 *frost.Commitment) error { if c1.Group != c2.Group { return errors.New("different groups") } @@ -557,7 +523,7 @@ func TestEncoding_Signer_InvalidCommitmentNonces_DuplicateID(t *testing.T) { pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen keyShareLen := len(s.KeyShare.Encode()) - commitmentLength := 8 + 2*sLen + int(commitment.EncodedSize(g)) + commitmentLength := 8 + 2*sLen + int(frost.EncodedSize(g)) offset := confLen + 4 + sLen + keyShareLen offset2 := offset + commitmentLength @@ -648,7 +614,7 @@ func TestEncoding_SignatureShare(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(commitment.List, len(signers)) + coms := make(frost.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -719,7 +685,7 @@ func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(commitment.List, len(signers)) + coms := make(frost.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -836,7 +802,7 @@ func TestEncoding_Commitment(t *testing.T) { com := signer.Commit() encoded := com.Encode() - decoded := new(commitment.Commitment) + decoded := new(frost.Commitment) if err := decoded.Decode(encoded); err != nil { t.Fatal(err) } @@ -856,7 +822,7 @@ func TestEncoding_Commitment_BadCiphersuite(t *testing.T) { encoded := com.Encode() encoded[0] = 0 - decoded := new(commitment.Commitment) + decoded := new(frost.Commitment) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -871,7 +837,7 @@ func TestEncoding_Commitment_InvalidLength1(t *testing.T) { com := signer.Commit() encoded := com.Encode() - decoded := new(commitment.Commitment) + decoded := new(frost.Commitment) if err := decoded.Decode(encoded[:16]); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -886,7 +852,7 @@ func TestEncoding_Commitment_InvalidLength2(t *testing.T) { com := signer.Commit() encoded := com.Encode() - decoded := new(commitment.Commitment) + decoded := new(frost.Commitment) if err := decoded.Decode(encoded[:35]); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -903,7 +869,7 @@ func TestEncoding_Commitment_InvalidHidingNonce(t *testing.T) { bad := badElement(t, test.ECGroup()) slices.Replace(encoded, 17, 17+test.ECGroup().ElementLength(), bad...) - decoded := new(commitment.Commitment) + decoded := new(frost.Commitment) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -921,7 +887,7 @@ func TestEncoding_Commitment_InvalidBindingNonce(t *testing.T) { bad := badElement(t, g) slices.Replace(encoded, 17+g.ElementLength(), 17+2*g.ElementLength(), bad...) - decoded := new(commitment.Commitment) + decoded := new(frost.Commitment) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -931,14 +897,14 @@ func TestEncoding_Commitment_InvalidBindingNonce(t *testing.T) { func TestEncoding_CommitmentList(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(commitment.List, len(signers)) + coms := make(frost.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() } encoded := coms.Encode() - list, err := commitment.DecodeList(encoded) + list, err := frost.DecodeList(encoded) if err != nil { t.Fatal(err) } @@ -960,7 +926,7 @@ func TestEncoding_CommitmentList_InvalidCiphersuite(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(commitment.List, len(signers)) + coms := make(frost.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -968,7 +934,7 @@ func TestEncoding_CommitmentList_InvalidCiphersuite(t *testing.T) { encoded := coms.Encode() encoded[0] = 0 - if _, err := commitment.DecodeList(encoded); err == nil || + if _, err := frost.DecodeList(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -980,14 +946,14 @@ func TestEncoding_CommitmentList_InvalidLength1(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(commitment.List, len(signers)) + coms := make(frost.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() } encoded := coms.Encode() - if _, err := commitment.DecodeList(encoded[:8]); err == nil || + if _, err := frost.DecodeList(encoded[:8]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -999,14 +965,14 @@ func TestEncoding_CommitmentList_InvalidLength2(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(commitment.List, len(signers)) + coms := make(frost.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() } encoded := coms.Encode() - if _, err := commitment.DecodeList(encoded[:9]); err == nil || + if _, err := frost.DecodeList(encoded[:9]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -1018,7 +984,7 @@ func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(commitment.List, len(signers)) + coms := make(frost.List, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -1026,7 +992,7 @@ func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { encoded := coms.Encode() encoded[9] = 0 - if _, err := commitment.DecodeList(encoded); err == nil || + if _, err := frost.DecodeList(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index 9e8e9f0..828de69 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -7,3 +7,478 @@ // https://spdx.org/licenses/MIT.html package frost_test + +import ( + "strings" + "testing" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost" + "github.com/bytemare/frost/debug" + "github.com/bytemare/frost/internal" +) + +func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_Threshold_0(t *testing.T) { + expectedErrorPrefix := "threshold is 0 or higher than maxSigners" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: 0, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_Threshold_Max(t *testing.T) { + expectedErrorPrefix := "threshold is 0 or higher than maxSigners" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.maxSigners + 1, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { + expectedErrorPrefix := "invalid group public key (nil, identity, or generator" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: nil, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { + expectedErrorPrefix := "invalid group public key (nil, identity, or generator" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: test.ECGroup().NewElement(), + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { + expectedErrorPrefix := "invalid group public key (nil, identity, or generator" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: test.ECGroup().Base(), + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { + expectedErrorPrefix := "invalid number of public keys (lower than threshold or above maximum)" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + // nil + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: nil, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // empty + configuration.SignerPublicKeys = []*frost.PublicKeyShare{} + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // too few + configuration.SignerPublicKeys = publicKeyShares[:threshold-1] + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // too many + configuration.SignerPublicKeys = append(publicKeyShares, &frost.PublicKeyShare{}) + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { + expectedErrorPrefix := "empty public key share at index 1" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + publicKeyShares[threshold-1] = nil + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { + expectedErrorPrefix := "invalid signer public key (nil, identity, or generator) for participant 2" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + // nil pk + configuration.SignerPublicKeys[threshold-1].PublicKey = nil + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // identity + configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().NewElement() + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // generator + configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().Base() + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignerPublicKeys_Duplicate_Identifiers(t *testing.T) { + expectedErrorPrefix := "found duplicate identifier for signer 1" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + // duplicate id + id1 := configuration.SignerPublicKeys[0].ID + configuration.SignerPublicKeys[1].ID = id1 + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) { + expectedErrorPrefix := "found duplicate public keys for signers 2 and 1" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + // duplicate id + pk1 := configuration.SignerPublicKeys[0].PublicKey.Copy() + configuration.SignerPublicKeys[1].PublicKey = pk1 + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_Signer_NotVerified(t *testing.T) { + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if _, err := configuration.Signer(keyShares[0]); err != nil { + t.Fatal(err) + } +} + +func TestConfiguration_Signer_BadConfig(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if _, err := configuration.Signer(keyShares[0]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitment_Verify_WrongGroup(t *testing.T) { + expectedErrorPrefix := "commitment for participant 1 has an unexpected ciphersuite: expected ristretto255_XMD:SHA-512_R255MAP_RO_, got %!s(PANIC=String method: invalid group identifier)" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + signer := makeSigners(t, tt)[0] + com := signer.Commit() + com.Group = 2 + + if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitment_Verify_BadHidingNonce(t *testing.T) { + expectedErrorPrefix := "invalid hiding nonce (nil, identity, or generator)" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + signer := makeSigners(t, tt)[0] + com := signer.Commit() + + // generator + com.HidingNonce.Base() + if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // point at infinity + com.HidingNonce.Identity() + if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // nil + com.HidingNonce = nil + if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitment_Verify_BadBindingNonce(t *testing.T) { + expectedErrorPrefix := "invalid binding nonce (nil, identity, or generator)" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + signer := makeSigners(t, tt)[0] + com := signer.Commit() + + // generator + com.BindingNonce.Base() + if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // point at infinity + com.BindingNonce.Identity() + if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } + + // nil + com.BindingNonce = nil + if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Verify_InsufficientCommitments(t *testing.T) { + expectedErrorPrefix := "too few commitments: expected at least 2 but got 1" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + signers := makeSigners(t, tt) + coms := make(frost.List, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if err := coms[:tt.threshold-1].Verify(group.Ristretto255Sha512, tt.threshold); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Verify_DuplicateSignerIDs(t *testing.T) { + expectedErrorPrefix := "commitment list contains multiple commitments of participant 2" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + signers := makeSigners(t, tt) + coms := make(frost.List, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + coms[2] = coms[1].Copy() + + if err := coms.Verify(group.Ristretto255Sha512, tt.threshold); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + } +} diff --git a/tests/frost_test.go b/tests/frost_test.go index ad9acce..9c87bec 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -15,7 +15,6 @@ import ( group "github.com/bytemare/crypto" "github.com/bytemare/frost" - "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/debug" ) @@ -83,7 +82,7 @@ func runFrost( } // Commit - commitments := make(commitment.List, threshold) + commitments := make(frost.List, threshold) for i, p := range participants { commitments[i] = p.Commit() } diff --git a/tests/misc_test.go b/tests/misc_test.go index 96f5454..86a267c 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -10,7 +10,6 @@ package frost_test import ( "errors" - "fmt" "strings" "testing" @@ -18,62 +17,14 @@ import ( "github.com/bytemare/dkg" "github.com/bytemare/frost" - "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" ) -var ( - errNoPanic = errors.New("no panic") - errNoPanicMessage = errors.New("panic but no message") -) - -func hasPanic(f func()) (has bool, err error) { - defer func() { - var report any - if report = recover(); report != nil { - has = true - err = fmt.Errorf("%v", report) - } - }() - - f() - - return has, err -} - -// testPanic executes the function f with the expectation to recover from a panic. If no panic occurred or if the -// panic message is not the one expected, ExpectPanic returns an error. -func testPanic(s string, expectedError error, f func()) error { - hasPanic, err := hasPanic(f) - - // if there was no panic - if !hasPanic { - return errNoPanic - } - - // panic, and we don't expect a particular message - if expectedError == nil { - return nil - } - - // panic, but the panic value is empty - if err == nil { - return errNoPanicMessage - } - - // panic, but the panic value is not what we expected - if err.Error() != expectedError.Error() { - return fmt.Errorf("expected panic on %s with message %q, got %q", s, expectedError, err) - } - - return nil -} - func TestCommitmentList_Sort(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(commitment.List, len(signers)) + coms := make(frost.List, len(signers)) // signer A < signer B coms[0] = signers[0].Commit() diff --git a/tests/utils_test.go b/tests/utils_test.go new file mode 100644 index 0000000..6e8bd65 --- /dev/null +++ b/tests/utils_test.go @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 frost_test + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "slices" + "testing" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost/internal" +) + +var ( + errNoPanic = errors.New("no panic") + errNoPanicMessage = errors.New("panic but no message") +) + +func hasPanic(f func()) (has bool, err error) { + defer func() { + var report any + if report = recover(); report != nil { + has = true + err = fmt.Errorf("%v", report) + } + }() + + f() + + return has, err +} + +// testPanic executes the function f with the expectation to recover from a panic. If no panic occurred or if the +// panic message is not the one expected, testPanic returns an error. +func testPanic(s string, expectedError error, f func()) error { + hasPanic, err := hasPanic(f) + + // if there was no panic + if !hasPanic { + return errNoPanic + } + + // panic, and we don't expect a particular message + if expectedError == nil { + return nil + } + + // panic, but the panic value is empty + if err == nil { + return errNoPanicMessage + } + + // panic, but the panic value is not what we expected + if err.Error() != expectedError.Error() { + return fmt.Errorf("expected panic on %s with message %q, got %q", s, expectedError, err) + } + + return nil +} + +func badScalar(t *testing.T, g group.Group) []byte { + order, ok := new(big.Int).SetString(g.Order(), 0) + if !ok { + t.Errorf("setting int in base %d failed: %v", 0, g.Order()) + } + + encoded := make([]byte, g.ScalarLength()) + order.FillBytes(encoded) + + if g == group.Ristretto255Sha512 || g == group.Edwards25519Sha512 { + slices.Reverse(encoded) + } + + return encoded +} + +func badElement(t *testing.T, g group.Group) []byte { + order, ok := new(big.Int).SetString(g.Order(), 0) + if !ok { + t.Errorf("setting int in base %d failed: %v", 0, g.Order()) + } + + encoded := make([]byte, g.ElementLength()) + order.FillBytes(encoded) + + if g == group.Ristretto255Sha512 || g == group.Edwards25519Sha512 { + slices.Reverse(encoded) + } + + return encoded +} + +func TestConcatenate(t *testing.T) { + inputs := [][]byte{ + {1, 2, 3}, + {4, 5, 6}, + {7, 8, 9}, + } + + var nilSlice []byte + + // nil + if !bytes.Equal(internal.Concatenate(), slices.Concat(nilSlice)) { + t.Fatal("expected equality") + } + + // empty + if !bytes.Equal(internal.Concatenate([]byte{}), slices.Concat([]byte{})) { + t.Fatal("expected equality") + } + + // using single input + if !bytes.Equal(internal.Concatenate(inputs[0]), slices.Concat(internal.Concatenate(inputs[0]))) { + t.Fatal("expected equality") + } + + // using multiple input + if !bytes.Equal(internal.Concatenate(inputs...), slices.Concat(internal.Concatenate(inputs...))) { + t.Fatal("expected equality") + } +} diff --git a/tests/vectors_test.go b/tests/vectors_test.go index e743927..a401e53 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -19,7 +19,6 @@ import ( group "github.com/bytemare/crypto" "github.com/bytemare/frost" - "github.com/bytemare/frost/commitment" "github.com/bytemare/frost/debug" ) @@ -104,7 +103,7 @@ func (v test) test(t *testing.T) { } // Round One: Commitment - commitmentList := make(commitment.List, len(v.RoundOneOutputs.Outputs)) + commitmentList := make(frost.List, len(v.RoundOneOutputs.Outputs)) for i, pid := range v.RoundOneOutputs.Outputs { p := participants.Get(pid.ID) if p == nil { From f673885a5bf071a217481cedf6935e21a723d03b Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:24:56 +0200 Subject: [PATCH 12/31] some refactor and added tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 30 ++-- coordinator.go | 39 ++--- debug/debug.go | 3 +- examples_test.go | 4 +- internal/challenge.go | 10 +- internal/hashing.go | 28 ++-- signer.go | 10 +- tests/encoding_test.go | 149 ++++++++++++++----- tests/frost_error_test.go | 292 ++++++++++++++++++++++++++++++++++---- tests/frost_test.go | 2 +- tests/misc_test.go | 26 +++- tests/vectors_test.go | 2 +- 12 files changed, 460 insertions(+), 135 deletions(-) diff --git a/commitment.go b/commitment.go index b44b4e8..828da70 100644 --- a/commitment.go +++ b/commitment.go @@ -133,8 +133,8 @@ func (c *Commitment) Decode(data []byte) error { return nil } -// List is a sortable list of commitments. -type List []*Commitment +// CommitmentList is a sortable list of commitments with search functions. +type CommitmentList []*Commitment func cmpID(a, b *Commitment) int { switch { @@ -148,17 +148,17 @@ func cmpID(a, b *Commitment) int { } // Sort sorts the list the ascending order of identifiers. -func (c List) Sort() { +func (c CommitmentList) Sort() { slices.SortFunc(c, cmpID) } // IsSorted returns whether the list is sorted in ascending order by identifier. -func (c List) IsSorted() bool { +func (c CommitmentList) IsSorted() bool { return slices.IsSortedFunc(c, cmpID) } // Get returns the commitment of the participant with the corresponding identifier, or nil if it was not found. -func (c List) Get(identifier uint64) *Commitment { +func (c CommitmentList) Get(identifier uint64) *Commitment { for _, com := range c { if com.SignerID == identifier { return com @@ -169,7 +169,7 @@ func (c List) Get(identifier uint64) *Commitment { } // ParticipantsUInt64 returns the uint64 list of participant identifiers in the list. -func (c List) ParticipantsUInt64() []uint64 { +func (c CommitmentList) ParticipantsUInt64() []uint64 { out := make([]uint64, len(c)) for i, com := range c { @@ -180,7 +180,7 @@ func (c List) ParticipantsUInt64() []uint64 { } // ParticipantsScalar returns the group.Scalar list of participant identifier in the list -func (c List) ParticipantsScalar() []*group.Scalar { +func (c CommitmentList) ParticipantsScalar() []*group.Scalar { if len(c) == 0 { return nil } @@ -197,7 +197,7 @@ func (c List) ParticipantsScalar() []*group.Scalar { } // Verify checks for the Commitment list's integrity. -func (c List) Verify(g group.Group, threshold uint64) error { +func (c CommitmentList) Verify(g group.Group, threshold uint64) error { // Verify number of commitments. if uint64(len(c)) < threshold { return fmt.Errorf("too few commitments: expected at least %d but got %d", threshold, len(c)) @@ -228,7 +228,7 @@ func (c List) Verify(g group.Group, threshold uint64) error { return nil } -func (c List) Encode() []byte { +func (c CommitmentList) Encode() []byte { n := len(c) if n == 0 { return nil @@ -247,7 +247,7 @@ func (c List) Encode() []byte { return out } -func DecodeList(data []byte) (List, error) { +func DecodeList(data []byte) (CommitmentList, error) { if len(data) < 9 { return nil, errInvalidLength } @@ -265,7 +265,7 @@ func DecodeList(data []byte) (List, error) { return nil, errInvalidLength } - c := make(List, 0, n) + c := make(CommitmentList, 0, n) for offset := uint64(9); offset < uint64(len(data)); offset += es { com := new(Commitment) @@ -279,7 +279,7 @@ func DecodeList(data []byte) (List, error) { return c, nil } -func (c List) GroupCommitmentAndBindingFactors( +func (c CommitmentList) GroupCommitmentAndBindingFactors( publicKey *group.Element, message []byte, ) (*group.Element, BindingFactors) { @@ -294,7 +294,7 @@ type commitmentWithEncodedID struct { ParticipantID []byte } -func commitmentsWithEncodedID(g group.Group, commitments List) []*commitmentWithEncodedID { +func commitmentsWithEncodedID(g group.Group, commitments CommitmentList) []*commitmentWithEncodedID { r := make([]*commitmentWithEncodedID, len(commitments)) for i, com := range commitments { r[i] = &commitmentWithEncodedID{ @@ -322,7 +322,7 @@ func encodeCommitmentList(g group.Group, commitments []*commitmentWithEncodedID) // BindingFactors is a map of participant identifier to BindingFactors. type BindingFactors map[uint64]*group.Scalar -func (c List) bindingFactors(publicKey *group.Element, message []byte) BindingFactors { +func (c CommitmentList) bindingFactors(publicKey *group.Element, message []byte) BindingFactors { g := c[0].Group coms := commitmentsWithEncodedID(g, c) encodedCommitHash := internal.H5(g, encodeCommitmentList(g, coms)) @@ -338,7 +338,7 @@ func (c List) bindingFactors(publicKey *group.Element, message []byte) BindingFa return bindingFactors } -func (c List) groupCommitment(bf BindingFactors) *group.Element { +func (c CommitmentList) groupCommitment(bf BindingFactors) *group.Element { g := c[0].Group gc := g.NewElement() diff --git a/coordinator.go b/coordinator.go index 1e4b095..a2668bd 100644 --- a/coordinator.go +++ b/coordinator.go @@ -39,16 +39,10 @@ type Signature struct { func (c *Configuration) AggregateSignatures( message []byte, sigShares []*SignatureShare, - commitments List, + commitments CommitmentList, verify bool, ) (*Signature, error) { - if !c.verified { - if err := c.verify(); err != nil { - return nil, err - } - } - - groupCommitment, bindingFactors, err := c.prepSigShareCheck(message, commitments, c.GroupPublicKey) + groupCommitment, bindingFactors, err := c.PrepareVerifySignatureShare(message, commitments) if err != nil { return nil, err } @@ -88,15 +82,9 @@ func (c *Configuration) AggregateSignatures( func (c *Configuration) VerifySignatureShare( sigShare *SignatureShare, message []byte, - commitments List, + commitments CommitmentList, ) error { - if !c.verified { - if err := c.verify(); err != nil { - return err - } - } - - groupCommitment, bindingFactors, err := c.prepSigShareCheck(message, commitments, c.GroupPublicKey) + groupCommitment, bindingFactors, err := c.PrepareVerifySignatureShare(message, commitments) if err != nil { return err } @@ -104,9 +92,8 @@ func (c *Configuration) VerifySignatureShare( return c.verifySignatureShare(sigShare, message, commitments, groupCommitment, bindingFactors) } -func (c *Configuration) prepSigShareCheck(message []byte, - commitments List, - groupPublicKey *group.Element, +func (c *Configuration) PrepareVerifySignatureShare(message []byte, + commitments CommitmentList, ) (*group.Element, BindingFactors, error) { if !c.verified { if err := c.verify(); err != nil { @@ -119,7 +106,7 @@ func (c *Configuration) prepSigShareCheck(message []byte, } groupCommitment, bindingFactors := commitments.GroupCommitmentAndBindingFactors( - groupPublicKey, + c.GroupPublicKey, message, ) @@ -139,29 +126,29 @@ func (c *Configuration) getSignerPubKey(id uint64) *group.Element { func (c *Configuration) verifySignatureShare( sigShare *SignatureShare, message []byte, - commitments List, + commitments CommitmentList, groupCommitment *group.Element, bindingFactors BindingFactors, ) error { com := commitments.Get(sigShare.SignerIdentifier) if com == nil { - return fmt.Errorf("commitment not registered for signer %q", sigShare.SignerIdentifier) + return fmt.Errorf("commitment not registered for signer %d", sigShare.SignerIdentifier) } pk := c.getSignerPubKey(sigShare.SignerIdentifier) if pk == nil { - return fmt.Errorf("public key not registered for signer %q", sigShare.SignerIdentifier) + return fmt.Errorf("public key not registered for signer %d", sigShare.SignerIdentifier) } participants := commitments.ParticipantsScalar() lambdaChall, err := internal.ComputeChallengeFactor( c.group, - groupCommitment, - nil, sigShare.SignerIdentifier, - message, + nil, participants, + message, + groupCommitment, c.GroupPublicKey, ) if err != nil { diff --git a/debug/debug.go b/debug/debug.go index db23e03..09fed55 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -129,7 +129,6 @@ func RecoverPublicKeys( } g := group.Group(c) - pk := commitment[0] keys := make([]*group.Element, maxSigners) for i := uint64(1); i <= maxSigners; i++ { @@ -141,7 +140,7 @@ func RecoverPublicKeys( keys[i-1] = pki } - return pk, keys, nil + return commitment[0], keys, nil } // VerifyVSS allows verification of a participant's secret share given a VSS commitment to the secret polynomial. diff --git a/examples_test.go b/examples_test.go index b3e4e66..8fa6c49 100644 --- a/examples_test.go +++ b/examples_test.go @@ -68,7 +68,7 @@ func Example_signer() { // Step 2: collect the commitments from the other participants and coordinator-chosen the message to sign, // and finalize by signing the message. - commitments := make(frost.List, threshold) + commitments := make(frost.CommitmentList, threshold) commitments[0] = com // This is not part of a participant's flow, but we need to collect the commitments of the other participants. @@ -148,7 +148,7 @@ func Example_coordinator() { } // Pre-commit - commitments := make(frost.List, threshold) + commitments := make(frost.CommitmentList, threshold) for i, p := range participants { commitments[i] = p.Commit() } diff --git a/internal/challenge.go b/internal/challenge.go index b7ac8ad..786acdd 100644 --- a/internal/challenge.go +++ b/internal/challenge.go @@ -15,7 +15,7 @@ import ( secretsharing "github.com/bytemare/secret-sharing" ) -func computeLambda(g group.Group, participantList secretsharing.Polynomial, id uint64) (*group.Scalar, error) { +func computeLambda(g group.Group, id uint64, participantList secretsharing.Polynomial) (*group.Scalar, error) { l, err := participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) if err != nil { return nil, fmt.Errorf("anomaly in participant identifiers: %w", err) @@ -27,16 +27,16 @@ func computeLambda(g group.Group, participantList secretsharing.Polynomial, id u // ComputeChallengeFactor computes and returns the Schnorr challenge factor used in signing and verification. func ComputeChallengeFactor( g group.Group, - groupCommitment *group.Element, - lambda *group.Scalar, id uint64, - message []byte, + lambda *group.Scalar, participants []*group.Scalar, + message []byte, + groupCommitment *group.Element, groupPublicKey *group.Element, ) (*group.Scalar, error) { // Compute the interpolating value if lambda == nil || lambda.IsZero() { - l, err := computeLambda(g, participants, id) + l, err := computeLambda(g, id, participants) if err != nil { return nil, err } diff --git a/internal/hashing.go b/internal/hashing.go index f421a42..90e5652 100644 --- a/internal/hashing.go +++ b/internal/hashing.go @@ -31,44 +31,48 @@ type ciphersuite struct { } var ciphersuites = [group.Secp256k1 + 1]ciphersuite{ - { + { // Ristretto255 hash: hash.SHA512.New(), contextString: []byte(ristretto255ContextString), }, - { + { // Ed448 - unused hash: hash.SHAKE256.New(), contextString: []byte(ed448ContextString), }, - { + { // P256 hash: hash.SHA256.New(), contextString: []byte(p256ContextString), }, - { + { // P384 hash: hash.SHA384.New(), contextString: []byte(p384ContextString), }, - { + { // P521 hash: hash.SHA512.New(), contextString: []byte(p521ContextString), }, - { + { // Ed25519 hash: hash.SHA512.New(), contextString: []byte(ed25519ContextString), }, - { + { // Secp256k1 hash: hash.SHA256.New(), contextString: []byte(secp256k1ContextString), }, } -func h1Ed25519(hashed []byte) *group.Scalar { +func h1Ed25519(input ...[]byte) *group.Scalar { + hashed := ciphersuites[group.Edwards25519Sha512-1].hash.Hash(0, input...) + s := edwards25519.NewScalar() if _, err := s.SetUniformBytes(hashed); err != nil { + // Fails only if len(hashed) != 64, but the hash function above always returns 64 bytes. panic(err) } s2 := group.Edwards25519Sha512.NewScalar() if err := s2.Decode(s.Bytes()); err != nil { + // Can't fail because the underlying encoding/decoding is compatible. panic(err) } @@ -81,19 +85,20 @@ func hx(g group.Group, input, dst []byte) *group.Scalar { switch g { case group.Edwards25519Sha512: - h := c.hash.Hash(0, c.contextString, dst, input) - sc = h1Ed25519(h) + sc = h1Ed25519(c.contextString, dst, input) case group.Ristretto255Sha512: h := c.hash.Hash(0, c.contextString, dst, input) s := ristretto255.NewScalar().FromUniformBytes(h) sc = g.NewScalar() if err := sc.Decode(s.Encode(nil)); err != nil { + // Can't fail because the underlying encoding/decoding is compatible. panic(err) } case group.P256Sha256, group.P384Sha384, group.P521Sha512, group.Secp256k1: sc = g.HashToScalar(input, append(c.contextString, dst...)) default: + // Can't fail because the function is always called with a compatible group previously checked. panic(ErrInvalidParameters) } @@ -109,8 +114,7 @@ func H1(g group.Group, input []byte) *group.Scalar { func H2(g group.Group, input []byte) *group.Scalar { if g == group.Edwards25519Sha512 { // For compatibility with RFC8032 H2 doesn't use a domain separator for Edwards25519. - h := ciphersuites[group.Edwards25519Sha512-1].hash.Hash(0, input) - return h1Ed25519(h) + return h1Ed25519(input) } return hx(g, input, []byte("chal")) diff --git a/signer.go b/signer.go index b188a10..8e5044a 100644 --- a/signer.go +++ b/signer.go @@ -135,7 +135,7 @@ func (s *Signer) verifyNonces(com *Commitment) error { } // VerifyCommitmentList checks for the Commitment list integrity and the signer's commitment. -func (s *Signer) VerifyCommitmentList(commitments List) error { +func (s *Signer) VerifyCommitmentList(commitments CommitmentList) error { if err := commitments.Verify(s.Configuration.group, s.Configuration.Threshold); err != nil { return fmt.Errorf("invalid list of commitments: %w", err) } @@ -158,7 +158,7 @@ func (s *Signer) VerifyCommitmentList(commitments List) error { // In particular, the Signer MUST validate commitment_list, deserializing each group Element in the list using // DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, // each signer MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. -func (s *Signer) Sign(commitmentID uint64, message []byte, commitments List) (*SignatureShare, error) { +func (s *Signer) Sign(commitmentID uint64, message []byte, commitments CommitmentList) (*SignatureShare, error) { com, exists := s.Commitments[commitmentID] if !exists { return nil, fmt.Errorf("commitmentID %d not registered", commitmentID) @@ -178,11 +178,11 @@ func (s *Signer) Sign(commitmentID uint64, message []byte, commitments List) (*S lambdaChall, err := internal.ComputeChallengeFactor( s.Configuration.group, - groupCommitment, - s.Lambda, s.KeyShare.ID, - message, + s.Lambda, participants, + message, + groupCommitment, s.Configuration.GroupPublicKey, ) if err != nil { diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 1e2e1a2..51c5deb 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -10,6 +10,7 @@ package frost_test import ( "bytes" + "encoding/json" "errors" "fmt" "slices" @@ -23,7 +24,7 @@ import ( "github.com/bytemare/frost/internal" ) -func makeConf(t *testing.T, test *tableTest) *frost.Configuration { +func makeConfAndShares(t *testing.T, test *tableTest) (*frost.Configuration, []*frost.KeyShare) { keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -39,25 +40,25 @@ func makeConf(t *testing.T, test *tableTest) *frost.Configuration { t.Fatal(err) } - return configuration + return configuration, keyShares } -func makeSigners(t *testing.T, test *tableTest) []*frost.Signer { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) +func makeConf(t *testing.T, test *tableTest) *frost.Configuration { + c, _ := makeConfAndShares(t, test) + return c +} - configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, +func getPublicKeyShares(keyShares []*frost.KeyShare) []*frost.PublicKeyShare { + publicKeyShares := make([]*frost.PublicKeyShare, 0, len(keyShares)) + for _, ks := range keyShares { + publicKeyShares = append(publicKeyShares, ks.Public()) } - if err := configuration.Init(); err != nil { - t.Fatal(err) - } + return publicKeyShares +} +func fullSetup(t *testing.T, test *tableTest) (*frost.Configuration, []*frost.Signer) { + configuration, keyShares := makeConfAndShares(t, test) signers := make([]*frost.Signer, test.maxSigners) for i, keyShare := range keyShares { @@ -69,16 +70,12 @@ func makeSigners(t *testing.T, test *tableTest) []*frost.Signer { signers[i] = s } - return signers + return configuration, signers } -func getPublicKeyShares(keyShares []*frost.KeyShare) []*frost.PublicKeyShare { - publicKeyShares := make([]*frost.PublicKeyShare, 0, len(keyShares)) - for _, ks := range keyShares { - publicKeyShares = append(publicKeyShares, ks.Public()) - } - - return publicKeyShares +func makeSigners(t *testing.T, test *tableTest) []*frost.Signer { + _, s := fullSetup(t, test) + return s } func compareConfigurations(t *testing.T, c1, c2 *frost.Configuration, expectedMatch bool) { @@ -289,7 +286,7 @@ func TestEncoding_Configuration_InvalidHeaderLength(t *testing.T) { decoded := new(frost.Configuration) if err := decoded.Decode(encoded[:24]); err == nil || err.Error() != expectedError.Error() { - t.Fatalf("extected %q, got %q", expectedError, err) + t.Fatalf("expected %q, got %q", expectedError, err) } }) } @@ -304,7 +301,7 @@ func TestEncoding_Configuration_InvalidCiphersuite(t *testing.T) { decoded := new(frost.Configuration) if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError.Error() { - t.Fatalf("extected %q, got %q", expectedError, err) + t.Fatalf("expected %q, got %q", expectedError, err) } }) } @@ -318,12 +315,12 @@ func TestEncoding_Configuration_InvalidLength(t *testing.T) { decoded := new(frost.Configuration) if err := decoded.Decode(encoded[:len(encoded)-1]); err == nil || err.Error() != expectedError.Error() { - t.Fatalf("extected %q, got %q", expectedError, err) + t.Fatalf("expected %q, got %q", expectedError, err) } encoded = append(encoded, []byte{0, 1}...) if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError.Error() { - t.Fatalf("extected %q, got %q", expectedError, err) + t.Fatalf("expected %q, got %q", expectedError, err) } }) } @@ -340,7 +337,7 @@ func TestEncoding_Configuration_InvalidGroupPublicKey(t *testing.T) { decoded := new(frost.Configuration) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -373,7 +370,7 @@ func TestEncoding_Configuration_InvalidPublicKeyShare(t *testing.T) { decoded := new(frost.Configuration) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -478,7 +475,7 @@ func TestEncoding_Signer_InvalidLambda(t *testing.T) { decoded := new(frost.Signer) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -504,7 +501,7 @@ func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { decoded := new(frost.Signer) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -614,7 +611,7 @@ func TestEncoding_SignatureShare(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -685,7 +682,7 @@ func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -897,7 +894,7 @@ func TestEncoding_Commitment_InvalidBindingNonce(t *testing.T) { func TestEncoding_CommitmentList(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -926,7 +923,7 @@ func TestEncoding_CommitmentList_InvalidCiphersuite(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -946,7 +943,7 @@ func TestEncoding_CommitmentList_InvalidLength1(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -965,7 +962,7 @@ func TestEncoding_CommitmentList_InvalidLength2(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -984,7 +981,7 @@ func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() } @@ -998,3 +995,81 @@ func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { } }) } + +func TestEncoding_KeyShare_Bytes(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + keyShare := s.KeyShare + + encoded := keyShare.Encode() + + decoded := new(frost.KeyShare) + if err := decoded.Decode(encoded); err != nil { + t.Fatal(err) + } + + if err := compareKeyShares(keyShare, decoded); err != nil { + t.Fatal(err) + } + }) +} + +func TestEncoding_KeyShare_JSON(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + keyShare := s.KeyShare + + encoded, err := json.Marshal(keyShare) + if err != nil { + t.Fatal(err) + } + + decoded := new(frost.KeyShare) + if err := json.Unmarshal(encoded, decoded); err != nil { + t.Fatal(err) + } + + if err := compareKeyShares(keyShare, decoded); err != nil { + t.Fatal(err) + } + }) +} + +func TestEncoding_PublicKeyShare_Bytes(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + keyShare := s.KeyShare.Public() + + encoded := keyShare.Encode() + + decoded := new(frost.PublicKeyShare) + if err := decoded.Decode(encoded); err != nil { + t.Fatal(err) + } + + if err := comparePublicKeyShare(keyShare, decoded); err != nil { + t.Fatal(err) + } + }) +} + +func TestEncoding_PublicKeyShare_JSON(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + keyShare := s.KeyShare.Public() + + encoded, err := json.Marshal(keyShare) + if err != nil { + t.Fatal(err) + } + + decoded := new(frost.PublicKeyShare) + if err := json.Unmarshal(encoded, decoded); err != nil { + t.Fatal(err) + } + + if err := comparePublicKeyShare(keyShare, decoded); err != nil { + t.Fatal(err) + } + }) +} diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index 828de69..1ba7874 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -40,7 +40,7 @@ func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -66,7 +66,7 @@ func TestConfiguration_Verify_Threshold_0(t *testing.T) { } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -92,7 +92,7 @@ func TestConfiguration_Verify_Threshold_Max(t *testing.T) { } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -113,7 +113,7 @@ func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -134,7 +134,7 @@ func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -155,7 +155,7 @@ func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -180,28 +180,28 @@ func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // empty configuration.SignerPublicKeys = []*frost.PublicKeyShare{} if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // too few configuration.SignerPublicKeys = publicKeyShares[:threshold-1] if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // too many configuration.SignerPublicKeys = append(publicKeyShares, &frost.PublicKeyShare{}) if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -225,7 +225,7 @@ func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -251,21 +251,21 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { configuration.SignerPublicKeys[threshold-1].PublicKey = nil if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // identity configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().NewElement() if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // generator configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().Base() if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -292,7 +292,7 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_Identifiers(t *testing.T configuration.SignerPublicKeys[1].ID = id1 if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -319,7 +319,7 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) configuration.SignerPublicKeys[1].PublicKey = pk1 if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -363,7 +363,228 @@ func TestConfiguration_Signer_BadConfig(t *testing.T) { if _, err := configuration.Signer(keyShares[0]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_PrepareVerifySignatureShare_BadNonVerifiedConfiguration(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if _, _, err := configuration.PrepareVerifySignatureShare(nil, nil); err == nil || + err.Error() != expectedErrorPrefix.Error() { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_PrepareVerifySignatureShare_InvalidCommitments(t *testing.T) { + expectedErrorPrefix := "invalid list of commitments: too few commitments: expected at least 2 but got 1" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if _, _, err := configuration.PrepareVerifySignatureShare(nil, coms[:1]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_BadPrep(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.VerifySignatureShare(nil, nil, nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_MissingCommitment(t *testing.T) { + expectedErrorPrefix := "commitment not registered for signer 1" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(coms[0].CommitmentID, message, coms) + if err != nil { + t.Fatal(err) + } + + coms[0].SignerID = tt.maxSigners + 1 + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_MissingPublicKey(t *testing.T) { + expectedErrorPrefix := "public key not registered for signer 1" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(coms[0].CommitmentID, message, coms) + if err != nil { + t.Fatal(err) + } + + configuration.SignerPublicKeys[0].ID = tt.maxSigners + 1 + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_BadSignerID(t *testing.T) { + expectedErrorPrefix := "can't compute challenge: anomaly in participant identifiers: one of the polynomial's coefficients is zero" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(coms[0].CommitmentID, message, coms) + if err != nil { + t.Fatal(err) + } + + coms[1].SignerID = 0 + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_InvalidSignatureShare(t *testing.T) { + expectedErrorPrefix := "invalid signature share for signer 1" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare := &frost.SignatureShare{ + SignatureShare: tt.Ciphersuite.ECGroup().NewScalar().Random(), + SignerIdentifier: 1, + Group: tt.Ciphersuite.ECGroup(), + } + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_AggregateSignatures_Verify_BadSigShare(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if _, err := configuration.AggregateSignatures(nil, nil, nil, false); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_AggregateSignatures_NonVerifiedCommitments(t *testing.T) { + expectedErrorPrefix := "invalid list of commitments: too few commitments: expected at least 3 but got 2" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 5, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if _, err := configuration.AggregateSignatures(nil, nil, coms[:2], false); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -379,7 +600,7 @@ func TestCommitment_Verify_WrongGroup(t *testing.T) { com.Group = 2 if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -396,19 +617,19 @@ func TestCommitment_Verify_BadHidingNonce(t *testing.T) { // generator com.HidingNonce.Base() if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // point at infinity com.HidingNonce.Identity() if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // nil com.HidingNonce = nil if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -425,19 +646,19 @@ func TestCommitment_Verify_BadBindingNonce(t *testing.T) { // generator com.BindingNonce.Base() if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // point at infinity com.BindingNonce.Identity() if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // nil com.BindingNonce = nil if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -449,7 +670,7 @@ func TestCommitmentList_Verify_InsufficientCommitments(t *testing.T) { maxSigners: 3, } signers := makeSigners(t, tt) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() @@ -457,7 +678,7 @@ func TestCommitmentList_Verify_InsufficientCommitments(t *testing.T) { if err := coms[:tt.threshold-1].Verify(group.Ristretto255Sha512, tt.threshold); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -469,7 +690,7 @@ func TestCommitmentList_Verify_DuplicateSignerIDs(t *testing.T) { maxSigners: 4, } signers := makeSigners(t, tt) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() @@ -479,6 +700,21 @@ func TestCommitmentList_Verify_DuplicateSignerIDs(t *testing.T) { if err := coms.Verify(group.Ristretto255Sha512, tt.threshold); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("extected %q, got %q", expectedErrorPrefix, err) + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestComputeChallengeFactor_InvalidID(t *testing.T) { + expectedErrorPrefix := "anomaly in participant identifiers: " + g := group.Ristretto255Sha512 + id := uint64(1) + participants := []*group.Scalar{ + g.NewScalar().SetUInt64(2), + g.NewScalar().SetUInt64(3), + } + + if _, err := internal.ComputeChallengeFactor(g, id, nil, participants, nil, nil, nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } diff --git a/tests/frost_test.go b/tests/frost_test.go index 9c87bec..98f1c9d 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -82,7 +82,7 @@ func runFrost( } // Commit - commitments := make(frost.List, threshold) + commitments := make(frost.CommitmentList, threshold) for i, p := range participants { commitments[i] = p.Commit() } diff --git a/tests/misc_test.go b/tests/misc_test.go index 86a267c..a2c1d23 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -24,7 +24,7 @@ import ( func TestCommitmentList_Sort(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - coms := make(frost.List, len(signers)) + coms := make(frost.CommitmentList, len(signers)) // signer A < signer B coms[0] = signers[0].Commit() @@ -339,6 +339,30 @@ func TestRecoverPublicKeys_InvalidCiphersuite(t *testing.T) { } } +func TestRecoverPublicKeys_BadCommitment(t *testing.T) { + expectedError := "commitment has nil element" + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + _, _, secretsharingCommitment := debug.TrustedDealerKeygen( + ciphersuite, + nil, + threshold, + maxSigners, + ) + + secretsharingCommitment[1] = nil + + _, _, err := debug.RecoverPublicKeys( + ciphersuite, + maxSigners, + secretsharingCommitment, + ) + if err == nil || err.Error() != expectedError { + t.Fatalf("expected %q, got %q", expectedError, err) + } +} + func TestPublicKeyShareVerification(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { keyShares, dealerGroupPubKey, _ := runDKG( diff --git a/tests/vectors_test.go b/tests/vectors_test.go index a401e53..fe6f275 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -103,7 +103,7 @@ func (v test) test(t *testing.T) { } // Round One: Commitment - commitmentList := make(frost.List, len(v.RoundOneOutputs.Outputs)) + commitmentList := make(frost.CommitmentList, len(v.RoundOneOutputs.Outputs)) for i, pid := range v.RoundOneOutputs.Outputs { p := participants.Get(pid.ID) if p == nil { From 9f09e45b4ce58d39b6f3ded527c9467cfb6a8043 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:54:13 +0200 Subject: [PATCH 13/31] some refactoring and test Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 60 ++++++++++--------- coordinator.go | 46 ++++++-------- debug/debug.go | 2 +- encoding.go | 122 ++++++++++++++++++++++---------------- frost.go | 22 +++++-- internal/challenge.go | 56 ----------------- internal/lambda.go | 68 +++++++++++++++++++++ signer.go | 98 ++++++++++++++++-------------- tests/encoding_test.go | 102 +++++++++++++++++-------------- tests/frost_error_test.go | 58 ++++++++---------- tests/vectors_test.go | 14 ++--- 11 files changed, 350 insertions(+), 298 deletions(-) delete mode 100644 internal/challenge.go create mode 100644 internal/lambda.go diff --git a/commitment.go b/commitment.go index 828da70..e9824dc 100644 --- a/commitment.go +++ b/commitment.go @@ -31,15 +31,15 @@ var ( // Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. type Commitment struct { - HidingNonce *group.Element - BindingNonce *group.Element - CommitmentID uint64 - SignerID uint64 - Group group.Group + HidingNonceCommitment *group.Element + BindingNonceCommitment *group.Element + CommitmentID uint64 + SignerID uint64 + Group group.Group } -// Verify returns an error if the commitment is -func (c *Commitment) Verify(g group.Group) error { +// Validate returns an error if the commitment is +func (c *Commitment) Validate(g group.Group) error { if c.Group != g { return fmt.Errorf( "commitment for participant %d has an unexpected ciphersuite: expected %s, got %s", @@ -51,11 +51,13 @@ func (c *Commitment) Verify(g group.Group) error { generator := g.Base() - if c.HidingNonce == nil || c.HidingNonce.IsIdentity() || c.HidingNonce.Equal(generator) == 1 { + if c.HidingNonceCommitment == nil || c.HidingNonceCommitment.IsIdentity() || + c.HidingNonceCommitment.Equal(generator) == 1 { return errHidingNonce } - if c.BindingNonce == nil || c.BindingNonce.IsIdentity() || c.BindingNonce.Equal(generator) == 1 { + if c.BindingNonceCommitment == nil || c.BindingNonceCommitment.IsIdentity() || + c.BindingNonceCommitment.Equal(generator) == 1 { return errBindingNonce } @@ -65,11 +67,11 @@ func (c *Commitment) Verify(g group.Group) error { // Copy returns a new Commitment struct populated with the same values as the receiver. func (c *Commitment) Copy() *Commitment { return &Commitment{ - HidingNonce: c.HidingNonce.Copy(), - BindingNonce: c.BindingNonce.Copy(), - CommitmentID: c.CommitmentID, - SignerID: c.SignerID, - Group: c.Group, + HidingNonceCommitment: c.HidingNonceCommitment.Copy(), + BindingNonceCommitment: c.BindingNonceCommitment.Copy(), + CommitmentID: c.CommitmentID, + SignerID: c.SignerID, + Group: c.Group, } } @@ -80,8 +82,8 @@ func EncodedSize(g group.Group) uint64 { // Encode returns the serialized byte encoding of a participant's commitment. func (c *Commitment) Encode() []byte { - hNonce := c.HidingNonce.Encode() - bNonce := c.BindingNonce.Encode() + hNonce := c.HidingNonceCommitment.Encode() + bNonce := c.BindingNonceCommitment.Encode() out := make([]byte, 17, EncodedSize(c.Group)) out[0] = byte(c.Group) @@ -127,8 +129,8 @@ func (c *Commitment) Decode(data []byte) error { c.Group = g c.CommitmentID = cID c.SignerID = pID - c.HidingNonce = hn - c.BindingNonce = bn + c.HidingNonceCommitment = hn + c.BindingNonceCommitment = bn return nil } @@ -168,8 +170,8 @@ func (c CommitmentList) Get(identifier uint64) *Commitment { return nil } -// ParticipantsUInt64 returns the uint64 list of participant identifiers in the list. -func (c CommitmentList) ParticipantsUInt64() []uint64 { +// Participants returns the uint64 list of participant identifiers in the list. +func (c CommitmentList) Participants() []uint64 { out := make([]uint64, len(c)) for i, com := range c { @@ -196,9 +198,9 @@ func (c CommitmentList) ParticipantsScalar() []*group.Scalar { }) } -// Verify checks for the Commitment list's integrity. -func (c CommitmentList) Verify(g group.Group, threshold uint64) error { - // Verify number of commitments. +// Validate checks for the Commitment list's integrity. +func (c CommitmentList) Validate(g group.Group, threshold uint64) error { + // Validate number of commitments. if uint64(len(c)) < threshold { return fmt.Errorf("too few commitments: expected at least %d but got %d", threshold, len(c)) } @@ -219,8 +221,8 @@ func (c CommitmentList) Verify(g group.Group, threshold uint64) error { set[com.SignerID] = struct{}{} - // Check general consistency. - if err := com.Verify(g); err != nil { + // Check general validity of the commitment. + if err := com.Validate(g); err != nil { return err } } @@ -312,8 +314,8 @@ func encodeCommitmentList(g group.Group, commitments []*commitmentWithEncodedID) for _, com := range commitments { encoded = append(encoded, com.ParticipantID...) - encoded = append(encoded, com.HidingNonce.Encode()...) - encoded = append(encoded, com.BindingNonce.Encode()...) + encoded = append(encoded, com.HidingNonceCommitment.Encode()...) + encoded = append(encoded, com.BindingNonceCommitment.Encode()...) } return encoded @@ -344,8 +346,8 @@ func (c CommitmentList) groupCommitment(bf BindingFactors) *group.Element { for _, com := range c { factor := bf[com.SignerID] - bindingNonce := com.BindingNonce.Copy().Multiply(factor) - gc.Add(com.HidingNonce).Add(bindingNonce) + bindingNonce := com.BindingNonceCommitment.Copy().Multiply(factor) + gc.Add(com.HidingNonceCommitment).Add(bindingNonce) } return gc diff --git a/coordinator.go b/coordinator.go index a2668bd..a8cf308 100644 --- a/coordinator.go +++ b/coordinator.go @@ -42,14 +42,14 @@ func (c *Configuration) AggregateSignatures( commitments CommitmentList, verify bool, ) (*Signature, error) { - groupCommitment, bindingFactors, err := c.PrepareVerifySignatureShare(message, commitments) + groupCommitment, bindingFactors, participants, err := c.PrepareVerifySignatureShare(message, commitments) if err != nil { return nil, err } if verify { for _, share := range sigShares { - if err = c.verifySignatureShare(share, message, commitments, + if err = c.verifySignatureShare(share, message, commitments, participants, groupCommitment, bindingFactors); err != nil { return nil, err } @@ -84,33 +84,32 @@ func (c *Configuration) VerifySignatureShare( message []byte, commitments CommitmentList, ) error { - groupCommitment, bindingFactors, err := c.PrepareVerifySignatureShare(message, commitments) + groupCommitment, bindingFactors, participants, err := c.PrepareVerifySignatureShare(message, commitments) if err != nil { return err } - return c.verifySignatureShare(sigShare, message, commitments, groupCommitment, bindingFactors) + return c.verifySignatureShare(sigShare, message, commitments, participants, groupCommitment, bindingFactors) } func (c *Configuration) PrepareVerifySignatureShare(message []byte, commitments CommitmentList, -) (*group.Element, BindingFactors, error) { +) (*group.Element, BindingFactors, []*group.Scalar, error) { if !c.verified { if err := c.verify(); err != nil { - return nil, nil, err + return nil, nil, nil, err } } - if err := commitments.Verify(c.group, c.Threshold); err != nil { - return nil, nil, fmt.Errorf("invalid list of commitments: %w", err) + // Check Commitment list integrity + if err := commitments.Validate(c.group, c.Threshold); err != nil { + return nil, nil, nil, fmt.Errorf("invalid list of commitments: %w", err) } - groupCommitment, bindingFactors := commitments.GroupCommitmentAndBindingFactors( - c.GroupPublicKey, - message, - ) + groupCommitment, bindingFactors := commitments.GroupCommitmentAndBindingFactors(c.GroupPublicKey, message) + participants := commitments.ParticipantsScalar() - return groupCommitment, bindingFactors, nil + return groupCommitment, bindingFactors, participants, nil } func (c *Configuration) getSignerPubKey(id uint64) *group.Element { @@ -127,6 +126,7 @@ func (c *Configuration) verifySignatureShare( sigShare *SignatureShare, message []byte, commitments CommitmentList, + participants []*group.Scalar, groupCommitment *group.Element, bindingFactors BindingFactors, ) error { @@ -140,24 +140,16 @@ func (c *Configuration) verifySignatureShare( return fmt.Errorf("public key not registered for signer %d", sigShare.SignerIdentifier) } - participants := commitments.ParticipantsScalar() - - lambdaChall, err := internal.ComputeChallengeFactor( - c.group, - sigShare.SignerIdentifier, - nil, - participants, - message, - groupCommitment, - c.GroupPublicKey, - ) + lambda, err := internal.Lambda(c.group, sigShare.SignerIdentifier, participants) if err != nil { - return fmt.Errorf("can't compute challenge: %w", err) + return err } + lambdaChall := c.challenge(lambda, message, groupCommitment) + // Commitment KeyShare: r = g(h + b*f + l*s) bindingFactor := bindingFactors[sigShare.SignerIdentifier] - commShare := com.HidingNonce.Copy().Add(com.BindingNonce.Copy().Multiply(bindingFactor)) + commShare := com.HidingNonceCommitment.Copy().Add(com.BindingNonceCommitment.Copy().Multiply(bindingFactor)) r := commShare.Add(pk.Copy().Multiply(lambdaChall)) l := c.group.Base().Multiply(sigShare.SignatureShare) @@ -175,7 +167,7 @@ func VerifySignature(c Ciphersuite, message []byte, signature *Signature, public return internal.ErrInvalidCiphersuite } - ch := internal.SchnorrChallenge(g, message, signature.R, publicKey) + ch := SchnorrChallenge(g, message, signature.R, publicKey) r := signature.R.Copy().Add(publicKey.Copy().Multiply(ch)) l := g.Base().Multiply(signature.Z) diff --git a/debug/debug.go b/debug/debug.go index 09fed55..81ab74b 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -108,7 +108,7 @@ func Sign(c frost.Ciphersuite, msg []byte, key *group.Scalar, random ...*group.S R := g.Base().Multiply(k) pk := g.Base().Multiply(key) - challenge := internal.SchnorrChallenge(g, msg, R, pk) + challenge := frost.SchnorrChallenge(g, msg, R, pk) z := k.Add(challenge.Multiply(key)) return &frost.Signature{ diff --git a/encoding.go b/encoding.go index 058ba0d..255f983 100644 --- a/encoding.go +++ b/encoding.go @@ -10,9 +10,9 @@ package frost import ( "encoding/binary" + "encoding/hex" "errors" "fmt" - "math" "slices" group "github.com/bytemare/crypto" @@ -27,11 +27,14 @@ const ( encSig = byte(4) encPubKeyShare = byte(5) encNonceCommitment = byte(6) + encLambda = byte(7) ) var ( - errInvalidConfigEncoding = errors.New("invalid values in Configuration encoding") - errZeroIdentifier = errors.New("identifier cannot be 0") + errInvalidConfigEncoding = errors.New( + "the threshold in the encoded configuration is higher than the number of maximum participants", + ) + errZeroIdentifier = errors.New("identifier cannot be 0") ) func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { @@ -42,7 +45,7 @@ func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { case encConf: return 1 + 3*8 + eLen + other[0] case encSigner: - return other[0] + 2 + 2 + sLen + other[1] + other[2] + return other[0] + 2 + 2 + 2 + other[1] + other[2] + other[3] case encSigShare: return 1 + 8 + uint64(g.ScalarLength()) case encSig: @@ -51,6 +54,8 @@ func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { return 1 + 8 + 4 + eLen + other[0] case encNonceCommitment: return 8 + 2*sLen + EncodedSize(g) + case encLambda: + return 32 + sLen default: panic("encoded id not recognized") } @@ -77,8 +82,8 @@ func (c *Configuration) Encode() []byte { } type confHeader struct { - g group.Group - h, t, m, n, length uint64 + g group.Group + h, t, n, nPks, length uint64 } func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { @@ -93,12 +98,12 @@ func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { g := group.Group(data[0]) t := binary.LittleEndian.Uint64(data[1:9]) - m := binary.LittleEndian.Uint64(data[9:17]) - n := binary.LittleEndian.Uint64(data[17:25]) + n := binary.LittleEndian.Uint64(data[9:17]) + nPks := binary.LittleEndian.Uint64(data[17:25]) pksLen := encodedLength(encPubKeyShare, g, t*uint64(g.ElementLength())) - length := encodedLength(encConf, g, n*pksLen) + length := encodedLength(encConf, g, nPks*pksLen) - if t > math.MaxUint || m > math.MaxUint { + if t > n { return nil, errInvalidConfigEncoding } @@ -106,8 +111,8 @@ func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { g: g, h: 25, t: t, - m: m, n: n, + nPks: nPks, length: length, }, nil } @@ -124,9 +129,9 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { offset := header.h + uint64(header.g.ElementLength()) pksLen := encodedLength(encPubKeyShare, header.g, header.t*uint64(header.g.ElementLength())) - pks := make([]*PublicKeyShare, header.n) + pks := make([]*PublicKeyShare, header.nPks) - for j := range header.n { + for j := range header.nPks { pk := new(PublicKeyShare) if err := pk.Decode(data[offset : offset+pksLen]); err != nil { return fmt.Errorf("could not decode signer public key share for signer %d: %w", j, err) @@ -139,7 +144,7 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { conf := &Configuration{ Ciphersuite: Ciphersuite(header.g), Threshold: header.t, - MaxSigners: header.m, + MaxSigners: header.n, GroupPublicKey: gpk, SignerPublicKeys: pks, } @@ -174,34 +179,40 @@ func (c *Configuration) Decode(data []byte) error { func (s *Signer) Encode() []byte { g := s.KeyShare.Group ks := s.KeyShare.Encode() - nCommitments := len(s.Commitments) + nCommitments := len(s.NonceCommitments) + nLambdas := len(s.LambdaRegistry) conf := s.Configuration.Encode() - out := make( - []byte, - len(conf)+4, - encodedLength( - encSigner, - g, - uint64(len(conf)), - uint64(len(ks)), - uint64(nCommitments)*encodedLength(encNonceCommitment, g), - ), + outLength := encodedLength( + encSigner, + g, + uint64(len(conf)), + uint64(len(ks)), + uint64(nCommitments)*encodedLength(encNonceCommitment, g), + uint64(nLambdas)*encodedLength(encLambda, g), ) + out := make([]byte, len(conf)+6, outLength) + copy(out, conf) binary.LittleEndian.PutUint16(out[len(conf):len(conf)+2], uint16(len(ks))) // key share length binary.LittleEndian.PutUint16(out[len(conf)+2:len(conf)+4], uint16(nCommitments)) // number of commitments + binary.LittleEndian.PutUint16(out[len(conf)+4:len(conf)+6], uint16(nLambdas)) // number of commitments - if s.Lambda != nil { - out = append(out, s.Lambda.Encode()...) - } else { - out = append(out, make([]byte, g.ScalarLength())...) - } out = append(out, ks...) // key share - for id, com := range s.Commitments { + for k, v := range s.LambdaRegistry { + b, err := hex.DecodeString(k) + if err != nil { + panic(fmt.Sprintf("failed te revert hex encoding to bytes of %s", k)) + } + + out = append(out, b...) + out = append(out, v.Encode()...) + } + + for id, com := range s.NonceCommitments { out = append(out, internal.Concatenate(internal.UInt64LE(id), - com.HidingNonceS.Encode(), - com.BindingNonceS.Encode(), + com.HidingNonce.Encode(), + com.BindingNonce.Encode(), com.Commitment.Encode())...) } @@ -221,36 +232,47 @@ func (s *Signer) Decode(data []byte) error { return err } - if uint64(len(data)) <= header.length+4 { + if uint64(len(data)) <= header.length+6 { return internal.ErrInvalidLength } ksLen := uint64(binary.LittleEndian.Uint16(data[header.length : header.length+2])) nCommitments := uint64(binary.LittleEndian.Uint16(data[header.length+2 : header.length+4])) + nLambdas := uint64(binary.LittleEndian.Uint16(data[header.length+4 : header.length+6])) g := conf.group nLen := encodedLength(encNonceCommitment, g) + lLem := encodedLength(encLambda, g) - length := encodedLength(encSigner, g, header.length, ksLen, nCommitments*nLen) + length := encodedLength(encSigner, g, header.length, ksLen, nCommitments*nLen, nLambdas*lLem) if uint64(len(data)) != length { return internal.ErrInvalidLength } - offset := header.length + 4 - - lambda := g.NewScalar() - if err := lambda.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { - return fmt.Errorf("failed to decode lambda: %w", err) - } + offset := header.length + 6 - offset += uint64(g.ScalarLength()) keyShare := new(KeyShare) - if err := keyShare.Decode(data[offset : offset+ksLen]); err != nil { return fmt.Errorf("failed to decode key share: %w", err) } offset += ksLen - commitments := make(map[uint64]*NonceCommitment) + stop := offset + nLambdas*lLem + lambdaRegistry := make(internal.LambdaRegistry, lLem) + + for offset < stop { + key := data[offset : offset+32] + offset += 32 + + lambda := g.NewScalar() + if err := lambda.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { + return fmt.Errorf("failed to decode lambda: %w", err) + } + + lambdaRegistry[hex.EncodeToString(key)] = lambda + offset += uint64(g.ScalarLength()) + } + + commitments := make(map[uint64]*Nonce) comLen := EncodedSize(g) for offset < uint64(len(data)) { @@ -283,16 +305,16 @@ func (s *Signer) Decode(data []byte) error { offset += comLen - commitments[id] = &NonceCommitment{ - HidingNonceS: hs, - BindingNonceS: bs, - Commitment: com, + commitments[id] = &Nonce{ + HidingNonce: hs, + BindingNonce: bs, + Commitment: com, } } s.KeyShare = keyShare - s.Lambda = lambda - s.Commitments = commitments + s.LambdaRegistry = lambdaRegistry + s.NonceCommitments = commitments s.Configuration = conf return nil diff --git a/frost.go b/frost.go index 2c6a0e6..dce5770 100644 --- a/frost.go +++ b/frost.go @@ -225,11 +225,21 @@ func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { } return &Signer{ - KeyShare: keyShare, - Lambda: nil, - Commitments: make(map[uint64]*NonceCommitment), - HidingRandom: nil, - BindingRandom: nil, - Configuration: c, + KeyShare: keyShare, + LambdaRegistry: make(internal.LambdaRegistry), + NonceCommitments: make(map[uint64]*Nonce), + HidingRandom: nil, + BindingRandom: nil, + Configuration: c, }, nil } + +func (c *Configuration) challenge(lambda *group.Scalar, message []byte, groupCommitment *group.Element) *group.Scalar { + chall := SchnorrChallenge(c.group, message, groupCommitment, c.GroupPublicKey) + return chall.Multiply(lambda) +} + +// SchnorrChallenge computes the per-message SchnorrChallenge. +func SchnorrChallenge(g group.Group, msg []byte, r, pk *group.Element) *group.Scalar { + return internal.H2(g, internal.Concatenate(r.Encode(), pk.Encode(), msg)) +} diff --git a/internal/challenge.go b/internal/challenge.go deleted file mode 100644 index 786acdd..0000000 --- a/internal/challenge.go +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// Copyright (C) 2024 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 internal - -import ( - "fmt" - - group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" -) - -func computeLambda(g group.Group, id uint64, participantList secretsharing.Polynomial) (*group.Scalar, error) { - l, err := participantList.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) - if err != nil { - return nil, fmt.Errorf("anomaly in participant identifiers: %w", err) - } - - return l, nil -} - -// ComputeChallengeFactor computes and returns the Schnorr challenge factor used in signing and verification. -func ComputeChallengeFactor( - g group.Group, - id uint64, - lambda *group.Scalar, - participants []*group.Scalar, - message []byte, - groupCommitment *group.Element, - groupPublicKey *group.Element, -) (*group.Scalar, error) { - // Compute the interpolating value - if lambda == nil || lambda.IsZero() { - l, err := computeLambda(g, id, participants) - if err != nil { - return nil, err - } - - lambda = l - } - - // Compute per message challenge - chall := SchnorrChallenge(g, message, groupCommitment, groupPublicKey) - - return chall.Multiply(lambda), nil -} - -// SchnorrChallenge computes the per-message SchnorrChallenge. -func SchnorrChallenge(g group.Group, msg []byte, r, pk *group.Element) *group.Scalar { - return H2(g, Concatenate(r.Encode(), pk.Encode(), msg)) -} diff --git a/internal/lambda.go b/internal/lambda.go new file mode 100644 index 0000000..7f562ed --- /dev/null +++ b/internal/lambda.go @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 internal + +import ( + "encoding/hex" + "fmt" + + group "github.com/bytemare/crypto" + "github.com/bytemare/hash" + secretsharing "github.com/bytemare/secret-sharing" +) + +func Lambda(g group.Group, id uint64, polynomial secretsharing.Polynomial) (*group.Scalar, error) { + l, err := polynomial.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) + if err != nil { + return nil, fmt.Errorf("anomaly in participant identifiers: %w", err) + } + + return l, nil +} + +type LambdaRegistry map[string]*group.Scalar + +const lambdaRegistryKeyDomainSeparator = "FROST-participants" + +func lambdaRegistryKey(participants []uint64) string { + a := fmt.Sprint(lambdaRegistryKeyDomainSeparator, participants) + return hex.EncodeToString(hash.SHA256.Hash([]byte(a))) // Length = 32 bytes, 64 in hex string +} + +func (l LambdaRegistry) Get(g group.Group, id uint64, participants []uint64) (*group.Scalar, error) { + key := lambdaRegistryKey(participants) + + lambda, registered := l[key] + if !registered { + polynomial := secretsharing.NewPolynomialFromListFunc(g, participants, func(p uint64) *group.Scalar { + return g.NewScalar().SetUInt64(p) + }) + + var err error + lambda, err = Lambda(g, id, polynomial) + if err != nil { + return nil, err + } + + l.Set(participants, lambda) + } + + return lambda, nil +} + +func (l LambdaRegistry) Set(participants []uint64, lambda *group.Scalar) { + key := lambdaRegistryKey(participants) + l[key] = lambda +} + +func (l LambdaRegistry) Delete(participants []uint64) { + key := lambdaRegistryKey(participants) + l[key].Zero() + delete(l, key) +} diff --git a/signer.go b/signer.go index 8e5044a..f653758 100644 --- a/signer.go +++ b/signer.go @@ -27,27 +27,43 @@ type SignatureShare struct { // Signer is a participant in a signing group. type Signer struct { - KeyShare *KeyShare - Lambda *group.Scalar - Commitments map[uint64]*NonceCommitment + // The KeyShare holds the signer's secret and public info, such as keys and identifier. + KeyShare *KeyShare + + // LambdaRegistry records all interpolating values for the signers for different combinations of participant + // groups. Each group makes up a unique polynomial defined by the participants' identifiers. A value will be + // computed once for the first time a group is encountered, and kept across encodings and decodings of the signer, + // accelerating subsequent signatures within the same group of signers. + LambdaRegistry internal.LambdaRegistry + + // NonceCommitments maps Nonce and their NonceCommitments to their Commitment's identifier. + NonceCommitments map[uint64]*Nonce + + // Configuration is the core FROST setup configuration. Configuration *Configuration - HidingRandom []byte + + // HidingRandom can be set to force the use its value for HidingNonce generation. This is only encouraged for vector + // reproduction, but should be left to nil in any production deployments. + HidingRandom []byte + + // HidingRandom can be set to force the use its value for HidingNonce generation. This is only encouraged for vector + // reproduction, but should be left to nil in any production deployments. BindingRandom []byte } -type NonceCommitment struct { - HidingNonceS *group.Scalar - BindingNonceS *group.Scalar +type Nonce struct { + HidingNonce *group.Scalar + BindingNonce *group.Scalar *Commitment } func (s *Signer) ClearNonceCommitment(commitmentID uint64) { - if com := s.Commitments[commitmentID]; com != nil { - com.HidingNonceS.Zero() - com.BindingNonceS.Zero() - com.HidingNonce.Identity() - com.BindingNonce.Identity() - delete(s.Commitments, commitmentID) + if com := s.NonceCommitments[commitmentID]; com != nil { + com.HidingNonce.Zero() + com.BindingNonce.Zero() + com.HidingNonceCommitment.Identity() + com.BindingNonceCommitment.Identity() + delete(s.NonceCommitments, commitmentID) } } @@ -72,16 +88,14 @@ func (s *Signer) generateNonce(secret *group.Scalar, random []byte) *group.Scala random = internal.RandomBytes(32) } - enc := secret.Encode() - - return internal.H3(s.Configuration.group, internal.Concatenate(random, enc)) + return internal.H3(s.Configuration.group, internal.Concatenate(random, secret.Encode())) } func (s *Signer) genNonceID() uint64 { cid := randomCommitmentID() for range 128 { - if _, exists := s.Commitments[cid]; !exists { + if _, exists := s.NonceCommitments[cid]; !exists { return cid } @@ -98,23 +112,23 @@ func (s *Signer) Commit() *Commitment { hn := s.generateNonce(s.KeyShare.Secret, s.HidingRandom) bn := s.generateNonce(s.KeyShare.Secret, s.BindingRandom) com := &Commitment{ - Group: s.Configuration.group, - SignerID: s.KeyShare.ID, - CommitmentID: cid, - HidingNonce: s.Configuration.group.Base().Multiply(hn), - BindingNonce: s.Configuration.group.Base().Multiply(bn), + Group: s.Configuration.group, + SignerID: s.KeyShare.ID, + CommitmentID: cid, + HidingNonceCommitment: s.Configuration.group.Base().Multiply(hn), + BindingNonceCommitment: s.Configuration.group.Base().Multiply(bn), } - s.Commitments[cid] = &NonceCommitment{ - HidingNonceS: hn, - BindingNonceS: bn, - Commitment: com, + s.NonceCommitments[cid] = &Nonce{ + HidingNonce: hn, + BindingNonce: bn, + Commitment: com, } return com.Copy() } func (s *Signer) verifyNonces(com *Commitment) error { - nonces, ok := s.Commitments[com.CommitmentID] + nonces, ok := s.NonceCommitments[com.CommitmentID] if !ok { return fmt.Errorf( "the commitment identifier %d for signer %d in the commitments is unknown to the signer", @@ -123,11 +137,11 @@ func (s *Signer) verifyNonces(com *Commitment) error { ) } - if nonces.HidingNonce.Equal(com.HidingNonce) != 1 { + if nonces.HidingNonceCommitment.Equal(com.HidingNonceCommitment) != 1 { return fmt.Errorf("invalid hiding nonce in commitment list for signer %d", s.KeyShare.ID) } - if nonces.BindingNonce.Equal(com.BindingNonce) != 1 { + if nonces.BindingNonceCommitment.Equal(com.BindingNonceCommitment) != 1 { return fmt.Errorf("invalid binding nonce in commitment list for signer %d", s.KeyShare.ID) } @@ -136,7 +150,7 @@ func (s *Signer) verifyNonces(com *Commitment) error { // VerifyCommitmentList checks for the Commitment list integrity and the signer's commitment. func (s *Signer) VerifyCommitmentList(commitments CommitmentList) error { - if err := commitments.Verify(s.Configuration.group, s.Configuration.Threshold); err != nil { + if err := commitments.Validate(s.Configuration.group, s.Configuration.Threshold); err != nil { return fmt.Errorf("invalid list of commitments: %w", err) } @@ -159,7 +173,7 @@ func (s *Signer) VerifyCommitmentList(commitments CommitmentList) error { // DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, // each signer MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. func (s *Signer) Sign(commitmentID uint64, message []byte, commitments CommitmentList) (*SignatureShare, error) { - com, exists := s.Commitments[commitmentID] + com, exists := s.NonceCommitments[commitmentID] if !exists { return nil, fmt.Errorf("commitmentID %d not registered", commitmentID) } @@ -173,26 +187,20 @@ func (s *Signer) Sign(commitmentID uint64, message []byte, commitments Commitmen message, ) - bindingFactor := bindingFactors[s.KeyShare.ID] - participants := commitments.ParticipantsScalar() + participants := commitments.Participants() - lambdaChall, err := internal.ComputeChallengeFactor( - s.Configuration.group, - s.KeyShare.ID, - s.Lambda, - participants, - message, - groupCommitment, - s.Configuration.GroupPublicKey, - ) + lambda, err := s.LambdaRegistry.Get(s.Configuration.group, s.KeyShare.ID, participants) if err != nil { - return nil, fmt.Errorf("can't compute challenge: %w", err) + return nil, err } - hidingNonce := com.HidingNonceS.Copy() - bindingNonce := com.BindingNonceS + lambdaChall := s.Configuration.challenge(lambda, message, groupCommitment) + + hidingNonce := com.HidingNonce.Copy() + bindingNonce := com.BindingNonce // Compute the signature share: h + b*f + l*s + bindingFactor := bindingFactors[s.KeyShare.ID] sigShare := hidingNonce. Add(bindingFactor.Multiply(bindingNonce). Add(lambdaChall.Multiply(s.KeyShare.Secret))) diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 51c5deb..b166a53 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -180,6 +180,18 @@ func compareCommitments(c1, c2 *frost.Commitment) error { return errors.New("different CommitmentID") } + if c1.HidingNonceCommitment.Equal(c2.HidingNonceCommitment) != 1 { + return errors.New("different HidingNonceCommitment") + } + + if c1.BindingNonceCommitment.Equal(c2.BindingNonceCommitment) != 1 { + return errors.New("different BindingNonceCommitment") + } + + return nil +} + +func compareNonceCommitments(c1, c2 *frost.Nonce) error { if c1.HidingNonce.Equal(c2.HidingNonce) != 1 { return errors.New("different HidingNonce") } @@ -188,19 +200,24 @@ func compareCommitments(c1, c2 *frost.Commitment) error { return errors.New("different BindingNonce") } - return nil + return compareCommitments(c1.Commitment, c2.Commitment) } -func compareNonceCommitments(c1, c2 *frost.NonceCommitment) error { - if c1.HidingNonceS.Equal(c2.HidingNonceS) != 1 { - return errors.New("different HidingNonceS") +func compareLambdaRegistries(t *testing.T, m1, m2 map[string]*group.Scalar) { + if len(m1) != len(m2) { + t.Fatal("unequal lengths") } - if c1.BindingNonceS.Equal(c2.BindingNonceS) != 1 { - return errors.New("different BindingNonceS") - } + for k1, v1 := range m1 { + v2, exists := m2[k1] + if !exists { + t.Fatalf("key %s is not present in second map", k1) + } - return compareCommitments(c1.Commitment, c2.Commitment) + if v1.Equal(v2) != 1 { + t.Fatalf("unequal lambdas for the participant list %s", k1) + } + } } func compareSigners(t *testing.T, s1, s2 *frost.Signer) { @@ -208,16 +225,14 @@ func compareSigners(t *testing.T, s1, s2 *frost.Signer) { t.Fatal(err) } - if !((s1.Lambda == nil && (s2.Lambda == nil || s2.Lambda.IsZero())) || (s2.Lambda == nil && (s1.Lambda == nil || s1.Lambda.IsZero()))) { - t.Fatalf("expected equality: %v / %v", s1.Lambda, s2.Lambda.IsZero()) - } + compareLambdaRegistries(t, s1.LambdaRegistry, s2.LambdaRegistry) - if len(s1.Commitments) != len(s2.Commitments) { + if len(s1.NonceCommitments) != len(s2.NonceCommitments) { t.Fatal("expected equality") } - for id, com := range s1.Commitments { - if com2, exists := s2.Commitments[id]; !exists { + for id, com := range s1.NonceCommitments { + if com2, exists := s2.NonceCommitments[id]; !exists { t.Fatalf("com id %d does not exist in s2", id) } else { if err := compareNonceCommitments(com, com2); err != nil { @@ -462,16 +477,29 @@ func TestEncoding_Signer_InvalidLambda(t *testing.T) { expectedErrorPrefix := "failed to decode lambda:" testAll(t, func(t *testing.T, test *tableTest) { - s := makeSigners(t, test)[0] + signers := makeSigners(t, test) g := group.Group(test.Ciphersuite) - eLen := g.ElementLength() - pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen - confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + message := []byte("message") + + coms := make(frost.CommitmentList, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + s := signers[0] + + _, err := s.Sign(coms[0].CommitmentID, message, coms) + if err != nil { + t.Fatal(err) + } + + confLen := len(s.Configuration.Encode()) + ksLen := len(s.KeyShare.Encode()) encoded := s.Encode() bad := badScalar(t, g) - - encoded = slices.Replace(encoded, confLen+4, confLen+4+g.ScalarLength(), bad...) + offset := confLen + 6 + ksLen + 32 + encoded = slices.Replace(encoded, offset, offset+g.ScalarLength(), bad...) decoded := new(frost.Signer) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -485,12 +513,8 @@ func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] - - g := group.Group(test.Ciphersuite) - eLen := g.ElementLength() - pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen - confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen - offset := confLen + 4 + g.ScalarLength() + confLen := len(s.Configuration.Encode()) + offset := confLen + 6 // Set an invalid group in the key share encoding. kse := s.KeyShare.Encode() @@ -515,13 +539,11 @@ func TestEncoding_Signer_InvalidCommitmentNonces_DuplicateID(t *testing.T) { s.Commit() s.Commit() g := group.Group(test.Ciphersuite) - eLen := g.ElementLength() sLen := g.ScalarLength() - pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen - confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) commitmentLength := 8 + 2*sLen + int(frost.EncodedSize(g)) - offset := confLen + 4 + sLen + keyShareLen + offset := confLen + 6 + keyShareLen offset2 := offset + commitmentLength encoded := s.Encode() @@ -541,12 +563,9 @@ func TestEncoding_Signer_InvalidHidingNonceCommitment(t *testing.T) { s := makeSigners(t, test)[0] s.Commit() g := group.Group(test.Ciphersuite) - eLen := g.ElementLength() - sLen := g.ScalarLength() - pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen - confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) - offset := confLen + 4 + sLen + keyShareLen + 8 + offset := confLen + 6 + keyShareLen + 8 encoded := s.Encode() data := slices.Replace(encoded, offset, offset+g.ScalarLength(), badScalar(t, g)...) @@ -565,12 +584,9 @@ func TestEncoding_Signer_InvalidBindingNonceCommitment(t *testing.T) { s := makeSigners(t, test)[0] s.Commit() g := group.Group(test.Ciphersuite) - eLen := g.ElementLength() - sLen := g.ScalarLength() - pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen - confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) - offset := confLen + 4 + sLen + keyShareLen + 8 + g.ScalarLength() + offset := confLen + 6 + keyShareLen + 8 + g.ScalarLength() encoded := s.Encode() data := slices.Replace(encoded, offset, offset+g.ScalarLength(), badScalar(t, g)...) @@ -589,12 +605,10 @@ func TestEncoding_Signer_InvalidCommitment(t *testing.T) { s := makeSigners(t, test)[0] s.Commit() g := group.Group(test.Ciphersuite) - eLen := g.ElementLength() sLen := g.ScalarLength() - pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen - confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) - offset := confLen + 4 + sLen + keyShareLen + 8 + 2*g.ScalarLength() + offset := confLen + 6 + keyShareLen + 8 + 2*sLen encoded := s.Encode() encoded[offset] = 0 diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index 1ba7874..d1fb9a1 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -384,7 +384,7 @@ func TestConfiguration_PrepareVerifySignatureShare_BadNonVerifiedConfiguration(t SignerPublicKeys: publicKeyShares, } - if _, _, err := configuration.PrepareVerifySignatureShare(nil, nil); err == nil || + if _, _, _, err := configuration.PrepareVerifySignatureShare(nil, nil); err == nil || err.Error() != expectedErrorPrefix.Error() { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -404,7 +404,7 @@ func TestConfiguration_PrepareVerifySignatureShare_InvalidCommitments(t *testing coms[i] = s.Commit() } - if _, _, err := configuration.PrepareVerifySignatureShare(nil, coms[:1]); err == nil || + if _, _, _, err := configuration.PrepareVerifySignatureShare(nil, coms[:1]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -491,7 +491,7 @@ func TestConfiguration_VerifySignatureShare_MissingPublicKey(t *testing.T) { } func TestConfiguration_VerifySignatureShare_BadSignerID(t *testing.T) { - expectedErrorPrefix := "can't compute challenge: anomaly in participant identifiers: one of the polynomial's coefficients is zero" + expectedErrorPrefix := "anomaly in participant identifiers: one of the polynomial's coefficients is zero" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -599,7 +599,8 @@ func TestCommitment_Verify_WrongGroup(t *testing.T) { com := signer.Commit() com.Group = 2 - if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + if err := com.Validate(group.Ristretto255Sha512); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -615,20 +616,23 @@ func TestCommitment_Verify_BadHidingNonce(t *testing.T) { com := signer.Commit() // generator - com.HidingNonce.Base() - if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + com.HidingNonceCommitment.Base() + if err := com.Validate(group.Ristretto255Sha512); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // point at infinity - com.HidingNonce.Identity() - if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + com.HidingNonceCommitment.Identity() + if err := com.Validate(group.Ristretto255Sha512); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // nil - com.HidingNonce = nil - if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + com.HidingNonceCommitment = nil + if err := com.Validate(group.Ristretto255Sha512); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -644,20 +648,23 @@ func TestCommitment_Verify_BadBindingNonce(t *testing.T) { com := signer.Commit() // generator - com.BindingNonce.Base() - if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + com.BindingNonceCommitment.Base() + if err := com.Validate(group.Ristretto255Sha512); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // point at infinity - com.BindingNonce.Identity() - if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + com.BindingNonceCommitment.Identity() + if err := com.Validate(group.Ristretto255Sha512); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // nil - com.BindingNonce = nil - if err := com.Verify(group.Ristretto255Sha512); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + com.BindingNonceCommitment = nil + if err := com.Validate(group.Ristretto255Sha512); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -676,7 +683,7 @@ func TestCommitmentList_Verify_InsufficientCommitments(t *testing.T) { coms[i] = s.Commit() } - if err := coms[:tt.threshold-1].Verify(group.Ristretto255Sha512, tt.threshold); err == nil || + if err := coms[:tt.threshold-1].Validate(group.Ristretto255Sha512, tt.threshold); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -698,22 +705,7 @@ func TestCommitmentList_Verify_DuplicateSignerIDs(t *testing.T) { coms[2] = coms[1].Copy() - if err := coms.Verify(group.Ristretto255Sha512, tt.threshold); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestComputeChallengeFactor_InvalidID(t *testing.T) { - expectedErrorPrefix := "anomaly in participant identifiers: " - g := group.Ristretto255Sha512 - id := uint64(1) - participants := []*group.Scalar{ - g.NewScalar().SetUInt64(2), - g.NewScalar().SetUInt64(3), - } - - if _, err := internal.ComputeChallengeFactor(g, id, nil, participants, nil, nil, nil); err == nil || + if err := coms.Validate(group.Ristretto255Sha512, tt.threshold); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } diff --git a/tests/vectors_test.go b/tests/vectors_test.go index fe6f275..2d09717 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -125,16 +125,16 @@ func (v test) test(t *testing.T) { com := p.Commit() - if p.Commitments[com.CommitmentID].HidingNonceS.Equal(pv.HidingNonce) != 1 { + if p.NonceCommitments[com.CommitmentID].HidingNonce.Equal(pv.HidingNonce) != 1 { t.Fatal(i) } - if p.Commitments[com.CommitmentID].BindingNonceS.Equal(pv.BindingNonce) != 1 { + if p.NonceCommitments[com.CommitmentID].BindingNonce.Equal(pv.BindingNonce) != 1 { t.Fatal(i) } - if com.HidingNonce.Equal(pv.HidingNonceCommitment) != 1 { + if com.HidingNonceCommitment.Equal(pv.HidingNonceCommitment) != 1 { t.Fatal(i) } - if com.BindingNonce.Equal(pv.BindingNonceCommitment) != 1 { + if com.BindingNonceCommitment.Equal(pv.BindingNonceCommitment) != 1 { t.Fatal(i) } @@ -163,13 +163,13 @@ func (v test) test(t *testing.T) { t.Fatalf("%s\n%s\n", share.SignatureShare.Hex(), sigShares[i].SignatureShare.Hex()) } - if err := v.Config.VerifySignatureShare(sigShares[i], v.Inputs.Message, commitmentList); err != nil { + if err = v.Config.VerifySignatureShare(sigShares[i], v.Inputs.Message, commitmentList); err != nil { t.Fatalf("signature share matched but verification failed: %s", err) } } // AggregateSignatures - sig, err := v.Config.AggregateSignatures(v.Inputs.Message, sigShares, commitmentList, false) + sig, err := v.Config.AggregateSignatures(v.Inputs.Message, sigShares, commitmentList, true) if err != nil { t.Fatal(err) } @@ -179,7 +179,7 @@ func (v test) test(t *testing.T) { } // Sanity Check - if err := frost.VerifySignature(conf.Ciphersuite, v.Inputs.Message, sig, groupPublicKey); err != nil { + if err = frost.VerifySignature(conf.Ciphersuite, v.Inputs.Message, sig, groupPublicKey); err != nil { t.Fatal() } } From 8873adbd424f63cdfebcb37a080b30e216fc88ed Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:47:09 +0200 Subject: [PATCH 14/31] add tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 12 +++--- encoding.go | 6 +-- frost.go | 2 +- internal/lambda.go | 34 ++++++++++------ signer.go | 2 +- tests/encoding_test.go | 70 +++++++++++++++++++++++++++---- tests/frost_error_test.go | 50 +++++++++++++++++++---- tests/misc_test.go | 86 +++++++++++++++++++++++++++++++++++++++ 8 files changed, 222 insertions(+), 40 deletions(-) diff --git a/commitment.go b/commitment.go index e9824dc..e6ca5ed 100644 --- a/commitment.go +++ b/commitment.go @@ -25,8 +25,8 @@ var ( errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") errInvalidCiphersuite = errors.New("ciphersuite not available") errInvalidLength = errors.New("invalid encoding length") - errHidingNonce = errors.New("invalid hiding nonce (nil, identity, or generator)") - errBindingNonce = errors.New("invalid binding nonce (nil, identity, or generator)") + errHidingNonceCommitment = errors.New("invalid hiding nonce commitment (nil, identity, or generator)") + errBindingNonceCommitment = errors.New("invalid binding nonce commitment (nil, identity, or generator)") ) // Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. @@ -53,12 +53,12 @@ func (c *Commitment) Validate(g group.Group) error { if c.HidingNonceCommitment == nil || c.HidingNonceCommitment.IsIdentity() || c.HidingNonceCommitment.Equal(generator) == 1 { - return errHidingNonce + return errHidingNonceCommitment } if c.BindingNonceCommitment == nil || c.BindingNonceCommitment.IsIdentity() || c.BindingNonceCommitment.Equal(generator) == 1 { - return errBindingNonce + return errBindingNonceCommitment } return nil @@ -116,14 +116,14 @@ func (c *Commitment) Decode(data []byte) error { hn := g.NewElement() if err := hn.Decode(data[offset : offset+g.ElementLength()]); err != nil { - return fmt.Errorf("invalid encoding of hiding nonce: %w", err) + return fmt.Errorf("invalid encoding of hiding nonce commitment: %w", err) } offset += g.ElementLength() 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 fmt.Errorf("invalid encoding of binding nonce commitment: %w", err) } c.Group = g diff --git a/encoding.go b/encoding.go index 255f983..a072514 100644 --- a/encoding.go +++ b/encoding.go @@ -195,7 +195,7 @@ func (s *Signer) Encode() []byte { copy(out, conf) binary.LittleEndian.PutUint16(out[len(conf):len(conf)+2], uint16(len(ks))) // key share length binary.LittleEndian.PutUint16(out[len(conf)+2:len(conf)+4], uint16(nCommitments)) // number of commitments - binary.LittleEndian.PutUint16(out[len(conf)+4:len(conf)+6], uint16(nLambdas)) // number of commitments + binary.LittleEndian.PutUint16(out[len(conf)+4:len(conf)+6], uint16(nLambdas)) // number of lambda entries out = append(out, ks...) // key share @@ -324,7 +324,7 @@ func (s *Signer) Decode(data []byte) error { func (s *SignatureShare) Encode() []byte { share := s.SignatureShare.Encode() - out := make([]byte, 1+8+s.Group.ScalarLength()) + out := make([]byte, encodedLength(encSigShare, s.Group)) out[0] = byte(s.Group) binary.LittleEndian.PutUint64(out[1:9], s.SignerIdentifier) copy(out[9:], share) @@ -345,7 +345,7 @@ func (s *SignatureShare) Decode(data []byte) error { return internal.ErrInvalidCiphersuite } - if len(data) != 1+8+g.ScalarLength() { + if uint64(len(data)) != encodedLength(encSigShare, g) { return internal.ErrInvalidLength } diff --git a/frost.go b/frost.go index dce5770..68587b3 100644 --- a/frost.go +++ b/frost.go @@ -127,7 +127,7 @@ type Configuration struct { var ( errInvalidThresholdParameter = errors.New("threshold is 0 or higher than maxSigners") errInvalidMaxSignersOrder = errors.New("maxSigners is higher than group order") - errInvalidGroupPublicKey = errors.New("invalid group public key (nil, identity, or generator") + errInvalidGroupPublicKey = errors.New("invalid group public key (nil, identity, or generator)") errInvalidNumberOfPublicKeys = errors.New("invalid number of public keys (lower than threshold or above maximum)") ) diff --git a/internal/lambda.go b/internal/lambda.go index 7f562ed..ad9b525 100644 --- a/internal/lambda.go +++ b/internal/lambda.go @@ -35,22 +35,30 @@ func lambdaRegistryKey(participants []uint64) string { return hex.EncodeToString(hash.SHA256.Hash([]byte(a))) // Length = 32 bytes, 64 in hex string } -func (l LambdaRegistry) Get(g group.Group, id uint64, participants []uint64) (*group.Scalar, error) { - key := lambdaRegistryKey(participants) +func (l LambdaRegistry) New(g group.Group, id uint64, participants []uint64) (*group.Scalar, error) { + polynomial := secretsharing.NewPolynomialFromListFunc(g, participants, func(p uint64) *group.Scalar { + return g.NewScalar().SetUInt64(p) + }) - lambda, registered := l[key] - if !registered { - polynomial := secretsharing.NewPolynomialFromListFunc(g, participants, func(p uint64) *group.Scalar { - return g.NewScalar().SetUInt64(p) - }) + lambda, err := Lambda(g, id, polynomial) + if err != nil { + return nil, err + } - var err error - lambda, err = Lambda(g, id, polynomial) - if err != nil { - return nil, err - } + l.Set(participants, lambda) + + return lambda, nil +} + +func (l LambdaRegistry) Get(participants []uint64) *group.Scalar { + key := lambdaRegistryKey(participants) + return l[key] +} - l.Set(participants, lambda) +func (l LambdaRegistry) GetOrNew(g group.Group, id uint64, participants []uint64) (*group.Scalar, error) { + lambda := l.Get(participants) + if lambda == nil { + return l.New(g, id, participants) } return lambda, nil diff --git a/signer.go b/signer.go index f653758..61194d6 100644 --- a/signer.go +++ b/signer.go @@ -189,7 +189,7 @@ func (s *Signer) Sign(commitmentID uint64, message []byte, commitments Commitmen participants := commitments.Participants() - lambda, err := s.LambdaRegistry.Get(s.Configuration.group, s.KeyShare.ID, participants) + lambda, err := s.LambdaRegistry.GetOrNew(s.Configuration.group, s.KeyShare.ID, participants) if err != nil { return nil, err } diff --git a/tests/encoding_test.go b/tests/encoding_test.go index b166a53..ad0225f 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -340,6 +340,23 @@ func TestEncoding_Configuration_InvalidLength(t *testing.T) { }) } +func TestEncoding_Configuration_InvalidConfigEncoding(t *testing.T) { + expectedErrorPrefix := "the threshold in the encoded configuration is higher than the number of maximum participants" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration := makeConf(t, tt) + configuration.Threshold = configuration.MaxSigners + 1 + encoded := configuration.Encode() + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestEncoding_Configuration_InvalidGroupPublicKey(t *testing.T) { expectedErrorPrefix := "could not decode group public key: element Decode: " @@ -390,11 +407,42 @@ func TestEncoding_Configuration_InvalidPublicKeyShare(t *testing.T) { }) } +func TestEncoding_Configuration_CantVerify_InvalidPubKey(t *testing.T) { + expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" + + testAll(t, func(t *testing.T, test *tableTest) { + configuration := makeConf(t, test) + configuration.GroupPublicKey.Base() + encoded := configuration.Encode() + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + func TestEncoding_Signer(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { - s := makeSigners(t, test)[0] + s := makeSigners(t, test)[1] s.Commit() s.Commit() + + participants := make([]uint64, test.maxSigners) + for i := range test.maxSigners { + participants[i] = i + 1 + } + + _, err := s.LambdaRegistry.New(test.ECGroup(), s.Identifier(), participants) + if err != nil { + t.Fatal(err) + } + + _, err = s.LambdaRegistry.New(test.ECGroup(), s.Identifier(), participants[1:]) + if err != nil { + t.Fatal(err) + } + encoded := s.Encode() decoded := new(frost.Signer) @@ -421,19 +469,18 @@ func TestEncoding_Signer_BadConfHeader(t *testing.T) { } func TestEncoding_Signer_BadConf(t *testing.T) { - expectedErr := internal.ErrInvalidLength + expectedErrorPrefix := "could not decode group public key:" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] - encoded := s.Encode() - eLen := s.Configuration.Ciphersuite.ECGroup().ElementLength() - pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen - confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen + encoded := s.Encode() + encoded = slices.Replace(encoded, 25, 25+eLen, badElement(t, test.ECGroup())...) decoded := new(frost.Signer) - if err := decoded.Decode(encoded[:confLen-2]); err == nil || err.Error() != expectedErr.Error() { - t.Fatalf("expected error %q, got %q", expectedErr, err) + if err := decoded.Decode(encoded); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } }) } @@ -932,6 +979,13 @@ func TestEncoding_CommitmentList(t *testing.T) { }) } +func TestEncoding_CommitmentList_Empty(t *testing.T) { + com := frost.CommitmentList{} + if out := com.Encode(); out != nil { + t.Fatal("unexpected output") + } +} + func TestEncoding_CommitmentList_InvalidCiphersuite(t *testing.T) { expectedErrorPrefix := internal.ErrInvalidCiphersuite.Error() diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index d1fb9a1..5aceaad 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -98,7 +98,7 @@ func TestConfiguration_Verify_Threshold_Max(t *testing.T) { } func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator" + expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" testAll(t, func(t *testing.T, test *tableTest) { keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) @@ -119,7 +119,7 @@ func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { } func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator" + expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" testAll(t, func(t *testing.T, test *tableTest) { keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) @@ -140,7 +140,7 @@ func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { } func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator" + expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" testAll(t, func(t *testing.T, test *tableTest) { keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) @@ -588,7 +588,7 @@ func TestConfiguration_AggregateSignatures_NonVerifiedCommitments(t *testing.T) } } -func TestCommitment_Verify_WrongGroup(t *testing.T) { +func TestCommitment_Validate_WrongGroup(t *testing.T) { expectedErrorPrefix := "commitment for participant 1 has an unexpected ciphersuite: expected ristretto255_XMD:SHA-512_R255MAP_RO_, got %!s(PANIC=String method: invalid group identifier)" tt := &tableTest{ Ciphersuite: frost.Ristretto255, @@ -605,7 +605,7 @@ func TestCommitment_Verify_WrongGroup(t *testing.T) { } } -func TestCommitment_Verify_BadHidingNonce(t *testing.T) { +func TestCommitment_Validate_BadHidingNonce(t *testing.T) { expectedErrorPrefix := "invalid hiding nonce (nil, identity, or generator)" tt := &tableTest{ Ciphersuite: frost.Ristretto255, @@ -637,7 +637,7 @@ func TestCommitment_Verify_BadHidingNonce(t *testing.T) { } } -func TestCommitment_Verify_BadBindingNonce(t *testing.T) { +func TestCommitment_Validate_BadBindingNonce(t *testing.T) { expectedErrorPrefix := "invalid binding nonce (nil, identity, or generator)" tt := &tableTest{ Ciphersuite: frost.Ristretto255, @@ -669,7 +669,7 @@ func TestCommitment_Verify_BadBindingNonce(t *testing.T) { } } -func TestCommitmentList_Verify_InsufficientCommitments(t *testing.T) { +func TestCommitmentList_Validate_InsufficientCommitments(t *testing.T) { expectedErrorPrefix := "too few commitments: expected at least 2 but got 1" tt := &tableTest{ Ciphersuite: frost.Ristretto255, @@ -689,7 +689,7 @@ func TestCommitmentList_Verify_InsufficientCommitments(t *testing.T) { } } -func TestCommitmentList_Verify_DuplicateSignerIDs(t *testing.T) { +func TestCommitmentList_Validate_DuplicateSignerIDs(t *testing.T) { expectedErrorPrefix := "commitment list contains multiple commitments of participant 2" tt := &tableTest{ Ciphersuite: frost.Ristretto255, @@ -710,3 +710,37 @@ func TestCommitmentList_Verify_DuplicateSignerIDs(t *testing.T) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } + +func TestCommitmentList_Validate_InvalidCommitment(t *testing.T) { + expectedErrorPrefix := "commitment list contains multiple commitments of participant 2" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + signers := makeSigners(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + coms[2].BindingNonceCommitment.Base() + + if err := coms.Validate(group.Ristretto255Sha512, tt.threshold); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_ParticipantsScalar_Empty(t *testing.T) { + com := frost.CommitmentList{} + if out := com.ParticipantsScalar(); out != nil { + t.Fatal("unexpected output") + } + + com = frost.CommitmentList{nil, nil} + if out := com.ParticipantsScalar(); out != nil { + t.Fatal("unexpected output") + } +} diff --git a/tests/misc_test.go b/tests/misc_test.go index a2c1d23..3614cda 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -423,3 +423,89 @@ func TestPublicKeyShareVerificationFail(t *testing.T) { } }) } + +func TestLambda_BadID(t *testing.T) { + expectedErrorPrefix := "anomaly in participant identifiers: one of the polynomial's coefficients is zero" + g := group.Ristretto255Sha512 + polynomial := []*group.Scalar{ + g.NewScalar().SetUInt64(1), + g.NewScalar().SetUInt64(0), + g.NewScalar().SetUInt64(1), + } + + if _, err := internal.Lambda(g, 1, polynomial); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestLambdaRegistry(t *testing.T) { + g := group.Ristretto255Sha512 + id := uint64(2) + participants := []uint64{1, 2, 3, 4} + lambdas := make(internal.LambdaRegistry) + + // Get should return nil + if lambda := lambdas.Get(participants); lambda != nil { + t.Fatal("unexpected result") + } + + // Create a new entry + lambda, err := lambdas.New(g, id, participants) + if err != nil { + t.Fatal(err) + } + + if lambda == nil { + t.Fatal("unexpected result") + } + + // Getting the same entry + lambda2 := lambdas.Get(participants) + if lambda.Equal(lambda2) != 1 { + t.Fatal("expected equality") + } + + lambda3, err := lambdas.GetOrNew(g, id, participants) + if err != nil { + t.Fatal(err) + } + + if lambda.Equal(lambda3) != 1 { + t.Fatal("expected equality") + } + + // Getting another entry must result in another returned value + lambda4, err := lambdas.GetOrNew(g, id, participants[:3]) + if err != nil { + t.Fatal(err) + } + + if lambda.Equal(lambda4) == 1 { + t.Fatal("unexpected equality") + } + + lambda5, err := lambdas.GetOrNew(g, id, participants[:3]) + if err != nil { + t.Fatal(err) + } + + if lambda4.Equal(lambda5) != 1 { + t.Fatal("expected equality") + } + + // Removing and checking for the same entry + lambdas.Delete(participants) + if lambda = lambdas.Get(participants); lambda != nil { + t.Fatal("unexpected result") + } + + // Setting must return the same value + lambda6 := g.NewScalar().Random() + lambdas.Set(participants, lambda6) + lambda7 := lambdas.Get(participants) + + if lambda6.Equal(lambda7) != 1 { + t.Fatal("expected equality") + } +} From c2b71291ede65a3832c220df2f1a787e7cc6e2ff Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:07:04 +0200 Subject: [PATCH 15/31] fix tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- tests/encoding_test.go | 4 ++-- tests/frost_error_test.go | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/encoding_test.go b/tests/encoding_test.go index ad0225f..efa2017 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -918,7 +918,7 @@ func TestEncoding_Commitment_InvalidLength2(t *testing.T) { } func TestEncoding_Commitment_InvalidHidingNonce(t *testing.T) { - expectedErrorPrefix := "invalid encoding of hiding nonce: " + expectedErrorPrefix := "invalid encoding of hiding nonce commitment: " testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] @@ -935,7 +935,7 @@ func TestEncoding_Commitment_InvalidHidingNonce(t *testing.T) { } func TestEncoding_Commitment_InvalidBindingNonce(t *testing.T) { - expectedErrorPrefix := "invalid encoding of binding nonce: " + expectedErrorPrefix := "invalid encoding of binding nonce commitment: " testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index 5aceaad..4aa1a2b 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -606,7 +606,7 @@ func TestCommitment_Validate_WrongGroup(t *testing.T) { } func TestCommitment_Validate_BadHidingNonce(t *testing.T) { - expectedErrorPrefix := "invalid hiding nonce (nil, identity, or generator)" + expectedErrorPrefix := "invalid hiding nonce commitment (nil, identity, or generator)" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -638,7 +638,7 @@ func TestCommitment_Validate_BadHidingNonce(t *testing.T) { } func TestCommitment_Validate_BadBindingNonce(t *testing.T) { - expectedErrorPrefix := "invalid binding nonce (nil, identity, or generator)" + expectedErrorPrefix := "invalid binding nonce commitment (nil, identity, or generator)" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -712,7 +712,7 @@ func TestCommitmentList_Validate_DuplicateSignerIDs(t *testing.T) { } func TestCommitmentList_Validate_InvalidCommitment(t *testing.T) { - expectedErrorPrefix := "commitment list contains multiple commitments of participant 2" + expectedErrorPrefix := "invalid binding nonce commitment (nil, identity, or generator)" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 3, From f145a58d1498718e5abf94d8c5d894961282cfb4 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Fri, 30 Aug 2024 10:33:14 +0200 Subject: [PATCH 16/31] some refactor and add tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 170 ++++++++++++++++++++-------------- coordinator.go | 47 +++------- encoding.go | 55 +++++++++++ examples_test.go | 4 +- frost.go | 27 +++++- internal/lambda.go | 41 +++++++-- signer.go | 59 ++++++------ tests/encoding_test.go | 19 +--- tests/frost_error_test.go | 186 ++++++++++++++++++++++++++++++-------- tests/frost_test.go | 3 +- tests/misc_test.go | 29 ++---- tests/utils_test.go | 17 ++++ tests/vectors_test.go | 5 +- 13 files changed, 445 insertions(+), 217 deletions(-) diff --git a/commitment.go b/commitment.go index e6ca5ed..43d932f 100644 --- a/commitment.go +++ b/commitment.go @@ -42,7 +42,8 @@ type Commitment struct { func (c *Commitment) Validate(g group.Group) error { if c.Group != g { return fmt.Errorf( - "commitment for participant %d has an unexpected ciphersuite: expected %s, got %s", + "commitment %d for participant %d has an unexpected ciphersuite: expected %s, got %s", + c.CommitmentID, c.SignerID, g, c.Group, @@ -53,12 +54,14 @@ func (c *Commitment) Validate(g group.Group) error { if c.HidingNonceCommitment == nil || c.HidingNonceCommitment.IsIdentity() || c.HidingNonceCommitment.Equal(generator) == 1 { - return errHidingNonceCommitment + return fmt.Errorf("commitment %d for signer %d has an %w", c.CommitmentID, + c.SignerID, errHidingNonceCommitment) } if c.BindingNonceCommitment == nil || c.BindingNonceCommitment.IsIdentity() || c.BindingNonceCommitment.Equal(generator) == 1 { - return errBindingNonceCommitment + return fmt.Errorf("commitment %d for signer %d has an %w", c.CommitmentID, + c.SignerID, errBindingNonceCommitment) } return nil @@ -80,66 +83,15 @@ func EncodedSize(g group.Group) uint64 { return 1 + 8 + 8 + 2*uint64(g.ElementLength()) } -// Encode returns the serialized byte encoding of a participant's commitment. -func (c *Commitment) Encode() []byte { - hNonce := c.HidingNonceCommitment.Encode() - bNonce := c.BindingNonceCommitment.Encode() - - out := make([]byte, 17, EncodedSize(c.Group)) - out[0] = byte(c.Group) - binary.LittleEndian.PutUint64(out[1:9], c.CommitmentID) - binary.LittleEndian.PutUint64(out[9:17], c.SignerID) - out = append(out, hNonce...) - out = append(out, bNonce...) - - return out -} - -// Decode attempts to deserialize the encoded commitment given as input, and to return it. -func (c *Commitment) Decode(data []byte) error { - if len(data) < 17 { - return errDecodeCommitmentLength - } - - g := group.Group(data[0]) - if !g.Available() { - return errInvalidCiphersuite - } - - if uint64(len(data)) != EncodedSize(g) { - return errDecodeCommitmentLength - } - - cID := binary.LittleEndian.Uint64(data[1:9]) - pID := binary.LittleEndian.Uint64(data[9:17]) - offset := 17 - - hn := g.NewElement() - if err := hn.Decode(data[offset : offset+g.ElementLength()]); err != nil { - return fmt.Errorf("invalid encoding of hiding nonce commitment: %w", err) - } - - offset += g.ElementLength() - - bn := g.NewElement() - if err := bn.Decode(data[offset : offset+g.ElementLength()]); err != nil { - return fmt.Errorf("invalid encoding of binding nonce commitment: %w", err) - } - - c.Group = g - c.CommitmentID = cID - c.SignerID = pID - c.HidingNonceCommitment = hn - c.BindingNonceCommitment = bn - - return nil -} - // CommitmentList is a sortable list of commitments with search functions. type CommitmentList []*Commitment +// cmpID returns a negative number when the signer identity of a < b, a positive number when +// a > b and zero when a == b. func cmpID(a, b *Commitment) int { switch { + case a == nil || b == nil: + return 0 case a.SignerID < b.SignerID: // a < b return -1 case a.SignerID > b.SignerID: @@ -151,7 +103,9 @@ func cmpID(a, b *Commitment) int { // Sort sorts the list the ascending order of identifiers. func (c CommitmentList) Sort() { - slices.SortFunc(c, cmpID) + if !c.IsSorted() { + slices.SortFunc(c, cmpID) + } } // IsSorted returns whether the list is sorted in ascending order by identifier. @@ -198,22 +152,101 @@ func (c CommitmentList) ParticipantsScalar() []*group.Scalar { }) } -// Validate checks for the Commitment list's integrity. -func (c CommitmentList) Validate(g group.Group, threshold uint64) error { +func (c *Configuration) isSignerRegistered(sid uint64) bool { + for _, peer := range c.SignerPublicKeys { + if peer.ID == sid { + return true + } + } + + return false +} + +// ValidateCommitmentList returns an error if at least one of the following conditions is not met: +// - list length is within [threshold;max] +// - no signer identifier in commitments is 0 +// - no singer identifier in commitments is > max signers +// - no duplicated in signer identifiers +// - all commitment signer identifiers are registered in the configuration +func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error { // Validate number of commitments. - if uint64(len(c)) < threshold { - return fmt.Errorf("too few commitments: expected at least %d but got %d", threshold, len(c)) + length := uint64(len(commitments)) + + if length == 0 { + return fmt.Errorf("commitment list is empty") } - // Ensure the list is sorted - if !c.IsSorted() { - c.Sort() + if length < c.Threshold { + return fmt.Errorf("too few commitments: expected at least %d but got %d", c.Threshold, length) + } + + if length > c.MaxSigners { + return fmt.Errorf("too many commitments: expected %d or less but got %d", c.MaxSigners, length) + } + + // set to detect duplication + set := make(map[uint64]struct{}, length) + + for i, commitment := range commitments { + if commitment == nil { + return fmt.Errorf("the commitment list has a nil commitment") + } + + if commitment.SignerID == 0 { + return fmt.Errorf("signer identifier for commitment %d is 0", commitment.CommitmentID) + } + + if commitment.SignerID > c.MaxSigners { + return fmt.Errorf( + "signer identifier %d for commitment %d is above allowed values (%d)", + commitment.SignerID, + commitment.CommitmentID, + c.MaxSigners, + ) + } + + // Check for duplicate participant entries. + if _, exists := set[commitment.SignerID]; exists { + return fmt.Errorf("commitment list contains multiple commitments of participant %d", commitment.SignerID) + } + + set[commitment.SignerID] = struct{}{} + + // Check general validity of the commitment. + if err := commitment.Validate(c.group); err != nil { + return err + } + + // List must be sorted, compare with the next commitment. + if uint64(i) < length-2 { + if cmpID(commitment, commitments[i+1]) > 0 { + return fmt.Errorf("commitment list is not sorted by signer identifiers") + } + } + + // Validate that all commitments come from registered signers. + if !c.isSignerRegistered(commitment.SignerID) { + return fmt.Errorf( + "signer identifier %d for commitment %d is not registered in the configuration", + commitment.SignerID, + commitment.CommitmentID, + ) + } } + return nil +} + +// Validate checks for the Commitment list's integrity. +// - list is returned sorted +// - no signer identifier in commitments is 0 +// - no +func (c CommitmentList) Validate(g group.Group) error { // set to detect duplication set := make(map[uint64]struct{}, len(c)) for _, com := range c { + // Check for duplicate participant entries. if _, exists := set[com.SignerID]; exists { return fmt.Errorf("commitment list contains multiple commitments of participant %d", com.SignerID) @@ -227,6 +260,11 @@ func (c CommitmentList) Validate(g group.Group, threshold uint64) error { } } + // Ensure the list is sorted + if !c.IsSorted() { + c.Sort() + } + return nil } diff --git a/coordinator.go b/coordinator.go index a8cf308..5b24732 100644 --- a/coordinator.go +++ b/coordinator.go @@ -25,7 +25,7 @@ type Signature struct { Z *group.Scalar } -// AggregateSignatures allows a coordinator to produce the final signature given all signature shares. +// AggregateSignatures enables a 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 // coordinator must abort the protocol, as the resulting signature will be invalid. @@ -69,6 +69,7 @@ func (c *Configuration) AggregateSignatures( if verify { if err = VerifySignature(c.Ciphersuite, message, signature, c.GroupPublicKey); err != nil { + // difficult to reach, because if all shares are valid, the final signature is valid. return nil, err } } @@ -101,8 +102,10 @@ func (c *Configuration) PrepareVerifySignatureShare(message []byte, } } - // Check Commitment list integrity - if err := commitments.Validate(c.group, c.Threshold); err != nil { + commitments.Sort() + + // Validate general consistency of the commitment list. + if err := c.ValidateCommitmentList(commitments); err != nil { return nil, nil, nil, fmt.Errorf("invalid list of commitments: %w", err) } @@ -130,9 +133,14 @@ func (c *Configuration) verifySignatureShare( groupCommitment *group.Element, bindingFactors BindingFactors, ) error { + // Due diligence check that no signer id == 0. + if sigShare.SignerIdentifier == 0 { + return errors.New("signer identifier is 0 (invalid)") + } + com := commitments.Get(sigShare.SignerIdentifier) if com == nil { - return fmt.Errorf("commitment not registered for signer %d", sigShare.SignerIdentifier) + return fmt.Errorf("commitment for signer %d is missing", sigShare.SignerIdentifier) } pk := c.getSignerPubKey(sigShare.SignerIdentifier) @@ -140,11 +148,7 @@ func (c *Configuration) verifySignatureShare( return fmt.Errorf("public key not registered for signer %d", sigShare.SignerIdentifier) } - lambda, err := internal.Lambda(c.group, sigShare.SignerIdentifier, participants) - if err != nil { - return err - } - + lambda := internal.Lambda2(c.group, sigShare.SignerIdentifier, participants) lambdaChall := c.challenge(lambda, message, groupCommitment) // Commitment KeyShare: r = g(h + b*f + l*s) @@ -159,28 +163,3 @@ func (c *Configuration) verifySignatureShare( return nil } - -// VerifySignature returns whether the signature of the message is valid under publicKey. -func VerifySignature(c Ciphersuite, message []byte, signature *Signature, publicKey *group.Element) error { - g := c.ECGroup() - if g == 0 { - return internal.ErrInvalidCiphersuite - } - - ch := SchnorrChallenge(g, message, signature.R, publicKey) - r := signature.R.Copy().Add(publicKey.Copy().Multiply(ch)) - l := g.Base().Multiply(signature.Z) - - // Clear the cofactor for Edwards25519. - if g == group.Edwards25519Sha512 { - cofactor := group.Edwards25519Sha512.NewScalar().SetUInt64(8) - l.Multiply(cofactor) - r.Multiply(cofactor) - } - - if l.Equal(r) != 1 { - return errInvalidSignature - } - - return nil -} diff --git a/encoding.go b/encoding.go index a072514..a440da0 100644 --- a/encoding.go +++ b/encoding.go @@ -320,6 +320,61 @@ func (s *Signer) Decode(data []byte) error { return nil } +// Encode returns the serialized byte encoding of a participant's commitment. +func (c *Commitment) Encode() []byte { + hNonce := c.HidingNonceCommitment.Encode() + bNonce := c.BindingNonceCommitment.Encode() + + out := make([]byte, 17, EncodedSize(c.Group)) + out[0] = byte(c.Group) + binary.LittleEndian.PutUint64(out[1:9], c.CommitmentID) + binary.LittleEndian.PutUint64(out[9:17], c.SignerID) + out = append(out, hNonce...) + out = append(out, bNonce...) + + return out +} + +// Decode attempts to deserialize the encoded commitment given as input, and to return it. +func (c *Commitment) Decode(data []byte) error { + if len(data) < 17 { + return errDecodeCommitmentLength + } + + g := group.Group(data[0]) + if !g.Available() { + return errInvalidCiphersuite + } + + if uint64(len(data)) != EncodedSize(g) { + return errDecodeCommitmentLength + } + + cID := binary.LittleEndian.Uint64(data[1:9]) + pID := binary.LittleEndian.Uint64(data[9:17]) + offset := 17 + + hn := g.NewElement() + if err := hn.Decode(data[offset : offset+g.ElementLength()]); err != nil { + return fmt.Errorf("invalid encoding of hiding nonce commitment: %w", err) + } + + offset += g.ElementLength() + + bn := g.NewElement() + if err := bn.Decode(data[offset : offset+g.ElementLength()]); err != nil { + return fmt.Errorf("invalid encoding of binding nonce commitment: %w", err) + } + + c.Group = g + c.CommitmentID = cID + c.SignerID = pID + c.HidingNonceCommitment = hn + c.BindingNonceCommitment = bn + + return nil +} + // Encode returns a compact byte encoding of the signature share. func (s *SignatureShare) Encode() []byte { share := s.SignatureShare.Encode() diff --git a/examples_test.go b/examples_test.go index 8fa6c49..087d670 100644 --- a/examples_test.go +++ b/examples_test.go @@ -87,7 +87,7 @@ func Example_signer() { // Step 3: The participant receives the commitments from the other signers and the message to sign. // Sign produces a signature share to be sent back to the coordinator. // Execution MUST be aborted upon errors. - signatureShare, err := participant.Sign(com.CommitmentID, message, commitments) + signatureShare, err := participant.Sign(message, commitments) if err != nil { panic(err) } @@ -159,7 +159,7 @@ func Example_coordinator() { signatureShares := make([]*frost.SignatureShare, threshold) for i, p := range participants { var err error - signatureShares[i], err = p.Sign(commitments[i].CommitmentID, message, commitments) + signatureShares[i], err = p.Sign(message, commitments) if err != nil { panic(err) } diff --git a/frost.go b/frost.go index 68587b3..59478e3 100644 --- a/frost.go +++ b/frost.go @@ -41,7 +41,7 @@ Requirements: - each participant MUST know the pub key of each other - network channels must be authenticated (confidentiality is not required) - Signers have local secret data - - secret key and lambda are long term + - secret key is long term - committed nonces between commitment and signature - When receiving the commitment list, each elements must be deserialized, and upon error, the signer MUST abort the @@ -243,3 +243,28 @@ func (c *Configuration) challenge(lambda *group.Scalar, message []byte, groupCom func SchnorrChallenge(g group.Group, msg []byte, r, pk *group.Element) *group.Scalar { return internal.H2(g, internal.Concatenate(r.Encode(), pk.Encode(), msg)) } + +// VerifySignature returns whether the signature of the message is valid under publicKey. +func VerifySignature(c Ciphersuite, message []byte, signature *Signature, publicKey *group.Element) error { + g := c.ECGroup() + if g == 0 { + return internal.ErrInvalidCiphersuite + } + + ch := SchnorrChallenge(g, message, signature.R, publicKey) + r := signature.R.Copy().Add(publicKey.Copy().Multiply(ch)) + l := g.Base().Multiply(signature.Z) + + // Clear the cofactor for Edwards25519. + if g == group.Edwards25519Sha512 { + cofactor := group.Edwards25519Sha512.NewScalar().SetUInt64(8) + l.Multiply(cofactor) + r.Multiply(cofactor) + } + + if l.Equal(r) != 1 { + return errInvalidSignature + } + + return nil +} diff --git a/internal/lambda.go b/internal/lambda.go index ad9b525..0b17786 100644 --- a/internal/lambda.go +++ b/internal/lambda.go @@ -26,6 +26,28 @@ func Lambda(g group.Group, id uint64, polynomial secretsharing.Polynomial) (*gro return l, nil } +// Lambda derives the interpolating value for id in the polynomial made by the participant identifiers. +// This function assumes that: +// - id is non-nil and != 0 +// - every scalar in participants is non-nil and != 0 +// - there are no duplicates in participants +func Lambda2(g group.Group, id uint64, participants []*group.Scalar) *group.Scalar { + sid := g.NewScalar().SetUInt64(id) + numerator := g.NewScalar().One() + denominator := g.NewScalar().One() + + for _, participant := range participants { + if participant.Equal(sid) == 1 { + continue + } + + numerator.Multiply(participant) + denominator.Multiply(participant.Copy().Subtract(sid)) + } + + return numerator.Multiply(denominator.Invert()) +} + type LambdaRegistry map[string]*group.Scalar const lambdaRegistryKeyDomainSeparator = "FROST-participants" @@ -35,19 +57,22 @@ func lambdaRegistryKey(participants []uint64) string { return hex.EncodeToString(hash.SHA256.Hash([]byte(a))) // Length = 32 bytes, 64 in hex string } -func (l LambdaRegistry) New(g group.Group, id uint64, participants []uint64) (*group.Scalar, error) { +func (l LambdaRegistry) New(g group.Group, id uint64, participants []uint64) *group.Scalar { polynomial := secretsharing.NewPolynomialFromListFunc(g, participants, func(p uint64) *group.Scalar { return g.NewScalar().SetUInt64(p) }) + /* + lambda, err := Lambda(g, id, polynomial) + if err != nil { + return nil, err + } - lambda, err := Lambda(g, id, polynomial) - if err != nil { - return nil, err - } + */ + lambda := Lambda2(g, id, polynomial) l.Set(participants, lambda) - return lambda, nil + return lambda } func (l LambdaRegistry) Get(participants []uint64) *group.Scalar { @@ -55,13 +80,13 @@ func (l LambdaRegistry) Get(participants []uint64) *group.Scalar { return l[key] } -func (l LambdaRegistry) GetOrNew(g group.Group, id uint64, participants []uint64) (*group.Scalar, error) { +func (l LambdaRegistry) GetOrNew(g group.Group, id uint64, participants []uint64) *group.Scalar { lambda := l.Get(participants) if lambda == nil { return l.New(g, id, participants) } - return lambda, nil + return lambda } func (l LambdaRegistry) Set(participants []uint64, lambda *group.Scalar) { diff --git a/signer.go b/signer.go index 61194d6..251b8b1 100644 --- a/signer.go +++ b/signer.go @@ -11,6 +11,7 @@ package frost import ( "crypto/rand" "encoding/binary" + "errors" "fmt" group "github.com/bytemare/crypto" @@ -51,12 +52,17 @@ type Signer struct { BindingRandom []byte } +// Nonce holds the signing nonces and their commitments. The Signer.Commit() method will generate and record a new nonce +// and return the Commitment to that nonce. That Commitment will be used in Signer.Sign() and the associated nonces to +// create a signature share. Note that nonces and their commitments are agnostic of the upcoming message to sign, and +// can therefore be pre-computed and the commitments shared before the signing session, saving a round-trip. type Nonce struct { HidingNonce *group.Scalar BindingNonce *group.Scalar *Commitment } +// ClearNonceCommitment zeroes-out the nonces and their commitments, and unregisters the nonce record. func (s *Signer) ClearNonceCommitment(commitmentID uint64) { if com := s.NonceCommitments[commitmentID]; com != nil { com.HidingNonce.Zero() @@ -148,36 +154,36 @@ func (s *Signer) verifyNonces(com *Commitment) error { return nil } -// VerifyCommitmentList checks for the Commitment list integrity and the signer's commitment. +// VerifyCommitmentList checks for the Commitment list integrity and the signer's commitment. This function must not +// return an error for Sign to succeed. func (s *Signer) VerifyCommitmentList(commitments CommitmentList) error { - if err := commitments.Validate(s.Configuration.group, s.Configuration.Threshold); err != nil { + // Due diligence check that no signer id == 0. + if s.KeyShare.ID == 0 { + return errors.New("signer identifier is 0 (invalid)") + } + + commitments.Sort() + + // Validate general consistency of the commitment list. + if err := s.Configuration.ValidateCommitmentList(commitments); err != nil { return fmt.Errorf("invalid list of commitments: %w", err) } - // Check commitment values for the signer. - for _, com := range commitments { - if com.SignerID == s.KeyShare.ID { - return s.verifyNonces(com) - } + // The signer's id must be among the commitments. + commitment := commitments.Get(s.KeyShare.ID) + if commitment == nil { + return fmt.Errorf("signer identifier %d not found in the commitment list", s.KeyShare.ID) } - return fmt.Errorf("no commitment for signer %d found in the commitment list", s.KeyShare.ID) + // Check commitment values for the signer. + return s.verifyNonces(commitment) } -// Sign produces a participant's signature share of the message msg. The commitmentID identifies the commitment produced -// on a previous call to Commit(). Once the signature with Sign() is produced, the internal commitment nonces are -// cleared and another call to Sign() with the same commitmentID will return an error. -// -// Each signer MUST validate the inputs before processing the Coordinator's request. -// In particular, the Signer MUST validate commitment_list, deserializing each group Element in the list using -// DeserializeElement from {{dep-pog}}. If deserialization fails, the Signer MUST abort the protocol. Moreover, -// each signer MUST ensure that its identifier and commitments (from the first round) appear in commitment_list. -func (s *Signer) Sign(commitmentID uint64, message []byte, commitments CommitmentList) (*SignatureShare, error) { - com, exists := s.NonceCommitments[commitmentID] - if !exists { - return nil, fmt.Errorf("commitmentID %d not registered", commitmentID) - } - +// Sign produces a participant's signature share of the message msg. The CommitmentList must contain a Commitment +// produced on a previous call to Commit(). Once the signature share with Sign() is produced, the internal commitment +// and nonces are cleared and another call to Sign() with the same Commitment will return an error. +func (s *Signer) Sign(message []byte, commitments CommitmentList) (*SignatureShare, error) { + commitments.Sort() if err := s.VerifyCommitmentList(commitments); err != nil { return nil, err } @@ -188,14 +194,11 @@ func (s *Signer) Sign(commitmentID uint64, message []byte, commitments Commitmen ) participants := commitments.Participants() - - lambda, err := s.LambdaRegistry.GetOrNew(s.Configuration.group, s.KeyShare.ID, participants) - if err != nil { - return nil, err - } - + lambda := s.LambdaRegistry.GetOrNew(s.Configuration.group, s.KeyShare.ID, participants) lambdaChall := s.Configuration.challenge(lambda, message, groupCommitment) + commitmentID := commitments.Get(s.KeyShare.ID).CommitmentID + com := s.NonceCommitments[commitmentID] hidingNonce := com.HidingNonce.Copy() bindingNonce := com.BindingNonce diff --git a/tests/encoding_test.go b/tests/encoding_test.go index efa2017..e212e6a 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -433,15 +433,8 @@ func TestEncoding_Signer(t *testing.T) { participants[i] = i + 1 } - _, err := s.LambdaRegistry.New(test.ECGroup(), s.Identifier(), participants) - if err != nil { - t.Fatal(err) - } - - _, err = s.LambdaRegistry.New(test.ECGroup(), s.Identifier(), participants[1:]) - if err != nil { - t.Fatal(err) - } + s.LambdaRegistry.New(test.ECGroup(), s.Identifier(), participants) + s.LambdaRegistry.New(test.ECGroup(), s.Identifier(), participants[1:]) encoded := s.Encode() @@ -536,7 +529,7 @@ func TestEncoding_Signer_InvalidLambda(t *testing.T) { s := signers[0] - _, err := s.Sign(coms[0].CommitmentID, message, coms) + _, err := s.Sign(message, coms) if err != nil { t.Fatal(err) } @@ -678,8 +671,7 @@ func TestEncoding_SignatureShare(t *testing.T) { } for _, s := range signers { - com := coms.Get(s.Identifier()).CommitmentID - sigShare, err := s.Sign(com, message, coms) + sigShare, err := s.Sign(message, coms) if err != nil { t.Fatal(err) } @@ -749,9 +741,8 @@ func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { } s := signers[0] - com := coms.Get(s.Identifier()).CommitmentID - sigShare, err := s.Sign(com, message, coms) + sigShare, err := s.Sign(message, coms) if err != nil { t.Fatal(err) } diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index 4aa1a2b..21e6679 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -9,6 +9,7 @@ package frost_test import ( + "fmt" "strings" "testing" @@ -435,11 +436,11 @@ func TestConfiguration_VerifySignatureShare_BadPrep(t *testing.T) { } func TestConfiguration_VerifySignatureShare_MissingCommitment(t *testing.T) { - expectedErrorPrefix := "commitment not registered for signer 1" + expectedErrorPrefix := "commitment for signer 1 is missing" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, - maxSigners: 3, + maxSigners: 4, } message := []byte("message") configuration, signers := fullSetup(t, tt) @@ -449,21 +450,18 @@ func TestConfiguration_VerifySignatureShare_MissingCommitment(t *testing.T) { coms[i] = s.Commit() } - sigShare, err := signers[0].Sign(coms[0].CommitmentID, message, coms) + sigShare, err := signers[0].Sign(message, coms) if err != nil { t.Fatal(err) } - coms[0].SignerID = tt.maxSigners + 1 - - if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + if err := configuration.VerifySignatureShare(sigShare, message, coms[1:]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } func TestConfiguration_VerifySignatureShare_MissingPublicKey(t *testing.T) { - expectedErrorPrefix := "public key not registered for signer 1" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -477,12 +475,17 @@ func TestConfiguration_VerifySignatureShare_MissingPublicKey(t *testing.T) { coms[i] = s.Commit() } - sigShare, err := signers[0].Sign(coms[0].CommitmentID, message, coms) + sigShare, err := signers[0].Sign(message, coms) if err != nil { t.Fatal(err) } configuration.SignerPublicKeys[0].ID = tt.maxSigners + 1 + expectedErrorPrefix := fmt.Sprintf( + "invalid list of commitments: signer identifier %d for commitment %d is not registered in the configuration", + 1, + coms[0].CommitmentID, + ) if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -491,7 +494,6 @@ func TestConfiguration_VerifySignatureShare_MissingPublicKey(t *testing.T) { } func TestConfiguration_VerifySignatureShare_BadSignerID(t *testing.T) { - expectedErrorPrefix := "anomaly in participant identifiers: one of the polynomial's coefficients is zero" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -505,12 +507,16 @@ func TestConfiguration_VerifySignatureShare_BadSignerID(t *testing.T) { coms[i] = s.Commit() } - sigShare, err := signers[0].Sign(coms[0].CommitmentID, message, coms) + sigShare, err := signers[0].Sign(message, coms) if err != nil { t.Fatal(err) } coms[1].SignerID = 0 + expectedErrorPrefix := fmt.Sprintf( + "invalid list of commitments: signer identifier for commitment %d is 0", + coms[1].CommitmentID, + ) if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -545,25 +551,34 @@ func TestConfiguration_VerifySignatureShare_InvalidSignatureShare(t *testing.T) } } -func TestConfiguration_AggregateSignatures_Verify_BadSigShare(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) +func TestConfiguration_AggregateSignatures_BadSigShare(t *testing.T) { + expectedErrorPrefix := "invalid signature share for signer 2" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) + coms := make(frost.CommitmentList, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } - configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + sigShares := make([]*frost.SignatureShare, len(signers)) + for i, s := range signers { + var err error + sigShares[i], err = s.Sign(message, coms) + if err != nil { + t.Fatal(err) + } } - if _, err := configuration.AggregateSignatures(nil, nil, nil, false); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + sigShares[1].SignatureShare.Random() + + if _, err := configuration.AggregateSignatures(message, sigShares, coms, true); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -588,8 +603,36 @@ func TestConfiguration_AggregateSignatures_NonVerifiedCommitments(t *testing.T) } } +func TestVerifySignature_BadCiphersuite(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + + if err := frost.VerifySignature(2, nil, nil, nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestVerifySignature_InvalidSignature(t *testing.T) { + expectedErrorPrefix := "invalid Signature" + message := []byte("message") + + testAll(t, func(t *testing.T, test *tableTest) { + configuration, _ := fullSetup(t, test) + + signature := &frost.Signature{ + R: test.ECGroup().Base(), + Z: test.ECGroup().NewScalar().Random(), + } + + if err := frost.VerifySignature(test.Ciphersuite, message, signature, configuration.GroupPublicKey); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + func TestCommitment_Validate_WrongGroup(t *testing.T) { - expectedErrorPrefix := "commitment for participant 1 has an unexpected ciphersuite: expected ristretto255_XMD:SHA-512_R255MAP_RO_, got %!s(PANIC=String method: invalid group identifier)" + expectedErrorPrefix := "commitment 1 for participant 1 has an unexpected ciphersuite: expected ristretto255_XMD:SHA-512_R255MAP_RO_, got %!s(PANIC=String method: invalid group identifier)" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -597,6 +640,8 @@ func TestCommitment_Validate_WrongGroup(t *testing.T) { } signer := makeSigners(t, tt)[0] com := signer.Commit() + signer.NonceCommitments[1] = signer.NonceCommitments[com.CommitmentID] + com.CommitmentID = 1 com.Group = 2 if err := com.Validate(group.Ristretto255Sha512); err == nil || @@ -605,8 +650,7 @@ func TestCommitment_Validate_WrongGroup(t *testing.T) { } } -func TestCommitment_Validate_BadHidingNonce(t *testing.T) { - expectedErrorPrefix := "invalid hiding nonce commitment (nil, identity, or generator)" +func TestCommitment_Validate_BadHidingNonceCommitment(t *testing.T) { tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -614,6 +658,11 @@ func TestCommitment_Validate_BadHidingNonce(t *testing.T) { } signer := makeSigners(t, tt)[0] com := signer.Commit() + expectedErrorPrefix := fmt.Sprintf( + "commitment %d for signer %d has an invalid hiding nonce commitment (nil, identity, or generator)", + com.CommitmentID, + com.SignerID, + ) // generator com.HidingNonceCommitment.Base() @@ -637,8 +686,7 @@ func TestCommitment_Validate_BadHidingNonce(t *testing.T) { } } -func TestCommitment_Validate_BadBindingNonce(t *testing.T) { - expectedErrorPrefix := "invalid binding nonce commitment (nil, identity, or generator)" +func TestCommitment_Validate_BadBindingNonceCommitment(t *testing.T) { tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -646,6 +694,11 @@ func TestCommitment_Validate_BadBindingNonce(t *testing.T) { } signer := makeSigners(t, tt)[0] com := signer.Commit() + expectedErrorPrefix := fmt.Sprintf( + "commitment %d for signer %d has an invalid binding nonce commitment (nil, identity, or generator)", + com.CommitmentID, + com.SignerID, + ) // generator com.BindingNonceCommitment.Base() @@ -676,14 +729,14 @@ func TestCommitmentList_Validate_InsufficientCommitments(t *testing.T) { threshold: 2, maxSigners: 3, } - signers := makeSigners(t, tt) + configuration, signers := fullSetup(t, tt) coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { coms[i] = s.Commit() } - if err := coms[:tt.threshold-1].Validate(group.Ristretto255Sha512, tt.threshold); err == nil || + if err := configuration.ValidateCommitmentList(coms[:tt.threshold-1]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -696,7 +749,7 @@ func TestCommitmentList_Validate_DuplicateSignerIDs(t *testing.T) { threshold: 3, maxSigners: 4, } - signers := makeSigners(t, tt) + configuration, signers := fullSetup(t, tt) coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { @@ -705,20 +758,19 @@ func TestCommitmentList_Validate_DuplicateSignerIDs(t *testing.T) { coms[2] = coms[1].Copy() - if err := coms.Validate(group.Ristretto255Sha512, tt.threshold); err == nil || + if err := configuration.ValidateCommitmentList(coms); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } func TestCommitmentList_Validate_InvalidCommitment(t *testing.T) { - expectedErrorPrefix := "invalid binding nonce commitment (nil, identity, or generator)" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 3, maxSigners: 4, } - signers := makeSigners(t, tt) + configuration, signers := fullSetup(t, tt) coms := make(frost.CommitmentList, len(signers)) for i, s := range signers { @@ -726,8 +778,13 @@ func TestCommitmentList_Validate_InvalidCommitment(t *testing.T) { } coms[2].BindingNonceCommitment.Base() + expectedErrorPrefix := fmt.Sprintf( + "commitment %d for signer %d has an invalid binding nonce commitment (nil, identity, or generator)", + coms[2].CommitmentID, + coms[2].SignerID, + ) - if err := coms.Validate(group.Ristretto255Sha512, tt.threshold); err == nil || + if err := configuration.ValidateCommitmentList(coms); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -744,3 +801,58 @@ func TestCommitmentList_ParticipantsScalar_Empty(t *testing.T) { t.Fatal("unexpected output") } } + +func TestSigner_Sign_NoNonceForCommitmentID(t *testing.T) { + message := []byte("message") + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + + signers := makeSigners(t, tt) + + coms := make(frost.CommitmentList, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + coms[0].CommitmentID = 0 + expectedErrorPrefix := fmt.Sprintf( + "the commitment identifier %d for signer %d in the commitments is unknown to the signer", + coms[0].CommitmentID, + coms[0].SignerID, + ) + + if _, err := signers[0].Sign(message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +/* +func TestSigner_Sign_FailedLambdaGeneration(t *testing.T) { + call signer.Sign +} + +func TestSigner_Sign_VerifyCommitmentList_BadCommitment(t *testing.T) { + call signer.Sign +} + +func TestSigner_Sign_VerifyCommitmentList_NoCommitmentForSigner(t *testing.T) { + call signer.Sign +} + +func TestSigner_Sign_VerifyNonces_BadCommitmentID(t *testing.T) { + +} + +func TestSigner_Sign_VerifyNonces_BadHidingNonceCommitment(t *testing.T) { + +} + +func TestSigner_Sign_VerifyNonces_BadBindingNonceCommitment(t *testing.T) { + +} + +*/ diff --git a/tests/frost_test.go b/tests/frost_test.go index 98f1c9d..06c4a34 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -93,8 +93,7 @@ func runFrost( sigShares := make([]*frost.SignatureShare, threshold) for i, p := range participants { var err error - commitmentID := commitments.Get(p.Identifier()).CommitmentID - sigShares[i], err = p.Sign(commitmentID, message, commitments) + sigShares[i], err = p.Sign(message, commitments) if err != nil { t.Fatal(err) } diff --git a/tests/misc_test.go b/tests/misc_test.go index 3614cda..a5d56ec 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -10,6 +10,7 @@ package frost_test import ( "errors" + "fmt" "strings" "testing" @@ -425,7 +426,7 @@ func TestPublicKeyShareVerificationFail(t *testing.T) { } func TestLambda_BadID(t *testing.T) { - expectedErrorPrefix := "anomaly in participant identifiers: one of the polynomial's coefficients is zero" + // expectedErrorPrefix := "anomaly in participant identifiers: one of the polynomial's coefficients is zero" g := group.Ristretto255Sha512 polynomial := []*group.Scalar{ g.NewScalar().SetUInt64(1), @@ -433,10 +434,8 @@ func TestLambda_BadID(t *testing.T) { g.NewScalar().SetUInt64(1), } - if _, err := internal.Lambda(g, 1, polynomial); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } + // todo : what happens if the participant list is not vetted? + fmt.Println(internal.Lambda2(g, 1, polynomial).Hex()) } func TestLambdaRegistry(t *testing.T) { @@ -451,10 +450,7 @@ func TestLambdaRegistry(t *testing.T) { } // Create a new entry - lambda, err := lambdas.New(g, id, participants) - if err != nil { - t.Fatal(err) - } + lambda := lambdas.New(g, id, participants) if lambda == nil { t.Fatal("unexpected result") @@ -466,29 +462,20 @@ func TestLambdaRegistry(t *testing.T) { t.Fatal("expected equality") } - lambda3, err := lambdas.GetOrNew(g, id, participants) - if err != nil { - t.Fatal(err) - } + lambda3 := lambdas.GetOrNew(g, id, participants) if lambda.Equal(lambda3) != 1 { t.Fatal("expected equality") } // Getting another entry must result in another returned value - lambda4, err := lambdas.GetOrNew(g, id, participants[:3]) - if err != nil { - t.Fatal(err) - } + lambda4 := lambdas.GetOrNew(g, id, participants[:3]) if lambda.Equal(lambda4) == 1 { t.Fatal("unexpected equality") } - lambda5, err := lambdas.GetOrNew(g, id, participants[:3]) - if err != nil { - t.Fatal(err) - } + lambda5 := lambdas.GetOrNew(g, id, participants[:3]) if lambda4.Equal(lambda5) != 1 { t.Fatal("expected equality") diff --git a/tests/utils_test.go b/tests/utils_test.go index 6e8bd65..6942903 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -14,6 +14,7 @@ import ( "fmt" "math/big" "slices" + "strings" "testing" group "github.com/bytemare/crypto" @@ -100,6 +101,22 @@ func badElement(t *testing.T, g group.Group) []byte { return encoded } +func expectError(expectedError error, f func() error) error { + if err := f(); err == nil || err.Error() != expectedError.Error() { + return fmt.Errorf("expected %q, got %q", expectedError, err) + } + + return nil +} + +func expectErrorPrefix(expectedErrorMessagePrefix string, f func() error) error { + if err := f(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorMessagePrefix) { + return fmt.Errorf("expected error prefix %q, got %q", expectedErrorMessagePrefix, err) + } + + return nil +} + func TestConcatenate(t *testing.T) { inputs := [][]byte{ {1, 2, 3}, diff --git a/tests/vectors_test.go b/tests/vectors_test.go index 2d09717..3f6a4d2 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -149,11 +149,8 @@ func (v test) test(t *testing.T) { t.Fatal(i) } - com := commitmentList.Get(p.Identifier()) - commitmentID := com.CommitmentID - var err error - sigShares[i], err = p.Sign(commitmentID, v.Inputs.Message, commitmentList) + sigShares[i], err = p.Sign(v.Inputs.Message, commitmentList) if err != nil { t.Fatal(err) } From 59b560eaf3b2fa2f9f0f8cd6adeb69fd51287f22 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Fri, 30 Aug 2024 13:23:01 +0200 Subject: [PATCH 17/31] some refactor and clean up Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 35 ++------------------ coordinator.go | 59 +++++++++++++++++++++++++++------- frost.go | 67 +++++++++++++++++++++++++++++++++++++-- internal/lambda.go | 13 ++------ signer.go | 7 ++-- tests/frost_error_test.go | 4 +-- tests/misc_test.go | 2 +- 7 files changed, 123 insertions(+), 64 deletions(-) diff --git a/commitment.go b/commitment.go index 43d932f..5d56a15 100644 --- a/commitment.go +++ b/commitment.go @@ -218,7 +218,7 @@ func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error } // List must be sorted, compare with the next commitment. - if uint64(i) < length-2 { + if uint64(i) <= length-2 { if cmpID(commitment, commitments[i+1]) > 0 { return fmt.Errorf("commitment list is not sorted by signer identifiers") } @@ -237,37 +237,6 @@ func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error return nil } -// Validate checks for the Commitment list's integrity. -// - list is returned sorted -// - no signer identifier in commitments is 0 -// - no -func (c CommitmentList) Validate(g group.Group) error { - // set to detect duplication - set := make(map[uint64]struct{}, len(c)) - - for _, com := range c { - - // Check for duplicate participant entries. - if _, exists := set[com.SignerID]; exists { - return fmt.Errorf("commitment list contains multiple commitments of participant %d", com.SignerID) - } - - set[com.SignerID] = struct{}{} - - // Check general validity of the commitment. - if err := com.Validate(g); err != nil { - return err - } - } - - // Ensure the list is sorted - if !c.IsSorted() { - c.Sort() - } - - return nil -} - func (c CommitmentList) Encode() []byte { n := len(c) if n == 0 { @@ -359,7 +328,7 @@ func encodeCommitmentList(g group.Group, commitments []*commitmentWithEncodedID) return encoded } -// BindingFactors is a map of participant identifier to BindingFactors. +// BindingFactors is a map of participant identifiers to BindingFactors. type BindingFactors map[uint64]*group.Scalar func (c CommitmentList) bindingFactors(publicKey *group.Element, message []byte) BindingFactors { diff --git a/coordinator.go b/coordinator.go index 5b24732..4c745fc 100644 --- a/coordinator.go +++ b/coordinator.go @@ -34,7 +34,7 @@ type Signature struct { // 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 signer from the set of allowed participants in future runs of FROST. If verify -// is set to true, AggregateSignatures will automatically verify the signature shares and produced signatures, and will +// is set to true, AggregateSignatures will automatically verify the signature shares and the output signature, and will // return an error with the first encountered invalid signature share. func (c *Configuration) AggregateSignatures( message []byte, @@ -42,7 +42,7 @@ func (c *Configuration) AggregateSignatures( commitments CommitmentList, verify bool, ) (*Signature, error) { - groupCommitment, bindingFactors, participants, err := c.PrepareVerifySignatureShare(message, commitments) + groupCommitment, bindingFactors, participants, err := c.PrepareSignatureShareVerification(message, commitments) if err != nil { return nil, err } @@ -85,7 +85,7 @@ func (c *Configuration) VerifySignatureShare( message []byte, commitments CommitmentList, ) error { - groupCommitment, bindingFactors, participants, err := c.PrepareVerifySignatureShare(message, commitments) + groupCommitment, bindingFactors, participants, err := c.PrepareSignatureShareVerification(message, commitments) if err != nil { return err } @@ -93,7 +93,7 @@ func (c *Configuration) VerifySignatureShare( return c.verifySignatureShare(sigShare, message, commitments, participants, groupCommitment, bindingFactors) } -func (c *Configuration) PrepareVerifySignatureShare(message []byte, +func (c *Configuration) PrepareSignatureShareVerification(message []byte, commitments CommitmentList, ) (*group.Element, BindingFactors, []*group.Scalar, error) { if !c.verified { @@ -125,6 +125,46 @@ func (c *Configuration) getSignerPubKey(id uint64) *group.Element { return nil } +func (c *Configuration) validateSignatureShareLight(sigShare *SignatureShare) error { + if sigShare == nil { + return errors.New("nil signature share") + } + + if sigShare.SignatureShare == nil || sigShare.SignatureShare.IsZero() { + return errors.New("invalid signature share (nil or zero)") + } + + return nil +} + +func (c *Configuration) validateSignatureShareExtensive(sigShare *SignatureShare) error { + if err := c.validateSignatureShareLight(sigShare); err != nil { + return err + } + + if sigShare.SignerIdentifier == 0 { + return errors.New("signature share's signer identifier is 0 (invalid)") + } + + if sigShare.SignerIdentifier > c.MaxSigners { + return fmt.Errorf( + "signature share has invalid ID %d, above authorized range [1:%d]", + sigShare.SignerIdentifier, + c.MaxSigners, + ) + } + + if sigShare.Group != c.group { + return fmt.Errorf("signature share has invalid group parameter, want %s got %s", c.group, sigShare.Group) + } + + if c.getSignerPubKey(sigShare.SignerIdentifier) == nil { + return fmt.Errorf("no public key registered for signer %d", sigShare.SignerIdentifier) + } + + return nil +} + func (c *Configuration) verifySignatureShare( sigShare *SignatureShare, message []byte, @@ -133,9 +173,8 @@ func (c *Configuration) verifySignatureShare( groupCommitment *group.Element, bindingFactors BindingFactors, ) error { - // Due diligence check that no signer id == 0. - if sigShare.SignerIdentifier == 0 { - return errors.New("signer identifier is 0 (invalid)") + if err := c.validateSignatureShareExtensive(sigShare); err != nil { + return err } com := commitments.Get(sigShare.SignerIdentifier) @@ -144,11 +183,7 @@ func (c *Configuration) verifySignatureShare( } pk := c.getSignerPubKey(sigShare.SignerIdentifier) - if pk == nil { - return fmt.Errorf("public key not registered for signer %d", sigShare.SignerIdentifier) - } - - lambda := internal.Lambda2(c.group, sigShare.SignerIdentifier, participants) + lambda := internal.Lambda(c.group, sigShare.SignerIdentifier, participants) lambdaChall := c.challenge(lambda, message, groupCommitment) // Commitment KeyShare: r = g(h + b*f + l*s) diff --git a/frost.go b/frost.go index 59478e3..2704cc5 100644 --- a/frost.go +++ b/frost.go @@ -132,8 +132,8 @@ var ( ) func (c *Configuration) verifySignerPublicKeys() error { - if uint64(len(c.SignerPublicKeys)) < c.Threshold || - uint64(len(c.SignerPublicKeys)) > c.MaxSigners { + length := uint64(len(c.SignerPublicKeys)) + if length < c.Threshold || length > c.MaxSigners { return errInvalidNumberOfPublicKeys } @@ -216,6 +216,65 @@ func (c *Configuration) Init() error { return nil } +func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { + if !c.verified { + if err := c.Init(); err != nil { + return err + } + } + + if keyShare == nil { + return errors.New("provided key share is nil") + } + + if keyShare.ID == 0 { + return errors.New("provided key share has invalid ID 0") + } + + if keyShare.ID > c.MaxSigners { + return fmt.Errorf( + "provided key share has invalid ID %d, above authorized range [1:%d]", + keyShare.ID, + c.MaxSigners, + ) + } + + if keyShare.Group != c.group { + return fmt.Errorf("provided key share has invalid group parameter, want %s got %s", c.group, keyShare.Group) + } + + if c.GroupPublicKey.Equal(keyShare.GroupPublicKey) != 1 { + return errors.New( + "the group's public key in the provided key share does not match the one in the configuration", + ) + } + + if keyShare.PublicKey == nil { + return errors.New("provided key share has nil public key") + } + + if keyShare.Secret == nil || keyShare.Secret.IsZero() { + return errors.New("provided key share has invalid secret key") + } + + if c.group.Base().Multiply(keyShare.Secret).Equal(keyShare.PublicKey) != 1 { + return errors.New("provided key share has non-matching secret and public keys") + } + + pk := c.getSignerPubKey(keyShare.ID) + if pk == nil { + return errors.New("provided key share has no registered signer identifier in the configuration") + } + + if pk.Equal(keyShare.PublicKey) != 1 { + return errors.New( + "provided key share has a different public key than the one registered for that signer in the configuration", + ) + } + + return nil +} + // Signer returns a new participant of the protocol instantiated from the Configuration and the signer's key share. func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { if !c.verified { @@ -224,6 +283,10 @@ func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { } } + if err := c.ValidateKeyShare(keyShare); err != nil { + return nil, err + } + return &Signer{ KeyShare: keyShare, LambdaRegistry: make(internal.LambdaRegistry), diff --git a/internal/lambda.go b/internal/lambda.go index 0b17786..d0b9dc5 100644 --- a/internal/lambda.go +++ b/internal/lambda.go @@ -17,21 +17,12 @@ import ( secretsharing "github.com/bytemare/secret-sharing" ) -func Lambda(g group.Group, id uint64, polynomial secretsharing.Polynomial) (*group.Scalar, error) { - l, err := polynomial.DeriveInterpolatingValue(g, g.NewScalar().SetUInt64(id)) - if err != nil { - return nil, fmt.Errorf("anomaly in participant identifiers: %w", err) - } - - return l, nil -} - // Lambda derives the interpolating value for id in the polynomial made by the participant identifiers. // This function assumes that: // - id is non-nil and != 0 // - every scalar in participants is non-nil and != 0 // - there are no duplicates in participants -func Lambda2(g group.Group, id uint64, participants []*group.Scalar) *group.Scalar { +func Lambda(g group.Group, id uint64, participants []*group.Scalar) *group.Scalar { sid := g.NewScalar().SetUInt64(id) numerator := g.NewScalar().One() denominator := g.NewScalar().One() @@ -68,7 +59,7 @@ func (l LambdaRegistry) New(g group.Group, id uint64, participants []uint64) *gr } */ - lambda := Lambda2(g, id, polynomial) + lambda := Lambda(g, id, polynomial) l.Set(participants, lambda) diff --git a/signer.go b/signer.go index 251b8b1..36fd895 100644 --- a/signer.go +++ b/signer.go @@ -81,8 +81,7 @@ func (s *Signer) Identifier() uint64 { func randomCommitmentID() uint64 { buf := make([]byte, 8) - _, err := rand.Read(buf) - if err != nil { + if _, err := rand.Read(buf); err != nil { panic(fmt.Errorf("FATAL: %w", err)) } @@ -100,6 +99,8 @@ func (s *Signer) generateNonce(secret *group.Scalar, random []byte) *group.Scala func (s *Signer) genNonceID() uint64 { cid := randomCommitmentID() + // In the extremely rare and unlikely case the CSPRNG returns an already registered ID, we try again 128 times + // before failing. for range 128 { if _, exists := s.NonceCommitments[cid]; !exists { return cid @@ -108,7 +109,7 @@ func (s *Signer) genNonceID() uint64 { cid = randomCommitmentID() } - panic("FATAL: CSPRNG could not generate a unique nonce over 128 iterations") + panic("FATAL: CSPRNG could not generate unique commitment identifiers over 128 iterations") } // Commit generates a signer's nonces and commitment, to be used in the second FROST round. The internal nonce must diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index 21e6679..f238a6b 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -385,7 +385,7 @@ func TestConfiguration_PrepareVerifySignatureShare_BadNonVerifiedConfiguration(t SignerPublicKeys: publicKeyShares, } - if _, _, _, err := configuration.PrepareVerifySignatureShare(nil, nil); err == nil || + if _, _, _, err := configuration.PrepareSignatureShareVerification(nil, nil); err == nil || err.Error() != expectedErrorPrefix.Error() { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -405,7 +405,7 @@ func TestConfiguration_PrepareVerifySignatureShare_InvalidCommitments(t *testing coms[i] = s.Commit() } - if _, _, _, err := configuration.PrepareVerifySignatureShare(nil, coms[:1]); err == nil || + if _, _, _, err := configuration.PrepareSignatureShareVerification(nil, coms[:1]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } diff --git a/tests/misc_test.go b/tests/misc_test.go index a5d56ec..ca74832 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -435,7 +435,7 @@ func TestLambda_BadID(t *testing.T) { } // todo : what happens if the participant list is not vetted? - fmt.Println(internal.Lambda2(g, 1, polynomial).Hex()) + fmt.Println(internal.Lambda(g, 1, polynomial).Hex()) } func TestLambdaRegistry(t *testing.T) { From e89a3c607a2c9c38e84aff8682276a1335a514fd Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Fri, 30 Aug 2024 18:23:03 +0200 Subject: [PATCH 18/31] add tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 241 +++++------ coordinator.go | 22 +- frost.go | 10 + signer.go | 8 - tests/commitment_test.go | 377 ++++++++++++++++++ tests/configuration_test.go | 768 ++++++++++++++++++++++++++++++++++++ tests/frost_error_test.go | 756 +---------------------------------- tests/frost_test.go | 3 - tests/misc_test.go | 104 +---- tests/signer_test.go | 186 +++++++++ 10 files changed, 1478 insertions(+), 997 deletions(-) create mode 100644 tests/commitment_test.go create mode 100644 tests/configuration_test.go create mode 100644 tests/signer_test.go diff --git a/commitment.go b/commitment.go index 5d56a15..51592dd 100644 --- a/commitment.go +++ b/commitment.go @@ -38,35 +38,6 @@ type Commitment struct { Group group.Group } -// Validate returns an error if the commitment is -func (c *Commitment) Validate(g group.Group) error { - if c.Group != g { - return fmt.Errorf( - "commitment %d for participant %d has an unexpected ciphersuite: expected %s, got %s", - c.CommitmentID, - c.SignerID, - g, - c.Group, - ) - } - - generator := g.Base() - - if c.HidingNonceCommitment == nil || c.HidingNonceCommitment.IsIdentity() || - c.HidingNonceCommitment.Equal(generator) == 1 { - return fmt.Errorf("commitment %d for signer %d has an %w", c.CommitmentID, - c.SignerID, errHidingNonceCommitment) - } - - if c.BindingNonceCommitment == nil || c.BindingNonceCommitment.IsIdentity() || - c.BindingNonceCommitment.Equal(generator) == 1 { - return fmt.Errorf("commitment %d for signer %d has an %w", c.CommitmentID, - c.SignerID, errBindingNonceCommitment) - } - - return nil -} - // Copy returns a new Commitment struct populated with the same values as the receiver. func (c *Commitment) Copy() *Commitment { return &Commitment{ @@ -90,8 +61,6 @@ type CommitmentList []*Commitment // a > b and zero when a == b. func cmpID(a, b *Commitment) int { switch { - case a == nil || b == nil: - return 0 case a.SignerID < b.SignerID: // a < b return -1 case a.SignerID > b.SignerID: @@ -152,91 +121,6 @@ func (c CommitmentList) ParticipantsScalar() []*group.Scalar { }) } -func (c *Configuration) isSignerRegistered(sid uint64) bool { - for _, peer := range c.SignerPublicKeys { - if peer.ID == sid { - return true - } - } - - return false -} - -// ValidateCommitmentList returns an error if at least one of the following conditions is not met: -// - list length is within [threshold;max] -// - no signer identifier in commitments is 0 -// - no singer identifier in commitments is > max signers -// - no duplicated in signer identifiers -// - all commitment signer identifiers are registered in the configuration -func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error { - // Validate number of commitments. - length := uint64(len(commitments)) - - if length == 0 { - return fmt.Errorf("commitment list is empty") - } - - if length < c.Threshold { - return fmt.Errorf("too few commitments: expected at least %d but got %d", c.Threshold, length) - } - - if length > c.MaxSigners { - return fmt.Errorf("too many commitments: expected %d or less but got %d", c.MaxSigners, length) - } - - // set to detect duplication - set := make(map[uint64]struct{}, length) - - for i, commitment := range commitments { - if commitment == nil { - return fmt.Errorf("the commitment list has a nil commitment") - } - - if commitment.SignerID == 0 { - return fmt.Errorf("signer identifier for commitment %d is 0", commitment.CommitmentID) - } - - if commitment.SignerID > c.MaxSigners { - return fmt.Errorf( - "signer identifier %d for commitment %d is above allowed values (%d)", - commitment.SignerID, - commitment.CommitmentID, - c.MaxSigners, - ) - } - - // Check for duplicate participant entries. - if _, exists := set[commitment.SignerID]; exists { - return fmt.Errorf("commitment list contains multiple commitments of participant %d", commitment.SignerID) - } - - set[commitment.SignerID] = struct{}{} - - // Check general validity of the commitment. - if err := commitment.Validate(c.group); err != nil { - return err - } - - // List must be sorted, compare with the next commitment. - if uint64(i) <= length-2 { - if cmpID(commitment, commitments[i+1]) > 0 { - return fmt.Errorf("commitment list is not sorted by signer identifiers") - } - } - - // Validate that all commitments come from registered signers. - if !c.isSignerRegistered(commitment.SignerID) { - return fmt.Errorf( - "signer identifier %d for commitment %d is not registered in the configuration", - commitment.SignerID, - commitment.CommitmentID, - ) - } - } - - return nil -} - func (c CommitmentList) Encode() []byte { n := len(c) if n == 0 { @@ -359,3 +243,128 @@ func (c CommitmentList) groupCommitment(bf BindingFactors) *group.Element { return gc } + +func (c *Configuration) isSignerRegistered(sid uint64) bool { + for _, peer := range c.SignerPublicKeys { + if peer.ID == sid { + return true + } + } + + return false +} + +// ValidateCommitment returns an error if the commitment is not valid. +func (c *Configuration) ValidateCommitment(commitment *Commitment) error { + if commitment == nil { + return fmt.Errorf("the commitment list has a nil commitment") + } + + if commitment.SignerID == 0 { + return fmt.Errorf("signer identifier for commitment %d is 0", commitment.CommitmentID) + } + + if commitment.SignerID > c.MaxSigners { + return fmt.Errorf( + "signer identifier %d for commitment %d is above allowed values (%d)", + commitment.SignerID, + commitment.CommitmentID, + c.MaxSigners, + ) + } + + if commitment.Group != c.group { + return fmt.Errorf( + "commitment %d for participant %d has an unexpected ciphersuite: expected %s, got %d", + commitment.CommitmentID, + commitment.SignerID, + c.group, + commitment.Group, + ) + } + + generator := c.group.Base() + + if commitment.HidingNonceCommitment == nil || commitment.HidingNonceCommitment.IsIdentity() || + commitment.HidingNonceCommitment.Equal(generator) == 1 { + return fmt.Errorf("commitment %d for signer %d has an %w", commitment.CommitmentID, + commitment.SignerID, errHidingNonceCommitment) + } + + if commitment.BindingNonceCommitment == nil || commitment.BindingNonceCommitment.IsIdentity() || + commitment.BindingNonceCommitment.Equal(generator) == 1 { + return fmt.Errorf("commitment %d for signer %d has an %w", commitment.CommitmentID, + commitment.SignerID, errBindingNonceCommitment) + } + + // Validate that the commitment comes from a registered signer. + if !c.isSignerRegistered(commitment.SignerID) { + return fmt.Errorf( + "signer identifier %d for commitment %d is not registered in the configuration", + commitment.SignerID, + commitment.CommitmentID, + ) + } + + return nil +} + +func (c *Configuration) validateCommitmentListLength(commitments CommitmentList) error { + length := uint64(len(commitments)) + + if length == 0 { + return fmt.Errorf("commitment list is empty") + } + + if length < c.Threshold { + return fmt.Errorf("too few commitments: expected at least %d but got %d", c.Threshold, length) + } + + if length > c.MaxSigners { + return fmt.Errorf("too many commitments: expected %d or less but got %d", c.MaxSigners, length) + } + + return nil +} + +// ValidateCommitmentList returns an error if at least one of the following conditions is not met: +// - list length is within [threshold;max] +// - no signer identifier in commitments is 0 +// - no singer identifier in commitments is > max signers +// - no duplicated in signer identifiers +// - all commitment signer identifiers are registered in the configuration +func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error { + if err := c.validateCommitmentListLength(commitments); err != nil { + return err + } + + // set to detect duplication + set := make(map[uint64]struct{}, len(commitments)) + + for i, commitment := range commitments { + // Check general validity of the commitment. + if err := c.ValidateCommitment(commitment); err != nil { + return err + } + + // Check for duplicate participant entries. + if _, exists := set[commitment.SignerID]; exists { + return fmt.Errorf("commitment list contains multiple commitments of participant %d", commitment.SignerID) + } + + set[commitment.SignerID] = struct{}{} + + // List must be sorted, compare with the next commitment. + if i <= len(commitments)-2 { + if commitments[i+1] == nil { + return fmt.Errorf("the commitment list has a nil commitment") + } + + if cmpID(commitment, commitments[i+1]) > 0 { + return fmt.Errorf("commitment list is not sorted by signer identifiers") + } + } + } + + return nil +} diff --git a/coordinator.go b/coordinator.go index 4c745fc..726ee8a 100644 --- a/coordinator.go +++ b/coordinator.go @@ -58,8 +58,12 @@ func (c *Configuration) AggregateSignatures( // Aggregate signatures z := group.Group(c.Ciphersuite).NewScalar() - for _, share := range sigShares { - z.Add(share.SignatureShare) + for _, sigShare := range sigShares { + if err := c.validateSignatureShareLight(sigShare); err != nil { + return nil, err + } + + z.Add(sigShare.SignatureShare) } signature := &Signature{ @@ -115,23 +119,13 @@ func (c *Configuration) PrepareSignatureShareVerification(message []byte, return groupCommitment, bindingFactors, participants, nil } -func (c *Configuration) getSignerPubKey(id uint64) *group.Element { - for _, pks := range c.SignerPublicKeys { - if pks.ID == id { - return pks.PublicKey - } - } - - return nil -} - func (c *Configuration) validateSignatureShareLight(sigShare *SignatureShare) error { if sigShare == nil { return errors.New("nil signature share") } if sigShare.SignatureShare == nil || sigShare.SignatureShare.IsZero() { - return errors.New("invalid signature share (nil or zero)") + return errors.New("invalid signature share (nil or zero scalar)") } return nil @@ -155,7 +149,7 @@ func (c *Configuration) validateSignatureShareExtensive(sigShare *SignatureShare } if sigShare.Group != c.group { - return fmt.Errorf("signature share has invalid group parameter, want %s got %s", c.group, sigShare.Group) + return fmt.Errorf("signature share has invalid group parameter, want %s got %d", c.group, sigShare.Group) } if c.getSignerPubKey(sigShare.SignerIdentifier) == nil { diff --git a/frost.go b/frost.go index 2704cc5..e107121 100644 --- a/frost.go +++ b/frost.go @@ -216,6 +216,16 @@ func (c *Configuration) Init() error { return nil } +func (c *Configuration) getSignerPubKey(id uint64) *group.Element { + for _, pks := range c.SignerPublicKeys { + if pks.ID == id { + return pks.PublicKey + } + } + + return nil +} + func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { if !c.verified { if err := c.Init(); err != nil { diff --git a/signer.go b/signer.go index 36fd895..1f14a12 100644 --- a/signer.go +++ b/signer.go @@ -11,7 +11,6 @@ package frost import ( "crypto/rand" "encoding/binary" - "errors" "fmt" group "github.com/bytemare/crypto" @@ -158,13 +157,6 @@ func (s *Signer) verifyNonces(com *Commitment) error { // VerifyCommitmentList checks for the Commitment list integrity and the signer's commitment. This function must not // return an error for Sign to succeed. func (s *Signer) VerifyCommitmentList(commitments CommitmentList) error { - // Due diligence check that no signer id == 0. - if s.KeyShare.ID == 0 { - return errors.New("signer identifier is 0 (invalid)") - } - - commitments.Sort() - // Validate general consistency of the commitment list. if err := s.Configuration.ValidateCommitmentList(commitments); err != nil { return fmt.Errorf("invalid list of commitments: %w", err) diff --git a/tests/commitment_test.go b/tests/commitment_test.go new file mode 100644 index 0000000..991ad63 --- /dev/null +++ b/tests/commitment_test.go @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 frost_test + +import ( + "fmt" + "slices" + "strings" + "testing" + + "github.com/bytemare/frost" +) + +func TestCommitment_Validate_NilCommitment(t *testing.T) { + expectedErrorPrefix := "the commitment list has a nil commitment" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + configuration, _ := fullSetup(t, tt) + + if err := configuration.ValidateCommitment(nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitment_Validate_SignerIDs0(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + configuration, signers := fullSetup(t, tt) + commitment := signers[0].Commit() + commitment.SignerID = 0 + expectedErrorPrefix := fmt.Sprintf("signer identifier for commitment %d is 0", commitment.CommitmentID) + + if err := configuration.ValidateCommitment(commitment); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitment_Validate_SignerIDInvalid(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + configuration, signers := fullSetup(t, tt) + commitment := signers[0].Commit() + commitment.SignerID = tt.maxSigners + 1 + expectedErrorPrefix := fmt.Sprintf( + "signer identifier %d for commitment %d is above allowed values (%d)", + commitment.SignerID, + commitment.CommitmentID, + tt.maxSigners, + ) + + if err := configuration.ValidateCommitment(commitment); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitment_Validate_WrongGroup(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, signers := fullSetup(t, tt) + com := signers[0].Commit() + com.Group = 2 + expectedErrorPrefix := fmt.Sprintf( + "commitment %d for participant 1 has an unexpected ciphersuite: expected ristretto255_XMD:SHA-512_R255MAP_RO_, got 2", + com.CommitmentID, + ) + + if err := configuration.ValidateCommitment(com); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitment_Validate_BadHidingNonceCommitment(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, signers := fullSetup(t, tt) + com := signers[0].Commit() + expectedErrorPrefix := fmt.Sprintf( + "commitment %d for signer %d has an invalid hiding nonce commitment (nil, identity, or generator)", + com.CommitmentID, + com.SignerID, + ) + + // generator + com.HidingNonceCommitment.Base() + if err := configuration.ValidateCommitment(com); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // point at infinity + com.HidingNonceCommitment.Identity() + if err := configuration.ValidateCommitment(com); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // nil + com.HidingNonceCommitment = nil + if err := configuration.ValidateCommitment(com); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitment_Validate_BadBindingNonceCommitment(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, signers := fullSetup(t, tt) + com := signers[0].Commit() + expectedErrorPrefix := fmt.Sprintf( + "commitment %d for signer %d has an invalid binding nonce commitment (nil, identity, or generator)", + com.CommitmentID, + com.SignerID, + ) + + // generator + com.BindingNonceCommitment.Base() + if err := configuration.ValidateCommitment(com); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // point at infinity + com.BindingNonceCommitment.Identity() + if err := configuration.ValidateCommitment(com); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // nil + com.BindingNonceCommitment = nil + if err := configuration.ValidateCommitment(com); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Sort(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(frost.CommitmentList, len(signers)) + + // signer A < signer B + coms[0] = signers[0].Commit() + coms[1] = signers[1].Commit() + coms[2] = signers[2].Commit() + + coms.Sort() + + if !coms.IsSorted() { + t.Fatal("expected sorted") + } + + // signer B > singer A + coms[0] = signers[1].Commit() + coms[1] = signers[0].Commit() + + coms.Sort() + + if !coms.IsSorted() { + t.Fatal("expected sorted") + } + + // signer B > singer A + coms[0] = signers[0].Commit() + coms[1] = signers[2].Commit() + coms[2] = signers[2].Commit() + + coms.Sort() + + if !coms.IsSorted() { + t.Fatal("expected sorted") + } + }) +} + +func TestCommitmentList_Validate_NoCommitments(t *testing.T) { + expectedErrorPrefix := "commitment list is empty" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if err := configuration.ValidateCommitmentList(nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + if err := configuration.ValidateCommitmentList(frost.CommitmentList{}); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Validate_InsufficientCommitments(t *testing.T) { + expectedErrorPrefix := "too few commitments: expected at least 2 but got 1" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if err := configuration.ValidateCommitmentList(coms[:tt.threshold-1]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Validate_TooManyCommitments(t *testing.T) { + expectedErrorPrefix := "too many commitments: expected 3 or less but got 4" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)+1) + + for i, s := range signers { + coms[i] = s.Commit() + } + coms[len(signers)] = coms[0].Copy() + + if err := configuration.ValidateCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Validate_DuplicateSignerIDs(t *testing.T) { + expectedErrorPrefix := "commitment list contains multiple commitments of participant 2" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + coms[2] = coms[1].Copy() + + if err := configuration.ValidateCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Validate_InvalidCommitment(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + coms[2].BindingNonceCommitment.Base() + expectedErrorPrefix := fmt.Sprintf( + "commitment %d for signer %d has an invalid binding nonce commitment (nil, identity, or generator)", + coms[2].CommitmentID, + coms[2].SignerID, + ) + + if err := configuration.ValidateCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Validate_NotSorted(t *testing.T) { + expectedErrorPrefix := "commitment list is not sorted by signer identifiers" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + coms[1].SignerID, coms[2].SignerID = coms[2].SignerID, coms[1].SignerID + + if err := configuration.ValidateCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_Validate_UnregisteredKey(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + configuration.SignerPublicKeys = slices.Delete(configuration.SignerPublicKeys, 1, 2) + expectedErrorPrefix := fmt.Sprintf( + "signer identifier %d for commitment %d is not registered in the configuration", + coms[1].SignerID, + coms[1].CommitmentID, + ) + + if err := configuration.ValidateCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestCommitmentList_ParticipantsScalar_Empty(t *testing.T) { + com := frost.CommitmentList{} + if out := com.ParticipantsScalar(); out != nil { + t.Fatal("unexpected output") + } + + com = frost.CommitmentList{nil, nil} + if out := com.ParticipantsScalar(); out != nil { + t.Fatal("unexpected output") + } +} diff --git a/tests/configuration_test.go b/tests/configuration_test.go new file mode 100644 index 0000000..45221a6 --- /dev/null +++ b/tests/configuration_test.go @@ -0,0 +1,768 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 frost_test + +import ( + "fmt" + "slices" + "strings" + "testing" + + "github.com/bytemare/frost" + "github.com/bytemare/frost/debug" + "github.com/bytemare/frost/internal" +) + +func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_Threshold_0(t *testing.T) { + expectedErrorPrefix := "threshold is 0 or higher than maxSigners" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: 0, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_Threshold_Max(t *testing.T) { + expectedErrorPrefix := "threshold is 0 or higher than maxSigners" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.maxSigners + 1, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { + expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: nil, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { + expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: test.ECGroup().NewElement(), + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { + expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: test.ECGroup().Base(), + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { + expectedErrorPrefix := "invalid number of public keys (lower than threshold or above maximum)" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + // nil + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: nil, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // empty + configuration.SignerPublicKeys = []*frost.PublicKeyShare{} + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // too few + configuration.SignerPublicKeys = publicKeyShares[:threshold-1] + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // too many + configuration.SignerPublicKeys = append(publicKeyShares, &frost.PublicKeyShare{}) + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { + expectedErrorPrefix := "empty public key share at index 1" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + publicKeyShares[threshold-1] = nil + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { + expectedErrorPrefix := "invalid signer public key (nil, identity, or generator) for participant 2" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + // nil pk + configuration.SignerPublicKeys[threshold-1].PublicKey = nil + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // identity + configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().NewElement() + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + // generator + configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().Base() + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignerPublicKeys_Duplicate_Identifiers(t *testing.T) { + expectedErrorPrefix := "found duplicate identifier for signer 1" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + // duplicate id + id1 := configuration.SignerPublicKeys[0].ID + configuration.SignerPublicKeys[1].ID = id1 + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) { + expectedErrorPrefix := "found duplicate public keys for signers 2 and 1" + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + // duplicate id + pk1 := configuration.SignerPublicKeys[0].PublicKey.Copy() + configuration.SignerPublicKeys[1].PublicKey = pk1 + + if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_Signer_NotVerified(t *testing.T) { + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if _, err := configuration.Signer(keyShares[0]); err != nil { + t.Fatal(err) + } +} + +func TestConfiguration_Signer_BadConfig(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if _, err := configuration.Signer(keyShares[0]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_PrepareSignatureShareVerification_BadNonVerifiedConfiguration(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if _, _, _, err := configuration.PrepareSignatureShareVerification(nil, nil); err == nil || + err.Error() != expectedErrorPrefix.Error() { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_PrepareSignatureShareVerification_InvalidCommitments(t *testing.T) { + expectedErrorPrefix := "invalid list of commitments: too few commitments: expected at least 2 but got 1" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if _, _, _, err := configuration.PrepareSignatureShareVerification(nil, coms[:1]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_BadPrep(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + + ciphersuite := frost.Ristretto255 + threshold := uint64(2) + maxSigners := uint64(3) + + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeys: publicKeyShares, + } + + if err := configuration.VerifySignatureShare(nil, nil, nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_NilShare(t *testing.T) { + expectedErrorPrefix := "nil signature share" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if err := configuration.VerifySignatureShare(nil, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_SignerID0(t *testing.T) { + expectedErrorPrefix := "signature share's signer identifier is 0 (invalid)" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(message, coms) + if err != nil { + t.Fatal(err) + } + + sigShare.SignerIdentifier = 0 + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_InvalidSignerID(t *testing.T) { + expectedErrorPrefix := "signature share has invalid ID 4, above authorized range [1:3]" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(message, coms) + if err != nil { + t.Fatal(err) + } + + sigShare.SignerIdentifier = tt.maxSigners + 1 + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_BadGroup(t *testing.T) { + expectedErrorPrefix := "signature share has invalid group parameter, want ristretto255_XMD:SHA-512_R255MAP_RO_ got 2" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(message, coms) + if err != nil { + t.Fatal(err) + } + + sigShare.Group = 2 + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_MissingPublicKey(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(message, coms) + if err != nil { + t.Fatal(err) + } + + configuration.SignerPublicKeys = slices.Delete(configuration.SignerPublicKeys, 0, 1) + expectedErrorPrefix := fmt.Sprintf("no public key registered for signer 1") + + if err := configuration.VerifySignatureShare(sigShare, message, coms[1:]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_MissingCommitment(t *testing.T) { + expectedErrorPrefix := "commitment for signer 1 is missing" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 4, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(message, coms) + if err != nil { + t.Fatal(err) + } + + if err := configuration.VerifySignatureShare(sigShare, message, coms[1:]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_BadCommitment_BadSignerID(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare, err := signers[0].Sign(message, coms) + if err != nil { + t.Fatal(err) + } + + coms[1].SignerID = 0 + expectedErrorPrefix := fmt.Sprintf( + "invalid list of commitments: signer identifier for commitment %d is 0", + coms[1].CommitmentID, + ) + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_VerifySignatureShare_InvalidSignatureShare(t *testing.T) { + expectedErrorPrefix := "invalid signature share for signer 1" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShare := &frost.SignatureShare{ + SignatureShare: tt.Ciphersuite.ECGroup().NewScalar().Random(), + SignerIdentifier: 1, + Group: tt.Ciphersuite.ECGroup(), + } + + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_AggregateSignatures_InvalidCommitments(t *testing.T) { + expectedErrorPrefix := "invalid list of commitments: too few commitments: expected at least 3 but got 2" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 5, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if _, err := configuration.AggregateSignatures(nil, nil, coms[:2], false); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_AggregateSignatures_BadSigShare1(t *testing.T) { + expectedErrorPrefix := "invalid signature share for signer 2" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + + coms := make(frost.CommitmentList, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShares := make([]*frost.SignatureShare, len(signers)) + for i, s := range signers { + var err error + sigShares[i], err = s.Sign(message, coms) + if err != nil { + t.Fatal(err) + } + } + + sigShares[1].SignatureShare.Random() + + if _, err := configuration.AggregateSignatures(message, sigShares, coms, true); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_AggregateSignatures_BadSigShare2(t *testing.T) { + expectedErrorPrefix := "nil signature share" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + + coms := make(frost.CommitmentList, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShares := make([]*frost.SignatureShare, len(signers)) + for i, s := range signers { + var err error + sigShares[i], err = s.Sign(message, coms) + if err != nil { + t.Fatal(err) + } + } + + sigShares[1] = nil + + if _, err := configuration.AggregateSignatures(message, sigShares, coms, false); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_AggregateSignatures_BadSigShare3(t *testing.T) { + expectedErrorPrefix := "invalid signature share (nil or zero scalar)" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + message := []byte("message") + configuration, signers := fullSetup(t, tt) + + coms := make(frost.CommitmentList, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + sigShares := make([]*frost.SignatureShare, len(signers)) + for i, s := range signers { + var err error + sigShares[i], err = s.Sign(message, coms) + if err != nil { + t.Fatal(err) + } + } + + sigShares[1].SignatureShare.Zero() + + if _, err := configuration.AggregateSignatures(message, sigShares, coms, false); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index f238a6b..eb6a74a 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -13,594 +13,11 @@ import ( "strings" "testing" - group "github.com/bytemare/crypto" - "github.com/bytemare/frost" - "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" ) -func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite - - testAll(t, func(t *testing.T, test *tableTest) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( - test.Ciphersuite, - nil, - test.threshold, - test.maxSigners, - ) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - }) -} - -func TestConfiguration_Verify_Threshold_0(t *testing.T) { - expectedErrorPrefix := "threshold is 0 or higher than maxSigners" - - testAll(t, func(t *testing.T, test *tableTest) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( - test.Ciphersuite, - nil, - test.threshold, - test.maxSigners, - ) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: 0, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - }) -} - -func TestConfiguration_Verify_Threshold_Max(t *testing.T) { - expectedErrorPrefix := "threshold is 0 or higher than maxSigners" - - testAll(t, func(t *testing.T, test *tableTest) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( - test.Ciphersuite, - nil, - test.threshold, - test.maxSigners, - ) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.maxSigners + 1, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - }) -} - -func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" - - testAll(t, func(t *testing.T, test *tableTest) { - keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: nil, - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - }) -} - -func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" - - testAll(t, func(t *testing.T, test *tableTest) { - keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: test.ECGroup().NewElement(), - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - }) -} - -func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" - - testAll(t, func(t *testing.T, test *tableTest) { - keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: test.ECGroup().Base(), - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - }) -} - -func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { - expectedErrorPrefix := "invalid number of public keys (lower than threshold or above maximum)" - - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - // nil - configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: nil, - } - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // empty - configuration.SignerPublicKeys = []*frost.PublicKeyShare{} - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // too few - configuration.SignerPublicKeys = publicKeyShares[:threshold-1] - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // too many - configuration.SignerPublicKeys = append(publicKeyShares, &frost.PublicKeyShare{}) - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { - expectedErrorPrefix := "empty public key share at index 1" - - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - publicKeyShares[threshold-1] = nil - - configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { - expectedErrorPrefix := "invalid signer public key (nil, identity, or generator) for participant 2" - - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - // nil pk - configuration.SignerPublicKeys[threshold-1].PublicKey = nil - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // identity - configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().NewElement() - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // generator - configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().Base() - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignerPublicKeys_Duplicate_Identifiers(t *testing.T) { - expectedErrorPrefix := "found duplicate identifier for signer 1" - - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - // duplicate id - id1 := configuration.SignerPublicKeys[0].ID - configuration.SignerPublicKeys[1].ID = id1 - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) { - expectedErrorPrefix := "found duplicate public keys for signers 2 and 1" - - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - // duplicate id - pk1 := configuration.SignerPublicKeys[0].PublicKey.Copy() - configuration.SignerPublicKeys[1].PublicKey = pk1 - - if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_Signer_NotVerified(t *testing.T) { - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if _, err := configuration.Signer(keyShares[0]); err != nil { - t.Fatal(err) - } -} - -func TestConfiguration_Signer_BadConfig(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if _, err := configuration.Signer(keyShares[0]); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_PrepareVerifySignatureShare_BadNonVerifiedConfiguration(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if _, _, _, err := configuration.PrepareSignatureShareVerification(nil, nil); err == nil || - err.Error() != expectedErrorPrefix.Error() { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_PrepareVerifySignatureShare_InvalidCommitments(t *testing.T) { - expectedErrorPrefix := "invalid list of commitments: too few commitments: expected at least 2 but got 1" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - if _, _, _, err := configuration.PrepareSignatureShareVerification(nil, coms[:1]); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignatureShare_BadPrep(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite - - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - - configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, - } - - if err := configuration.VerifySignatureShare(nil, nil, nil); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignatureShare_MissingCommitment(t *testing.T) { - expectedErrorPrefix := "commitment for signer 1 is missing" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 4, - } - message := []byte("message") - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - sigShare, err := signers[0].Sign(message, coms) - if err != nil { - t.Fatal(err) - } - - if err := configuration.VerifySignatureShare(sigShare, message, coms[1:]); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignatureShare_MissingPublicKey(t *testing.T) { - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - message := []byte("message") - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - sigShare, err := signers[0].Sign(message, coms) - if err != nil { - t.Fatal(err) - } - - configuration.SignerPublicKeys[0].ID = tt.maxSigners + 1 - expectedErrorPrefix := fmt.Sprintf( - "invalid list of commitments: signer identifier %d for commitment %d is not registered in the configuration", - 1, - coms[0].CommitmentID, - ) - - if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignatureShare_BadSignerID(t *testing.T) { - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - message := []byte("message") - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - sigShare, err := signers[0].Sign(message, coms) - if err != nil { - t.Fatal(err) - } - - coms[1].SignerID = 0 - expectedErrorPrefix := fmt.Sprintf( - "invalid list of commitments: signer identifier for commitment %d is 0", - coms[1].CommitmentID, - ) - - if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_VerifySignatureShare_InvalidSignatureShare(t *testing.T) { - expectedErrorPrefix := "invalid signature share for signer 1" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - message := []byte("message") - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - sigShare := &frost.SignatureShare{ - SignatureShare: tt.Ciphersuite.ECGroup().NewScalar().Random(), - SignerIdentifier: 1, - Group: tt.Ciphersuite.ECGroup(), - } - - if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_AggregateSignatures_BadSigShare(t *testing.T) { - expectedErrorPrefix := "invalid signature share for signer 2" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - message := []byte("message") - configuration, signers := fullSetup(t, tt) - - coms := make(frost.CommitmentList, len(signers)) - for i, s := range signers { - coms[i] = s.Commit() - } - - sigShares := make([]*frost.SignatureShare, len(signers)) - for i, s := range signers { - var err error - sigShares[i], err = s.Sign(message, coms) - if err != nil { - t.Fatal(err) - } - } - - sigShares[1].SignatureShare.Random() - - if _, err := configuration.AggregateSignatures(message, sigShares, coms, true); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_AggregateSignatures_NonVerifiedCommitments(t *testing.T) { - expectedErrorPrefix := "invalid list of commitments: too few commitments: expected at least 3 but got 2" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 3, - maxSigners: 5, - } - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - if _, err := configuration.AggregateSignatures(nil, nil, coms[:2], false); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } +func TestMaliciousSigner(t *testing.T) { } func TestVerifySignature_BadCiphersuite(t *testing.T) { @@ -631,177 +48,6 @@ func TestVerifySignature_InvalidSignature(t *testing.T) { }) } -func TestCommitment_Validate_WrongGroup(t *testing.T) { - expectedErrorPrefix := "commitment 1 for participant 1 has an unexpected ciphersuite: expected ristretto255_XMD:SHA-512_R255MAP_RO_, got %!s(PANIC=String method: invalid group identifier)" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - signer := makeSigners(t, tt)[0] - com := signer.Commit() - signer.NonceCommitments[1] = signer.NonceCommitments[com.CommitmentID] - com.CommitmentID = 1 - com.Group = 2 - - if err := com.Validate(group.Ristretto255Sha512); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestCommitment_Validate_BadHidingNonceCommitment(t *testing.T) { - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - signer := makeSigners(t, tt)[0] - com := signer.Commit() - expectedErrorPrefix := fmt.Sprintf( - "commitment %d for signer %d has an invalid hiding nonce commitment (nil, identity, or generator)", - com.CommitmentID, - com.SignerID, - ) - - // generator - com.HidingNonceCommitment.Base() - if err := com.Validate(group.Ristretto255Sha512); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // point at infinity - com.HidingNonceCommitment.Identity() - if err := com.Validate(group.Ristretto255Sha512); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // nil - com.HidingNonceCommitment = nil - if err := com.Validate(group.Ristretto255Sha512); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestCommitment_Validate_BadBindingNonceCommitment(t *testing.T) { - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - signer := makeSigners(t, tt)[0] - com := signer.Commit() - expectedErrorPrefix := fmt.Sprintf( - "commitment %d for signer %d has an invalid binding nonce commitment (nil, identity, or generator)", - com.CommitmentID, - com.SignerID, - ) - - // generator - com.BindingNonceCommitment.Base() - if err := com.Validate(group.Ristretto255Sha512); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // point at infinity - com.BindingNonceCommitment.Identity() - if err := com.Validate(group.Ristretto255Sha512); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } - - // nil - com.BindingNonceCommitment = nil - if err := com.Validate(group.Ristretto255Sha512); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestCommitmentList_Validate_InsufficientCommitments(t *testing.T) { - expectedErrorPrefix := "too few commitments: expected at least 2 but got 1" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - if err := configuration.ValidateCommitmentList(coms[:tt.threshold-1]); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestCommitmentList_Validate_DuplicateSignerIDs(t *testing.T) { - expectedErrorPrefix := "commitment list contains multiple commitments of participant 2" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 3, - maxSigners: 4, - } - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - coms[2] = coms[1].Copy() - - if err := configuration.ValidateCommitmentList(coms); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestCommitmentList_Validate_InvalidCommitment(t *testing.T) { - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 3, - maxSigners: 4, - } - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - coms[2].BindingNonceCommitment.Base() - expectedErrorPrefix := fmt.Sprintf( - "commitment %d for signer %d has an invalid binding nonce commitment (nil, identity, or generator)", - coms[2].CommitmentID, - coms[2].SignerID, - ) - - if err := configuration.ValidateCommitmentList(coms); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestCommitmentList_ParticipantsScalar_Empty(t *testing.T) { - com := frost.CommitmentList{} - if out := com.ParticipantsScalar(); out != nil { - t.Fatal("unexpected output") - } - - com = frost.CommitmentList{nil, nil} - if out := com.ParticipantsScalar(); out != nil { - t.Fatal("unexpected output") - } -} - func TestSigner_Sign_NoNonceForCommitmentID(t *testing.T) { message := []byte("message") tt := &tableTest{ diff --git a/tests/frost_test.go b/tests/frost_test.go index 06c4a34..9877d61 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -149,6 +149,3 @@ func testAll(t *testing.T, f func(*testing.T, *tableTest)) { }) } } - -func TestMaliciousSigner(t *testing.T) { -} diff --git a/tests/misc_test.go b/tests/misc_test.go index ca74832..ab2724c 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -22,45 +22,6 @@ import ( "github.com/bytemare/frost/internal" ) -func TestCommitmentList_Sort(t *testing.T) { - testAll(t, func(t *testing.T, test *tableTest) { - signers := makeSigners(t, test) - coms := make(frost.CommitmentList, len(signers)) - - // signer A < signer B - coms[0] = signers[0].Commit() - coms[1] = signers[1].Commit() - coms[2] = signers[2].Commit() - - coms.Sort() - - if !coms.IsSorted() { - t.Fatal("expected sorted") - } - - // signer B > singer A - coms[0] = signers[1].Commit() - coms[1] = signers[0].Commit() - - coms.Sort() - - if !coms.IsSorted() { - t.Fatal("expected sorted") - } - - // signer B > singer A - coms[0] = signers[0].Commit() - coms[1] = signers[2].Commit() - coms[2] = signers[2].Commit() - - coms.Sort() - - if !coms.IsSorted() { - t.Fatal("expected sorted") - } - }) -} - func verifyTrustedDealerKeygen( t *testing.T, test *tableTest, @@ -430,69 +391,10 @@ func TestLambda_BadID(t *testing.T) { g := group.Ristretto255Sha512 polynomial := []*group.Scalar{ g.NewScalar().SetUInt64(1), - g.NewScalar().SetUInt64(0), - g.NewScalar().SetUInt64(1), + g.NewScalar().SetUInt64(2), + g.NewScalar().SetUInt64(3), } // todo : what happens if the participant list is not vetted? - fmt.Println(internal.Lambda(g, 1, polynomial).Hex()) -} - -func TestLambdaRegistry(t *testing.T) { - g := group.Ristretto255Sha512 - id := uint64(2) - participants := []uint64{1, 2, 3, 4} - lambdas := make(internal.LambdaRegistry) - - // Get should return nil - if lambda := lambdas.Get(participants); lambda != nil { - t.Fatal("unexpected result") - } - - // Create a new entry - lambda := lambdas.New(g, id, participants) - - if lambda == nil { - t.Fatal("unexpected result") - } - - // Getting the same entry - lambda2 := lambdas.Get(participants) - if lambda.Equal(lambda2) != 1 { - t.Fatal("expected equality") - } - - lambda3 := lambdas.GetOrNew(g, id, participants) - - if lambda.Equal(lambda3) != 1 { - t.Fatal("expected equality") - } - - // Getting another entry must result in another returned value - lambda4 := lambdas.GetOrNew(g, id, participants[:3]) - - if lambda.Equal(lambda4) == 1 { - t.Fatal("unexpected equality") - } - - lambda5 := lambdas.GetOrNew(g, id, participants[:3]) - - if lambda4.Equal(lambda5) != 1 { - t.Fatal("expected equality") - } - - // Removing and checking for the same entry - lambdas.Delete(participants) - if lambda = lambdas.Get(participants); lambda != nil { - t.Fatal("unexpected result") - } - - // Setting must return the same value - lambda6 := g.NewScalar().Random() - lambdas.Set(participants, lambda6) - lambda7 := lambdas.Get(participants) - - if lambda6.Equal(lambda7) != 1 { - t.Fatal("expected equality") - } + fmt.Println(internal.Lambda(g, 4, polynomial).Hex()) } diff --git a/tests/signer_test.go b/tests/signer_test.go new file mode 100644 index 0000000..7601658 --- /dev/null +++ b/tests/signer_test.go @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (C) 2024 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 frost_test + +import ( + "fmt" + "strings" + "testing" + + group "github.com/bytemare/crypto" + + "github.com/bytemare/frost" + "github.com/bytemare/frost/internal" +) + +func TestLambdaRegistry(t *testing.T) { + g := group.Ristretto255Sha512 + id := uint64(2) + participants := []uint64{1, 2, 3, 4} + lambdas := make(internal.LambdaRegistry) + + // Get should return nil + if lambda := lambdas.Get(participants); lambda != nil { + t.Fatal("unexpected result") + } + + // Create a new entry + lambda := lambdas.New(g, id, participants) + + if lambda == nil { + t.Fatal("unexpected result") + } + + // Getting the same entry + lambda2 := lambdas.Get(participants) + if lambda.Equal(lambda2) != 1 { + t.Fatal("expected equality") + } + + lambda3 := lambdas.GetOrNew(g, id, participants) + + if lambda.Equal(lambda3) != 1 { + t.Fatal("expected equality") + } + + // Getting another entry must result in another returned value + lambda4 := lambdas.GetOrNew(g, id, participants[:3]) + + if lambda.Equal(lambda4) == 1 { + t.Fatal("unexpected equality") + } + + lambda5 := lambdas.GetOrNew(g, id, participants[:3]) + + if lambda4.Equal(lambda5) != 1 { + t.Fatal("expected equality") + } + + // Removing and checking for the same entry + lambdas.Delete(participants) + if lambda = lambdas.Get(participants); lambda != nil { + t.Fatal("unexpected result") + } + + // Setting must return the same value + lambda6 := g.NewScalar().Random() + lambdas.Set(participants, lambda6) + lambda7 := lambdas.Get(participants) + + if lambda6.Equal(lambda7) != 1 { + t.Fatal("expected equality") + } +} + +func TestSigner_VerifyCommitmentList_InvalidCommitmentList(t *testing.T) { + expectedErrorPrefix := "invalid list of commitments: commitment list is empty" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + _, signers := fullSetup(t, tt) + + if err := signers[0].VerifyCommitmentList(nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestSigner_VerifyCommitmentList_MissingCommitment(t *testing.T) { + expectedErrorPrefix := "signer identifier 1 not found in the commitment list" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + _, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + if err := signers[0].VerifyCommitmentList(coms[1:]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestSigner_VerifyCommitmentList_MissingNonce(t *testing.T) { + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + _, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + delete(signers[0].NonceCommitments, coms[0].CommitmentID) + expectedErrorPrefix := fmt.Sprintf( + "the commitment identifier %d for signer %d in the commitments is unknown to the signer", + coms[0].CommitmentID, + coms[0].SignerID, + ) + + if err := signers[0].VerifyCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestSigner_VerifyCommitmentList_BadHidingNonce(t *testing.T) { + expectedErrorPrefix := "invalid hiding nonce in commitment list for signer 1" + + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + _, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + signers[0].NonceCommitments[coms[0].CommitmentID].HidingNonceCommitment.Base() + + if err := signers[0].VerifyCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestSigner_VerifyCommitmentList_BadBindingNonce(t *testing.T) { + expectedErrorPrefix := "invalid binding nonce in commitment list for signer 1" + + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + _, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + signers[0].NonceCommitments[coms[0].CommitmentID].BindingNonceCommitment.Base() + + if err := signers[0].VerifyCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} From 973cc2fe1cabe121922569d64981d6e62010a6e0 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Sun, 1 Sep 2024 20:48:55 +0200 Subject: [PATCH 19/31] some refactor and add tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 48 ++++----- coordinator.go | 18 +--- encoding.go | 159 ++++++++++++++++------------- frost.go | 83 +++++++++------ internal/lambda.go | 31 ++++-- tests/commitment_test.go | 63 ++++++++---- tests/configuration_test.go | 197 +++++++++++++++++++++++++++--------- tests/encoding_test.go | 9 +- 8 files changed, 390 insertions(+), 218 deletions(-) diff --git a/commitment.go b/commitment.go index 51592dd..b190a02 100644 --- a/commitment.go +++ b/commitment.go @@ -25,8 +25,6 @@ var ( errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") errInvalidCiphersuite = errors.New("ciphersuite not available") errInvalidLength = errors.New("invalid encoding length") - errHidingNonceCommitment = errors.New("invalid hiding nonce commitment (nil, identity, or generator)") - errBindingNonceCommitment = errors.New("invalid binding nonce commitment (nil, identity, or generator)") ) // Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. @@ -49,11 +47,6 @@ func (c *Commitment) Copy() *Commitment { } } -// EncodedSize returns the byte size of the output of Encode(). -func EncodedSize(g group.Group) uint64 { - return 1 + 8 + 8 + 2*uint64(g.ElementLength()) -} - // CommitmentList is a sortable list of commitments with search functions. type CommitmentList []*Commitment @@ -128,7 +121,7 @@ func (c CommitmentList) Encode() []byte { } g := c[0].Group - size := 1 + 8 + uint64(n)*EncodedSize(g) + size := 1 + 8 + uint64(n)*encodedLength(encCommitment, g) out := make([]byte, 9, size) out[0] = byte(g) binary.LittleEndian.PutUint64(out[1:9], uint64(n)) @@ -151,7 +144,7 @@ func DecodeList(data []byte) (CommitmentList, error) { } n := binary.LittleEndian.Uint64(data[1:9]) - es := EncodedSize(g) + es := encodedLength(encCommitment, g) size := 1 + 8 + n*es if uint64(len(data)) != size { @@ -260,17 +253,8 @@ func (c *Configuration) ValidateCommitment(commitment *Commitment) error { return fmt.Errorf("the commitment list has a nil commitment") } - if commitment.SignerID == 0 { - return fmt.Errorf("signer identifier for commitment %d is 0", commitment.CommitmentID) - } - - if commitment.SignerID > c.MaxSigners { - return fmt.Errorf( - "signer identifier %d for commitment %d is above allowed values (%d)", - commitment.SignerID, - commitment.CommitmentID, - c.MaxSigners, - ) + if err := c.validateIdentifier(commitment.SignerID); err != nil { + return fmt.Errorf("invalid identifier for signer in commitment %d, the %w", commitment.CommitmentID, err) } if commitment.Group != c.group { @@ -283,18 +267,22 @@ func (c *Configuration) ValidateCommitment(commitment *Commitment) error { ) } - generator := c.group.Base() - - if commitment.HidingNonceCommitment == nil || commitment.HidingNonceCommitment.IsIdentity() || - commitment.HidingNonceCommitment.Equal(generator) == 1 { - return fmt.Errorf("commitment %d for signer %d has an %w", commitment.CommitmentID, - commitment.SignerID, errHidingNonceCommitment) + if err := c.validateGroupElement(commitment.HidingNonceCommitment); err != nil { + return fmt.Errorf( + "invalid commitment %d for signer %d, the hiding nonce commitment %w", + commitment.CommitmentID, + commitment.SignerID, + err, + ) } - if commitment.BindingNonceCommitment == nil || commitment.BindingNonceCommitment.IsIdentity() || - commitment.BindingNonceCommitment.Equal(generator) == 1 { - return fmt.Errorf("commitment %d for signer %d has an %w", commitment.CommitmentID, - commitment.SignerID, errBindingNonceCommitment) + if err := c.validateGroupElement(commitment.BindingNonceCommitment); err != nil { + return fmt.Errorf( + "invalid commitment %d for signer %d, the binding nonce commitment %w", + commitment.CommitmentID, + commitment.SignerID, + err, + ) } // Validate that the commitment comes from a registered signer. diff --git a/coordinator.go b/coordinator.go index 726ee8a..975b3f7 100644 --- a/coordinator.go +++ b/coordinator.go @@ -42,7 +42,7 @@ func (c *Configuration) AggregateSignatures( commitments CommitmentList, verify bool, ) (*Signature, error) { - groupCommitment, bindingFactors, participants, err := c.PrepareSignatureShareVerification(message, commitments) + groupCommitment, bindingFactors, participants, err := c.prepareSignatureShareVerification(message, commitments) if err != nil { return nil, err } @@ -89,7 +89,7 @@ func (c *Configuration) VerifySignatureShare( message []byte, commitments CommitmentList, ) error { - groupCommitment, bindingFactors, participants, err := c.PrepareSignatureShareVerification(message, commitments) + groupCommitment, bindingFactors, participants, err := c.prepareSignatureShareVerification(message, commitments) if err != nil { return err } @@ -97,7 +97,7 @@ func (c *Configuration) VerifySignatureShare( return c.verifySignatureShare(sigShare, message, commitments, participants, groupCommitment, bindingFactors) } -func (c *Configuration) PrepareSignatureShareVerification(message []byte, +func (c *Configuration) prepareSignatureShareVerification(message []byte, commitments CommitmentList, ) (*group.Element, BindingFactors, []*group.Scalar, error) { if !c.verified { @@ -136,16 +136,8 @@ func (c *Configuration) validateSignatureShareExtensive(sigShare *SignatureShare return err } - if sigShare.SignerIdentifier == 0 { - return errors.New("signature share's signer identifier is 0 (invalid)") - } - - if sigShare.SignerIdentifier > c.MaxSigners { - return fmt.Errorf( - "signature share has invalid ID %d, above authorized range [1:%d]", - sigShare.SignerIdentifier, - c.MaxSigners, - ) + if err := c.validateIdentifier(sigShare.SignerIdentifier); err != nil { + return fmt.Errorf("invalid identifier for signer in signature share, the %w", err) } if sigShare.Group != c.group { diff --git a/encoding.go b/encoding.go index a440da0..1315e1b 100644 --- a/encoding.go +++ b/encoding.go @@ -21,13 +21,14 @@ import ( ) const ( - encConf = byte(1) - encSigner = byte(2) - encSigShare = byte(3) - encSig = byte(4) - encPubKeyShare = byte(5) - encNonceCommitment = byte(6) - encLambda = byte(7) + encConf byte = iota + 1 + encSigner + encSigShare + encSig + encPubKeyShare + encNonceCommitment + encLambda + encCommitment ) var ( @@ -47,15 +48,17 @@ func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { case encSigner: return other[0] + 2 + 2 + 2 + other[1] + other[2] + other[3] case encSigShare: - return 1 + 8 + uint64(g.ScalarLength()) + return 1 + 8 + sLen case encSig: - return eLen + uint64(g.ScalarLength()) + return eLen + sLen case encPubKeyShare: return 1 + 8 + 4 + eLen + other[0] case encNonceCommitment: - return 8 + 2*sLen + EncodedSize(g) + return 8 + 2*sLen + encodedLength(encCommitment, g) case encLambda: return 32 + sLen + case encCommitment: + return 1 + 8 + 8 + 2*eLen default: panic("encoded id not recognized") } @@ -131,24 +134,29 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { pksLen := encodedLength(encPubKeyShare, header.g, header.t*uint64(header.g.ElementLength())) pks := make([]*PublicKeyShare, header.nPks) + conf := &Configuration{ + Ciphersuite: Ciphersuite(header.g), + Threshold: header.t, + MaxSigners: header.n, + GroupPublicKey: gpk, + SignerPublicKeys: pks, + group: header.g, + } + for j := range header.nPks { pk := new(PublicKeyShare) if err := pk.Decode(data[offset : offset+pksLen]); err != nil { return fmt.Errorf("could not decode signer public key share for signer %d: %w", j, err) } + if err := conf.validatePublicKeyShare(pk); err != nil { + return err + } + offset += pksLen pks[j] = pk } - conf := &Configuration{ - Ciphersuite: Ciphersuite(header.g), - Threshold: header.t, - MaxSigners: header.n, - GroupPublicKey: gpk, - SignerPublicKeys: pks, - } - if err := conf.verify(); err != nil { return err } @@ -174,11 +182,21 @@ func (c *Configuration) Decode(data []byte) error { return c.decode(header, data) } +func (s *Signer) encodeNonceCommitments(out []byte) []byte { + for id, com := range s.NonceCommitments { + out = append(out, internal.Concatenate(internal.UInt64LE(id), + com.HidingNonce.Encode(), + com.BindingNonce.Encode(), + com.Commitment.Encode())...) + } + return out +} + // Encode serializes the client with its long term values, containing its secret share. This is useful for saving state // and backup. func (s *Signer) Encode() []byte { g := s.KeyShare.Group - ks := s.KeyShare.Encode() + keyShare := s.KeyShare.Encode() nCommitments := len(s.NonceCommitments) nLambdas := len(s.LambdaRegistry) conf := s.Configuration.Encode() @@ -186,19 +204,18 @@ func (s *Signer) Encode() []byte { encSigner, g, uint64(len(conf)), - uint64(len(ks)), - uint64(nCommitments)*encodedLength(encNonceCommitment, g), + uint64(len(keyShare)), uint64(nLambdas)*encodedLength(encLambda, g), + uint64(nCommitments)*encodedLength(encNonceCommitment, g), ) out := make([]byte, len(conf)+6, outLength) copy(out, conf) - binary.LittleEndian.PutUint16(out[len(conf):len(conf)+2], uint16(len(ks))) // key share length + binary.LittleEndian.PutUint16(out[len(conf):len(conf)+2], uint16(len(keyShare))) // key share length binary.LittleEndian.PutUint16(out[len(conf)+2:len(conf)+4], uint16(nCommitments)) // number of commitments binary.LittleEndian.PutUint16(out[len(conf)+4:len(conf)+6], uint16(nLambdas)) // number of lambda entries - out = append(out, ks...) // key share - + out = append(out, keyShare...) for k, v := range s.LambdaRegistry { b, err := hex.DecodeString(k) if err != nil { @@ -208,7 +225,6 @@ func (s *Signer) Encode() []byte { out = append(out, b...) out = append(out, v.Encode()...) } - for id, com := range s.NonceCommitments { out = append(out, internal.Concatenate(internal.UInt64LE(id), com.HidingNonce.Encode(), @@ -219,6 +235,34 @@ func (s *Signer) Encode() []byte { return out } +func (n *Nonce) decode(g group.Group, id, comLen uint64, data []byte) error { + sLen := uint64(g.ScalarLength()) + offset := uint64(g.ScalarLength()) + + hn := g.NewScalar() + if err := hn.Decode(data[:offset]); err != nil { + return fmt.Errorf("can't decode hiding nonce for commitment %d: %w", id, err) + } + + bn := g.NewScalar() + if err := bn.Decode(data[offset : offset+sLen]); err != nil { + return fmt.Errorf("can't decode binding nonce for commitment %d: %w", id, err) + } + + offset += sLen + + com := new(Commitment) + if err := com.Decode(data[offset : offset+comLen]); err != nil { + return fmt.Errorf("can't decode nonce commitment %d: %w", id, err) + } + + n.HidingNonce = hn + n.BindingNonce = bn + n.Commitment = com + + return nil +} + // Decode attempts to deserialize the encoded backup data into the Signer. func (s *Signer) Decode(data []byte) error { conf := new(Configuration) @@ -251,30 +295,25 @@ func (s *Signer) Decode(data []byte) error { offset := header.length + 6 keyShare := new(KeyShare) - if err := keyShare.Decode(data[offset : offset+ksLen]); err != nil { + if err = keyShare.Decode(data[offset : offset+ksLen]); err != nil { return fmt.Errorf("failed to decode key share: %w", err) } + if err = conf.ValidateKeyShare(keyShare); err != nil { + return err + } + offset += ksLen stop := offset + nLambdas*lLem lambdaRegistry := make(internal.LambdaRegistry, lLem) - - for offset < stop { - key := data[offset : offset+32] - offset += 32 - - lambda := g.NewScalar() - if err := lambda.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { - return fmt.Errorf("failed to decode lambda: %w", err) - } - - lambdaRegistry[hex.EncodeToString(key)] = lambda - offset += uint64(g.ScalarLength()) + if err = lambdaRegistry.Decode(g, data[offset:stop]); err != nil { + return err } + offset = stop commitments := make(map[uint64]*Nonce) - comLen := EncodedSize(g) - + comLen := encodedLength(encCommitment, g) + nComLen := encodedLength(encNonceCommitment, g) for offset < uint64(len(data)) { id := binary.LittleEndian.Uint64(data[offset : offset+8]) @@ -282,34 +321,13 @@ func (s *Signer) Decode(data []byte) error { return fmt.Errorf("multiple encoded commitments with the same id: %d", id) } - offset += 8 - - hs := g.NewScalar() - if err = hs.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { - return fmt.Errorf("can't decode hiding nonce for commitment %d: %w", id, err) - } - - offset += uint64(g.ScalarLength()) - - bs := g.NewScalar() - if err = bs.Decode(data[offset : offset+uint64(g.ScalarLength())]); err != nil { - return fmt.Errorf("can't decode binding nonce for commitment %d: %w", id, err) - } - - offset += uint64(g.ScalarLength()) - - com := new(Commitment) - if err = com.Decode(data[offset : offset+comLen]); err != nil { - return fmt.Errorf("can't decode nonce commitment %d: %w", id, err) + n := new(Nonce) + if err = n.decode(g, id, comLen, data[offset+8:]); err != nil { + return err } - offset += comLen - - commitments[id] = &Nonce{ - HidingNonce: hs, - BindingNonce: bs, - Commitment: com, - } + commitments[id] = n + offset += nComLen } s.KeyShare = keyShare @@ -325,7 +343,7 @@ func (c *Commitment) Encode() []byte { hNonce := c.HidingNonceCommitment.Encode() bNonce := c.BindingNonceCommitment.Encode() - out := make([]byte, 17, EncodedSize(c.Group)) + out := make([]byte, 17, encodedLength(encCommitment, c.Group)) out[0] = byte(c.Group) binary.LittleEndian.PutUint64(out[1:9], c.CommitmentID) binary.LittleEndian.PutUint64(out[9:17], c.SignerID) @@ -346,12 +364,17 @@ func (c *Commitment) Decode(data []byte) error { return errInvalidCiphersuite } - if uint64(len(data)) != EncodedSize(g) { + if uint64(len(data)) != encodedLength(encCommitment, g) { return errDecodeCommitmentLength } cID := binary.LittleEndian.Uint64(data[1:9]) + pID := binary.LittleEndian.Uint64(data[9:17]) + if pID == 0 { + return errZeroIdentifier + } + offset := 17 hn := g.NewElement() diff --git a/frost.go b/frost.go index e107121..99e68f5 100644 --- a/frost.go +++ b/frost.go @@ -127,10 +127,29 @@ type Configuration struct { var ( errInvalidThresholdParameter = errors.New("threshold is 0 or higher than maxSigners") errInvalidMaxSignersOrder = errors.New("maxSigners is higher than group order") - errInvalidGroupPublicKey = errors.New("invalid group public key (nil, identity, or generator)") errInvalidNumberOfPublicKeys = errors.New("invalid number of public keys (lower than threshold or above maximum)") ) +func (c *Configuration) validatePublicKeyShare(pks *PublicKeyShare) error { + if pks == nil { + return errors.New("public key share is nil") + } + + if pks.Group != c.group { + return fmt.Errorf("key share has invalid group parameter, want %s got %s", c.group, pks.Group) + } + + if err := c.validateIdentifier(pks.ID); err != nil { + return fmt.Errorf("invalid identifier for public key share, the %w", err) + } + + if err := c.validateGroupElement(pks.PublicKey); err != nil { + return fmt.Errorf("invalid public key for participant %d, the key %w", pks.ID, err) + } + + return nil +} + func (c *Configuration) verifySignerPublicKeys() error { length := uint64(len(c.SignerPublicKeys)) if length < c.Threshold || length > c.MaxSigners { @@ -140,16 +159,14 @@ func (c *Configuration) verifySignerPublicKeys() error { // Sets to detect duplicates. pkSet := make(map[string]uint64, len(c.SignerPublicKeys)) idSet := make(map[uint64]struct{}, len(c.SignerPublicKeys)) - g := group.Group(c.Ciphersuite) - base := g.Base() for i, pks := range c.SignerPublicKeys { if pks == nil { return fmt.Errorf("empty public key share at index %d", i) } - if pks.PublicKey == nil || pks.PublicKey.IsIdentity() || pks.PublicKey.Equal(base) == 1 { - return fmt.Errorf("invalid signer public key (nil, identity, or generator) for participant %d", pks.ID) + if err := c.validatePublicKeyShare(pks); err != nil { + return err } // Verify whether the ID has duplicates @@ -191,11 +208,8 @@ func (c *Configuration) verify() error { return errInvalidMaxSignersOrder } - g := group.Group(c.Ciphersuite) - base := g.Base() - - if c.GroupPublicKey == nil || c.GroupPublicKey.IsIdentity() || c.GroupPublicKey.Equal(base) == 1 { - return errInvalidGroupPublicKey + if err := c.validateGroupElement(c.GroupPublicKey); err != nil { + return fmt.Errorf("invalid group public key, the key %w", err) } if err := c.verifySignerPublicKeys(); err != nil { @@ -206,12 +220,13 @@ func (c *Configuration) verify() error { } func (c *Configuration) Init() error { + c.group = group.Group(c.Ciphersuite) + if err := c.verify(); err != nil { return err } c.verified = true - c.group = group.Group(c.Ciphersuite) return nil } @@ -237,32 +252,16 @@ func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { return errors.New("provided key share is nil") } - if keyShare.ID == 0 { - return errors.New("provided key share has invalid ID 0") - } - - if keyShare.ID > c.MaxSigners { - return fmt.Errorf( - "provided key share has invalid ID %d, above authorized range [1:%d]", - keyShare.ID, - c.MaxSigners, - ) - } - - if keyShare.Group != c.group { - return fmt.Errorf("provided key share has invalid group parameter, want %s got %s", c.group, keyShare.Group) + if err := c.validatePublicKeyShare(keyShare.Public()); err != nil { + return err } if c.GroupPublicKey.Equal(keyShare.GroupPublicKey) != 1 { return errors.New( - "the group's public key in the provided key share does not match the one in the configuration", + "the key share's group public key does not match the one in the configuration", ) } - if keyShare.PublicKey == nil { - return errors.New("provided key share has nil public key") - } - if keyShare.Secret == nil || keyShare.Secret.IsZero() { return errors.New("provided key share has invalid secret key") } @@ -285,6 +284,30 @@ func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { return nil } +func (c *Configuration) validateIdentifier(id uint64) error { + switch { + case id == 0: + return errors.New("identifier is 0") + case id > c.MaxSigners: + return fmt.Errorf("identifier %d is above authorized range [1:%d]", id, c.MaxSigners) + } + + return nil +} + +func (c *Configuration) validateGroupElement(e *group.Element) error { + switch { + case e == nil: + return errors.New("is nil") + case e.IsIdentity(): + return errors.New("is the identity element") + case c.group.Base().Equal(e) == 1: + return errors.New("is the group generator (base element)") + } + + return nil +} + // Signer returns a new participant of the protocol instantiated from the Configuration and the signer's key share. func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { if !c.verified { diff --git a/internal/lambda.go b/internal/lambda.go index d0b9dc5..aa5d233 100644 --- a/internal/lambda.go +++ b/internal/lambda.go @@ -48,19 +48,16 @@ func lambdaRegistryKey(participants []uint64) string { return hex.EncodeToString(hash.SHA256.Hash([]byte(a))) // Length = 32 bytes, 64 in hex string } +// New creates a new lambda and for the participant list for the participant id, and registers it. +// This function assumes that: +// - id is non-nil and != 0 +// - every participant id is != 0 +// - there are no duplicates in participants func (l LambdaRegistry) New(g group.Group, id uint64, participants []uint64) *group.Scalar { polynomial := secretsharing.NewPolynomialFromListFunc(g, participants, func(p uint64) *group.Scalar { return g.NewScalar().SetUInt64(p) }) - /* - lambda, err := Lambda(g, id, polynomial) - if err != nil { - return nil, err - } - - */ lambda := Lambda(g, id, polynomial) - l.Set(participants, lambda) return lambda @@ -90,3 +87,21 @@ func (l LambdaRegistry) Delete(participants []uint64) { l[key].Zero() delete(l, key) } + +func (l LambdaRegistry) Decode(g group.Group, data []byte) error { + offset := 0 + for offset < len(data) { + key := data[offset : offset+32] + offset += 32 + + lambda := g.NewScalar() + if err := lambda.Decode(data[offset : offset+g.ScalarLength()]); err != nil { + return fmt.Errorf("failed to decode lambda: %w", err) + } + + l[hex.EncodeToString(key)] = lambda + offset += g.ScalarLength() + } + + return nil +} diff --git a/tests/commitment_test.go b/tests/commitment_test.go index 991ad63..bfed5be 100644 --- a/tests/commitment_test.go +++ b/tests/commitment_test.go @@ -41,7 +41,10 @@ func TestCommitment_Validate_SignerIDs0(t *testing.T) { configuration, signers := fullSetup(t, tt) commitment := signers[0].Commit() commitment.SignerID = 0 - expectedErrorPrefix := fmt.Sprintf("signer identifier for commitment %d is 0", commitment.CommitmentID) + expectedErrorPrefix := fmt.Sprintf( + "invalid identifier for signer in commitment %d, the identifier is 0", + commitment.CommitmentID, + ) if err := configuration.ValidateCommitment(commitment); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -59,9 +62,9 @@ func TestCommitment_Validate_SignerIDInvalid(t *testing.T) { commitment := signers[0].Commit() commitment.SignerID = tt.maxSigners + 1 expectedErrorPrefix := fmt.Sprintf( - "signer identifier %d for commitment %d is above allowed values (%d)", - commitment.SignerID, + "invalid identifier for signer in commitment %d, the identifier %d is above authorized range [1:%d]", commitment.CommitmentID, + commitment.SignerID, tt.maxSigners, ) @@ -99,28 +102,41 @@ func TestCommitment_Validate_BadHidingNonceCommitment(t *testing.T) { } configuration, signers := fullSetup(t, tt) com := signers[0].Commit() + + // nil expectedErrorPrefix := fmt.Sprintf( - "commitment %d for signer %d has an invalid hiding nonce commitment (nil, identity, or generator)", + "invalid commitment %d for signer %d, the hiding nonce commitment is nil", com.CommitmentID, com.SignerID, ) - // generator - com.HidingNonceCommitment.Base() + com.HidingNonceCommitment = nil if err := configuration.ValidateCommitment(com); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // point at infinity - com.HidingNonceCommitment.Identity() + expectedErrorPrefix = fmt.Sprintf( + "invalid commitment %d for signer %d, the hiding nonce commitment is the identity element", + com.CommitmentID, + com.SignerID, + ) + + com.HidingNonceCommitment = tt.ECGroup().NewElement() if err := configuration.ValidateCommitment(com); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } - // nil - com.HidingNonceCommitment = nil + // generator + expectedErrorPrefix = fmt.Sprintf( + "invalid commitment %d for signer %d, the hiding nonce commitment is the group generator (base element)", + com.CommitmentID, + com.SignerID, + ) + + com.HidingNonceCommitment.Base() if err := configuration.ValidateCommitment(com); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -135,28 +151,41 @@ func TestCommitment_Validate_BadBindingNonceCommitment(t *testing.T) { } configuration, signers := fullSetup(t, tt) com := signers[0].Commit() + + // nil expectedErrorPrefix := fmt.Sprintf( - "commitment %d for signer %d has an invalid binding nonce commitment (nil, identity, or generator)", + "invalid commitment %d for signer %d, the binding nonce commitment is nil", com.CommitmentID, com.SignerID, ) - // generator - com.BindingNonceCommitment.Base() + com.BindingNonceCommitment = nil if err := configuration.ValidateCommitment(com); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // point at infinity - com.BindingNonceCommitment.Identity() + expectedErrorPrefix = fmt.Sprintf( + "invalid commitment %d for signer %d, the binding nonce commitment is the identity element", + com.CommitmentID, + com.SignerID, + ) + + com.BindingNonceCommitment = tt.ECGroup().NewElement() if err := configuration.ValidateCommitment(com); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } - // nil - com.BindingNonceCommitment = nil + // generator + expectedErrorPrefix = fmt.Sprintf( + "invalid commitment %d for signer %d, the binding nonce commitment is the group generator (base element)", + com.CommitmentID, + com.SignerID, + ) + + com.BindingNonceCommitment.Base() if err := configuration.ValidateCommitment(com); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -282,7 +311,7 @@ func TestCommitmentList_Validate_DuplicateSignerIDs(t *testing.T) { coms[i] = s.Commit() } - coms[2] = coms[1].Copy() + coms[2].SignerID = coms[1].SignerID if err := configuration.ValidateCommitmentList(coms); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -305,7 +334,7 @@ func TestCommitmentList_Validate_InvalidCommitment(t *testing.T) { coms[2].BindingNonceCommitment.Base() expectedErrorPrefix := fmt.Sprintf( - "commitment %d for signer %d has an invalid binding nonce commitment (nil, identity, or generator)", + "invalid commitment %d for signer %d, the binding nonce commitment is the group generator (base element)", coms[2].CommitmentID, coms[2].SignerID, ) diff --git a/tests/configuration_test.go b/tests/configuration_test.go index 45221a6..4105cbc 100644 --- a/tests/configuration_test.go +++ b/tests/configuration_test.go @@ -98,7 +98,7 @@ func TestConfiguration_Verify_Threshold_Max(t *testing.T) { } func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" + expectedErrorPrefix := "invalid group public key, the key is nil" testAll(t, func(t *testing.T, test *tableTest) { keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) @@ -119,7 +119,7 @@ func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { } func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" + expectedErrorPrefix := "invalid group public key, the key is the identity element" testAll(t, func(t *testing.T, test *tableTest) { keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) @@ -140,7 +140,7 @@ func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { } func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" + expectedErrorPrefix := "invalid group public key, the key is the group generator (base element)" testAll(t, func(t *testing.T, test *tableTest) { keyShares, _, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) @@ -230,8 +230,6 @@ func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { } func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { - expectedErrorPrefix := "invalid signer public key (nil, identity, or generator) for participant 2" - ciphersuite := frost.Ristretto255 threshold := uint64(2) maxSigners := uint64(3) @@ -248,6 +246,10 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { } // nil pk + expectedErrorPrefix := fmt.Sprintf( + "invalid public key for participant %d, the key is nil", + configuration.SignerPublicKeys[threshold-1].ID, + ) configuration.SignerPublicKeys[threshold-1].PublicKey = nil if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -255,6 +257,10 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { } // identity + expectedErrorPrefix = fmt.Sprintf( + "invalid public key for participant %d, the key is the identity element", + configuration.SignerPublicKeys[threshold-1].ID, + ) configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().NewElement() if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -262,6 +268,10 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { } // generator + expectedErrorPrefix = fmt.Sprintf( + "invalid public key for participant %d, the key is the group generator (base element)", + configuration.SignerPublicKeys[threshold-1].ID, + ) configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().Base() if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -323,29 +333,136 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) } } -func TestConfiguration_Signer_NotVerified(t *testing.T) { - ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) - - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) +func TestConfiguration_ValidateKeyShare_InvalidConf(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(tt.Ciphersuite, nil, tt.threshold, tt.maxSigners) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, + Ciphersuite: 2, + Threshold: tt.threshold, + MaxSigners: tt.maxSigners, GroupPublicKey: groupPublicKey, SignerPublicKeys: publicKeyShares, } - if _, err := configuration.Signer(keyShares[0]); err != nil { - t.Fatal(err) + if err := configuration.ValidateKeyShare(nil); err == nil || err.Error() != expectedErrorPrefix.Error() { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } -func TestConfiguration_Signer_BadConfig(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite +func TestConfiguration_ValidateKeyShare_Nil(t *testing.T) { + expectedErrorPrefix := "provided key share is nil" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, _ := makeConfAndShares(t, tt) + + if err := configuration.ValidateKeyShare(nil); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidateKeyShare_InvalidGroupPublicKey(t *testing.T) { + expectedErrorPrefix := "the key share's group public key does not match the one in the configuration" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, keyShares := makeConfAndShares(t, tt) + keyShare := keyShares[0] + + keyShare.GroupPublicKey = nil + if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidateKeyShare_WrongPublicKeyShare(t *testing.T) { + expectedErrorPrefix := "the key share's group public key does not match the one in the configuration" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, keyShares := makeConfAndShares(t, tt) + keyShare := keyShares[0] + + random := tt.ECGroup().NewScalar().Random() + keyShare.GroupPublicKey = tt.ECGroup().Base().Multiply(random) + if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidateKeyShare_InvalidSecretKey(t *testing.T) { + expectedErrorPrefix := "provided key share has invalid secret key" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, keyShares := makeConfAndShares(t, tt) + keyShare := keyShares[0] + + keyShare.Secret = nil + if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + keyShare.Secret = tt.ECGroup().NewScalar() + if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidateKeyShare_KeysNotMatching(t *testing.T) { + expectedErrorPrefix := "provided key share has non-matching secret and public keys" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, keyShares := makeConfAndShares(t, tt) + keyShare := keyShares[0] + + random := tt.ECGroup().NewScalar().Random() + keyShare.PublicKey = tt.ECGroup().Base().Multiply(random) + if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidateKeyShare_NotRegistered(t *testing.T) { + expectedErrorPrefix := "provided key share has no registered signer identifier in the configuration" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, keyShares := makeConfAndShares(t, tt) + + pks := make([]*frost.PublicKeyShare, len(keyShares)-1) + for i, ks := range keyShares[1:] { + pks[i] = ks.Public() + } + + configuration.SignerPublicKeys = pks + + if err := configuration.ValidateKeyShare(keyShares[0]); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_Signer_NotVerified(t *testing.T) { ciphersuite := frost.Ristretto255 threshold := uint64(2) maxSigners := uint64(3) @@ -354,20 +471,19 @@ func TestConfiguration_Signer_BadConfig(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: 2, + Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, GroupPublicKey: groupPublicKey, SignerPublicKeys: publicKeyShares, } - if _, err := configuration.Signer(keyShares[0]); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + if _, err := configuration.Signer(keyShares[0]); err != nil { + t.Fatal(err) } } -func TestConfiguration_PrepareSignatureShareVerification_BadNonVerifiedConfiguration(t *testing.T) { +func TestConfiguration_Signer_BadConfig(t *testing.T) { expectedErrorPrefix := internal.ErrInvalidCiphersuite ciphersuite := frost.Ristretto255 threshold := uint64(2) @@ -384,28 +500,8 @@ func TestConfiguration_PrepareSignatureShareVerification_BadNonVerifiedConfigura SignerPublicKeys: publicKeyShares, } - if _, _, _, err := configuration.PrepareSignatureShareVerification(nil, nil); err == nil || - err.Error() != expectedErrorPrefix.Error() { - t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) - } -} - -func TestConfiguration_PrepareSignatureShareVerification_InvalidCommitments(t *testing.T) { - expectedErrorPrefix := "invalid list of commitments: too few commitments: expected at least 2 but got 1" - tt := &tableTest{ - Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, - } - configuration, signers := fullSetup(t, tt) - coms := make(frost.CommitmentList, len(signers)) - - for i, s := range signers { - coms[i] = s.Commit() - } - - if _, _, _, err := configuration.PrepareSignatureShareVerification(nil, coms[:1]); err == nil || - !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + if _, err := configuration.Signer(keyShares[0]); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -456,7 +552,7 @@ func TestConfiguration_VerifySignatureShare_NilShare(t *testing.T) { } func TestConfiguration_VerifySignatureShare_SignerID0(t *testing.T) { - expectedErrorPrefix := "signature share's signer identifier is 0 (invalid)" + expectedErrorPrefix := "invalid identifier for signer in signature share, the identifier is 0" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -484,7 +580,6 @@ func TestConfiguration_VerifySignatureShare_SignerID0(t *testing.T) { } func TestConfiguration_VerifySignatureShare_InvalidSignerID(t *testing.T) { - expectedErrorPrefix := "signature share has invalid ID 4, above authorized range [1:3]" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -505,6 +600,12 @@ func TestConfiguration_VerifySignatureShare_InvalidSignerID(t *testing.T) { sigShare.SignerIdentifier = tt.maxSigners + 1 + expectedErrorPrefix := fmt.Sprintf( + "invalid identifier for signer in signature share, the identifier %d is above authorized range [1:%d]", + sigShare.SignerIdentifier, + tt.maxSigners, + ) + if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -614,7 +715,7 @@ func TestConfiguration_VerifySignatureShare_BadCommitment_BadSignerID(t *testing coms[1].SignerID = 0 expectedErrorPrefix := fmt.Sprintf( - "invalid list of commitments: signer identifier for commitment %d is 0", + "invalid list of commitments: invalid identifier for signer in commitment %d, the identifier is 0", coms[1].CommitmentID, ) diff --git a/tests/encoding_test.go b/tests/encoding_test.go index e212e6a..adc2b80 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -205,7 +205,7 @@ func compareNonceCommitments(c1, c2 *frost.Nonce) error { func compareLambdaRegistries(t *testing.T, m1, m2 map[string]*group.Scalar) { if len(m1) != len(m2) { - t.Fatal("unequal lengths") + t.Fatalf("unequal lengths: %d / %d", len(m1), len(m2)) } for k1, v1 := range m1 { @@ -408,7 +408,7 @@ func TestEncoding_Configuration_InvalidPublicKeyShare(t *testing.T) { } func TestEncoding_Configuration_CantVerify_InvalidPubKey(t *testing.T) { - expectedErrorPrefix := "invalid group public key (nil, identity, or generator)" + expectedErrorPrefix := "invalid group public key, the key is the group generator (base element)" testAll(t, func(t *testing.T, test *tableTest) { configuration := makeConf(t, test) @@ -582,9 +582,10 @@ func TestEncoding_Signer_InvalidCommitmentNonces_DuplicateID(t *testing.T) { sLen := g.ScalarLength() confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) - commitmentLength := 8 + 2*sLen + int(frost.EncodedSize(g)) + commitmentLength := 1 + 8 + 8 + 2*uint64(g.ElementLength()) + nonceCommitmentLength := 8 + 2*sLen + int(commitmentLength) offset := confLen + 6 + keyShareLen - offset2 := offset + commitmentLength + offset2 := offset + nonceCommitmentLength encoded := s.Encode() data := slices.Replace(encoded, offset2, offset2+8, encoded[offset:offset+8]...) From 0db81f6e32e1bac99f0b91373f9233e26e2bda25 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:31:05 +0200 Subject: [PATCH 20/31] some refacto and added tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 15 +- coordinator.go | 18 +- encoding.go | 32 ++-- examples_test.go | 20 +-- frost.go | 212 ++++++++++++----------- tests/commitment_test.go | 2 +- tests/configuration_test.go | 332 +++++++++++++++++++++++++----------- tests/encoding_test.go | 32 ++-- tests/frost_test.go | 10 +- tests/vector_utils_test.go | 14 +- 10 files changed, 430 insertions(+), 257 deletions(-) diff --git a/commitment.go b/commitment.go index b190a02..76da4d9 100644 --- a/commitment.go +++ b/commitment.go @@ -6,7 +6,6 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -// Package commitment defines the FROST Signer commitment. package frost import ( @@ -238,7 +237,7 @@ func (c CommitmentList) groupCommitment(bf BindingFactors) *group.Element { } func (c *Configuration) isSignerRegistered(sid uint64) bool { - for _, peer := range c.SignerPublicKeys { + for _, peer := range c.SignerPublicKeyShares { if peer.ID == sid { return true } @@ -249,6 +248,12 @@ func (c *Configuration) isSignerRegistered(sid uint64) bool { // ValidateCommitment returns an error if the commitment is not valid. func (c *Configuration) ValidateCommitment(commitment *Commitment) error { + if !c.verified || !c.keysVerified { + if err := c.Init(); err != nil { + return err + } + } + if commitment == nil { return fmt.Errorf("the commitment list has a nil commitment") } @@ -322,6 +327,12 @@ func (c *Configuration) validateCommitmentListLength(commitments CommitmentList) // - no duplicated in signer identifiers // - all commitment signer identifiers are registered in the configuration func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error { + if !c.verified || !c.keysVerified { + if err := c.Init(); err != nil { + return err + } + } + if err := c.validateCommitmentListLength(commitments); err != nil { return err } diff --git a/coordinator.go b/coordinator.go index 975b3f7..82fd9da 100644 --- a/coordinator.go +++ b/coordinator.go @@ -42,6 +42,12 @@ func (c *Configuration) AggregateSignatures( commitments CommitmentList, verify bool, ) (*Signature, error) { + if !c.verified || !c.keysVerified { + if err := c.Init(); err != nil { + return nil, err + } + } + groupCommitment, bindingFactors, participants, err := c.prepareSignatureShareVerification(message, commitments) if err != nil { return nil, err @@ -89,6 +95,12 @@ func (c *Configuration) VerifySignatureShare( message []byte, commitments CommitmentList, ) error { + if !c.verified || !c.keysVerified { + if err := c.Init(); err != nil { + return err + } + } + groupCommitment, bindingFactors, participants, err := c.prepareSignatureShareVerification(message, commitments) if err != nil { return err @@ -100,12 +112,6 @@ func (c *Configuration) VerifySignatureShare( func (c *Configuration) prepareSignatureShareVerification(message []byte, commitments CommitmentList, ) (*group.Element, BindingFactors, []*group.Scalar, error) { - if !c.verified { - if err := c.verify(); err != nil { - return nil, nil, nil, err - } - } - commitments.Sort() // Validate general consistency of the commitment list. diff --git a/encoding.go b/encoding.go index 1315e1b..77d9598 100644 --- a/encoding.go +++ b/encoding.go @@ -68,16 +68,16 @@ func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { func (c *Configuration) Encode() []byte { g := group.Group(c.Ciphersuite) pksLen := encodedLength(encPubKeyShare, g, c.Threshold*uint64(g.ElementLength())) - size := encodedLength(encConf, g, uint64(len(c.SignerPublicKeys))*pksLen) + size := encodedLength(encConf, g, uint64(len(c.SignerPublicKeyShares))*pksLen) out := make([]byte, 25, size) out[0] = byte(g) binary.LittleEndian.PutUint64(out[1:9], c.Threshold) binary.LittleEndian.PutUint64(out[9:17], c.MaxSigners) - binary.LittleEndian.PutUint64(out[17:25], uint64(len(c.SignerPublicKeys))) + binary.LittleEndian.PutUint64(out[17:25], uint64(len(c.SignerPublicKeyShares))) out = append(out, c.GroupPublicKey.Encode()...) - for _, pk := range c.SignerPublicKeys { + for _, pk := range c.SignerPublicKeyShares { out = append(out, pk.Encode()...) } @@ -106,7 +106,7 @@ func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { pksLen := encodedLength(encPubKeyShare, g, t*uint64(g.ElementLength())) length := encodedLength(encConf, g, nPks*pksLen) - if t > n { + if t == 0 || t > n { return nil, errInvalidConfigEncoding } @@ -135,12 +135,15 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { pks := make([]*PublicKeyShare, header.nPks) conf := &Configuration{ - Ciphersuite: Ciphersuite(header.g), - Threshold: header.t, - MaxSigners: header.n, - GroupPublicKey: gpk, - SignerPublicKeys: pks, - group: header.g, + Ciphersuite: Ciphersuite(header.g), + Threshold: header.t, + MaxSigners: header.n, + GroupPublicKey: gpk, + SignerPublicKeyShares: pks, + } + + if err := conf.verifyConfiguration(); err != nil { + return err } for j := range header.nPks { @@ -149,15 +152,11 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { return fmt.Errorf("could not decode signer public key share for signer %d: %w", j, err) } - if err := conf.validatePublicKeyShare(pk); err != nil { - return err - } - offset += pksLen pks[j] = pk } - if err := conf.verify(); err != nil { + if err := conf.verifySignerPublicKeyShares(); err != nil { return err } @@ -165,9 +164,10 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { c.Threshold = conf.Threshold c.MaxSigners = conf.MaxSigners c.GroupPublicKey = gpk - c.SignerPublicKeys = pks + c.SignerPublicKeyShares = pks c.group = group.Group(conf.Ciphersuite) c.verified = true + c.keysVerified = true return nil } diff --git a/examples_test.go b/examples_test.go index 087d670..bc95aab 100644 --- a/examples_test.go +++ b/examples_test.go @@ -39,11 +39,11 @@ func Example_signer() { // This is how to set up the Configuration for FROST, the same for every signer and the coordinator. configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err != nil { @@ -126,11 +126,11 @@ func Example_coordinator() { // This is how to set up the Configuration for FROST, the same for every signer and the coordinator. configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err != nil { diff --git a/frost.go b/frost.go index 99e68f5..0c0719d 100644 --- a/frost.go +++ b/frost.go @@ -113,15 +113,16 @@ func (c Ciphersuite) ECGroup() group.Group { return group.Group(c) } -// Configuration holds long term Configuration information. +// Configuration holds the Configuration for a signing session. type Configuration struct { - GroupPublicKey *group.Element - SignerPublicKeys []*PublicKeyShare - Threshold uint64 - MaxSigners uint64 - Ciphersuite Ciphersuite - group group.Group - verified bool + GroupPublicKey *group.Element + SignerPublicKeyShares []*PublicKeyShare + Threshold uint64 + MaxSigners uint64 + Ciphersuite Ciphersuite + group group.Group + verified bool + keysVerified bool } var ( @@ -130,13 +131,57 @@ var ( errInvalidNumberOfPublicKeys = errors.New("invalid number of public keys (lower than threshold or above maximum)") ) -func (c *Configuration) validatePublicKeyShare(pks *PublicKeyShare) error { +func (c *Configuration) Init() error { + if !c.verified { + if err := c.verifyConfiguration(); err != nil { + return err + } + } + + if !c.keysVerified { + if err := c.verifySignerPublicKeyShares(); err != nil { + return err + } + } + + return nil +} + +// Signer returns a new participant of the protocol instantiated from the Configuration and the signer's key share. +func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { + if !c.verified || !c.keysVerified { + if err := c.Init(); err != nil { + return nil, err + } + } + + if err := c.ValidateKeyShare(keyShare); err != nil { + return nil, err + } + + return &Signer{ + KeyShare: keyShare, + LambdaRegistry: make(internal.LambdaRegistry), + NonceCommitments: make(map[uint64]*Nonce), + HidingRandom: nil, + BindingRandom: nil, + Configuration: c, + }, nil +} + +func (c *Configuration) ValidatePublicKeyShare(pks *PublicKeyShare) error { + if !c.verified { + if err := c.verifyConfiguration(); err != nil { + return err + } + } + if pks == nil { return errors.New("public key share is nil") } if pks.Group != c.group { - return fmt.Errorf("key share has invalid group parameter, want %s got %s", c.group, pks.Group) + return fmt.Errorf("key share has invalid group parameter, want %s got %d", c.group, pks.Group) } if err := c.validateIdentifier(pks.ID); err != nil { @@ -150,22 +195,65 @@ func (c *Configuration) validatePublicKeyShare(pks *PublicKeyShare) error { return nil } -func (c *Configuration) verifySignerPublicKeys() error { - length := uint64(len(c.SignerPublicKeys)) +func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { + if !c.verified || !c.keysVerified { + if err := c.Init(); err != nil { + return err + } + } + + if keyShare == nil { + return errors.New("provided key share is nil") + } + + if err := c.ValidatePublicKeyShare(keyShare.Public()); err != nil { + return err + } + + if c.GroupPublicKey.Equal(keyShare.GroupPublicKey) != 1 { + return errors.New( + "the key share's group public key does not match the one in the configuration", + ) + } + + if keyShare.Secret == nil || keyShare.Secret.IsZero() { + return errors.New("provided key share has invalid secret key") + } + + if c.group.Base().Multiply(keyShare.Secret).Equal(keyShare.PublicKey) != 1 { + return errors.New("provided key share has non-matching secret and public keys") + } + + pk := c.getSignerPubKey(keyShare.ID) + if pk == nil { + return errors.New("provided key share has no registered signer identifier in the configuration") + } + + if pk.Equal(keyShare.PublicKey) != 1 { + return errors.New( + "provided key share has a different public key than the one registered for that signer in the configuration", + ) + } + + return nil +} + +func (c *Configuration) verifySignerPublicKeyShares() error { + length := uint64(len(c.SignerPublicKeyShares)) if length < c.Threshold || length > c.MaxSigners { return errInvalidNumberOfPublicKeys } // Sets to detect duplicates. - pkSet := make(map[string]uint64, len(c.SignerPublicKeys)) - idSet := make(map[uint64]struct{}, len(c.SignerPublicKeys)) + pkSet := make(map[string]uint64, len(c.SignerPublicKeyShares)) + idSet := make(map[uint64]struct{}, len(c.SignerPublicKeyShares)) - for i, pks := range c.SignerPublicKeys { + for i, pks := range c.SignerPublicKeyShares { if pks == nil { return fmt.Errorf("empty public key share at index %d", i) } - if err := c.validatePublicKeyShare(pks); err != nil { + if err := c.ValidatePublicKeyShare(pks); err != nil { return err } @@ -184,19 +272,23 @@ func (c *Configuration) verifySignerPublicKeys() error { idSet[pks.ID] = struct{}{} } + c.keysVerified = true + return nil } -func (c *Configuration) verify() error { +func (c *Configuration) verifyConfiguration() error { if !c.Ciphersuite.Available() { return internal.ErrInvalidCiphersuite } + g := group.Group(c.Ciphersuite) + if c.Threshold == 0 || c.Threshold > c.MaxSigners { return errInvalidThresholdParameter } - order, _ := new(big.Int).SetString(group.Group(c.Ciphersuite).Order(), 0) + order, _ := new(big.Int).SetString(g.Order(), 0) if order == nil { panic("can't set group order number") } @@ -212,27 +304,14 @@ func (c *Configuration) verify() error { return fmt.Errorf("invalid group public key, the key %w", err) } - if err := c.verifySignerPublicKeys(); err != nil { - return err - } - - return nil -} - -func (c *Configuration) Init() error { - c.group = group.Group(c.Ciphersuite) - - if err := c.verify(); err != nil { - return err - } - + c.group = g c.verified = true return nil } func (c *Configuration) getSignerPubKey(id uint64) *group.Element { - for _, pks := range c.SignerPublicKeys { + for _, pks := range c.SignerPublicKeyShares { if pks.ID == id { return pks.PublicKey } @@ -241,49 +320,6 @@ func (c *Configuration) getSignerPubKey(id uint64) *group.Element { return nil } -func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { - if !c.verified { - if err := c.Init(); err != nil { - return err - } - } - - if keyShare == nil { - return errors.New("provided key share is nil") - } - - if err := c.validatePublicKeyShare(keyShare.Public()); err != nil { - return err - } - - if c.GroupPublicKey.Equal(keyShare.GroupPublicKey) != 1 { - return errors.New( - "the key share's group public key does not match the one in the configuration", - ) - } - - if keyShare.Secret == nil || keyShare.Secret.IsZero() { - return errors.New("provided key share has invalid secret key") - } - - if c.group.Base().Multiply(keyShare.Secret).Equal(keyShare.PublicKey) != 1 { - return errors.New("provided key share has non-matching secret and public keys") - } - - pk := c.getSignerPubKey(keyShare.ID) - if pk == nil { - return errors.New("provided key share has no registered signer identifier in the configuration") - } - - if pk.Equal(keyShare.PublicKey) != 1 { - return errors.New( - "provided key share has a different public key than the one registered for that signer in the configuration", - ) - } - - return nil -} - func (c *Configuration) validateIdentifier(id uint64) error { switch { case id == 0: @@ -301,35 +337,13 @@ func (c *Configuration) validateGroupElement(e *group.Element) error { return errors.New("is nil") case e.IsIdentity(): return errors.New("is the identity element") - case c.group.Base().Equal(e) == 1: + case group.Group(c.Ciphersuite).Base().Equal(e) == 1: return errors.New("is the group generator (base element)") } return nil } -// Signer returns a new participant of the protocol instantiated from the Configuration and the signer's key share. -func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { - if !c.verified { - if err := c.Init(); err != nil { - return nil, err - } - } - - if err := c.ValidateKeyShare(keyShare); err != nil { - return nil, err - } - - return &Signer{ - KeyShare: keyShare, - LambdaRegistry: make(internal.LambdaRegistry), - NonceCommitments: make(map[uint64]*Nonce), - HidingRandom: nil, - BindingRandom: nil, - Configuration: c, - }, nil -} - func (c *Configuration) challenge(lambda *group.Scalar, message []byte, groupCommitment *group.Element) *group.Scalar { chall := SchnorrChallenge(c.group, message, groupCommitment, c.GroupPublicKey) return chall.Multiply(lambda) diff --git a/tests/commitment_test.go b/tests/commitment_test.go index bfed5be..0add67e 100644 --- a/tests/commitment_test.go +++ b/tests/commitment_test.go @@ -380,7 +380,7 @@ func TestCommitmentList_Validate_UnregisteredKey(t *testing.T) { coms[i] = s.Commit() } - configuration.SignerPublicKeys = slices.Delete(configuration.SignerPublicKeys, 1, 2) + configuration.SignerPublicKeyShares = slices.Delete(configuration.SignerPublicKeyShares, 1, 2) expectedErrorPrefix := fmt.Sprintf( "signer identifier %d for commitment %d is not registered in the configuration", coms[1].SignerID, diff --git a/tests/configuration_test.go b/tests/configuration_test.go index 4105cbc..f63c7f0 100644 --- a/tests/configuration_test.go +++ b/tests/configuration_test.go @@ -14,6 +14,8 @@ import ( "strings" "testing" + secretsharing "github.com/bytemare/secret-sharing" + "github.com/bytemare/frost" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" @@ -32,11 +34,11 @@ func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: 2, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix.Error()) { @@ -58,11 +60,11 @@ func TestConfiguration_Verify_Threshold_0(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: 0, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: test.Ciphersuite, + Threshold: 0, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -84,11 +86,11 @@ func TestConfiguration_Verify_Threshold_Max(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.maxSigners + 1, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: test.Ciphersuite, + Threshold: test.maxSigners + 1, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -105,11 +107,11 @@ func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: nil, - SignerPublicKeys: publicKeyShares, + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: nil, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -126,11 +128,11 @@ func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: test.ECGroup().NewElement(), - SignerPublicKeys: publicKeyShares, + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: test.ECGroup().NewElement(), + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -147,11 +149,11 @@ func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: test.ECGroup().Base(), - SignerPublicKeys: publicKeyShares, + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: test.ECGroup().Base(), + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -172,11 +174,11 @@ func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { // nil configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: nil, + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: nil, } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -184,21 +186,21 @@ func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { } // empty - configuration.SignerPublicKeys = []*frost.PublicKeyShare{} + configuration.SignerPublicKeyShares = []*frost.PublicKeyShare{} if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // too few - configuration.SignerPublicKeys = publicKeyShares[:threshold-1] + configuration.SignerPublicKeyShares = publicKeyShares[:threshold-1] if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } // too many - configuration.SignerPublicKeys = append(publicKeyShares, &frost.PublicKeyShare{}) + configuration.SignerPublicKeyShares = append(publicKeyShares, &frost.PublicKeyShare{}) if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -217,11 +219,11 @@ func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { publicKeyShares[threshold-1] = nil configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -238,19 +240,19 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } // nil pk expectedErrorPrefix := fmt.Sprintf( "invalid public key for participant %d, the key is nil", - configuration.SignerPublicKeys[threshold-1].ID, + configuration.SignerPublicKeyShares[threshold-1].ID, ) - configuration.SignerPublicKeys[threshold-1].PublicKey = nil + configuration.SignerPublicKeyShares[threshold-1].PublicKey = nil if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -259,9 +261,9 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { // identity expectedErrorPrefix = fmt.Sprintf( "invalid public key for participant %d, the key is the identity element", - configuration.SignerPublicKeys[threshold-1].ID, + configuration.SignerPublicKeyShares[threshold-1].ID, ) - configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().NewElement() + configuration.SignerPublicKeyShares[threshold-1].PublicKey = ciphersuite.ECGroup().NewElement() if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -270,9 +272,9 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { // generator expectedErrorPrefix = fmt.Sprintf( "invalid public key for participant %d, the key is the group generator (base element)", - configuration.SignerPublicKeys[threshold-1].ID, + configuration.SignerPublicKeyShares[threshold-1].ID, ) - configuration.SignerPublicKeys[threshold-1].PublicKey = ciphersuite.ECGroup().Base() + configuration.SignerPublicKeyShares[threshold-1].PublicKey = ciphersuite.ECGroup().Base() if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -290,16 +292,16 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_Identifiers(t *testing.T publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } // duplicate id - id1 := configuration.SignerPublicKeys[0].ID - configuration.SignerPublicKeys[1].ID = id1 + id1 := configuration.SignerPublicKeyShares[0].ID + configuration.SignerPublicKeyShares[1].ID = id1 if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -317,22 +319,112 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } // duplicate id - pk1 := configuration.SignerPublicKeys[0].PublicKey.Copy() - configuration.SignerPublicKeys[1].PublicKey = pk1 + pk1 := configuration.SignerPublicKeyShares[0].PublicKey.Copy() + configuration.SignerPublicKeyShares[1].PublicKey = pk1 if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } +func TestConfiguration_ValidatePublicKeyShare_Nil(t *testing.T) { + expectedErrorPrefix := "public key share is nil" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, _ := makeConfAndShares(t, tt) + + if err := configuration.ValidatePublicKeyShare(nil); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidatePublicKeyShare_WrongGroup(t *testing.T) { + expectedErrorPrefix := "key share has invalid group parameter, want ristretto255_XMD:SHA-512_R255MAP_RO_ got 0" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, _ := makeConfAndShares(t, tt) + + pks := &frost.PublicKeyShare{ + Group: 0, + } + + if err := configuration.ValidatePublicKeyShare(pks); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidatePublicKeyShare_ID0(t *testing.T) { + expectedErrorPrefix := "invalid identifier for public key share, the identifier is 0" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, _ := makeConfAndShares(t, tt) + + pks := &frost.PublicKeyShare{ + Group: tt.ECGroup(), + ID: 0, + } + + if err := configuration.ValidatePublicKeyShare(pks); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidatePublicKeyShare_InvalidID(t *testing.T) { + expectedErrorPrefix := "invalid identifier for public key share, the identifier 4 is above authorized range [1:3]" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, _ := makeConfAndShares(t, tt) + + pks := &frost.PublicKeyShare{ + Group: tt.ECGroup(), + ID: tt.maxSigners + 1, + } + + if err := configuration.ValidatePublicKeyShare(pks); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + +func TestConfiguration_ValidatePublicKeyShare_InvalidPublicKey(t *testing.T) { + expectedErrorPrefix := "invalid public key for participant 1, the key is the group generator (base element)" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, _ := makeConfAndShares(t, tt) + + pks := &frost.PublicKeyShare{ + Group: tt.ECGroup(), + ID: 1, + PublicKey: tt.ECGroup().Base(), + } + + if err := configuration.ValidatePublicKeyShare(pks); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestConfiguration_ValidateKeyShare_InvalidConf(t *testing.T) { expectedErrorPrefix := internal.ErrInvalidCiphersuite tt := &tableTest{ @@ -344,11 +436,11 @@ func TestConfiguration_ValidateKeyShare_InvalidConf(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: tt.threshold, - MaxSigners: tt.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: 2, + Threshold: tt.threshold, + MaxSigners: tt.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.ValidateKeyShare(nil); err == nil || err.Error() != expectedErrorPrefix.Error() { @@ -384,10 +476,20 @@ func TestConfiguration_ValidateKeyShare_InvalidGroupPublicKey(t *testing.T) { if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } + + keyShare.GroupPublicKey = tt.ECGroup().NewElement() + if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + + keyShare.GroupPublicKey.Base() + if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } } -func TestConfiguration_ValidateKeyShare_WrongPublicKeyShare(t *testing.T) { - expectedErrorPrefix := "the key share's group public key does not match the one in the configuration" +func TestConfiguration_ValidateKeyShare_BadPublicKeyShare(t *testing.T) { + expectedErrorPrefix := "invalid public key for participant 1, the key is nil" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -396,8 +498,7 @@ func TestConfiguration_ValidateKeyShare_WrongPublicKeyShare(t *testing.T) { configuration, keyShares := makeConfAndShares(t, tt) keyShare := keyShares[0] - random := tt.ECGroup().NewScalar().Random() - keyShare.GroupPublicKey = tt.ECGroup().Base().Multiply(random) + keyShare.PublicKey = nil if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -441,7 +542,7 @@ func TestConfiguration_ValidateKeyShare_KeysNotMatching(t *testing.T) { } } -func TestConfiguration_ValidateKeyShare_NotRegistered(t *testing.T) { +func TestConfiguration_ValidateKeyShare_SignerIDNotRegistered(t *testing.T) { expectedErrorPrefix := "provided key share has no registered signer identifier in the configuration" tt := &tableTest{ Ciphersuite: frost.Ristretto255, @@ -455,13 +556,38 @@ func TestConfiguration_ValidateKeyShare_NotRegistered(t *testing.T) { pks[i] = ks.Public() } - configuration.SignerPublicKeys = pks + configuration.SignerPublicKeyShares = pks if err := configuration.ValidateKeyShare(keyShares[0]); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } +func TestConfiguration_ValidateKeyShare_WrongPublicKey(t *testing.T) { + expectedErrorPrefix := "provided key share has a different public key than the one registered for that signer in the configuration" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + configuration, keyShares := makeConfAndShares(t, tt) + + random := tt.ECGroup().NewScalar().Random() + keyShare := &frost.KeyShare{ + Secret: random, + GroupPublicKey: keyShares[0].GroupPublicKey, + PublicKeyShare: secretsharing.PublicKeyShare{ + PublicKey: tt.ECGroup().Base().Multiply(random), + ID: keyShares[0].ID, + Group: keyShares[0].Group, + }, + } + + if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestConfiguration_Signer_NotVerified(t *testing.T) { ciphersuite := frost.Ristretto255 threshold := uint64(2) @@ -471,11 +597,11 @@ func TestConfiguration_Signer_NotVerified(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if _, err := configuration.Signer(keyShares[0]); err != nil { @@ -493,11 +619,11 @@ func TestConfiguration_Signer_BadConfig(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if _, err := configuration.Signer(keyShares[0]); err == nil || @@ -506,6 +632,22 @@ func TestConfiguration_Signer_BadConfig(t *testing.T) { } } +func TestConfiguration_Singer_BadKeyShare(t *testing.T) { + expectedErrorPrefix := "provided key share is nil" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 2, + maxSigners: 3, + } + + configuration := makeConf(t, tt) + + if _, err := configuration.Signer(nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestConfiguration_VerifySignatureShare_BadPrep(t *testing.T) { expectedErrorPrefix := internal.ErrInvalidCiphersuite @@ -517,11 +659,11 @@ func TestConfiguration_VerifySignatureShare_BadPrep(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: 2, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: 2, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.VerifySignatureShare(nil, nil, nil); err == nil || @@ -659,7 +801,7 @@ func TestConfiguration_VerifySignatureShare_MissingPublicKey(t *testing.T) { t.Fatal(err) } - configuration.SignerPublicKeys = slices.Delete(configuration.SignerPublicKeys, 0, 1) + configuration.SignerPublicKeyShares = slices.Delete(configuration.SignerPublicKeyShares, 0, 1) expectedErrorPrefix := fmt.Sprintf("no public key registered for signer 1") if err := configuration.VerifySignatureShare(sigShare, message, coms[1:]); err == nil || diff --git a/tests/encoding_test.go b/tests/encoding_test.go index adc2b80..cc07ef1 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -29,11 +29,11 @@ func makeConfAndShares(t *testing.T, test *tableTest) (*frost.Configuration, []* publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err != nil { @@ -100,16 +100,16 @@ func compareConfigurations(t *testing.T, c1, c2 *frost.Configuration, expectedMa t.Fatalf("expected matching GroupPublicKey: %q / %q", c1.Ciphersuite, c2.Ciphersuite) } - if len(c1.SignerPublicKeys) != len(c2.SignerPublicKeys) { + if len(c1.SignerPublicKeyShares) != len(c2.SignerPublicKeyShares) { t.Fatalf( - "expected matching SignerPublicKeys lengths: %q / %q", - len(c1.SignerPublicKeys), - len(c2.SignerPublicKeys), + "expected matching SignerPublicKeyShares lengths: %q / %q", + len(c1.SignerPublicKeyShares), + len(c2.SignerPublicKeyShares), ) } - for i, p1 := range c1.SignerPublicKeys { - p2 := c2.SignerPublicKeys[i] + for i, p1 := range c1.SignerPublicKeyShares { + p2 := c2.SignerPublicKeyShares[i] if err := comparePublicKeyShare(p1, p2); !expectedMatch && err != nil { t.Fatal(err) } @@ -387,11 +387,11 @@ func TestEncoding_Configuration_InvalidPublicKeyShare(t *testing.T) { publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: test.threshold, - MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } g := group.Group(test.Ciphersuite) pksSize := len(publicKeyShares[0].Encode()) diff --git a/tests/frost_test.go b/tests/frost_test.go index 9877d61..3543595 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -59,11 +59,11 @@ func runFrost( // Set up configuration. configuration := &frost.Configuration{ - Ciphersuite: test.Ciphersuite, - Threshold: threshold, - MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeys: publicKeyShares, + Ciphersuite: test.Ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, } if err := configuration.Init(); err != nil { diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index 597a1f6..36005d9 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -205,11 +205,11 @@ type testRoundTwoOutputs struct { func makeFrostConfig(c frost.Ciphersuite, threshold, maxSigners uint) *frost.Configuration { return &frost.Configuration{ - Ciphersuite: c, - Threshold: uint64(threshold), - MaxSigners: uint64(maxSigners), - GroupPublicKey: nil, - SignerPublicKeys: nil, + Ciphersuite: c, + Threshold: uint64(threshold), + MaxSigners: uint64(maxSigners), + GroupPublicKey: nil, + SignerPublicKeyShares: nil, } } @@ -313,10 +313,10 @@ func (v testVector) decode(t *testing.T) *test { inputs := v.Inputs.decode(t, conf.Ciphersuite.ECGroup()) conf.GroupPublicKey = inputs.GroupPublicKey - conf.SignerPublicKeys = make([]*frost.PublicKeyShare, len(inputs.Participants)) + conf.SignerPublicKeyShares = make([]*frost.PublicKeyShare, len(inputs.Participants)) for i, ks := range inputs.Participants { - conf.SignerPublicKeys[i] = ks.Public() + conf.SignerPublicKeyShares[i] = ks.Public() } if err := conf.Configuration.Init(); err != nil { From df7407ecca1d19505511c5ec7b195b3016022c38 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 3 Sep 2024 00:18:37 +0200 Subject: [PATCH 21/31] add tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- encoding.go | 14 ++------- examples_test.go | 25 +++++++++------ tests/configuration_test.go | 61 ++++++++++++++++++++++++++++------- tests/encoding_test.go | 63 +++++++++++++++++++++++++++++++++---- tests/utils_test.go | 7 +++++ 5 files changed, 132 insertions(+), 38 deletions(-) diff --git a/encoding.go b/encoding.go index 77d9598..ecc177f 100644 --- a/encoding.go +++ b/encoding.go @@ -182,16 +182,6 @@ func (c *Configuration) Decode(data []byte) error { return c.decode(header, data) } -func (s *Signer) encodeNonceCommitments(out []byte) []byte { - for id, com := range s.NonceCommitments { - out = append(out, internal.Concatenate(internal.UInt64LE(id), - com.HidingNonce.Encode(), - com.BindingNonce.Encode(), - com.Commitment.Encode())...) - } - return out -} - // Encode serializes the client with its long term values, containing its secret share. This is useful for saving state // and backup. func (s *Signer) Encode() []byte { @@ -216,6 +206,7 @@ func (s *Signer) Encode() []byte { binary.LittleEndian.PutUint16(out[len(conf)+4:len(conf)+6], uint16(nLambdas)) // number of lambda entries out = append(out, keyShare...) + for k, v := range s.LambdaRegistry { b, err := hex.DecodeString(k) if err != nil { @@ -225,6 +216,7 @@ func (s *Signer) Encode() []byte { out = append(out, b...) out = append(out, v.Encode()...) } + for id, com := range s.NonceCommitments { out = append(out, internal.Concatenate(internal.UInt64LE(id), com.HidingNonce.Encode(), @@ -300,7 +292,7 @@ func (s *Signer) Decode(data []byte) error { } if err = conf.ValidateKeyShare(keyShare); err != nil { - return err + return fmt.Errorf("invalid key share: %w", err) } offset += ksLen diff --git a/examples_test.go b/examples_test.go index bc95aab..85ca1f6 100644 --- a/examples_test.go +++ b/examples_test.go @@ -22,7 +22,8 @@ func Example_signer() { message := []byte("example message") ciphersuite := frost.Ristretto255 - // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. + // We assume you already have a pool of participants with distinct non-zero identifiers in [1:maxSingers] + // and their signing share. // This example uses a centralised trusted dealer, but it is strongly recommended to use distributed key generation, // e.g. from github.com/bytemare/dkg, which is compatible with FROST. secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) @@ -38,6 +39,8 @@ func Example_signer() { } // This is how to set up the Configuration for FROST, the same for every signer and the coordinator. + // Note that every configuration setup for a Signer needs the public key shares of all other signers participating + // in a signing session (at least for the Sign() step). configuration := &frost.Configuration{ Ciphersuite: ciphersuite, Threshold: threshold, @@ -50,28 +53,30 @@ func Example_signer() { panic(err) } - // Instantiate the participant. + // Instantiate the participant using its secret share. + // A participant (or Signer) can be backed up by serialization, and directly instantiated from that backup. participant, err := configuration.Signer(participantSecretKeyShare) if err != nil { panic(err) } // Step 1: call Commit() on each participant. This will return the participant's single-use commitment for a - // a signature. Every commitment has an identifier that must be provided to Sign() to use that commitment. - // Send this to the coordinator or all other participants over an authenticated + // signature (which is independent of the future message to sign). + // Send this to the coordinator or all other participants (depending on your setup) over an authenticated // channel (confidentiality is not required). - // A participant keeps an internal state during the protocol run across the two rounds. + // A participant (or Signer) keeps an internal state during the protocol run across the two rounds. // A participant can pre-compute multiple commitments in advance: these commitments can be shared, but the // participant keeps an internal state of corresponding values, so it must the same instance or a backup of it using // the serialization functions. com := participant.Commit() - // Step 2: collect the commitments from the other participants and coordinator-chosen the message to sign, + // Step 2: collect the commitments from the other participants and coordinator-chosen message to sign, // and finalize by signing the message. commitments := make(frost.CommitmentList, threshold) commitments[0] = com - // This is not part of a participant's flow, but we need to collect the commitments of the other participants. + // This is not part of a participant's flow, but we need to collect the commitments of the other participants for + // the demo. { for i := uint64(1); i < threshold; i++ { signer, err := configuration.Signer(secretKeyShares[i]) @@ -102,8 +107,8 @@ func Example_signer() { // Output: Signing successful. } -// Example_coordinator shows how to aggregate signature shares into the final signature, and verify a FROST signature -// produced by multiple signers. +// Example_coordinator shows how to aggregate signature shares produced by signers into the final signature +// and verify a final FROST signature. func Example_coordinator() { maxSigners := uint64(5) threshold := uint64(3) @@ -170,7 +175,7 @@ func Example_coordinator() { // The coordinator assembles the shares. If the verify argument is set to true, AggregateSignatures will internally // verify each signature share and return an error on the first that is invalid. It will also verify whether the - // signature is valid. + // output signature is valid. signature, err := configuration.AggregateSignatures(message, signatureShares, commitments, true) if err != nil { panic(err) diff --git a/tests/configuration_test.go b/tests/configuration_test.go index f63c7f0..2146cb8 100644 --- a/tests/configuration_test.go +++ b/tests/configuration_test.go @@ -335,6 +335,27 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) } } +func TestConfiguration_ValidatePublicKeyShare_InvalidConfiguration(t *testing.T) { + expectedErrorPrefix := "invalid group public key, the key is nil" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 5, + } + configuration := &frost.Configuration{ + Ciphersuite: tt.Ciphersuite, + Threshold: tt.threshold, + MaxSigners: tt.maxSigners, + GroupPublicKey: nil, + SignerPublicKeyShares: nil, + } + + if err := configuration.ValidatePublicKeyShare(nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestConfiguration_ValidatePublicKeyShare_Nil(t *testing.T) { expectedErrorPrefix := "public key share is nil" tt := &tableTest{ @@ -425,25 +446,22 @@ func TestConfiguration_ValidatePublicKeyShare_InvalidPublicKey(t *testing.T) { } } -func TestConfiguration_ValidateKeyShare_InvalidConf(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite +func TestConfiguration_ValidateKeyShare_InvalidConfiguration(t *testing.T) { + expectedErrorPrefix := "invalid group public key, the key is nil" tt := &tableTest{ Ciphersuite: frost.Ristretto255, - threshold: 2, - maxSigners: 3, + threshold: 3, + maxSigners: 5, } - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(tt.Ciphersuite, nil, tt.threshold, tt.maxSigners) - publicKeyShares := getPublicKeyShares(keyShares) - configuration := &frost.Configuration{ - Ciphersuite: 2, + Ciphersuite: tt.Ciphersuite, Threshold: tt.threshold, MaxSigners: tt.maxSigners, - GroupPublicKey: groupPublicKey, - SignerPublicKeyShares: publicKeyShares, + GroupPublicKey: nil, + SignerPublicKeyShares: nil, } - if err := configuration.ValidateKeyShare(nil); err == nil || err.Error() != expectedErrorPrefix.Error() { + if err := configuration.ValidateKeyShare(nil); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } } @@ -894,6 +912,27 @@ func TestConfiguration_VerifySignatureShare_InvalidSignatureShare(t *testing.T) } } +func TestConfiguration_AggregateSignatures_InvalidConfiguration(t *testing.T) { + expectedErrorPrefix := "invalid group public key, the key is nil" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 5, + } + configuration := &frost.Configuration{ + Ciphersuite: tt.Ciphersuite, + Threshold: tt.threshold, + MaxSigners: tt.maxSigners, + GroupPublicKey: nil, + SignerPublicKeyShares: nil, + } + + if _, err := configuration.AggregateSignatures(nil, nil, nil, false); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestConfiguration_AggregateSignatures_InvalidCommitments(t *testing.T) { expectedErrorPrefix := "invalid list of commitments: too few commitments: expected at least 3 but got 2" tt := &tableTest{ diff --git a/tests/encoding_test.go b/tests/encoding_test.go index cc07ef1..2afc46a 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -548,8 +548,8 @@ func TestEncoding_Signer_InvalidLambda(t *testing.T) { }) } -func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { - expectedErrorPrefix := "failed to decode key share:" +func TestEncoding_Signer_BadKeyShare(t *testing.T) { + expectedErrorPrefix := "failed to decode key share: invalid group identifier" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] @@ -557,11 +557,28 @@ func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { offset := confLen + 6 // Set an invalid group in the key share encoding. - kse := s.KeyShare.Encode() - kse[0] = 2 + encoded := s.Encode() + encoded = slices.Replace(encoded, offset, offset+1, []byte{2}...) + + decoded := new(frost.Signer) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} +func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { + expectedErrorPrefix := "invalid key share: invalid identifier for public key share, the identifier is 0" + + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + confLen := len(s.Configuration.Encode()) + offset := confLen + 6 + 1 + + // Set an invalid identifier. encoded := s.Encode() - encoded = slices.Replace(encoded, offset, offset+len(kse), kse...) + badID := [8]byte{} + encoded = slices.Replace(encoded, offset, offset+8, badID[:]...) decoded := new(frost.Signer) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -718,7 +735,7 @@ func TestEncoding_SignatureShare_InvalidLength2(t *testing.T) { } } -func TestEncoding_SignatureShare_ZeroID(t *testing.T) { +func TestEncoding_SignatureShare_InvalidIdentifier(t *testing.T) { // todo: check for zero id in all decodings expectedError := errors.New("identifier cannot be 0") encoded := make([]byte, 41) @@ -909,6 +926,22 @@ func TestEncoding_Commitment_InvalidLength2(t *testing.T) { }) } +func TestEncoding_Commitment_InvalidIdentifier(t *testing.T) { + expectedErrorPrefix := "identifier cannot be 0" + + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + com.SignerID = 0 + encoded := com.Encode() + + decoded := new(frost.Commitment) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + func TestEncoding_Commitment_InvalidHidingNonce(t *testing.T) { expectedErrorPrefix := "invalid encoding of hiding nonce commitment: " @@ -1092,6 +1125,15 @@ func TestEncoding_KeyShare_JSON(t *testing.T) { if err := compareKeyShares(keyShare, decoded); err != nil { t.Fatal(err) } + + // expect error + decoded = new(frost.KeyShare) + expectedError := errors.New("invalid group identifier") + encoded = replaceStringInBytes(encoded, fmt.Sprintf("\"group\":%d", test.ECGroup()), "\"group\":70") + + if err := json.Unmarshal(encoded, decoded); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected error %q, got %q", expectedError, err) + } }) } @@ -1131,5 +1173,14 @@ func TestEncoding_PublicKeyShare_JSON(t *testing.T) { if err := comparePublicKeyShare(keyShare, decoded); err != nil { t.Fatal(err) } + + // expect error + decoded = new(frost.PublicKeyShare) + expectedError := errors.New("invalid group identifier") + encoded = replaceStringInBytes(encoded, fmt.Sprintf("\"group\":%d", test.ECGroup()), "\"group\":70") + + if err := json.Unmarshal(encoded, decoded); err == nil || err.Error() != expectedError.Error() { + t.Fatalf("expected error %q, got %q", expectedError, err) + } }) } diff --git a/tests/utils_test.go b/tests/utils_test.go index 6942903..04acce9 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -117,6 +117,13 @@ func expectErrorPrefix(expectedErrorMessagePrefix string, f func() error) error return nil } +func replaceStringInBytes(data []byte, old, new string) []byte { + s := string(data) + s = strings.Replace(s, old, new, 1) + + return []byte(s) +} + func TestConcatenate(t *testing.T) { inputs := [][]byte{ {1, 2, 3}, From 549391a6d84d7d1f2a757713647e5d8d7bce8c24 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:05:17 +0200 Subject: [PATCH 22/31] add tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 12 ++++---- encoding.go | 1 + frost.go | 3 +- internal/lambda.go | 20 ++++++++---- tests/commitment_test.go | 66 +++++++++++++++++++++++++++++++++++++++- tests/encoding_test.go | 31 ++++++++++++++++++- 6 files changed, 118 insertions(+), 15 deletions(-) diff --git a/commitment.go b/commitment.go index 76da4d9..6b50fb8 100644 --- a/commitment.go +++ b/commitment.go @@ -96,7 +96,7 @@ func (c CommitmentList) Participants() []uint64 { return out } -// ParticipantsScalar returns the group.Scalar list of participant identifier in the list +// ParticipantsScalar returns the group.Scalar list of participant identifier in the list. func (c CommitmentList) ParticipantsScalar() []*group.Scalar { if len(c) == 0 { return nil @@ -321,11 +321,11 @@ func (c *Configuration) validateCommitmentListLength(commitments CommitmentList) } // ValidateCommitmentList returns an error if at least one of the following conditions is not met: -// - list length is within [threshold;max] -// - no signer identifier in commitments is 0 -// - no singer identifier in commitments is > max signers -// - no duplicated in signer identifiers -// - all commitment signer identifiers are registered in the configuration +// - list length is within [threshold;max]. +// - no signer identifier in commitments is 0. +// - no singer identifier in commitments is > max signers. +// - no duplicated in signer identifiers. +// - all commitment signer identifiers are registered in the configuration. func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error { if !c.verified || !c.keysVerified { if err := c.Init(); err != nil { diff --git a/encoding.go b/encoding.go index ecc177f..dcf9256 100644 --- a/encoding.go +++ b/encoding.go @@ -46,6 +46,7 @@ func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { case encConf: return 1 + 3*8 + eLen + other[0] case encSigner: + _ = other[3] return other[0] + 2 + 2 + 2 + other[1] + other[2] + other[3] case encSigShare: return 1 + 8 + sLen diff --git a/frost.go b/frost.go index 0c0719d..ae820f0 100644 --- a/frost.go +++ b/frost.go @@ -231,7 +231,8 @@ func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { if pk.Equal(keyShare.PublicKey) != 1 { return errors.New( - "provided key share has a different public key than the one registered for that signer in the configuration", + "provided key share has a different public key than" + + "the one registered for that signer in the configuration", ) } diff --git a/internal/lambda.go b/internal/lambda.go index aa5d233..02d9655 100644 --- a/internal/lambda.go +++ b/internal/lambda.go @@ -19,9 +19,9 @@ import ( // Lambda derives the interpolating value for id in the polynomial made by the participant identifiers. // This function assumes that: -// - id is non-nil and != 0 -// - every scalar in participants is non-nil and != 0 -// - there are no duplicates in participants +// - id is non-nil and != 0. +// - every scalar in participants is non-nil and != 0. +// - there are no duplicates in participants. func Lambda(g group.Group, id uint64, participants []*group.Scalar) *group.Scalar { sid := g.NewScalar().SetUInt64(id) numerator := g.NewScalar().One() @@ -39,6 +39,8 @@ func Lambda(g group.Group, id uint64, participants []*group.Scalar) *group.Scala return numerator.Multiply(denominator.Invert()) } +// LambdaRegistry holds a signers pre-computed lambda values, indexed by the list of participants they are associated +// to. A sorted set of participants will yield the same lambda. type LambdaRegistry map[string]*group.Scalar const lambdaRegistryKeyDomainSeparator = "FROST-participants" @@ -50,9 +52,9 @@ func lambdaRegistryKey(participants []uint64) string { // New creates a new lambda and for the participant list for the participant id, and registers it. // This function assumes that: -// - id is non-nil and != 0 -// - every participant id is != 0 -// - there are no duplicates in participants +// - id is non-nil and != 0. +// - every participant id is != 0. +// - there are no duplicates in participants. func (l LambdaRegistry) New(g group.Group, id uint64, participants []uint64) *group.Scalar { polynomial := secretsharing.NewPolynomialFromListFunc(g, participants, func(p uint64) *group.Scalar { return g.NewScalar().SetUInt64(p) @@ -63,11 +65,14 @@ func (l LambdaRegistry) New(g group.Group, id uint64, participants []uint64) *gr return lambda } +// Get returns the recorded lambda for the list of participants, or nil if it wasn't found. func (l LambdaRegistry) Get(participants []uint64) *group.Scalar { key := lambdaRegistryKey(participants) return l[key] } +// GetOrNew returns the recorded lambda for the list of participants, or created, records, and returns a new one if +// it wasn't found. func (l LambdaRegistry) GetOrNew(g group.Group, id uint64, participants []uint64) *group.Scalar { lambda := l.Get(participants) if lambda == nil { @@ -77,17 +82,20 @@ func (l LambdaRegistry) GetOrNew(g group.Group, id uint64, participants []uint64 return lambda } +// Set records lambda for the given set of participants. func (l LambdaRegistry) Set(participants []uint64, lambda *group.Scalar) { key := lambdaRegistryKey(participants) l[key] = lambda } +// Delete deletes the lambda for the given set of participants. func (l LambdaRegistry) Delete(participants []uint64) { key := lambdaRegistryKey(participants) l[key].Zero() delete(l, key) } +// Decode populates the receiver from the byte encoded serialization in data. func (l LambdaRegistry) Decode(g group.Group, data []byte) error { offset := 0 for offset < len(data) { diff --git a/tests/commitment_test.go b/tests/commitment_test.go index 0add67e..daebe6f 100644 --- a/tests/commitment_test.go +++ b/tests/commitment_test.go @@ -17,6 +17,27 @@ import ( "github.com/bytemare/frost" ) +func TestCommitment_Validate_InvalidConfiguration(t *testing.T) { + expectedErrorPrefix := "invalid group public key, the key is nil" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 5, + } + configuration := &frost.Configuration{ + Ciphersuite: tt.Ciphersuite, + Threshold: tt.threshold, + MaxSigners: tt.maxSigners, + GroupPublicKey: nil, + SignerPublicKeyShares: nil, + } + + if err := configuration.ValidateCommitment(nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestCommitment_Validate_NilCommitment(t *testing.T) { expectedErrorPrefix := "the commitment list has a nil commitment" tt := &tableTest{ @@ -24,7 +45,7 @@ func TestCommitment_Validate_NilCommitment(t *testing.T) { threshold: 3, maxSigners: 4, } - configuration, _ := fullSetup(t, tt) + configuration := makeConf(t, tt) if err := configuration.ValidateCommitment(nil); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -231,6 +252,27 @@ func TestCommitmentList_Sort(t *testing.T) { }) } +func TestCommitmentList_Validate_InvalidConfiguration(t *testing.T) { + expectedErrorPrefix := "invalid group public key, the key is nil" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 5, + } + configuration := &frost.Configuration{ + Ciphersuite: tt.Ciphersuite, + Threshold: tt.threshold, + MaxSigners: tt.maxSigners, + GroupPublicKey: nil, + SignerPublicKeyShares: nil, + } + + if err := configuration.ValidateCommitmentList(nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestCommitmentList_Validate_NoCommitments(t *testing.T) { expectedErrorPrefix := "commitment list is empty" tt := &tableTest{ @@ -345,6 +387,28 @@ func TestCommitmentList_Validate_InvalidCommitment(t *testing.T) { } } +func TestCommitmentList_Validate_NilCommitment(t *testing.T) { + expectedErrorPrefix := "the commitment list has a nil commitment" + tt := &tableTest{ + Ciphersuite: frost.Ristretto255, + threshold: 3, + maxSigners: 4, + } + configuration, signers := fullSetup(t, tt) + coms := make(frost.CommitmentList, len(signers)) + + for i, s := range signers { + coms[i] = s.Commit() + } + + coms[2] = nil + + if err := configuration.ValidateCommitmentList(coms); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestCommitmentList_Validate_NotSorted(t *testing.T) { expectedErrorPrefix := "commitment list is not sorted by signer identifiers" tt := &tableTest{ diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 2afc46a..f0fd075 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -374,7 +374,7 @@ func TestEncoding_Configuration_InvalidGroupPublicKey(t *testing.T) { }) } -func TestEncoding_Configuration_InvalidPublicKeyShare(t *testing.T) { +func TestEncoding_Configuration_BadPublicKeyShare(t *testing.T) { expectedErrorPrefix := "could not decode signer public key share for signer 1: " testAll(t, func(t *testing.T, test *tableTest) { @@ -407,6 +407,35 @@ func TestEncoding_Configuration_InvalidPublicKeyShare(t *testing.T) { }) } +func TestEncoding_Configuration_InvalidPublicKeyShares(t *testing.T) { + expectedErrorPrefix := "invalid number of public keys (lower than threshold or above maximum)" + + testAll(t, func(t *testing.T, test *tableTest) { + keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + nil, + test.threshold, + test.maxSigners, + ) + publicKeyShares := getPublicKeyShares(keyShares) + + configuration := &frost.Configuration{ + Ciphersuite: test.Ciphersuite, + Threshold: test.threshold, + MaxSigners: test.maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, + } + configuration.SignerPublicKeyShares = configuration.SignerPublicKeyShares[:test.threshold-1] + encoded := configuration.Encode() + + decoded := new(frost.Configuration) + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + func TestEncoding_Configuration_CantVerify_InvalidPubKey(t *testing.T) { expectedErrorPrefix := "invalid group public key, the key is the group generator (base element)" From 683d02c5ee0d536eeba240f479401934e61856e1 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:09:20 +0200 Subject: [PATCH 23/31] fix typo Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- frost.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frost.go b/frost.go index ae820f0..65050bf 100644 --- a/frost.go +++ b/frost.go @@ -231,7 +231,7 @@ func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { if pk.Equal(keyShare.PublicKey) != 1 { return errors.New( - "provided key share has a different public key than" + + "provided key share has a different public key than " + "the one registered for that signer in the configuration", ) } From 768f36fff3a7288ed961ec00deda57d97e7c2333 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:23:04 +0200 Subject: [PATCH 24/31] add tests, comments, documentation Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- .github/.golangci.yml | 11 +- README.md | 215 +++++++++++++++++++++++++++++++++--- commitment.go | 39 +++++-- coordinator.go | 37 ++++--- debug/debug.go | 25 +++-- encoding.go | 26 +++-- examples_test.go | 24 +++- frost.go | 121 +++++++++++++------- internal/errors.go | 3 + keys.go => keys/keys.go | 3 +- signer.go | 6 +- tests/commitment_test.go | 2 +- tests/configuration_test.go | 17 +-- tests/dkg_test.go | 8 +- tests/encoding_test.go | 25 +++-- tests/frost_test.go | 3 +- tests/misc_test.go | 9 +- tests/vector_utils_test.go | 9 +- tests/vectors_test.go | 3 +- 19 files changed, 439 insertions(+), 147 deletions(-) rename keys.go => keys/keys.go (97%) diff --git a/.github/.golangci.yml b/.github/.golangci.yml index f64a5cd..8507b20 100644 --- a/.github/.golangci.yml +++ b/.github/.golangci.yml @@ -31,7 +31,7 @@ linters: - gci - ginkgolinter - gocheckcompilerdirectives - - gochecknoglobals + #- gochecknoglobals - gochecknoinits - gochecksumtype - gocognit @@ -111,7 +111,7 @@ linters: linters-settings: cyclop: - max-complexity: 11 + max-complexity: 15 skip-tests: true dupl: threshold: 100 @@ -163,6 +163,13 @@ linters-settings: simplify: true goimports: local-prefixes: github.com/bytemare/frost + gosec: + excludes: + - G115 + config: + G602: + frost: + - encodedLength gosimple: checks: [ "all" ] govet: diff --git a/README.md b/README.md index ab7ffb2..5be3428 100644 --- a/README.md +++ b/README.md @@ -8,33 +8,214 @@ import "github.com/bytemare/frost" ``` -This package implements [FROST Flexible Round-Optimized Schnorr Threshold](https://datatracker.ietf.org/doc/draft-irtf-cfrg-frost) and the [FROST Distributed Key Generation](https://eprint.iacr.org/2020/852.pdf) protocols. +This package implements [RFC9591 - The FROST Flexible Round-Optimized Schnorr Threshold](https://datatracker.ietf.org/doc/rfc9591/) protocol. FROST provides Two-Round Threshold Schnorr Signatures. -#### What is frost? +The Ristretto255, Edwards25519, Secp256k1, and NIST elliptic curve groups are fully supported. -> FROST reduces network overhead during threshold signing operations while employing a novel technique to protect -> against forgery attacks applicable to prior Schnorr-based threshold signature constructions. FROST signatures can be -> issued after a threshold number of entities cooperate to compute a signature, allowing for improved distribution of -> trust and redundancy with respect to a secret key. +The [FROST Distributed Key Generation](https://github.com/bytemare/dkg) protocol produces compatible keys, as described +in the [original work](https://eprint.iacr.org/2020/852.pdf). + +### Requirements + +- When communicating at protocol execution, network channels don't need to be confidential but *MUST* be authenticated. This + package verifies a lot of things with regard to the correctness to the protocol, but it assumes that signers and coordinators + really communicate with the relevant peer. +- Long-term fixed configuration values *MUST* be known to all participant signers and coordinators (i.e. the ciphersuite, + threshold and maximum amount of signers, and the public key for signature verification) +- For every signing session, at least the public key shares of all other participants *MUST* be known to all participant + signers and coordinators (which can be a subset t-among-n of the initial key generation setup) +- Data provided to these functions (especially when received over the network) *MUST* be deserialized using the corresponding + decoding functions. If data deserialization/decoding fails for a signer, protocol execution must be aborted. +- Identifiers (for participants/signers) *MUST* be between 1 and n, which is the maximum amount of participants defined at key generation. #### Supported Ciphersuites -| ID | Name | Backend | -|-----|--------------|-------------------------------| -| 1 | Edwards25519 | filippo.io/edwards25519 | -| 2 | Ristretto255 | github.com/gtank/ristretto255 | -| 3 | Edwards448 | not yet supported | -| 4 | P-256 | filippo.io/nistec | -| 5 | Secp256k1 | github.com/bytemare/crypto | +| ID | Name | Backend | +|----|----------------------------|-------------------------------| +| 1 | Ristretto255 (recommended) | github.com/gtank/ristretto255 | +| 3 | P-256 | filippo.io/nistec | +| 4 | P-384 | filippo.io/nistec | +| 5 | P-521 | filippo.io/nistec | +| 6 | Edwards25519 | filippo.io/edwards25519 | +| 7 | Secp256k1 | github.com/bytemare/crypto | + +The groups, scalars (secret keys and nonces), and group elements (public keys and commitments) are opaque objects that +expose all necessary cryptographic and serialization functions. +If you have existing cryptographic material in their canonical encodings, they can of course be imported. + +## Usage + +Usage examples and comments can be found in [examples_test.go](https://github.com/bytemare/frost/blob/main/examples_test.go). + +### Key Generation + +The [FROST Distributed Key Generation](https://github.com/bytemare/dkg) is recommended to produce key material for all +participants in the setup. This package also puts out KeyShares and PublicKeyShares ready to use with this FROST implementation. +It also ensures correct identifier generation compatible with FROST. + +It is heavily recommended to use the same instances for distributed key generation and signing, as this will avoid that +the secret key material leaves that instance. + +For testing and debugging _only_, the [debug package](https://github.com/bytemare/frost/debug) provides a centralised +key generation with a trusted dealer. + +### Key Management + +If the [DKG](https://github.com/bytemare/dkg) package was used to generate keys, signers can use the produced KeyShare +and must communicate their PublicKeyShare to the coordinator and other signers. + +It is easy to encode and decode these key shares and public key shares for transmission and storage, +using the ```Encode()``` and ```Decode()``` methods (or in JSON marshalling). + +#### Import existing identifiers and keys + +Existing key material (e.g. identifiers, secret public, public keys) that has been generated otherwise (or transmitted or backed up) +and encoded in their canonical byte representation can be imported. + +To create a ```KeyShare``` and ```PublicKeyShare``` from individually encoded secret and public keys, use the +```keys.NewKeyShare()``` and ```NewPublicKeyShare()``` functions, respectively. +If a ```KeyShare``` or ```PublicKeyShare``` have been encoded using their respective ```Encode()``` method, they can be +easily recovered using the corresponding ```Decode()``` method. + +More generally, to decode an element (or point) in the Ristretto255 group, +```go +import ( + group "https://github.com/bytemare" +) + +bytesPublicKey := []byte{1, 2, 3, ...} + +g := group.Ristretto255Sha512 + +publicKey := g.NewElement() +if err := publicKey.Decode(bytesPublicKey); err != nil { + return fmt.Errorf("can't decode public key: %w", err) +} +``` + +The same goes for secret keys (or scalars), +```go +import ( + group "https://github.com/bytemare" +) + +bytesSecretKey := []byte{1, 2, 3, ...} + +g := group.Ristretto255Sha512 + +secretKey := g.NewScalar() +if err := secretKey.Decode(bytesSecretKey); err != nil { + return fmt.Errorf("can't decode secret key: %w", err) +} +``` + +and any other byte or json encoded structure. + +### Setup + +Both signers and coordinators must first instantiate a ```Configuration``` with the long-term fixed values as used at +key generation: +- the ciphersuite (see the frost.Ciphersuite values for available ciphersuites) +- threshold (t) and maximum amount of signers (n) +- the global public key for signature verification (as put out at key generation) + +Then add the PublicKeyShares of the participants (or signers). For simplicity, it is recommended to add all PublicKeyShares +of the all participants from the key generation step. It is sufficient, though, to only use the shares for the signers that +will participate in a signing session (which can be a subset _t among n_). + +```go +configuration := &frost.Configuration{ + Ciphersuite: ciphersuite, + Threshold: threshold, + MaxSigners: maxSigners, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, + } + +if err := configuration.Init(); err != nil { + return err +} +``` + +This configuration can be encoded for transmission and offline storage, and re-instantiated using its +```Encode()``` and ```Decode()``` methods. This avoids having to store the parameters separately. + +#### Signers + +Once the configuration is initialised, setting up a signer is straightforward, using the ```Signer()``` method +and providing the signer's ```KeyShare```. + +### Protocol execution + +FROST is a two round signing protocol, in which the first round can be asynchronously pre-computed, so that signing can +actually be done in one round when necessary. + +First Round: Signer commitment +- Signers commit to internal nonces, by calling the ```commitment := signer.Commit()``` method, which returns one commitment +stores corresponding nonces internally. In this manner, signers can produce many commitments before signing sessions start. +Signers send these commitments to either a coordinator or all other signers. Note that a commitment is not function of the +future message to sign, so a signer can produce them without knowing the message in advance. +- The coordinator (or all other signers) collect these commitments, into a list. + +Second Round: Signing +- The coordinator broadcasts the message to be signed and a list of commitments, one from each signer, to each signer. +- The signers sign the message ```sigShare, err := signer.Sign(message, commitmentList)```, each producing their signature share. +- These signature shares must then be shared and aggregated to produce the final signature, ```signature, err := configuration.AggregateSignatures(message, sigShares, commitmentList, true)```. + +#### Coordinator + +The coordinator does not have any secret or private information, and must never have. It is also assumed to behave honestly. + +Commitments received by signers have an identifier, which allows for triage and registration. Commitments must only be +used once. The coordinator may further hedge against nonce-reuse by tracking the nonce commitments used for a given group key. + +If the ```verify``` argument in the ```AggregateSignatures()``` is set to ```true``` (recommended), signature shares and output signature are thoroughly verified. +Upon error or invalid share, the error message indicates the first invalid share it encountered. +A coordinator should always verify the signature after ```AggregateSignatures()``` if the ```verify``` argument has been set to ```false```. + +If verification fails, the coordinator can then check signature shares individually to deter the misbehaving signer, leveraging the authenticated channel associated to them. +That signer can then be denied of further contributions. + +### Resumption and storage + +Configurations, keys, commitments, commitment lists, and even signers can be backed up to byte strings for transmission and storage, +and re-instantiated from them. To decode, just create that object and use its ```Decode()``` method. + +For example, to back up a signer with its private keys and commitments, use: +```go +bytes := signer.Encode() +``` + +To re-instantiate that same signer from the byte string, do: +```go +// bytes := signer.Encode() + +signer := new(frost.Signer) +if err := signer.Decode(bytes); err != nil { + return err +} +``` + +Keep in mind that signer encoding embeds the private key and secret nonces, and that they must be secured accordingly. + +## Notes + +Signers have local secret data and state, offline and during protocol execution: +- the long term secret key +- the internally stored commitment nonces, maintained between commitment and signature -#### References -- [The original paper](https://eprint.iacr.org/2020/852.pdf) from Chelsea Komlo and Ian Goldberg. -- [The Github repo](https://github.com/cfrg/draft-irtf-cfrg-frost) where the draft is being specified. +- FROST is _not robust_ by design. + - This means that there is a misbehaving participant if signature aggregation fails + (or if the output signature is not valid), in which case the protocol should be aborted and the problem investigated + (you shouldn't have a compromised or misbehaving participant in a sane infrastructure). + - Misbehaving signers can DOS the protocol by providing wrong sig shares or not contributing. +- The coordinator may further hedge against nonce-reuse by tracking the nonce commitments used for a given group key +- For message pre-hashing, see [RFC](https://datatracker.ietf.org/doc/rfc9591) ## Documentation [![Go Reference](https://pkg.go.dev/badge/github.com/bytemare/frost.svg)](https://pkg.go.dev/github.com/bytemare/frost) -You can find the documentation and usage examples in [the package doc](https://pkg.go.dev/github.com/bytemare/frost). +You can find the godoc documentation and usage examples in [the package doc](https://pkg.go.dev/github.com/bytemare/frost). ## Versioning diff --git a/commitment.go b/commitment.go index 6b50fb8..cabd930 100644 --- a/commitment.go +++ b/commitment.go @@ -21,9 +21,13 @@ import ( ) var ( - errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") - errInvalidCiphersuite = errors.New("ciphersuite not available") - errInvalidLength = errors.New("invalid encoding length") + errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") + errInvalidCiphersuite = errors.New("ciphersuite not available") + errInvalidLength = errors.New("invalid encoding length") + errCommitmentNil = errors.New("the commitment is nil") + errCommitmentListEmpty = errors.New("commitment list is empty") + errCommitmentListNotSorted = errors.New("commitment list is not sorted by signer identifiers") + errCommitmentListHasNil = errors.New("the commitment list has a nil commitment") ) // Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. @@ -113,6 +117,7 @@ func (c CommitmentList) ParticipantsScalar() []*group.Scalar { }) } +// Encode serializes the CommitmentList into a compact byte encoding. func (c CommitmentList) Encode() []byte { n := len(c) if n == 0 { @@ -132,6 +137,7 @@ func (c CommitmentList) Encode() []byte { return out } +// DecodeList decodes a byte string produced by the CommitmentList.Encode() method. func DecodeList(data []byte) (CommitmentList, error) { if len(data) < 9 { return nil, errInvalidLength @@ -164,7 +170,7 @@ func DecodeList(data []byte) (CommitmentList, error) { return c, nil } -func (c CommitmentList) GroupCommitmentAndBindingFactors( +func (c CommitmentList) groupCommitmentAndBindingFactors( publicKey *group.Element, message []byte, ) (*group.Element, BindingFactors) { @@ -255,7 +261,7 @@ func (c *Configuration) ValidateCommitment(commitment *Commitment) error { } if commitment == nil { - return fmt.Errorf("the commitment list has a nil commitment") + return errCommitmentNil } if err := c.validateIdentifier(commitment.SignerID); err != nil { @@ -306,7 +312,7 @@ func (c *Configuration) validateCommitmentListLength(commitments CommitmentList) length := uint64(len(commitments)) if length == 0 { - return fmt.Errorf("commitment list is empty") + return errCommitmentListEmpty } if length < c.Threshold { @@ -355,15 +361,24 @@ func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error // List must be sorted, compare with the next commitment. if i <= len(commitments)-2 { - if commitments[i+1] == nil { - return fmt.Errorf("the commitment list has a nil commitment") - } - - if cmpID(commitment, commitments[i+1]) > 0 { - return fmt.Errorf("commitment list is not sorted by signer identifiers") + if err := compareCommitments(commitment, commitments[i+1]); err != nil { + return err } } } return nil } + +func compareCommitments(c1, c2 *Commitment) error { + if c2 == nil { + return errCommitmentListHasNil + } + + // if the current commitment has an id higher than the next one, return error. + if cmpID(c1, c2) > 0 { + return errCommitmentListNotSorted + } + + return nil +} diff --git a/coordinator.go b/coordinator.go index 82fd9da..157c050 100644 --- a/coordinator.go +++ b/coordinator.go @@ -62,21 +62,13 @@ func (c *Configuration) AggregateSignatures( } } - // Aggregate signatures - z := group.Group(c.Ciphersuite).NewScalar() - for _, sigShare := range sigShares { - if err := c.validateSignatureShareLight(sigShare); err != nil { - return nil, err - } - - z.Add(sigShare.SignatureShare) - } - - signature := &Signature{ - R: groupCommitment, - Z: z, + // Aggregate signatures. + signature, err := c.sumShares(sigShares, groupCommitment) + if err != nil { + return nil, err } + // Verify the final signature. if verify { if err = VerifySignature(c.Ciphersuite, message, signature, c.GroupPublicKey); err != nil { // difficult to reach, because if all shares are valid, the final signature is valid. @@ -87,6 +79,23 @@ func (c *Configuration) AggregateSignatures( return signature, nil } +func (c *Configuration) sumShares(shares []*SignatureShare, groupCommitment *group.Element) (*Signature, error) { + z := group.Group(c.Ciphersuite).NewScalar() + + for _, sigShare := range shares { + if err := c.validateSignatureShareLight(sigShare); err != nil { + return nil, err + } + + z.Add(sigShare.SignatureShare) + } + + return &Signature{ + R: groupCommitment, + Z: z, + }, nil +} + // VerifySignatureShare verifies a signature share. sigShare is the signer's signature share to be verified. // // The CommitmentList must be sorted in ascending order by identifier. @@ -119,7 +128,7 @@ func (c *Configuration) prepareSignatureShareVerification(message []byte, return nil, nil, nil, fmt.Errorf("invalid list of commitments: %w", err) } - groupCommitment, bindingFactors := commitments.GroupCommitmentAndBindingFactors(c.GroupPublicKey, message) + groupCommitment, bindingFactors := commitments.groupCommitmentAndBindingFactors(c.GroupPublicKey, message) participants := commitments.ParticipantsScalar() return groupCommitment, bindingFactors, participants, nil diff --git a/debug/debug.go b/debug/debug.go index 81ab74b..1892641 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -19,6 +19,7 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/internal" + "github.com/bytemare/frost/keys" ) // TrustedDealerKeygen uses Shamir and Verifiable Secret Sharing to create secret shares of an input group secret. If @@ -31,7 +32,7 @@ func TrustedDealerKeygen( secret *group.Scalar, threshold, maxSigners uint64, coeffs ...*group.Scalar, -) ([]*frost.KeyShare, *group.Element, []*group.Element) { +) ([]*keys.KeyShare, *group.Element, []*group.Element) { g := group.Group(c) if secret == nil { @@ -51,9 +52,9 @@ func TrustedDealerKeygen( coms := secretsharing.Commit(g, poly) - shares := make([]*frost.KeyShare, maxSigners) + shares := make([]*keys.KeyShare, maxSigners) for i, k := range privateKeyShares { - shares[i] = &frost.KeyShare{ + shares[i] = &keys.KeyShare{ Secret: k.Secret, GroupPublicKey: coms[0], PublicKeyShare: secretsharing.PublicKeyShare{ @@ -70,19 +71,19 @@ func TrustedDealerKeygen( // 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(c frost.Ciphersuite, keyShares []*frost.KeyShare) (*group.Scalar, error) { +func RecoverGroupSecret(c frost.Ciphersuite, keyShares []*keys.KeyShare) (*group.Scalar, error) { if !c.Available() { return nil, internal.ErrInvalidCiphersuite } g := group.Group(c) - keys := make([]secretsharing.Share, len(keyShares)) + publicKeys := make([]secretsharing.Share, len(keyShares)) for i, v := range keyShares { - keys[i] = v + publicKeys[i] = v } - secret, err := secretsharing.CombineShares(g, keys) + secret, err := secretsharing.CombineShares(g, publicKeys) if err != nil { return nil, fmt.Errorf("failed to reconstruct group secret: %w", err) } @@ -129,22 +130,22 @@ func RecoverPublicKeys( } g := group.Group(c) - keys := make([]*group.Element, maxSigners) + publicKeys := make([]*group.Element, maxSigners) for i := uint64(1); i <= maxSigners; i++ { pki, err := secretsharing.PubKeyForCommitment(g, i, commitment) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("can't recover public keys: %w", err) } - keys[i-1] = pki + publicKeys[i-1] = pki } - return commitment[0], keys, nil + return commitment[0], publicKeys, nil } // 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 { +func VerifyVSS(g group.Group, share *keys.KeyShare, commitment []*group.Element) bool { pk := g.Base().Multiply(share.SecretKey()) return secretsharing.Verify(g, share.Identifier(), pk, commitment) } diff --git a/encoding.go b/encoding.go index dcf9256..047f310 100644 --- a/encoding.go +++ b/encoding.go @@ -18,6 +18,7 @@ import ( group "github.com/bytemare/crypto" "github.com/bytemare/frost/internal" + "github.com/bytemare/frost/keys" ) const ( @@ -86,8 +87,8 @@ func (c *Configuration) Encode() []byte { } type confHeader struct { - g group.Group - h, t, n, nPks, length uint64 + g group.Group + h, t, n, pksLen, nPks, length uint64 } func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { @@ -116,6 +117,7 @@ func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { h: 25, t: t, n: n, + pksLen: pksLen, nPks: nPks, length: length, }, nil @@ -132,8 +134,7 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { } offset := header.h + uint64(header.g.ElementLength()) - pksLen := encodedLength(encPubKeyShare, header.g, header.t*uint64(header.g.ElementLength())) - pks := make([]*PublicKeyShare, header.nPks) + pks := make([]*keys.PublicKeyShare, header.nPks) conf := &Configuration{ Ciphersuite: Ciphersuite(header.g), @@ -141,6 +142,9 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { MaxSigners: header.n, GroupPublicKey: gpk, SignerPublicKeyShares: pks, + group: header.g, + verified: false, + keysVerified: false, } if err := conf.verifyConfiguration(); err != nil { @@ -148,12 +152,12 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { } for j := range header.nPks { - pk := new(PublicKeyShare) - if err := pk.Decode(data[offset : offset+pksLen]); err != nil { + pk := new(keys.PublicKeyShare) + if err := pk.Decode(data[offset : offset+header.pksLen]); err != nil { return fmt.Errorf("could not decode signer public key share for signer %d: %w", j, err) } - offset += pksLen + offset += header.pksLen pks[j] = pk } @@ -211,7 +215,7 @@ func (s *Signer) Encode() []byte { for k, v := range s.LambdaRegistry { b, err := hex.DecodeString(k) if err != nil { - panic(fmt.Sprintf("failed te revert hex encoding to bytes of %s", k)) + panic("failed te revert hex encoding to bytes of " + k) } out = append(out, b...) @@ -287,7 +291,7 @@ func (s *Signer) Decode(data []byte) error { offset := header.length + 6 - keyShare := new(KeyShare) + keyShare := new(keys.KeyShare) if err = keyShare.Decode(data[offset : offset+ksLen]); err != nil { return fmt.Errorf("failed to decode key share: %w", err) } @@ -298,15 +302,17 @@ func (s *Signer) Decode(data []byte) error { offset += ksLen stop := offset + nLambdas*lLem + lambdaRegistry := make(internal.LambdaRegistry, lLem) if err = lambdaRegistry.Decode(g, data[offset:stop]); err != nil { - return err + return fmt.Errorf("failed to decode lambda registry in signer: %w", err) } offset = stop commitments := make(map[uint64]*Nonce) comLen := encodedLength(encCommitment, g) nComLen := encodedLength(encNonceCommitment, g) + for offset < uint64(len(data)) { id := binary.LittleEndian.Uint64(data[offset : offset+8]) diff --git a/examples_test.go b/examples_test.go index 85ca1f6..d1a6bd1 100644 --- a/examples_test.go +++ b/examples_test.go @@ -13,6 +13,7 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/debug" + "github.com/bytemare/frost/keys" ) // Example_signer shows the execution steps of a FROST participant. @@ -20,7 +21,7 @@ func Example_signer() { maxSigners := uint64(5) threshold := uint64(3) message := []byte("example message") - ciphersuite := frost.Ristretto255 + ciphersuite := frost.Default // We assume you already have a pool of participants with distinct non-zero identifiers in [1:maxSingers] // and their signing share. @@ -33,7 +34,7 @@ func Example_signer() { // At key generation, each participant must send their public key share to the coordinator, and the collection must // be broadcast to every participant. - publicKeyShares := make([]*frost.PublicKeyShare, len(secretKeyShares)) + publicKeyShares := make([]*keys.PublicKeyShare, len(secretKeyShares)) for i, sk := range secretKeyShares { publicKeyShares[i] = sk.Public() } @@ -113,7 +114,7 @@ func Example_coordinator() { maxSigners := uint64(5) threshold := uint64(3) message := []byte("example message") - ciphersuite := frost.Ristretto255 + ciphersuite := frost.Default // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. // The following block uses a centralised trusted dealer to do this, but it is strongly recommended to use @@ -124,7 +125,7 @@ func Example_coordinator() { // At key generation, each participant must send their public key share to the coordinator, and the collection must // be broadcast to every participant. - publicKeyShares := make([]*frost.PublicKeyShare, len(secretKeyShares)) + publicKeyShares := make([]*keys.PublicKeyShare, len(secretKeyShares)) for i, sk := range secretKeyShares { publicKeyShares[i] = sk.Public() } @@ -207,3 +208,18 @@ func Example_coordinator() { // Output: Signature is valid. } + +func Example_key_generation() { +} + +func Example_existing_keys() { +} + +// Example_serialization shows how to encode and decode data used in FROST. +func Example_serialization() { + // Private keys and scalars. + + // Public keys and elements. + + // Key shares +} diff --git a/frost.go b/frost.go index 65050bf..db58e3d 100644 --- a/frost.go +++ b/frost.go @@ -15,8 +15,10 @@ import ( "math/big" group "github.com/bytemare/crypto" + secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/frost/internal" + "github.com/bytemare/frost/keys" ) /* @@ -32,39 +34,16 @@ import ( - Chu: https://eprint.iacr.org/2023/899 - re-randomize keys: https://eprint.iacr.org/2024/436.pdf -Requirements: -- group MUST be of prime order -- threshold <= max -- max < order -- identifier is in [1:max] and must be distinct form other ids -- each participant MUST know the group public key -- each participant MUST know the pub key of each other -- network channels must be authenticated (confidentiality is not required) -- Signers have local secret data - - secret key is long term - - committed nonces between commitment and signature - -- When receiving the commitment list, each elements must be deserialized, and upon error, the signer MUST abort the - protocol -- A signer must check whether their id and commitment appear in the commitment list - -- A coordinator aggregates, and then should verify the signature. If signature fails, then check shares. - TODO: -- verify serialize and deserialize functions of messages, scalars, and elements -Notes: -- Frost is not robust, i.e. - - if aggregated signature is not valid, SHOULD abort - - misbehaving signers can DOS the protocol by providing wrong sig shares or not contributing -- Wrong shares can be identified, and with the authenticated channel associated with the signer, which can then be -denied of further contributions -- R255 is recommended -- the coordinator does not not have any secret or private information -- the coordinator is assumed to behave honestly -- the coordinator may further hedge against nonce reuse by tracking the nonce commitments used for a given group key -- for message pre-hashing, see RFC +- identifiers, min and max, are uint16 +- verify serialize and deserialize functions of messages, scalars, and elements +- add deserialization examples with hardcoded input +- add versioning to encodings +- align on serialization of https://frost.zfnd.org/user/serialization.html (good doc)\ +- make a `go run`-able program to generate trusted dealer keys +- DKG: can derive an identifier from a byte string (e.g. name or email address) */ @@ -72,10 +51,10 @@ denied of further contributions type Ciphersuite byte const ( - // Ed25519 uses Edwards25519 and SHA-512, producing Ed25519-compliant signatures as specified in RFC8032. - Ed25519 = Ciphersuite(group.Edwards25519Sha512) + // Default and recommended ciphersuite for FROST. + Default = Ristretto255 - // Ristretto255 uses Ristretto255 and SHA-512. + // Ristretto255 uses Ristretto255 and SHA-512. This ciphersuite is recommended. Ristretto255 = Ciphersuite(group.Ristretto255Sha512) // Ed448 uses Edwards448 and SHAKE256, producing Ed448-compliant signatures as specified in RFC8032. @@ -90,6 +69,9 @@ const ( // P521 uses P-521 and SHA-512. P521 = Ciphersuite(group.P521Sha512) + // Ed25519 uses Edwards25519 and SHA-512, producing Ed25519-compliant signatures as specified in RFC8032. + Ed25519 = Ciphersuite(group.Edwards25519Sha512) + // Secp256k1 uses Secp256k1 and SHA-256. Secp256k1 = Ciphersuite(group.Secp256k1) ) @@ -116,7 +98,7 @@ func (c Ciphersuite) ECGroup() group.Group { // Configuration holds the Configuration for a signing session. type Configuration struct { GroupPublicKey *group.Element - SignerPublicKeyShares []*PublicKeyShare + SignerPublicKeyShares []*keys.PublicKeyShare Threshold uint64 MaxSigners uint64 Ciphersuite Ciphersuite @@ -131,6 +113,8 @@ var ( errInvalidNumberOfPublicKeys = errors.New("invalid number of public keys (lower than threshold or above maximum)") ) +// Init verifies whether the configuration's components are valid, in which case it initializes internal values, or +// returns an error otherwise. func (c *Configuration) Init() error { if !c.verified { if err := c.verifyConfiguration(); err != nil { @@ -148,7 +132,7 @@ func (c *Configuration) Init() error { } // Signer returns a new participant of the protocol instantiated from the Configuration and the signer's key share. -func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { +func (c *Configuration) Signer(keyShare *keys.KeyShare) (*Signer, error) { if !c.verified || !c.keysVerified { if err := c.Init(); err != nil { return nil, err @@ -169,7 +153,9 @@ func (c *Configuration) Signer(keyShare *KeyShare) (*Signer, error) { }, nil } -func (c *Configuration) ValidatePublicKeyShare(pks *PublicKeyShare) error { +// ValidatePublicKeyShare returns an error if they PublicKeyShare has invalid components or properties that not +// compatible with the configuration. +func (c *Configuration) ValidatePublicKeyShare(pks *keys.PublicKeyShare) error { if !c.verified { if err := c.verifyConfiguration(); err != nil { return err @@ -195,7 +181,9 @@ func (c *Configuration) ValidatePublicKeyShare(pks *PublicKeyShare) error { return nil } -func (c *Configuration) ValidateKeyShare(keyShare *KeyShare) error { +// ValidateKeyShare returns an error if they KeyShare has invalid components or properties that not compatible with the +// configuration. +func (c *Configuration) ValidateKeyShare(keyShare *keys.KeyShare) error { if !c.verified || !c.keysVerified { if err := c.Init(); err != nil { return err @@ -324,7 +312,7 @@ func (c *Configuration) getSignerPubKey(id uint64) *group.Element { func (c *Configuration) validateIdentifier(id uint64) error { switch { case id == 0: - return errors.New("identifier is 0") + return internal.ErrIdentifierIs0 case id > c.MaxSigners: return fmt.Errorf("identifier %d is above authorized range [1:%d]", id, c.MaxSigners) } @@ -379,3 +367,60 @@ func VerifySignature(c Ciphersuite, message []byte, signature *Signature, public return nil } + +// NewPublicKeyShare returns a PublicKeyShare from separately encoded key material. To deserialize a byte string +// produced by the PublicKeyShare.Encode() method, use the PublicKeyShare.Decode() method. +func NewPublicKeyShare(c Ciphersuite, id uint64, signerPublicKey []byte) (*keys.PublicKeyShare, error) { + if !c.Available() { + return nil, internal.ErrInvalidCiphersuite + } + + if id == 0 { + return nil, internal.ErrIdentifierIs0 + } + + g := c.ECGroup() + + pk := g.NewElement() + if err := pk.Decode(signerPublicKey); err != nil { + return nil, fmt.Errorf("could not decode public share: %w", err) + } + + return &keys.PublicKeyShare{ + PublicKey: pk, + ID: id, + Group: g, + Commitment: nil, + }, nil +} + +// NewKeyShare returns a KeyShare from separately encoded key material. To deserialize a byte string produced by the +// KeyShare.Encode() method, use the KeyShare.Decode() method. +func NewKeyShare( + c Ciphersuite, + id uint64, + secretShare, signerPublicKey, groupPublicKey []byte, +) (*keys.KeyShare, error) { + pks, err := NewPublicKeyShare(c, id, signerPublicKey) + if err != nil { + return nil, err + } + + g := c.ECGroup() + + s := g.NewScalar() + if err = s.Decode(secretShare); err != nil { + return nil, fmt.Errorf("could not decode secret share: %w", err) + } + + gpk := g.NewElement() + if err = gpk.Decode(groupPublicKey); err != nil { + return nil, fmt.Errorf("could not decode the group public key: %w", err) + } + + return &keys.KeyShare{ + Secret: s, + GroupPublicKey: gpk, + PublicKeyShare: secretsharing.PublicKeyShare(*pks), + }, nil +} diff --git a/internal/errors.go b/internal/errors.go index 422044e..babf0a9 100644 --- a/internal/errors.go +++ b/internal/errors.go @@ -19,4 +19,7 @@ var ( // ErrInvalidLength indicates that a provided encoded data piece is not of the expected length. ErrInvalidLength = errors.New("invalid encoding length") + + // ErrIdentifierIs0 is returned when the invalid 0 identifier is encountered. + ErrIdentifierIs0 = errors.New("identifier is 0") ) diff --git a/keys.go b/keys/keys.go similarity index 97% rename from keys.go rename to keys/keys.go index 2c5aeff..440e80b 100644 --- a/keys.go +++ b/keys/keys.go @@ -6,7 +6,8 @@ // LICENSE file in the root directory of this source tree or at // https://spdx.org/licenses/MIT.html -package frost +// Package keys defines the Key structures used in FROST. +package keys import ( "fmt" diff --git a/signer.go b/signer.go index 1f14a12..88a2182 100644 --- a/signer.go +++ b/signer.go @@ -16,6 +16,7 @@ import ( group "github.com/bytemare/crypto" "github.com/bytemare/frost/internal" + "github.com/bytemare/frost/keys" ) // SignatureShare represents a Signer's signature share and its identifier. @@ -28,7 +29,7 @@ type SignatureShare struct { // Signer is a participant in a signing group. type Signer struct { // The KeyShare holds the signer's secret and public info, such as keys and identifier. - KeyShare *KeyShare + KeyShare *keys.KeyShare // LambdaRegistry records all interpolating values for the signers for different combinations of participant // groups. Each group makes up a unique polynomial defined by the participants' identifiers. A value will be @@ -177,11 +178,12 @@ func (s *Signer) VerifyCommitmentList(commitments CommitmentList) error { // and nonces are cleared and another call to Sign() with the same Commitment will return an error. func (s *Signer) Sign(message []byte, commitments CommitmentList) (*SignatureShare, error) { commitments.Sort() + if err := s.VerifyCommitmentList(commitments); err != nil { return nil, err } - groupCommitment, bindingFactors := commitments.GroupCommitmentAndBindingFactors( + groupCommitment, bindingFactors := commitments.groupCommitmentAndBindingFactors( s.Configuration.GroupPublicKey, message, ) diff --git a/tests/commitment_test.go b/tests/commitment_test.go index daebe6f..0aabf48 100644 --- a/tests/commitment_test.go +++ b/tests/commitment_test.go @@ -39,7 +39,7 @@ func TestCommitment_Validate_InvalidConfiguration(t *testing.T) { } func TestCommitment_Validate_NilCommitment(t *testing.T) { - expectedErrorPrefix := "the commitment list has a nil commitment" + expectedErrorPrefix := "the commitment is nil" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 3, diff --git a/tests/configuration_test.go b/tests/configuration_test.go index 2146cb8..a78cc80 100644 --- a/tests/configuration_test.go +++ b/tests/configuration_test.go @@ -19,6 +19,7 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" + "github.com/bytemare/frost/keys" ) func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { @@ -186,7 +187,7 @@ func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { } // empty - configuration.SignerPublicKeyShares = []*frost.PublicKeyShare{} + configuration.SignerPublicKeyShares = []*keys.PublicKeyShare{} if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -200,7 +201,7 @@ func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { } // too many - configuration.SignerPublicKeyShares = append(publicKeyShares, &frost.PublicKeyShare{}) + configuration.SignerPublicKeyShares = append(publicKeyShares, &keys.PublicKeyShare{}) if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -379,7 +380,7 @@ func TestConfiguration_ValidatePublicKeyShare_WrongGroup(t *testing.T) { } configuration, _ := makeConfAndShares(t, tt) - pks := &frost.PublicKeyShare{ + pks := &keys.PublicKeyShare{ Group: 0, } @@ -397,7 +398,7 @@ func TestConfiguration_ValidatePublicKeyShare_ID0(t *testing.T) { } configuration, _ := makeConfAndShares(t, tt) - pks := &frost.PublicKeyShare{ + pks := &keys.PublicKeyShare{ Group: tt.ECGroup(), ID: 0, } @@ -416,7 +417,7 @@ func TestConfiguration_ValidatePublicKeyShare_InvalidID(t *testing.T) { } configuration, _ := makeConfAndShares(t, tt) - pks := &frost.PublicKeyShare{ + pks := &keys.PublicKeyShare{ Group: tt.ECGroup(), ID: tt.maxSigners + 1, } @@ -435,7 +436,7 @@ func TestConfiguration_ValidatePublicKeyShare_InvalidPublicKey(t *testing.T) { } configuration, _ := makeConfAndShares(t, tt) - pks := &frost.PublicKeyShare{ + pks := &keys.PublicKeyShare{ Group: tt.ECGroup(), ID: 1, PublicKey: tt.ECGroup().Base(), @@ -569,7 +570,7 @@ func TestConfiguration_ValidateKeyShare_SignerIDNotRegistered(t *testing.T) { } configuration, keyShares := makeConfAndShares(t, tt) - pks := make([]*frost.PublicKeyShare, len(keyShares)-1) + pks := make([]*keys.PublicKeyShare, len(keyShares)-1) for i, ks := range keyShares[1:] { pks[i] = ks.Public() } @@ -591,7 +592,7 @@ func TestConfiguration_ValidateKeyShare_WrongPublicKey(t *testing.T) { configuration, keyShares := makeConfAndShares(t, tt) random := tt.ECGroup().NewScalar().Random() - keyShare := &frost.KeyShare{ + keyShare := &keys.KeyShare{ Secret: random, GroupPublicKey: keyShares[0].GroupPublicKey, PublicKeyShare: secretsharing.PublicKeyShare{ diff --git a/tests/dkg_test.go b/tests/dkg_test.go index 1cc8d29..78cc701 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -14,7 +14,7 @@ import ( group "github.com/bytemare/crypto" "github.com/bytemare/dkg" - "github.com/bytemare/frost" + "github.com/bytemare/frost/keys" ) func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, threshold uint64) []*dkg.Participant { @@ -35,7 +35,7 @@ func runDKG( t *testing.T, g group.Group, threshold, maxSigners uint64, -) ([]*frost.KeyShare, *group.Element, []*group.Element) { +) ([]*keys.KeyShare, *group.Element, []*group.Element) { c := dkg.Ciphersuite(g) // valid r1DataSet set with and without own package @@ -77,7 +77,7 @@ func runDKG( } // Step 4: Finalize and test outputs. - keyShares := make([]*frost.KeyShare, 0, maxSigners) + keyShares := make([]*keys.KeyShare, 0, maxSigners) for _, p := range participants { keyShare, err := p.Finalize(r1, r2[p.Identifier]) @@ -97,7 +97,7 @@ func runDKG( t.Fatal(err) } - keyShares = append(keyShares, (*frost.KeyShare)(keyShare)) + keyShares = append(keyShares, (*keys.KeyShare)(keyShare)) } return keyShares, pubKey, nil diff --git a/tests/encoding_test.go b/tests/encoding_test.go index f0fd075..3de573e 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -22,9 +22,10 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" + "github.com/bytemare/frost/keys" ) -func makeConfAndShares(t *testing.T, test *tableTest) (*frost.Configuration, []*frost.KeyShare) { +func makeConfAndShares(t *testing.T, test *tableTest) (*frost.Configuration, []*keys.KeyShare) { keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -48,8 +49,8 @@ func makeConf(t *testing.T, test *tableTest) *frost.Configuration { return c } -func getPublicKeyShares(keyShares []*frost.KeyShare) []*frost.PublicKeyShare { - publicKeyShares := make([]*frost.PublicKeyShare, 0, len(keyShares)) +func getPublicKeyShares(keyShares []*keys.KeyShare) []*keys.PublicKeyShare { + publicKeyShares := make([]*keys.PublicKeyShare, 0, len(keyShares)) for _, ks := range keyShares { publicKeyShares = append(publicKeyShares, ks.Public()) } @@ -116,7 +117,7 @@ func compareConfigurations(t *testing.T, c1, c2 *frost.Configuration, expectedMa } } -func comparePublicKeyShare(p1, p2 *frost.PublicKeyShare) error { +func comparePublicKeyShare(p1, p2 *keys.PublicKeyShare) error { if p1.PublicKey.Equal(p2.PublicKey) != 1 { return fmt.Errorf("Expected equality on PublicKey:\n\t%s\n\t%s\n", p1.PublicKey.Hex(), p2.PublicKey.Hex()) } @@ -151,7 +152,7 @@ func comparePublicKeyShare(p1, p2 *frost.PublicKeyShare) error { return nil } -func compareKeyShares(s1, s2 *frost.KeyShare) error { +func compareKeyShares(s1, s2 *keys.KeyShare) error { if s1.Secret.Equal(s2.Secret) != 1 { return fmt.Errorf("Expected equality on Secret:\n\t%s\n\t%s\n", s1.Secret.Hex(), s2.Secret.Hex()) } @@ -543,7 +544,7 @@ func TestEncoding_Signer_InvalidLength2(t *testing.T) { } func TestEncoding_Signer_InvalidLambda(t *testing.T) { - expectedErrorPrefix := "failed to decode lambda:" + expectedErrorPrefix := "failed to decode lambda registry in signer:" testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) @@ -1125,7 +1126,7 @@ func TestEncoding_KeyShare_Bytes(t *testing.T) { encoded := keyShare.Encode() - decoded := new(frost.KeyShare) + decoded := new(keys.KeyShare) if err := decoded.Decode(encoded); err != nil { t.Fatal(err) } @@ -1146,7 +1147,7 @@ func TestEncoding_KeyShare_JSON(t *testing.T) { t.Fatal(err) } - decoded := new(frost.KeyShare) + decoded := new(keys.KeyShare) if err := json.Unmarshal(encoded, decoded); err != nil { t.Fatal(err) } @@ -1156,7 +1157,7 @@ func TestEncoding_KeyShare_JSON(t *testing.T) { } // expect error - decoded = new(frost.KeyShare) + decoded = new(keys.KeyShare) expectedError := errors.New("invalid group identifier") encoded = replaceStringInBytes(encoded, fmt.Sprintf("\"group\":%d", test.ECGroup()), "\"group\":70") @@ -1173,7 +1174,7 @@ func TestEncoding_PublicKeyShare_Bytes(t *testing.T) { encoded := keyShare.Encode() - decoded := new(frost.PublicKeyShare) + decoded := new(keys.PublicKeyShare) if err := decoded.Decode(encoded); err != nil { t.Fatal(err) } @@ -1194,7 +1195,7 @@ func TestEncoding_PublicKeyShare_JSON(t *testing.T) { t.Fatal(err) } - decoded := new(frost.PublicKeyShare) + decoded := new(keys.PublicKeyShare) if err := json.Unmarshal(encoded, decoded); err != nil { t.Fatal(err) } @@ -1204,7 +1205,7 @@ func TestEncoding_PublicKeyShare_JSON(t *testing.T) { } // expect error - decoded = new(frost.PublicKeyShare) + decoded = new(keys.PublicKeyShare) expectedError := errors.New("invalid group identifier") encoded = replaceStringInBytes(encoded, fmt.Sprintf("\"group\":%d", test.ECGroup()), "\"group\":70") diff --git a/tests/frost_test.go b/tests/frost_test.go index 3543595..72fa12d 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -16,6 +16,7 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/debug" + "github.com/bytemare/frost/keys" ) type tableTest struct { @@ -51,7 +52,7 @@ func runFrost( test *tableTest, threshold, maxSigners uint64, message []byte, - keyShares []*frost.KeyShare, + keyShares []*keys.KeyShare, groupPublicKey *group.Element, ) { // Collect public keys. diff --git a/tests/misc_test.go b/tests/misc_test.go index ab2724c..12769f2 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -20,12 +20,13 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" + "github.com/bytemare/frost/keys" ) func verifyTrustedDealerKeygen( t *testing.T, test *tableTest, - ks []*frost.KeyShare, + ks []*keys.KeyShare, pk *group.Element, coms []*group.Element, ) { @@ -302,7 +303,7 @@ func TestRecoverPublicKeys_InvalidCiphersuite(t *testing.T) { } func TestRecoverPublicKeys_BadCommitment(t *testing.T) { - expectedError := "commitment has nil element" + expectedError := "can't recover public keys: commitment has nil element" ciphersuite := frost.Ristretto255 threshold := uint64(2) maxSigners := uint64(3) @@ -335,7 +336,7 @@ func TestPublicKeyShareVerification(t *testing.T) { ) vssComs := make([][]*group.Element, test.maxSigners) - pkShares := make([]*frost.PublicKeyShare, test.maxSigners) + pkShares := make([]*keys.PublicKeyShare, test.maxSigners) for i, keyShare := range keyShares { pk := keyShare.Public() @@ -365,7 +366,7 @@ func TestPublicKeyShareVerificationFail(t *testing.T) { ) vssComs := make([][]*group.Element, test.maxSigners) - pkShares := make([]*frost.PublicKeyShare, test.maxSigners) + pkShares := make([]*keys.PublicKeyShare, test.maxSigners) for i, keyShare := range keyShares { pk := keyShare.Public() diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index 36005d9..98cc87f 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -19,6 +19,7 @@ import ( secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/frost" + "github.com/bytemare/frost/keys" ) type ParticipantList []*frost.Signer @@ -168,7 +169,7 @@ type testInput struct { GroupPublicKey *group.Element Message []byte SharePolynomialCoefficients []*group.Scalar - Participants []*frost.KeyShare + Participants []*keys.KeyShare } type test struct { @@ -250,7 +251,7 @@ func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { GroupPublicKey: decodeElement(t, g, i.GroupPublicKey), Message: i.Message, SharePolynomialCoefficients: make([]*group.Scalar, len(i.SharePolynomialCoefficients)+1), - Participants: make([]*frost.KeyShare, len(i.ParticipantShares)), + Participants: make([]*keys.KeyShare, len(i.ParticipantShares)), ParticipantList: make([]uint64, len(i.ParticipantList)), } @@ -266,7 +267,7 @@ func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { for j, p := range i.ParticipantShares { secret := decodeScalar(t, g, p.ParticipantShare) public := g.Base().Multiply(secret) - input.Participants[j] = &frost.KeyShare{ + input.Participants[j] = &keys.KeyShare{ Secret: secret, GroupPublicKey: input.GroupPublicKey, PublicKeyShare: secretsharing.PublicKeyShare{ @@ -313,7 +314,7 @@ func (v testVector) decode(t *testing.T) *test { inputs := v.Inputs.decode(t, conf.Ciphersuite.ECGroup()) conf.GroupPublicKey = inputs.GroupPublicKey - conf.SignerPublicKeyShares = make([]*frost.PublicKeyShare, len(inputs.Participants)) + conf.SignerPublicKeyShares = make([]*keys.PublicKeyShare, len(inputs.Participants)) for i, ks := range inputs.Participants { conf.SignerPublicKeyShares[i] = ks.Public() diff --git a/tests/vectors_test.go b/tests/vectors_test.go index 3f6a4d2..803e757 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -20,9 +20,10 @@ import ( "github.com/bytemare/frost" "github.com/bytemare/frost/debug" + "github.com/bytemare/frost/keys" ) -func (v test) testTrustedDealer(t *testing.T) ([]*frost.KeyShare, *group.Element) { +func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *group.Element) { g := v.Config.Ciphersuite.ECGroup() keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( From d88ad550bbb6a047e07d3e70933d4a33c41dc9d2 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:55:25 +0200 Subject: [PATCH 25/31] various improvements Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- .github/workflows/tests.yml | 2 +- README.md | 4 +- commitment.go | 76 ++--- coordinator.go | 36 ++- debug/debug.go | 63 ++-- encoding.go | 564 ++++++++++++++++++++++++++------- examples_test.go | 250 ++++++++++++++- frost.go | 177 +++++------ go.mod | 14 +- go.sum | 24 +- internal/errors.go | 3 + internal/hashing.go | 32 +- internal/lambda.go | 103 +++++-- keys/keys.go | 90 ------ signer.go | 45 ++- tests/commitment_test.go | 16 +- tests/configuration_test.go | 71 +++-- tests/dkg_test.go | 33 +- tests/encoding_test.go | 600 +++++++++++++++++++----------------- tests/frost_error_test.go | 124 +++++++- tests/frost_test.go | 26 +- tests/misc_test.go | 81 ++--- tests/signer_test.go | 18 +- tests/utils_test.go | 37 +-- tests/vector_utils_test.go | 71 +++-- tests/vectors_test.go | 30 +- 26 files changed, 1600 insertions(+), 990 deletions(-) delete mode 100644 keys/keys.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e408132..60c86e6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - go: [ '1.22', '1.21' ] + go: [ '1.23', '1.22', '1.21' ] uses: bytemare/workflows/.github/workflows/test-go.yml@232148ec449718765bacb8bd4684de41f15b8258 with: command: cd .github && make test diff --git a/README.md b/README.md index 5be3428..3d219f9 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ import ( bytesPublicKey := []byte{1, 2, 3, ...} -g := group.Ristretto255Sha512 +g := ecc.Ristretto255Sha512 publicKey := g.NewElement() if err := publicKey.Decode(bytesPublicKey); err != nil { @@ -102,7 +102,7 @@ import ( bytesSecretKey := []byte{1, 2, 3, ...} -g := group.Ristretto255Sha512 +g := ecc.Ristretto255Sha512 secretKey := g.NewScalar() if err := secretKey.Decode(bytesSecretKey); err != nil { diff --git a/commitment.go b/commitment.go index cabd930..b83d6f1 100644 --- a/commitment.go +++ b/commitment.go @@ -14,14 +14,13 @@ import ( "fmt" "slices" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/frost/internal" ) var ( - errDecodeCommitmentLength = errors.New("failed to decode commitment: invalid length") errInvalidCiphersuite = errors.New("ciphersuite not available") errInvalidLength = errors.New("invalid encoding length") errCommitmentNil = errors.New("the commitment is nil") @@ -32,11 +31,11 @@ var ( // Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces. type Commitment struct { - HidingNonceCommitment *group.Element - BindingNonceCommitment *group.Element - CommitmentID uint64 - SignerID uint64 - Group group.Group + HidingNonceCommitment *ecc.Element `json:"hidingNonceCommitment"` + BindingNonceCommitment *ecc.Element `json:"bindingNonceCommitment"` + CommitmentID uint64 `json:"commitmentId"` + SignerID uint16 `json:"signerId"` + Group ecc.Group `json:"group"` } // Copy returns a new Commitment struct populated with the same values as the receiver. @@ -79,7 +78,7 @@ func (c CommitmentList) IsSorted() bool { } // 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 { +func (c CommitmentList) Get(identifier uint16) *Commitment { for _, com := range c { if com.SignerID == identifier { return com @@ -90,8 +89,8 @@ func (c CommitmentList) Get(identifier uint64) *Commitment { } // Participants returns the uint64 list of participant identifiers in the list. -func (c CommitmentList) Participants() []uint64 { - out := make([]uint64, len(c)) +func (c CommitmentList) Participants() []uint16 { + out := make([]uint16, len(c)) for i, com := range c { out[i] = com.SignerID @@ -100,8 +99,8 @@ func (c CommitmentList) Participants() []uint64 { return out } -// ParticipantsScalar returns the group.Scalar list of participant identifier in the list. -func (c CommitmentList) ParticipantsScalar() []*group.Scalar { +// ParticipantsScalar returns the ecc.Scalar list of participant identifier in the list. +func (c CommitmentList) ParticipantsScalar() []*ecc.Scalar { if len(c) == 0 { return nil } @@ -112,8 +111,8 @@ func (c CommitmentList) ParticipantsScalar() []*group.Scalar { g := c[0].Group - return secretsharing.NewPolynomialFromListFunc(g, c, func(c *Commitment) *group.Scalar { - return g.NewScalar().SetUInt64(c.SignerID) + return secretsharing.NewPolynomialFromListFunc(g, c, func(c *Commitment) *ecc.Scalar { + return g.NewScalar().SetUInt64(uint64(c.SignerID)) }) } @@ -125,10 +124,11 @@ func (c CommitmentList) Encode() []byte { } g := c[0].Group - size := 1 + 8 + uint64(n)*encodedLength(encCommitment, g) - out := make([]byte, 9, size) + _, comLength := encodedLength(encCommitment, g) + size := 1 + 2 + n*comLength + out := make([]byte, 3, size) out[0] = byte(g) - binary.LittleEndian.PutUint64(out[1:9], uint64(n)) + binary.LittleEndian.PutUint16(out[1:3], uint16(n)) for _, com := range c { out = append(out, com.Encode()...) @@ -139,28 +139,28 @@ func (c CommitmentList) Encode() []byte { // DecodeList decodes a byte string produced by the CommitmentList.Encode() method. func DecodeList(data []byte) (CommitmentList, error) { - if len(data) < 9 { + if len(data) < 3 { return nil, errInvalidLength } - g := group.Group(data[0]) + g := ecc.Group(data[0]) if !g.Available() { return nil, errInvalidCiphersuite } - n := binary.LittleEndian.Uint64(data[1:9]) - es := encodedLength(encCommitment, g) - size := 1 + 8 + n*es + n := int(binary.LittleEndian.Uint16(data[1:3])) + _, comLength := encodedLength(encCommitment, g) + size := 1 + 2 + n*comLength - if uint64(len(data)) != size { + if len(data) != size { return nil, errInvalidLength } c := make(CommitmentList, 0, n) - for offset := uint64(9); offset < uint64(len(data)); offset += es { + for offset := 3; offset < len(data); offset += comLength { com := new(Commitment) - if err := com.Decode(data[offset : offset+es]); err != nil { + if err := com.Decode(data[offset : offset+comLength]); err != nil { return nil, fmt.Errorf("invalid encoding of commitment: %w", err) } @@ -171,9 +171,9 @@ func DecodeList(data []byte) (CommitmentList, error) { } func (c CommitmentList) groupCommitmentAndBindingFactors( - publicKey *group.Element, + publicKey *ecc.Element, message []byte, -) (*group.Element, BindingFactors) { +) (*ecc.Element, BindingFactors) { bindingFactors := c.bindingFactors(publicKey, message) groupCommitment := c.groupCommitment(bindingFactors) @@ -181,15 +181,15 @@ func (c CommitmentList) groupCommitmentAndBindingFactors( } type commitmentWithEncodedID struct { - *Commitment - ParticipantID []byte + *Commitment `json:"commitment"` + ParticipantID []byte `json:"participantId"` } -func commitmentsWithEncodedID(g group.Group, commitments CommitmentList) []*commitmentWithEncodedID { +func commitmentsWithEncodedID(g ecc.Group, commitments CommitmentList) []*commitmentWithEncodedID { r := make([]*commitmentWithEncodedID, len(commitments)) for i, com := range commitments { r[i] = &commitmentWithEncodedID{ - ParticipantID: g.NewScalar().SetUInt64(com.SignerID).Encode(), + ParticipantID: g.NewScalar().SetUInt64(uint64(com.SignerID)).Encode(), Commitment: com, } } @@ -197,7 +197,7 @@ func commitmentsWithEncodedID(g group.Group, commitments CommitmentList) []*comm return r } -func encodeCommitmentList(g group.Group, commitments []*commitmentWithEncodedID) []byte { +func encodeCommitmentList(g ecc.Group, commitments []*commitmentWithEncodedID) []byte { size := len(commitments) * (g.ScalarLength() + 2*g.ElementLength()) encoded := make([]byte, 0, size) @@ -211,9 +211,9 @@ func encodeCommitmentList(g group.Group, commitments []*commitmentWithEncodedID) } // BindingFactors is a map of participant identifiers to BindingFactors. -type BindingFactors map[uint64]*group.Scalar +type BindingFactors map[uint16]*ecc.Scalar -func (c CommitmentList) bindingFactors(publicKey *group.Element, message []byte) BindingFactors { +func (c CommitmentList) bindingFactors(publicKey *ecc.Element, message []byte) BindingFactors { g := c[0].Group coms := commitmentsWithEncodedID(g, c) encodedCommitHash := internal.H5(g, encodeCommitmentList(g, coms)) @@ -229,7 +229,7 @@ func (c CommitmentList) bindingFactors(publicKey *group.Element, message []byte) return bindingFactors } -func (c CommitmentList) groupCommitment(bf BindingFactors) *group.Element { +func (c CommitmentList) groupCommitment(bf BindingFactors) *ecc.Element { g := c[0].Group gc := g.NewElement() @@ -242,7 +242,7 @@ func (c CommitmentList) groupCommitment(bf BindingFactors) *group.Element { return gc } -func (c *Configuration) isSignerRegistered(sid uint64) bool { +func (c *Configuration) isSignerRegistered(sid uint16) bool { for _, peer := range c.SignerPublicKeyShares { if peer.ID == sid { return true @@ -309,7 +309,7 @@ func (c *Configuration) ValidateCommitment(commitment *Commitment) error { } func (c *Configuration) validateCommitmentListLength(commitments CommitmentList) error { - length := uint64(len(commitments)) + length := uint16(len(commitments)) if length == 0 { return errCommitmentListEmpty @@ -344,7 +344,7 @@ func (c *Configuration) ValidateCommitmentList(commitments CommitmentList) error } // set to detect duplication - set := make(map[uint64]struct{}, len(commitments)) + set := make(map[uint16]struct{}, len(commitments)) for i, commitment := range commitments { // Check general validity of the commitment. diff --git a/coordinator.go b/coordinator.go index 157c050..05743de 100644 --- a/coordinator.go +++ b/coordinator.go @@ -12,17 +12,24 @@ import ( "errors" "fmt" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" "github.com/bytemare/frost/internal" ) var errInvalidSignature = errors.New("invalid Signature") -// Signature represent a Schnorr signature. +// Signature represents a Schnorr signature. type Signature struct { - R *group.Element - Z *group.Scalar + R *ecc.Element `json:"r"` + Z *ecc.Scalar `json:"z"` + Group ecc.Group `json:"group"` +} + +// Clear overwrites the original values with default ones. +func (s *Signature) Clear() { + s.R.Identity() + s.Z.Zero() } // AggregateSignatures enables a coordinator to produce the final signature given all signature shares. @@ -68,7 +75,7 @@ func (c *Configuration) AggregateSignatures( return nil, err } - // Verify the final signature. + // Verify the final signature. Failure is unlikely to happen, as the signature is valid if the signature shares are. if verify { if err = VerifySignature(c.Ciphersuite, message, signature, c.GroupPublicKey); err != nil { // difficult to reach, because if all shares are valid, the final signature is valid. @@ -79,8 +86,8 @@ func (c *Configuration) AggregateSignatures( return signature, nil } -func (c *Configuration) sumShares(shares []*SignatureShare, groupCommitment *group.Element) (*Signature, error) { - z := group.Group(c.Ciphersuite).NewScalar() +func (c *Configuration) sumShares(shares []*SignatureShare, groupCommitment *ecc.Element) (*Signature, error) { + z := ecc.Group(c.Ciphersuite).NewScalar() for _, sigShare := range shares { if err := c.validateSignatureShareLight(sigShare); err != nil { @@ -91,8 +98,9 @@ func (c *Configuration) sumShares(shares []*SignatureShare, groupCommitment *gro } return &Signature{ - R: groupCommitment, - Z: z, + Group: c.group, + R: groupCommitment, + Z: z, }, nil } @@ -120,7 +128,7 @@ func (c *Configuration) VerifySignatureShare( func (c *Configuration) prepareSignatureShareVerification(message []byte, commitments CommitmentList, -) (*group.Element, BindingFactors, []*group.Scalar, error) { +) (*ecc.Element, BindingFactors, []*ecc.Scalar, error) { commitments.Sort() // Validate general consistency of the commitment list. @@ -170,8 +178,8 @@ func (c *Configuration) verifySignatureShare( sigShare *SignatureShare, message []byte, commitments CommitmentList, - participants []*group.Scalar, - groupCommitment *group.Element, + participants []*ecc.Scalar, + groupCommitment *ecc.Element, bindingFactors BindingFactors, ) error { if err := c.validateSignatureShareExtensive(sigShare); err != nil { @@ -184,7 +192,7 @@ func (c *Configuration) verifySignatureShare( } pk := c.getSignerPubKey(sigShare.SignerIdentifier) - lambda := internal.Lambda(c.group, sigShare.SignerIdentifier, participants) + lambda := internal.ComputeLambda(c.group, sigShare.SignerIdentifier, participants) lambdaChall := c.challenge(lambda, message, groupCommitment) // Commitment KeyShare: r = g(h + b*f + l*s) @@ -193,7 +201,7 @@ func (c *Configuration) verifySignatureShare( r := commShare.Add(pk.Copy().Multiply(lambdaChall)) l := c.group.Base().Multiply(sigShare.SignatureShare) - if l.Equal(r) != 1 { + if !l.Equal(r) { return fmt.Errorf("invalid signature share for signer %d", sigShare.SignerIdentifier) } diff --git a/debug/debug.go b/debug/debug.go index 1892641..c1b429d 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -14,12 +14,12 @@ package debug import ( "fmt" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" secretsharing "github.com/bytemare/secret-sharing" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/keys" ) // TrustedDealerKeygen uses Shamir and Verifiable Secret Sharing to create secret shares of an input group secret. If @@ -29,22 +29,22 @@ import ( // package. func TrustedDealerKeygen( c frost.Ciphersuite, - secret *group.Scalar, - threshold, maxSigners uint64, - coeffs ...*group.Scalar, -) ([]*keys.KeyShare, *group.Element, []*group.Element) { - g := group.Group(c) + secret *ecc.Scalar, + threshold, maxSigners uint16, + coeffs ...*ecc.Scalar, +) ([]*keys.KeyShare, *ecc.Element, []*ecc.Element) { + g := ecc.Group(c) if secret == nil { - // If no secret provided, generated a new random secret. + // If no secret provided, generate a new random secret. g.NewScalar().Random() } privateKeyShares, poly, err := secretsharing.ShardReturnPolynomial( g, secret, - uint(threshold), - uint(maxSigners), + threshold, + maxSigners, coeffs...) if err != nil { panic(err) @@ -57,11 +57,11 @@ func TrustedDealerKeygen( shares[i] = &keys.KeyShare{ Secret: k.Secret, GroupPublicKey: coms[0], - PublicKeyShare: secretsharing.PublicKeyShare{ - PublicKey: g.Base().Multiply(k.Secret), - Commitment: coms, - ID: k.ID, - Group: g, + PublicKeyShare: keys.PublicKeyShare{ + PublicKey: g.Base().Multiply(k.Secret), + VssCommitment: coms, + ID: k.ID, + Group: g, }, } } @@ -71,19 +71,17 @@ func TrustedDealerKeygen( // 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(c frost.Ciphersuite, keyShares []*keys.KeyShare) (*group.Scalar, error) { +func RecoverGroupSecret(c frost.Ciphersuite, keyShares []*keys.KeyShare) (*ecc.Scalar, error) { if !c.Available() { return nil, internal.ErrInvalidCiphersuite } - g := group.Group(c) - - publicKeys := make([]secretsharing.Share, len(keyShares)) + publicKeys := make([]keys.Share, len(keyShares)) for i, v := range keyShares { publicKeys[i] = v } - secret, err := secretsharing.CombineShares(g, publicKeys) + secret, err := secretsharing.CombineShares(publicKeys) if err != nil { return nil, fmt.Errorf("failed to reconstruct group secret: %w", err) } @@ -93,13 +91,13 @@ func RecoverGroupSecret(c frost.Ciphersuite, keyShares []*keys.KeyShare) (*group // Sign returns a Schnorr signature over the message msg with the full secret signing key (as opposed to a key share). // The optional random argument is the random k in Schnorr signatures. Setting it allows for reproducible signatures. -func Sign(c frost.Ciphersuite, msg []byte, key *group.Scalar, random ...*group.Scalar) (*frost.Signature, error) { - g := c.ECGroup() +func Sign(c frost.Ciphersuite, msg []byte, key *ecc.Scalar, random ...*ecc.Scalar) (*frost.Signature, error) { + g := c.Group() if g == 0 { return nil, internal.ErrInvalidCiphersuite } - var k *group.Scalar + var k *ecc.Scalar if len(random) != 0 && random[0] != nil { k = random[0].Copy() @@ -113,8 +111,9 @@ func Sign(c frost.Ciphersuite, msg []byte, key *group.Scalar, random ...*group.S z := k.Add(challenge.Multiply(key)) return &frost.Signature{ - R: R, - Z: z, + Group: g, + R: R, + Z: z, }, nil } @@ -122,17 +121,17 @@ func Sign(c frost.Ciphersuite, msg []byte, key *group.Scalar, random ...*group.S // if the identifiers are 1, 2, ..., maxSigners, given the VSS commitment vector. func RecoverPublicKeys( c frost.Ciphersuite, - maxSigners uint64, - commitment []*group.Element, -) (*group.Element, []*group.Element, error) { + maxSigners uint16, + commitment []*ecc.Element, +) (*ecc.Element, []*ecc.Element, error) { if !c.Available() { return nil, nil, internal.ErrInvalidCiphersuite } - g := group.Group(c) - publicKeys := make([]*group.Element, maxSigners) + g := ecc.Group(c) + publicKeys := make([]*ecc.Element, maxSigners) - for i := uint64(1); i <= maxSigners; i++ { + for i := uint16(1); i <= maxSigners; i++ { pki, err := secretsharing.PubKeyForCommitment(g, i, commitment) if err != nil { return nil, nil, fmt.Errorf("can't recover public keys: %w", err) @@ -145,7 +144,7 @@ func RecoverPublicKeys( } // VerifyVSS allows verification of a participant's secret share given a VSS commitment to the secret polynomial. -func VerifyVSS(g group.Group, share *keys.KeyShare, commitment []*group.Element) bool { +func VerifyVSS(g ecc.Group, share *keys.KeyShare, commitment []*ecc.Element) bool { pk := g.Base().Multiply(share.SecretKey()) return secretsharing.Verify(g, share.Identifier(), pk, commitment) } diff --git a/encoding.go b/encoding.go index 047f310..6c37782 100644 --- a/encoding.go +++ b/encoding.go @@ -11,14 +11,16 @@ package frost import ( "encoding/binary" "encoding/hex" + "encoding/json" "errors" "fmt" - "slices" + "regexp" + "strconv" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/keys" ) const ( @@ -30,6 +32,8 @@ const ( encNonceCommitment encLambda encCommitment + + errFmt = "%w: %w" ) var ( @@ -37,45 +41,66 @@ var ( "the threshold in the encoded configuration is higher than the number of maximum participants", ) errZeroIdentifier = errors.New("identifier cannot be 0") + + errDecodeConfigurationPrefix = errors.New("failed to decode Configuration") + errDecodeSignerPrefix = errors.New("failed to decode Signer") + errDecodeCommitmentPrefix = errors.New("failed to decode Commitment") + errDecodeSignatureSharePrefix = errors.New("failed to decode SignatureShare") + errDecodeSignaturePrefix = errors.New("failed to decode Signature") + + errDecodeProofR = errors.New("invalid encoding of R proof") + errDecodeProofZ = errors.New("invalid encoding of z proof") ) -func encodedLength(encID byte, g group.Group, other ...uint64) uint64 { - eLen := uint64(g.ElementLength()) - sLen := uint64(g.ScalarLength()) +func encodedLength(encID byte, g ecc.Group, other ...int) (int, int) { + eLen := g.ElementLength() + sLen := g.ScalarLength() + var header, tail int switch encID { case encConf: - return 1 + 3*8 + eLen + other[0] + header = 1 + 3*2 // group, threshold, max, n signer public key shares + tail = eLen + other[0] // verification key, signer public key shares case encSigner: - _ = other[3] - return other[0] + 2 + 2 + 2 + other[1] + other[2] + other[3] + _ = other[3] // #nosec G602 -- false positive + header = other[0] + 6 // conf length, length key share, n commitments, n lambdas + tail = other[1] + other[2] + other[3] // #nosec G602 -- key share, lambdas, nonce commitments case encSigShare: - return 1 + 8 + sLen + header = 1 + 2 // group, signer id + tail = sLen // signature share case encSig: - return eLen + sLen + header = 1 + tail = eLen + sLen // R, z case encPubKeyShare: - return 1 + 8 + 4 + eLen + other[0] + header = 1 + 2 + 4 // group, signer id, length VSS commitment + tail = eLen + other[0] // public key, vss commitment case encNonceCommitment: - return 8 + 2*sLen + encodedLength(encCommitment, g) + header = 8 // commitment id + _, com := encodedLength(encCommitment, g) + tail = 2*sLen + com // nonces, commitment case encLambda: - return 32 + sLen + header = 0 + tail = 32 + sLen // SHA256 hash of identifier key, lambda case encCommitment: - return 1 + 8 + 8 + 2*eLen + header = 1 + 8 + 2 // group, commitment ID, signer id + tail = 2 * eLen // nonce commitments default: panic("encoded id not recognized") } + + return header, header + tail } // Encode serializes the Configuration into a compact byte slice. func (c *Configuration) Encode() []byte { - g := group.Group(c.Ciphersuite) - pksLen := encodedLength(encPubKeyShare, g, c.Threshold*uint64(g.ElementLength())) - size := encodedLength(encConf, g, uint64(len(c.SignerPublicKeyShares))*pksLen) - out := make([]byte, 25, size) + g := ecc.Group(c.Ciphersuite) + _, pksLen := encodedLength(encPubKeyShare, g, int(c.Threshold)*g.ElementLength()) + header, size := encodedLength(encConf, g, len(c.SignerPublicKeyShares)*pksLen) + out := make([]byte, header, size) out[0] = byte(g) - binary.LittleEndian.PutUint64(out[1:9], c.Threshold) - binary.LittleEndian.PutUint64(out[9:17], c.MaxSigners) - binary.LittleEndian.PutUint64(out[17:25], uint64(len(c.SignerPublicKeyShares))) + binary.LittleEndian.PutUint16(out[1:3], c.Threshold) + binary.LittleEndian.PutUint16(out[3:5], c.MaxSigners) + binary.LittleEndian.PutUint16(out[5:7], uint16(len(c.SignerPublicKeyShares))) out = append(out, c.GroupPublicKey.Encode()...) @@ -87,34 +112,34 @@ func (c *Configuration) Encode() []byte { } type confHeader struct { - g group.Group - h, t, n, pksLen, nPks, length uint64 + g ecc.Group + h, t, n, pksLen, nPks, length int } func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { - if len(data) <= 25 { - return nil, internal.ErrInvalidLength + if len(data) <= 7 { + return nil, fmt.Errorf(errFmt, errDecodeConfigurationPrefix, internal.ErrInvalidLength) } cs := Ciphersuite(data[0]) if !cs.Available() { - return nil, internal.ErrInvalidCiphersuite + return nil, fmt.Errorf(errFmt, errDecodeConfigurationPrefix, internal.ErrInvalidCiphersuite) } - g := group.Group(data[0]) - t := binary.LittleEndian.Uint64(data[1:9]) - n := binary.LittleEndian.Uint64(data[9:17]) - nPks := binary.LittleEndian.Uint64(data[17:25]) - pksLen := encodedLength(encPubKeyShare, g, t*uint64(g.ElementLength())) - length := encodedLength(encConf, g, nPks*pksLen) + g := ecc.Group(data[0]) + t := int(binary.LittleEndian.Uint16(data[1:3])) + n := int(binary.LittleEndian.Uint16(data[3:5])) + nPks := int(binary.LittleEndian.Uint16(data[5:7])) + _, pksLen := encodedLength(encPubKeyShare, g, t*g.ElementLength()) + _, length := encodedLength(encConf, g, nPks*pksLen) if t == 0 || t > n { - return nil, errInvalidConfigEncoding + return nil, fmt.Errorf(errFmt, errDecodeConfigurationPrefix, errInvalidConfigEncoding) } return &confHeader{ g: g, - h: 25, + h: 7, t: t, n: n, pksLen: pksLen, @@ -124,22 +149,22 @@ func (c *Configuration) decodeHeader(data []byte) (*confHeader, error) { } func (c *Configuration) decode(header *confHeader, data []byte) error { - if uint64(len(data)) != header.length { + if len(data) != header.length { return internal.ErrInvalidLength } gpk := header.g.NewElement() - if err := gpk.Decode(data[header.h : header.h+uint64(header.g.ElementLength())]); err != nil { - return fmt.Errorf("could not decode group public key: %w", err) + if err := gpk.Decode(data[header.h : header.h+header.g.ElementLength()]); err != nil { + return fmt.Errorf("%w: could not decode group public key: %w", errDecodeConfigurationPrefix, err) } - offset := header.h + uint64(header.g.ElementLength()) + offset := header.h + header.g.ElementLength() pks := make([]*keys.PublicKeyShare, header.nPks) conf := &Configuration{ Ciphersuite: Ciphersuite(header.g), - Threshold: header.t, - MaxSigners: header.n, + Threshold: uint16(header.t), + MaxSigners: uint16(header.n), GroupPublicKey: gpk, SignerPublicKeyShares: pks, group: header.g, @@ -148,13 +173,18 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { } if err := conf.verifyConfiguration(); err != nil { - return err + return fmt.Errorf(errFmt, errDecodeConfigurationPrefix, err) } for j := range header.nPks { pk := new(keys.PublicKeyShare) if err := pk.Decode(data[offset : offset+header.pksLen]); err != nil { - return fmt.Errorf("could not decode signer public key share for signer %d: %w", j, err) + return fmt.Errorf( + "%w: could not decode signer public key share for signer %d: %w", + errDecodeConfigurationPrefix, + j, + err, + ) } offset += header.pksLen @@ -162,7 +192,7 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { } if err := conf.verifySignerPublicKeyShares(); err != nil { - return err + return fmt.Errorf(errFmt, errDecodeConfigurationPrefix, err) } c.Ciphersuite = conf.Ciphersuite @@ -170,7 +200,7 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { c.MaxSigners = conf.MaxSigners c.GroupPublicKey = gpk c.SignerPublicKeyShares = pks - c.group = group.Group(conf.Ciphersuite) + c.group = ecc.Group(conf.Ciphersuite) c.verified = true c.keysVerified = true @@ -187,23 +217,57 @@ func (c *Configuration) Decode(data []byte) error { return c.decode(header, data) } +// Hex returns the hexadecimal representation of the byte encoding returned by Encode(). +func (c *Configuration) Hex() string { + return hex.EncodeToString(c.Encode()) +} + +// DecodeHex sets s to the decoding of the hex encoded representation returned by Hex(). +func (c *Configuration) DecodeHex(h string) error { + b, err := hex.DecodeString(h) + if err != nil { + return fmt.Errorf(errFmt, errDecodeConfigurationPrefix, err) + } + + return c.Decode(b) +} + +// UnmarshalJSON decodes data into c, or returns an error. +func (c *Configuration) UnmarshalJSON(data []byte) error { + shadow := new(configurationShadow) + if err := unmarshalJSON(data, shadow); err != nil { + return fmt.Errorf(errFmt, errDecodeConfigurationPrefix, err) + } + + c2 := (*Configuration)(shadow) + if err := c2.Init(); err != nil { + return fmt.Errorf(errFmt, errDecodeConfigurationPrefix, err) + } + + *c = *c2 + + return nil +} + // Encode serializes the client with its long term values, containing its secret share. This is useful for saving state // and backup. func (s *Signer) Encode() []byte { - g := s.KeyShare.Group + g := s.KeyShare.Group() keyShare := s.KeyShare.Encode() nCommitments := len(s.NonceCommitments) nLambdas := len(s.LambdaRegistry) conf := s.Configuration.Encode() - outLength := encodedLength( + _, lambdaLength := encodedLength(encLambda, g) + _, ncLength := encodedLength(encNonceCommitment, g) + header, size := encodedLength( encSigner, g, - uint64(len(conf)), - uint64(len(keyShare)), - uint64(nLambdas)*encodedLength(encLambda, g), - uint64(nCommitments)*encodedLength(encNonceCommitment, g), + len(conf), + len(keyShare), + nLambdas*lambdaLength, + nCommitments*ncLength, ) - out := make([]byte, len(conf)+6, outLength) + out := make([]byte, header, size) copy(out, conf) binary.LittleEndian.PutUint16(out[len(conf):len(conf)+2], uint16(len(keyShare))) // key share length @@ -219,7 +283,7 @@ func (s *Signer) Encode() []byte { } out = append(out, b...) - out = append(out, v.Encode()...) + out = append(out, v.Value.Encode()...) } for id, com := range s.NonceCommitments { @@ -232,9 +296,9 @@ func (s *Signer) Encode() []byte { return out } -func (n *Nonce) decode(g group.Group, id, comLen uint64, data []byte) error { - sLen := uint64(g.ScalarLength()) - offset := uint64(g.ScalarLength()) +func (n *Nonce) decode(g ecc.Group, id uint64, comLen int, data []byte) error { + sLen := g.ScalarLength() + offset := g.ScalarLength() hn := g.NewScalar() if err := hn.Decode(data[:offset]); err != nil { @@ -260,44 +324,62 @@ func (n *Nonce) decode(g group.Group, id, comLen uint64, data []byte) error { return nil } +func (n *Nonce) populate(ns *nonceShadow) { + n.HidingNonce = ns.HidingNonce + n.BindingNonce = ns.BindingNonce + n.Commitment = (*Commitment)(ns.commitmentShadow) +} + +// UnmarshalJSON decodes data into n, or returns an error. +func (n *Nonce) UnmarshalJSON(data []byte) error { + shadow := new(nonceShadow) + if err := unmarshalJSON(data, shadow); err != nil { + return fmt.Errorf(errFmt, errDecodeCommitmentPrefix, err) + } + + n.populate(shadow) + + return nil +} + // Decode attempts to deserialize the encoded backup data into the Signer. func (s *Signer) Decode(data []byte) error { conf := new(Configuration) header, err := conf.decodeHeader(data) if err != nil { - return err + return fmt.Errorf(errFmt, errDecodeSignerPrefix, err) } if err = conf.decode(header, data[:header.length]); err != nil { - return err + return fmt.Errorf(errFmt, errDecodeSignerPrefix, err) } - if uint64(len(data)) <= header.length+6 { - return internal.ErrInvalidLength + if len(data) <= header.length+6 { + return fmt.Errorf(errFmt, errDecodeSignerPrefix, errInvalidLength) } - ksLen := uint64(binary.LittleEndian.Uint16(data[header.length : header.length+2])) - nCommitments := uint64(binary.LittleEndian.Uint16(data[header.length+2 : header.length+4])) - nLambdas := uint64(binary.LittleEndian.Uint16(data[header.length+4 : header.length+6])) + ksLen := int(binary.LittleEndian.Uint16(data[header.length : header.length+2])) + nCommitments := int(binary.LittleEndian.Uint16(data[header.length+2 : header.length+4])) + nLambdas := int(binary.LittleEndian.Uint16(data[header.length+4 : header.length+6])) g := conf.group - nLen := encodedLength(encNonceCommitment, g) - lLem := encodedLength(encLambda, g) + _, nLen := encodedLength(encNonceCommitment, g) + _, lLem := encodedLength(encLambda, g) - length := encodedLength(encSigner, g, header.length, ksLen, nCommitments*nLen, nLambdas*lLem) - if uint64(len(data)) != length { - return internal.ErrInvalidLength + _, length := encodedLength(encSigner, g, header.length, ksLen, nCommitments*nLen, nLambdas*lLem) + if len(data) != length { + return fmt.Errorf(errFmt, errDecodeSignerPrefix, errInvalidLength) } offset := header.length + 6 keyShare := new(keys.KeyShare) if err = keyShare.Decode(data[offset : offset+ksLen]); err != nil { - return fmt.Errorf("failed to decode key share: %w", err) + return fmt.Errorf(errFmt, errDecodeSignerPrefix, err) } if err = conf.ValidateKeyShare(keyShare); err != nil { - return fmt.Errorf("invalid key share: %w", err) + return fmt.Errorf("%w: invalid key share: %w", errDecodeSignerPrefix, err) } offset += ksLen @@ -305,24 +387,25 @@ func (s *Signer) Decode(data []byte) error { lambdaRegistry := make(internal.LambdaRegistry, lLem) if err = lambdaRegistry.Decode(g, data[offset:stop]); err != nil { - return fmt.Errorf("failed to decode lambda registry in signer: %w", err) + return fmt.Errorf("%w: failed to decode lambda registry in signer: %w", errDecodeSignerPrefix, err) } offset = stop commitments := make(map[uint64]*Nonce) - comLen := encodedLength(encCommitment, g) - nComLen := encodedLength(encNonceCommitment, g) + _, comLen := encodedLength(encCommitment, g) + _, nComLen := encodedLength(encNonceCommitment, g) - for offset < uint64(len(data)) { + for offset < len(data) { + // commitment ID id := binary.LittleEndian.Uint64(data[offset : offset+8]) if _, exists := commitments[id]; exists { - return fmt.Errorf("multiple encoded commitments with the same id: %d", id) + return fmt.Errorf("%w: multiple encoded commitments with the same id: %d", errDecodeSignerPrefix, id) } n := new(Nonce) if err = n.decode(g, id, comLen, data[offset+8:]); err != nil { - return err + return fmt.Errorf(errFmt, errDecodeSignerPrefix, err) } commitments[id] = n @@ -337,15 +420,43 @@ func (s *Signer) Decode(data []byte) error { return nil } +// Hex returns the hexadecimal representation of the byte encoding returned by Encode(). +func (s *Signer) Hex() string { + return hex.EncodeToString(s.Encode()) +} + +// DecodeHex sets s to the decoding of the hex encoded representation returned by Hex(). +func (s *Signer) DecodeHex(h string) error { + b, err := hex.DecodeString(h) + if err != nil { + return fmt.Errorf(errFmt, errDecodeSignerPrefix, err) + } + + return s.Decode(b) +} + +// UnmarshalJSON decodes data into s, or returns an error. +func (s *Signer) UnmarshalJSON(data []byte) error { + shadow := new(signerShadow) + if err := unmarshalJSON(data, shadow); err != nil { + return fmt.Errorf(errFmt, errDecodeSignerPrefix, err) + } + + *s = Signer(*shadow) + + return nil +} + // Encode returns the serialized byte encoding of a participant's commitment. func (c *Commitment) Encode() []byte { hNonce := c.HidingNonceCommitment.Encode() bNonce := c.BindingNonceCommitment.Encode() - out := make([]byte, 17, encodedLength(encCommitment, c.Group)) + header, size := encodedLength(encCommitment, c.Group) + out := make([]byte, header, size) out[0] = byte(c.Group) binary.LittleEndian.PutUint64(out[1:9], c.CommitmentID) - binary.LittleEndian.PutUint64(out[9:17], c.SignerID) + binary.LittleEndian.PutUint16(out[9:11], c.SignerID) out = append(out, hNonce...) out = append(out, bNonce...) @@ -354,38 +465,39 @@ func (c *Commitment) Encode() []byte { // Decode attempts to deserialize the encoded commitment given as input, and to return it. func (c *Commitment) Decode(data []byte) error { - if len(data) < 17 { - return errDecodeCommitmentLength + if len(data) < 11 { + return fmt.Errorf(errFmt, errDecodeCommitmentPrefix, errInvalidLength) } - g := group.Group(data[0]) + g := ecc.Group(data[0]) if !g.Available() { - return errInvalidCiphersuite + return fmt.Errorf(errFmt, errDecodeCommitmentPrefix, errInvalidCiphersuite) } - if uint64(len(data)) != encodedLength(encCommitment, g) { - return errDecodeCommitmentLength + _, size := encodedLength(encCommitment, g) + if len(data) != size { + return fmt.Errorf(errFmt, errDecodeCommitmentPrefix, errInvalidLength) } cID := binary.LittleEndian.Uint64(data[1:9]) - pID := binary.LittleEndian.Uint64(data[9:17]) + pID := binary.LittleEndian.Uint16(data[9:11]) if pID == 0 { - return errZeroIdentifier + return fmt.Errorf(errFmt, errDecodeCommitmentPrefix, errZeroIdentifier) } - offset := 17 + offset := 11 hn := g.NewElement() if err := hn.Decode(data[offset : offset+g.ElementLength()]); err != nil { - return fmt.Errorf("invalid encoding of hiding nonce commitment: %w", err) + return fmt.Errorf("%w: invalid encoding of hiding nonce commitment: %w", errDecodeCommitmentPrefix, err) } offset += g.ElementLength() bn := g.NewElement() if err := bn.Decode(data[offset : offset+g.ElementLength()]); err != nil { - return fmt.Errorf("invalid encoding of binding nonce commitment: %w", err) + return fmt.Errorf("%w: invalid encoding of binding nonce commitment: %w", errDecodeCommitmentPrefix, err) } c.Group = g @@ -397,14 +509,42 @@ func (c *Commitment) Decode(data []byte) error { return nil } +// Hex returns the hexadecimal representation of the byte encoding returned by Encode(). +func (c *Commitment) Hex() string { + return hex.EncodeToString(c.Encode()) +} + +// DecodeHex sets s to the decoding of the hex encoded representation returned by Hex(). +func (c *Commitment) DecodeHex(h string) error { + b, err := hex.DecodeString(h) + if err != nil { + return fmt.Errorf(errFmt, errDecodeCommitmentPrefix, err) + } + + return c.Decode(b) +} + +// UnmarshalJSON decodes data into c, or returns an error. +func (c *Commitment) UnmarshalJSON(data []byte) error { + shadow := new(commitmentShadow) + if err := unmarshalJSON(data, shadow); err != nil { + return fmt.Errorf(errFmt, errDecodeCommitmentPrefix, err) + } + + *c = Commitment(*shadow) + + return nil +} + // Encode returns a compact byte encoding of the signature share. func (s *SignatureShare) Encode() []byte { share := s.SignatureShare.Encode() - out := make([]byte, encodedLength(encSigShare, s.Group)) + _, size := encodedLength(encSigShare, s.Group) + out := make([]byte, size) out[0] = byte(s.Group) - binary.LittleEndian.PutUint64(out[1:9], s.SignerIdentifier) - copy(out[9:], share) + binary.LittleEndian.PutUint16(out[1:3], s.SignerIdentifier) + copy(out[3:], share) return out } @@ -412,28 +552,29 @@ func (s *SignatureShare) Encode() []byte { // Decode takes a byte string and attempts to decode it to return the signature share. func (s *SignatureShare) Decode(data []byte) error { if len(data) < 1 { - return internal.ErrInvalidLength + return fmt.Errorf(errFmt, errDecodeSignatureSharePrefix, errInvalidLength) } c := Ciphersuite(data[0]) - g := c.ECGroup() + g := c.Group() if g == 0 { - return internal.ErrInvalidCiphersuite + return fmt.Errorf(errFmt, errDecodeSignatureSharePrefix, errInvalidCiphersuite) } - if uint64(len(data)) != encodedLength(encSigShare, g) { - return internal.ErrInvalidLength + _, size := encodedLength(encSigShare, g) + if len(data) != size { + return fmt.Errorf(errFmt, errDecodeSignatureSharePrefix, errInvalidLength) } - id := binary.LittleEndian.Uint64(data[1:9]) + id := binary.LittleEndian.Uint16(data[1:3]) if id == 0 { - return errZeroIdentifier + return fmt.Errorf(errFmt, errDecodeSignatureSharePrefix, errZeroIdentifier) } share := g.NewScalar() - if err := share.Decode(data[9:]); err != nil { - return fmt.Errorf("failed to decode signature share: %w", err) + if err := share.Decode(data[3:]); err != nil { + return fmt.Errorf(errFmt, errDecodeSignatureSharePrefix, err) } s.Group = g @@ -443,36 +584,223 @@ func (s *SignatureShare) Decode(data []byte) error { return nil } +// Hex returns the hexadecimal representation of the byte encoding returned by Encode(). +func (s *SignatureShare) Hex() string { + return hex.EncodeToString(s.Encode()) +} + +// DecodeHex sets s to the decoding of the hex encoded representation returned by Hex(). +func (s *SignatureShare) DecodeHex(h string) error { + b, err := hex.DecodeString(h) + if err != nil { + return fmt.Errorf(errFmt, errDecodeSignatureSharePrefix, err) + } + + return s.Decode(b) +} + +// UnmarshalJSON decodes data into s, or returns an error. +func (s *SignatureShare) UnmarshalJSON(data []byte) error { + shadow := new(signatureShareShadow) + if err := unmarshalJSON(data, shadow); err != nil { + return fmt.Errorf(errFmt, errDecodeSignatureSharePrefix, err) + } + + *s = SignatureShare(*shadow) + + return nil +} + // Encode serializes the signature into a byte string. func (s *Signature) Encode() []byte { - r := s.R.Encode() - z := s.Z.Encode() - r = slices.Grow(r, len(z)) + h, l := encodedLength(encSig, s.Group) + out := make([]byte, h, l) + out[0] = byte(s.Group) + out = append(out, s.R.Encode()...) + out = append(out, s.Z.Encode()...) - return append(r, z...) + return out } -// Decode attempts to deserialize the encoded input into the signature in the group. -func (s *Signature) Decode(c Ciphersuite, data []byte) error { - g := c.ECGroup() - if g == 0 { - return internal.ErrInvalidCiphersuite +// Decode deserializes the compact encoding obtained from Encode(), or returns an error. +func (s *Signature) Decode(data []byte) error { + if len(data) <= 1 { + return fmt.Errorf(errFmt, errDecodeSignaturePrefix, errInvalidLength) } - eLen := g.ElementLength() + if !Ciphersuite(data[0]).Available() { + return fmt.Errorf(errFmt, errDecodeSignaturePrefix, errInvalidCiphersuite) + } - if uint64(len(data)) != encodedLength(encSig, g) { - return internal.ErrInvalidLength + g := ecc.Group(data[0]) + _, expectedLength := encodedLength(encSig, g) + + if len(data) != expectedLength { + return fmt.Errorf(errFmt, errDecodeSignaturePrefix, errInvalidLength) } - s.R = g.NewElement() - if err := s.R.Decode(data[:eLen]); err != nil { - return fmt.Errorf("invalid signature - decoding R: %w", err) + r := g.NewElement() + if err := r.Decode(data[1 : 1+g.ElementLength()]); err != nil { + return fmt.Errorf("%w: %w: %w", errDecodeSignaturePrefix, errDecodeProofR, err) + } + + z := g.NewScalar() + if err := z.Decode(data[1+g.ElementLength():]); err != nil { + return fmt.Errorf("%w: %w: %w", errDecodeSignaturePrefix, errDecodeProofZ, err) + } + + s.Group = g + s.R = r + s.Z = z + + return nil +} + +// Hex returns the hexadecimal representation of the byte encoding returned by Encode(). +func (s *Signature) Hex() string { + return hex.EncodeToString(s.Encode()) +} + +// DecodeHex sets s to the decoding of the hex encoded representation returned by Hex(). +func (s *Signature) DecodeHex(h string) error { + b, err := hex.DecodeString(h) + if err != nil { + return fmt.Errorf(errFmt, errDecodeSignaturePrefix, err) } + return s.Decode(b) +} + +// UnmarshalJSON decodes data into s, or returns an error. +func (s *Signature) UnmarshalJSON(data []byte) error { + shadow := new(signatureShadow) + if err := unmarshalJSON(data, shadow); err != nil { + return fmt.Errorf(errFmt, errDecodeSignaturePrefix, err) + } + + *s = Signature(*shadow) + + return nil +} + +// decoding helpers + +type shadowInit interface { + init(g ecc.Group) +} + +type configurationShadow Configuration + +func (c *configurationShadow) init(g ecc.Group) { + c.GroupPublicKey = g.NewElement() +} + +type signerShadow Signer + +func (s *signerShadow) init(g ecc.Group) { + s.KeyShare = &keys.KeyShare{ + Secret: g.NewScalar(), + GroupPublicKey: g.NewElement(), + PublicKeyShare: keys.PublicKeyShare{ + PublicKey: g.NewElement(), + VssCommitment: nil, + ID: 0, + Group: g, + }, + } + s.Configuration = &Configuration{ + GroupPublicKey: g.NewElement(), + SignerPublicKeyShares: nil, + Threshold: 0, + MaxSigners: 0, + Ciphersuite: 0, + group: 0, + verified: false, + keysVerified: false, + } + s.NonceCommitments = make(map[uint64]*Nonce) +} + +type nonceShadow struct { + HidingNonce *ecc.Scalar `json:"hidingNonce"` + BindingNonce *ecc.Scalar `json:"bindingNonce"` + *commitmentShadow `json:"commitment"` +} + +func (n *nonceShadow) init(g ecc.Group) { + n.HidingNonce = g.NewScalar() + n.BindingNonce = g.NewScalar() + n.commitmentShadow = new(commitmentShadow) + n.commitmentShadow.init(g) +} + +type commitmentShadow Commitment + +func (c *commitmentShadow) init(g ecc.Group) { + c.HidingNonceCommitment = g.NewElement() + c.BindingNonceCommitment = g.NewElement() + c.Group = g +} + +type signatureShareShadow SignatureShare + +func (s *signatureShareShadow) init(g ecc.Group) { + s.SignatureShare = g.NewScalar() +} + +type signatureShadow Signature + +func (s *signatureShadow) init(g ecc.Group) { + s.R = g.NewElement() s.Z = g.NewScalar() - if err := s.Z.Decode(data[eLen:]); err != nil { - return fmt.Errorf("invalid signature - decoding Z: %w", err) +} + +func jsonReGetField(key, s, catch string) (string, error) { + r := fmt.Sprintf(`%q:%s`, key, catch) + re := regexp.MustCompile(r) + matches := re.FindStringSubmatch(s) + + if len(matches) != 2 { + return "", internal.ErrEncodingInvalidJSONEncoding + } + + return matches[1], nil +} + +// jsonReGetGroup attempts to find the Ciphersuite JSON encoding in s. +func jsonReGetGroup(s string) (ecc.Group, error) { + f, err := jsonReGetField("group", s, `(\w+)`) + if err != nil { + return 0, err + } + + g, err := strconv.Atoi(f) + if err != nil { + return 0, fmt.Errorf("failed to read Group: %w", err) + } + + if g < 0 || g > 63 { + return 0, errInvalidCiphersuite + } + + c := Ciphersuite(g) + if !c.Available() { + return 0, errInvalidCiphersuite + } + + return ecc.Group(g), nil +} + +func unmarshalJSON(data []byte, target shadowInit) error { + g, err := jsonReGetGroup(string(data)) + if err != nil { + return err + } + + target.init(g) + + if err = json.Unmarshal(data, target); err != nil { + return fmt.Errorf("%w", err) } return nil diff --git a/examples_test.go b/examples_test.go index d1a6bd1..5477f0d 100644 --- a/examples_test.go +++ b/examples_test.go @@ -9,17 +9,21 @@ package frost_test import ( + "bytes" + "encoding/hex" "fmt" + "strings" + + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" "github.com/bytemare/frost/debug" - "github.com/bytemare/frost/keys" ) // Example_signer shows the execution steps of a FROST participant. func Example_signer() { - maxSigners := uint64(5) - threshold := uint64(3) + maxSigners := uint16(5) + threshold := uint16(3) message := []byte("example message") ciphersuite := frost.Default @@ -79,7 +83,7 @@ func Example_signer() { // This is not part of a participant's flow, but we need to collect the commitments of the other participants for // the demo. { - for i := uint64(1); i < threshold; i++ { + for i := uint16(1); i < threshold; i++ { signer, err := configuration.Signer(secretKeyShares[i]) if err != nil { panic(err) @@ -111,8 +115,8 @@ func Example_signer() { // Example_coordinator shows how to aggregate signature shares produced by signers into the final signature // and verify a final FROST signature. func Example_coordinator() { - maxSigners := uint64(5) - threshold := uint64(3) + maxSigners := uint16(5) + threshold := uint16(3) message := []byte("example message") ciphersuite := frost.Default @@ -209,17 +213,241 @@ func Example_coordinator() { // Output: Signature is valid. } -func Example_key_generation() { +// Example_key_generation shows how to create keys in a threshold setup with a centralized trusted dealer. +// - a decentralised protocol described in the original FROST paper +func Example_key_generation_centralised_trusted_dealer() { + panic(nil) +} + +// Example_key_generation shows how to create keys in a threshold setup with distributed key generation described in +// the original FROST paper. +func Example_key_generation_decentralised() { + panic(nil) } +// Example_existing_keys shows how to import existing keys in their canonical byte encoding. func Example_existing_keys() { + ciphersuite := frost.Ristretto255 + id := 5 + signerSecretKey := "941c0685dc7c567dd206a39bce556008367fdf633b56c010cde5561435f75b0e" + signerPublicKey := "d4b9a3acda8acb133c1eff7b99838908c3f9271569c734591ac8f609f321d01a" + verificationKey := "4400e5808c12c6ef9dc751135acf76edfa73780c08e766537bb6c49bea591872" + + fmt.Println("Decoding to key share:") + fmt.Printf("- signer identifier: %d\n", id) + fmt.Printf("- signer secret key: %s\n", signerSecretKey) + fmt.Printf("- signer public key: %s\n", signerPublicKey) + fmt.Printf("- global verification key: %s\n", verificationKey) + + // First, let's rebuilt a public key share. + signerPublicKeyBytes, err := hex.DecodeString(signerPublicKey) + if err != nil { + fmt.Println(err) + } + + signerPublicKeyShare, err := frost.NewPublicKeyShare(ciphersuite, uint16(id), signerPublicKeyBytes) + if err != nil { + fmt.Println(err) + } + + encodedPublicKeyShare := hex.EncodeToString(signerPublicKeyShare.Encode()) + fmt.Printf( + "Decoded individual elements to a public key share, and re-encoded as a whole: %s\n", + encodedPublicKeyShare, + ) + + // Now, we rebuilt a private key share. + signerSecretKeyBytes, err := hex.DecodeString(signerSecretKey) + if err != nil { + fmt.Println(err) + } + + verificationKeyBytes, err := hex.DecodeString(verificationKey) + if err != nil { + fmt.Println(err) + } + + signerKeyShare, err := frost.NewKeyShare( + ciphersuite, + uint16(id), + signerSecretKeyBytes, + signerPublicKeyBytes, + verificationKeyBytes, + ) + if err != nil { + fmt.Println(err) + } + + encodedKeyShare := hex.EncodeToString(signerKeyShare.Encode()) + fmt.Printf("Decoded individual elements to a secret key share, and re-encoded as a whole: %s\n", encodedKeyShare) + + if !strings.HasPrefix(encodedKeyShare, encodedPublicKeyShare) { + fmt.Println( + "Something went wrong when re-encoding: the public key share must be part of the private key share.", + ) + } + + // Output: Decoding to key share: + //- signer identifier: 5 + //- signer secret key: 941c0685dc7c567dd206a39bce556008367fdf633b56c010cde5561435f75b0e + //- signer public key: d4b9a3acda8acb133c1eff7b99838908c3f9271569c734591ac8f609f321d01a + //- global verification key: 4400e5808c12c6ef9dc751135acf76edfa73780c08e766537bb6c49bea591872 + //Decoded individual elements to a public key share, and re-encoded as a whole: 01050000000000d4b9a3acda8acb133c1eff7b99838908c3f9271569c734591ac8f609f321d01a + //Decoded individual elements to a secret key share, and re-encoded as a whole: 01050000000000d4b9a3acda8acb133c1eff7b99838908c3f9271569c734591ac8f609f321d01a941c0685dc7c567dd206a39bce556008367fdf633b56c010cde5561435f75b0e4400e5808c12c6ef9dc751135acf76edfa73780c08e766537bb6c49bea591872 } -// Example_serialization shows how to encode and decode data used in FROST. -func Example_serialization() { +// Example_key_deserialization shows how to encode and decode scalars (e.g. secret keys) and elements (e.g. public keys). +// Note you must know the group beforehand. +func Example_key_deserialization() { + ciphersuite := frost.Ristretto255 + group := ciphersuite.Group() + // Private keys and scalars. + privateKeyHex := "941c0685dc7c567dd206a39bce556008367fdf633b56c010cde5561435f75b0e" + privateKey := group.NewScalar() + + // You can directly decode a hex string to a scalar. + if err := privateKey.DecodeHex(privateKeyHex); err != nil { + fmt.Println(err) + } + + // Or you can use byte slices. + privateKeyBytes, err := hex.DecodeString(privateKeyHex) + if err != nil { + fmt.Println(err) + } + + if err = privateKey.Decode(privateKeyBytes); err != nil { + fmt.Println(err) + } + + if privateKeyHex != privateKey.Hex() { + fmt.Println("something went wrong re-encoding the scalar in hex, which should yield the same output") + } + + if !bytes.Equal(privateKeyBytes, privateKey.Encode()) { + fmt.Println("something went wrong re-encoding the scalar in bytes, which should yield the same output") + } + + // Same thing for public keys and group elements. + publicKeyHex := "d4b9a3acda8acb133c1eff7b99838908c3f9271569c734591ac8f609f321d01a" + publicKey := group.NewElement() + + // You can directly decode a hex string to an element. + if err = publicKey.DecodeHex(publicKeyHex); err != nil { + panic(err) + } - // Public keys and elements. + // Or you can use byte slices. + publicKeyBytes, err := hex.DecodeString(publicKeyHex) + if err != nil { + panic(err) + } + + if err = publicKey.Decode(publicKeyBytes); err != nil { + panic(err) + } + + if publicKeyHex != publicKey.Hex() { + fmt.Println("something went wrong re-encoding the element in hex, which should yield the same output") + } + + if !bytes.Equal(publicKeyBytes, publicKey.Encode()) { + fmt.Println("something went wrong re-encoding the element in bytes, which should yield the same output") + } + + // Output: +} + +// Example_deserialize shows how to encode and decode a FROST messages. +func Example_deserialize() { + groupPublicKeyHex := "74144431f64b052a173c2505e4224a6cc5f3e81d587d4f23369e1b2b1fd0d427" + publicKeySharesHex := []string{ + "010100000000003c5ff80cd593a3b7e9007fdbc2b8fe6caee380e7d23eb7ba35160a5b7a51cb08", + "0102000000000002db540a823f17b975d9eb206ccfbcf3a7667a0365ec1918fa2c3bb69acb105c", + "010300000000008cff0ae1ded90e77095b55218d3632cd90b669d05c888bca26093681e5250870", + } + + g := frost.Default.Group() + groupPublicKey := g.NewElement() + if err := groupPublicKey.DecodeHex(groupPublicKeyHex); err != nil { + fmt.Println(err) + } + + publicKeyShares := make([]*keys.PublicKeyShare, len(publicKeySharesHex)) + for i, p := range publicKeySharesHex { + publicKeyShares[i] = new(keys.PublicKeyShare) + if err := publicKeyShares[i].DecodeHex(p); err != nil { + fmt.Println(err) + } + } + + // This is how to set up the Configuration for FROST, the same for every signer and the coordinator. + // Note that every configuration setup for a Signer needs the public key shares of all other signers participating + // in a signing session (at least for the Sign() step). + configuration := &frost.Configuration{ + Ciphersuite: frost.Default, + Threshold: 2, + MaxSigners: 3, + GroupPublicKey: groupPublicKey, + SignerPublicKeyShares: publicKeyShares, + } + + // Decoding a commitment. + commitment1Hex := "01963090de7d665c5101009073f1a30f4fb9a84275206002fc4394aea7a6cbaf944a7b2f0ae" + + "9143f39fe62808704f776fccfc0080e90e59fdf9bf0156141732728d41fb15554b46a037a40" + commitment2Hex := "017615b41957cca8d70200c2d3d3e8133d18daf95aee5371f397771118be5f3917058502637" + + "0fa893828462400bfab522a542010e70b2b6d4eb388f92b47d6e01abbc16ea24aed5b4fb652" + + commitment1 := new(frost.Commitment) + if err := commitment1.DecodeHex(commitment1Hex); err != nil { + fmt.Println(err) + } + + commitment2 := new(frost.Commitment) + if err := commitment2.DecodeHex(commitment2Hex); err != nil { + fmt.Println(err) + } + + // You can individually check a commitment + if err := configuration.ValidateCommitment(commitment1); err != nil { + fmt.Println(err) + } + + // You can then assemble these commitments to build a list. + commitmentList := make(frost.CommitmentList, 2) + commitmentList[0] = commitment1 + commitmentList[1] = commitment2 + + encodedCommitmentListBytes := commitmentList.Encode() + encodedCommitmentListHex := hex.EncodeToString(encodedCommitmentListBytes) + + // Note that the commitments are the same, but serializing using a CommitmentList is slightly different (3 bytes more) + // since it has a length prefix header. + commitmentListHex := "010200" + + "01963090de7d665c5101009073f1a30f4fb9a84275206002fc4394aea7a6cbaf944a7b2f0ae" + + "9143f39fe62808704f776fccfc0080e90e59fdf9bf0156141732728d41fb15554b46a037a40" + + "017615b41957cca8d70200c2d3d3e8133d18daf95aee5371f397771118be5f3917058502637" + + "0fa893828462400bfab522a542010e70b2b6d4eb388f92b47d6e01abbc16ea24aed5b4fb652" + + if commitmentListHex != encodedCommitmentListHex { + fmt.Println( + "something went wrong when re-encoding the first commitment list, which should yield the same output", + ) + } + + // Decoding a whole commitment list. + decodedCommitmentList, err := frost.DecodeList(encodedCommitmentListBytes) + if err != nil { + fmt.Println(err) + } + + reEncodedListBytes := decodedCommitmentList.Encode() + if !bytes.Equal(reEncodedListBytes, encodedCommitmentListBytes) { + fmt.Println( + "something went wrong when re-encoding the second commitment list, which should yield the same output", + ) + } - // Key shares + // Output: } diff --git a/frost.go b/frost.go index db58e3d..60283e7 100644 --- a/frost.go +++ b/frost.go @@ -13,41 +13,15 @@ import ( "errors" "fmt" "math/big" + "slices" - group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" + "github.com/bytemare/ecc" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/keys" ) -/* -- check RFC -- update description - - more buzz - - show supported ciphersuites -- Check for - - FROST2-CKM: https://eprint.iacr.org/2021/1375 (has duplicate checks) - - FROST2-BTZ: https://eprint.iacr.org/2022/833 - - FROST3 (ROAST): https://eprint.iacr.org/2022/550 (most efficient variant of FROST) - - wrapper increasing robustness and apparently reducing some calculations? - - Chu: https://eprint.iacr.org/2023/899 - - re-randomize keys: https://eprint.iacr.org/2024/436.pdf - -TODO: - -- identifiers, min and max, are uint16 - -- verify serialize and deserialize functions of messages, scalars, and elements -- add deserialization examples with hardcoded input -- add versioning to encodings -- align on serialization of https://frost.zfnd.org/user/serialization.html (good doc)\ -- make a `go run`-able program to generate trusted dealer keys -- DKG: can derive an identifier from a byte string (e.g. name or email address) - -*/ - -// Ciphersuite identifies the group and hash to use for FROST. +// Ciphersuite identifies the group and hash function to use for FROST. type Ciphersuite byte const ( @@ -55,25 +29,25 @@ const ( Default = Ristretto255 // Ristretto255 uses Ristretto255 and SHA-512. This ciphersuite is recommended. - Ristretto255 = Ciphersuite(group.Ristretto255Sha512) + Ristretto255 = Ciphersuite(ecc.Ristretto255Sha512) // Ed448 uses Edwards448 and SHAKE256, producing Ed448-compliant signatures as specified in RFC8032. // ed448 = Ciphersuite(2). // P256 uses P-256 and SHA-256. - P256 = Ciphersuite(group.P256Sha256) + P256 = Ciphersuite(ecc.P256Sha256) // P384 uses P-384 and SHA-384. - P384 = Ciphersuite(group.P384Sha384) + P384 = Ciphersuite(ecc.P384Sha384) // P521 uses P-521 and SHA-512. - P521 = Ciphersuite(group.P521Sha512) + P521 = Ciphersuite(ecc.P521Sha512) // Ed25519 uses Edwards25519 and SHA-512, producing Ed25519-compliant signatures as specified in RFC8032. - Ed25519 = Ciphersuite(group.Edwards25519Sha512) + Ed25519 = Ciphersuite(ecc.Edwards25519Sha512) // Secp256k1 uses Secp256k1 and SHA-256. - Secp256k1 = Ciphersuite(group.Secp256k1) + Secp256k1 = Ciphersuite(ecc.Secp256k1Sha256) ) // Available returns whether the selected ciphersuite is available. @@ -86,23 +60,23 @@ func (c Ciphersuite) Available() bool { } } -// ECGroup returns the elliptic curve group used in the ciphersuite. -func (c Ciphersuite) ECGroup() group.Group { +// Group returns the elliptic curve group used in the ciphersuite. +func (c Ciphersuite) Group() ecc.Group { if !c.Available() { return 0 } - return group.Group(c) + return ecc.Group(c) } // Configuration holds the Configuration for a signing session. type Configuration struct { - GroupPublicKey *group.Element - SignerPublicKeyShares []*keys.PublicKeyShare - Threshold uint64 - MaxSigners uint64 - Ciphersuite Ciphersuite - group group.Group + GroupPublicKey *ecc.Element `json:"groupPublicKey"` + SignerPublicKeyShares []*keys.PublicKeyShare `json:"signerPublicKeyShares"` + Threshold uint16 `json:"threshold"` + MaxSigners uint16 `json:"maxSigners"` + Ciphersuite Ciphersuite `json:"ciphersuite"` + group ecc.Group verified bool keysVerified bool } @@ -111,6 +85,18 @@ var ( errInvalidThresholdParameter = errors.New("threshold is 0 or higher than maxSigners") errInvalidMaxSignersOrder = errors.New("maxSigners is higher than group order") errInvalidNumberOfPublicKeys = errors.New("invalid number of public keys (lower than threshold or above maximum)") + errKeyShareNotMatch = errors.New( + "the key share's group public key does not match the one in the configuration", + ) + errInvalidSecretKey = errors.New("provided key share has invalid secret key") + errKeyShareNil = errors.New("provided key share is nil") + errInvalidKeyShare = errors.New("provided key share has non-matching secret and public keys") + errInvalidKeyShareUnknownID = errors.New( + "provided key share has no registered signer identifier in the configuration", + ) + errPublicKeyShareNoMatch = errors.New( + "provided key share has a different public key than the one registered for that signer in the configuration", + ) ) // Init verifies whether the configuration's components are valid, in which case it initializes internal values, or @@ -191,51 +177,46 @@ func (c *Configuration) ValidateKeyShare(keyShare *keys.KeyShare) error { } if keyShare == nil { - return errors.New("provided key share is nil") + return errKeyShareNil } if err := c.ValidatePublicKeyShare(keyShare.Public()); err != nil { return err } - if c.GroupPublicKey.Equal(keyShare.GroupPublicKey) != 1 { - return errors.New( - "the key share's group public key does not match the one in the configuration", - ) + if !c.GroupPublicKey.Equal(keyShare.GroupPublicKey) { + return errKeyShareNotMatch } if keyShare.Secret == nil || keyShare.Secret.IsZero() { - return errors.New("provided key share has invalid secret key") + return errInvalidSecretKey } - if c.group.Base().Multiply(keyShare.Secret).Equal(keyShare.PublicKey) != 1 { - return errors.New("provided key share has non-matching secret and public keys") + if !c.group.Base().Multiply(keyShare.Secret).Equal(keyShare.PublicKey) { + return errInvalidKeyShare } pk := c.getSignerPubKey(keyShare.ID) if pk == nil { - return errors.New("provided key share has no registered signer identifier in the configuration") + return errInvalidKeyShareUnknownID } - if pk.Equal(keyShare.PublicKey) != 1 { - return errors.New( - "provided key share has a different public key than " + - "the one registered for that signer in the configuration", - ) + if !pk.Equal(keyShare.PublicKey) { + return errPublicKeyShareNoMatch } return nil } func (c *Configuration) verifySignerPublicKeyShares() error { - length := uint64(len(c.SignerPublicKeyShares)) - if length < c.Threshold || length > c.MaxSigners { + length := len(c.SignerPublicKeyShares) + if length < int(c.Threshold) || length > int(c.MaxSigners) { return errInvalidNumberOfPublicKeys } // Sets to detect duplicates. - pkSet := make(map[string]uint64, len(c.SignerPublicKeyShares)) - idSet := make(map[uint64]struct{}, len(c.SignerPublicKeyShares)) + pkSet := make(map[string]uint16, len(c.SignerPublicKeyShares)) + idSet := make(map[uint16]struct{}, len(c.SignerPublicKeyShares)) for i, pks := range c.SignerPublicKeyShares { if pks == nil { @@ -266,26 +247,33 @@ func (c *Configuration) verifySignerPublicKeyShares() error { return nil } +func getOrder(g ecc.Group) *big.Int { + bytes := g.Order() + + if g == ecc.Ristretto255Sha512 || g == ecc.Edwards25519Sha512 { + slices.Reverse(bytes) + } + + return big.NewInt(0).SetBytes(bytes) +} + func (c *Configuration) verifyConfiguration() error { if !c.Ciphersuite.Available() { return internal.ErrInvalidCiphersuite } - g := group.Group(c.Ciphersuite) + g := ecc.Group(c.Ciphersuite) if c.Threshold == 0 || c.Threshold > c.MaxSigners { return errInvalidThresholdParameter } - order, _ := new(big.Int).SetString(g.Order(), 0) - if order == nil { - panic("can't set group order number") - } + order := getOrder(g) + maxSigners := new(big.Int).SetUint64(uint64(c.MaxSigners)) - bigMax := new(big.Int).SetUint64(c.MaxSigners) - if order.Cmp(bigMax) != 1 { - // This is unlikely to happen, as the usual group orders cannot be represented in a uint64. - // Only a new, unregistered group would make it fail here. + // This is unlikely to happen, as the usual Group orders cannot be represented in a uint64. + // Only a new, unregistered Group would make it fail here. + if order.Cmp(maxSigners) != 1 { return errInvalidMaxSignersOrder } @@ -299,7 +287,7 @@ func (c *Configuration) verifyConfiguration() error { return nil } -func (c *Configuration) getSignerPubKey(id uint64) *group.Element { +func (c *Configuration) getSignerPubKey(id uint16) *ecc.Element { for _, pks := range c.SignerPublicKeyShares { if pks.ID == id { return pks.PublicKey @@ -309,7 +297,7 @@ func (c *Configuration) getSignerPubKey(id uint64) *group.Element { return nil } -func (c *Configuration) validateIdentifier(id uint64) error { +func (c *Configuration) validateIdentifier(id uint16) error { switch { case id == 0: return internal.ErrIdentifierIs0 @@ -320,32 +308,32 @@ func (c *Configuration) validateIdentifier(id uint64) error { return nil } -func (c *Configuration) validateGroupElement(e *group.Element) error { +func (c *Configuration) validateGroupElement(e *ecc.Element) error { switch { case e == nil: return errors.New("is nil") case e.IsIdentity(): return errors.New("is the identity element") - case group.Group(c.Ciphersuite).Base().Equal(e) == 1: + case ecc.Group(c.Ciphersuite).Base().Equal(e): return errors.New("is the group generator (base element)") } return nil } -func (c *Configuration) challenge(lambda *group.Scalar, message []byte, groupCommitment *group.Element) *group.Scalar { +func (c *Configuration) challenge(lambda *ecc.Scalar, message []byte, groupCommitment *ecc.Element) *ecc.Scalar { chall := SchnorrChallenge(c.group, message, groupCommitment, c.GroupPublicKey) return chall.Multiply(lambda) } // SchnorrChallenge computes the per-message SchnorrChallenge. -func SchnorrChallenge(g group.Group, msg []byte, r, pk *group.Element) *group.Scalar { +func SchnorrChallenge(g ecc.Group, msg []byte, r, pk *ecc.Element) *ecc.Scalar { return internal.H2(g, internal.Concatenate(r.Encode(), pk.Encode(), msg)) } // VerifySignature returns whether the signature of the message is valid under publicKey. -func VerifySignature(c Ciphersuite, message []byte, signature *Signature, publicKey *group.Element) error { - g := c.ECGroup() +func VerifySignature(c Ciphersuite, message []byte, signature *Signature, publicKey *ecc.Element) error { + g := c.Group() if g == 0 { return internal.ErrInvalidCiphersuite } @@ -355,13 +343,13 @@ func VerifySignature(c Ciphersuite, message []byte, signature *Signature, public l := g.Base().Multiply(signature.Z) // Clear the cofactor for Edwards25519. - if g == group.Edwards25519Sha512 { - cofactor := group.Edwards25519Sha512.NewScalar().SetUInt64(8) + if g == ecc.Edwards25519Sha512 { + cofactor := ecc.Edwards25519Sha512.NewScalar().SetUInt64(8) l.Multiply(cofactor) r.Multiply(cofactor) } - if l.Equal(r) != 1 { + if !l.Equal(r) { return errInvalidSignature } @@ -370,7 +358,7 @@ func VerifySignature(c Ciphersuite, message []byte, signature *Signature, public // NewPublicKeyShare returns a PublicKeyShare from separately encoded key material. To deserialize a byte string // produced by the PublicKeyShare.Encode() method, use the PublicKeyShare.Decode() method. -func NewPublicKeyShare(c Ciphersuite, id uint64, signerPublicKey []byte) (*keys.PublicKeyShare, error) { +func NewPublicKeyShare(c Ciphersuite, id uint16, signerPublicKey []byte) (*keys.PublicKeyShare, error) { if !c.Available() { return nil, internal.ErrInvalidCiphersuite } @@ -379,7 +367,7 @@ func NewPublicKeyShare(c Ciphersuite, id uint64, signerPublicKey []byte) (*keys. return nil, internal.ErrIdentifierIs0 } - g := c.ECGroup() + g := c.Group() pk := g.NewElement() if err := pk.Decode(signerPublicKey); err != nil { @@ -387,10 +375,10 @@ func NewPublicKeyShare(c Ciphersuite, id uint64, signerPublicKey []byte) (*keys. } return &keys.PublicKeyShare{ - PublicKey: pk, - ID: id, - Group: g, - Commitment: nil, + PublicKey: pk, + ID: id, + Group: g, + VssCommitment: nil, }, nil } @@ -398,7 +386,7 @@ func NewPublicKeyShare(c Ciphersuite, id uint64, signerPublicKey []byte) (*keys. // KeyShare.Encode() method, use the KeyShare.Decode() method. func NewKeyShare( c Ciphersuite, - id uint64, + id uint16, secretShare, signerPublicKey, groupPublicKey []byte, ) (*keys.KeyShare, error) { pks, err := NewPublicKeyShare(c, id, signerPublicKey) @@ -406,13 +394,18 @@ func NewKeyShare( return nil, err } - g := c.ECGroup() + g := c.Group() s := g.NewScalar() if err = s.Decode(secretShare); err != nil { return nil, fmt.Errorf("could not decode secret share: %w", err) } + vpk := g.Base().Multiply(s) + if !vpk.Equal(pks.PublicKey) { + return nil, errInvalidKeyShare + } + gpk := g.NewElement() if err = gpk.Decode(groupPublicKey); err != nil { return nil, fmt.Errorf("could not decode the group public key: %w", err) @@ -421,6 +414,6 @@ func NewKeyShare( return &keys.KeyShare{ Secret: s, GroupPublicKey: gpk, - PublicKeyShare: secretsharing.PublicKeyShare(*pks), + PublicKeyShare: *pks, }, nil } diff --git a/go.mod b/go.mod index be5695d..3bc910d 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,20 @@ module github.com/bytemare/frost -go 1.22.3 +go 1.23.1 require ( filippo.io/edwards25519 v1.1.0 - github.com/bytemare/crypto v0.7.5 - github.com/bytemare/dkg v0.0.0-20240724114445-f93f2b4fc5d5 + github.com/bytemare/dkg v0.0.0-20241004153610-04af7b423593 + github.com/bytemare/ecc v0.8.2 github.com/bytemare/hash v0.3.0 - github.com/bytemare/secret-sharing v0.3.0 + github.com/bytemare/secret-sharing v0.6.0 github.com/gtank/ristretto255 v0.1.2 ) require ( filippo.io/nistec v0.0.3 // indirect github.com/bytemare/hash2curve v0.3.0 // indirect - github.com/bytemare/secp256k1 v0.1.4 // indirect - golang.org/x/crypto v0.26.0 // indirect - golang.org/x/sys v0.24.0 // indirect + github.com/bytemare/secp256k1 v0.1.6 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index f6a5762..c11d5ef 100644 --- a/go.sum +++ b/go.sum @@ -2,21 +2,21 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/nistec v0.0.3 h1:h336Je2jRDZdBCLy2fLDUd9E2unG32JLwcJi0JQE9Cw= filippo.io/nistec v0.0.3/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw= -github.com/bytemare/crypto v0.7.5 h1:aRZzSmRZFlPt4ydpI5KKr+UQbzNd1559mNnRj9tNjRw= -github.com/bytemare/crypto v0.7.5/go.mod h1:qRA6Tdg0Q9zMTuxeKkyVtEyDAgIuwM0YN5tffzFQJQw= -github.com/bytemare/dkg v0.0.0-20240724114445-f93f2b4fc5d5 h1:A19axQ2U11TMSRBYHsWUhRrudV8zR9HWQYcFAJozcaU= -github.com/bytemare/dkg v0.0.0-20240724114445-f93f2b4fc5d5/go.mod h1:jXow/Ycil51++OmvmBJ25ksb93RtSEnMxGxadZjWRVk= +github.com/bytemare/dkg v0.0.0-20241004153610-04af7b423593 h1:pIVaRXwCFangFes/2adBWxQskwWbWU5DlgO/i1f0Q0w= +github.com/bytemare/dkg v0.0.0-20241004153610-04af7b423593/go.mod h1:uu0zK5IObiwEexMegqZe/wLyK+HZTstGnrz47ZdDkiI= +github.com/bytemare/ecc v0.8.2 h1:MN+Ah48hApFpzJgIMa1xOrK7/R5uwCV06dtJyuHAi3Y= +github.com/bytemare/ecc v0.8.2/go.mod h1:dvkSikSCejw8YaTdJs6lZSN4qz9B4PC5PtGq+CRDmHk= github.com/bytemare/hash v0.3.0 h1:RqFMt3mqpF7UxLdjBrsOZm/2cz0cQiAOnYc9gDLopWE= github.com/bytemare/hash v0.3.0/go.mod h1:YKOBchL0l8hRLFinVCL8YUKokGNIMhrWEHPHo3EV7/M= github.com/bytemare/hash2curve v0.3.0 h1:41Npcbc+u/E252A5aCMtxDcz7JPkkX1QzShneTFm4eg= github.com/bytemare/hash2curve v0.3.0/go.mod h1:itj45U8uqvCtWC0eCswIHVHswXcEHkpFui7gfJdPSfQ= -github.com/bytemare/secp256k1 v0.1.4 h1:6F1yP6RiUiWwH7AsGHsHktmHm24QcetdDcc39roBd2M= -github.com/bytemare/secp256k1 v0.1.4/go.mod h1:Pxb9miDs8PTt5mOktvvXiRflvLxI1wdxbXrc6IYsaho= -github.com/bytemare/secret-sharing v0.3.0 h1:IK+wi3dhh+s8amN4xqdpgd8Byi36jZJQ9oAX3bowto0= -github.com/bytemare/secret-sharing v0.3.0/go.mod h1:kZ8Ty314nPP1LLd9ZsAAoc77625CEvXzRtimtEE1M9I= +github.com/bytemare/secp256k1 v0.1.6 h1:5pOA84UBBTPTUmCkjtH6jHrbvZSh2kyxG0mW/OjSih0= +github.com/bytemare/secp256k1 v0.1.6/go.mod h1:Zr7o3YCog5jKx5JwgYbj984gRIqVioTDZMSDo1y0zgE= +github.com/bytemare/secret-sharing v0.6.0 h1:/gQhsC3BY2pn7nIl+1sQDtI4c9IfkjuTbBXsvh922UM= +github.com/bytemare/secret-sharing v0.6.0/go.mod h1:CQ7ALe5CIbvnEGhcF50LKu9brAki7efQPT3d/UUhzQQ= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/errors.go b/internal/errors.go index babf0a9..c37c335 100644 --- a/internal/errors.go +++ b/internal/errors.go @@ -22,4 +22,7 @@ var ( // ErrIdentifierIs0 is returned when the invalid 0 identifier is encountered. ErrIdentifierIs0 = errors.New("identifier is 0") + + // ErrEncodingInvalidJSONEncoding is returned when invalid JSON is detected. + ErrEncodingInvalidJSONEncoding = errors.New("invalid JSON encoding") ) diff --git a/internal/hashing.go b/internal/hashing.go index 90e5652..1091172 100644 --- a/internal/hashing.go +++ b/internal/hashing.go @@ -10,7 +10,7 @@ package internal import ( "filippo.io/edwards25519" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" "github.com/bytemare/hash" "github.com/gtank/ristretto255" ) @@ -30,7 +30,7 @@ type ciphersuite struct { contextString []byte } -var ciphersuites = [group.Secp256k1 + 1]ciphersuite{ +var ciphersuites = [ecc.Secp256k1Sha256 + 1]ciphersuite{ { // Ristretto255 hash: hash.SHA512.New(), contextString: []byte(ristretto255ContextString), @@ -61,8 +61,8 @@ var ciphersuites = [group.Secp256k1 + 1]ciphersuite{ }, } -func h1Ed25519(input ...[]byte) *group.Scalar { - hashed := ciphersuites[group.Edwards25519Sha512-1].hash.Hash(0, input...) +func h1Ed25519(input ...[]byte) *ecc.Scalar { + hashed := ciphersuites[ecc.Edwards25519Sha512-1].hash.Hash(0, input...) s := edwards25519.NewScalar() if _, err := s.SetUniformBytes(hashed); err != nil { @@ -70,7 +70,7 @@ func h1Ed25519(input ...[]byte) *group.Scalar { panic(err) } - s2 := group.Edwards25519Sha512.NewScalar() + s2 := ecc.Edwards25519Sha512.NewScalar() if err := s2.Decode(s.Bytes()); err != nil { // Can't fail because the underlying encoding/decoding is compatible. panic(err) @@ -79,14 +79,14 @@ func h1Ed25519(input ...[]byte) *group.Scalar { return s2 } -func hx(g group.Group, input, dst []byte) *group.Scalar { - var sc *group.Scalar +func hx(g ecc.Group, input, dst []byte) *ecc.Scalar { + var sc *ecc.Scalar c := ciphersuites[g-1] switch g { - case group.Edwards25519Sha512: + case ecc.Edwards25519Sha512: sc = h1Ed25519(c.contextString, dst, input) - case group.Ristretto255Sha512: + case ecc.Ristretto255Sha512: h := c.hash.Hash(0, c.contextString, dst, input) s := ristretto255.NewScalar().FromUniformBytes(h) @@ -95,7 +95,7 @@ func hx(g group.Group, input, dst []byte) *group.Scalar { // Can't fail because the underlying encoding/decoding is compatible. panic(err) } - case group.P256Sha256, group.P384Sha384, group.P521Sha512, group.Secp256k1: + case ecc.P256Sha256, ecc.P384Sha384, ecc.P521Sha512, ecc.Secp256k1Sha256: sc = g.HashToScalar(input, append(c.contextString, dst...)) default: // Can't fail because the function is always called with a compatible group previously checked. @@ -106,13 +106,13 @@ func hx(g group.Group, input, dst []byte) *group.Scalar { } // H1 hashes the input and proves the "rho" DST. -func H1(g group.Group, input []byte) *group.Scalar { +func H1(g ecc.Group, input []byte) *ecc.Scalar { return hx(g, input, []byte("rho")) } // H2 hashes the input and proves the "chal" DST. -func H2(g group.Group, input []byte) *group.Scalar { - if g == group.Edwards25519Sha512 { +func H2(g ecc.Group, input []byte) *ecc.Scalar { + if g == ecc.Edwards25519Sha512 { // For compatibility with RFC8032 H2 doesn't use a domain separator for Edwards25519. return h1Ed25519(input) } @@ -121,18 +121,18 @@ func H2(g group.Group, input []byte) *group.Scalar { } // H3 hashes the input and proves the "nonce" DST. -func H3(g group.Group, input []byte) *group.Scalar { +func H3(g ecc.Group, input []byte) *ecc.Scalar { return hx(g, input, []byte("nonce")) } // H4 hashes the input and proves the "msg" DST. -func H4(g group.Group, msg []byte) []byte { +func H4(g ecc.Group, msg []byte) []byte { cs := ciphersuites[g-1] return cs.hash.Hash(0, cs.contextString, []byte("msg"), msg) } // H5 hashes the input and proves the "com" DST. -func H5(g group.Group, msg []byte) []byte { +func H5(g ecc.Group, msg []byte) []byte { cs := ciphersuites[g-1] return cs.hash.Hash(0, cs.contextString, []byte("com"), msg) } diff --git a/internal/lambda.go b/internal/lambda.go index 02d9655..d9d87ea 100644 --- a/internal/lambda.go +++ b/internal/lambda.go @@ -10,25 +10,27 @@ package internal import ( "encoding/hex" + "encoding/json" "fmt" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" + eccEncoding "github.com/bytemare/ecc/encoding" "github.com/bytemare/hash" secretsharing "github.com/bytemare/secret-sharing" ) -// Lambda derives the interpolating value for id in the polynomial made by the participant identifiers. +// ComputeLambda derives the interpolating value for id in the polynomial made by the participant identifiers. // This function assumes that: // - id is non-nil and != 0. // - every scalar in participants is non-nil and != 0. // - there are no duplicates in participants. -func Lambda(g group.Group, id uint64, participants []*group.Scalar) *group.Scalar { - sid := g.NewScalar().SetUInt64(id) +func ComputeLambda(g ecc.Group, id uint16, participants []*ecc.Scalar) *ecc.Scalar { + sid := g.NewScalar().SetUInt64(uint64(id)) numerator := g.NewScalar().One() denominator := g.NewScalar().One() for _, participant := range participants { - if participant.Equal(sid) == 1 { + if participant.Equal(sid) { continue } @@ -39,41 +41,76 @@ func Lambda(g group.Group, id uint64, participants []*group.Scalar) *group.Scala return numerator.Multiply(denominator.Invert()) } -// LambdaRegistry holds a signers pre-computed lambda values, indexed by the list of participants they are associated -// to. A sorted set of participants will yield the same lambda. -type LambdaRegistry map[string]*group.Scalar +// A Lambda is the interpolating value for a given id in the polynomial made by the participant identifiers. +type Lambda struct { + Value *ecc.Scalar `json:"value"` + Group ecc.Group `json:"group"` +} + +type lambdaShadow Lambda + +// UnmarshalJSON decodes data into l, or returns an error. +func (l *Lambda) UnmarshalJSON(data []byte) error { + shadow := new(lambdaShadow) + + g, err := eccEncoding.JSONReGetGroup(string(data)) + if err != nil { + return fmt.Errorf("failed to decode Lambda: %w", err) + } + + shadow.Group = g + shadow.Value = g.NewScalar() + + if err = json.Unmarshal(data, shadow); err != nil { + return fmt.Errorf("failed to decode Lambda: %w", err) + } + + *l = Lambda(*shadow) + + return nil +} + +// LambdaRegistry holds a signers pre-computed Lambda values, indexed by the list of participants they are associated +// to. A sorted set of participants will yield the same Lambda. +type LambdaRegistry map[string]*Lambda const lambdaRegistryKeyDomainSeparator = "FROST-participants" -func lambdaRegistryKey(participants []uint64) string { +func lambdaRegistryKey(participants []uint16) string { a := fmt.Sprint(lambdaRegistryKeyDomainSeparator, participants) return hex.EncodeToString(hash.SHA256.Hash([]byte(a))) // Length = 32 bytes, 64 in hex string } -// New creates a new lambda and for the participant list for the participant id, and registers it. +// New creates a new Lambda and for the participant list for the participant id, and registers it. // This function assumes that: // - id is non-nil and != 0. // - every participant id is != 0. // - there are no duplicates in participants. -func (l LambdaRegistry) New(g group.Group, id uint64, participants []uint64) *group.Scalar { - polynomial := secretsharing.NewPolynomialFromListFunc(g, participants, func(p uint64) *group.Scalar { - return g.NewScalar().SetUInt64(p) +func (l LambdaRegistry) New(g ecc.Group, id uint16, participants []uint16) *ecc.Scalar { + polynomial := secretsharing.NewPolynomialFromListFunc(g, participants, func(p uint16) *ecc.Scalar { + return g.NewScalar().SetUInt64(uint64(p)) }) - lambda := Lambda(g, id, polynomial) + lambda := ComputeLambda(g, id, polynomial) l.Set(participants, lambda) return lambda } -// Get returns the recorded lambda for the list of participants, or nil if it wasn't found. -func (l LambdaRegistry) Get(participants []uint64) *group.Scalar { +// Get returns the recorded Lambda for the list of participants, or nil if it wasn't found. +func (l LambdaRegistry) Get(participants []uint16) *ecc.Scalar { key := lambdaRegistryKey(participants) - return l[key] + + v := l[key] + if v == nil { + return nil + } + + return v.Value } -// GetOrNew returns the recorded lambda for the list of participants, or created, records, and returns a new one if +// GetOrNew returns the recorded Lambda for the list of participants, or created, records, and returns a new one if // it wasn't found. -func (l LambdaRegistry) GetOrNew(g group.Group, id uint64, participants []uint64) *group.Scalar { +func (l LambdaRegistry) GetOrNew(g ecc.Group, id uint16, participants []uint16) *ecc.Scalar { lambda := l.Get(participants) if lambda == nil { return l.New(g, id, participants) @@ -82,32 +119,38 @@ func (l LambdaRegistry) GetOrNew(g group.Group, id uint64, participants []uint64 return lambda } -// Set records lambda for the given set of participants. -func (l LambdaRegistry) Set(participants []uint64, lambda *group.Scalar) { +// Set records Lambda for the given set of participants. +func (l LambdaRegistry) Set(participants []uint16, value *ecc.Scalar) { key := lambdaRegistryKey(participants) - l[key] = lambda + l[key] = &Lambda{ + Group: value.Group(), + Value: value, + } } -// Delete deletes the lambda for the given set of participants. -func (l LambdaRegistry) Delete(participants []uint64) { +// Delete deletes the Lambda for the given set of participants. +func (l LambdaRegistry) Delete(participants []uint16) { key := lambdaRegistryKey(participants) - l[key].Zero() + l[key].Value.Zero() delete(l, key) } // Decode populates the receiver from the byte encoded serialization in data. -func (l LambdaRegistry) Decode(g group.Group, data []byte) error { +func (l LambdaRegistry) Decode(g ecc.Group, data []byte) error { offset := 0 for offset < len(data) { key := data[offset : offset+32] offset += 32 - lambda := g.NewScalar() - if err := lambda.Decode(data[offset : offset+g.ScalarLength()]); err != nil { - return fmt.Errorf("failed to decode lambda: %w", err) + value := g.NewScalar() + if err := value.Decode(data[offset : offset+g.ScalarLength()]); err != nil { + return fmt.Errorf("failed to decode Lambda: %w", err) } - l[hex.EncodeToString(key)] = lambda + l[hex.EncodeToString(key)] = &Lambda{ + Group: g, + Value: value, + } offset += g.ScalarLength() } diff --git a/keys/keys.go b/keys/keys.go deleted file mode 100644 index 440e80b..0000000 --- a/keys/keys.go +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: MIT -// -// Copyright (C) 2024 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 keys defines the Key structures used in FROST. -package keys - -import ( - "fmt" - - group "github.com/bytemare/crypto" - "github.com/bytemare/dkg" - secretsharing "github.com/bytemare/secret-sharing" -) - -// KeyShare identifies the sharded key share for a given participant. -type KeyShare secretsharing.KeyShare - -// Identifier returns the identity for this share. -func (k *KeyShare) Identifier() uint64 { - return (*secretsharing.KeyShare)(k).Identifier() -} - -// SecretKey returns the participant's secret share. -func (k *KeyShare) SecretKey() *group.Scalar { - return (*secretsharing.KeyShare)(k).SecretKey() -} - -// Public returns the public key share and identifier corresponding to the secret key share. -func (k *KeyShare) Public() *PublicKeyShare { - return (*PublicKeyShare)(&k.PublicKeyShare) -} - -// Encode serializes k into a compact byte string. -func (k *KeyShare) Encode() []byte { - return (*secretsharing.KeyShare)(k).Encode() -} - -// Decode deserializes the compact encoding obtained from Encode(), or returns an error. -func (k *KeyShare) Decode(data []byte) error { - if err := (*secretsharing.KeyShare)(k).Decode(data); err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} - -// UnmarshalJSON decodes data into k, or returns an error. -func (k *KeyShare) UnmarshalJSON(data []byte) error { - if err := (*secretsharing.KeyShare)(k).UnmarshalJSON(data); err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} - -// PublicKeyShare specifies the public key of a participant identified with ID. -type PublicKeyShare secretsharing.PublicKeyShare - -// Verify returns whether the PublicKeyShare's public key is valid given its VSS commitment to the secret polynomial. -func (p *PublicKeyShare) Verify(commitments [][]*group.Element) bool { - return dkg.VerifyPublicKey(dkg.Ciphersuite(p.Group), p.ID, p.PublicKey, commitments) == nil -} - -// Encode serializes p into a compact byte string. -func (p *PublicKeyShare) Encode() []byte { - return (*secretsharing.PublicKeyShare)(p).Encode() -} - -// Decode deserializes the compact encoding obtained from Encode(), or returns an error. -func (p *PublicKeyShare) Decode(data []byte) error { - if err := (*secretsharing.PublicKeyShare)(p).Decode(data); err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} - -// UnmarshalJSON decodes data into p, or returns an error. -func (p *PublicKeyShare) UnmarshalJSON(data []byte) error { - if err := (*secretsharing.PublicKeyShare)(p).UnmarshalJSON(data); err != nil { - return fmt.Errorf("%w", err) - } - - return nil -} diff --git a/signer.go b/signer.go index 88a2182..d9e4d1c 100644 --- a/signer.go +++ b/signer.go @@ -13,43 +13,43 @@ import ( "encoding/binary" "fmt" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/keys" ) // SignatureShare represents a Signer's signature share and its identifier. type SignatureShare struct { - SignatureShare *group.Scalar - SignerIdentifier uint64 - Group group.Group + SignatureShare *ecc.Scalar `json:"signatureShare"` + SignerIdentifier uint16 `json:"signerIdentifier"` + Group ecc.Group `json:"group"` } // Signer is a participant in a signing group. type Signer struct { // The KeyShare holds the signer's secret and public info, such as keys and identifier. - KeyShare *keys.KeyShare + KeyShare *keys.KeyShare `json:"keyShare"` // LambdaRegistry records all interpolating values for the signers for different combinations of participant // groups. Each group makes up a unique polynomial defined by the participants' identifiers. A value will be // computed once for the first time a group is encountered, and kept across encodings and decodings of the signer, // accelerating subsequent signatures within the same group of signers. - LambdaRegistry internal.LambdaRegistry + LambdaRegistry internal.LambdaRegistry `json:"lambdaRegistry"` // NonceCommitments maps Nonce and their NonceCommitments to their Commitment's identifier. - NonceCommitments map[uint64]*Nonce + NonceCommitments map[uint64]*Nonce `json:"nonceCommitments"` // Configuration is the core FROST setup configuration. - Configuration *Configuration + Configuration *Configuration `json:"configuration"` // HidingRandom can be set to force the use its value for HidingNonce generation. This is only encouraged for vector // reproduction, but should be left to nil in any production deployments. - HidingRandom []byte + HidingRandom []byte `json:"hidingRandom,omitempty"` // HidingRandom can be set to force the use its value for HidingNonce generation. This is only encouraged for vector // reproduction, but should be left to nil in any production deployments. - BindingRandom []byte + BindingRandom []byte `json:"bindingRandom,omitempty"` } // Nonce holds the signing nonces and their commitments. The Signer.Commit() method will generate and record a new nonce @@ -57,9 +57,9 @@ type Signer struct { // create a signature share. Note that nonces and their commitments are agnostic of the upcoming message to sign, and // can therefore be pre-computed and the commitments shared before the signing session, saving a round-trip. type Nonce struct { - HidingNonce *group.Scalar - BindingNonce *group.Scalar - *Commitment + HidingNonce *ecc.Scalar `json:"hidingNonce"` + BindingNonce *ecc.Scalar `json:"bindingNonce"` + *Commitment `json:"commitment"` } // ClearNonceCommitment zeroes-out the nonces and their commitments, and unregisters the nonce record. @@ -74,7 +74,7 @@ func (s *Signer) ClearNonceCommitment(commitmentID uint64) { } // Identifier returns the Signer's identifier. -func (s *Signer) Identifier() uint64 { +func (s *Signer) Identifier() uint16 { return s.KeyShare.ID } @@ -88,7 +88,7 @@ func randomCommitmentID() uint64 { return binary.LittleEndian.Uint64(buf) } -func (s *Signer) generateNonce(secret *group.Scalar, random []byte) *group.Scalar { +func (s *Signer) generateNonce(secret *ecc.Scalar, random []byte) *ecc.Scalar { if random == nil { random = internal.RandomBytes(32) } @@ -97,16 +97,15 @@ func (s *Signer) generateNonce(secret *group.Scalar, random []byte) *group.Scala } func (s *Signer) genNonceID() uint64 { - cid := randomCommitmentID() + var cid uint64 - // In the extremely rare and unlikely case the CSPRNG returns an already registered ID, we try again 128 times - // before failing. + // In the extremely rare and unlikely case the CSPRNG returns an already registered ID, we try again 128 times max + // before failing. CSPRNG is a serious issue at which point protocol execution must be stopped. for range 128 { + cid = randomCommitmentID() if _, exists := s.NonceCommitments[cid]; !exists { return cid } - - cid = randomCommitmentID() } panic("FATAL: CSPRNG could not generate unique commitment identifiers over 128 iterations") @@ -144,11 +143,11 @@ func (s *Signer) verifyNonces(com *Commitment) error { ) } - if nonces.HidingNonceCommitment.Equal(com.HidingNonceCommitment) != 1 { + if !nonces.HidingNonceCommitment.Equal(com.HidingNonceCommitment) { return fmt.Errorf("invalid hiding nonce in commitment list for signer %d", s.KeyShare.ID) } - if nonces.BindingNonceCommitment.Equal(com.BindingNonceCommitment) != 1 { + if !nonces.BindingNonceCommitment.Equal(com.BindingNonceCommitment) { return fmt.Errorf("invalid binding nonce in commitment list for signer %d", s.KeyShare.ID) } diff --git a/tests/commitment_test.go b/tests/commitment_test.go index 0aabf48..32bb1a7 100644 --- a/tests/commitment_test.go +++ b/tests/commitment_test.go @@ -103,13 +103,19 @@ func TestCommitment_Validate_WrongGroup(t *testing.T) { } configuration, signers := fullSetup(t, tt) com := signers[0].Commit() - com.Group = 2 + commitment := &frost.Commitment{ + HidingNonceCommitment: com.HidingNonceCommitment, + BindingNonceCommitment: com.BindingNonceCommitment, + CommitmentID: com.CommitmentID, + SignerID: com.SignerID, + Group: 0, + } expectedErrorPrefix := fmt.Sprintf( - "commitment %d for participant 1 has an unexpected ciphersuite: expected ristretto255_XMD:SHA-512_R255MAP_RO_, got 2", + "commitment %d for participant 1 has an unexpected ciphersuite: expected ristretto255_XMD:SHA-512_R255MAP_RO_, got 0", com.CommitmentID, ) - if err := configuration.ValidateCommitment(com); err == nil || + if err := configuration.ValidateCommitment(commitment); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -144,7 +150,7 @@ func TestCommitment_Validate_BadHidingNonceCommitment(t *testing.T) { com.SignerID, ) - com.HidingNonceCommitment = tt.ECGroup().NewElement() + com.HidingNonceCommitment = tt.Group().NewElement() if err := configuration.ValidateCommitment(com); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -193,7 +199,7 @@ func TestCommitment_Validate_BadBindingNonceCommitment(t *testing.T) { com.SignerID, ) - com.BindingNonceCommitment = tt.ECGroup().NewElement() + com.BindingNonceCommitment = tt.Group().NewElement() if err := configuration.ValidateCommitment(com); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) diff --git a/tests/configuration_test.go b/tests/configuration_test.go index a78cc80..4492481 100644 --- a/tests/configuration_test.go +++ b/tests/configuration_test.go @@ -14,12 +14,11 @@ import ( "strings" "testing" - secretsharing "github.com/bytemare/secret-sharing" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/keys" ) func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { @@ -132,7 +131,7 @@ func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: test.ECGroup().NewElement(), + GroupPublicKey: test.Group().NewElement(), SignerPublicKeyShares: publicKeyShares, } @@ -153,7 +152,7 @@ func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: test.ECGroup().Base(), + GroupPublicKey: test.Group().Base(), SignerPublicKeyShares: publicKeyShares, } @@ -167,8 +166,8 @@ func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { expectedErrorPrefix := "invalid number of public keys (lower than threshold or above maximum)" ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -212,8 +211,8 @@ func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { expectedErrorPrefix := "empty public key share at index 1" ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -234,8 +233,8 @@ func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -264,7 +263,7 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { "invalid public key for participant %d, the key is the identity element", configuration.SignerPublicKeyShares[threshold-1].ID, ) - configuration.SignerPublicKeyShares[threshold-1].PublicKey = ciphersuite.ECGroup().NewElement() + configuration.SignerPublicKeyShares[threshold-1].PublicKey = ciphersuite.Group().NewElement() if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -275,7 +274,7 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { "invalid public key for participant %d, the key is the group generator (base element)", configuration.SignerPublicKeyShares[threshold-1].ID, ) - configuration.SignerPublicKeyShares[threshold-1].PublicKey = ciphersuite.ECGroup().Base() + configuration.SignerPublicKeyShares[threshold-1].PublicKey = ciphersuite.Group().Base() if err := configuration.Init(); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) @@ -286,8 +285,8 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_Identifiers(t *testing.T expectedErrorPrefix := "found duplicate identifier for signer 1" ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -313,8 +312,8 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) expectedErrorPrefix := "found duplicate public keys for signers 2 and 1" ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -399,7 +398,7 @@ func TestConfiguration_ValidatePublicKeyShare_ID0(t *testing.T) { configuration, _ := makeConfAndShares(t, tt) pks := &keys.PublicKeyShare{ - Group: tt.ECGroup(), + Group: tt.Group(), ID: 0, } @@ -418,7 +417,7 @@ func TestConfiguration_ValidatePublicKeyShare_InvalidID(t *testing.T) { configuration, _ := makeConfAndShares(t, tt) pks := &keys.PublicKeyShare{ - Group: tt.ECGroup(), + Group: tt.Group(), ID: tt.maxSigners + 1, } @@ -437,9 +436,9 @@ func TestConfiguration_ValidatePublicKeyShare_InvalidPublicKey(t *testing.T) { configuration, _ := makeConfAndShares(t, tt) pks := &keys.PublicKeyShare{ - Group: tt.ECGroup(), + Group: tt.Group(), ID: 1, - PublicKey: tt.ECGroup().Base(), + PublicKey: tt.Group().Base(), } if err := configuration.ValidatePublicKeyShare(pks); err == nil || err.Error() != expectedErrorPrefix { @@ -496,7 +495,7 @@ func TestConfiguration_ValidateKeyShare_InvalidGroupPublicKey(t *testing.T) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } - keyShare.GroupPublicKey = tt.ECGroup().NewElement() + keyShare.GroupPublicKey = tt.Group().NewElement() if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -538,7 +537,7 @@ func TestConfiguration_ValidateKeyShare_InvalidSecretKey(t *testing.T) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } - keyShare.Secret = tt.ECGroup().NewScalar() + keyShare.Secret = tt.Group().NewScalar() if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -554,8 +553,8 @@ func TestConfiguration_ValidateKeyShare_KeysNotMatching(t *testing.T) { configuration, keyShares := makeConfAndShares(t, tt) keyShare := keyShares[0] - random := tt.ECGroup().NewScalar().Random() - keyShare.PublicKey = tt.ECGroup().Base().Multiply(random) + random := tt.Group().NewScalar().Random() + keyShare.PublicKey = tt.Group().Base().Multiply(random) if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -591,14 +590,14 @@ func TestConfiguration_ValidateKeyShare_WrongPublicKey(t *testing.T) { } configuration, keyShares := makeConfAndShares(t, tt) - random := tt.ECGroup().NewScalar().Random() + random := tt.Group().NewScalar().Random() keyShare := &keys.KeyShare{ Secret: random, GroupPublicKey: keyShares[0].GroupPublicKey, - PublicKeyShare: secretsharing.PublicKeyShare{ - PublicKey: tt.ECGroup().Base().Multiply(random), + PublicKeyShare: keys.PublicKeyShare{ + PublicKey: tt.Group().Base().Multiply(random), ID: keyShares[0].ID, - Group: keyShares[0].Group, + Group: keyShares[0].Group(), }, } @@ -609,8 +608,8 @@ func TestConfiguration_ValidateKeyShare_WrongPublicKey(t *testing.T) { func TestConfiguration_Signer_NotVerified(t *testing.T) { ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -631,8 +630,8 @@ func TestConfiguration_Signer_NotVerified(t *testing.T) { func TestConfiguration_Signer_BadConfig(t *testing.T) { expectedErrorPrefix := internal.ErrInvalidCiphersuite ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -671,8 +670,8 @@ func TestConfiguration_VerifySignatureShare_BadPrep(t *testing.T) { expectedErrorPrefix := internal.ErrInvalidCiphersuite ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) @@ -902,9 +901,9 @@ func TestConfiguration_VerifySignatureShare_InvalidSignatureShare(t *testing.T) } sigShare := &frost.SignatureShare{ - SignatureShare: tt.Ciphersuite.ECGroup().NewScalar().Random(), + SignatureShare: tt.Ciphersuite.Group().NewScalar().Random(), SignerIdentifier: 1, - Group: tt.Ciphersuite.ECGroup(), + Group: tt.Ciphersuite.Group(), } if err := configuration.VerifySignatureShare(sigShare, message, coms); err == nil || diff --git a/tests/dkg_test.go b/tests/dkg_test.go index 78cc701..d73e86a 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -11,16 +11,15 @@ package frost_test import ( "testing" - group "github.com/bytemare/crypto" "github.com/bytemare/dkg" - - "github.com/bytemare/frost/keys" + "github.com/bytemare/ecc" + "github.com/bytemare/secret-sharing/keys" ) -func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, threshold uint64) []*dkg.Participant { +func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, threshold, maxSigners uint16) []*dkg.Participant { ps := make([]*dkg.Participant, 0, maxSigners) for i := range maxSigners { - p, err := ciphersuite.NewParticipant(i+1, uint(maxSigners), uint(threshold)) + p, err := ciphersuite.NewParticipant(i+1, threshold, maxSigners) if err != nil { t.Fatal(err) } @@ -33,15 +32,15 @@ func dkgMakeParticipants(t *testing.T, ciphersuite dkg.Ciphersuite, maxSigners, func runDKG( t *testing.T, - g group.Group, - threshold, maxSigners uint64, -) ([]*keys.KeyShare, *group.Element, []*group.Element) { + g ecc.Group, + threshold, maxSigners uint16, +) ([]*keys.KeyShare, *ecc.Element, []*ecc.Element) { c := dkg.Ciphersuite(g) // valid r1DataSet set with and without own package - participants := dkgMakeParticipants(t, c, maxSigners, threshold) + participants := dkgMakeParticipants(t, c, threshold, maxSigners) r1 := make([]*dkg.Round1Data, maxSigners) - commitments := make([][]*group.Element, maxSigners) + commitments := make([][]*ecc.Element, maxSigners) // Step 1: Start and assemble packages. for i := range maxSigners { @@ -55,7 +54,7 @@ func runDKG( } // Step 2: Continue and assemble + triage packages. - r2 := make(map[uint64][]*dkg.Round2Data, maxSigners) + r2 := make(map[uint16][]*dkg.Round2Data, maxSigners) for i := range maxSigners { r, err := participants[i].Continue(r1) if err != nil { @@ -85,11 +84,15 @@ func runDKG( t.Fatal() } - if keyShare.GroupPublicKey.Equal(pubKey) != 1 { - t.Fatalf("expected same public key") + //if !secretsharing.VerifyPublicKeyShare(keyShare.Public()) { + // t.Fatal("expected validity") + //} + + if !keyShare.GroupPublicKey.Equal(pubKey) { + t.Fatal("expected same public key") } - if keyShare.PublicKey.Equal(g.Base().Multiply(keyShare.SecretKey())) != 1 { + if !keyShare.PublicKey.Equal(g.Base().Multiply(keyShare.SecretKey())) { t.Fatal("expected equality") } @@ -97,7 +100,7 @@ func runDKG( t.Fatal(err) } - keyShares = append(keyShares, (*keys.KeyShare)(keyShare)) + keyShares = append(keyShares, keyShare) } return keyShares, pubKey, nil diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 3de573e..a005bce 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -12,17 +12,17 @@ import ( "bytes" "encoding/json" "errors" - "fmt" "slices" "strings" "testing" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" + debugec "github.com/bytemare/ecc/debug" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/keys" ) func makeConfAndShares(t *testing.T, test *tableTest) (*frost.Configuration, []*keys.KeyShare) { @@ -79,8 +79,18 @@ func makeSigners(t *testing.T, test *tableTest) []*frost.Signer { return s } -func compareConfigurations(t *testing.T, c1, c2 *frost.Configuration, expectedMatch bool) { - if c1 == nil || c2 == nil { +func compareConfigurations(t *testing.T, a, b serde, expectedMatch bool) { + c1, ok := a.(*frost.Configuration) + if !ok && expectedMatch { + t.Fatal("first argument is of wrong type") + } + + c2, ok := b.(*frost.Configuration) + if !ok && expectedMatch { + t.Fatal("second argument is of wrong type") + } + + if (c1 == nil || c2 == nil) && expectedMatch { t.Fatal("nil config") } @@ -96,12 +106,12 @@ func compareConfigurations(t *testing.T, c1, c2 *frost.Configuration, expectedMa t.Fatalf("expected matching max signers: %q / %q", c1.MaxSigners, c2.MaxSigners) } - if ((c1.GroupPublicKey == nil || c2.GroupPublicKey == nil) || (c1.GroupPublicKey.Equal(c2.GroupPublicKey) != 1)) && + if ((c1.GroupPublicKey == nil || c2.GroupPublicKey == nil) || !c1.GroupPublicKey.Equal(c2.GroupPublicKey)) && expectedMatch { t.Fatalf("expected matching GroupPublicKey: %q / %q", c1.Ciphersuite, c2.Ciphersuite) } - if len(c1.SignerPublicKeyShares) != len(c2.SignerPublicKeyShares) { + if len(c1.SignerPublicKeyShares) != len(c2.SignerPublicKeyShares) && expectedMatch { t.Fatalf( "expected matching SignerPublicKeyShares lengths: %q / %q", len(c1.SignerPublicKeyShares), @@ -111,124 +121,170 @@ func compareConfigurations(t *testing.T, c1, c2 *frost.Configuration, expectedMa for i, p1 := range c1.SignerPublicKeyShares { p2 := c2.SignerPublicKeyShares[i] - if err := comparePublicKeyShare(p1, p2); !expectedMatch && err != nil { - t.Fatal(err) - } + comparePublicKeyShare(t, p1, p2, expectedMatch) } } -func comparePublicKeyShare(p1, p2 *keys.PublicKeyShare) error { - if p1.PublicKey.Equal(p2.PublicKey) != 1 { - return fmt.Errorf("Expected equality on PublicKey:\n\t%s\n\t%s\n", p1.PublicKey.Hex(), p2.PublicKey.Hex()) +func comparePublicKeyShare(t *testing.T, a, b serde, expectedMatch bool) { + p1, ok := a.(*keys.PublicKeyShare) + if !ok && expectedMatch { + t.Fatal("first argument is of wrong type") } - if p1.ID != p2.ID { - return fmt.Errorf("Expected equality on ID:\n\t%d\n\t%d\n", p1.ID, p2.ID) + p2, ok := b.(*keys.PublicKeyShare) + if !ok && expectedMatch { + t.Fatal("second argument is of wrong type") } - if p1.Group != p2.Group { - return fmt.Errorf("Expected equality on Group:\n\t%v\n\t%v\n", p1.Group, p2.Group) + if !p1.PublicKey.Equal(p2.PublicKey) && expectedMatch { + t.Fatalf("Expected equality on PublicKey:\n\t%s\n\t%s\n", p1.PublicKey.Hex(), p2.PublicKey.Hex()) } - if len(p1.Commitment) != len(p2.Commitment) { - return fmt.Errorf( - "Expected equality on Commitment length:\n\t%d\n\t%d\n", - len(p1.Commitment), - len(p1.Commitment), - ) + if p1.ID != p2.ID && expectedMatch { + t.Fatalf("Expected equality on ID:\n\t%d\n\t%d\n", p1.ID, p2.ID) + } + + if p1.Group != p2.Group && expectedMatch { + t.Fatalf("Expected equality on Group:\n\t%v\n\t%v\n", p1.Group, p2.Group) } - for i := range p1.Commitment { - if p1.Commitment[i].Equal(p2.Commitment[i]) != 1 { - return fmt.Errorf( - "Expected equality on Commitment %d:\n\t%s\n\t%s\n", - i, - p1.Commitment[i].Hex(), - p1.Commitment[i].Hex(), + lenP1Com := len(p1.VssCommitment) + lenP2Com := len(p2.VssCommitment) + + if lenP1Com != 0 && lenP2Com != 0 { + if lenP1Com != lenP2Com && expectedMatch { + t.Fatalf( + "Expected equality on Commitment length:\n\t%d\n\t%d\n", + len(p1.VssCommitment), + len(p2.VssCommitment), ) } - } - return nil + for i := range p1.VssCommitment { + if !p1.VssCommitment[i].Equal(p2.VssCommitment[i]) && expectedMatch { + t.Fatalf( + "Expected equality on Commitment %d:\n\t%s\n\t%s\n", + i, + p1.VssCommitment[i].Hex(), + p2.VssCommitment[i].Hex(), + ) + } + } + } } -func compareKeyShares(s1, s2 *keys.KeyShare) error { - if s1.Secret.Equal(s2.Secret) != 1 { - return fmt.Errorf("Expected equality on Secret:\n\t%s\n\t%s\n", s1.Secret.Hex(), s2.Secret.Hex()) +func compareKeyShares(t *testing.T, a, b serde, expectedMatch bool) { + s1, ok := a.(*keys.KeyShare) + if !ok && expectedMatch { + t.Fatal("first argument is of wrong type") + } + + s2, ok := b.(*keys.KeyShare) + if !ok && expectedMatch { + t.Fatal("second argument is of wrong type") } - if s1.GroupPublicKey.Equal(s2.GroupPublicKey) != 1 { - return fmt.Errorf( + if !s1.Secret.Equal(s2.Secret) && expectedMatch { + t.Fatalf("Expected equality on Secret:\n\t%s\n\t%s\n", s1.Secret.Hex(), s2.Secret.Hex()) + } + + if !s1.GroupPublicKey.Equal(s2.GroupPublicKey) && expectedMatch { + t.Fatalf( "Expected equality on GroupPublicKey:\n\t%s\n\t%s\n", s1.GroupPublicKey.Hex(), s2.GroupPublicKey.Hex(), ) } - return comparePublicKeyShare(s1.Public(), s2.Public()) + comparePublicKeyShare(t, s1.Public(), s2.Public(), expectedMatch) } -func compareCommitments(c1, c2 *frost.Commitment) error { - if c1.Group != c2.Group { - return errors.New("different groups") +func compareCommitments(t *testing.T, a, b serde, expectedMatch bool) { + c1, ok := a.(*frost.Commitment) + if !ok && expectedMatch { + t.Fatal("first argument is of wrong type") } - if c1.SignerID != c2.SignerID { - return errors.New("different SignerID") + c2, ok := b.(*frost.Commitment) + if !ok && expectedMatch { + t.Fatal("second argument is of wrong type") } - if c1.CommitmentID != c2.CommitmentID { - return errors.New("different CommitmentID") + if c1.SignerID != c2.SignerID && expectedMatch { + t.Fatal("different SignerID") } - if c1.HidingNonceCommitment.Equal(c2.HidingNonceCommitment) != 1 { - return errors.New("different HidingNonceCommitment") + if c1.CommitmentID != c2.CommitmentID && expectedMatch { + t.Fatal("different CommitmentID") } - if c1.BindingNonceCommitment.Equal(c2.BindingNonceCommitment) != 1 { - return errors.New("different BindingNonceCommitment") + if !c1.HidingNonceCommitment.Equal(c2.HidingNonceCommitment) && expectedMatch { + t.Fatal("different HidingNonceCommitment") } - return nil + if !c1.BindingNonceCommitment.Equal(c2.BindingNonceCommitment) && expectedMatch { + t.Fatal("different BindingNonceCommitment") + } } -func compareNonceCommitments(c1, c2 *frost.Nonce) error { - if c1.HidingNonce.Equal(c2.HidingNonce) != 1 { - return errors.New("different HidingNonce") +func compareNonceCommitments(t *testing.T, a, b serde, expectedMatch bool) { + c1, ok := a.(*frost.Nonce) + if !ok && expectedMatch { + t.Fatal("first argument is of wrong type") + } + + c2, ok := b.(*frost.Nonce) + if !ok && expectedMatch { + t.Fatal("second argument is of wrong type") + } + + if !c1.HidingNonce.Equal(c2.HidingNonce) && expectedMatch { + t.Fatalf("different HidingNonce:\n\t%s\n\t%s\n", c1.HidingNonce.Hex(), c2.HidingNonce.Hex()) } - if c1.BindingNonce.Equal(c2.BindingNonce) != 1 { - return errors.New("different BindingNonce") + if !c1.BindingNonce.Equal(c2.BindingNonce) && expectedMatch { + t.Fatalf("different BindingNonce:\n\t%s\n\t%s\n", c1.BindingNonce.Hex(), c2.BindingNonce.Hex()) } - return compareCommitments(c1.Commitment, c2.Commitment) + compareCommitments(t, c1.Commitment, c2.Commitment, expectedMatch) } -func compareLambdaRegistries(t *testing.T, m1, m2 map[string]*group.Scalar) { - if len(m1) != len(m2) { +func compareLambdaRegistries(t *testing.T, m1, m2 map[string]*internal.Lambda, expectedMatch bool) { + if len(m1) != len(m2) && expectedMatch { t.Fatalf("unequal lengths: %d / %d", len(m1), len(m2)) } for k1, v1 := range m1 { v2, exists := m2[k1] - if !exists { + if !exists && expectedMatch { t.Fatalf("key %s is not present in second map", k1) } - if v1.Equal(v2) != 1 { + if v1.Group != v2.Group && expectedMatch { + t.Fatalf("unequal lambdas for the participant list %s", k1) + } + + if !v1.Value.Equal(v2.Value) && expectedMatch { t.Fatalf("unequal lambdas for the participant list %s", k1) } } } -func compareSigners(t *testing.T, s1, s2 *frost.Signer) { - if err := compareKeyShares(s1.KeyShare, s2.KeyShare); err != nil { - t.Fatal(err) +func compareSigners(t *testing.T, a, b serde, expectedMatch bool) { + s1, ok := a.(*frost.Signer) + if !ok && expectedMatch { + t.Fatal("first argument is of wrong type") } - compareLambdaRegistries(t, s1.LambdaRegistry, s2.LambdaRegistry) + s2, ok := b.(*frost.Signer) + if !ok && expectedMatch { + t.Fatal("second argument is of wrong type") + } + + compareKeyShares(t, s1.KeyShare, s2.KeyShare, expectedMatch) + compareLambdaRegistries(t, s1.LambdaRegistry, s2.LambdaRegistry, expectedMatch) - if len(s1.NonceCommitments) != len(s2.NonceCommitments) { + if len(s1.NonceCommitments) != len(s2.NonceCommitments) && expectedMatch { t.Fatal("expected equality") } @@ -236,25 +292,33 @@ func compareSigners(t *testing.T, s1, s2 *frost.Signer) { if com2, exists := s2.NonceCommitments[id]; !exists { t.Fatalf("com id %d does not exist in s2", id) } else { - if err := compareNonceCommitments(com, com2); err != nil { - t.Fatal(err) - } + compareNonceCommitments(t, com, com2, expectedMatch) } } - if bytes.Compare(s1.HidingRandom, s2.HidingRandom) != 0 { + if bytes.Compare(s1.HidingRandom, s2.HidingRandom) != 0 && expectedMatch { t.Fatal("expected equality") } - if bytes.Compare(s1.BindingRandom, s2.BindingRandom) != 0 { + if bytes.Compare(s1.BindingRandom, s2.BindingRandom) != 0 && expectedMatch { t.Fatal("expected equality") } - compareConfigurations(t, s1.Configuration, s2.Configuration, true) + compareConfigurations(t, s1.Configuration, s2.Configuration, expectedMatch) } -func compareSignatureShares(t *testing.T, s1, s2 *frost.SignatureShare) { - if s1.Group != s2.Group { +func compareSignatureShares(t *testing.T, a, b serde, expectedMatch bool) { + s1, ok := a.(*frost.SignatureShare) + if !ok && expectedMatch { + t.Fatal("first argument is of wrong type") + } + + s2, ok := b.(*frost.SignatureShare) + if !ok && expectedMatch { + t.Fatal("second argument is of wrong type") + } + + if s1.Group != s2.Group && expectedMatch { t.Fatal("unexpected group") } @@ -262,34 +326,38 @@ func compareSignatureShares(t *testing.T, s1, s2 *frost.SignatureShare) { t.Fatal("expected equality") } - if s1.SignatureShare.Equal(s2.SignatureShare) != 1 { + if !s1.SignatureShare.Equal(s2.SignatureShare) { t.Fatal("expected equality") } } -func compareSignatures(s1, s2 *frost.Signature, expectEqual bool) error { - if s1.R.Equal(s2.R) == 1 != expectEqual { - return fmt.Errorf("expected %v R", expectEqual) +func compareSignatures(t *testing.T, a, b serde, expectedMatch bool) { + s1, ok := a.(*frost.Signature) + if !ok && expectedMatch { + t.Fatal("first argument is of wrong type") } - if s1.Z.Equal(s2.Z) == 1 != expectEqual { - return fmt.Errorf("expected %v Z", expectEqual) + s2, ok := b.(*frost.Signature) + if !ok && expectedMatch { + t.Fatal("second argument is of wrong type") } - return nil + if !s1.R.Equal(s2.R) && expectedMatch { + t.Fatalf("expected %v R", expectedMatch) + } + + if !s1.Z.Equal(s2.Z) && expectedMatch { + t.Fatalf("expected %v Z", expectedMatch) + } } func TestEncoding_Configuration(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { configuration := makeConf(t, test) - encoded := configuration.Encode() - decoded := new(frost.Configuration) - if err := decoded.Decode(encoded); err != nil { - t.Fatal(err) - } - - compareConfigurations(t, configuration, decoded, true) + testAndCompareSerde(t, configuration, true, compareConfigurations, func() serde { + return new(frost.Configuration) + }) }) } @@ -308,7 +376,7 @@ func TestEncoding_Configuration_InvalidHeaderLength(t *testing.T) { } func TestEncoding_Configuration_InvalidCiphersuite(t *testing.T) { - expectedError := internal.ErrInvalidCiphersuite + expectedError := "failed to decode Configuration: " + internal.ErrInvalidCiphersuite.Error() testAll(t, func(t *testing.T, test *tableTest) { configuration := makeConf(t, test) @@ -316,7 +384,7 @@ func TestEncoding_Configuration_InvalidCiphersuite(t *testing.T) { encoded[0] = 2 decoded := new(frost.Configuration) - if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError.Error() { + if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError { t.Fatalf("expected %q, got %q", expectedError, err) } }) @@ -342,7 +410,7 @@ func TestEncoding_Configuration_InvalidLength(t *testing.T) { } func TestEncoding_Configuration_InvalidConfigEncoding(t *testing.T) { - expectedErrorPrefix := "the threshold in the encoded configuration is higher than the number of maximum participants" + expectedErrorPrefix := "failed to decode Configuration: the threshold in the encoded configuration is higher than the number of maximum participants" tt := &tableTest{ Ciphersuite: frost.Ristretto255, threshold: 2, @@ -359,14 +427,14 @@ func TestEncoding_Configuration_InvalidConfigEncoding(t *testing.T) { } func TestEncoding_Configuration_InvalidGroupPublicKey(t *testing.T) { - expectedErrorPrefix := "could not decode group public key: element Decode: " + expectedErrorPrefix := "failed to decode Configuration: could not decode group public key: element Decode: " testAll(t, func(t *testing.T, test *tableTest) { configuration := makeConf(t, test) - g := group.Group(test.Ciphersuite) + g := ecc.Group(test.Ciphersuite) encoded := configuration.Encode() - bad := badElement(t, g) - encoded = slices.Replace(encoded, 25, 25+g.ElementLength(), bad...) + bad := debugec.BadElementOffCurve(g) + encoded = slices.Replace(encoded, 7, 7+g.ElementLength(), bad...) decoded := new(frost.Configuration) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -376,7 +444,7 @@ func TestEncoding_Configuration_InvalidGroupPublicKey(t *testing.T) { } func TestEncoding_Configuration_BadPublicKeyShare(t *testing.T) { - expectedErrorPrefix := "could not decode signer public key share for signer 1: " + expectedErrorPrefix := "failed to decode Configuration: could not decode signer public key share for signer 1: " testAll(t, func(t *testing.T, test *tableTest) { keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( @@ -394,10 +462,10 @@ func TestEncoding_Configuration_BadPublicKeyShare(t *testing.T) { GroupPublicKey: groupPublicKey, SignerPublicKeyShares: publicKeyShares, } - g := group.Group(test.Ciphersuite) + g := ecc.Group(test.Ciphersuite) pksSize := len(publicKeyShares[0].Encode()) - bad := badElement(t, g) - offset := 25 + g.ElementLength() + pksSize + 1 + 8 + 4 + bad := debugec.BadElementOffCurve(g) + offset := 7 + g.ElementLength() + pksSize + 1 + 2 + 4 encoded := configuration.Encode() encoded = slices.Replace(encoded, offset, offset+g.ElementLength(), bad...) @@ -409,7 +477,7 @@ func TestEncoding_Configuration_BadPublicKeyShare(t *testing.T) { } func TestEncoding_Configuration_InvalidPublicKeyShares(t *testing.T) { - expectedErrorPrefix := "invalid number of public keys (lower than threshold or above maximum)" + expectedErrorPrefix := "failed to decode Configuration: invalid number of public keys (lower than threshold or above maximum)" testAll(t, func(t *testing.T, test *tableTest) { keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( @@ -438,7 +506,7 @@ func TestEncoding_Configuration_InvalidPublicKeyShares(t *testing.T) { } func TestEncoding_Configuration_CantVerify_InvalidPubKey(t *testing.T) { - expectedErrorPrefix := "invalid group public key, the key is the group generator (base element)" + expectedErrorPrefix := "failed to decode Configuration: invalid group public key, the key is the group generator (base element)" testAll(t, func(t *testing.T, test *tableTest) { configuration := makeConf(t, test) @@ -458,47 +526,42 @@ func TestEncoding_Signer(t *testing.T) { s.Commit() s.Commit() - participants := make([]uint64, test.maxSigners) + participants := make([]uint16, test.maxSigners) for i := range test.maxSigners { participants[i] = i + 1 } - s.LambdaRegistry.New(test.ECGroup(), s.Identifier(), participants) - s.LambdaRegistry.New(test.ECGroup(), s.Identifier(), participants[1:]) - - encoded := s.Encode() + s.LambdaRegistry.New(test.Group(), s.Identifier(), participants) + // s.LambdaRegistry.New(test.Group(), s.Identifier(), participants[1:]) - decoded := new(frost.Signer) - if err := decoded.Decode(encoded); err != nil { - t.Fatal(err) - } - - compareSigners(t, s, decoded) + testAndCompareSerde(t, s, true, compareSigners, func() serde { + return new(frost.Signer) + }) }) } func TestEncoding_Signer_BadConfHeader(t *testing.T) { - expectedErr := internal.ErrInvalidLength + expectedErr := "failed to decode Signer: " + internal.ErrInvalidLength.Error() testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] encoded := s.Encode() decoded := new(frost.Signer) - if err := decoded.Decode(encoded[:20]); err == nil || err.Error() != expectedErr.Error() { + if err := decoded.Decode(encoded[:20]); err == nil || err.Error() != expectedErr { t.Fatalf("expected error %q, got %q", expectedErr, err) } }) } func TestEncoding_Signer_BadConf(t *testing.T) { - expectedErrorPrefix := "could not decode group public key:" + expectedErrorPrefix := "failed to decode Signer: failed to decode Configuration: could not decode group public key:" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] - eLen := s.Configuration.Ciphersuite.ECGroup().ElementLength() + eLen := s.Configuration.Ciphersuite.Group().ElementLength() encoded := s.Encode() - encoded = slices.Replace(encoded, 25, 25+eLen, badElement(t, test.ECGroup())...) + encoded = slices.Replace(encoded, 7, 7+eLen, debugec.BadElementOffCurve(test.Group())...) decoded := new(frost.Signer) if err := decoded.Decode(encoded); err == nil || @@ -509,47 +572,47 @@ func TestEncoding_Signer_BadConf(t *testing.T) { } func TestEncoding_Signer_InvalidLength1(t *testing.T) { - expectedErr := internal.ErrInvalidLength + expectedErr := "failed to decode Signer: " + internal.ErrInvalidLength.Error() testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] encoded := s.Encode() - eLen := s.Configuration.Ciphersuite.ECGroup().ElementLength() + eLen := s.Configuration.Ciphersuite.Group().ElementLength() pksLen := 1 + 8 + 4 + eLen + int(test.threshold)*eLen confLen := 1 + 3*8 + eLen + int(test.maxSigners)*pksLen decoded := new(frost.Signer) - if err := decoded.Decode(encoded[:confLen+2]); err == nil || err.Error() != expectedErr.Error() { + if err := decoded.Decode(encoded[:confLen+2]); err == nil || err.Error() != expectedErr { t.Fatalf("expected error %q, got %q", expectedErr, err) } }) } func TestEncoding_Signer_InvalidLength2(t *testing.T) { - expectedErr := internal.ErrInvalidLength + expectedErr := "failed to decode Signer: " + internal.ErrInvalidLength.Error() testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] encoded := s.Encode() decoded := new(frost.Signer) - if err := decoded.Decode(encoded[:len(encoded)-1]); err == nil || err.Error() != expectedErr.Error() { + if err := decoded.Decode(encoded[:len(encoded)-1]); err == nil || err.Error() != expectedErr { t.Fatalf("expected error %q, got %q", expectedErr, err) } - if err := decoded.Decode(append(encoded, []byte{0}...)); err == nil || err.Error() != expectedErr.Error() { + if err := decoded.Decode(append(encoded, []byte{0}...)); err == nil || err.Error() != expectedErr { t.Fatalf("expected error %q, got %q", expectedErr, err) } }) } func TestEncoding_Signer_InvalidLambda(t *testing.T) { - expectedErrorPrefix := "failed to decode lambda registry in signer:" + expectedErrorPrefix := "failed to decode Signer: failed to decode lambda registry in signer:" testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) - g := group.Group(test.Ciphersuite) + g := ecc.Group(test.Ciphersuite) message := []byte("message") coms := make(frost.CommitmentList, len(signers)) @@ -567,7 +630,7 @@ func TestEncoding_Signer_InvalidLambda(t *testing.T) { confLen := len(s.Configuration.Encode()) ksLen := len(s.KeyShare.Encode()) encoded := s.Encode() - bad := badScalar(t, g) + bad := debugec.BadScalarHigh(g) offset := confLen + 6 + ksLen + 32 encoded = slices.Replace(encoded, offset, offset+g.ScalarLength(), bad...) @@ -579,7 +642,7 @@ func TestEncoding_Signer_InvalidLambda(t *testing.T) { } func TestEncoding_Signer_BadKeyShare(t *testing.T) { - expectedErrorPrefix := "failed to decode key share: invalid group identifier" + expectedErrorPrefix := "failed to decode Signer: failed to decode KeyShare: invalid group identifier" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] @@ -598,7 +661,7 @@ func TestEncoding_Signer_BadKeyShare(t *testing.T) { } func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { - expectedErrorPrefix := "invalid key share: invalid identifier for public key share, the identifier is 0" + expectedErrorPrefix := "failed to decode Signer: invalid key share: invalid identifier for public key share, the identifier is 0" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] @@ -607,8 +670,8 @@ func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { // Set an invalid identifier. encoded := s.Encode() - badID := [8]byte{} - encoded = slices.Replace(encoded, offset, offset+8, badID[:]...) + badID := [2]byte{} + encoded = slices.Replace(encoded, offset, offset+2, badID[:]...) decoded := new(frost.Signer) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -618,18 +681,18 @@ func TestEncoding_Signer_InvalidKeyShare(t *testing.T) { } func TestEncoding_Signer_InvalidCommitmentNonces_DuplicateID(t *testing.T) { - expectedErrorPrefix := "multiple encoded commitments with the same id:" + expectedErrorPrefix := "failed to decode Signer: multiple encoded commitments with the same id:" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] s.Commit() s.Commit() s.Commit() - g := group.Group(test.Ciphersuite) + g := ecc.Group(test.Ciphersuite) sLen := g.ScalarLength() confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) - commitmentLength := 1 + 8 + 8 + 2*uint64(g.ElementLength()) + commitmentLength := 1 + 8 + 2 + 2*uint64(g.ElementLength()) nonceCommitmentLength := 8 + 2*sLen + int(commitmentLength) offset := confLen + 6 + keyShareLen offset2 := offset + nonceCommitmentLength @@ -645,18 +708,18 @@ func TestEncoding_Signer_InvalidCommitmentNonces_DuplicateID(t *testing.T) { } func TestEncoding_Signer_InvalidHidingNonceCommitment(t *testing.T) { - expectedErrorPrefix := "can't decode hiding nonce for commitment" + expectedErrorPrefix := "failed to decode Signer: can't decode hiding nonce for commitment" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] s.Commit() - g := group.Group(test.Ciphersuite) + g := ecc.Group(test.Ciphersuite) confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) offset := confLen + 6 + keyShareLen + 8 encoded := s.Encode() - data := slices.Replace(encoded, offset, offset+g.ScalarLength(), badScalar(t, g)...) + data := slices.Replace(encoded, offset, offset+g.ScalarLength(), debugec.BadScalarHigh(g)...) decoded := new(frost.Signer) if err := decoded.Decode(data); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -666,18 +729,18 @@ func TestEncoding_Signer_InvalidHidingNonceCommitment(t *testing.T) { } func TestEncoding_Signer_InvalidBindingNonceCommitment(t *testing.T) { - expectedErrorPrefix := "can't decode binding nonce for commitment" + expectedErrorPrefix := "failed to decode Signer: can't decode binding nonce for commitment" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] s.Commit() - g := group.Group(test.Ciphersuite) + g := ecc.Group(test.Ciphersuite) confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) offset := confLen + 6 + keyShareLen + 8 + g.ScalarLength() encoded := s.Encode() - data := slices.Replace(encoded, offset, offset+g.ScalarLength(), badScalar(t, g)...) + data := slices.Replace(encoded, offset, offset+g.ScalarLength(), debugec.BadScalarHigh(g)...) decoded := new(frost.Signer) if err := decoded.Decode(data); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -687,12 +750,12 @@ func TestEncoding_Signer_InvalidBindingNonceCommitment(t *testing.T) { } func TestEncoding_Signer_InvalidCommitment(t *testing.T) { - expectedErrorPrefix := "can't decode nonce commitment" + expectedErrorPrefix := "failed to decode Signer: can't decode nonce commitment" testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] s.Commit() - g := group.Group(test.Ciphersuite) + g := ecc.Group(test.Ciphersuite) sLen := g.ScalarLength() confLen := len(s.Configuration.Encode()) keyShareLen := len(s.KeyShare.Encode()) @@ -724,51 +787,46 @@ func TestEncoding_SignatureShare(t *testing.T) { t.Fatal(err) } - encoded := sigShare.Encode() - - decoded := new(frost.SignatureShare) - if err = decoded.Decode(encoded); err != nil { - t.Fatalf("unexpected error %q", err) - } - - compareSignatureShares(t, sigShare, decoded) + testAndCompareSerde(t, sigShare, true, compareSignatureShares, func() serde { + return new(frost.SignatureShare) + }) } }) } func TestEncoding_SignatureShare_InvalidCiphersuite(t *testing.T) { - expectedError := internal.ErrInvalidCiphersuite + expectedError := "failed to decode SignatureShare: " + internal.ErrInvalidCiphersuite.Error() encoded := make([]byte, 3) decoded := new(frost.SignatureShare) - if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError.Error() { + if err := decoded.Decode(encoded); err == nil || err.Error() != expectedError { t.Fatalf("expected %q, got %q", expectedError, err) } } func TestEncoding_SignatureShare_InvalidLength1(t *testing.T) { - expectedError := internal.ErrInvalidLength + expectedError := "failed to decode SignatureShare: " + internal.ErrInvalidLength.Error() decoded := new(frost.SignatureShare) - if err := decoded.Decode([]byte{}); err == nil || err.Error() != expectedError.Error() { + if err := decoded.Decode([]byte{}); err == nil || err.Error() != expectedError { t.Fatalf("expected %q, got %q", expectedError, err) } } func TestEncoding_SignatureShare_InvalidLength2(t *testing.T) { - expectedError := internal.ErrInvalidLength + expectedError := "failed to decode SignatureShare: " + internal.ErrInvalidLength.Error() decoded := new(frost.SignatureShare) - if err := decoded.Decode([]byte{1, 0, 0}); err == nil || err.Error() != expectedError.Error() { + if err := decoded.Decode([]byte{1, 0, 0}); err == nil || err.Error() != expectedError { t.Fatalf("expected %q, got %q", expectedError, err) } } func TestEncoding_SignatureShare_InvalidIdentifier(t *testing.T) { // todo: check for zero id in all decodings - expectedError := errors.New("identifier cannot be 0") - encoded := make([]byte, 41) + expectedError := errors.New("failed to decode SignatureShare: identifier cannot be 0") + encoded := make([]byte, 35) encoded[0] = 1 decoded := new(frost.SignatureShare) @@ -778,7 +836,7 @@ func TestEncoding_SignatureShare_InvalidIdentifier(t *testing.T) { } func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { - expectedErrorPrefix := "failed to decode signature share: " + expectedErrorPrefix := "failed to decode SignatureShare: scalar Decode: invalid scalar encoding" message := []byte("message") testAll(t, func(t *testing.T, test *tableTest) { @@ -796,7 +854,7 @@ func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { } encoded := sigShare.Encode() - slices.Replace(encoded, 9, 9+test.ECGroup().ScalarLength(), badScalar(t, test.ECGroup())...) + slices.Replace(encoded, 3, 3+test.Group().ScalarLength(), debugec.BadScalarHigh(test.Group())...) decoded := new(frost.SignatureShare) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -809,59 +867,59 @@ func TestEncoding_Signature(t *testing.T) { message := []byte("message") testAll(t, func(t *testing.T, test *tableTest) { - key := test.ECGroup().NewScalar().Random() + key := test.Group().NewScalar().Random() signature, err := debug.Sign(test.Ciphersuite, message, key) if err != nil { t.Fatal(err) } - encoded := signature.Encode() - - decoded := new(frost.Signature) - if err = decoded.Decode(test.Ciphersuite, encoded); err != nil { - t.Fatal(err) - } - - if err = compareSignatures(signature, decoded, true); err != nil { - t.Fatal(err) - } + testAndCompareSerde(t, signature, true, compareSignatures, func() serde { + return new(frost.Signature) + }) }) } func TestEncoding_Signature_InvalidCiphersuite(t *testing.T) { + expectedError := "failed to decode Signature: " + internal.ErrInvalidCiphersuite.Error() decoded := new(frost.Signature) - if err := decoded.Decode(0, nil); err == nil || err.Error() != internal.ErrInvalidCiphersuite.Error() { - t.Fatalf("expected %q, got %q", internal.ErrInvalidCiphersuite, err) + + if err := decoded.Decode([]byte{2, 0}); err == nil || err.Error() != expectedError { + t.Fatalf("expected %q, got %q", expectedError, err) } } func TestEncoding_Signature_InvalidLength(t *testing.T) { + expectedError := "failed to decode Signature: " + internal.ErrInvalidLength.Error() decoded := new(frost.Signature) - if err := decoded.Decode(1, make([]byte, 63)); err == nil || err.Error() != internal.ErrInvalidLength.Error() { - t.Fatalf("expected %q, got %q", internal.ErrInvalidLength, err) + b := make([]byte, 63) + b[0] = 1 + if err := decoded.Decode(b); err == nil || err.Error() != expectedError { + t.Fatalf("expected %q, got %q", expectedError, err) } } func TestEncoding_Signature_InvalidR(t *testing.T) { - expectedErrorPrefix := "invalid signature - decoding R:" + expectedErrorPrefix := "failed to decode Signature: invalid encoding of R proof: element Decode: " message := []byte("message") testAll(t, func(t *testing.T, test *tableTest) { - key := test.ECGroup().NewScalar().Random() + key := test.Group().NewScalar().Random() signature, err := debug.Sign(test.Ciphersuite, message, key) if err != nil { t.Fatal(err) } encoded := signature.Encode() + + bad := debugec.BadElementOffCurve(test.Ciphersuite.Group()) slices.Replace( encoded, - 0, - test.Ciphersuite.ECGroup().ElementLength(), - badElement(t, test.Ciphersuite.ECGroup())...) + 1, + 1+test.Ciphersuite.Group().ElementLength(), + bad...) decoded := new(frost.Signature) - if err := decoded.Decode(test.Ciphersuite, encoded); err == nil || + if err = decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -869,24 +927,24 @@ func TestEncoding_Signature_InvalidR(t *testing.T) { } func TestEncoding_Signature_InvalidZ(t *testing.T) { - expectedErrorPrefix := "invalid signature - decoding Z:" + expectedErrorPrefix := "failed to decode Signature: invalid encoding of z proof: scalar Decode: " message := []byte("message") testAll(t, func(t *testing.T, test *tableTest) { - key := test.ECGroup().NewScalar().Random() + key := test.Group().NewScalar().Random() signature, err := debug.Sign(test.Ciphersuite, message, key) if err != nil { t.Fatal(err) } encoded := signature.Encode() - g := test.Ciphersuite.ECGroup() + g := test.Ciphersuite.Group() eLen := g.ElementLength() sLen := g.ScalarLength() - slices.Replace(encoded, eLen, eLen+sLen, badScalar(t, g)...) + slices.Replace(encoded, 1+eLen, 1+eLen+sLen, debugec.BadScalarHigh(g)...) decoded := new(frost.Signature) - if err := decoded.Decode(test.Ciphersuite, encoded); err == nil || + if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -897,21 +955,15 @@ func TestEncoding_Commitment(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] com := signer.Commit() - encoded := com.Encode() - decoded := new(frost.Commitment) - if err := decoded.Decode(encoded); err != nil { - t.Fatal(err) - } - - if err := compareCommitments(com, decoded); err != nil { - t.Fatal(err) - } + testAndCompareSerde(t, com, true, compareCommitments, func() serde { + return new(frost.Commitment) + }) }) } func TestEncoding_Commitment_BadCiphersuite(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite.Error() + expectedErrorPrefix := "failed to decode Commitment: " + internal.ErrInvalidCiphersuite.Error() testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] @@ -927,7 +979,7 @@ func TestEncoding_Commitment_BadCiphersuite(t *testing.T) { } func TestEncoding_Commitment_InvalidLength1(t *testing.T) { - expectedErrorPrefix := "failed to decode commitment: invalid length" + expectedErrorPrefix := "failed to decode Commitment: " + internal.ErrInvalidLength.Error() testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] @@ -942,7 +994,7 @@ func TestEncoding_Commitment_InvalidLength1(t *testing.T) { } func TestEncoding_Commitment_InvalidLength2(t *testing.T) { - expectedErrorPrefix := "failed to decode commitment: invalid length" + expectedErrorPrefix := "failed to decode Commitment: " + internal.ErrInvalidLength.Error() testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] @@ -957,7 +1009,7 @@ func TestEncoding_Commitment_InvalidLength2(t *testing.T) { } func TestEncoding_Commitment_InvalidIdentifier(t *testing.T) { - expectedErrorPrefix := "identifier cannot be 0" + expectedErrorPrefix := "failed to decode Commitment: identifier cannot be 0" testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] @@ -973,14 +1025,14 @@ func TestEncoding_Commitment_InvalidIdentifier(t *testing.T) { } func TestEncoding_Commitment_InvalidHidingNonce(t *testing.T) { - expectedErrorPrefix := "invalid encoding of hiding nonce commitment: " + expectedErrorPrefix := "failed to decode Commitment: invalid encoding of hiding nonce commitment: " testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] com := signer.Commit() encoded := com.Encode() - bad := badElement(t, test.ECGroup()) - slices.Replace(encoded, 17, 17+test.ECGroup().ElementLength(), bad...) + bad := debugec.BadElementOffCurve(test.Group()) + slices.Replace(encoded, 11, 11+test.Group().ElementLength(), bad...) decoded := new(frost.Commitment) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -990,15 +1042,15 @@ func TestEncoding_Commitment_InvalidHidingNonce(t *testing.T) { } func TestEncoding_Commitment_InvalidBindingNonce(t *testing.T) { - expectedErrorPrefix := "invalid encoding of binding nonce commitment: " + expectedErrorPrefix := "failed to decode Commitment: invalid encoding of binding nonce commitment: " testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] com := signer.Commit() encoded := com.Encode() - g := test.ECGroup() - bad := badElement(t, g) - slices.Replace(encoded, 17+g.ElementLength(), 17+2*g.ElementLength(), bad...) + g := test.Group() + bad := debugec.BadElementOffCurve(g) + slices.Replace(encoded, 11+g.ElementLength(), 11+2*g.ElementLength(), bad...) decoded := new(frost.Commitment) if err := decoded.Decode(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -1027,9 +1079,7 @@ func TestEncoding_CommitmentList(t *testing.T) { } for i, com := range coms { - if err = compareCommitments(com, list[i]); err != nil { - t.Fatal(err) - } + compareCommitments(t, com, list[i], true) } }) } @@ -1100,7 +1150,7 @@ func TestEncoding_CommitmentList_InvalidLength2(t *testing.T) { } func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { - expectedErrorPrefix := "invalid encoding of commitment: " + internal.ErrInvalidCiphersuite.Error() + expectedErrorPrefix := "invalid encoding of commitment: failed to decode Commitment: " + internal.ErrInvalidCiphersuite.Error() testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) @@ -1110,7 +1160,7 @@ func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { } encoded := coms.Encode() - encoded[9] = 0 + encoded[3] = 0 if _, err := frost.DecodeList(encoded); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { @@ -1119,98 +1169,78 @@ func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { }) } -func TestEncoding_KeyShare_Bytes(t *testing.T) { - testAll(t, func(t *testing.T, test *tableTest) { - s := makeSigners(t, test)[0] - keyShare := s.KeyShare - - encoded := keyShare.Encode() +/* - decoded := new(keys.KeyShare) - if err := decoded.Decode(encoded); err != nil { - t.Fatal(err) - } + */ - if err := compareKeyShares(keyShare, decoded); err != nil { - t.Fatal(err) - } - }) +type serde interface { + Encode() []byte + Decode([]byte) error + Hex() string + DecodeHex(string) error + json.Unmarshaler } -func TestEncoding_KeyShare_JSON(t *testing.T) { - testAll(t, func(t *testing.T, test *tableTest) { - s := makeSigners(t, test)[0] - keyShare := s.KeyShare +type tester func(t *testing.T, in, out serde) error - encoded, err := json.Marshal(keyShare) - if err != nil { - t.Fatal(err) - } - - decoded := new(keys.KeyShare) - if err := json.Unmarshal(encoded, decoded); err != nil { - t.Fatal(err) - } +func testByteEncoding(t *testing.T, in, out serde) error { + bEnc := in.Encode() - if err := compareKeyShares(keyShare, decoded); err != nil { - t.Fatal(err) - } - - // expect error - decoded = new(keys.KeyShare) - expectedError := errors.New("invalid group identifier") - encoded = replaceStringInBytes(encoded, fmt.Sprintf("\"group\":%d", test.ECGroup()), "\"group\":70") + if err := out.Decode(bEnc); err != nil { + return err + } - if err := json.Unmarshal(encoded, decoded); err == nil || err.Error() != expectedError.Error() { - t.Fatalf("expected error %q, got %q", expectedError, err) - } - }) + return nil } -func TestEncoding_PublicKeyShare_Bytes(t *testing.T) { - testAll(t, func(t *testing.T, test *tableTest) { - s := makeSigners(t, test)[0] - keyShare := s.KeyShare.Public() +func testHexEncoding(t *testing.T, in, out serde) error { + h := in.Hex() - encoded := keyShare.Encode() - - decoded := new(keys.PublicKeyShare) - if err := decoded.Decode(encoded); err != nil { - t.Fatal(err) - } + if err := out.DecodeHex(h); err != nil { + return err + } - if err := comparePublicKeyShare(keyShare, decoded); err != nil { - t.Fatal(err) - } - }) + return nil } -func TestEncoding_PublicKeyShare_JSON(t *testing.T) { - testAll(t, func(t *testing.T, test *tableTest) { - s := makeSigners(t, test)[0] - keyShare := s.KeyShare.Public() +func testJSONEncoding(t *testing.T, in, out serde) error { + jsonEnc, err := json.Marshal(in) + if err != nil { + return err + } - encoded, err := json.Marshal(keyShare) - if err != nil { - t.Fatal(err) - } + t.Log(string(jsonEnc)) - decoded := new(keys.PublicKeyShare) - if err := json.Unmarshal(encoded, decoded); err != nil { - t.Fatal(err) - } + if err = json.Unmarshal(jsonEnc, out); err != nil { + return err + } - if err := comparePublicKeyShare(keyShare, decoded); err != nil { - t.Fatal(err) - } + return nil +} - // expect error - decoded = new(keys.PublicKeyShare) - expectedError := errors.New("invalid group identifier") - encoded = replaceStringInBytes(encoded, fmt.Sprintf("\"group\":%d", test.ECGroup()), "\"group\":70") +func testAndCompareSerdeSimple( + t *testing.T, + in serde, + maker func() serde, + expectedMatch bool, + tester tester, + compare func(t *testing.T, a, b serde, expectedMatch bool), +) { + out := maker() + if err := tester(t, in, out); err != nil { + t.Fatal(err) + } + compare(t, in, out, expectedMatch) +} - if err := json.Unmarshal(encoded, decoded); err == nil || err.Error() != expectedError.Error() { - t.Fatalf("expected error %q, got %q", expectedError, err) - } - }) +func testAndCompareSerde( + t *testing.T, + in serde, + expectedMatch bool, + compare func(t *testing.T, a, b serde, expectedMatch bool), + maker func() serde, +) { + testAndCompareSerdeSimple(t, in, maker, expectedMatch, testByteEncoding, compare) + testAndCompareSerdeSimple(t, in, maker, expectedMatch, testHexEncoding, compare) + testAndCompareSerdeSimple(t, in, maker, expectedMatch, testJSONEncoding, compare) } diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index eb6a74a..41f065f 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -13,6 +13,8 @@ import ( "strings" "testing" + debugec "github.com/bytemare/ecc/debug" + "github.com/bytemare/frost" "github.com/bytemare/frost/internal" ) @@ -37,8 +39,8 @@ func TestVerifySignature_InvalidSignature(t *testing.T) { configuration, _ := fullSetup(t, test) signature := &frost.Signature{ - R: test.ECGroup().Base(), - Z: test.ECGroup().NewScalar().Random(), + R: test.Group().Base(), + Z: test.Group().NewScalar().Random(), } if err := frost.VerifySignature(test.Ciphersuite, message, signature, configuration.GroupPublicKey); err == nil || @@ -76,29 +78,125 @@ func TestSigner_Sign_NoNonceForCommitmentID(t *testing.T) { } } -/* -func TestSigner_Sign_FailedLambdaGeneration(t *testing.T) { - call signer.Sign +func TestFrost_NewPublicKeyShare(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + configuration, keyShares := makeConfAndShares(t, test) + publicKeyShare := keyShares[0].Public() + + newPublicKeyShare, err := frost.NewPublicKeyShare( + configuration.Ciphersuite, + publicKeyShare.ID, + publicKeyShare.PublicKey.Encode(), + ) + if err != nil { + t.Fatal(err) + } + + comparePublicKeyShare(t, publicKeyShare, newPublicKeyShare, true) + }) +} + +func TestFrost_NewPublicKeyShare_InvalidCiphersuite(t *testing.T) { + expectedErrorPrefix := internal.ErrInvalidCiphersuite + + if _, err := frost.NewPublicKeyShare(0, 0, nil); err == nil || + err.Error() != expectedErrorPrefix.Error() { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } } -func TestSigner_Sign_VerifyCommitmentList_BadCommitment(t *testing.T) { - call signer.Sign +func TestFrost_NewPublicKeyShare_IdentifierIs0(t *testing.T) { + expectedErrorPrefix := internal.ErrIdentifierIs0 + + if _, err := frost.NewPublicKeyShare(frost.Default, 0, nil); err == nil || + err.Error() != expectedErrorPrefix.Error() { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } } -func TestSigner_Sign_VerifyCommitmentList_NoCommitmentForSigner(t *testing.T) { - call signer.Sign +func TestFrost_NewPublicKeyShare_BadPublicKey(t *testing.T) { + expectedErrorPrefix := "could not decode public share: element Decode: " + + testAll(t, func(t *testing.T, test *tableTest) { + bad := debugec.BadElementOffCurve(test.Group()) + + if _, err := frost.NewPublicKeyShare(test.Ciphersuite, 1, bad); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} + +func TestFrost_NewKeyShare(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + configuration, keyShares := makeConfAndShares(t, test) + keyShare := keyShares[0] + + newKeyShare, err := frost.NewKeyShare(configuration.Ciphersuite, keyShare.ID, keyShare.SecretKey().Encode(), + keyShare.PublicKey.Encode(), configuration.GroupPublicKey.Encode()) + if err != nil { + t.Fatal(err) + } + + compareKeyShares(t, keyShare, newKeyShare, true) + }) } -func TestSigner_Sign_VerifyNonces_BadCommitmentID(t *testing.T) { +func TestFrost_NewKeyShare_InvalidPublicKey(t *testing.T) { + expectedErrorPrefix := "could not decode public share: element Decode: " + + testAll(t, func(t *testing.T, test *tableTest) { + randomSecret := test.Ciphersuite.Group().NewScalar().Random().Encode() + bad := debugec.BadElementOffCurve(test.Group()) + if _, err := frost.NewKeyShare(test.Ciphersuite, 1, randomSecret, bad, nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) } -func TestSigner_Sign_VerifyNonces_BadHidingNonceCommitment(t *testing.T) { +func TestFrost_NewKeyShare_BadSecretKey(t *testing.T) { + expectedErrorPrefix := "could not decode secret share: scalar Decode: " + testAll(t, func(t *testing.T, test *tableTest) { + bad := debugec.BadScalarHigh(test.Group()) + pk := test.Group().NewElement().Base().Encode() + + if _, err := frost.NewKeyShare(test.Ciphersuite, 1, bad, pk, nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) } -func TestSigner_Sign_VerifyNonces_BadBindingNonceCommitment(t *testing.T) { +func TestFrost_NewKeyShare_BadPublicKey(t *testing.T) { + expectedErrorPrefix := "the signer's public key doesn't match its private key" + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Ciphersuite.Group() + secret := g.NewScalar().Random().Encode() + badPublic := g.NewElement().Base().Encode() + + if _, err := frost.NewKeyShare(test.Ciphersuite, 1, secret, badPublic, nil); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) } -*/ +func TestFrost_NewKeyShare_InvalidVerificationKey(t *testing.T) { + expectedErrorPrefix := "could not decode the group public key: element Decode: " + + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Ciphersuite.Group() + secret := g.NewScalar().Random() + public := g.NewElement().Base().Multiply(secret).Encode() + bad := debugec.BadElementOffCurve(g) + + if _, err := frost.NewKeyShare(test.Ciphersuite, 1, secret.Encode(), public, bad); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } + }) +} diff --git a/tests/frost_test.go b/tests/frost_test.go index 72fa12d..e40464d 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -12,16 +12,16 @@ import ( "fmt" "testing" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" "github.com/bytemare/frost/debug" - "github.com/bytemare/frost/keys" ) type tableTest struct { frost.Ciphersuite - threshold, maxSigners uint64 + threshold, maxSigners uint16 } var testTable = []tableTest{ @@ -40,6 +40,16 @@ var testTable = []tableTest{ threshold: 2, maxSigners: 3, }, + { + Ciphersuite: frost.P384, + threshold: 2, + maxSigners: 3, + }, + { + Ciphersuite: frost.P521, + threshold: 2, + maxSigners: 3, + }, { Ciphersuite: frost.Secp256k1, threshold: 2, @@ -50,10 +60,10 @@ var testTable = []tableTest{ func runFrost( t *testing.T, test *tableTest, - threshold, maxSigners uint64, + threshold, maxSigners uint16, message []byte, keyShares []*keys.KeyShare, - groupPublicKey *group.Element, + groupPublicKey *ecc.Element, ) { // Collect public keys. publicKeyShares := getPublicKeyShares(keyShares) @@ -126,7 +136,7 @@ func TestFrost_WithTrustedDealer(t *testing.T) { message := []byte("test") testAll(t, func(t *testing.T, test *tableTest) { - g := test.Ciphersuite.ECGroup() + g := test.Ciphersuite.Group() sk := g.NewScalar().Random() keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, sk, test.threshold, test.maxSigners) runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, groupPublicKey) @@ -137,7 +147,7 @@ func TestFrost_WithDKG(t *testing.T) { message := []byte("test") testAll(t, func(t *testing.T, test *tableTest) { - g := test.Ciphersuite.ECGroup() + g := test.Ciphersuite.Group() keyShares, groupPublicKey, _ := runDKG(t, g, test.threshold, test.maxSigners) runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, groupPublicKey) }) @@ -145,7 +155,7 @@ func TestFrost_WithDKG(t *testing.T) { func testAll(t *testing.T, f func(*testing.T, *tableTest)) { for _, test := range testTable { - t.Run(fmt.Sprintf("%s", test.ECGroup()), func(t *testing.T) { + t.Run(fmt.Sprintf("%s", test.Group()), func(t *testing.T) { f(t, &test) }) } diff --git a/tests/misc_test.go b/tests/misc_test.go index 12769f2..8ed7bac 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -10,27 +10,26 @@ package frost_test import ( "errors" - "fmt" "strings" "testing" - group "github.com/bytemare/crypto" "github.com/bytemare/dkg" + "github.com/bytemare/ecc" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" "github.com/bytemare/frost/debug" "github.com/bytemare/frost/internal" - "github.com/bytemare/frost/keys" ) func verifyTrustedDealerKeygen( t *testing.T, test *tableTest, ks []*keys.KeyShare, - pk *group.Element, - coms []*group.Element, + pk *ecc.Element, + coms []*ecc.Element, ) { - if uint64(len(coms)) != test.threshold { + if len(coms) != int(test.threshold) { t.Fatalf("%d / %d", len(coms), test.threshold) } @@ -48,15 +47,15 @@ func verifyTrustedDealerKeygen( t.Fatal(err) } - if uint64(len(participantPublicKeys)) != test.maxSigners { + if len(participantPublicKeys) != int(test.maxSigners) { t.Fatal() } - if groupPublicKey.Equal(pk) != 1 { + if !groupPublicKey.Equal(pk) { t.Fatal() } - g := test.Ciphersuite.ECGroup() + g := test.Ciphersuite.Group() for i, shareI := range ks { if !debug.VerifyVSS(g, shareI, coms) { @@ -76,7 +75,7 @@ func verifyTrustedDealerKeygen( func TestTrustedDealerKeygen(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { - g := test.Ciphersuite.ECGroup() + g := test.Ciphersuite.Group() groupSecretKey := g.NewScalar().Random() keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( test.Ciphersuite, @@ -138,7 +137,7 @@ func TestSchnorrSign(t *testing.T) { message1 := []byte("message-1") message2 := []byte("message-2") testAll(t, func(t *testing.T, test *tableTest) { - g := test.ECGroup() + g := test.Group() secretKey1 := g.NewScalar().Random() verificationKey1 := g.Base().Multiply(secretKey1) secretKey2 := g.NewScalar().Random() @@ -164,9 +163,7 @@ func TestSchnorrSign(t *testing.T) { t.Fatal(err) } - if err = compareSignatures(signature1, signature2, false); err != nil { - t.Fatal(err) - } + compareSignatures(t, signature1, signature2, false) // Same key, same message = different signatures signature1, err = debug.Sign(test.Ciphersuite, message1, secretKey1) @@ -179,9 +176,7 @@ func TestSchnorrSign(t *testing.T) { t.Fatal(err) } - if err = compareSignatures(signature1, signature2, false); err != nil { - t.Fatal(err) - } + compareSignatures(t, signature1, signature2, false) // Same key, same message, same random = same signatures k := g.NewScalar().Random() @@ -196,15 +191,13 @@ func TestSchnorrSign(t *testing.T) { t.Fatal(err) } - if err = compareSignatures(signature1, signature2, true); err != nil { - t.Fatal(err) - } + compareSignatures(t, signature1, signature2, true) // Same key, same message, explicit different random = same signatures k1 := g.NewScalar().Random() k2 := g.NewScalar().Random() - if k1.Equal(k2) == 1 { + if k1.Equal(k2) { t.Fatal("unexpected equality") } @@ -218,9 +211,7 @@ func TestSchnorrSign(t *testing.T) { t.Fatal(err) } - if err = compareSignatures(signature1, signature2, false); err != nil { - t.Fatal(err) - } + compareSignatures(t, signature1, signature2, false) // Different keys, same message = different signatures signature1, err = debug.Sign(test.Ciphersuite, message1, secretKey1) @@ -233,9 +224,7 @@ func TestSchnorrSign(t *testing.T) { t.Fatal(err) } - if err = compareSignatures(signature1, signature2, false); err != nil { - t.Fatal(err) - } + compareSignatures(t, signature1, signature2, false) // Different keys, different messages = different signatures signature1, err = debug.Sign(test.Ciphersuite, message1, secretKey1) @@ -248,9 +237,7 @@ func TestSchnorrSign(t *testing.T) { t.Fatal(err) } - if err = compareSignatures(signature1, signature2, false); err != nil { - t.Fatal(err) - } + compareSignatures(t, signature1, signature2, false) }) } @@ -279,7 +266,7 @@ func TestRecoverPublicKeys(t *testing.T) { t.Fatal(err) } - if dealerGroupPubKey.Equal(groupPublicKey) != 1 { + if !dealerGroupPubKey.Equal(groupPublicKey) { t.Fatal("expected equality") } @@ -288,7 +275,7 @@ func TestRecoverPublicKeys(t *testing.T) { } for i, keyShare := range keyShares { - if keyShare.PublicKey.Equal(participantPublicKeys[i]) != 1 { + if !keyShare.PublicKey.Equal(participantPublicKeys[i]) { t.Fatal("expected equality") } } @@ -305,8 +292,8 @@ func TestRecoverPublicKeys_InvalidCiphersuite(t *testing.T) { func TestRecoverPublicKeys_BadCommitment(t *testing.T) { expectedError := "can't recover public keys: commitment has nil element" ciphersuite := frost.Ristretto255 - threshold := uint64(2) - maxSigners := uint64(3) + threshold := uint16(2) + maxSigners := uint16(3) _, _, secretsharingCommitment := debug.TrustedDealerKeygen( ciphersuite, nil, @@ -330,17 +317,17 @@ func TestPublicKeyShareVerification(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { keyShares, dealerGroupPubKey, _ := runDKG( t, - test.Ciphersuite.ECGroup(), + test.Ciphersuite.Group(), test.threshold, test.maxSigners, ) - vssComs := make([][]*group.Element, test.maxSigners) + vssComs := make([][]*ecc.Element, test.maxSigners) pkShares := make([]*keys.PublicKeyShare, test.maxSigners) for i, keyShare := range keyShares { pk := keyShare.Public() - vssComs[i] = pk.Commitment + vssComs[i] = pk.VssCommitment pkShares[i] = pk } @@ -349,8 +336,8 @@ func TestPublicKeyShareVerification(t *testing.T) { } for _, pk := range pkShares { - if !pk.Verify(vssComs) { - t.Fatal("expected validity") + if err := dkg.VerifyPublicKey(dkg.Ciphersuite(test.Ciphersuite), pk.ID, pk.PublicKey, vssComs); err != nil { + t.Fatalf("expected validity: %s", err) } } }) @@ -360,18 +347,18 @@ func TestPublicKeyShareVerificationFail(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { keyShares, dealerGroupPubKey, _ := runDKG( t, - test.Ciphersuite.ECGroup(), + test.Ciphersuite.Group(), test.threshold, test.maxSigners, ) - vssComs := make([][]*group.Element, test.maxSigners) + vssComs := make([][]*ecc.Element, test.maxSigners) pkShares := make([]*keys.PublicKeyShare, test.maxSigners) for i, keyShare := range keyShares { pk := keyShare.Public() - vssComs[i] = pk.Commitment - pk.PublicKey = nil + vssComs[i] = pk.VssCommitment + pk.PublicKey = test.Group().Base() pkShares[i] = pk } @@ -380,7 +367,7 @@ func TestPublicKeyShareVerificationFail(t *testing.T) { } for _, pk := range pkShares { - if pk.Verify(vssComs) { + if dkg.VerifyPublicKey(dkg.Ciphersuite(test.Ciphersuite), pk.ID, pk.PublicKey, vssComs) == nil { t.Fatal("expected invalidity") } } @@ -389,13 +376,13 @@ func TestPublicKeyShareVerificationFail(t *testing.T) { func TestLambda_BadID(t *testing.T) { // expectedErrorPrefix := "anomaly in participant identifiers: one of the polynomial's coefficients is zero" - g := group.Ristretto255Sha512 - polynomial := []*group.Scalar{ + g := ecc.Ristretto255Sha512 + polynomial := []*ecc.Scalar{ g.NewScalar().SetUInt64(1), g.NewScalar().SetUInt64(2), g.NewScalar().SetUInt64(3), } // todo : what happens if the participant list is not vetted? - fmt.Println(internal.Lambda(g, 4, polynomial).Hex()) + t.Log(internal.ComputeLambda(g, 4, polynomial).Hex()) } diff --git a/tests/signer_test.go b/tests/signer_test.go index 7601658..6eddd50 100644 --- a/tests/signer_test.go +++ b/tests/signer_test.go @@ -13,16 +13,16 @@ import ( "strings" "testing" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" "github.com/bytemare/frost" "github.com/bytemare/frost/internal" ) func TestLambdaRegistry(t *testing.T) { - g := group.Ristretto255Sha512 - id := uint64(2) - participants := []uint64{1, 2, 3, 4} + g := ecc.Ristretto255Sha512 + id := uint16(2) + participants := []uint16{1, 2, 3, 4} lambdas := make(internal.LambdaRegistry) // Get should return nil @@ -39,26 +39,26 @@ func TestLambdaRegistry(t *testing.T) { // Getting the same entry lambda2 := lambdas.Get(participants) - if lambda.Equal(lambda2) != 1 { + if !lambda.Equal(lambda2) { t.Fatal("expected equality") } lambda3 := lambdas.GetOrNew(g, id, participants) - if lambda.Equal(lambda3) != 1 { + if !lambda.Equal(lambda3) { t.Fatal("expected equality") } // Getting another entry must result in another returned value lambda4 := lambdas.GetOrNew(g, id, participants[:3]) - if lambda.Equal(lambda4) == 1 { + if lambda.Equal(lambda4) { t.Fatal("unexpected equality") } lambda5 := lambdas.GetOrNew(g, id, participants[:3]) - if lambda4.Equal(lambda5) != 1 { + if !lambda4.Equal(lambda5) { t.Fatal("expected equality") } @@ -73,7 +73,7 @@ func TestLambdaRegistry(t *testing.T) { lambdas.Set(participants, lambda6) lambda7 := lambdas.Get(participants) - if lambda6.Equal(lambda7) != 1 { + if !lambda6.Equal(lambda7) { t.Fatal("expected equality") } } diff --git a/tests/utils_test.go b/tests/utils_test.go index 04acce9..3bbd056 100644 --- a/tests/utils_test.go +++ b/tests/utils_test.go @@ -12,13 +12,10 @@ import ( "bytes" "errors" "fmt" - "math/big" "slices" "strings" "testing" - group "github.com/bytemare/crypto" - "github.com/bytemare/frost/internal" ) @@ -27,6 +24,8 @@ var ( errNoPanicMessage = errors.New("panic but no message") ) +// hasPanic runs f and recovers from a panic if any occurred, and returns whether it did and the panic message as an +// error. func hasPanic(f func()) (has bool, err error) { defer func() { var report any @@ -69,38 +68,6 @@ func testPanic(s string, expectedError error, f func()) error { return nil } -func badScalar(t *testing.T, g group.Group) []byte { - order, ok := new(big.Int).SetString(g.Order(), 0) - if !ok { - t.Errorf("setting int in base %d failed: %v", 0, g.Order()) - } - - encoded := make([]byte, g.ScalarLength()) - order.FillBytes(encoded) - - if g == group.Ristretto255Sha512 || g == group.Edwards25519Sha512 { - slices.Reverse(encoded) - } - - return encoded -} - -func badElement(t *testing.T, g group.Group) []byte { - order, ok := new(big.Int).SetString(g.Order(), 0) - if !ok { - t.Errorf("setting int in base %d failed: %v", 0, g.Order()) - } - - encoded := make([]byte, g.ElementLength()) - order.FillBytes(encoded) - - if g == group.Ristretto255Sha512 || g == group.Edwards25519Sha512 { - slices.Reverse(encoded) - } - - return encoded -} - func expectError(expectedError error, f func() error) error { if err := f(); err == nil || err.Error() != expectedError.Error() { return fmt.Errorf("expected %q, got %q", expectedError, err) diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index 98cc87f..3c3787f 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -15,16 +15,15 @@ import ( "strings" "testing" - group "github.com/bytemare/crypto" - secretsharing "github.com/bytemare/secret-sharing" + "github.com/bytemare/ecc" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" - "github.com/bytemare/frost/keys" ) type ParticipantList []*frost.Signer -func (p ParticipantList) Get(id uint64) *frost.Signer { +func (p ParticipantList) Get(id uint16) *frost.Signer { for _, i := range p { if i.KeyShare.ID == id { return i @@ -43,7 +42,7 @@ func stringToUint(t *testing.T, s string) uint { return uint(i) } -func decodeScalar(t *testing.T, g group.Group, enc []byte) *group.Scalar { +func decodeScalar(t *testing.T, g ecc.Group, enc []byte) *ecc.Scalar { scalar := g.NewScalar() if err := scalar.Decode(enc); err != nil { t.Fatal(err) @@ -52,7 +51,7 @@ func decodeScalar(t *testing.T, g group.Group, enc []byte) *group.Scalar { return scalar } -func decodeElement(t *testing.T, g group.Group, enc []byte) *group.Element { +func decodeElement(t *testing.T, g ecc.Group, enc []byte) *ecc.Element { element := g.NewElement() if err := element.Decode(enc); err != nil { t.Fatal(err) @@ -84,7 +83,7 @@ func (j *ByteToHex) UnmarshalJSON(b []byte) error { */ type testVectorInput struct { - ParticipantList []uint64 `json:"participant_list"` + ParticipantList []uint16 `json:"participant_list"` GroupSecretKey ByteToHex `json:"group_secret_key"` GroupPublicKey ByteToHex `json:"group_public_key"` Message ByteToHex `json:"message"` @@ -124,7 +123,7 @@ func (c testVectorConfig) decode(t *testing.T) *testConfig { type testVectorParticipantShare struct { ParticipantShare ByteToHex `json:"participant_share"` - Identifier uint64 `json:"identifier"` + Identifier uint16 `json:"identifier"` } type testParticipant struct { @@ -136,7 +135,7 @@ type testParticipant struct { BindingNonceCommitment ByteToHex `json:"binding_nonce_commitment"` BindingFactorInput ByteToHex `json:"binding_factor_input"` BindingFactor ByteToHex `json:"binding_factor"` - Identifier uint64 `json:"identifier"` + Identifier uint16 `json:"identifier"` } type testVectorRoundOneOutputs struct { @@ -145,7 +144,7 @@ type testVectorRoundOneOutputs struct { type testVectorSigShares struct { SigShare ByteToHex `json:"sig_share"` - Identifier uint64 `json:"identifier"` + Identifier uint16 `json:"identifier"` } type testVectorRoundTwoOutputs struct { @@ -164,11 +163,11 @@ type testConfig struct { } type testInput struct { - ParticipantList []uint64 - GroupSecretKey *group.Scalar - GroupPublicKey *group.Element + ParticipantList []uint16 + GroupSecretKey *ecc.Scalar + GroupPublicKey *ecc.Element Message []byte - SharePolynomialCoefficients []*group.Scalar + SharePolynomialCoefficients []*ecc.Scalar Participants []*keys.KeyShare } @@ -181,15 +180,15 @@ type test struct { } type participant struct { - HidingNonce *group.Scalar - BindingNonce *group.Scalar - HidingNonceCommitment *group.Element - BindingNonceCommitment *group.Element - BindingFactor *group.Scalar + HidingNonce *ecc.Scalar + BindingNonce *ecc.Scalar + HidingNonceCommitment *ecc.Element + BindingNonceCommitment *ecc.Element + BindingFactor *ecc.Scalar HidingNonceRandomness []byte BindingNonceRandomness []byte BindingFactorInput []byte - ID uint64 + ID uint16 } type testRoundOneOutputs struct { @@ -207,8 +206,8 @@ type testRoundTwoOutputs struct { func makeFrostConfig(c frost.Ciphersuite, threshold, maxSigners uint) *frost.Configuration { return &frost.Configuration{ Ciphersuite: c, - Threshold: uint64(threshold), - MaxSigners: uint64(maxSigners), + Threshold: uint16(threshold), + MaxSigners: uint16(maxSigners), GroupPublicKey: nil, SignerPublicKeyShares: nil, } @@ -231,7 +230,7 @@ func configToConfiguration(t *testing.T, c *testVectorConfig, threshold, maxSign return nil } -func decodeParticipant(t *testing.T, g group.Group, tp *testParticipant) *participant { +func decodeParticipant(t *testing.T, g ecc.Group, tp *testParticipant) *participant { return &participant{ ID: tp.Identifier, HidingNonceRandomness: tp.HidingNonceRandomness, @@ -245,14 +244,14 @@ func decodeParticipant(t *testing.T, g group.Group, tp *testParticipant) *partic } } -func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { +func (i testVectorInput) decode(t *testing.T, g ecc.Group) *testInput { input := &testInput{ GroupSecretKey: decodeScalar(t, g, i.GroupSecretKey), GroupPublicKey: decodeElement(t, g, i.GroupPublicKey), Message: i.Message, - SharePolynomialCoefficients: make([]*group.Scalar, len(i.SharePolynomialCoefficients)+1), + SharePolynomialCoefficients: make([]*ecc.Scalar, len(i.SharePolynomialCoefficients)+1), Participants: make([]*keys.KeyShare, len(i.ParticipantShares)), - ParticipantList: make([]uint64, len(i.ParticipantList)), + ParticipantList: make([]uint16, len(i.ParticipantList)), } for j, id := range i.ParticipantList { @@ -270,11 +269,11 @@ func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { input.Participants[j] = &keys.KeyShare{ Secret: secret, GroupPublicKey: input.GroupPublicKey, - PublicKeyShare: secretsharing.PublicKeyShare{ - PublicKey: public, - Commitment: nil, - ID: p.Identifier, - Group: g, + PublicKeyShare: keys.PublicKeyShare{ + PublicKey: public, + VssCommitment: nil, + ID: p.Identifier, + Group: g, }, } } @@ -282,7 +281,7 @@ func (i testVectorInput) decode(t *testing.T, g group.Group) *testInput { return input } -func (o testVectorRoundOneOutputs) decode(t *testing.T, g group.Group) *testRoundOneOutputs { +func (o testVectorRoundOneOutputs) decode(t *testing.T, g ecc.Group) *testRoundOneOutputs { r := &testRoundOneOutputs{ Outputs: make([]*participant, len(o.Outputs)), } @@ -294,7 +293,7 @@ func (o testVectorRoundOneOutputs) decode(t *testing.T, g group.Group) *testRoun return r } -func (o testVectorRoundTwoOutputs) decode(t *testing.T, g group.Group) *testRoundTwoOutputs { +func (o testVectorRoundTwoOutputs) decode(t *testing.T, g ecc.Group) *testRoundTwoOutputs { r := &testRoundTwoOutputs{ Outputs: make([]*frost.SignatureShare, len(o.Outputs)), } @@ -311,7 +310,7 @@ func (o testVectorRoundTwoOutputs) decode(t *testing.T, g group.Group) *testRoun func (v testVector) decode(t *testing.T) *test { conf := v.Config.decode(t) - inputs := v.Inputs.decode(t, conf.Ciphersuite.ECGroup()) + inputs := v.Inputs.decode(t, conf.Ciphersuite.Group()) conf.GroupPublicKey = inputs.GroupPublicKey conf.SignerPublicKeyShares = make([]*keys.PublicKeyShare, len(inputs.Participants)) @@ -327,8 +326,8 @@ func (v testVector) decode(t *testing.T) *test { return &test{ Config: conf, Inputs: inputs, - RoundOneOutputs: v.RoundOneOutputs.decode(t, conf.Ciphersuite.ECGroup()), - RoundTwoOutputs: v.RoundTwoOutputs.decode(t, conf.Ciphersuite.ECGroup()), + RoundOneOutputs: v.RoundOneOutputs.decode(t, conf.Ciphersuite.Group()), + RoundTwoOutputs: v.RoundTwoOutputs.decode(t, conf.Ciphersuite.Group()), FinalOutput: v.FinalOutput.Sig, } } diff --git a/tests/vectors_test.go b/tests/vectors_test.go index 803e757..f84f9fa 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -16,15 +16,15 @@ import ( "path/filepath" "testing" - group "github.com/bytemare/crypto" + "github.com/bytemare/ecc" + "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" "github.com/bytemare/frost/debug" - "github.com/bytemare/frost/keys" ) -func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *group.Element) { - g := v.Config.Ciphersuite.ECGroup() +func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *ecc.Element) { + g := v.Config.Ciphersuite.Group() keyShares, dealerGroupPubKey, secretsharingCommitment := debug.TrustedDealerKeygen( v.Config.Ciphersuite, @@ -33,7 +33,7 @@ func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *group.Element) v.Config.Configuration.MaxSigners, v.Inputs.SharePolynomialCoefficients...) - if uint64(len(secretsharingCommitment)) != v.Config.Configuration.Threshold { + if len(secretsharingCommitment) != int(v.Config.Configuration.Threshold) { t.Fatalf( "%d / %d", len(secretsharingCommitment), v.Config.Configuration.Threshold) } @@ -44,7 +44,7 @@ func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *group.Element) t.Fatal(err) } - if recoveredKey.Equal(v.Inputs.GroupSecretKey) != 1 { + if !recoveredKey.Equal(v.Inputs.GroupSecretKey) { t.Fatal() } @@ -57,11 +57,11 @@ func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *group.Element) t.Fatal(err) } - if uint64(len(participantPublicKey)) != v.Config.Configuration.MaxSigners { + if len(participantPublicKey) != int(v.Config.Configuration.MaxSigners) { t.Fatal() } - if groupPublicKey.Equal(dealerGroupPubKey) != 1 { + if !groupPublicKey.Equal(dealerGroupPubKey) { t.Fatal() } @@ -81,7 +81,7 @@ func (v test) test(t *testing.T) { cpt := len(keyShares) for _, p := range keyShares { for _, p2 := range v.Inputs.Participants { - if p2.Identifier() == p.Identifier() && p2.SecretKey().Equal(p.Secret) == 1 { + if p2.Identifier() == p.Identifier() && p2.SecretKey().Equal(p.Secret) { cpt-- } } @@ -126,16 +126,16 @@ func (v test) test(t *testing.T) { com := p.Commit() - if p.NonceCommitments[com.CommitmentID].HidingNonce.Equal(pv.HidingNonce) != 1 { + if !p.NonceCommitments[com.CommitmentID].HidingNonce.Equal(pv.HidingNonce) { t.Fatal(i) } - if p.NonceCommitments[com.CommitmentID].BindingNonce.Equal(pv.BindingNonce) != 1 { + if !p.NonceCommitments[com.CommitmentID].BindingNonce.Equal(pv.BindingNonce) { t.Fatal(i) } - if com.HidingNonceCommitment.Equal(pv.HidingNonceCommitment) != 1 { + if !com.HidingNonceCommitment.Equal(pv.HidingNonceCommitment) { t.Fatal(i) } - if com.BindingNonceCommitment.Equal(pv.BindingNonceCommitment) != 1 { + if !com.BindingNonceCommitment.Equal(pv.BindingNonceCommitment) { t.Fatal(i) } @@ -157,7 +157,7 @@ func (v test) test(t *testing.T) { } // Check against vector - if share.SignatureShare.Equal(sigShares[i].SignatureShare) != 1 { + if !share.SignatureShare.Equal(sigShares[i].SignatureShare) { t.Fatalf("%s\n%s\n", share.SignatureShare.Hex(), sigShares[i].SignatureShare.Hex()) } @@ -172,7 +172,7 @@ func (v test) test(t *testing.T) { t.Fatal(err) } - if !bytes.Equal(sig.Encode(), v.FinalOutput) { + if !bytes.Equal(sig.Encode()[1:], v.FinalOutput) { t.Fatal("") } From 423322fcd226f10be1f37de4912cc2aea66dd1c6 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:33:29 +0200 Subject: [PATCH 26/31] fix err message in test Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- README.md | 4 ++-- tests/frost_error_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d219f9..a61d87c 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ easily recovered using the corresponding ```Decode()``` method. More generally, to decode an element (or point) in the Ristretto255 group, ```go import ( - group "https://github.com/bytemare" + "https://github.com/bytemare/ecc" ) bytesPublicKey := []byte{1, 2, 3, ...} @@ -97,7 +97,7 @@ if err := publicKey.Decode(bytesPublicKey); err != nil { The same goes for secret keys (or scalars), ```go import ( - group "https://github.com/bytemare" + "https://github.com/bytemare/ecc" ) bytesSecretKey := []byte{1, 2, 3, ...} diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index 41f065f..b639f3e 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -171,7 +171,7 @@ func TestFrost_NewKeyShare_BadSecretKey(t *testing.T) { } func TestFrost_NewKeyShare_BadPublicKey(t *testing.T) { - expectedErrorPrefix := "the signer's public key doesn't match its private key" + expectedErrorPrefix := "provided key share has non-matching secret and public keys" testAll(t, func(t *testing.T, test *tableTest) { g := test.Ciphersuite.Group() From 6fcca60530ff69920d6d5b02572be78cdbea39ba Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:50:45 +0200 Subject: [PATCH 27/31] reuse json group decoder and remove comment Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- coordinator.go | 1 - encoding.go | 45 +++------------------------------------------ 2 files changed, 3 insertions(+), 43 deletions(-) diff --git a/coordinator.go b/coordinator.go index 05743de..7ad9aa3 100644 --- a/coordinator.go +++ b/coordinator.go @@ -78,7 +78,6 @@ func (c *Configuration) AggregateSignatures( // Verify the final signature. Failure is unlikely to happen, as the signature is valid if the signature shares are. if verify { if err = VerifySignature(c.Ciphersuite, message, signature, c.GroupPublicKey); err != nil { - // difficult to reach, because if all shares are valid, the final signature is valid. return nil, err } } diff --git a/encoding.go b/encoding.go index 6c37782..0f04951 100644 --- a/encoding.go +++ b/encoding.go @@ -14,13 +14,10 @@ import ( "encoding/json" "errors" "fmt" - "regexp" - "strconv" - "github.com/bytemare/ecc" - "github.com/bytemare/secret-sharing/keys" - + "github.com/bytemare/ecc/encoding" "github.com/bytemare/frost/internal" + "github.com/bytemare/secret-sharing/keys" ) const ( @@ -755,44 +752,8 @@ func (s *signatureShadow) init(g ecc.Group) { s.Z = g.NewScalar() } -func jsonReGetField(key, s, catch string) (string, error) { - r := fmt.Sprintf(`%q:%s`, key, catch) - re := regexp.MustCompile(r) - matches := re.FindStringSubmatch(s) - - if len(matches) != 2 { - return "", internal.ErrEncodingInvalidJSONEncoding - } - - return matches[1], nil -} - -// jsonReGetGroup attempts to find the Ciphersuite JSON encoding in s. -func jsonReGetGroup(s string) (ecc.Group, error) { - f, err := jsonReGetField("group", s, `(\w+)`) - if err != nil { - return 0, err - } - - g, err := strconv.Atoi(f) - if err != nil { - return 0, fmt.Errorf("failed to read Group: %w", err) - } - - if g < 0 || g > 63 { - return 0, errInvalidCiphersuite - } - - c := Ciphersuite(g) - if !c.Available() { - return 0, errInvalidCiphersuite - } - - return ecc.Group(g), nil -} - func unmarshalJSON(data []byte, target shadowInit) error { - g, err := jsonReGetGroup(string(data)) + g, err := encoding.JSONReGetGroup(string(data)) if err != nil { return err } From a42fbf4f1e3a294987c09d2bd305d970073efd2c Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:00:11 +0200 Subject: [PATCH 28/31] remove unreachable code Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- coordinator.go | 11 ++--------- encoding.go | 2 +- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/coordinator.go b/coordinator.go index 7ad9aa3..f0c8588 100644 --- a/coordinator.go +++ b/coordinator.go @@ -41,8 +41,8 @@ func (s *Signature) Clear() { // 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 signer from the set of allowed participants in future runs of FROST. If verify -// is set to true, AggregateSignatures will automatically verify the signature shares and the output signature, and will -// return an error with the first encountered invalid signature share. +// is set to true, AggregateSignatures will automatically verify the signature shares, and will return an error on the +// first encountered invalid signature share func (c *Configuration) AggregateSignatures( message []byte, sigShares []*SignatureShare, @@ -75,13 +75,6 @@ func (c *Configuration) AggregateSignatures( return nil, err } - // Verify the final signature. Failure is unlikely to happen, as the signature is valid if the signature shares are. - if verify { - if err = VerifySignature(c.Ciphersuite, message, signature, c.GroupPublicKey); err != nil { - return nil, err - } - } - return signature, nil } diff --git a/encoding.go b/encoding.go index 0f04951..8b7c420 100644 --- a/encoding.go +++ b/encoding.go @@ -755,7 +755,7 @@ func (s *signatureShadow) init(g ecc.Group) { func unmarshalJSON(data []byte, target shadowInit) error { g, err := encoding.JSONReGetGroup(string(data)) if err != nil { - return err + return fmt.Errorf("%w", err) } target.init(g) From 44be77fb75c80b1c06db1e76ddcda8275b40e404 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:02:12 +0200 Subject: [PATCH 29/31] added tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- commitment.go | 8 +- coordinator.go | 2 +- encoding.go | 5 +- signer.go | 17 +-- tests/encoding_test.go | 257 ++++++++++++++++++++++++++++++++++++++++- tests/frost_test.go | 10 ++ 6 files changed, 284 insertions(+), 15 deletions(-) diff --git a/commitment.go b/commitment.go index b83d6f1..11818b4 100644 --- a/commitment.go +++ b/commitment.go @@ -140,12 +140,12 @@ func (c CommitmentList) Encode() []byte { // DecodeList decodes a byte string produced by the CommitmentList.Encode() method. func DecodeList(data []byte) (CommitmentList, error) { if len(data) < 3 { - return nil, errInvalidLength + return nil, fmt.Errorf(errFmt, errDecodeCommitmentListPrefix, internal.ErrInvalidLength) } g := ecc.Group(data[0]) if !g.Available() { - return nil, errInvalidCiphersuite + return nil, fmt.Errorf(errFmt, errDecodeCommitmentListPrefix, internal.ErrInvalidCiphersuite) } n := int(binary.LittleEndian.Uint16(data[1:3])) @@ -153,7 +153,7 @@ func DecodeList(data []byte) (CommitmentList, error) { size := 1 + 2 + n*comLength if len(data) != size { - return nil, errInvalidLength + return nil, fmt.Errorf(errFmt, errDecodeCommitmentListPrefix, internal.ErrInvalidLength) } c := make(CommitmentList, 0, n) @@ -161,7 +161,7 @@ func DecodeList(data []byte) (CommitmentList, error) { for offset := 3; offset < len(data); offset += comLength { com := new(Commitment) if err := com.Decode(data[offset : offset+comLength]); err != nil { - return nil, fmt.Errorf("invalid encoding of commitment: %w", err) + return nil, fmt.Errorf("%w: invalid encoding of commitment: %w", errDecodeCommitmentListPrefix, err) } c = append(c, com) diff --git a/coordinator.go b/coordinator.go index f0c8588..ad7008b 100644 --- a/coordinator.go +++ b/coordinator.go @@ -42,7 +42,7 @@ func (s *Signature) Clear() { // 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 signer from the set of allowed participants in future runs of FROST. If verify // is set to true, AggregateSignatures will automatically verify the signature shares, and will return an error on the -// first encountered invalid signature share +// first encountered invalid signature share. func (c *Configuration) AggregateSignatures( message []byte, sigShares []*SignatureShare, diff --git a/encoding.go b/encoding.go index 8b7c420..1a981d1 100644 --- a/encoding.go +++ b/encoding.go @@ -14,10 +14,12 @@ import ( "encoding/json" "errors" "fmt" + "github.com/bytemare/ecc" "github.com/bytemare/ecc/encoding" - "github.com/bytemare/frost/internal" "github.com/bytemare/secret-sharing/keys" + + "github.com/bytemare/frost/internal" ) const ( @@ -44,6 +46,7 @@ var ( errDecodeCommitmentPrefix = errors.New("failed to decode Commitment") errDecodeSignatureSharePrefix = errors.New("failed to decode SignatureShare") errDecodeSignaturePrefix = errors.New("failed to decode Signature") + errDecodeCommitmentListPrefix = errors.New("failed to decode CommitmentList") errDecodeProofR = errors.New("invalid encoding of R proof") errDecodeProofZ = errors.New("invalid encoding of z proof") diff --git a/signer.go b/signer.go index d9e4d1c..bda5922 100644 --- a/signer.go +++ b/signer.go @@ -78,9 +78,18 @@ func (s *Signer) Identifier() uint16 { return s.KeyShare.ID } +func (s *Signer) generateNonce(secret *ecc.Scalar, random []byte) *ecc.Scalar { + if random == nil { + random = internal.RandomBytes(32) + } + + return internal.H3(s.Configuration.group, internal.Concatenate(random, secret.Encode())) +} + func randomCommitmentID() uint64 { buf := make([]byte, 8) + // In the extremely rare and unlikely case the CSPRNG returns, panic. It's over. if _, err := rand.Read(buf); err != nil { panic(fmt.Errorf("FATAL: %w", err)) } @@ -88,14 +97,6 @@ func randomCommitmentID() uint64 { return binary.LittleEndian.Uint64(buf) } -func (s *Signer) generateNonce(secret *ecc.Scalar, random []byte) *ecc.Scalar { - if random == nil { - random = internal.RandomBytes(32) - } - - return internal.H3(s.Configuration.group, internal.Concatenate(random, secret.Encode())) -} - func (s *Signer) genNonceID() uint64 { var cid uint64 diff --git a/tests/encoding_test.go b/tests/encoding_test.go index a005bce..2885a1a 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -12,6 +12,7 @@ import ( "bytes" "encoding/json" "errors" + "fmt" "slices" "strings" "testing" @@ -505,7 +506,7 @@ func TestEncoding_Configuration_InvalidPublicKeyShares(t *testing.T) { }) } -func TestEncoding_Configuration_CantVerify_InvalidPubKey(t *testing.T) { +func TestEncoding_Configuration_CantVerify_InvalidGroupPublicKey(t *testing.T) { expectedErrorPrefix := "failed to decode Configuration: invalid group public key, the key is the group generator (base element)" testAll(t, func(t *testing.T, test *tableTest) { @@ -520,6 +521,22 @@ func TestEncoding_Configuration_CantVerify_InvalidPubKey(t *testing.T) { }) } +func TestEncoding_Configuration_BadHex(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + configuration := makeConf(t, test) + testDecodingHexFails(t, configuration, new(frost.Configuration), "failed to decode Configuration:") + }) +} + +func TestEncoding_Configuration_BadJSON(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + configuration := makeConf(t, test) + errInvalidJSON := "failed to decode Configuration: failed to decode PublicKeyShare: invalid JSON encoding" + testDecodingJSONFails(t, "failed to decode Configuration", + errInvalidJSON, configuration, new(frost.Configuration)) + }) +} + func TestEncoding_Signer(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[1] @@ -771,6 +788,22 @@ func TestEncoding_Signer_InvalidCommitment(t *testing.T) { }) } +func TestEncoding_Signer_BadHex(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + testDecodingHexFails(t, s, new(frost.Signer), "failed to decode Signer:") + }) +} + +func TestEncoding_Signer_BadJSON(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + s := makeSigners(t, test)[0] + errInvalidJSON := "failed to decode Signer: failed to decode KeyShare: invalid JSON encoding" + testDecodingJSONFails(t, "failed to decode Signer", + errInvalidJSON, s, new(frost.Signer)) + }) +} + func TestEncoding_SignatureShare(t *testing.T) { message := []byte("message") @@ -863,6 +896,46 @@ func TestEncoding_SignatureShare_InvalidShare(t *testing.T) { }) } +func TestEncoding_SignatureShare_BadHex(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(frost.CommitmentList, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + s := signers[0] + + sigShare, err := s.Sign([]byte("message"), coms) + if err != nil { + t.Fatal(err) + } + + testDecodingHexFails(t, sigShare, new(frost.SignatureShare), "failed to decode SignatureShare:") + }) +} + +func TestEncoding_SignatureShare_BadJSON(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signers := makeSigners(t, test) + coms := make(frost.CommitmentList, len(signers)) + for i, s := range signers { + coms[i] = s.Commit() + } + + s := signers[0] + + sigShare, err := s.Sign([]byte("message"), coms) + if err != nil { + t.Fatal(err) + } + + errInvalidJSON := "failed to decode SignatureShare: invalid JSON encoding" + testDecodingJSONFails(t, "failed to decode SignatureShare", + errInvalidJSON, sigShare, new(frost.SignatureShare)) + }) +} + func TestEncoding_Signature(t *testing.T) { message := []byte("message") @@ -951,6 +1024,32 @@ func TestEncoding_Signature_InvalidZ(t *testing.T) { }) } +func TestEncoding_Signature_BadHex(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + key := test.Group().NewScalar().Random() + signature, err := debug.Sign(test.Ciphersuite, []byte("message"), key) + if err != nil { + t.Fatal(err) + } + + testDecodingHexFails(t, signature, new(frost.Signature), "failed to decode Signature:") + }) +} + +func TestEncoding_Signature_BadJSON(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + key := test.Group().NewScalar().Random() + signature, err := debug.Sign(test.Ciphersuite, []byte("message"), key) + if err != nil { + t.Fatal(err) + } + + errInvalidJSON := "failed to decode Signature: invalid JSON encoding" + testDecodingJSONFails(t, "failed to decode Signature", + errInvalidJSON, signature, new(frost.Signature)) + }) +} + func TestEncoding_Commitment(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signer := makeSigners(t, test)[0] @@ -1059,6 +1158,26 @@ func TestEncoding_Commitment_InvalidBindingNonce(t *testing.T) { }) } +func TestEncoding_Commitment_BadHex(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + + testDecodingHexFails(t, com, new(frost.Commitment), "failed to decode Commitment:") + }) +} + +func TestEncoding_Commitment_BadJSON(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + + errInvalidJSON := "failed to decode Commitment: invalid JSON encoding" + testDecodingJSONFails(t, "failed to decode Commitment", + errInvalidJSON, com, new(frost.Commitment)) + }) +} + func TestEncoding_CommitmentList(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) @@ -1111,6 +1230,15 @@ func TestEncoding_CommitmentList_InvalidCiphersuite(t *testing.T) { }) } +func TestEncoding_CommitmentList_InvalidLength_Short(t *testing.T) { + expectedErrorPrefix := "failed to decode CommitmentList: " + internal.ErrInvalidLength.Error() + + if _, err := frost.DecodeList([]byte{0, 0}); err == nil || + !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) + } +} + func TestEncoding_CommitmentList_InvalidLength1(t *testing.T) { expectedErrorPrefix := internal.ErrInvalidLength.Error() @@ -1244,3 +1372,130 @@ func testAndCompareSerde( testAndCompareSerdeSimple(t, in, maker, expectedMatch, testHexEncoding, compare) testAndCompareSerdeSimple(t, in, maker, expectedMatch, testJSONEncoding, compare) } + +func testDecodingHexFails(t *testing.T, thing1, thing2 serde, expectedErrorPrefix string) { + // empty string + if err := thing2.DecodeHex(""); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatal("expected error on empty string") + } + + // uneven length + e := thing1.Hex() + if err := thing2.DecodeHex(e[:len(e)-1]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { + t.Fatal("expected error on empty string") + } + + // malformed string + hexed := thing1.Hex() + malformed := []rune(hexed) + malformed[0] = []rune("_")[0] + + expectedError := expectedErrorPrefix + " encoding/hex: invalid byte: U+005F '_'" + + if err := thing2.DecodeHex(string(malformed)); err == nil { + t.Fatal("expected error on malformed string") + } else if err.Error() != expectedError { + t.Fatalf("unexpected error: want %q, got %q", expectedError, err) + } +} + +type jsonTesterBaddie struct { + key, value, expectedError string +} + +func testJSONBaddie(in any, decoded json.Unmarshaler, baddie jsonTesterBaddie) error { + data, err := json.Marshal(in) + if err != nil { + return err + } + + data = replaceStringInBytes(data, baddie.key, baddie.value) + + err = json.Unmarshal(data, decoded) + + if len(baddie.expectedError) != 0 { // we're expecting an error + if err == nil || + !strings.HasPrefix(err.Error(), baddie.expectedError) { + return fmt.Errorf("expected error %q, got %q", baddie.expectedError, err) + } + } else { + if err != nil { + return fmt.Errorf("unexpected error %q", err) + } + } + + return nil +} + +func testDecodingJSONFails( + t *testing.T, + errPrefix, badJSONErr string, + in any, + decoded json.Unmarshaler, + baddies ...jsonTesterBaddie, +) { + errInvalidCiphersuite := errPrefix + ": invalid group" + + // JSON: bad json + baddie := jsonTesterBaddie{ + key: "\"group\"", + value: "bad", + expectedError: "invalid character 'b' looking for beginning of object key string", + } + + if err := testJSONBaddie(in, decoded, baddie); err != nil { + t.Fatal(err) + } + + // UnmarshallJSON: bad group + baddie = jsonTesterBaddie{ + key: "\"group\"", + value: "\"group\":2, \"oldGroup\"", + expectedError: errInvalidCiphersuite, + } + + if err := testJSONBaddie(in, decoded, baddie); err != nil { + t.Fatal(err) + } + + // UnmarshallJSON: bad ciphersuite + baddie = jsonTesterBaddie{ + key: "\"group\"", + value: "\"group\":70, \"oldGroup\"", + expectedError: errInvalidCiphersuite, + } + + if err := testJSONBaddie(in, decoded, baddie); err != nil { + t.Fatal(err) + } + + // UnmarshallJSON: bad ciphersuite + baddie = jsonTesterBaddie{ + key: "\"group\"", + value: "\"group\":-1, \"oldGroup\"", + expectedError: badJSONErr, + } + + if err := testJSONBaddie(in, decoded, baddie); err != nil { + t.Fatal(err) + } + + // UnmarshallJSON: bad ciphersuite + overflow := "9223372036854775808" // MaxInt64 + 1 + baddie = jsonTesterBaddie{ + key: "\"group\"", + value: "\"group\":" + overflow + ", \"oldGroup\"", + expectedError: errPrefix + ": failed to read Group: strconv.Atoi: parsing \"9223372036854775808\": value out of range", + } + + if err := testJSONBaddie(in, decoded, baddie); err != nil { + t.Fatal(err) + } + + // Replace keys and values + for _, baddie = range baddies { + if err := testJSONBaddie(in, decoded, baddie); err != nil { + t.Fatal(err) + } + } +} diff --git a/tests/frost_test.go b/tests/frost_test.go index e40464d..79a50ff 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -130,6 +130,16 @@ func runFrost( if err = frost.VerifySignature(test.Ciphersuite, message, singleSig, groupPublicKey); err != nil { t.Fatal(err) } + + singleSig.Clear() + + if !singleSig.R.IsIdentity() { + t.Fatal("expected identity") + } + + if !singleSig.Z.IsZero() { + t.Fatal("expected 0") + } } func TestFrost_WithTrustedDealer(t *testing.T) { From 4f1004ec2bf1e9ba1ee23897bfd8bcf15091bbba Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:06:05 +0200 Subject: [PATCH 30/31] fix new error messages Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- tests/encoding_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/encoding_test.go b/tests/encoding_test.go index 2885a1a..df9d6a2 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -1211,7 +1211,7 @@ func TestEncoding_CommitmentList_Empty(t *testing.T) { } func TestEncoding_CommitmentList_InvalidCiphersuite(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidCiphersuite.Error() + expectedErrorPrefix := "failed to decode CommitmentList: " + internal.ErrInvalidCiphersuite.Error() testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) @@ -1240,7 +1240,7 @@ func TestEncoding_CommitmentList_InvalidLength_Short(t *testing.T) { } func TestEncoding_CommitmentList_InvalidLength1(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidLength.Error() + expectedErrorPrefix := "failed to decode CommitmentList: " + internal.ErrInvalidLength.Error() testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) @@ -1259,7 +1259,7 @@ func TestEncoding_CommitmentList_InvalidLength1(t *testing.T) { } func TestEncoding_CommitmentList_InvalidLength2(t *testing.T) { - expectedErrorPrefix := internal.ErrInvalidLength.Error() + expectedErrorPrefix := "failed to decode CommitmentList: " + internal.ErrInvalidLength.Error() testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) @@ -1278,7 +1278,7 @@ func TestEncoding_CommitmentList_InvalidLength2(t *testing.T) { } func TestEncoding_CommitmentList_InvalidCommitment(t *testing.T) { - expectedErrorPrefix := "invalid encoding of commitment: failed to decode Commitment: " + internal.ErrInvalidCiphersuite.Error() + expectedErrorPrefix := "failed to decode CommitmentList: invalid encoding of commitment: failed to decode Commitment: " + internal.ErrInvalidCiphersuite.Error() testAll(t, func(t *testing.T, test *tableTest) { signers := makeSigners(t, test) From 8050e3a2809976d8c71362f6b38d069ffb3d76d4 Mon Sep 17 00:00:00 2001 From: bytemare <3641580+bytemare@users.noreply.github.com> Date: Tue, 8 Oct 2024 02:14:33 +0200 Subject: [PATCH 31/31] rename groupublickey to verificationkey, add tests Signed-off-by: bytemare <3641580+bytemare@users.noreply.github.com> --- README.md | 2 +- coordinator.go | 2 +- debug/debug.go | 4 +- encoding.go | 22 +++++----- examples_test.go | 42 ++++++++++++++----- frost.go | 18 ++++----- go.mod | 4 +- go.sum | 8 ++-- internal/lambda.go | 11 ++++- signer.go | 2 +- tests/commitment_test.go | 4 +- tests/configuration_test.go | 74 ++++++++++++++++----------------- tests/dkg_test.go | 4 +- tests/encoding_test.go | 81 +++++++++++++++++++++---------------- tests/frost_error_test.go | 4 +- tests/frost_test.go | 19 +++++---- tests/misc_test.go | 57 +++++++++++++++++++------- tests/vector_utils_test.go | 14 +++---- tests/vectors_test.go | 8 ++-- 19 files changed, 227 insertions(+), 153 deletions(-) diff --git a/README.md b/README.md index a61d87c..7ac3a99 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ configuration := &frost.Configuration{ Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } diff --git a/coordinator.go b/coordinator.go index ad7008b..72acc7e 100644 --- a/coordinator.go +++ b/coordinator.go @@ -128,7 +128,7 @@ func (c *Configuration) prepareSignatureShareVerification(message []byte, return nil, nil, nil, fmt.Errorf("invalid list of commitments: %w", err) } - groupCommitment, bindingFactors := commitments.groupCommitmentAndBindingFactors(c.GroupPublicKey, message) + groupCommitment, bindingFactors := commitments.groupCommitmentAndBindingFactors(c.VerificationKey, message) participants := commitments.ParticipantsScalar() return groupCommitment, bindingFactors, participants, nil diff --git a/debug/debug.go b/debug/debug.go index c1b429d..ce9a8bd 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -55,8 +55,8 @@ func TrustedDealerKeygen( shares := make([]*keys.KeyShare, maxSigners) for i, k := range privateKeyShares { shares[i] = &keys.KeyShare{ - Secret: k.Secret, - GroupPublicKey: coms[0], + Secret: k.Secret, + VerificationKey: coms[0], PublicKeyShare: keys.PublicKeyShare{ PublicKey: g.Base().Multiply(k.Secret), VssCommitment: coms, diff --git a/encoding.go b/encoding.go index 1a981d1..83f9ba4 100644 --- a/encoding.go +++ b/encoding.go @@ -102,7 +102,7 @@ func (c *Configuration) Encode() []byte { binary.LittleEndian.PutUint16(out[3:5], c.MaxSigners) binary.LittleEndian.PutUint16(out[5:7], uint16(len(c.SignerPublicKeyShares))) - out = append(out, c.GroupPublicKey.Encode()...) + out = append(out, c.VerificationKey.Encode()...) for _, pk := range c.SignerPublicKeyShares { out = append(out, pk.Encode()...) @@ -165,7 +165,7 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { Ciphersuite: Ciphersuite(header.g), Threshold: uint16(header.t), MaxSigners: uint16(header.n), - GroupPublicKey: gpk, + VerificationKey: gpk, SignerPublicKeyShares: pks, group: header.g, verified: false, @@ -198,7 +198,7 @@ func (c *Configuration) decode(header *confHeader, data []byte) error { c.Ciphersuite = conf.Ciphersuite c.Threshold = conf.Threshold c.MaxSigners = conf.MaxSigners - c.GroupPublicKey = gpk + c.VerificationKey = gpk c.SignerPublicKeyShares = pks c.group = ecc.Group(conf.Ciphersuite) c.verified = true @@ -364,9 +364,9 @@ func (s *Signer) Decode(data []byte) error { nLambdas := int(binary.LittleEndian.Uint16(data[header.length+4 : header.length+6])) g := conf.group _, nLen := encodedLength(encNonceCommitment, g) - _, lLem := encodedLength(encLambda, g) + _, llen := encodedLength(encLambda, g) - _, length := encodedLength(encSigner, g, header.length, ksLen, nCommitments*nLen, nLambdas*lLem) + _, length := encodedLength(encSigner, g, header.length, ksLen, nCommitments*nLen, nLambdas*llen) if len(data) != length { return fmt.Errorf(errFmt, errDecodeSignerPrefix, errInvalidLength) } @@ -383,9 +383,9 @@ func (s *Signer) Decode(data []byte) error { } offset += ksLen - stop := offset + nLambdas*lLem + stop := offset + nLambdas*llen - lambdaRegistry := make(internal.LambdaRegistry, lLem) + lambdaRegistry := make(internal.LambdaRegistry, llen) if err = lambdaRegistry.Decode(g, data[offset:stop]); err != nil { return fmt.Errorf("%w: failed to decode lambda registry in signer: %w", errDecodeSignerPrefix, err) } @@ -692,15 +692,15 @@ type shadowInit interface { type configurationShadow Configuration func (c *configurationShadow) init(g ecc.Group) { - c.GroupPublicKey = g.NewElement() + c.VerificationKey = g.NewElement() } type signerShadow Signer func (s *signerShadow) init(g ecc.Group) { s.KeyShare = &keys.KeyShare{ - Secret: g.NewScalar(), - GroupPublicKey: g.NewElement(), + Secret: g.NewScalar(), + VerificationKey: g.NewElement(), PublicKeyShare: keys.PublicKeyShare{ PublicKey: g.NewElement(), VssCommitment: nil, @@ -709,7 +709,7 @@ func (s *signerShadow) init(g ecc.Group) { }, } s.Configuration = &Configuration{ - GroupPublicKey: g.NewElement(), + VerificationKey: g.NewElement(), SignerPublicKeyShares: nil, Threshold: 0, MaxSigners: 0, diff --git a/examples_test.go b/examples_test.go index 5477f0d..75622ad 100644 --- a/examples_test.go +++ b/examples_test.go @@ -14,6 +14,7 @@ import ( "fmt" "strings" + "github.com/bytemare/ecc" "github.com/bytemare/secret-sharing/keys" "github.com/bytemare/frost" @@ -31,7 +32,7 @@ func Example_signer() { // and their signing share. // This example uses a centralised trusted dealer, but it is strongly recommended to use distributed key generation, // e.g. from github.com/bytemare/dkg, which is compatible with FROST. - secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + secretKeyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) // Since we used a centralised key generation, we only take the first key share for our participant. participantSecretKeyShare := secretKeyShares[0] @@ -50,7 +51,7 @@ func Example_signer() { Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -123,7 +124,7 @@ func Example_coordinator() { // We assume you already have a pool of participants with distinct non-zero identifiers and their signing share. // The following block uses a centralised trusted dealer to do this, but it is strongly recommended to use // distributed key generation, e.g. from github.com/bytemare/dkg, which is compatible with FROST. - secretKeyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + secretKeyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) participantSecretKeyShares := secretKeyShares[:threshold] participants := make([]*frost.Signer, threshold) @@ -139,7 +140,7 @@ func Example_coordinator() { Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -189,7 +190,7 @@ func Example_coordinator() { // Verify the signature and identify potential foul players. Note that since we set verify to true when calling // AggregateSignatures, the following is redundant. // Anyone can verify the signature given the ciphersuite parameter, message, and the group public key. - if err = frost.VerifySignature(ciphersuite, message, signature, groupPublicKey); err != nil { + if err = frost.VerifySignature(ciphersuite, message, signature, verificationKey); err != nil { // At this point one should try to identify which participant's signature share is invalid and act on it. // This verification is done as follows: for _, signatureShare := range signatureShares { @@ -216,13 +217,32 @@ func Example_coordinator() { // Example_key_generation shows how to create keys in a threshold setup with a centralized trusted dealer. // - a decentralised protocol described in the original FROST paper func Example_key_generation_centralised_trusted_dealer() { - panic(nil) + maxSigners := uint16(5) + threshold := uint16(3) + ciphersuite := frost.Default + + optionnalSecretKey := ciphersuite.Group().NewScalar().Random() + keyShares, verificationKey, vssCommitment := debug.TrustedDealerKeygen( + ciphersuite, + optionnalSecretKey, + threshold, + maxSigners, + ) + + fmt.Printf("Created %d key shares with %d vss commitments and %d verification key.", + len(keyShares), + len(vssCommitment), + len([]*ecc.Element{verificationKey}), // yes that line is ugly but it's pretext to use the variable produced. + ) + + // Output: Created 5 key shares with 3 vss commitments and 1 verification key. } // Example_key_generation shows how to create keys in a threshold setup with distributed key generation described in // the original FROST paper. func Example_key_generation_decentralised() { - panic(nil) + fmt.Println("Visit github.com/bytemare/dkg for an example and documentation.") + // Output: Visit github.com/bytemare/dkg for an example and documentation. } // Example_existing_keys shows how to import existing keys in their canonical byte encoding. @@ -361,7 +381,7 @@ func Example_key_deserialization() { // Example_deserialize shows how to encode and decode a FROST messages. func Example_deserialize() { - groupPublicKeyHex := "74144431f64b052a173c2505e4224a6cc5f3e81d587d4f23369e1b2b1fd0d427" + verificationKeyHex := "74144431f64b052a173c2505e4224a6cc5f3e81d587d4f23369e1b2b1fd0d427" publicKeySharesHex := []string{ "010100000000003c5ff80cd593a3b7e9007fdbc2b8fe6caee380e7d23eb7ba35160a5b7a51cb08", "0102000000000002db540a823f17b975d9eb206ccfbcf3a7667a0365ec1918fa2c3bb69acb105c", @@ -369,8 +389,8 @@ func Example_deserialize() { } g := frost.Default.Group() - groupPublicKey := g.NewElement() - if err := groupPublicKey.DecodeHex(groupPublicKeyHex); err != nil { + verificationKey := g.NewElement() + if err := verificationKey.DecodeHex(verificationKeyHex); err != nil { fmt.Println(err) } @@ -389,7 +409,7 @@ func Example_deserialize() { Ciphersuite: frost.Default, Threshold: 2, MaxSigners: 3, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } diff --git a/frost.go b/frost.go index 60283e7..416f683 100644 --- a/frost.go +++ b/frost.go @@ -71,7 +71,7 @@ func (c Ciphersuite) Group() ecc.Group { // Configuration holds the Configuration for a signing session. type Configuration struct { - GroupPublicKey *ecc.Element `json:"groupPublicKey"` + VerificationKey *ecc.Element `json:"verificationKey"` SignerPublicKeyShares []*keys.PublicKeyShare `json:"signerPublicKeyShares"` Threshold uint16 `json:"threshold"` MaxSigners uint16 `json:"maxSigners"` @@ -184,7 +184,7 @@ func (c *Configuration) ValidateKeyShare(keyShare *keys.KeyShare) error { return err } - if !c.GroupPublicKey.Equal(keyShare.GroupPublicKey) { + if !c.VerificationKey.Equal(keyShare.VerificationKey) { return errKeyShareNotMatch } @@ -277,7 +277,7 @@ func (c *Configuration) verifyConfiguration() error { return errInvalidMaxSignersOrder } - if err := c.validateGroupElement(c.GroupPublicKey); err != nil { + if err := c.validateGroupElement(c.VerificationKey); err != nil { return fmt.Errorf("invalid group public key, the key %w", err) } @@ -322,7 +322,7 @@ func (c *Configuration) validateGroupElement(e *ecc.Element) error { } func (c *Configuration) challenge(lambda *ecc.Scalar, message []byte, groupCommitment *ecc.Element) *ecc.Scalar { - chall := SchnorrChallenge(c.group, message, groupCommitment, c.GroupPublicKey) + chall := SchnorrChallenge(c.group, message, groupCommitment, c.VerificationKey) return chall.Multiply(lambda) } @@ -387,7 +387,7 @@ func NewPublicKeyShare(c Ciphersuite, id uint16, signerPublicKey []byte) (*keys. func NewKeyShare( c Ciphersuite, id uint16, - secretShare, signerPublicKey, groupPublicKey []byte, + secretShare, signerPublicKey, verificationKey []byte, ) (*keys.KeyShare, error) { pks, err := NewPublicKeyShare(c, id, signerPublicKey) if err != nil { @@ -407,13 +407,13 @@ func NewKeyShare( } gpk := g.NewElement() - if err = gpk.Decode(groupPublicKey); err != nil { + if err = gpk.Decode(verificationKey); err != nil { return nil, fmt.Errorf("could not decode the group public key: %w", err) } return &keys.KeyShare{ - Secret: s, - GroupPublicKey: gpk, - PublicKeyShare: *pks, + Secret: s, + VerificationKey: gpk, + PublicKeyShare: *pks, }, nil } diff --git a/go.mod b/go.mod index 3bc910d..7b401e0 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.23.1 require ( filippo.io/edwards25519 v1.1.0 - github.com/bytemare/dkg v0.0.0-20241004153610-04af7b423593 + github.com/bytemare/dkg v0.0.0-20241007182121-23ea4d549880 github.com/bytemare/ecc v0.8.2 github.com/bytemare/hash v0.3.0 - github.com/bytemare/secret-sharing v0.6.0 + github.com/bytemare/secret-sharing v0.7.0 github.com/gtank/ristretto255 v0.1.2 ) diff --git a/go.sum b/go.sum index c11d5ef..0a9b082 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/nistec v0.0.3 h1:h336Je2jRDZdBCLy2fLDUd9E2unG32JLwcJi0JQE9Cw= filippo.io/nistec v0.0.3/go.mod h1:84fxC9mi+MhC2AERXI4LSa8cmSVOzrFikg6hZ4IfCyw= -github.com/bytemare/dkg v0.0.0-20241004153610-04af7b423593 h1:pIVaRXwCFangFes/2adBWxQskwWbWU5DlgO/i1f0Q0w= -github.com/bytemare/dkg v0.0.0-20241004153610-04af7b423593/go.mod h1:uu0zK5IObiwEexMegqZe/wLyK+HZTstGnrz47ZdDkiI= +github.com/bytemare/dkg v0.0.0-20241007182121-23ea4d549880 h1:KoEDglTZoJx0EaWdmYkvdrPNxAr/Hkc1WgWvH2b/XCw= +github.com/bytemare/dkg v0.0.0-20241007182121-23ea4d549880/go.mod h1:szhmKyIBs11r5IPo/jGqwxfmnpELmbj8okgdKxA+QVs= github.com/bytemare/ecc v0.8.2 h1:MN+Ah48hApFpzJgIMa1xOrK7/R5uwCV06dtJyuHAi3Y= github.com/bytemare/ecc v0.8.2/go.mod h1:dvkSikSCejw8YaTdJs6lZSN4qz9B4PC5PtGq+CRDmHk= github.com/bytemare/hash v0.3.0 h1:RqFMt3mqpF7UxLdjBrsOZm/2cz0cQiAOnYc9gDLopWE= @@ -12,8 +12,8 @@ github.com/bytemare/hash2curve v0.3.0 h1:41Npcbc+u/E252A5aCMtxDcz7JPkkX1QzShneTF github.com/bytemare/hash2curve v0.3.0/go.mod h1:itj45U8uqvCtWC0eCswIHVHswXcEHkpFui7gfJdPSfQ= github.com/bytemare/secp256k1 v0.1.6 h1:5pOA84UBBTPTUmCkjtH6jHrbvZSh2kyxG0mW/OjSih0= github.com/bytemare/secp256k1 v0.1.6/go.mod h1:Zr7o3YCog5jKx5JwgYbj984gRIqVioTDZMSDo1y0zgE= -github.com/bytemare/secret-sharing v0.6.0 h1:/gQhsC3BY2pn7nIl+1sQDtI4c9IfkjuTbBXsvh922UM= -github.com/bytemare/secret-sharing v0.6.0/go.mod h1:CQ7ALe5CIbvnEGhcF50LKu9brAki7efQPT3d/UUhzQQ= +github.com/bytemare/secret-sharing v0.7.0 h1:ayJWEhwQzeChtavB4WrqufRJPnG5u2IePe1MEeJJEgs= +github.com/bytemare/secret-sharing v0.7.0/go.mod h1:Qzrf83Sk36D2NGJpk1/0H6YJx0SnsiOtrS6zaiISL2o= github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= diff --git a/internal/lambda.go b/internal/lambda.go index d9d87ea..70a8ac2 100644 --- a/internal/lambda.go +++ b/internal/lambda.go @@ -20,7 +20,7 @@ import ( ) // ComputeLambda derives the interpolating value for id in the polynomial made by the participant identifiers. -// This function assumes that: +// This function is not public to protect its usage, as the following conditions MUST be met. // - id is non-nil and != 0. // - every scalar in participants is non-nil and != 0. // - there are no duplicates in participants. @@ -43,8 +43,11 @@ func ComputeLambda(g ecc.Group, id uint16, participants []*ecc.Scalar) *ecc.Scal // A Lambda is the interpolating value for a given id in the polynomial made by the participant identifiers. type Lambda struct { + // Value is the actual Lambda value. Value *ecc.Scalar `json:"value"` - Group ecc.Group `json:"group"` + + // Group is necessary so the Value scalar can reliably be decoded in the right group. + Group ecc.Group `json:"group"` } type lambdaShadow Lambda @@ -110,6 +113,10 @@ func (l LambdaRegistry) Get(participants []uint16) *ecc.Scalar { // GetOrNew returns the recorded Lambda for the list of participants, or created, records, and returns a new one if // it wasn't found. +// This function assumes that: +// - id is non-nil and != 0. +// - every scalar in participants is non-nil and != 0. +// - there are no duplicates in participants. func (l LambdaRegistry) GetOrNew(g ecc.Group, id uint16, participants []uint16) *ecc.Scalar { lambda := l.Get(participants) if lambda == nil { diff --git a/signer.go b/signer.go index bda5922..82b45f6 100644 --- a/signer.go +++ b/signer.go @@ -184,7 +184,7 @@ func (s *Signer) Sign(message []byte, commitments CommitmentList) (*SignatureSha } groupCommitment, bindingFactors := commitments.groupCommitmentAndBindingFactors( - s.Configuration.GroupPublicKey, + s.Configuration.VerificationKey, message, ) diff --git a/tests/commitment_test.go b/tests/commitment_test.go index 32bb1a7..1053bec 100644 --- a/tests/commitment_test.go +++ b/tests/commitment_test.go @@ -28,7 +28,7 @@ func TestCommitment_Validate_InvalidConfiguration(t *testing.T) { Ciphersuite: tt.Ciphersuite, Threshold: tt.threshold, MaxSigners: tt.maxSigners, - GroupPublicKey: nil, + VerificationKey: nil, SignerPublicKeyShares: nil, } @@ -269,7 +269,7 @@ func TestCommitmentList_Validate_InvalidConfiguration(t *testing.T) { Ciphersuite: tt.Ciphersuite, Threshold: tt.threshold, MaxSigners: tt.maxSigners, - GroupPublicKey: nil, + VerificationKey: nil, SignerPublicKeyShares: nil, } diff --git a/tests/configuration_test.go b/tests/configuration_test.go index 4492481..9489c55 100644 --- a/tests/configuration_test.go +++ b/tests/configuration_test.go @@ -25,7 +25,7 @@ func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { expectedErrorPrefix := internal.ErrInvalidCiphersuite testAll(t, func(t *testing.T, test *tableTest) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + keyShares, verificationKey, _ := debug.TrustedDealerKeygen( test.Ciphersuite, nil, test.threshold, @@ -37,7 +37,7 @@ func TestConfiguration_Verify_InvalidCiphersuite(t *testing.T) { Ciphersuite: 2, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -51,7 +51,7 @@ func TestConfiguration_Verify_Threshold_0(t *testing.T) { expectedErrorPrefix := "threshold is 0 or higher than maxSigners" testAll(t, func(t *testing.T, test *tableTest) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + keyShares, verificationKey, _ := debug.TrustedDealerKeygen( test.Ciphersuite, nil, test.threshold, @@ -63,7 +63,7 @@ func TestConfiguration_Verify_Threshold_0(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: 0, MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -77,7 +77,7 @@ func TestConfiguration_Verify_Threshold_Max(t *testing.T) { expectedErrorPrefix := "threshold is 0 or higher than maxSigners" testAll(t, func(t *testing.T, test *tableTest) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + keyShares, verificationKey, _ := debug.TrustedDealerKeygen( test.Ciphersuite, nil, test.threshold, @@ -89,7 +89,7 @@ func TestConfiguration_Verify_Threshold_Max(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: test.maxSigners + 1, MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -99,7 +99,7 @@ func TestConfiguration_Verify_Threshold_Max(t *testing.T) { }) } -func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { +func TestConfiguration_Verify_verificationKey_Nil(t *testing.T) { expectedErrorPrefix := "invalid group public key, the key is nil" testAll(t, func(t *testing.T, test *tableTest) { @@ -110,7 +110,7 @@ func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: nil, + VerificationKey: nil, SignerPublicKeyShares: publicKeyShares, } @@ -120,7 +120,7 @@ func TestConfiguration_Verify_GroupPublicKey_Nil(t *testing.T) { }) } -func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { +func TestConfiguration_Verify_verificationKey_Identity(t *testing.T) { expectedErrorPrefix := "invalid group public key, the key is the identity element" testAll(t, func(t *testing.T, test *tableTest) { @@ -131,7 +131,7 @@ func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: test.Group().NewElement(), + VerificationKey: test.Group().NewElement(), SignerPublicKeyShares: publicKeyShares, } @@ -141,7 +141,7 @@ func TestConfiguration_Verify_GroupPublicKey_Identity(t *testing.T) { }) } -func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { +func TestConfiguration_Verify_verificationKey_Generator(t *testing.T) { expectedErrorPrefix := "invalid group public key, the key is the group generator (base element)" testAll(t, func(t *testing.T, test *tableTest) { @@ -152,7 +152,7 @@ func TestConfiguration_Verify_GroupPublicKey_Generator(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: test.Group().Base(), + VerificationKey: test.Group().Base(), SignerPublicKeyShares: publicKeyShares, } @@ -169,7 +169,7 @@ func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { threshold := uint16(2) maxSigners := uint16(3) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) // nil @@ -177,7 +177,7 @@ func TestConfiguration_VerifySignerPublicKeys_InvalidNumber(t *testing.T) { Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: nil, } @@ -214,7 +214,7 @@ func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { threshold := uint16(2) maxSigners := uint16(3) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) publicKeyShares[threshold-1] = nil @@ -222,7 +222,7 @@ func TestConfiguration_VerifySignerPublicKeys_Nil(t *testing.T) { Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -236,14 +236,14 @@ func TestConfiguration_VerifySignerPublicKeys_BadPublicKey(t *testing.T) { threshold := uint16(2) maxSigners := uint16(3) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -288,14 +288,14 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_Identifiers(t *testing.T threshold := uint16(2) maxSigners := uint16(3) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -315,14 +315,14 @@ func TestConfiguration_VerifySignerPublicKeys_Duplicate_PublicKeys(t *testing.T) threshold := uint16(2) maxSigners := uint16(3) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -346,7 +346,7 @@ func TestConfiguration_ValidatePublicKeyShare_InvalidConfiguration(t *testing.T) Ciphersuite: tt.Ciphersuite, Threshold: tt.threshold, MaxSigners: tt.maxSigners, - GroupPublicKey: nil, + VerificationKey: nil, SignerPublicKeyShares: nil, } @@ -457,7 +457,7 @@ func TestConfiguration_ValidateKeyShare_InvalidConfiguration(t *testing.T) { Ciphersuite: tt.Ciphersuite, Threshold: tt.threshold, MaxSigners: tt.maxSigners, - GroupPublicKey: nil, + VerificationKey: nil, SignerPublicKeyShares: nil, } @@ -480,7 +480,7 @@ func TestConfiguration_ValidateKeyShare_Nil(t *testing.T) { } } -func TestConfiguration_ValidateKeyShare_InvalidGroupPublicKey(t *testing.T) { +func TestConfiguration_ValidateKeyShare_InvalidverificationKey(t *testing.T) { expectedErrorPrefix := "the key share's group public key does not match the one in the configuration" tt := &tableTest{ Ciphersuite: frost.Ristretto255, @@ -490,17 +490,17 @@ func TestConfiguration_ValidateKeyShare_InvalidGroupPublicKey(t *testing.T) { configuration, keyShares := makeConfAndShares(t, tt) keyShare := keyShares[0] - keyShare.GroupPublicKey = nil + keyShare.VerificationKey = nil if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } - keyShare.GroupPublicKey = tt.Group().NewElement() + keyShare.VerificationKey = tt.Group().NewElement() if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } - keyShare.GroupPublicKey.Base() + keyShare.VerificationKey.Base() if err := configuration.ValidateKeyShare(keyShare); err == nil || err.Error() != expectedErrorPrefix { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -592,8 +592,8 @@ func TestConfiguration_ValidateKeyShare_WrongPublicKey(t *testing.T) { random := tt.Group().NewScalar().Random() keyShare := &keys.KeyShare{ - Secret: random, - GroupPublicKey: keyShares[0].GroupPublicKey, + Secret: random, + VerificationKey: keyShares[0].VerificationKey, PublicKeyShare: keys.PublicKeyShare{ PublicKey: tt.Group().Base().Multiply(random), ID: keyShares[0].ID, @@ -611,14 +611,14 @@ func TestConfiguration_Signer_NotVerified(t *testing.T) { threshold := uint16(2) maxSigners := uint16(3) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ Ciphersuite: ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -633,14 +633,14 @@ func TestConfiguration_Signer_BadConfig(t *testing.T) { threshold := uint16(2) maxSigners := uint16(3) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ Ciphersuite: 2, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -673,14 +673,14 @@ func TestConfiguration_VerifySignatureShare_BadPrep(t *testing.T) { threshold := uint16(2) maxSigners := uint16(3) - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(ciphersuite, nil, threshold, maxSigners) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ Ciphersuite: 2, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -923,7 +923,7 @@ func TestConfiguration_AggregateSignatures_InvalidConfiguration(t *testing.T) { Ciphersuite: tt.Ciphersuite, Threshold: tt.threshold, MaxSigners: tt.maxSigners, - GroupPublicKey: nil, + VerificationKey: nil, SignerPublicKeyShares: nil, } diff --git a/tests/dkg_test.go b/tests/dkg_test.go index d73e86a..1852db1 100644 --- a/tests/dkg_test.go +++ b/tests/dkg_test.go @@ -48,7 +48,7 @@ func runDKG( commitments[i] = r1[i].Commitment } - pubKey, err := dkg.GroupPublicKeyFromRound1(c, r1) + pubKey, err := dkg.VerificationKeyFromRound1(c, r1) if err != nil { t.Fatal(err) } @@ -88,7 +88,7 @@ func runDKG( // t.Fatal("expected validity") //} - if !keyShare.GroupPublicKey.Equal(pubKey) { + if !keyShare.VerificationKey.Equal(pubKey) { t.Fatal("expected same public key") } diff --git a/tests/encoding_test.go b/tests/encoding_test.go index df9d6a2..25ea68b 100644 --- a/tests/encoding_test.go +++ b/tests/encoding_test.go @@ -27,14 +27,14 @@ import ( ) func makeConfAndShares(t *testing.T, test *tableTest) (*frost.Configuration, []*keys.KeyShare) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, nil, test.threshold, test.maxSigners) publicKeyShares := getPublicKeyShares(keyShares) configuration := &frost.Configuration{ Ciphersuite: test.Ciphersuite, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -107,9 +107,9 @@ func compareConfigurations(t *testing.T, a, b serde, expectedMatch bool) { t.Fatalf("expected matching max signers: %q / %q", c1.MaxSigners, c2.MaxSigners) } - if ((c1.GroupPublicKey == nil || c2.GroupPublicKey == nil) || !c1.GroupPublicKey.Equal(c2.GroupPublicKey)) && + if ((c1.VerificationKey == nil || c2.VerificationKey == nil) || !c1.VerificationKey.Equal(c2.VerificationKey)) && expectedMatch { - t.Fatalf("expected matching GroupPublicKey: %q / %q", c1.Ciphersuite, c2.Ciphersuite) + t.Fatalf("expected matching VerificationKey: %q / %q", c1.Ciphersuite, c2.Ciphersuite) } if len(c1.SignerPublicKeyShares) != len(c2.SignerPublicKeyShares) && expectedMatch { @@ -189,11 +189,11 @@ func compareKeyShares(t *testing.T, a, b serde, expectedMatch bool) { t.Fatalf("Expected equality on Secret:\n\t%s\n\t%s\n", s1.Secret.Hex(), s2.Secret.Hex()) } - if !s1.GroupPublicKey.Equal(s2.GroupPublicKey) && expectedMatch { + if !s1.VerificationKey.Equal(s2.VerificationKey) && expectedMatch { t.Fatalf( - "Expected equality on GroupPublicKey:\n\t%s\n\t%s\n", - s1.GroupPublicKey.Hex(), - s2.GroupPublicKey.Hex(), + "Expected equality on VerificationKey:\n\t%s\n\t%s\n", + s1.VerificationKey.Hex(), + s2.VerificationKey.Hex(), ) } @@ -427,7 +427,7 @@ func TestEncoding_Configuration_InvalidConfigEncoding(t *testing.T) { } } -func TestEncoding_Configuration_InvalidGroupPublicKey(t *testing.T) { +func TestEncoding_Configuration_InvalidVerificationKey(t *testing.T) { expectedErrorPrefix := "failed to decode Configuration: could not decode group public key: element Decode: " testAll(t, func(t *testing.T, test *tableTest) { @@ -448,7 +448,7 @@ func TestEncoding_Configuration_BadPublicKeyShare(t *testing.T) { expectedErrorPrefix := "failed to decode Configuration: could not decode signer public key share for signer 1: " testAll(t, func(t *testing.T, test *tableTest) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + keyShares, verificationKey, _ := debug.TrustedDealerKeygen( test.Ciphersuite, nil, test.threshold, @@ -460,7 +460,7 @@ func TestEncoding_Configuration_BadPublicKeyShare(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } g := ecc.Group(test.Ciphersuite) @@ -481,7 +481,7 @@ func TestEncoding_Configuration_InvalidPublicKeyShares(t *testing.T) { expectedErrorPrefix := "failed to decode Configuration: invalid number of public keys (lower than threshold or above maximum)" testAll(t, func(t *testing.T, test *tableTest) { - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen( + keyShares, verificationKey, _ := debug.TrustedDealerKeygen( test.Ciphersuite, nil, test.threshold, @@ -493,7 +493,7 @@ func TestEncoding_Configuration_InvalidPublicKeyShares(t *testing.T) { Ciphersuite: test.Ciphersuite, Threshold: test.threshold, MaxSigners: test.maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } configuration.SignerPublicKeyShares = configuration.SignerPublicKeyShares[:test.threshold-1] @@ -506,12 +506,12 @@ func TestEncoding_Configuration_InvalidPublicKeyShares(t *testing.T) { }) } -func TestEncoding_Configuration_CantVerify_InvalidGroupPublicKey(t *testing.T) { +func TestEncoding_Configuration_CantVerify_InvalidVerificationKey(t *testing.T) { expectedErrorPrefix := "failed to decode Configuration: invalid group public key, the key is the group generator (base element)" testAll(t, func(t *testing.T, test *tableTest) { configuration := makeConf(t, test) - configuration.GroupPublicKey.Base() + configuration.VerificationKey.Base() encoded := configuration.Encode() decoded := new(frost.Configuration) @@ -524,7 +524,7 @@ func TestEncoding_Configuration_CantVerify_InvalidGroupPublicKey(t *testing.T) { func TestEncoding_Configuration_BadHex(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { configuration := makeConf(t, test) - testDecodingHexFails(t, configuration, new(frost.Configuration), "failed to decode Configuration:") + testDecodeHexFails(t, configuration, new(frost.Configuration), "failed to decode Configuration:") }) } @@ -534,6 +534,17 @@ func TestEncoding_Configuration_BadJSON(t *testing.T) { errInvalidJSON := "failed to decode Configuration: failed to decode PublicKeyShare: invalid JSON encoding" testDecodingJSONFails(t, "failed to decode Configuration", errInvalidJSON, configuration, new(frost.Configuration)) + + configuration.SignerPublicKeyShares[1].PublicKey.Base() + j, err := json.Marshal(configuration) + if err != nil { + t.Fatal(err) + } + + expectedError := "failed to decode Configuration: invalid public key for participant 2, the key is the group generator (base element)" + if err = json.Unmarshal(j, new(frost.Configuration)); err == nil || err.Error() != expectedError { + t.Fatalf("expected %q, got %q", errInvalidJSON, err) + } }) } @@ -791,7 +802,7 @@ func TestEncoding_Signer_InvalidCommitment(t *testing.T) { func TestEncoding_Signer_BadHex(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { s := makeSigners(t, test)[0] - testDecodingHexFails(t, s, new(frost.Signer), "failed to decode Signer:") + testDecodeHexFails(t, s, new(frost.Signer), "failed to decode Signer:") }) } @@ -804,6 +815,18 @@ func TestEncoding_Signer_BadJSON(t *testing.T) { }) } +func TestEncoding_Nonce_BadJSON(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + signer := makeSigners(t, test)[0] + com := signer.Commit() + nonce := signer.NonceCommitments[com.CommitmentID] + + errInvalidJSON := "failed to decode Commitment: invalid JSON encoding" + testDecodingJSONFails(t, "failed to decode Commitment", + errInvalidJSON, nonce, new(frost.Nonce)) + }) +} + func TestEncoding_SignatureShare(t *testing.T) { message := []byte("message") @@ -857,7 +880,6 @@ func TestEncoding_SignatureShare_InvalidLength2(t *testing.T) { } func TestEncoding_SignatureShare_InvalidIdentifier(t *testing.T) { - // todo: check for zero id in all decodings expectedError := errors.New("failed to decode SignatureShare: identifier cannot be 0") encoded := make([]byte, 35) encoded[0] = 1 @@ -911,7 +933,7 @@ func TestEncoding_SignatureShare_BadHex(t *testing.T) { t.Fatal(err) } - testDecodingHexFails(t, sigShare, new(frost.SignatureShare), "failed to decode SignatureShare:") + testDecodeHexFails(t, sigShare, new(frost.SignatureShare), "failed to decode SignatureShare:") }) } @@ -1032,7 +1054,7 @@ func TestEncoding_Signature_BadHex(t *testing.T) { t.Fatal(err) } - testDecodingHexFails(t, signature, new(frost.Signature), "failed to decode Signature:") + testDecodeHexFails(t, signature, new(frost.Signature), "failed to decode Signature:") }) } @@ -1163,7 +1185,7 @@ func TestEncoding_Commitment_BadHex(t *testing.T) { signer := makeSigners(t, test)[0] com := signer.Commit() - testDecodingHexFails(t, com, new(frost.Commitment), "failed to decode Commitment:") + testDecodeHexFails(t, com, new(frost.Commitment), "failed to decode Commitment:") }) } @@ -1337,8 +1359,6 @@ func testJSONEncoding(t *testing.T, in, out serde) error { return err } - t.Log(string(jsonEnc)) - if err = json.Unmarshal(jsonEnc, out); err != nil { return err } @@ -1373,25 +1393,26 @@ func testAndCompareSerde( testAndCompareSerdeSimple(t, in, maker, expectedMatch, testJSONEncoding, compare) } -func testDecodingHexFails(t *testing.T, thing1, thing2 serde, expectedErrorPrefix string) { +func testDecodeHexFails(t *testing.T, thing1, thing2 serde, expectedErrorPrefix string) { // empty string if err := thing2.DecodeHex(""); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatal("expected error on empty string") } // uneven length + expectedError := expectedErrorPrefix + " encoding/hex: odd length hex string" e := thing1.Hex() + if err := thing2.DecodeHex(e[:len(e)-1]); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatal("expected error on empty string") } // malformed string + expectedError = expectedErrorPrefix + " encoding/hex: invalid byte: U+005F '_'" hexed := thing1.Hex() malformed := []rune(hexed) malformed[0] = []rune("_")[0] - expectedError := expectedErrorPrefix + " encoding/hex: invalid byte: U+005F '_'" - if err := thing2.DecodeHex(string(malformed)); err == nil { t.Fatal("expected error on malformed string") } else if err.Error() != expectedError { @@ -1432,7 +1453,6 @@ func testDecodingJSONFails( errPrefix, badJSONErr string, in any, decoded json.Unmarshaler, - baddies ...jsonTesterBaddie, ) { errInvalidCiphersuite := errPrefix + ": invalid group" @@ -1491,11 +1511,4 @@ func testDecodingJSONFails( if err := testJSONBaddie(in, decoded, baddie); err != nil { t.Fatal(err) } - - // Replace keys and values - for _, baddie = range baddies { - if err := testJSONBaddie(in, decoded, baddie); err != nil { - t.Fatal(err) - } - } } diff --git a/tests/frost_error_test.go b/tests/frost_error_test.go index b639f3e..257e2b2 100644 --- a/tests/frost_error_test.go +++ b/tests/frost_error_test.go @@ -43,7 +43,7 @@ func TestVerifySignature_InvalidSignature(t *testing.T) { Z: test.Group().NewScalar().Random(), } - if err := frost.VerifySignature(test.Ciphersuite, message, signature, configuration.GroupPublicKey); err == nil || + if err := frost.VerifySignature(test.Ciphersuite, message, signature, configuration.VerificationKey); err == nil || !strings.HasPrefix(err.Error(), expectedErrorPrefix) { t.Fatalf("expected %q, got %q", expectedErrorPrefix, err) } @@ -133,7 +133,7 @@ func TestFrost_NewKeyShare(t *testing.T) { keyShare := keyShares[0] newKeyShare, err := frost.NewKeyShare(configuration.Ciphersuite, keyShare.ID, keyShare.SecretKey().Encode(), - keyShare.PublicKey.Encode(), configuration.GroupPublicKey.Encode()) + keyShare.PublicKey.Encode(), configuration.VerificationKey.Encode()) if err != nil { t.Fatal(err) } diff --git a/tests/frost_test.go b/tests/frost_test.go index 79a50ff..a3a6c80 100644 --- a/tests/frost_test.go +++ b/tests/frost_test.go @@ -63,7 +63,7 @@ func runFrost( threshold, maxSigners uint16, message []byte, keyShares []*keys.KeyShare, - groupPublicKey *ecc.Element, + verificationKey *ecc.Element, ) { // Collect public keys. publicKeyShares := getPublicKeyShares(keyShares) @@ -73,7 +73,7 @@ func runFrost( Ciphersuite: test.Ciphersuite, Threshold: threshold, MaxSigners: maxSigners, - GroupPublicKey: groupPublicKey, + VerificationKey: verificationKey, SignerPublicKeyShares: publicKeyShares, } @@ -127,7 +127,7 @@ func runFrost( t.Fatal(err) } - if err = frost.VerifySignature(test.Ciphersuite, message, singleSig, groupPublicKey); err != nil { + if err = frost.VerifySignature(test.Ciphersuite, message, singleSig, verificationKey); err != nil { t.Fatal(err) } @@ -148,8 +148,13 @@ func TestFrost_WithTrustedDealer(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { g := test.Ciphersuite.Group() sk := g.NewScalar().Random() - keyShares, groupPublicKey, _ := debug.TrustedDealerKeygen(test.Ciphersuite, sk, test.threshold, test.maxSigners) - runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, groupPublicKey) + keyShares, verificationKey, _ := debug.TrustedDealerKeygen( + test.Ciphersuite, + sk, + test.threshold, + test.maxSigners, + ) + runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, verificationKey) }) } @@ -158,8 +163,8 @@ func TestFrost_WithDKG(t *testing.T) { testAll(t, func(t *testing.T, test *tableTest) { g := test.Ciphersuite.Group() - keyShares, groupPublicKey, _ := runDKG(t, g, test.threshold, test.maxSigners) - runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, groupPublicKey) + keyShares, verificationKey, _ := runDKG(t, g, test.threshold, test.maxSigners) + runFrost(t, test, test.threshold, test.maxSigners, message, keyShares, verificationKey) }) } diff --git a/tests/misc_test.go b/tests/misc_test.go index 8ed7bac..9b0473b 100644 --- a/tests/misc_test.go +++ b/tests/misc_test.go @@ -38,7 +38,7 @@ func verifyTrustedDealerKeygen( t.Fatal(err) } - groupPublicKey, participantPublicKeys, err := debug.RecoverPublicKeys( + verificationKey, participantPublicKeys, err := debug.RecoverPublicKeys( test.Ciphersuite, test.maxSigners, coms, @@ -51,7 +51,7 @@ func verifyTrustedDealerKeygen( t.Fatal() } - if !groupPublicKey.Equal(pk) { + if !verificationKey.Equal(pk) { t.Fatal() } @@ -68,7 +68,7 @@ func verifyTrustedDealerKeygen( t.Fatal(err) } - if err = frost.VerifySignature(test.Ciphersuite, []byte("message"), sig, groupPublicKey); err != nil { + if err = frost.VerifySignature(test.Ciphersuite, []byte("message"), sig, verificationKey); err != nil { t.Fatal(err) } } @@ -257,7 +257,7 @@ func TestRecoverPublicKeys(t *testing.T) { test.maxSigners, ) - groupPublicKey, participantPublicKeys, err := debug.RecoverPublicKeys( + verificationKey, participantPublicKeys, err := debug.RecoverPublicKeys( test.Ciphersuite, test.maxSigners, secretsharingCommitment, @@ -266,7 +266,7 @@ func TestRecoverPublicKeys(t *testing.T) { t.Fatal(err) } - if !dealerGroupPubKey.Equal(groupPublicKey) { + if !dealerGroupPubKey.Equal(verificationKey) { t.Fatal("expected equality") } @@ -374,15 +374,44 @@ func TestPublicKeyShareVerificationFail(t *testing.T) { }) } -func TestLambda_BadID(t *testing.T) { - // expectedErrorPrefix := "anomaly in participant identifiers: one of the polynomial's coefficients is zero" - g := ecc.Ristretto255Sha512 - polynomial := []*ecc.Scalar{ - g.NewScalar().SetUInt64(1), - g.NewScalar().SetUInt64(2), - g.NewScalar().SetUInt64(3), +func runComputeLambda(g ecc.Group, id uint16, expectedValue *ecc.Scalar, participants ...int) *ecc.Scalar { + ps := make([]*ecc.Scalar, len(participants)) + for i, p := range participants { + ps[i] = g.NewScalar().SetUInt64(uint64(p)) } - // todo : what happens if the participant list is not vetted? - t.Log(internal.ComputeLambda(g, 4, polynomial).Hex()) + if s := internal.ComputeLambda(g, id, ps); !s.Equal(expectedValue) { + return s + } + + return nil +} + +func TestComputeLambda_BadID(t *testing.T) { + testAll(t, func(t *testing.T, test *tableTest) { + g := test.Group() + + // id is 0 + expected := g.NewScalar().SetUInt64(1) + if s := runComputeLambda(g, 0, expected, 1, 2, 3); s != nil { + t.Fatalf("expected %v, got %v", expected.Hex(), s.Hex()) + } + + // no participants + if s := runComputeLambda(g, 1, expected); s != nil { + t.Fatalf("expected %v, got %v", expected.Hex(), s.Hex()) + } + + // participants has 0 id + expected = g.NewScalar() + if s := runComputeLambda(g, 1, expected, 2, 0, 3); s != nil { + t.Fatalf("expected %v, got %v", expected.Hex(), s.Hex()) + } + + // participants has only 0 ids + expected = g.NewScalar() + if s := runComputeLambda(g, 1, expected, 0, 0, 0); s != nil { + t.Fatalf("expected %v, got %v", expected.Hex(), s.Hex()) + } + }) } diff --git a/tests/vector_utils_test.go b/tests/vector_utils_test.go index 3c3787f..4149777 100644 --- a/tests/vector_utils_test.go +++ b/tests/vector_utils_test.go @@ -85,7 +85,7 @@ func (j *ByteToHex) UnmarshalJSON(b []byte) error { type testVectorInput struct { ParticipantList []uint16 `json:"participant_list"` GroupSecretKey ByteToHex `json:"group_secret_key"` - GroupPublicKey ByteToHex `json:"group_public_key"` + VerificationKey ByteToHex `json:"group_public_key"` Message ByteToHex `json:"message"` SharePolynomialCoefficients []ByteToHex `json:"share_polynomial_coefficients"` ParticipantShares []testVectorParticipantShare `json:"participant_shares"` @@ -165,7 +165,7 @@ type testConfig struct { type testInput struct { ParticipantList []uint16 GroupSecretKey *ecc.Scalar - GroupPublicKey *ecc.Element + VerificationKey *ecc.Element Message []byte SharePolynomialCoefficients []*ecc.Scalar Participants []*keys.KeyShare @@ -208,7 +208,7 @@ func makeFrostConfig(c frost.Ciphersuite, threshold, maxSigners uint) *frost.Con Ciphersuite: c, Threshold: uint16(threshold), MaxSigners: uint16(maxSigners), - GroupPublicKey: nil, + VerificationKey: nil, SignerPublicKeyShares: nil, } } @@ -247,7 +247,7 @@ func decodeParticipant(t *testing.T, g ecc.Group, tp *testParticipant) *particip func (i testVectorInput) decode(t *testing.T, g ecc.Group) *testInput { input := &testInput{ GroupSecretKey: decodeScalar(t, g, i.GroupSecretKey), - GroupPublicKey: decodeElement(t, g, i.GroupPublicKey), + VerificationKey: decodeElement(t, g, i.VerificationKey), Message: i.Message, SharePolynomialCoefficients: make([]*ecc.Scalar, len(i.SharePolynomialCoefficients)+1), Participants: make([]*keys.KeyShare, len(i.ParticipantShares)), @@ -267,8 +267,8 @@ func (i testVectorInput) decode(t *testing.T, g ecc.Group) *testInput { secret := decodeScalar(t, g, p.ParticipantShare) public := g.Base().Multiply(secret) input.Participants[j] = &keys.KeyShare{ - Secret: secret, - GroupPublicKey: input.GroupPublicKey, + Secret: secret, + VerificationKey: input.VerificationKey, PublicKeyShare: keys.PublicKeyShare{ PublicKey: public, VssCommitment: nil, @@ -312,7 +312,7 @@ func (v testVector) decode(t *testing.T) *test { conf := v.Config.decode(t) inputs := v.Inputs.decode(t, conf.Ciphersuite.Group()) - conf.GroupPublicKey = inputs.GroupPublicKey + conf.VerificationKey = inputs.VerificationKey conf.SignerPublicKeyShares = make([]*keys.PublicKeyShare, len(inputs.Participants)) for i, ks := range inputs.Participants { diff --git a/tests/vectors_test.go b/tests/vectors_test.go index f84f9fa..fddf795 100644 --- a/tests/vectors_test.go +++ b/tests/vectors_test.go @@ -48,7 +48,7 @@ func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *ecc.Element) { t.Fatal() } - groupPublicKey, participantPublicKey, err := debug.RecoverPublicKeys( + verificationKey, participantPublicKey, err := debug.RecoverPublicKeys( v.Config.Ciphersuite, v.Config.Configuration.MaxSigners, secretsharingCommitment, @@ -61,7 +61,7 @@ func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *ecc.Element) { t.Fatal() } - if !groupPublicKey.Equal(dealerGroupPubKey) { + if !verificationKey.Equal(dealerGroupPubKey) { t.Fatal() } @@ -75,7 +75,7 @@ func (v test) testTrustedDealer(t *testing.T) ([]*keys.KeyShare, *ecc.Element) { } func (v test) test(t *testing.T) { - keyShares, groupPublicKey := v.testTrustedDealer(t) + keyShares, verificationKey := v.testTrustedDealer(t) // Check whether key shares are the same cpt := len(keyShares) @@ -177,7 +177,7 @@ func (v test) test(t *testing.T) { } // Sanity Check - if err = frost.VerifySignature(conf.Ciphersuite, v.Inputs.Message, sig, groupPublicKey); err != nil { + if err = frost.VerifySignature(conf.Ciphersuite, v.Inputs.Message, sig, verificationKey); err != nil { t.Fatal() } }