From 1d3e7db3792dc51ef76dcc99c030dad7d4f64f54 Mon Sep 17 00:00:00 2001
From: Mykyta Zhelnov <jelnoff96@gmail.com>
Date: Sun, 19 Feb 2023 19:42:13 +0200
Subject: [PATCH] crypto/tss/recovery: add private key recovery from dkg
 results

---
 crypto/tss/recovery/recover_private_key.go    |  73 ++++++
 .../tss/recovery/recover_private_key_test.go  | 231 ++++++++++++++++++
 2 files changed, 304 insertions(+)
 create mode 100644 crypto/tss/recovery/recover_private_key.go
 create mode 100644 crypto/tss/recovery/recover_private_key_test.go

diff --git a/crypto/tss/recovery/recover_private_key.go b/crypto/tss/recovery/recover_private_key.go
new file mode 100644
index 00000000..f5f8af0d
--- /dev/null
+++ b/crypto/tss/recovery/recover_private_key.go
@@ -0,0 +1,73 @@
+package recovery
+
+import (
+	"crypto/ecdsa"
+	"errors"
+	"fmt"
+	"math/big"
+
+	"github.com/getamis/alice/crypto/birkhoffinterpolation"
+	"github.com/getamis/alice/crypto/ecpointgrouplaw"
+	"github.com/getamis/alice/crypto/elliptic"
+	"github.com/getamis/alice/crypto/utils"
+)
+
+var (
+	ErrNotEnoughPeers = errors.New("not enough input peers, need at least 2")
+	ErrAbsentCurve    = errors.New("curve is nil")
+	ErrPubKeyMismatch = errors.New("pubkey derived from recovered privkey is not equal to pubkey provided")
+)
+
+type RecoveryPeer struct {
+	share *big.Int
+	bk    *birkhoffinterpolation.BkParameter
+}
+
+func RecoverPrivateKey(curve elliptic.Curve, threshold uint32, pubKey *ecpointgrouplaw.ECPoint, peers []RecoveryPeer) (*ecdsa.PrivateKey, error) {
+	peerNum := len(peers)
+	if peerNum < 2 {
+		return nil, ErrNotEnoughPeers
+	}
+	if curve == nil {
+		return nil, ErrAbsentCurve
+	}
+	if err := utils.EnsureThreshold(threshold, uint32(peerNum)); err != nil {
+		return nil, err
+	}
+
+	bks := make([]*birkhoffinterpolation.BkParameter, 0, peerNum)
+	shares := make([]*big.Int, 0, peerNum)
+	for _, peer := range peers {
+		shares = append(shares, peer.share)
+		bks = append(bks, peer.bk)
+	}
+
+	fieldOrder := curve.Params().N
+	bksInterface := birkhoffinterpolation.BkParameters(bks)
+
+	if err := bksInterface.CheckValid(threshold, fieldOrder); err != nil {
+		return nil, fmt.Errorf("BKS are incorrect: %w", err)
+	}
+	coefs, err := bksInterface.ComputeBkCoefficient(threshold, fieldOrder)
+	if err != nil {
+		return nil, err
+	}
+
+	privKeyBigInt := big.NewInt(0)
+	for i, coef := range coefs {
+		privKeyBigInt.Add(privKeyBigInt, new(big.Int).Mul(coef, shares[i]))
+	}
+	privKeyBigInt.Mod(privKeyBigInt, fieldOrder)
+
+	derivedPubKey := ecpointgrouplaw.NewBase(curve).ScalarMult(privKeyBigInt)
+	if !derivedPubKey.Equal(pubKey) {
+		return nil, ErrPubKeyMismatch
+	}
+
+	privKey := &ecdsa.PrivateKey{
+		PublicKey: *derivedPubKey.ToPubKey(),
+		D:         privKeyBigInt,
+	}
+
+	return privKey, nil
+}
diff --git a/crypto/tss/recovery/recover_private_key_test.go b/crypto/tss/recovery/recover_private_key_test.go
new file mode 100644
index 00000000..2a5172bc
--- /dev/null
+++ b/crypto/tss/recovery/recover_private_key_test.go
@@ -0,0 +1,231 @@
+package recovery
+
+import (
+	"math/big"
+	"testing"
+
+	"crypto/ecdsa"
+	"crypto/rand"
+
+	"github.com/decred/dcrd/dcrec/edwards"
+	"github.com/getamis/alice/crypto/birkhoffinterpolation"
+	"github.com/getamis/alice/crypto/ecpointgrouplaw"
+	"github.com/getamis/alice/crypto/elliptic"
+	"github.com/getamis/alice/crypto/utils"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/ginkgo/extensions/table"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("Private Key Recovery", func() {
+
+	Context("validation", func() {
+		It("should fail on 0 peers provided", func() {
+			result, err := RecoverPrivateKey(elliptic.Secp256k1(), 2, nil, []RecoveryPeer{})
+			Expect(result).Should(BeNil())
+			Expect(err).Should(MatchError(ErrNotEnoughPeers))
+		})
+		It("should fail on 1 peer provided", func() {
+			result, err := RecoverPrivateKey(elliptic.Secp256k1(), 2, nil, []RecoveryPeer{{}})
+			Expect(result).Should(BeNil())
+			Expect(err).Should(MatchError(ErrNotEnoughPeers))
+		})
+		It("should fail if no curve provided", func() {
+			result, err := RecoverPrivateKey(nil, 2, nil, []RecoveryPeer{{}, {}, {}})
+			Expect(result).Should(BeNil())
+			Expect(err).Should(MatchError(ErrAbsentCurve))
+		})
+		It("should fail on invalid threshold provided", func() {
+			result, err := RecoverPrivateKey(elliptic.Secp256k1(), 3, nil, []RecoveryPeer{{}, {}})
+			Expect(result).Should(BeNil())
+			Expect(err).Should(MatchError(utils.ErrLargeThreshold))
+			result, err = RecoverPrivateKey(elliptic.Secp256k1(), 1, nil, []RecoveryPeer{{}, {}})
+			Expect(result).Should(BeNil())
+			Expect(err).Should(MatchError(utils.ErrSmallThreshold))
+		})
+		It("should fail on invalid BKs", func() {
+			result, err := RecoverPrivateKey(elliptic.Secp256k1(), 2, nil, []RecoveryPeer{
+				{
+					share: nil,
+					bk:    birkhoffinterpolation.NewBkParameter(nil, 0),
+				},
+				{
+					share: nil,
+					bk:    birkhoffinterpolation.NewBkParameter(nil, 0),
+				},
+				{
+					share: nil,
+					bk:    birkhoffinterpolation.NewBkParameter(nil, 0),
+				},
+			})
+			Expect(result).Should(BeNil())
+			Expect(err.Error()).Should(ContainSubstring("BKS are incorrect: invalid bks"))
+		})
+		It("should fail on invalid pubkey to match", func() {
+			result, err := RecoverPrivateKey(elliptic.Secp256k1(), 2, nil, MakeRecoveryPeers(
+				[]string{
+					"104609342634350601677472000055166148093040084008779475605306409018125790763384",
+					"24163161798290927046425102830821018901476566166011270959061926968896994036828",
+					"63066954971367271319238473967771679083666721823457348774233036722916669993451",
+				},
+				[]string{
+					"112236885864076099358310462008741642913349768825204638119688077310570757734766",
+					"20713194488405082366064300662102750959499818497887478230897041769232303982022",
+					"28909585968450592089400243672753269836965419090880450440411327286593499063726",
+				},
+			))
+			Expect(result).Should(BeNil())
+			Expect(err).Should(MatchError(ErrPubKeyMismatch))
+		})
+	})
+
+	DescribeTable(
+		"RecoverPrivateKey() ecdsa",
+		func(curve elliptic.Curve, threshold int, testDkgData []RecoveryPeer, pubKey *ecpointgrouplaw.ECPoint) {
+			privKey, err := RecoverPrivateKey(curve, uint32(threshold), pubKey, testDkgData)
+			if err != nil {
+				Expect(err).Should(BeNil())
+			}
+
+			data := []byte("some tx hash to sign")
+			r, s, err := ecdsa.Sign(rand.Reader, privKey, data)
+			Expect(err).Should(BeNil())
+
+			Expect(ecdsa.Verify(&privKey.PublicKey, data, r, s)).Should(BeTrue())
+		},
+		Entry(
+			"3/3 quorum",
+			elliptic.Secp256k1(),
+			2,
+			MakeRecoveryPeers(
+				[]string{
+					"104609342634350601677472000055166148093040084008779475605306409018125790763384",
+					"24163161798290927046425102830821018901476566166011270959061926968896994036828",
+					"63066954971367271319238473967771679083666721823457348774233036722916669993451",
+				},
+				[]string{
+					"112236885864076099358310462008741642913349768825204638119688077310570757734766",
+					"20713194488405082366064300662102750959499818497887478230897041769232303982022",
+					"28909585968450592089400243672753269836965419090880450440411327286593499063726",
+				},
+			),
+			MakePubKey(
+				"24951056819363353476818025996777971284120929729704886050366724870604080939790",
+				"47651179196288923110559855714961823695288337160431011508811998037385251801902",
+				elliptic.Secp256k1(),
+			),
+		),
+		Entry(
+			"2/3 quorum",
+			elliptic.Secp256k1(),
+			2,
+			MakeRecoveryPeers(
+				[]string{
+					"104609342634350601677472000055166148093040084008779475605306409018125790763384",
+					"63066954971367271319238473967771679083666721823457348774233036722916669993451",
+				},
+				[]string{
+					"112236885864076099358310462008741642913349768825204638119688077310570757734766",
+					"28909585968450592089400243672753269836965419090880450440411327286593499063726",
+				},
+			),
+			MakePubKey(
+				"24951056819363353476818025996777971284120929729704886050366724870604080939790",
+				"47651179196288923110559855714961823695288337160431011508811998037385251801902",
+				elliptic.Secp256k1(),
+			),
+		),
+	)
+
+	DescribeTable(
+		"RecoverPrivateKey() eddsa",
+		func(curve elliptic.Curve, threshold int, testDkgData []RecoveryPeer, pubKey *ecpointgrouplaw.ECPoint) {
+			privKey, err := RecoverPrivateKey(curve, uint32(threshold), pubKey, testDkgData)
+			if err != nil {
+				Expect(err).Should(BeNil())
+			}
+
+			data := []byte("some tx hash to sign")
+
+			priv, pub, err := edwards.PrivKeyFromScalar(edwards.Edwards(), privKey.D.Bytes())
+			Expect(err).Should(BeNil())
+			Expect(pub.X.Cmp(pubKey.GetX())).Should(BeZero())
+			Expect(pub.Y.Cmp(pubKey.GetY())).Should(BeZero())
+
+			r, s, err := edwards.Sign(edwards.Edwards(), priv, data)
+			Expect(err).Should(BeNil())
+
+			Expect(edwards.Verify(edwards.NewPublicKey(edwards.Edwards(), pubKey.GetX(), pubKey.GetY()), data, r, s)).Should(BeTrue())
+		},
+		Entry(
+			"3/3 quorum",
+			elliptic.Ed25519(),
+			2,
+			MakeRecoveryPeers(
+				[]string{
+					"3502109557042490544838324442604034236999785614721987641956076911051160401944",
+					"1103861929415814231586749933547278014778471148545711715092471976804157872704",
+					"3399052917932924022114908121544556052008108447896375201293210781395183567243",
+				},
+				[]string{
+					"7230155880034998276592769027360707810284675615875867582236795781241695079804",
+					"5272874863729098099670216622703886559502263393787069547616689911708461775143",
+					"3925249267150905323417733485189576433991970087669974755241055176173816181663",
+				},
+			),
+			MakePubKey(
+				"38485518761780627407120390846860597853897888230510522807636948334195936489504",
+				"10129321156846869276162967126585445016496573486379629538155569021664335120579",
+				elliptic.Ed25519(),
+			),
+		),
+		Entry(
+			"2/3 quorum",
+			elliptic.Ed25519(),
+			2,
+			MakeRecoveryPeers(
+				[]string{
+					"3502109557042490544838324442604034236999785614721987641956076911051160401944",
+					"3399052917932924022114908121544556052008108447896375201293210781395183567243",
+				},
+				[]string{
+					"7230155880034998276592769027360707810284675615875867582236795781241695079804",
+					"3925249267150905323417733485189576433991970087669974755241055176173816181663",
+				},
+			),
+			MakePubKey(
+				"38485518761780627407120390846860597853897888230510522807636948334195936489504",
+				"10129321156846869276162967126585445016496573486379629538155569021664335120579",
+				elliptic.Ed25519(),
+			),
+		),
+	)
+
+})
+
+func TestBinaryField(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Privaate Key Recovery Test")
+}
+
+func MakeRecoveryPeers(shares, bkxs []string) []RecoveryPeer {
+	recPeers := make([]RecoveryPeer, 0, len(shares))
+	for index, share := range shares {
+		share, _ := big.NewInt(0).SetString(share, 10)
+		bkx, _ := new(big.Int).SetString(bkxs[index], 10)
+		recPeers = append(recPeers, RecoveryPeer{
+			share: share,
+			bk:    birkhoffinterpolation.NewBkParameter(bkx, 0),
+			// TODO: 0 its a rank, test it with different ranks
+		})
+	}
+	return recPeers
+}
+
+func MakePubKey(x, y string, curve elliptic.Curve) *ecpointgrouplaw.ECPoint {
+	pubX, _ := big.NewInt(0).SetString(x, 10)
+	pubY, _ := big.NewInt(0).SetString(y, 10)
+	pubKey, _ := ecpointgrouplaw.NewECPoint(curve, pubX, pubY)
+	return pubKey
+}