Skip to content

Commit

Permalink
Merge pull request #7 from threshold-network/polynomials
Browse files Browse the repository at this point in the history
Implemented deriveInterpolatingValue function
  • Loading branch information
eth-r authored Dec 22, 2023
2 parents 132bf56 + 1398d05 commit 52d5838
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 7 deletions.
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 group produced by the elliptic curve generator.
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
4 changes: 4 additions & 0 deletions frost/ciphersuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ type Curve interface {
// Identity returns elliptic curve identity element.
Identity() *Point

// Order returns the order of the group produced by the elliptic curve
// generator.
Order() *big.Int

// 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 {
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
}
126 changes: 120 additions & 6 deletions frost/signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ func TestValidateGroupCommitments_Errors(t *testing.T) {
signers := createSigners(t)
_, commitments := executeRound1(t, signers)

tmp := commitments[31]
// duplicate commitment from signer 5 at positions 4 and 5
commitments[5] = commitments[4]
// at the position where we'd expect a commitment from signer 32 we have
// a commitment from signer 51
tmp := commitments[31]
commitments[31] = commitments[50]
// at the position where we'd expect a commitment from signer 51 we have
// a commitment from signer 32
Expand All @@ -59,16 +61,18 @@ func TestValidateGroupCommitments_Errors(t *testing.T) {

validationErrors := signer.validateGroupCommitments(commitments)

expectedError1 := "commitments not sorted in ascending order: commitments[31].signerIndex=51, commitments[32].signerIndex=33"
expectedError2 := "commitments not sorted in ascending order: commitments[49].signerIndex=50, commitments[50].signerIndex=32"
expectedError3 := "binding nonce commitment from signer [81] is not a valid non-identity point on the curve: [Point[X=0x64, Y=0xc8]]"
expectedError4 := "hiding nonce commitment from signer [100] is not a valid non-identity point on the curve: [Point[X=0x12c, Y=0x190]]"
expectedError1 := "commitments not sorted in ascending order: commitments[4].signerIndex=5, commitments[5].signerIndex=5"
expectedError2 := "commitments not sorted in ascending order: commitments[31].signerIndex=51, commitments[32].signerIndex=33"
expectedError3 := "commitments not sorted in ascending order: commitments[49].signerIndex=50, commitments[50].signerIndex=32"
expectedError4 := "binding nonce commitment from signer [81] is not a valid non-identity point on the curve: [Point[X=0x64, Y=0xc8]]"
expectedError5 := "hiding nonce commitment from signer [100] is not a valid non-identity point on the curve: [Point[X=0x12c, Y=0x190]]"

testutils.AssertIntsEqual(t, "number of validation errors", 4, len(validationErrors))
testutils.AssertIntsEqual(t, "number of validation errors", 5, len(validationErrors))
testutils.AssertStringsEqual(t, "validation error #1", expectedError1, validationErrors[0].Error())
testutils.AssertStringsEqual(t, "validation error #2", expectedError2, validationErrors[1].Error())
testutils.AssertStringsEqual(t, "validation error #3", expectedError3, validationErrors[2].Error())
testutils.AssertStringsEqual(t, "validation error #4", expectedError4, validationErrors[3].Error())
testutils.AssertStringsEqual(t, "validation error #5", expectedError5, validationErrors[4].Error())
}

func TestEncodeGroupCommitments(t *testing.T) {
Expand Down Expand Up @@ -122,6 +126,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

0 comments on commit 52d5838

Please sign in to comment.