diff --git a/frost/bip340.go b/frost/bip340.go index 9cd3285..459bfda 100644 --- a/frost/bip340.go +++ b/frost/bip340.go @@ -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 { diff --git a/frost/ciphersuite.go b/frost/ciphersuite.go index 9e2332c..712c3e3 100644 --- a/frost/ciphersuite.go +++ b/frost/ciphersuite.go @@ -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. // diff --git a/frost/signer.go b/frost/signer.go index a2cc859..1300334 100644 --- a/frost/signer.go +++ b/frost/signer.go @@ -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 @@ -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 +} diff --git a/frost/signer_test.go b/frost/signer_test.go index 242766d..4321979 100644 --- a/frost/signer_test.go +++ b/frost/signer_test.go @@ -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 @@ -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) { @@ -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