mirror of
https://source.quilibrium.com/quilibrium/ceremonyclient.git
synced 2024-12-27 09:05:18 +00:00
241 lines
7.9 KiB
Go
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
|
|
}
|