Skip to content

Commit

Permalink
Merge pull request #239 from zhelnov/ecdsa-private-key-recovery
Browse files Browse the repository at this point in the history
crypto/tss/recovery: add private key recovery from dkg results
  • Loading branch information
cychuang0924 authored Nov 23, 2023
2 parents 09a854b + d555cf1 commit 9ec2bc7
Show file tree
Hide file tree
Showing 2 changed files with 304 additions and 0 deletions.
73 changes: 73 additions & 0 deletions crypto/tss/recovery/recover_private_key.go
Original file line number Diff line number Diff line change
@@ -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
}
231 changes: 231 additions & 0 deletions crypto/tss/recovery/recover_private_key_test.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 9ec2bc7

Please sign in to comment.