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

Implemented deriveInterpolatingValue function #7

Merged
merged 3 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions frost/bip340.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ func (bc *Bip340Curve) Identity() *Point {
return &Point{big.NewInt(0), big.NewInt(0)}
}

// Order returns the order of the elliptic curve.
func (bc *Bip340Curve) Order() *big.Int {
return new(big.Int).Set(bc.N)
}

// IsPointOnCurve validates if the point lies on the curve and is not an
// identity element.
func (bc *Bip340Curve) IsPointOnCurve(p *Point) bool {
Expand Down
3 changes: 3 additions & 0 deletions frost/ciphersuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ type Curve interface {
// Identity returns elliptic curve identity element.
Identity() *Point

// Order returns the order of the elliptic curve.
Order() *big.Int
Copy link
Contributor

Choose a reason for hiding this comment

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

For some potential future implementations, this comment will need to be amended to "returns the order of the group produced by the generator". On some curves the generator produces a prime-order subgroup, whose order is not the same as that of the entire curve.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a really good point. Improved in e1c912c.


// IsPointOnCurve validates if the point lies on the curve and is not an
// identity element.
//
Expand Down
86 changes: 85 additions & 1 deletion frost/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ func (s *Signer) computeGroupCommitment(
}

// encodeGroupCommitment implements def encode_group_commitment_list(commitment_list)
// function from [FROST], as defined in section 4.3. List Operations.
// function from [FROST], as defined in section 4.3. List Operations.
//
// The function calling encodeGroupCommitment must ensure a valid number of
// commitments have been received and call validateGroupCommitment to validate
Expand Down Expand Up @@ -364,3 +364,87 @@ func (s *Signer) encodeGroupCommitment(commitments []*NonceCommitment) []byte {
// return encoded_group_commitment
return b
}

// deriveInterpolatingValue implements def derive_interpolating_value(L, x_i)
// 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) {
// From [FROST]:
//
// 4.2. Polynomials
//
// This section defines polynomials over Scalars that are used in the
// main protocol. A polynomial of maximum degree t is represented as a
// list of t+1 coefficients, where the constant term of the polynomial
// is in the first position and the highest-degree coefficient is in the
// last position. For example, the polynomial x^2 + 2x + 3 has degree 2
// and is represented as a list of 3 coefficients [3, 2, 1]. A point on
// the polynomial f is a tuple (x, y), where y = f(x).
//
// The function derive_interpolating_value derives a value used for
// polynomial interpolation. It is provided a list of x-coordinates as
// input, each of which cannot equal 0.
//
// Inputs:
// - L, the list of x-coordinates, each a NonZeroScalar.
// - x_i, an x-coordinate contained in L, a NonZeroScalar.
//
// Outputs:
// - value, a Scalar.
//
// Errors:
// - "invalid parameters", if 1) x_i is not in L, or if 2) any
// x-coordinate is represented more than once in L.
//
// def derive_interpolating_value(L, x_i):

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't actually implement the specified functionality. We need to expect that the indices L are sorted in ascending order, with no repeated instances, and keep track of the previous x_j'. Comparing x_j to x_j' and requiring that x_j > x_j' gives the specified functionality. This was a genuine oversight in the prototype.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's true though looking at Round2 implementation in #8 I do wonder if we need to worry about it in deriveInterpolatingValue at all. validateGroupCommitments makes sure that all commitments are sorted in ascending order and there is no duplicates.

We could improve unit tests for validateGroupCommitments to cover duplicates and make it clear in deriveInterpolatingValue that the validation must happen before this function is called.

This would allow us to eliminate this error handling:

roast-go/frost/signer.go

Lines 120 to 125 in 5795702

// lambda_i = derive_interpolating_value(participant_list, identifier)
lambda, err := s.deriveInterpolatingValue(s.signerIndex, participants)
if err != nil {
// should never be the case for properly validated group commitments
return nil, err
}

and validateGroupCommitments would be the only function returning errors in Round2. This wouldn't be 1:1 with the paper but this solution feels quite elegant to me.

What do you think about it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Regardless of the decision here, I added a test case to validateGroupCommitments unit tests covering the duplicate element case: 1398d05.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think if we note in a comment that validateGroupCommitments is a necessary part for checking the correctness, it should be fine.

Copy link
Member Author

Choose a reason for hiding this comment

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

I will handle it in #8.

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
}
// numerator *= x_j
num.Mul(num, big.NewInt(int64(xj)))
num.Mod(num, order)
// denominator *= x_j - x_i
den.Mul(den, big.NewInt(int64(xj)-int64(xi)))
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
}
110 changes: 110 additions & 0 deletions frost/signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,116 @@ func TestEncodeGroupCommitments(t *testing.T) {
)
}

func TestDeriveInterpolatingValue(t *testing.T) {
var tests = map[string]struct {
xi uint64
L []uint64
expected string
}{
// Lagrange coefficient l_0 is:
//
// (x-4)(x-5)
// l_0 = ----------
// (1-4)(1-5)
//
// Since x is always 0 for this function, l_0 = 20/12 (mod Q).
//
// Then we calculate ((12^-1 mod Q) * 20) mod Q
// where Q is the order of secp256k1.
//
"xi = 1, L = {1, 4, 5}": {
xi: 1,
L: []uint64{1, 4, 5},
expected: "38597363079105398474523661669562635950945854759691634794201721047172720498114",
},
// Lagrange coefficient l_1 is:
//
// (x-1)(x-5)
// l_1 = ----------
// (4-1)(4-5)
//
// Since x is always 0 for this function, l_0 = 5/-3 (mod Q).
// Given the negative denominator and mod Q, the number will be
// l_0 = 5/(Q-3).
//
// Then we calculate (((Q-3)^-1 mod Q) * 5) mod Q
// where Q is the order of secp256k1.
//
"xi = 4, L = {1, 4, 5}": {
xi: 4,
L: []uint64{1, 4, 5},
expected: "77194726158210796949047323339125271901891709519383269588403442094345440996223",
},
// Lagrange coefficient l_2 is:
//
// (x-1)(x-4)
// l_1 = ----------
// (5-1)(5-4)
//
// Since x is always 0 for this function, l_0 = 4/4 (mod Q).
//
// Then we calculate ((1^-1 mod Q) * 1) mod Q
// where Q is the order of secp256k1.
//
"xi = 5, L = {1, 4, 5}": {
xi: 5,
L: []uint64{1, 4, 5},
expected: "1",
},
}

signer := createSigners(t)[0]
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
result, err := signer.deriveInterpolatingValue(test.xi, test.L)
if err != nil {
t.Fatal(err)
}
testutils.AssertStringsEqual(
t,
"interpolating value",
test.expected,
result.Text(10),
)
})
}
}

func TestDeriveInterpolatingValue_InvalidParameters(t *testing.T) {
var tests = map[string]struct {
xi uint64
L []uint64
expectedErr string
}{
"xi present more than one time in L": {
xi: 5,
L: []uint64{1, 4, 5, 5},
expectedErr: "invalid parameters: xi=[5] present more than one time in L=[[1 4 5 5]]",
},
"xi not present in L": {
xi: 3,
L: []uint64{1, 4, 5},
expectedErr: "invalid parameters: xi=[3] not present in L=[[1 4 5]]",
},
}

signer := createSigners(t)[0]
for testName, test := range tests {
t.Run(testName, func(t *testing.T) {
_, err := signer.deriveInterpolatingValue(test.xi, test.L)
if err == nil {
t.Fatalf("expected a non-nil error")
}
testutils.AssertStringsEqual(
t,
"parameters error",
test.expectedErr,
err.Error(),
)
})
}
}

func createSigners(t *testing.T) []*Signer {
var signers []*Signer

Expand Down