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"
"github.com/bytemare/ecc"
"github.com/bytemare/secret-sharing/keys"
"github.com/bytemare/dkg"
secretsharing "github.com/bytemare/secret-sharing"
)
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 an authenticated 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 {
r1, err := p.Start()
if err != nil {
panic(err)
}
accumulatedRound1DataBytes[i] = r1.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. Round2Data carries secret shares, so send each item
// only to its intended peer over an authenticated confidential transport and never broadcast or log it.
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 sent to the intended recipient).
// 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.
publicKeyShares := make([]*keys.PublicKeyShare, len(keyShares))
for i, ks := range keyShares {
// A participant extracts its public key share and sends it to the others or the coordinator.
publicKeyShares[i] = ks.PublicKeyShare()
}
// Anyone can maintain a registry for a complete setup.
PublicKeyShareRegistry, err := keys.NewPublicKeyShareRegistry(
c.Group(),
threshold,
totalAmountOfParticipants,
keyShares[0].VerificationKey(),
publicKeyShares,
)
if err != nil {
panic(err)
}
// A complete validated registry checks every finalized public key of the setup.
for _, pks := range PublicKeyShareRegistry.Shares() {
if err = PublicKeyShareRegistry.ContainsPublicKey(pks.Identifier(), pks.PublicKey()); 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 Round1 data before proofs are cleared, 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,
[][]*ecc.Element{dkg.VSSCommitmentFromRegistry(PublicKeyShareRegistry)},
)
if err != nil {
panic(err)
}
if !verificationKey1.Equal(verificationKey2) || !verificationKey2.Equal(verificationKey3) {
panic("group public key recovery failed")
}
// 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].PublicKeyShare()
if err = PublicKeyShareRegistry.ContainsPublicKey(publicKeyShare.Identifier(), publicKeyShare.PublicKey()); 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.KeyShare,
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, threshold)
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 702 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 VSSCommitmentFromRegistry(registry *keys.PublicKeyShareRegistry) []*ecc.Element
- func VSSCommitmentsFromRegistry(registry *keys.PublicKeyShareRegistry) [][]*ecc.Elementdeprecated
- 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, error)
- func (p *Participant) StartWithRandom(random *ecc.Scalar) (*Round1Data, error)
- 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 VSSCommitmentFromRegistry ¶
func VSSCommitmentFromRegistry(registry *keys.PublicKeyShareRegistry) []*ecc.Element
VSSCommitmentFromRegistry returns the aggregate commitment for a complete registry.
func VSSCommitmentsFromRegistry
deprecated
func VSSCommitmentsFromRegistry(registry *keys.PublicKeyShareRegistry) [][]*ecc.Element
VSSCommitmentsFromRegistry returns the aggregate commitment for a complete registry.
Deprecated: use VSSCommitmentFromRegistry.
func VerificationKeyFromCommitments ¶
func VerificationKeyFromCommitments(c Ciphersuite, commitments [][]*ecc.Element) (*ecc.Element, error)
VerificationKeyFromCommitments returns the threshold setup's group public key from participant commitments. It assumes those commitments came from an already validated DKG transcript or another trusted source, because it does not verify the Round 1 proofs of knowledge.
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. It validates each Round 1 commitment and proof of knowledge, so it must be called before proofs are cleared.
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 non-zero and unique among the set of participants. maxSigners and threshold must be non-zero, and threshold must be at most maxSigners.
The same Participant instance must be used throughout the protocol execution, because it stores the validated intermediary values between Start, Continue, and Finalize. Optionally, the participant's secret polynomial can be provided to set its secret and commitment, which enables re-instantiating the same participant if the same polynomial is used. A provided polynomial must have exactly threshold coefficients, valid same-group scalar coefficients, a non-zero secret coefficient, and a non-zero highest-degree coefficient. Interior zero coefficients and repeated coefficient values are valid.
type Participant ¶
type Participant struct {
Identifier uint16
// contains filtered or unexported fields
}
Participant represents a party in the Distributed Key Generation. A Participant is stateful: Start, Continue, and Finalize are one-shot protocol phases on the same instance. Once the DKG is complete, all intermediary 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, verifies their proofs of knowledge, stores the verified commitments for Finalize, and returns one Round2Data package for each peer.
func (*Participant) Finalize ¶
func (p *Participant) Finalize(r1DataSet []*Round1Data, r2DataSet []*Round2Data) (*keys.KeyShare, error)
Finalize ingests the same round 1 commitments accepted by Continue and the round 2 data destined for the participant, then returns the participant's secret share, verification key, and the group's public key. Round 1 proofs may be cleared after Continue, but the commitments must still match the verified transcript.
func (*Participant) Start ¶
func (p *Participant) Start() (*Round1Data, error)
Start returns a participant's output for the first round and advances the participant to the started state.
func (*Participant) StartWithRandom ¶
func (p *Participant) StartWithRandom(random *ecc.Scalar) (*Round1Data, error)
StartWithRandom returns a participant's output for the first round and allows setting the Schnorr proof nonce used by the NIZK proof. Omit random in normal use; it must stay secret and be unique for a given secret across distinct challenges, because reuse or disclosure can leak the secret.
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. Keep ProofOfKnowledge intact until all participants have called Continue and until VerificationKeyFromRound1 has been called, if used.
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. It returns nil for nil or malformed values.
func (*Round1Data) Hex ¶
func (d *Round1Data) Hex() string
Hex returns the hexadecimal representation of the byte encoding returned by Encode(). It returns an empty string when Encode returns nil.
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 only to RecipientIdentifier. It contains secret share material and must be sent over authenticated confidential transport; never broadcast or log it.
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. It returns nil for nil or malformed values.
func (*Round2Data) Hex ¶
func (d *Round2Data) Hex() string
Hex returns the hexadecimal representation of the byte encoding returned by Encode(). It returns an empty string when Encode returns nil.
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. Omit rand in normal use. If provided, exactly one rand value is accepted; it is the Schnorr proof nonce and must stay secret and be unique for a given secret across distinct challenges, because reuse or disclosure can leak the secret.
func (*Signature) Clear ¶
func (s *Signature) Clear()
Clear overwrites the original values with default ones. It is a no-op for nil or malformed signatures.
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) Encode ¶
Encode serializes the signature into a byte string. It returns nil for nil or malformed values.
func (*Signature) Hex ¶
Hex returns the hexadecimal representation of the byte encoding returned by Encode(). It returns an empty string when Encode returns nil.
func (*Signature) UnmarshalJSON ¶
UnmarshalJSON decodes data into k, or returns an error.