dkg

package module
v0.0.0-...-23ea4d5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 7, 2024 License: MIT Imports: 15 Imported by: 0

README

Distributed Key Generation

dkg Go Reference codecov

  import "github.com/bytemare/dkg"

Package dkg provides an efficient distributed key generation system in Go, easy to use. It builds on the 2-round Pederson DGK and extends it with zero-knowledge proofs to protect against rogue-key attacks of Byzantine participants, as defined in FROST. This is secure for any t among n participants in a (t,n)-threshold scheme.

This effectively generates keys among participants without the need of a trusted dealer or third-party. These keys are generally valid keys, and can be used in FROST and OPRFs.

References

Documentation Go Reference

You can find the documentation and usage examples in the package doc.

Usage

Requirements
  • All parties are identified with distinct uint16 non-zero IDs.
  • Communicate over confidential, authenticated, and secure channels.
  • All participants honestly follow the protocol (they can, nevertheless, identify a misbehaving participant).
Setup

Use the same ciphersuite for the DKG setup and the key usage in other protocol executions.

Error handling

In case of an identified misbehaving participant, abort the protocol immediately. If this happens there might be a serious problem that must be investigated. One may re-run the protocol after excluding that participant and solving the problem.

Protocol

The following steps describe how to run the DKG among participants. Note that participants maintain a state between phases. For each participant:

  1. Run Init()
    • this returns a round 1 package
    • send/broadcast this package to every other participant (this might include the very same participant, in which case it will discard it)
  2. Collect all the round 1 packages from other participants
  3. Run Continue() with the collection of round 1 packages
    • this returns round 2 packages, one destined to each other participant
    • each package specifies the intended receiver
    • send it to the intended receiver
  4. Collect all round 2 packages destined to the participant
  5. Run Finalize() with the collected round 1 and round 2 packages
    • returns the participant's own secret signing share, the corresponding verification/public share, and the group's public key
  6. Erase all intermediary values received and computed by the participants (including in their states)
  7. Optionally, compute the verification keys for each other participant and store them
  8. You might want each participant to already send their PublicKeyShare to a central coordinator or broadcast it to the other participants, as required to run the FROST protocol.

Versioning

SemVer is used for versioning. For the versions available, see the tags on the repository.

Contributing

Please read CONTRIBUTING.md for details on the code of conduct, and the process for submitting pull requests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

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/bytemare/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

Examples

Constants

View Source
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 {
	SecretShare         *ecc.Scalar `json:"secretShare"`
	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

func (s *Signature) Decode(data []byte) error

Decode deserializes the compact encoding obtained from Encode(), or returns an error.

func (*Signature) DecodeHex

func (s *Signature) DecodeHex(h string) error

DecodeHex sets s to the decoding of the hex encoded representation returned by Hex().

func (*Signature) Encode

func (s *Signature) Encode() []byte

Encode serializes the signature into a byte string.

func (*Signature) Hex

func (s *Signature) Hex() string

Hex returns the hexadecimal representation of the byte encoding returned by Encode().

func (*Signature) UnmarshalJSON

func (s *Signature) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes data into k, or returns an error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL