ceremonyclient/nekryptology/pkg/ted25519/ted25519/keygen.go
2023-07-05 00:32:28 -05:00

241 lines
7.9 KiB
Go

//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"encoding/binary"
"fmt"
"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
v1 "source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/sharing/v1"
)
// PublicKeyFromBytes converts byte array into PublicKey byte array
func PublicKeyFromBytes(bytes []byte) ([]byte, error) {
if l := len(bytes); l != PublicKeySize {
return nil, fmt.Errorf("invalid public key size: %d", l)
}
return bytes, nil
}
// KeyShare represents a share of a generated key.
type KeyShare struct {
*v1.ShamirShare
}
// NewKeyShare is a KeyShare constructor.
func NewKeyShare(identifier byte, secret []byte) *KeyShare {
field := curves.NewField(curves.Ed25519Order())
return &KeyShare{v1.NewShamirShare(uint32(identifier), secret, field)}
}
// Commitments is a collection of public keys with each coefficient of a polynomial as the secret keys.
type Commitments []curves.Point
// CommitmentsToBytes converts commitments to bytes
func (commitments Commitments) CommitmentsToBytes() [][]byte {
bytes := make([][]byte, len(commitments))
for i, c := range commitments {
bytes[i] = c.ToAffineCompressed()
}
return bytes
}
// CommitmentsFromBytes converts bytes to commitments
func CommitmentsFromBytes(bytes [][]byte) (Commitments, error) {
comms := make([]curves.Point, len(bytes))
for i, pubKeyBytes := range bytes {
pubKey, err := PublicKeyFromBytes(pubKeyBytes)
if err != nil {
return nil, err
}
comms[i], err = new(curves.PointEd25519).FromAffineCompressed(pubKey)
if err != nil {
return nil, err
}
}
return comms, nil
}
// KeyShareFromBytes converts byte array into KeyShare type
func KeyShareFromBytes(bytes []byte) *KeyShare {
field := curves.NewField(curves.Ed25519Order())
element := field.ElementFromBytes(bytes[4:])
// We set first 4 bytes as identifier
identifier := binary.BigEndian.Uint32(bytes[:4])
return &KeyShare{&v1.ShamirShare{Identifier: identifier, Value: element}}
}
// ShareConfiguration sets threshold and limit for the protocol
type ShareConfiguration struct {
T int // threshold
N int // total shares
}
// generateSharableKey generates a random key and returns the public key and private key in
// big-endian encoding. It returns an error if it cannot acquire sufficient randomness.
func generateSharableKey() (PublicKey, []byte, error) {
pub, priv, err := GenerateKey(nil)
if err != nil {
return nil, nil, err
}
// Internally the PrivateKey type is represented as the seed || public key, but we want to pull
// out seed to share which is the actual private key.
seed := priv.Seed()
// We must apply the key expansion to the seed before splitting the key.
// Ed25519 signing by default will apply this during signature generation, but since it involves
// a hash function, it breaks the relationship between shares and breaks aggregating signatures.
// Our signature generation does not apply this mutation at signing time.
//
// As per anything that comes from the ed25519 library this value should be treated as
// little-endian so we reverse it before using it.
expandedSeed := reverseBytes(ExpandSeed(seed))
// Lastly we must reduce this value into the size of the field so we can share it. This diverges
// from how the standard implementation treats this because their scalar multiplication accepts
// values up to the curve order but we must constrain it to be able to split it and aggregate.
//
// If you read the documentation for the ReducedElementFromBytes function we call below, it
// includes a big warning about how it will return non-uniform outputs depending on the input.
// This is true, but not a concern for keygen specifically because the value we are providing it
// has been generated as the ed25519 spec requires, which has a slight bias by definition of how
// the ExpandSeed operation works.
field := &curves.Field{Int: curves.Ed25519Order()}
expandedSeedReduced := field.ReducedElementFromBytes(expandedSeed)
return pub, expandedSeedReduced.Bytes(), nil
}
// GenerateSharedKey generates a random key, splits it, and returns the public key, shares, and VSS commitments.
func GenerateSharedKey(config *ShareConfiguration) (PublicKey, []*KeyShare, Commitments, error) {
pub, priv, err := generateSharableKey()
//pub, priv, err := ed25519.GenerateKey(nil)
if err != nil {
return nil, nil, nil, err
}
keyShares, commitments, err := splitPrivateKey(config, priv)
if err != nil {
return nil, nil, nil, err
}
return pub, keyShares, commitments, nil
}
// splitPrivateKey splits the secret into a set of secret shares and creates a set of commitments of them.
func splitPrivateKey(config *ShareConfiguration, priv []byte) ([]*KeyShare, Commitments, error) {
commitments, shares, err := split(priv, config)
if err != nil {
return nil, nil, err
}
keyShares := make([]*KeyShare, len(shares))
for i, s := range shares {
keyShares[i] = &KeyShare{s}
}
return keyShares, commitments, nil
}
// split contains core operations to split the secret and generate commitments.
func split(secret []byte, config *ShareConfiguration) ([]curves.Point, []*v1.ShamirShare, error) {
field := curves.NewField(curves.Ed25519Order())
shamir, err := v1.NewShamir(config.T, config.N, field)
if err != nil {
return nil, nil, fmt.Errorf("Error in NewShamir")
}
shares, poly, err := shamir.GetSharesAndPolynomial(secret)
if err != nil {
return nil, nil, fmt.Errorf("Error in GetSharesAndPolynomial")
}
// Generate the verifiable commitments to the polynomial for the shares
verifiers := make([]curves.Point, len(poly.Coefficients))
// curve := sharing.Ed25519()
for i, c := range poly.Coefficients {
// We have to reverse each coefficient, which is different than the method sharing.Split
reverseC := reverseBytes(c.Bytes())
var reverseInput [32]byte
copy(reverseInput[:], reverseC)
cScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(reverseInput[:])
if err != nil {
return nil, nil, fmt.Errorf("Error in SetBytesCanonical reverseC")
}
v := curves.ED25519().Point.Generator().Mul(cScalar)
verifiers[i] = v
}
return verifiers, shares, nil
}
// Reconstruct recovers the secret from a set of secret shares.
func Reconstruct(keyShares []*KeyShare, config *ShareConfiguration) ([]byte, error) {
curve := v1.Ed25519()
field := curves.NewField(curve.Params().N)
shamir, err := v1.NewShamir(config.T, config.N, field)
if err != nil {
return nil, err
}
shares := make([]*v1.ShamirShare, len(keyShares))
for i, s := range keyShares {
shares[i] = s.ShamirShare
}
return shamir.Combine(shares...)
}
// VerifyVSS validates that a Share represents a solution to a Shamir polynomial
// in which len(commitments) + 1 solutions are required to construct the private
// key for the public key at commitments[0].
func (share *KeyShare) VerifyVSS(commitments Commitments, config *ShareConfiguration) (bool, error) {
if len(commitments) < config.T {
return false, fmt.Errorf("not enough verifiers to check")
}
field := curves.NewField(curves.Ed25519Order())
xBytes := make([]byte, 4)
binary.BigEndian.PutUint32(xBytes, share.Identifier)
x := field.ElementFromBytes(xBytes)
i := share.Value.Modulus.One()
// c_0
rhs := commitments[0]
// Compute the sum of products
// c_0 * c_1^i * c_2^{i^2} *c_3^{i^3}
for j := 1; j < len(commitments); j++ {
// i *= x
i = i.Mul(x)
var iBytes [32]byte
copy(iBytes[:], i.Bytes()[:])
iScalar, err := new(curves.ScalarEd25519).SetBytesCanonical(iBytes[:])
if err != nil {
return false, fmt.Errorf("Error in SetBytesCanonical iBytes")
}
c := commitments[j].Mul(iScalar)
// ...* c_j^{i^j}
rhs = rhs.Add(c)
}
vValue := reverseBytes(share.Value.Bytes())
var vInput [32]byte
copy(vInput[:], vValue)
vScalar, err := new(curves.ScalarEd25519).SetBytes(vInput[:])
if err != nil {
return false, err
}
lhs := curves.ED25519().ScalarBaseMult(vScalar)
// Check if lhs == rhs
return lhs.Equal(rhs), nil
}