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