diff --git a/tss/ecdsa/hstmaj/ecdsaLocalhm.go b/tss/ecdsa/hstmaj/ecdsaLocalhm.go new file mode 100644 index 000000000..b898d0532 --- /dev/null +++ b/tss/ecdsa/hstmaj/ecdsaLocalhm.go @@ -0,0 +1,296 @@ +// Assumptions and Terminology +// 1. There are n parties: p_1...p_n +// 2. Every party has a label and receives a share of the secret key from the core. +// 3. Elliptic curve E(Z_p) of order q is defined as: y^2=x^3 + ax + b (mod p) +// where a, b in Z_p and Z_p is the underlying finite field for E. +// 4. We use Feldman TSS because every party needs to verify the msg from any other party. + +package hstmaj + +import ( + "crypto/rand" + "errors" + + "github.com/cloudflare/circl/secretsharing" + + "github.com/cloudflare/circl/group" +) + +// Local Sign functions + +// During online round, the metals will construct their own signature share upon receiving the message +// Input: currParty, the local party +func (currParty *partySign) LocalGenSignatureShare() { + currParty.sharesig.Share.Mul(currParty.r, currParty.sharesk.Share) + currParty.sharesig.Share.Add(currParty.sharesig.Share, currParty.hashMSG) + currParty.sharesig.Share.Mul(currParty.sharesig.Share, currParty.sharekInv.Share) +} + +// Initiate local party parameters for final round of signature generation +// Input: i, this party index +// Input: currParty, the local party +// Input: preSign, the same party with preSign informations +// Input: myGroup, the group we operate in +func (currParty *partySign) LocalInit(i uint, myGroup group.Group, preSign partyPreSign) { + currParty.myGroup = preSign.myGroup + currParty.index = i + currParty.sharekInv.ID = i + currParty.sharekInv.Share = preSign.sharekInv.Share.Copy() + currParty.r = myGroup.NewScalar() + currParty.r = preSign.r.Copy() + currParty.sharesk.ID = i + currParty.sharesk.Share = myGroup.NewScalar() + currParty.sharesk.Share.SetUint64(uint64(0)) + currParty.sharesig.ID = i + currParty.sharesig.Share = myGroup.NewScalar() + currParty.sharesig.Share.SetUint64(uint64(0)) + currParty.hashMSG = myGroup.NewScalar() +} + +// Input: currParty, the local party +// Input: sssk, the share of secret key +func (currParty *partySign) Setss(sssk group.Scalar) { + currParty.sharesk.Share = sssk.Copy() +} + +func (currParty *partySign) SetMSG(hashMSG group.Scalar) { + currParty.hashMSG = hashMSG.Copy() +} + +// Local Pre computation functions + +// Initiate local party parameters for preComputation +// Input: i, this party index +// Input: n, the number of parties +// Input: currParty, the local party +// Input: myGroup, the group we operate in +func (currParty *partyPreSign) LocalInit(i, n uint, myGroup group.Group) { + currParty.index = i + currParty.myGroup = myGroup + currParty.sharek.ID = i + currParty.sharek.Share = myGroup.NewScalar() + currParty.sharek.Share.SetUint64(uint64(0)) + currParty.shareb.ID = i + currParty.shareb.Share = myGroup.NewScalar() + currParty.shareb.Share.SetUint64(uint64(0)) + currParty.sharekb.ID = i + currParty.sharekb.Share = myGroup.NewScalar() + currParty.sharekb.Share.SetUint64(uint64(0)) + currParty.sharekInv.ID = i + currParty.sharekInv.Share = myGroup.NewScalar() + currParty.sharekInv.Share.SetUint64(uint64(0)) + currParty.sharekG = myGroup.NewElement() + currParty.obfCoefks = make([][]group.Element, n) + currParty.obfCoefbs = make([][]group.Element, n) +} + +// Generate the local party information for nonce k and blinding b, +// later will be used in Feldman secret sharing to construct shares of the nonce k and k^{-1} +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +func (currParty *partyPreSign) LocalGenkb(t, n uint) { + + // first coefficient of secret polynomial k_i for this party i + currParty.polyki = currParty.myGroup.RandomNonZeroScalar(rand.Reader) + vs, err := secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + currParty.sski, currParty.obfCoefki = vs.Shard(rand.Reader, currParty.polyki) + + // secret polynomial b_i for this party i + currParty.polybi = currParty.myGroup.RandomNonZeroScalar(rand.Reader) + vs, err = secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + // the shares of polynomial b_i for every single party in the game and the obfuscated coefficient for proving correctness + currParty.ssbi, currParty.obfCoefbi = vs.Shard(rand.Reader, currParty.polybi) + + currParty.sharek = currParty.sski[currParty.index-1] + currParty.shareb = currParty.ssbi[currParty.index-1] +} + +// Update local shares of k and b +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: sharekUpdate, update for nonce k share from other party +// Input: sharebUpdate, update for blinding b share from other party +// Input: obfCoefk, the obfuscated coefficient (commitment) as for proving correctness of sharekUpdate +// Input: obfCoefb, the obfuscated coefficient (commitment) as for proving correctness of sharebUpdate +// Input: currParty, the local party +// Input: fromIndex, the index of the party who sends this update +// Output: fromIndex if Feldman check fails, otherwise 0 +func (currParty *partyPreSign) LocalUpdatekb(t, n uint, sharekUpdate, sharebUpdate secretsharing.Share, obfCoefk, obfCoefb []group.Element, fromIndex uint) uint { + currParty.sharek.Share.Add(currParty.sharek.Share, sharekUpdate.Share) + vs, err := secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + check := vs.Verify(sharekUpdate, obfCoefk) + if !check { + return fromIndex + } + + currParty.shareb.Share.Add(currParty.shareb.Share, sharebUpdate.Share) + vs, err = secretsharing.NewVerifiable(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + check = vs.Verify(sharebUpdate, obfCoefb) + if !check { + return fromIndex + } + + return 0 +} + +// Compute shares for k*b as sharek*shareb +// Input: currParty, the local party +func (currParty *partyPreSign) LocalSharekb() { + currParty.sharekb.Share.Mul(currParty.sharek.Share, currParty.shareb.Share) +} + +// Compute [sharek]G +// Input: currParty, the local party +func (currParty *partyPreSign) LocalkG() { + currParty.sharekG.MulGen(currParty.sharek.Share) +} + +// Local party as a combiner collects shares for kb and computes kb^{-1} +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +// Input: shareskb, the shares for kb from other parties +// Output: kb^{-1} and possible error +func (currParty *partyPreSign) CombinerCompkbInv(t, n uint, shareskb []secretsharing.Share) (group.Scalar, error) { + s, err := secretsharing.New(currParty.myGroup, t, n) + if err != nil { + panic(err) + } + kb, err := s.Recover(shareskb) + kbInv := currParty.myGroup.NewScalar() + kbInv.Inv(kb) + return kbInv, err +} + +// Local party as a combiner collects shares for [sharek]G and computes [k]G +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: currParty, the local party +// Input: shareskG, the shares for [k]G from other parties +// Input: indexes, the indexes for all other parties +// Output: x coordinate of [k]G and possible error +func (currParty *partyPreSign) CombinerCompkG(t, n uint, shareskG []group.Element, indexes []group.Scalar) (group.Scalar, error) { + resultkG, errkG := lagrangeInterpolatePoint(t, currParty.myGroup, shareskG, indexes) + if errkG != nil { + return nil, errkG + } + + kGBinary, errBinary := resultkG.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + + xCoor := kGBinary[1 : currParty.myGroup.Params().ScalarLength+1] + xScalar := currParty.myGroup.NewScalar() + errBinary = xScalar.UnmarshalBinary(xCoor) + if errBinary != nil { + panic(errBinary) + } + return xScalar, nil +} + +// Set the x coordinate of [k]G as r +// Input: currParty, the local party +// Input: xCoor, the x coordinate of [k]G +func (currParty *partyPreSign) Setr(xCoor group.Scalar) { + currParty.r = xCoor.Copy() +} + +// Compute share of k^{-1} as (kb)^{-1}*shareb +// Input: currParty, the local party +// Input: kbInv, the (kb)^{-1} +func (currParty *partyPreSign) LocalSharekInv(kbInv group.Scalar) { + currParty.kbInv = kbInv.Copy() + currParty.sharekInv.Share.Mul(currParty.kbInv, currParty.shareb.Share) +} + +// Helper functions + +// Check everyone receives the same coefficient for Feldman +// Input: t, the threshold parameter +// Input: obfCoefj, the obfuscated coefficient for f_i send to party j +// Input: obfCoefk, the obfuscated coefficient for f_i send to party k +// Ouput: true if obfCoefj == obfCoefk, false otherwise. +func checkObf(t uint, obfCoefj []group.Element, obfCoefk []group.Element) bool { + check := true + for i := uint(0); i < t+1; i++ { + if !(obfCoefj[i].IsEqual(obfCoefk[i])) { + check = false + } + } + return check +} + +// Lagrange Interpolation of y as element but not scalar + +// Input: myGroup, the group we operate in +// Input: targetIndex, the i +// Input: currShare, the [y_i]G +// Input: indexes, the indexes for each party +// Output: Compute a single [f_i(0)]G +func lagrangeSinglePoint(myGroup group.Group, targetIndex int, currShare group.Element, indexes []group.Scalar) group.Element { + // f_i(0) = y_i[G] + result := currShare.Copy() + + // x_i + targetLabel := (indexes)[targetIndex].Copy() + + interValue := myGroup.NewScalar() + invValue := myGroup.NewScalar() + + for k := 0; k < len(indexes); k++ { + //f_i(0) = f_i(0) * (0-x_k)/(x_i-x_k) + if k != targetIndex { + // x_k + currLabel := (indexes)[k].Copy() + + // f_i(0) * (0-x_k) + interValue.SetUint64(uint64(0)) + interValue.Sub(interValue, currLabel) + result.Mul(result, interValue) + + // (x_i-x_k) + invValue.Sub(targetLabel, currLabel) + invValue.Inv(invValue) + result.Mul(result, invValue) + + } + } + return result +} + +// Input: t, the threshold, we need at least t+1 points for Lagrange Interpolation +// Input: myGroup, the group we operate in +// Input: ss, the secret shares multiplied by generator G +// Input: indexes, the indexes for each party +// Ouput: the re-constructed secret [f(0)]G +func lagrangeInterpolatePoint(t uint, myGroup group.Group, ss []group.Element, indexes []group.Scalar) (group.Element, error) { + if uint(len(ss)) < t+1 { + return nil, errors.New("need at least t+1 points to do Lagrange Interpolation") + } + + secret := myGroup.NewElement() + for i := 0; i < len(ss); i++ { + fi := lagrangeSinglePoint(myGroup, i, ss[i], indexes) + if i == 0 { + secret.Set(fi) + } else { + secret.Add(secret, fi) + } + } + + return secret, nil +} diff --git a/tss/ecdsa/hstmaj/ecdsahm.go b/tss/ecdsa/hstmaj/ecdsahm.go new file mode 100644 index 000000000..1c40d6541 --- /dev/null +++ b/tss/ecdsa/hstmaj/ecdsahm.go @@ -0,0 +1,113 @@ +// Assumptions and Terminology +// 1. There are n parties: p_1...p_n +// 2. Every party has a label and receives a share of the secret key from the core. +// 3. Elliptic curve E(Z_p) of order q is defined as: y^2=x^3 + ax + b (mod p) +// where a, b in Z_p and Z_p is the underlying finite field for E. +// 4. We use Feldman TSS because every party needs to verify the msg from any other party. + +// Background: +// ECDSA signing: Input secret key sk +// 1. Generate a random nonce k. +// 2. Compute k[G] and get its x coordinate as r. Back to step 1 if r = 0. +// 3. Compute s = k^{-1}(H(m)+sk*r). Back to step 1 if s = 0. +// 4. Signature is (r,s). + +// ECDSA Threshold Signature with Feldman secret sharing +// 1. Every party p_i has a share of the secret key, sharesk, and a share of the public key, shareskG: [sharesk]G. +// 2. Round 1: Parties use Feldman to jointly get shares, sharek, for nonce k. +// Parties use Feldman to jointly get shares, shareb, for a blinding b, which is used to derived k^{-1}. +// Party j needs to broadcast Feldman Coefficients received from Party i to all other parties and make sure everyone receives the same. +// Local: Parties compute shares, sharekb, for k*b locally. +// 3. Round 2: A combiner is responsible for collect sharekb and compute kb and (kb)^{-1} and broadcast `kb` to all parties. +// A combiner is responsible for collect [sharek]G and compute [k]G and broadcasts x coordinate of [k]G to all parties. +// All parties upon receiving (kb)^{-1}, compute (kb)^{-1}*shareb as share of k^{-1} +// 4. Online round 3: message hash arrived. +// To finish the online round for signature generation, we need: +// a. sharesk, shares for the secret key (core gave us). +// b. r, xoordinate of [k]G (Step3). +// c. hashMSG, the hash of message to be signed (Just arrived). +// d. sharekInv, shares of the k^{-1} (Step3). +// All above are ready, parties can locally compute the signature share, sharesig, as sharekInv*(hashMSG+sharesk*r) +// A combiner is responsible for collect sharesig and compute the final signature sig = (r, s) and verify it with the public key. +// +// Security: +// 1. Note this scheme has a major drawback: leaking of t+1 shares is enough to compromise the secret key., +// but 2t+1 parties are required to reconstruct the signature in step 3. +// 2. Any party fails to pass Feldman check in Round 1 should be marked as malicious. +// 3. Any party caught broadcasting different Feldman Coefficients should be marked as malicious. +// +// Limitation: Require at least 3 parties in the pre-computation phase and recontruction phase +package hstmaj + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "math/big" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +// Upon receiving the secret key, f0, from customer, +// Dealer/Core generates a secret polynomial, f, for further generating party shares. +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: myGroup, the group we operate in +// Input: f0, the secret key also will be the first coefficient of polynomial f +// Output: shares of the secret key for n parties +func genSecretShare(t, n uint, myGroup group.Group, f0 group.Scalar) []secretsharing.Share { + + s, err := secretsharing.New(myGroup, t, n) + if err != nil { + panic(err) + } + + shares := s.Shard(rand.Reader, f0) + + return shares +} + +func hashToInt(hash []byte, c elliptic.Curve) *big.Int { + orderBits := c.Params().N.BitLen() + orderBytes := (orderBits + 7) / 8 + if len(hash) > orderBytes { + hash = hash[:orderBytes] + } + + ret := new(big.Int).SetBytes(hash) + excess := len(hash)*8 - orderBits + if excess > 0 { + ret.Rsh(ret, uint(excess)) + } + return ret +} + +// ECDSA threshold signature verification +// Input: (r,s), the signature +// Input: hashMSG, the message +// Input: publicKey, the ECDSA public key +// Output: verification passed or not +func Verify(r, s group.Scalar, hashMSG []byte, publicKey *ecdsa.PublicKey) error { + rBig := new(big.Int) + sBig := new(big.Int) + + rByte, errBinary := r.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + rBig.SetBytes(rByte) + + sByte, errBinary := s.MarshalBinary() + if errBinary != nil { + panic(errBinary) + } + sBig.SetBytes(sByte) + + verify := ecdsa.Verify(publicKey, hashMSG, rBig, sBig) + if !verify { + return errors.New("ECDSA threshold verification failed") + } + return nil +} diff --git a/tss/ecdsa/hstmaj/ecdsahmParty.go b/tss/ecdsa/hstmaj/ecdsahmParty.go new file mode 100644 index 000000000..1c4baee52 --- /dev/null +++ b/tss/ecdsa/hstmaj/ecdsahmParty.go @@ -0,0 +1,49 @@ +package hstmaj + +import ( + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +// Party struct for Presign +type partyPreSign struct { + myGroup group.Group + index uint // index starting from 1...n. + + // nonce k + polyki group.Scalar // first coefficient of secret polynomial k_i for this party i + sski []secretsharing.Share // the shares of polynomial k_i for every single party + obfCoefki []group.Element // obfuscated coefficient for proving correctness of sski + sharek secretsharing.Share // the final share of nonce k + sharekInv secretsharing.Share // the final share of nonce k inverse + sharekG group.Element // the final share of [k]G + obfCoefks [][]group.Element // obfuscated coefficient received from other parties + + // blinding b + polybi group.Scalar // first coefficient of secret polynomial b_i for this party i + ssbi []secretsharing.Share // the shares of polynomial b_i for every single party + obfCoefbi []group.Element // obfuscated coefficient for proving correctness of ssbi + shareb secretsharing.Share // the final share of blinding b + obfCoefbs [][]group.Element // obfuscated coefficient received from other parties + + // k * b + sharekb secretsharing.Share // the final share of k*b + kbInv group.Scalar // the inverse of k*b + + // r + r group.Scalar // the x coordinate of [k]G +} + +type partySign struct { + myGroup group.Group + + index uint // index starting from 1...n. + sharesk secretsharing.Share // The share of secret key, s, for this party, generated by the server + sharekInv secretsharing.Share // the final share of nonce k inverse + sharesig secretsharing.Share // the final signature share + + // r + r group.Scalar // the x coordinate of [k]G + // message hash + hashMSG group.Scalar +} diff --git a/tss/ecdsa/hstmaj/ecdsahm_test.go b/tss/ecdsa/hstmaj/ecdsahm_test.go new file mode 100644 index 000000000..0eb99c5c8 --- /dev/null +++ b/tss/ecdsa/hstmaj/ecdsahm_test.go @@ -0,0 +1,358 @@ +package hstmaj + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "errors" + "testing" + + "github.com/cloudflare/circl/group" + "github.com/cloudflare/circl/secretsharing" +) + +const testThreshold = 2 +const benchThreshold = 1 +const benchn = 3 +const benchnPrime = 3 + +// Generate ECDSA key +func genKey(curve elliptic.Curve) (*ecdsa.PrivateKey, *ecdsa.PublicKey) { + + privateKey, err := ecdsa.GenerateKey(curve, rand.Reader) + + if err != nil { + panic(err) + } + + if privateKey == nil { + panic(err) + } + + publicKey := &privateKey.PublicKey + + if publicKey == nil { + panic(err) + } + return privateKey, publicKey + +} + +func core(t, n uint, prv *ecdsa.PrivateKey, myGroup group.Group, parties []partySign, curve elliptic.Curve) []secretsharing.Share { + + // Convert the ECDSA secret key bigint into a Scalar + secretByte := prv.D.Bytes() + secretScalar := myGroup.NewScalar() + errBinary := secretScalar.UnmarshalBinary(secretByte) + if errBinary != nil { + panic(errBinary) + } + + // Core distribute shares of secret key + sharesk := genSecretShare(t, n, myGroup, secretScalar) + return sharesk +} + +// Jointly and sequentially compute shares for the nonce k , k^{-1} as well as x coordinate of [k]G +// Input: t, the threshold parameter +// Input: n, the number of parties +// Input: myGroup, the group we operate in +// Input: parties, the parties of size n +func preSign(t, n uint, myGroup group.Group, parties []partyPreSign) error { + // Local: Parties initiate the parameters + for i := uint(0); i < n; i++ { + parties[i].LocalInit(i+1, n, myGroup) + } + + // Local: Parties generate their local information for nonce k and blinding b + for i := uint(0); i < n; i++ { + parties[i].LocalGenkb(t, n) + } + + // Round 1 + // Parties broadcast their local information for nonce k and blinding b + // From party i to party j + for i := uint(0); i < n; i++ { + for j := uint(0); j < n; j++ { + if i != j { + errorLable := parties[j].LocalUpdatekb(t, n, parties[i].sski[j], parties[i].ssbi[j], parties[i].obfCoefki, parties[i].obfCoefbi, i) + if errorLable != 0 { + return errors.New("feldman verification failed") + } + } + parties[j].obfCoefks[i] = parties[i].obfCoefki + parties[j].obfCoefbs[i] = parties[i].obfCoefbi + } + } + + // Party j broadcast feldman coefficient received from party i to all other parties and everyone confirm they receive the same + for j := uint(0); j < n; j++ { + for i := uint(0); i < n; i++ { + // party j sends feldman coefficient received from party i to party l!=i or j + if i != j { + for l := uint(0); l < n; l++ { + if (l != i) && (l != j) { + check := checkObf(t, parties[j].obfCoefbs[i], parties[l].obfCoefbs[i]) + if !check { + return errors.New("broadcasting feldman coefficient failed") + } + check = checkObf(t, parties[j].obfCoefks[i], parties[l].obfCoefks[i]) + if !check { + return errors.New("broadcasting feldman coefficient failed") + } + } + } + } + + } + } + + // Local: Parties compute shares for k*b and [sharek]G + sskb := make([]secretsharing.Share, n) + for i := uint(0); i < n; i++ { + parties[i].LocalSharekb() + parties[i].LocalkG() + sskb[i].ID = parties[i].sharekb.ID + sskb[i].Share = parties[i].sharekb.Share.Copy() + } + + // Round 2 + // A combiner, assume party 0, combines shares for kb and compute (kb)^{-1} + if n < (2*t + 1) { + return errors.New("at least 2t+1 parties are required for computing multiplication") + } + + kbInv, err := parties[0].CombinerCompkbInv(t, n, sskb) + if err != nil { + return err + } + + // A combiner, assume party 0, combines shares for [sharek]G, compute [k]G + sskG := make([]group.Element, n) + indexes := make([]group.Scalar, n) + for i := uint(0); i < n; i++ { + sskG[i] = parties[i].sharekG + indexes[i] = myGroup.NewScalar() + indexes[i].SetUint64(uint64(parties[i].index)) + } + + xCoor, err := parties[0].CombinerCompkG(t, n, sskG, indexes) + if err != nil { + return err + } + + // Combiner informs all other party of (kb)^{-1}, and all party computes (kb)^{-1}*shareb as share of k^{-1} + // Combiner broadcasts x coordinate of [k]G + for i := uint(0); i < n; i++ { + parties[i].Setr(xCoor) + } + + for i := uint(0); i < n; i++ { + parties[i].LocalSharekInv(kbInv) + } + + return nil +} + +// During online round, all metals construct their own signature share upon receiving the message +// Input: n, the number of parties +// Input: myGroup, the group we operate in +func genSignatureShare(n uint, myGroup group.Group, parties []partySign) { + for i := uint(0); i < n; i++ { + parties[i].LocalGenSignatureShare() + } +} + +// ECDSA threshold signature generation +// Input: t, the threshold parameter +// Input: nPrime, the number of parties involved in the final signature generation +// Input: myGroup, the group we operate in +// Input: parties, the parties of size n +// Input: msg, the message hash +// curve: curve, the curve we operate in +// Output: signature (r,s) or possible error +func sign(t, nPrime uint, myGroup group.Group, sharesk []secretsharing.Share, parties []partySign, preParties []partyPreSign, msg []byte, curve elliptic.Curve) (group.Scalar, group.Scalar, error) { + + // Local: Parties initiate the parameters + for i := uint(0); i < nPrime; i++ { + parties[i].LocalInit(i+1, myGroup, preParties[i]) + parties[i].Setss(sharesk[i].Share) + } + + msgBig := hashToInt(msg, curve) + msgByte := msgBig.Bytes() + + hashScalar := myGroup.NewScalar() + errBinary := hashScalar.UnmarshalBinary(msgByte) + if errBinary != nil { + panic(errBinary) + } + // Every party gets the hash Scalar + for i := uint(0); i < nPrime; i++ { + parties[i].SetMSG(hashScalar) + } + + // Parties generate signature online round 3 + genSignatureShare(nPrime, myGroup, parties) + + // A combiner interpolate the signature + sigShares := make([]secretsharing.Share, nPrime) + for i := uint(0); i < nPrime; i++ { + sigShares[i].ID = parties[i].sharesig.ID + sigShares[i].Share = parties[i].sharesig.Share.Copy() + } + s, err := secretsharing.New(parties[0].myGroup, t, nPrime) + if err != nil { + panic(err) + } + signature, err := s.Recover(sigShares) + if err != nil { + return nil, nil, err + } + return parties[0].r, signature, nil +} + +func testECDSAThresholdSingle(t, n, nPrime uint, myGroup group.Group, curve elliptic.Curve, prv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) error { + + // Construct parties + parties := make([]partyPreSign, n) + + // PreSign: Precomputation + errPreSign := preSign(t, n, myGroup, parties) + + if errPreSign != nil { + return errPreSign + } + + // Sign the message + + // Construct parties for signing + partiesSign := make([]partySign, n) + + // Core generates secret shares for every party + sharesk := core(t, n, prv, myGroup, partiesSign, curve) + + msg := []byte("Cloudflare: meow meow") + r, s, errSign := sign(t, nPrime, myGroup, sharesk, partiesSign, parties, msg, curve) + if errSign != nil { + return errSign + } + + // Verify the signature + errVerify := Verify(r, s, msg, pub) + + if errVerify != nil { + return errVerify + } + + return nil +} + +func testECDSAThreshold(t *testing.T, threshold, n, nPrime uint, myGroup group.Group, curve elliptic.Curve) { + prv, pub := genKey(curve) + err := testECDSAThresholdSingle(threshold, n, nPrime, myGroup, curve, prv, pub) + if n < 2*threshold+1 { + if err == nil { + t.Error("Less than 2t+1 parties should fail") + } + } else { + if nPrime < 2*threshold+1 { + if err == nil { + t.Error("Signature generation should fail with less than 2t+1 parties") + } + } else { + if err != nil { + t.Error("Signature generation fail") + } + } + } +} + +func benchECDSAThreshold(b *testing.B, myGroup group.Group, curve elliptic.Curve) { + + prv, pub := genKey(curve) + + // Construct parties + parties := make([]partyPreSign, benchn) + + // Bench PreSign + b.Run(curve.Params().Name+"-PreSign", func(b *testing.B) { + for i := 0; i < b.N; i++ { + errPreSign := preSign(benchThreshold, benchn, myGroup, parties) + if errPreSign != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + } + }) + + // PreSign: Precomputation + errPreSign := preSign(benchThreshold, benchn, myGroup, parties) + + if errPreSign != nil { + b.Error("Bench ECDSA TSS Precomputation FAIL!") + } + + // Construct parties for signing + partiesSign := make([]partySign, benchn) + // Core generates secret shares for every party + sharesk := core(benchThreshold, benchn, prv, myGroup, partiesSign, curve) + msg := []byte("Cloudflare: meow meow") + + // Bench Sign + b.Run(curve.Params().Name+"-Sign", func(b *testing.B) { + for i := 0; i < b.N; i++ { + + r, s, errSign := sign(benchThreshold, benchnPrime, myGroup, sharesk, partiesSign, parties, msg, curve) + if errSign != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + + errVerify := Verify(r, s, msg, pub) + if errVerify != nil { + b.Error("Bench ECDSA TSS FAIL!") + } + } + }) + +} + +func TestECDSAThreshold(t *testing.T) { + for threshold := uint(1); threshold <= testThreshold; threshold++ { + for n := threshold + 1; n < 3*threshold+1; n++ { + for nPrime := threshold + 1; nPrime <= n; nPrime++ { + + t.Run("ECDSATSS256", func(t *testing.T) { + pubkeyCurve := elliptic.P256() + curr_group := group.P256 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + t.Run("ECDSATSS384", func(t *testing.T) { + pubkeyCurve := elliptic.P384() + curr_group := group.P384 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + + t.Run("ECDSATSS521", func(t *testing.T) { + pubkeyCurve := elliptic.P521() + curr_group := group.P521 + testECDSAThreshold(t, threshold, n, nPrime, curr_group, pubkeyCurve) + }) + } + } + } +} + +func BenchmarkECDSASign256(b *testing.B) { + pubkeyCurve := elliptic.P256() + curr_group := group.P256 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + + pubkeyCurve = elliptic.P384() + curr_group = group.P384 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + + pubkeyCurve = elliptic.P521() + curr_group = group.P521 + benchECDSAThreshold(b, curr_group, pubkeyCurve) + +}