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

124 lines
3.8 KiB
Go

//
// Copyright Coinbase, Inc. All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
package ted25519
import (
"crypto/rand"
"crypto/sha256"
"io"
"golang.org/x/crypto/hkdf"
"source.quilibrium.com/quilibrium/monorepo/nekryptology/pkg/core/curves"
)
// NonceShare represents a share of a generated nonce.
type NonceShare struct {
*KeyShare
}
// NewNonceShare is a NonceShare construction
func NewNonceShare(identifier byte, secret []byte) *NonceShare {
return &NonceShare{NewKeyShare(identifier, secret)}
}
// NonceShareFromBytes unmashals a NonceShare from its bytes representation
func NonceShareFromBytes(bytes []byte) *NonceShare {
return &NonceShare{KeyShareFromBytes(bytes)}
}
func generateSharableNonce(s *KeyShare, p PublicKey, m Message) (PublicKey, []byte, error) {
// Create an HKDF reader that produces random bytes that we will use to create a nonce
hkdf, err := generateRandomHkdf(s, p, m)
if err != nil {
return nil, nil, err
}
// Generate a random nonce that is within the field range so that we can share it.
//
// This diverges from how the standard implementation treats it because their scalar
// multiplication accepts values up to the curve order, but we must constrain it to be able to
// split it and aggregate.
//
// WARN: This operation is not constant time and we are dealing with a secret value
nonce, err := curves.NewField(curves.Ed25519Order()).RandomElement(hkdf)
if err != nil {
return nil, nil, err
}
nonceBytes := nonce.Bytes()
reverseBytes := reverseBytes(nonceBytes)
var reverseInput [32]byte
copy(reverseInput[:], reverseBytes)
scalar, err := new(curves.ScalarEd25519).SetBytesCanonical(reverseInput[:])
if err != nil {
return nil, nil, err
}
// Generate the nonce pubkey by multiplying it by the base point.
noncePubkey := curves.ED25519().Point.Generator().Mul(scalar)
return noncePubkey.ToAffineCompressed(), nonceBytes, nil
}
// GenerateSharedNonce generates a random nonce, splits it, and returns the nonce pubkey, nonce shares, and
// VSS commitments.
func GenerateSharedNonce(config *ShareConfiguration, s *KeyShare, p PublicKey, m Message) (
PublicKey,
[]*NonceShare,
Commitments,
error,
) {
noncePubkey, nonce, err := generateSharableNonce(s, p, m)
if err != nil {
return nil, nil, nil, err
}
keyShares, vssCommitments, err := splitPrivateKey(config, nonce)
if err != nil {
return nil, nil, nil, err
}
nonceShares := make([]*NonceShare, len(keyShares))
for i, k := range keyShares {
nonceShares[i] = &NonceShare{k}
}
return noncePubkey, nonceShares, vssCommitments, nil
}
// Add returns the sum of two NonceShares.
func (n NonceShare) Add(other *NonceShare) *NonceShare {
return &NonceShare{
&KeyShare{
// use Add method from the shamir.Share type to sum the shares
// WARN: This is not constant time and deals with secrets
n.ShamirShare.Add(other.ShamirShare),
},
}
}
// generateRandomHkdf returns an HMAC-based extract-and-expand Key Derivation Function (see RFC 5869).
func generateRandomHkdf(s *KeyShare, p PublicKey, m Message) (io.Reader, error) {
// We _must_ introduce randomness to the HKDF to make the output non-deterministic because deterministic nonces open
// up threshold schemes to potential nonce-reuse attacks. We continue to use the HKDF that takes in context about
// what is going to be signed as it adds some protection against bad local randomness.
randNonce := make([]byte, SeedSize)
if _, err := io.ReadFull(rand.Reader, randNonce); err != nil {
return nil, err
}
var secret []byte
secret = append(secret, s.Bytes()...)
secret = append(secret, randNonce...)
info := []byte("ted25519nonce")
// We use info for non-secret inputs to limit an attacker's ability to influence the key.
info = append(info, p.Bytes()...)
info = append(info, m...)
return hkdf.New(sha256.New, secret, nil, info), nil
}