Documentation ¶
Overview ¶
Package dkg implements the Distributed Key Generation described in FROST, using zero-knowledge proofs in Schnorr signatures.
Example (Dkg) ¶
Example_dkg shows the 3-step 2-message distributed key generation procedure that must be executed by each participant to build their secret key share.
package main import ( "fmt" secretsharing "github.com/bytemare/secret-sharing" "github.com/bytemare/secret-sharing/keys" "github.com/0xBridge/dkg" ) func main() { // Each participant must be set to use the same configuration. We use (3,5) here for the demo, on Ristretto255. totalAmountOfParticipants := uint16(5) threshold := uint16(3) c := dkg.Ristretto255Sha512 var err error // Step 0: Initialise your participant. Each participant must be given an identifier that MUST be unique among // all participants. For this example, The participants will have the identifiers 1, 2, 3, 4, and 5. participants := make([]*dkg.Participant, totalAmountOfParticipants) for id := uint16(1); id <= totalAmountOfParticipants; id++ { participants[id-1], err = c.NewParticipant(id, threshold, totalAmountOfParticipants) if err != nil { panic(err) } } // Step 1: Call Start() on each participant. This will return data that must be broadcast to all other participants // over a secure channel, which can be encoded/serialized to send over the network. The proxy coordinator or every // participant must compile all these packages so that all have the same set. accumulatedRound1DataBytes := make([][]byte, totalAmountOfParticipants) for i, p := range participants { accumulatedRound1DataBytes[i] = p.Start().Encode() } // Upon reception of the encoded set, decode each item. decodedRound1Data := make([]*dkg.Round1Data, totalAmountOfParticipants) for i, data := range accumulatedRound1DataBytes { decodedRound1Data[i] = new(dkg.Round1Data) if err = decodedRound1Data[i].Decode(data); err != nil { panic(err) } } // Step 2: Call Continue() on each participant providing them with the compiled decoded data. Each participant will // return a map of Round2Data, one for each other participant, which must be sent to the specific peer // (not broadcast). accumulatedRound2Data := make([]map[uint16]*dkg.Round2Data, totalAmountOfParticipants) for i, p := range participants { if accumulatedRound2Data[i], err = p.Continue(decodedRound1Data); err != nil { panic(err) } } // We'll skip the encoding/decoding part (each Round2Data item can be encoded and send over the network). // Step 3: Each participant receives the Round2Data set destined to them (there's a Receiver identifier in each // Round2Data item), and then calls Finalize with the Round1 and their Round2 data. This will output the // participant's key share, containing its secret, public key share, and the group's public key that can be used for // signature verification. keyShares := make([]*keys.KeyShare, totalAmountOfParticipants) for i, p := range participants { accumulatedRound2DataForParticipant := make([]*dkg.Round2Data, 0, totalAmountOfParticipants) for _, r2Data := range accumulatedRound2Data { if d := r2Data[p.Identifier]; d != nil && d.RecipientIdentifier == p.Identifier { accumulatedRound2DataForParticipant = append(accumulatedRound2DataForParticipant, d) } } if keyShares[i], err = p.Finalize(decodedRound1Data, accumulatedRound2DataForParticipant); err != nil { panic(err) } } // Optional: Each participant can extract their public info pks := keyShare.Public() and send it to others // or a registry of participants. You can encode the registry for transmission or storage (in byte strings or JSON), // and recover it. PublicKeyShareRegistry := keys.NewPublicKeyShareRegistry(c.Group(), threshold, totalAmountOfParticipants) for _, ks := range keyShares { // A participant extracts its public key share and sends it to the others or the coordinator. pks := ks.Public() // Anyone can maintain a registry, and add keys for a setup. if err = PublicKeyShareRegistry.Add(pks); err != nil { panic(err) } } // Given all the commitments (as found in the Round1 data packages or all PublicKeyShare from all participants), // one can verify every public key of the setup. commitments := dkg.VSSCommitmentsFromRegistry(PublicKeyShareRegistry) for _, pks := range PublicKeyShareRegistry.PublicKeyShares { if err = dkg.VerifyPublicKey(c, pks.ID, pks.PublicKey, commitments); err != nil { panic(err) } } // Optional: There are multiple ways on how you can get the group's public key (the one used for signature validation) // 1. Participant's Finalize() function returns a KeyShare, which contains the VerificationKey, which can be sent to // the coordinator or registry. // 2. Using the commitments in the Round1 data, this is convenient during protocol execution. // 3. Using the participants' commitments in their public key share, this is convenient after protocol execution. verificationKey1 := keyShares[0].VerificationKey verificationKey2, err := dkg.VerificationKeyFromRound1(c, decodedRound1Data) if err != nil { panic(err) } verificationKey3, err := dkg.VerificationKeyFromCommitments( c, dkg.VSSCommitmentsFromRegistry(PublicKeyShareRegistry), ) if err != nil { panic(err) } if !verificationKey1.Equal(verificationKey2) || !verificationKey2.Equal(verificationKey3) { panic("group public key recovery failed") } PublicKeyShareRegistry.VerificationKey = verificationKey3 // A registry can be encoded for backup or transmission. encodedRegistry := PublicKeyShareRegistry.Encode() fmt.Printf("The encoded registry of public keys is %d bytes long.\n", len(encodedRegistry)) // Optional: This is how a participant can verify any participants public key of the protocol, given all the commitments. // This can be done with the Commitments in the Round1 data set or in the collection of public key shares. publicKeyShare := keyShares[2].Public() if err = dkg.VerifyPublicKey(c, publicKeyShare.ID, publicKeyShare.PublicKey, dkg.VSSCommitmentsFromRegistry(PublicKeyShareRegistry)); err != nil { panic(err) } fmt.Printf("Signing keys for participant set up and valid.") // Not recommended, but shown for consistency: if you gather at least threshold amount of secret keys from participants, // you can reconstruct the private key, and validate it with the group's public key. In our example, we use only // one participant, so the keys are equivalent. In a true setup, you don't want to extract and gather participants' // private keys, as it defeats the purpose of a DKG and might expose them. g := c.Group() shares := make( []keys.Share, threshold, ) // Here you would add the secret keys from the other participants. for i, k := range keyShares[:threshold] { shares[i] = k } recombinedSecret, err := secretsharing.CombineShares(shares) if err != nil { panic("failed to reconstruct secret") } groupPubKey := g.Base().Multiply(recombinedSecret) if !groupPubKey.Equal(verificationKey3) { panic("failed to recover the correct group secret") } }
Output: The encoded registry of public keys is 712 bytes long. Signing keys for participant set up and valid.
Index ¶
- Constants
- func ComputeParticipantPublicKey(c Ciphersuite, id uint16, commitments [][]*ecc.Element) (*ecc.Element, error)
- func FrostVerifyZeroKnowledgeProof(c Ciphersuite, id uint16, pubkey *ecc.Element, proof *Signature) (bool, error)
- func VSSCommitmentsFromRegistry(registry *keys.PublicKeyShareRegistry) [][]*ecc.Element
- func VerificationKeyFromCommitments(c Ciphersuite, commitments [][]*ecc.Element) (*ecc.Element, error)
- func VerificationKeyFromRound1(c Ciphersuite, r1DataSet []*Round1Data) (*ecc.Element, error)
- func VerifyPublicKey(c Ciphersuite, id uint16, pubKey *ecc.Element, commitments [][]*ecc.Element) error
- type Ciphersuite
- type Participant
- func (p *Participant) Continue(r1DataSet []*Round1Data) (map[uint16]*Round2Data, error)
- func (p *Participant) Finalize(r1DataSet []*Round1Data, r2DataSet []*Round2Data) (*keys.KeyShare, error)
- func (p *Participant) Start() *Round1Data
- func (p *Participant) StartWithRandom(random *ecc.Scalar) *Round1Data
- type Round1Data
- type Round2Data
- type Signature
Examples ¶
Constants ¶
const ( // Ristretto255Sha512 identifies the Ristretto255 group and SHA-512. Ristretto255Sha512 = Ciphersuite(ecc.Ristretto255Sha512) // P256Sha256 identifies the NIST P-256 group and SHA-256. P256Sha256 = Ciphersuite(ecc.P256Sha256) // P384Sha384 identifies the NIST P-384 group and SHA-384. P384Sha384 = Ciphersuite(ecc.P384Sha384) // P521Sha512 identifies the NIST P-512 group and SHA-512. P521Sha512 = Ciphersuite(ecc.P521Sha512) // Edwards25519Sha512 identifies the Edwards25519 group and SHA2-512. Edwards25519Sha512 = Ciphersuite(ecc.Edwards25519Sha512) // Secp256k1 identifies the SECp256k1 group and SHA-256. Secp256k1 = Ciphersuite(ecc.Secp256k1Sha256) )
Variables ¶
This section is empty.
Functions ¶
func ComputeParticipantPublicKey ¶
func ComputeParticipantPublicKey(c Ciphersuite, id uint16, commitments [][]*ecc.Element) (*ecc.Element, error)
ComputeParticipantPublicKey computes the verification share for participant id given the commitments of round 1.
func FrostVerifyZeroKnowledgeProof ¶
func FrostVerifyZeroKnowledgeProof(c Ciphersuite, id uint16, pubkey *ecc.Element, proof *Signature) (bool, error)
FrostVerifyZeroKnowledgeProof verifies a proof generated by FrostGenerateZeroKnowledgeProof.
func VSSCommitmentsFromRegistry ¶
func VSSCommitmentsFromRegistry(registry *keys.PublicKeyShareRegistry) [][]*ecc.Element
VSSCommitmentsFromRegistry returns all the commitments for the set of PublicKeyShares in the registry.
func VerificationKeyFromCommitments ¶
func VerificationKeyFromCommitments(c Ciphersuite, commitments [][]*ecc.Element) (*ecc.Element, error)
VerificationKeyFromCommitments returns the threshold's setup group public key, given all the commitments from all the participants.
func VerificationKeyFromRound1 ¶
func VerificationKeyFromRound1(c Ciphersuite, r1DataSet []*Round1Data) (*ecc.Element, error)
VerificationKeyFromRound1 returns the global public key, usable to verify signatures produced in a threshold scheme.
func VerifyPublicKey ¶
func VerifyPublicKey(c Ciphersuite, id uint16, pubKey *ecc.Element, commitments [][]*ecc.Element) error
VerifyPublicKey verifies if the pubKey associated to id is valid given the public VSS commitments of the other participants.
Types ¶
type Ciphersuite ¶
type Ciphersuite byte
A Ciphersuite defines the elliptic curve group to use.
func (Ciphersuite) Available ¶
func (c Ciphersuite) Available() bool
Available returns whether the Ciphersuite is supported, useful to avoid casting to an unsupported group identifier.
func (Ciphersuite) Group ¶
func (c Ciphersuite) Group() ecc.Group
Group returns the elliptic curve group used in the ciphersuite.
func (Ciphersuite) NewParticipant ¶
func (c Ciphersuite) NewParticipant( id uint16, threshold, maxSigners uint16, polynomial ...*ecc.Scalar, ) (*Participant, error)
NewParticipant instantiates a new participant with identifier id. The identifier must be different from zero and unique among the set of participants. The same participant instance must be used throughout the protocol execution, to ensure the correct internal intermediary values are used. Optionally, the participant's secret polynomial can be provided to set its secret and commitment (also enabling re-instantiating the same participant if the same polynomial is used).
type Participant ¶
type Participant struct { Identifier uint16 // contains filtered or unexported fields }
Participant represent a party in the Distributed Key Generation. Once the DKG completed, all values must be erased.
func (*Participant) Continue ¶
func (p *Participant) Continue(r1DataSet []*Round1Data) (map[uint16]*Round2Data, error)
Continue ingests the broadcast data from other peers and returns a map of dedicated Round2Data structures for each peer.
func (*Participant) Finalize ¶
func (p *Participant) Finalize(r1DataSet []*Round1Data, r2DataSet []*Round2Data) (*keys.KeyShare, error)
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 (*Participant) Start ¶
func (p *Participant) Start() *Round1Data
Start returns a participant's output for the first round.
func (*Participant) StartWithRandom ¶
func (p *Participant) StartWithRandom(random *ecc.Scalar) *Round1Data
StartWithRandom returns a participant's output for the first round and allows setting the random input for the NIZK proof.
type Round1Data ¶
type Round1Data struct { ProofOfKnowledge *Signature `json:"proof"` Commitment []*ecc.Element `json:"commitment"` SenderIdentifier uint16 `json:"senderId"` Group ecc.Group `json:"group"` }
Round1Data is the output data of the Start() function, to be broadcast to all participants.
func (*Round1Data) Decode ¶
func (d *Round1Data) Decode(data []byte) error
Decode deserializes a valid byte encoding of Round1Data.
func (*Round1Data) DecodeHex ¶
func (d *Round1Data) DecodeHex(h string) error
DecodeHex sets k to the decoding of the hex encoded representation returned by Hex().
func (*Round1Data) Encode ¶
func (d *Round1Data) Encode() []byte
Encode returns a compact byte serialization of Round1Data.
func (*Round1Data) Hex ¶
func (d *Round1Data) Hex() string
Hex returns the hexadecimal representation of the byte encoding returned by Encode().
func (*Round1Data) UnmarshalJSON ¶
func (d *Round1Data) UnmarshalJSON(data []byte) error
UnmarshalJSON reads the input data as JSON and deserializes it into the receiver. It doesn't modify the receiver when encountering an error.
type Round2Data ¶
type Round2Data struct { SenderIdentifier uint16 `json:"senderId"` RecipientIdentifier uint16 `json:"recipientId"` Group ecc.Group `json:"group"` }
Round2Data is an output of the Continue() function, to be sent to the Receiver.
func (*Round2Data) Decode ¶
func (d *Round2Data) Decode(data []byte) error
Decode deserializes a valid byte encoding of Round2Data.
func (*Round2Data) DecodeHex ¶
func (d *Round2Data) DecodeHex(h string) error
DecodeHex sets k to the decoding of the hex encoded representation returned by Hex().
func (*Round2Data) Encode ¶
func (d *Round2Data) Encode() []byte
Encode returns a compact byte serialization of Round2Data.
func (*Round2Data) Hex ¶
func (d *Round2Data) Hex() string
Hex returns the hexadecimal representation of the byte encoding returned by Encode().
func (*Round2Data) UnmarshalJSON ¶
func (d *Round2Data) UnmarshalJSON(data []byte) error
UnmarshalJSON reads the input data as JSON and deserializes it into the receiver. It doesn't modify the receiver when encountering an error.
type Signature ¶
type Signature struct { R *ecc.Element `json:"r"` Z *ecc.Scalar `json:"z"` Group ecc.Group `json:"group"` }
Signature represents a Schnorr signature.
func FrostGenerateZeroKnowledgeProof ¶
func FrostGenerateZeroKnowledgeProof( c Ciphersuite, id uint16, secret *ecc.Scalar, pubkey *ecc.Element, rand ...*ecc.Scalar, ) (*Signature, error)
FrostGenerateZeroKnowledgeProof generates a zero-knowledge proof of secret, as defined by the FROST protocol. You most probably don't want to set r, which is a random component necessary for the proof, and can safely ignore it.
func (*Signature) Clear ¶
func (s *Signature) Clear()
Clear overwrites the original values with default ones.
func (*Signature) Decode ¶
Decode deserializes the compact encoding obtained from Encode(), or returns an error.
func (*Signature) DecodeHex ¶
DecodeHex sets s to the decoding of the hex encoded representation returned by Hex().
func (*Signature) Hex ¶
Hex returns the hexadecimal representation of the byte encoding returned by Encode().
func (*Signature) UnmarshalJSON ¶
UnmarshalJSON decodes data into k, or returns an error.