Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FROST Round Two: Signature Share Generation #8

Merged
merged 4 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions frost/bip340_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func TestBip340CurveDeserialize(t *testing.T) {

serialized := curve.SerializePoint(point)

var tests = map[string]struct {
tests := map[string]struct {
input []byte
}{
"nil": {
Expand Down Expand Up @@ -133,7 +133,7 @@ func TestBip340CiphersuiteH1(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
var tests = map[string]struct {
tests := map[string]struct {
m []byte
expected string
}{
Expand All @@ -160,7 +160,7 @@ func TestBip340CiphersuiteH2(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
var tests = map[string]struct {
tests := map[string]struct {
m []byte
ms [][]byte
expected string
Expand Down Expand Up @@ -200,7 +200,7 @@ func TestBip340CiphersuiteH3(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
var tests = map[string]struct {
tests := map[string]struct {
m []byte
ms [][]byte
expected string
Expand Down Expand Up @@ -239,7 +239,7 @@ func TestBip340CiphersuiteH4(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
var tests = map[string]struct {
tests := map[string]struct {
m []byte
expected string
}{
Expand Down Expand Up @@ -269,7 +269,7 @@ func TestBip340CiphersuiteH5(t *testing.T) {
// There are no official test vectors available. Yet, we want to ensure the
// function does not panic for empty or nil. We also want to make sure the
// happy path works producing a non-zero value.
var tests = map[string]struct {
tests := map[string]struct {
m []byte
expected string
}{
Expand All @@ -296,7 +296,7 @@ func TestBip340CiphersuiteH5(t *testing.T) {
}

func TestBip340CiphersuiteHashToScalar(t *testing.T) {
var tests = map[string]struct {
tests := map[string]struct {
tag []byte
msg []byte
}{
Expand Down Expand Up @@ -345,7 +345,7 @@ func TestBip340CiphersuiteHashToScalar(t *testing.T) {
}

func TestBip340CiphersuiteHash(t *testing.T) {
var tests = map[string]struct {
tests := map[string]struct {
tag []byte
msg []byte
}{
Expand Down Expand Up @@ -395,7 +395,7 @@ func TestBip340CiphersuiteHash(t *testing.T) {
}

func TestConcat(t *testing.T) {
var tests = map[string]struct {
tests := map[string]struct {
expected []byte
a []byte
b [][]byte
Expand Down Expand Up @@ -453,7 +453,7 @@ func TestConcat(t *testing.T) {
}

func TestOs2Ip(t *testing.T) {
var tests = map[string]struct {
tests := map[string]struct {
expected *big.Int
input []byte
}{
Expand Down
164 changes: 128 additions & 36 deletions frost/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,22 +94,64 @@ func (s *Signer) generateNonce(secret []byte) (*big.Int, error) {
return s.ciphersuite.H3(b, secret), nil
}

func (s *Signer) Round2(message []byte, commitments []*NonceCommitment) (*big.Int, error) {
validationErrors := s.validateGroupCommitments(commitments)
// Round2 implements the Round Two - Signature Share Generation phase from
// [FROST], section 5.2 Round Two - Signature Share Generation.
func (s *Signer) Round2(
message []byte,
nonce *Nonce,
commitments []*NonceCommitment,
) (*big.Int, error) {
// TODO: validate number of commitments?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's look at it in a separate PR, where we figure out the coordinator's code.


// participant_list = participants_from_commitment_list(commitment_list)
validationErrors, participants := s.validateGroupCommitments(commitments)
if len(validationErrors) != 0 {
return nil, errors.Join(validationErrors...)
}

return nil, nil // TODO: return signature share
// binding_factor_list = compute_binding_factors(group_public_key, commitment_list, msg)
bindingFactors := s.computeBindingFactors(message, commitments)
// binding_factor = binding_factor_for_participant(binding_factor_list, identifier)
bindingFactor := bindingFactors[s.signerIndex]

// group_commitment = compute_group_commitment(commitment_list, binding_factor_list)
groupCommitment := s.computeGroupCommitment(commitments, bindingFactors)

// lambda_i = derive_interpolating_value(participant_list, identifier)
lambda := s.deriveInterpolatingValue(s.signerIndex, participants)

// challenge = compute_challenge(group_commitment, group_public_key, msg)
challenge := s.computeChallenge(message, groupCommitment)

bnbf := new(big.Int).Mul(nonce.bindingNonce, bindingFactor) // (binding_nonce * binding_factor)
lski := new(big.Int).Mul(lambda, s.secretKeyShare) // lambda_i * sk_i
lskic := new(big.Int).Mul(lski, challenge) // (lambda_i * sk_i * challenge)

// sig_share = hiding_nonce + (binding_nonce * binding_factor) + (lambda_i * sk_i * challenge)
sigShare := new(big.Int).Add(
nonce.hidingNonce,
new(big.Int).Add(bnbf, lskic),
)

return sigShare, nil
}

// validateGroupCommitments is a helper function used internally by
// encodeGroupCommitment to validate the group commitments. Two validations are
// done:
// encodeGroupCommitment to validate the group commitments. Four validations
// are done:
// - None of the commitments is a point not lying on the curve.
// - The list of commitments is sorted in ascending order by signer identifier.
func (s *Signer) validateGroupCommitments(commitments []*NonceCommitment) []error {
// From [FROST]:
// - This signer's commitment is included in the commitments.
// - None of the commitments is nil.
//
// Additionally, the function returns the list of participants if there were no
// validation errors. This way, the function implements
// def participants_from_commitment_list(commitment_list) function from [FROST]
// section 4.3. List Operations.
func (s *Signer) validateGroupCommitments(
commitments []*NonceCommitment,
) ([]error, []uint64) {
// Validations required, as specified in [FROST]:
//
// 3.1 Prime-Order Group
//
Expand All @@ -129,19 +171,31 @@ func (s *Signer) validateGroupCommitments(commitments []*NonceCommitment) []erro
// NonZeroScalar identifier i and two commitment Element values
// (hiding_nonce_commitment_i, binding_nonce_commitment_i). This list
// MUST be sorted in ascending order by identifier.

participants := make([]uint64, len(commitments))
var errors []error

curve := s.ciphersuite.Curve()

found := false

// we index from 1 so this number will always be lower
lastSignerIndex := uint64(0)

for i, c := range commitments {
if c == nil {
errors = append(
errors,
fmt.Errorf("commitment at position [%d] is nil", i),
)
continue
}

if c.signerIndex <= lastSignerIndex {
errors = append(
errors, fmt.Errorf(
"commitments not sorted in ascending order: "+
"commitments[%v].signerIndex=%v, commitments[%v].signerIndex=%v",
"commitments[%d].signerIndex=%d, commitments[%d].signerIndex=%d",
i-1,
lastSignerIndex,
i,
Expand All @@ -151,10 +205,15 @@ func (s *Signer) validateGroupCommitments(commitments []*NonceCommitment) []erro
}

lastSignerIndex = c.signerIndex
participants[i] = c.signerIndex

if c.signerIndex == s.signerIndex {
found = true
}

if !curve.IsPointOnCurve(c.bindingNonceCommitment) {
errors = append(errors, fmt.Errorf(
"binding nonce commitment from signer [%v] is not a valid "+
"binding nonce commitment from signer [%d] is not a valid "+
"non-identity point on the curve: [%s]",
c.signerIndex,
c.bindingNonceCommitment,
Expand All @@ -163,15 +222,27 @@ func (s *Signer) validateGroupCommitments(commitments []*NonceCommitment) []erro

if !curve.IsPointOnCurve(c.hidingNonceCommitment) {
errors = append(errors, fmt.Errorf(
"hiding nonce commitment from signer [%v] is not a valid "+
"hiding nonce commitment from signer [%d] is not a valid "+
"non-identity point on the curve: [%s]",
c.signerIndex,
c.hidingNonceCommitment,
))
}
}

return errors
if !found {
errors = append(
errors,
fmt.Errorf("current signer's commitment not found on the list"),
)
}

// return participants only when there were no validation errors
if len(errors) == 0 {
return nil, participants
}

return errors, nil
}

// computeBindingFactors implements def compute_binding_factors(group_public_key,
Expand All @@ -182,8 +253,8 @@ func (s *Signer) validateGroupCommitments(commitments []*NonceCommitment) []erro
// commitments have been received and call validateGroupCommitment to validate
// the received commitments.
func (s *Signer) computeBindingFactors(
commitments []*NonceCommitment,
message []byte,
commitments []*NonceCommitment,
) bindingFactors {
// From [FROST]:
//
Expand Down Expand Up @@ -369,7 +440,11 @@ func (s *Signer) encodeGroupCommitment(commitments []*NonceCommitment) []byte {
// function from [FROST], as defined in section 4.2 Polynomials.
// L is the list of the indices of the members of the particular group.
// xi is the index of the participant i.
func (s *Signer) deriveInterpolatingValue(xi uint64, L []uint64) (*big.Int, error) {
//
// The function calling deriveInterpolatingValue must ensure a valid number of
// commitments have been received and call validateGroupCommitment to validate
// the received commitments.
func (s *Signer) deriveInterpolatingValue(xi uint64, L []uint64) *big.Int {
// From [FROST]:
//
// 4.2. Polynomials
Expand Down Expand Up @@ -399,26 +474,16 @@ func (s *Signer) deriveInterpolatingValue(xi uint64, L []uint64) (*big.Int, erro
//
// def derive_interpolating_value(L, x_i):

// Note that the validation is handled in validateGroupCommitment function.

order := s.ciphersuite.Curve().Order()
found := false
// numerator = Scalar(1)
num := big.NewInt(1)
// denominator = Scalar(1)
den := big.NewInt(1)
// for x_j in L:
for _, xj := range L {
if xj == xi {
// for x_j in L:
// if count(x_j, L) > 1:
// raise "invalid parameters"
if found {
return nil, fmt.Errorf(
"invalid parameters: xi=[%v] present more than one time in L=[%v]",
xi,
L,
)
}
found = true
// if x_j == x_i: continue
continue
}
Expand All @@ -430,21 +495,48 @@ func (s *Signer) deriveInterpolatingValue(xi uint64, L []uint64) (*big.Int, erro
den.Mod(den, order)
}

// if x_i not in L:
// raise "invalid parameters"
if !found {
return nil, fmt.Errorf(
"invalid parameters: xi=[%v] not present in L=[%v]",
xi,
L,
)
}

// value = numerator / denominator
denInv := new(big.Int).ModInverse(den, order)
res := new(big.Int).Mul(num, denInv)
res = res.Mod(res, order)

// return value
return res, nil
return res
}

// computeChallenge implements def compute_group_commitment(commitment_list,
// binding_factor_list) from [FROST] as defined in section 4.6. Signature
// Challenge Computation.
func (s *Signer) computeChallenge(
message []byte,
groupCommitment *Point,
) *big.Int {

// From [FROST]:
//
// 4.6. Signature Challenge Computation
//
// This section describes the subroutine for creating the per-message
// challenge.
//
// Inputs:
// - group_commitment, the group commitment, an Element.
// - group_public_key, the public key corresponding to the group signing
// key, an Element.
// - msg, the message to be signed, a byte string.
//
// Outputs:
// - challenge, a Scalar.
//
// def compute_group_commitment(commitment_list, binding_factor_list)

curve := s.ciphersuite.Curve()
// group_comm_enc = G.SerializeElement(group_commitment)
groupCommitmentEncoded := curve.SerializePoint(groupCommitment)
// group_public_key_enc = G.SerializeElement(group_public_key)
publicKeyEncoded := curve.SerializePoint(s.publicKey)
// challenge_input = group_comm_enc || group_public_key_enc || msg
// challenge = H2(challenge_input)
// return challenge
return s.ciphersuite.H2(groupCommitmentEncoded, publicKeyEncoded, message)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible that some suites may use something weird for H2 which would be incompatible with this, but I think for the most part this is likely to be fine. Something we may want to consider is whether H2 should take in (Point, Point, []byte) arguments instead, but it shouldn't be a blocker.

}
Loading