Documentation ¶
Overview ¶
Package frost implements FROST, the Flexible Round-Optimized Schnorr Threshold (FROST) signing protocol.
Example (Coordinator) ¶
Example_coordinator shows the execution steps of a FROST coordinator.
package main import ( "encoding/hex" "fmt" group "github.com/bytemare/crypto" "github.com/bytemare/frost" "github.com/bytemare/frost/dkg" ) var ( participantGeneratedInDKG *frost.Participant 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() { maximumAmountOfParticipants := 1 threshold := 1 configuration := frost.Ristretto255.Configuration() participantIdentifier := configuration.IDFromInt(1) dkgParticipant := dkg.NewParticipant( configuration.Ciphersuite, participantIdentifier, maximumAmountOfParticipants, threshold, ) round1Data := dkgParticipant.Init() if round1Data.SenderIdentifier.Equal(participantIdentifier) != 1 { panic("this is just a test, and it failed") } accumulatedRound1Data := make([]*dkg.Round1Data, 0, maximumAmountOfParticipants) accumulatedRound1Data = append(accumulatedRound1Data, round1Data) 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") } accumulatedRound2Data := round2Data var participantsSecretKey *group.Scalar participantsSecretKey, _, groupPublicKeyGeneratedInDKG, err = dkgParticipant.Finalize( accumulatedRound1Data, accumulatedRound2Data, ) if err != nil { panic(err) } configuration.GroupPublicKey = groupPublicKeyGeneratedInDKG participantGeneratedInDKG = configuration.Participant(participantIdentifier, participantsSecretKey) fmt.Printf("Signing keys for participant set up. ID: %s\n", hex.EncodeToString(participantIdentifier.Encode())) } func main() { /* 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. */ // 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. ) // 0. We suppose a previous run of DKG with a setup of participants. Here we will only use 1 participant. Example_dkg() participant := participantGeneratedInDKG groupPublicKey := groupPublicKeyGeneratedInDKG // A coordinator CAN be a participant. In this instance, we chose it not to be one. 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, } // 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, } signature := coordinator.Aggregate(commitments, message, signatureShares[:]) if !frost.Verify(configuration.Ciphersuite, message, signature, groupPublicKey) { fmt.Println("invalid 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 { // 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()) } } panic("Failed.") } fmt.Printf("Valid signature for %q.", message) }
Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000 Valid signature for "example".
Example (Dkg) ¶
Example_dkg shows the distributed key generation procedure that must be executed by each participant to build the secret key.
package main import ( "encoding/hex" "fmt" group "github.com/bytemare/crypto" "github.com/bytemare/frost" "github.com/bytemare/frost/dkg" ) var ( participantGeneratedInDKG *frost.Participant groupPublicKeyGeneratedInDKG *group.Element ) func main() { // 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 // over a secure channel. round1Data := dkgParticipant.Init() if round1Data.SenderIdentifier.Equal(participantIdentifier) != 1 { panic("this is just a test, and it failed") } // 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) // 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") } // 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, 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) } // It is important to set the group's public key. configuration.GroupPublicKey = groupPublicKeyGeneratedInDKG // Now you can build a Signing Participant for the FROST protocol with this ID and key. participantGeneratedInDKG = configuration.Participant(participantIdentifier, participantsSecretKey) fmt.Printf("Signing keys for participant set up. ID: %s\n", hex.EncodeToString(participantIdentifier.Encode())) }
Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000
Example (Signer) ¶
Example_signer shows the execution steps of a FROST participant.
package main import ( "encoding/hex" "fmt" group "github.com/bytemare/crypto" "github.com/bytemare/frost" "github.com/bytemare/frost/dkg" ) var ( participantGeneratedInDKG *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() { maximumAmountOfParticipants := 1 threshold := 1 configuration := frost.Ristretto255.Configuration() participantIdentifier := configuration.IDFromInt(1) dkgParticipant := dkg.NewParticipant( configuration.Ciphersuite, participantIdentifier, maximumAmountOfParticipants, threshold, ) round1Data := dkgParticipant.Init() if round1Data.SenderIdentifier.Equal(participantIdentifier) != 1 { panic("this is just a test, and it failed") } accumulatedRound1Data := make([]*dkg.Round1Data, 0, maximumAmountOfParticipants) accumulatedRound1Data = append(accumulatedRound1Data, round1Data) 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") } accumulatedRound2Data := round2Data var participantsSecretKey *group.Scalar participantsSecretKey, _, groupPublicKeyGeneratedInDKG, err = dkgParticipant.Finalize( accumulatedRound1Data, accumulatedRound2Data, ) if err != nil { panic(err) } configuration.GroupPublicKey = groupPublicKeyGeneratedInDKG participantGeneratedInDKG = configuration.Participant(participantIdentifier, participantsSecretKey) fmt.Printf("Signing keys for participant set up. ID: %s\n", hex.EncodeToString(participantIdentifier.Encode())) } func main() { // The following are your setup variables and configuration. numberOfParticipants := 1 // 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 // 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 { 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) commitments = append(commitments, commitment) // This will 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) if !participant.VerifySignatureShare( commitment, participant.GroupPublicKey, signatureShare.SignatureShare, commitments, message, ) { panic("this is a test and it failed") } fmt.Println("Signing successful.") }
Output: Signing keys for participant set up. ID: 0100000000000000000000000000000000000000000000000000000000000000 Signing successful.
Index ¶
- func TrustedDealerKeygen(g group.Group, secret *group.Scalar, max, min int, coeffs ...*group.Scalar) ([]*secretsharing.KeyShare, *group.Element, secretsharing.Commitment, error)
- func Verify(cs internal.Ciphersuite, msg []byte, signature *Signature, pk *group.Element) bool
- func VerifyVSS(g group.Group, share *secretsharing.KeyShare, coms secretsharing.Commitment) bool
- type Ciphersuite
- type Commitment
- type CommitmentList
- type Configuration
- type Participant
- func (p *Participant) Aggregate(list CommitmentList, msg []byte, sigShares []*SignatureShare) *Signature
- func (p *Participant) Backup() []byte
- func (p *Participant) Commit() *Commitment
- func (p *Participant) ComputeChallenge(coms CommitmentList, msg []byte) *group.Scalar
- func (p *Participant) Sign(msg []byte, list CommitmentList) (*SignatureShare, error)
- func (p *Participant) VerifySignatureShare(commitment *Commitment, pki *group.Element, sigShareI *group.Scalar, ...) bool
- type ParticipantInfo
- type PublicKeys
- type Signature
- type SignatureShare
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func TrustedDealerKeygen ¶
func TrustedDealerKeygen( g group.Group, secret *group.Scalar, max, min int, coeffs ...*group.Scalar, ) ([]*secretsharing.KeyShare, *group.Element, secretsharing.Commitment, error)
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.
func Verify ¶
Verify returns whether the signature of the message msg is valid under the public key pk.
func VerifyVSS ¶
func VerifyVSS(g group.Group, share *secretsharing.KeyShare, coms secretsharing.Commitment) bool
VerifyVSS allows verification of a participant's secret share given a VSS commitment to the secret polynomial.
Types ¶
type Ciphersuite ¶
type Ciphersuite byte
Ciphersuite identifies the group and hash to use for FROST.
const ( // Ed25519 uses Edwards25519 and SHA-512, producing Ed25519-compliant signatures as specified in RFC8032. Ed25519 Ciphersuite = 1 + iota // Ristretto255 uses Ristretto255 and SHA-512. Ristretto255 // P256 uses P-256 and SHA-256. P256 // Secp256k1 uses Secp256k1 and SHA-256. Secp256k1 )
func (Ciphersuite) Available ¶
func (c Ciphersuite) Available() bool
Available returns whether the selected ciphersuite is available.
func (Ciphersuite) Configuration ¶
func (c Ciphersuite) Configuration(groupPublicKey ...*group.Element) *Configuration
Configuration returns a configuration created for the ciphersuite.
type Commitment ¶
type Commitment struct { Identifier *group.Scalar HidingNonce *group.Element BindingNonce *group.Element }
Commitment is a participant's one-time commitment holding its identifier, and hiding and binding nonces.
func DecodeCommitment ¶
func DecodeCommitment(cs Ciphersuite, data []byte) (*Commitment, error)
DecodeCommitment attempts to deserialize the encoded commitment given as input, and to return it.
func (Commitment) Encode ¶
func (c Commitment) Encode() []byte
Encode returns the serialized byte encoding of a participant's commitment.
type CommitmentList ¶
type CommitmentList []*Commitment
CommitmentList is a sortable list of commitments.
func (CommitmentList) Encode ¶
func (c CommitmentList) Encode() []byte
Encode serializes a whole commitment list.
func (CommitmentList) Get ¶
func (c CommitmentList) Get(identifier *group.Scalar) *Commitment
Get returns the commitment of the participant with the corresponding identifier, or nil if it was not found.
func (CommitmentList) IsSorted ¶
func (c CommitmentList) IsSorted() bool
IsSorted returns whether the list is sorted in ascending order by identifier.
func (CommitmentList) Participants ¶
func (c CommitmentList) Participants() []*group.Scalar
Participants returns the list of participants in the commitment list.
func (CommitmentList) Sort ¶
func (c CommitmentList) Sort()
Sort sorts the list the ascending order of identifiers.
type Configuration ¶
type Configuration struct { GroupPublicKey *group.Element Ciphersuite internal.Ciphersuite }
Configuration holds long term configuration information.
func (Configuration) DecodeSignatureShare ¶
func (c Configuration) DecodeSignatureShare(data []byte) (*SignatureShare, error)
DecodeSignatureShare takes a byte string and attempts to decode it to return the signature share.
func (Configuration) IDFromInt ¶
func (c Configuration) IDFromInt(id int) *group.Scalar
IDFromInt returns a valid ID from and integer given the configuration.
func (Configuration) Participant ¶
func (c Configuration) Participant(id, keyShare *group.Scalar) *Participant
Participant returns a new participant of the protocol instantiated from the configuration an input.
type Participant ¶
type Participant struct { ParticipantInfo Nonce [2]*group.Scalar HidingRandom []byte BindingRandom []byte Configuration }
Participant is a signer of a group.
func RecoverParticipant ¶
func RecoverParticipant(c Ciphersuite, backup []byte) (*Participant, error)
RecoverParticipant attempts to deserialize the encoded backup into a Participant.
func (*Participant) Aggregate ¶
func (p *Participant) Aggregate( list CommitmentList, msg []byte, sigShares []*SignatureShare, ) *Signature
Aggregate 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. The CommitmentList must be sorted in ascending order by identifier.
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 (*Participant) Backup ¶
func (p *Participant) Backup() []byte
Backup serializes the client with its long term values, containing its secret share.
func (*Participant) Commit ¶
func (p *Participant) Commit() *Commitment
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.
func (*Participant) ComputeChallenge ¶
func (p *Participant) ComputeChallenge( coms CommitmentList, msg []byte, ) *group.Scalar
func (*Participant) Sign ¶
func (p *Participant) Sign(msg []byte, list CommitmentList) (*SignatureShare, error)
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 (*Participant) VerifySignatureShare ¶
func (p *Participant) VerifySignatureShare( commitment *Commitment, pki *group.Element, sigShareI *group.Scalar, coms CommitmentList, msg []byte, ) bool
VerifySignatureShare verifies a signature share. id, pki, commi, and sigShareI are, respectively, the identifier, public key, commitment, and signature share of the participant whose share is to be verified.
The CommitmentList must be sorted in ascending order by identifier.
type ParticipantInfo ¶
type ParticipantInfo struct { Lambda *group.Scalar // lamba can be computed once and reused across FROST signing operations PublicKey *group.Element }
ParticipantInfo holds the participant specific long-term values.
type PublicKeys ¶
PublicKeys is the tuple defining a commitment.
func DeriveGroupInfo ¶
func DeriveGroupInfo(g group.Group, max int, coms secretsharing.Commitment) (*group.Element, PublicKeys)
DeriveGroupInfo returns the group public key as well those from all participants.
type Signature ¶
Signature represent a Schnorr signature.
func Sign ¶
Sign returns a Schnorr signature over the message msg with the full secret signing key (as opposed to a key share).
type SignatureShare ¶
type SignatureShare struct {}
SignatureShare represents a participants signature share, specifying which participant it was produced by.
func (SignatureShare) Encode ¶
func (s SignatureShare) Encode() []byte
Encode returns a compact byte encoding of the signature share.
Source Files ¶
Directories ¶
Path | Synopsis |
---|---|
Package dkg implements the Distributed Key Generation described in FROST, using zero-knowledge proofs in Schnorr signatures.
|
Package dkg implements the Distributed Key Generation described in FROST, using zero-knowledge proofs in Schnorr signatures. |
Package internal provides values, structures, and functions to operate FROST that are not part of the public API.
|
Package internal provides values, structures, and functions to operate FROST that are not part of the public API. |