-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from threshold-network/roundtrip-frost
Roundtrip integration test for FROST
- Loading branch information
Showing
5 changed files
with
243 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package frost | ||
|
||
import ( | ||
"crypto/rand" | ||
"fmt" | ||
"math/big" | ||
"testing" | ||
|
||
"threshold.network/roast/internal/testutils" | ||
) | ||
|
||
var ciphersuite = NewBip340Ciphersuite() | ||
var threshold = 51 | ||
var groupSize = 100 | ||
|
||
func TestFrostRoundtrip(t *testing.T) { | ||
message := []byte("For even the very wise cannot see all ends") | ||
|
||
tests := map[string]struct { | ||
numberOfSigners int | ||
}{ | ||
"the entire group": { | ||
numberOfSigners: groupSize, | ||
}, | ||
"threshold of the group": { | ||
numberOfSigners: threshold, | ||
}, | ||
} | ||
|
||
for testName, test := range tests { | ||
t.Run(testName, func(t *testing.T) { | ||
signers := createSigners(t)[:test.numberOfSigners] | ||
publicKey := signers[0].publicKey | ||
|
||
isSignatureValid := false | ||
maxAttempts := 5 | ||
|
||
// From [BIP-340]: | ||
// Let k' = int(rand) mod n[13]. | ||
// Fail if k' = 0. | ||
// Let R = k'⋅G. | ||
// Let k = k' if has_even_y(R), otherwise let k = n - k' . | ||
// | ||
// Although it is easy to address similar requirement for `d` by | ||
// inverting the secret key earlier in the test, for `k` the | ||
// situation is more complicated because it is generated by [FROST]. | ||
// For now, we just retry. | ||
// | ||
// See https://github.com/threshold-network/roast-go/issues/12 | ||
for i := 0; !isSignatureValid && i < maxAttempts; i++ { | ||
nonces, commitments := executeRound1(t, signers) | ||
signatureShares := executeRound2(t, signers, message, nonces, commitments) | ||
|
||
coordinator := NewCoordinator(ciphersuite, publicKey) | ||
signature, err := coordinator.Aggregate(message, commitments, signatureShares) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
isSignatureValid, err = ciphersuite.VerifySignature( | ||
signature, | ||
publicKey, | ||
message, | ||
) | ||
if err != nil { | ||
fmt.Printf( | ||
"signature verification error on attempt [%v]: [%v]\n", | ||
i, | ||
err, | ||
) | ||
} | ||
} | ||
|
||
testutils.AssertBoolsEqual( | ||
t, | ||
"signature verification result", | ||
true, | ||
isSignatureValid, | ||
) | ||
}) | ||
} | ||
} | ||
|
||
func createSigners(t *testing.T) []*Signer { | ||
curve := ciphersuite.Curve() | ||
order := curve.Order() | ||
|
||
secretKey, err := rand.Int(rand.Reader, order) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
publicKey := curve.EcBaseMul(secretKey) | ||
|
||
// From [BIP-340]: | ||
// Let d' = int(sk) | ||
// Fail if d' = 0 or d' ≥ n | ||
// Let P = d'⋅G | ||
// Let d = d' if has_even_y(P), otherwise let d = n - d' . | ||
if publicKey.Y.Bit(0) != 0 { // is Y even? | ||
secretKey.Sub(order, secretKey) | ||
publicKey = curve.EcBaseMul(secretKey) | ||
} | ||
|
||
keyShares := testutils.GenerateKeyShares( | ||
secretKey, | ||
groupSize, | ||
threshold, | ||
order, | ||
) | ||
|
||
signers := make([]*Signer, groupSize) | ||
|
||
for i := 0; i < groupSize; i++ { | ||
j := i + 1 | ||
signers[i] = NewSigner(ciphersuite, uint64(j), publicKey, keyShares[i]) | ||
} | ||
|
||
return signers | ||
} | ||
|
||
func executeRound1( | ||
t *testing.T, | ||
signers []*Signer, | ||
) ([]*Nonce, []*NonceCommitment) { | ||
nonces := make([]*Nonce, len(signers)) | ||
commitments := make([]*NonceCommitment, len(signers)) | ||
|
||
for i, signer := range signers { | ||
n, c, err := signer.Round1() | ||
if err != nil { | ||
t.Fatal(t) | ||
} | ||
|
||
nonces[i] = n | ||
commitments[i] = c | ||
} | ||
|
||
return nonces, commitments | ||
} | ||
|
||
func executeRound2( | ||
t *testing.T, | ||
signers []*Signer, | ||
message []byte, | ||
nonces []*Nonce, | ||
nonceCommitments []*NonceCommitment, | ||
) []*big.Int { | ||
signatureShares := make([]*big.Int, len(signers)) | ||
|
||
for i, signer := range signers { | ||
signatureShare, err := signer.Round2(message, nonces[i], nonceCommitments) | ||
if err != nil { | ||
t.Fatal(t) | ||
} | ||
|
||
signatureShares[i] = signatureShare | ||
} | ||
|
||
return signatureShares | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package testutils | ||
|
||
import ( | ||
"crypto/rand" | ||
"math/big" | ||
) | ||
|
||
// GenerateKeyShares generates a secret key and secret key shares for the group | ||
// of the given size with the required signing threshold. | ||
func GenerateKeyShares( | ||
secretKey *big.Int, | ||
groupSize int, | ||
threshold int, | ||
order *big.Int, | ||
) []*big.Int { | ||
coefficients := generatePolynomial(secretKey, threshold, order) | ||
|
||
secretKeyShares := make([]*big.Int, groupSize) | ||
for i := 0; i < groupSize; i++ { | ||
j := i + 1 | ||
secretKeyShares[i] = calculatePolynomial( | ||
coefficients, | ||
j, | ||
order, | ||
) | ||
} | ||
|
||
return secretKeyShares | ||
} | ||
|
||
// generatePolynomial generates a polynomial of degree equal to `threshold` with | ||
// random coefficients, not higher than the group `order`. | ||
func generatePolynomial( | ||
secretKey *big.Int, | ||
threshold int, | ||
order *big.Int, | ||
) []*big.Int { | ||
arr := make([]*big.Int, threshold) | ||
arr[0] = secretKey | ||
for i := 1; i < threshold; i++ { | ||
random, err := rand.Int(rand.Reader, order) | ||
if err != nil { | ||
panic(err) | ||
} | ||
arr[i] = random | ||
} | ||
|
||
return arr | ||
} | ||
|
||
// calculatePolynomial calculates the polynomial value for the given `x` modulo | ||
// group `order`. Polynomial `coefficients` need to be passed as parameters. | ||
func calculatePolynomial( | ||
coefficients []*big.Int, | ||
x int, | ||
order *big.Int, | ||
) *big.Int { | ||
result := new(big.Int) | ||
|
||
bigX := big.NewInt(int64(x)) | ||
|
||
for i, c := range coefficients { | ||
tmp := new(big.Int).Exp(bigX, big.NewInt(int64(i)), order) | ||
tmp.Mul(tmp, c) | ||
result.Add(result, tmp) | ||
} | ||
|
||
return new(big.Int).Mod(result, order) | ||
} |