Skip to content

Commit

Permalink
add ecdsatss, currently refactoring ecdsa/keygen for simpler system
Browse files Browse the repository at this point in the history
  • Loading branch information
MagicalTux committed Jun 4, 2024
1 parent 409388d commit 5e39ded
Show file tree
Hide file tree
Showing 4 changed files with 393 additions and 0 deletions.
49 changes: 49 additions & 0 deletions ecdsatss/key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ecdsatss

import (
"math/big"

"github.com/ModChain/tss-lib/v2/crypto"
"github.com/ModChain/tss-lib/v2/crypto/paillier"
)

// Key represents the data for a local share of key
type Key struct {
LocalPreParams
LocalSecrets

Ks []*big.Int // original indexes (ki in signing preparation phase)
NTildej, H1j, H2j []*big.Int // // n-tilde, h1, h2 for range proofs
// public keys (Xj = uj*G for each Pj)
BigXj []*crypto.ECPoint // Xj
PaillierPKs []*paillier.PublicKey // pkj
// used for test assertions (may be discarded)
ECDSAPub *crypto.ECPoint // y
}

type LocalPreParams struct {
PaillierSK *paillier.PrivateKey // ski
NTildei, H1i, H2i, Alpha, Beta, P, Q *big.Int
}

type LocalSecrets struct {
// secret fields (not shared, but stored locally)
Xi, ShareID *big.Int // xi, kj
}

func (preParams LocalPreParams) Validate() bool {
return preParams.PaillierSK != nil &&
preParams.NTildei != nil &&
preParams.H1i != nil &&
preParams.H2i != nil
}

func (preParams LocalPreParams) ValidateWithProof() bool {
return preParams.Validate() &&
preParams.PaillierSK.P != nil &&
preParams.PaillierSK.Q != nil &&
preParams.Alpha != nil &&
preParams.Beta != nil &&
preParams.P != nil &&
preParams.Q != nil
}
167 changes: 167 additions & 0 deletions ecdsatss/keygen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package ecdsatss

import (
"context"
"errors"
"math/big"

"github.com/ModChain/tss-lib/v2/common"
"github.com/ModChain/tss-lib/v2/crypto"
cmts "github.com/ModChain/tss-lib/v2/crypto/commitments"
"github.com/ModChain/tss-lib/v2/crypto/dlnproof"
"github.com/ModChain/tss-lib/v2/crypto/vss"
"github.com/ModChain/tss-lib/v2/tss"
)

var zero = big.NewInt(0)

// Keygen is an object used to track a key currently being generated
type Keygen struct {
params *tss.Parameters // contains curve, parties, etc
KGCs []cmts.HashCommitment
vs vss.Vs
ssid []byte // ssid for current round/values
ssidNonce *big.Int
shares vss.Shares
deCommitPolyG cmts.HashDeCommitment
data *Key // key data currently being generated
round int // current round

Receiver tss.JsonExpect
}

func NewKeygen(params *tss.Parameters) (*Keygen, error) {
partyCount := params.PartyCount()
res := &Keygen{
params: params,
KGCs: make([]cmts.HashCommitment, partyCount),
data: &Key{},
round: 1,
}
err := res.round1()
if err != nil {
return nil, err
}

return res, nil
}

// getSSID returns ssid from local params
func (kg *Keygen) getSSID(roundNum int) ([]byte, error) {
ssidList := []*big.Int{kg.params.EC().Params().P, kg.params.EC().Params().N, kg.params.EC().Params().Gx, kg.params.EC().Params().Gy} // ec curve
ssidList = append(ssidList, kg.params.Parties().IDs().Keys()...)
ssidList = append(ssidList, big.NewInt(int64(roundNum))) // round number
ssidList = append(ssidList, kg.ssidNonce)
ssid := common.SHA512_256i(ssidList...).Bytes()

return ssid, nil
}

func (kg *Keygen) round1() error {
Pi := kg.params.PartyID()
i := Pi.Index
// 1. calculate "partial" key share ui
ui := common.GetRandomPositiveInt(kg.params.PartialKeyRand(), kg.params.EC().Params().N)

// 2. compute the vss shares
ids := kg.params.Parties().IDs().Keys()
vs, shares, err := vss.Create(kg.params.EC(), kg.params.Threshold(), ui, ids, kg.params.Rand())
if err != nil {
return err
}
kg.data.Ks = ids

// security: the original u_i may be discarded
ui = zero // clears the secret data from memory
_ = ui // silences a linter warning

// make commitment -> (C, D)
pGFlat, err := crypto.FlattenECPoints(vs)
if err != nil {
return err
}
cmt := cmts.NewHashCommitment(kg.params.Rand(), pGFlat...)

// 4. generate Paillier public key E_i, private key and proof
// 5-7. generate safe primes for ZKPs used later on
// 9-11. compute ntilde, h1, h2 (uses safe primes)
var preParams *LocalPreParams
if kg.data.LocalPreParams.Validate() && !kg.data.LocalPreParams.ValidateWithProof() {
return errors.New("`optionalPreParams` failed to validate; it might have been generated with an older version of tss-lib")
} else if kg.data.LocalPreParams.ValidateWithProof() {
preParams = &kg.data.LocalPreParams
} else {
ctx, cancel := context.WithTimeout(context.Background(), kg.params.SafePrimeGenTimeout())
defer cancel()
preParams, err = (&LocalPreGenerator{Context: ctx, Rand: kg.params.Rand(), Concurrency: kg.params.Concurrency()}).Generate()
if err != nil {
return errors.New("pre-params generation failed")
}
}
kg.data.LocalPreParams = *preParams
kg.data.NTildej[i] = preParams.NTildei
kg.data.H1j[i], kg.data.H2j[i] = preParams.H1i, preParams.H2i

// generate the dlnproofs for keygen
h1i, h2i, alpha, beta, p, q, NTildei := preParams.H1i, preParams.H2i, preParams.Alpha, preParams.Beta, preParams.P, preParams.Q, preParams.NTildei
dlnProof1 := dlnproof.NewDLNProof(h1i, h2i, alpha, p, q, NTildei, kg.params.Rand())
dlnProof2 := dlnproof.NewDLNProof(h2i, h1i, beta, p, q, NTildei, kg.params.Rand())

// for this P: SAVE
// - shareID
// and keep in temporary storage:
// - VSS Vs
// - our set of Shamir shares
kg.ssidNonce = new(big.Int).SetUint64(0)
kg.data.ShareID = ids[i]
kg.vs = vs
ssid, err := kg.getSSID(kg.round) // for round 1
if err != nil {
return errors.New("failed to generate ssid")
}
kg.ssid = ssid
kg.shares = shares

// for this P: SAVE de-commitments, paillier keys for round 2
kg.data.PaillierSK = preParams.PaillierSK
kg.data.PaillierPKs[i] = &preParams.PaillierSK.PublicKey
kg.deCommitPolyG = cmt.D

// send commitments, paillier pk + proof; round 1 message
// round.PartyID(), cmt.C, &preParams.PaillierSK.PublicKey, preParams.NTildei, preParams.H1i, preParams.H2i, dlnProof1, dlnProof2
dlnProof1Bz, err := dlnProof1.Serialize()
if err != nil {
return err
}
dlnProof2Bz, err := dlnProof2.Serialize()
if err != nil {
return err
}
msg := &keygenRound1msg{
Commitment: cmt.C.Bytes(),
PaillierN: preParams.PaillierSK.PublicKey.N.Bytes(),
NTilde: preParams.NTildei.Bytes(),
H1: preParams.H1i.Bytes(),
H2: preParams.H2i.Bytes(),
Dlnproof_1: dlnProof1Bz,
Dlnproof_2: dlnProof2Bz,
}

var otherIds []*tss.PartyID
for n, p := range kg.params.Parties().IDs() {
if n == i {
// do not send to self
continue
}
otherIds = append(otherIds, p)
m := tss.JsonWrap("ecdsa:keygen:round1", msg, Pi, p)
_ = m
}

kg.Receiver = tss.NewJsonExpect[keygenRound2msg1]("ecdsa:keygen:round2-1", otherIds, kg.round2)

return nil
}

func (kg *Keygen) round2(otherIds []*tss.PartyID, r2msg1 []*keygenRound2msg1) {
}
27 changes: 27 additions & 0 deletions ecdsatss/msgkeygen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package ecdsatss

// messages for keygen

type keygenRound1msg struct {
Commitment []byte
PaillierN []byte
NTilde []byte
H1 []byte
H2 []byte
Dlnproof_1 [][]byte
Dlnproof_2 [][]byte
}

type keygenRound2msg1 struct {
Share []byte
FacProof [][]byte
}

type keygenRound2msg2 struct {
DeCommitment [][]byte
ModProof [][]byte
}

type keygenRound3msg struct {
PaillierProof [][]byte
}
150 changes: 150 additions & 0 deletions ecdsatss/prepare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package ecdsatss

import (
"context"
"crypto/rand"
"errors"
"io"
"math/big"
"runtime"
"time"

"github.com/ModChain/tss-lib/v2/common"
"github.com/ModChain/tss-lib/v2/crypto/paillier"
)

const (
// Using a modulus length of 2048 is recommended in the GG18 spec
paillierModulusLen = 2048
// Two 1024-bit safe primes to produce NTilde
safePrimeBitLen = 1024
// Ticker for printing log statements while generating primes/modulus
logProgressTickInterval = 8 * time.Second
// Safe big len using random for ssid
SafeBitLen = 1024
)

type LocalPreGenerator struct {
context.Context // Context used to stop generation if needed
Rand io.Reader // reader used for random, defaults to rand.Reader if nil
Concurrency int // concurrency, defaults to runtime.NumCPU() if nil
}

// GeneratePreParams finds two safe primes and computes the Paillier secret required for the protocol.
// This can be a time consuming process so it is recommended to do it out-of-band.
// If not specified, a concurrency value equal to the number of available CPU cores will be used.
// If pre-parameters could not be generated before the timeout, an error is returned.
func GeneratePreParams(timeout time.Duration, optionalConcurrency ...int) (*LocalPreParams, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
opts := &LocalPreGenerator{Context: ctx}
if len(optionalConcurrency) > 0 {
opts.Concurrency = optionalConcurrency[0]
}
return opts.Generate()
}

func (g *LocalPreGenerator) getConcurrency() int {
if g == nil || g.Concurrency <= 0 {
return runtime.NumCPU()
}
return g.Concurrency
}

func (g *LocalPreGenerator) getRand() io.Reader {
if g == nil || g.Rand == nil {
return rand.Reader
}
return g.Rand
}

func (g *LocalPreGenerator) getContext() context.Context {
if g == nil || g.Context == nil {
return context.Background()
}
return g
}

func (g *LocalPreGenerator) Generate() (*LocalPreParams, error) {
concurrency := g.getConcurrency()

if concurrency /= 3; concurrency < 1 {
concurrency = 1
}

// prepare for concurrent Paillier and safe prime generation
paiCh := make(chan *paillier.PrivateKey, 1)
sgpCh := make(chan []*common.GermainSafePrime, 1)

// 4. generate Paillier public key E_i, private key and proof
go func(ch chan<- *paillier.PrivateKey) {
// more concurrency weight is assigned here because the paillier primes have a requirement of having "large" P-Q
PiPaillierSk, _, err := paillier.GenerateKeyPair(g.getContext(), g.getRand(), paillierModulusLen, concurrency*2)
if err != nil {
ch <- nil
return
}
ch <- PiPaillierSk
}(paiCh)

// 5-7. generate safe primes for ZKPs used later on
go func(ch chan<- []*common.GermainSafePrime) {
var err error
sgps, err := common.GetRandomSafePrimesConcurrent(g.getContext(), safePrimeBitLen, 2, concurrency, g.getRand())
if err != nil {
ch <- nil
return
}
ch <- sgps
}(sgpCh)

var sgps []*common.GermainSafePrime
var paiSK *paillier.PrivateKey

consumer:
for {
select {
case sgps = <-sgpCh:
if sgps == nil ||
sgps[0] == nil || sgps[1] == nil ||
!sgps[0].Prime().ProbablyPrime(30) || !sgps[1].Prime().ProbablyPrime(30) ||
!sgps[0].SafePrime().ProbablyPrime(30) || !sgps[1].SafePrime().ProbablyPrime(30) {
return nil, errors.New("timeout or error while generating the safe primes")
}
if paiSK != nil {
break consumer
}
case paiSK = <-paiCh:
if paiSK == nil {
return nil, errors.New("timeout or error while generating the Paillier secret key")
}
if sgps != nil {
break consumer
}
}
}

P, Q := sgps[0].SafePrime(), sgps[1].SafePrime()
NTildei := new(big.Int).Mul(P, Q)
modNTildeI := common.ModInt(NTildei)

p, q := sgps[0].Prime(), sgps[1].Prime()
modPQ := common.ModInt(new(big.Int).Mul(p, q))
f1 := common.GetRandomPositiveRelativelyPrimeInt(g.getRand(), NTildei)
alpha := common.GetRandomPositiveRelativelyPrimeInt(g.getRand(), NTildei)
beta := modPQ.ModInverse(alpha)
h1i := modNTildeI.Mul(f1, f1)
h2i := modNTildeI.Exp(h1i, alpha)

preParams := &LocalPreParams{
PaillierSK: paiSK,
NTildei: NTildei,
H1i: h1i,
H2i: h2i,
Alpha: alpha,
Beta: beta,
P: p,
Q: q,
}
return preParams, nil
}

0 comments on commit 5e39ded

Please sign in to comment.