frost

package module
v0.0.0-...-d9d8b5d Latest Latest
Warning

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

Go to latest
Published: Mar 1, 2024 License: MIT Imports: 8 Imported by: 0

README

❄ FROST

frost Go Reference codecov

  import "github.com/bytemare/frost"

This package implements FROST Flexible Round-Optimized Schnorr Threshold and the FROST Distributed Key Generation protocols. FROST provides Two-Round Threshold Schnorr Signatures.

What is frost?

FROST reduces network overhead during threshold signing operations while employing a novel technique to protect against forgery attacks applicable to prior Schnorr-based threshold signature constructions. FROST signatures can be issued after a threshold number of entities cooperate to compute a signature, allowing for improved distribution of trust and redundancy with respect to a secret key.

Supported Ciphersuites
ID Name Backend
1 Edwards25519 filippo.io/edwards25519
2 Ristretto255 github.com/gtank/ristretto255
3 Edwards448 not yet supported
4 P-256 filippo.io/nistec
5 Secp256k1 github.com/bytemare/crypto
References

Documentation Go Reference

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

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

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

func Verify(cs internal.Ciphersuite, msg []byte, signature *Signature, pk *group.Element) bool

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 {
	KeyShare  *secretsharing.KeyShare
	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

type PublicKeys []*group.Element

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

type Signature struct {
	R *group.Element
	Z *group.Scalar
}

Signature represent a Schnorr signature.

func Sign

func Sign(cs internal.Ciphersuite, msg []byte, key *group.Scalar) *Signature

Sign returns a Schnorr signature over the message msg with the full secret signing key (as opposed to a key share).

func (*Signature) Decode

func (s *Signature) Decode(g group.Group, encoded []byte) error

Decode attempts to deserialize the encoded input into the signature in the group.

func (*Signature) Encode

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

Encode serializes the signature into a byte string.

type SignatureShare

type SignatureShare struct {
	Identifier     *group.Scalar
	SignatureShare *group.Scalar
}

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.

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.

Jump to

Keyboard shortcuts

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