bnb-chain / tss-lib

Threshold Signature Scheme, for ECDSA and EDDSA
MIT License
786 stars 267 forks source link

P.temp undefined (type *keygen.LocalParty has no field or method temp)compiler #204

Closed r4881t closed 2 years ago

r4881t commented 2 years ago

I am trying to write a local keygen function for ECDSA. This is based on the test code. I am encountering a (probably stupid) error. The issue seems to arise from the fact that somehow keygen.NewLocalParty returns a *keygen.LocalParty whose temp and data fields are not available. This is weird because I can see that they are defined. They work correctly on the test case, but not on my code.

Any help is appreciated.

go.mod

module github.com/org/mpc-tsslib-app

go 1.16

require github.com/bnb-chain/tss-lib v1.3.4-0.20220817120535-83efb6398209 // indirect

replace github.com/agl/ed25519 => github.com/binance-chain/edwards25519 v0.0.0-20200305024217-f36fc4b53d43

main.go

func keyGen(userId big.Int, threshold int) {
    server_id := big.NewInt(1)
    if userId.Cmp(server_id) == 0 {
        panic("Invalid user id. Must != " + server_id.String())
    }

    log.Printf("Received request to generate keypair for user: %s", userId.String())

    preParamsUser, _ := keygen.GeneratePreParams(1 * time.Minute)
    preParamsServer, _ := keygen.GeneratePreParams(1 * time.Minute)
    log.Printf("Generated pre-params")

    userPartyName := userId.String()
    userParty := tss.NewPartyID(userPartyName, userPartyName, &userId)

    serverPartyName := userPartyName + "_server"
    serverParty := tss.NewPartyID(serverPartyName, serverPartyName, server_id)

    _pIDs := []*tss.PartyID{userParty, serverParty}
    pIDs := tss.SortPartyIDs(_pIDs)
    ctx := tss.NewPeerContext(pIDs)

    partiesCount := len(pIDs)
    errCh := make(chan *tss.Error, partiesCount)
    outCh := make(chan tss.Message, partiesCount)
    endCh := make(chan keygen.LocalPartySaveData, partiesCount)

    parties := make([]*keygen.LocalParty, 0, partiesCount)

    curve := tss.S256()

    userParams := tss.NewParameters(curve, ctx, userParty, len(pIDs), threshold)
    userLocalParty := keygen.NewLocalParty(
        userParams,
        outCh,
        endCh,
        *preParamsUser).(*keygen.LocalParty)
    parties = append(parties, userLocalParty)

    serverParams := tss.NewParameters(curve, ctx, serverParty, len(pIDs), threshold)
    serverLocalParty := keygen.NewLocalParty(
        serverParams,
        outCh,
        endCh,
        *preParamsServer).(*keygen.LocalParty)
    parties = append(parties, serverLocalParty)

    log.Printf("Starting goroutines")
    go func(P *keygen.LocalParty) {
        if err := P.Start(); err != nil {
            errCh <- err
        }
    }(userLocalParty)

    go func(P *keygen.LocalParty) {
        if err := P.Start(); err != nil {
            errCh <- err
        }
    }(serverLocalParty)

    var ended int32

keygen:

    for {
        select {
        case err := <-errCh:
            log.Printf("Error: %s", err.Error())
            break keygen

        case msg := <-outCh:
            dest := msg.GetTo()
            if dest == nil { // broadcast!
                for _, P := range parties {
                    if P.PartyID().Index == msg.GetFrom().Index {
                        continue
                    }
                    go SharedPartyUpdater(P, msg, errCh)
                }
            } else { // point-to-point!
                if dest[0].Index == msg.GetFrom().Index {
                    log.Printf("party %d tried to send a message to itself (%d)", dest[0].Index, msg.GetFrom().Index)
                    panic("Party Sending Msg to Itself")
                    return
                }
                go SharedPartyUpdater(parties[dest[0].Index], msg, errCh)
            }

        case save := <-endCh:
            _, err := save.OriginalIndex()
            if err != nil {
                panic("Unknown Error while saving party's data: " + err.Error())
                return
            }

            atomic.AddInt32(&ended, 1)
            if atomic.LoadInt32(&ended) == int32(partiesCount) {
                log.Printf("Done. Received save data from %d participants", ended)

                // combine shares for each Pj to get u
                u := new(big.Int)
                for j, Pj := range parties {
                    pShares := make(vss.Shares, 0)
                    for _, P := range parties {
                        vssMsgs := P.temp.kgRound2Message1s
                        share := vssMsgs[j].Content().(*KGRound2Message1).Share
                        shareStruct := &vss.Share{
                            Threshold: threshold,
                            ID:        P.PartyID().KeyInt(),
                            Share:     new(big.Int).SetBytes(share),
                        }
                        pShares = append(pShares, shareStruct)
                    }

                    uj, err := pShares[:threshold+1].ReConstruct(tss.S256())
                    if err != nil {
                        panic("Error while vss.reconstructing shares: " + err.Error())
                        return
                    }

                    // uG test: u*G[j] == V[0]
                    // assert.Equal(t, uj, Pj.temp.ui)
                    uG := crypto.ScalarBaseMult(tss.EC(), uj)
                    // assert.True(t, uG.Equals(Pj.temp.vs[0]), "ensure u*G[j] == V_0")

                    // xj tests: BigXj == xj*G
                    xj := Pj.data.Xi
                    gXj := crypto.ScalarBaseMult(tss.EC(), xj)
                    BigXj := Pj.data.BigXj[j]
                    // assert.True(t, BigXj.Equals(gXj), "ensure BigX_j == g^x_j")

                    // fails if threshold cannot be satisfied (bad share)
                    {
                        badShares := pShares[:threshold]
                        badShares[len(badShares)-1].Share.Set(big.NewInt(0))
                        uj, err := pShares[:threshold].ReConstruct(tss.S256())
                        if err != nil {
                            panic("Error while vss.reconstructing shares: " + err.Error())
                            return
                        }
                        // assert.NotEqual(t, parties[j].temp.ui, uj)
                        BigXjX, BigXjY := tss.EC().ScalarBaseMult(uj.Bytes())
                        // assert.NotEqual(t, BigXjX, Pj.temp.vs[0].X())
                        // assert.NotEqual(t, BigXjY, Pj.temp.vs[0].Y())
                    }
                    u = new(big.Int).Add(u, uj)
                }

                // build ecdsa key pair
                pkX, pkY := save.ECDSAPub.X(), save.ECDSAPub.Y()
                pk := ecdsa.PublicKey{
                    Curve: tss.EC(),
                    X:     pkX,
                    Y:     pkY,
                }
                sk := ecdsa.PrivateKey{
                    PublicKey: pk,
                    D:         u,
                }
                // test pub key, should be on curve and match pkX, pkY
                // assert.True(t, sk.IsOnCurve(pkX, pkY), "public key must be on curve")

                // public key tests
                // assert.NotZero(t, u, "u should not be zero")
                ourPkX, ourPkY := tss.EC().ScalarBaseMult(u.Bytes())
                // assert.Equal(t, pkX, ourPkX, "pkX should match expected pk derived from u")
                // assert.Equal(t, pkY, ourPkY, "pkY should match expected pk derived from u")
                // t.Log("Public key tests done.")

                // make sure everyone has the same ECDSA public key
                // for _, Pj := range parties {
                // assert.Equal(t, pkX, Pj.data.ECDSAPub.X())
                // assert.Equal(t, pkY, Pj.data.ECDSAPub.Y())
                // }
                // t.Log("Public key distribution test done.")

                // test sign/verify
                data := make([]byte, 32)
                for i := range data {
                    data[i] = byte(i)
                }
                r, s, _ := ecdsa.Sign(rand.Reader, &sk, data)
                // assert.NoError(t, err, "sign should not throw an error")
                ecdsa.Verify(&pk, data, r, s)
                // assert.True(t, ok, "signature should be ok")
                log.Printf("ECDSA signing test done.")

                // log.Printf("Start goroutines: %d, End goroutines: %d", startGR, runtime.NumGoroutine())

                break keygen
            }
        }
    }

}

func SharedPartyUpdater(party tss.Party, msg tss.Message, errCh chan<- *tss.Error) {
    // do not send a message from this party back to itself
    if party.PartyID() == msg.GetFrom() {
        return
    }
    bz, _, err := msg.WireBytes()
    if err != nil {
        errCh <- party.WrapError(err)
        return
    }
    pMsg, err := tss.ParseWireMessage(bz, msg.GetFrom(), msg.IsBroadcast())
    if err != nil {
        errCh <- party.WrapError(err)
        return
    }
    if _, err := party.Update(pMsg); err != nil {
        errCh <- err
    }
}
Screenshot 2022-08-26 at 4 58 16 PM
$ go build
./main.go:161:19: P.temp undefined (cannot refer to unexported field or method temp)
./main.go:183:14: Pj.data undefined (cannot refer to unexported field or method data)
./main.go:185:17: Pj.data undefined (cannot refer to unexported field or method data)
yshurik commented 2 years ago

Lower-case symbols are not exported in Go modules as far as I remember (temp, data). So it may work as part of module unit-tests, but not when you imported github.com/bnb-chain/tss-lib into your app

r4881t commented 2 years ago

I believe you are correct. I am new to Go. How should I proceed in this case then? What's the way to generate shares? The README mentions to simply do below for each party with proper communications setup. But the tests make it seem like there's a lot more to be done.

party := keygen.NewLocalParty(params, outCh, endCh, preParams) // Omit the last arg to compute the pre-params in round 1
go func() {
    err := party.Start()
    // handle err ...
}()
yshurik commented 2 years ago

You only have to read (for each party) from endCh data of type keygen.LocalPartySaveData. This data you can just serialize to the disk via json.Marshal. Same if your app restart, you just loads saved "share" from disk by json.Unmarshal. This "share" of type keygen.LocalPartySaveData you use later for signing routines (for example) This is kind of "key" of one party.

r4881t commented 2 years ago

Hi @yshurik Thanks for being patient. I tried what you recommended, and it seems to make me move forward. But not complete. Now I can see messages passing around between the parties, but I don't get anything on the endCh to save. This is a 2 Party setup and the value of threshold is 1

main.go

func SharedPartyUpdater(party tss.Party, msg tss.Message, errCh chan<- *tss.Error) {
    // do not send a message from this party back to itself
    log.Printf("Got a message from %s for %s",
        msg.GetFrom().GetMoniker(),
        party.PartyID().GetMoniker())
    if party.PartyID() == msg.GetFrom() {
        log.Print("Ignored...")
        return
    }

    // Returns the encoded message bytes to send over the wire along with routing information
    bz, _, err := msg.WireBytes()
    if err != nil {
        errCh <- party.WrapError(err)
        return
    }

    pMsg, err := tss.ParseWireMessage(bz, msg.GetFrom(), msg.IsBroadcast())
    if err != nil {
        errCh <- party.WrapError(err)
        return
    }

    // Will only work in local setup
    if _, err := party.Update(pMsg); err != nil {
        errCh <- err
    }
}

func keyGen(userId big.Int, threshold int) {
    server_id := big.NewInt(1)
    if userId.Cmp(server_id) == 0 {
        panic("Invalid user id. Must != " + server_id.String())
    }

    log.Printf("Received request to generate keypair for user: %s", userId.String())

    preParamsUser, _ := keygen.GeneratePreParams(1 * time.Minute)
    log.Print(("Generated pre params for User"))
    preParamsServer, _ := keygen.GeneratePreParams(1 * time.Minute)
    log.Printf("Generated pre-params for Server")

    userPartyName := userId.String()
    userParty := tss.NewPartyID(userPartyName, userPartyName, &userId)

    serverPartyName := userPartyName + "_server"
    serverParty := tss.NewPartyID(serverPartyName, serverPartyName, server_id)

    _pIDs := []*tss.PartyID{userParty, serverParty}
    pIDs := tss.SortPartyIDs(_pIDs)
    ctx := tss.NewPeerContext(pIDs)

    partiesCount := len(pIDs)
    errCh := make(chan *tss.Error, partiesCount)
    outCh := make(chan tss.Message, partiesCount)
    endCh := make(chan keygen.LocalPartySaveData)

    parties := make([]*keygen.LocalParty, 0, partiesCount)

    curve := tss.S256()

    userParams := tss.NewParameters(curve, ctx, userParty, len(pIDs), threshold)
    userLocalParty := keygen.NewLocalParty(
        userParams,
        outCh,
        endCh,
        *preParamsUser).(*keygen.LocalParty)
    parties = append(parties, userLocalParty)

    serverParams := tss.NewParameters(curve, ctx, serverParty, len(pIDs), threshold)
    serverLocalParty := keygen.NewLocalParty(
        serverParams,
        outCh,
        endCh,
        *preParamsServer).(*keygen.LocalParty)
    parties = append(parties, serverLocalParty)

    log.Printf("Starting goroutines")
    go func(P *keygen.LocalParty) {
        if err := P.Start(); err != nil {
            errCh <- err
        }

        log.Print("local party finished")
    }(userLocalParty)

    go func(P *keygen.LocalParty) {
        if err := P.Start(); err != nil {
            errCh <- err
        }
        log.Print("server party finished")
    }(serverLocalParty)

    var ended int32

keygen:

    for {
        select {
        case err := <-errCh:
            log.Printf("Error: %s", err.Error())
            break keygen

        case msg := <-outCh:
            dest := msg.GetTo()
            if dest == nil { // broadcast!
                log.Print("Broadcasting...")
                for _, P := range parties {
                    if P.PartyID().Index == msg.GetFrom().Index {
                        continue
                    }
                    go SharedPartyUpdater(P, msg, errCh)
                }
            } else { // point-to-point!
                log.Print("Point-to-point...")
                if dest[0].Index == msg.GetFrom().Index {
                    log.Printf("party %d tried to send a message to itself (%d)", dest[0].Index, msg.GetFrom().Index)
                    panic("Party Sending Msg to Itself")
                }
                go SharedPartyUpdater(parties[dest[0].Index], msg, errCh)
            }

        case save := <-endCh:
            log.Printf("Got save data from party %d", save.ShareID)
            _, err := save.OriginalIndex()
            if err != nil {
                panic("Unknown Error while saving party's data: " + err.Error())
            }
            fmt.Print(save)

            log.Print("Got save data from party")
            atomic.AddInt32(&ended, 1)
            if atomic.LoadInt32(&ended) == int32(partiesCount) {
                log.Printf("Done. Received save data from %d participants", ended)
                log.Print(save.ShareID.String())
                json.Marshal(save)
                break keygen
            }
        }
    }

}

Upon executing I get the following

bitpack-tss-lib % ./mpc-app-tsslib
2022/08/27 13:22:17 Received request to generate keypair for user: 3
2022/08/27 13:22:30 Generated pre params for User
2022/08/27 13:22:44 Generated pre-params for Server
2022/08/27 13:22:44 Starting goroutines
2022/08/27 13:22:45 Broadcasting...
2022/08/27 13:22:45 local party finished
2022/08/27 13:22:45 Got a message from 3 for 3_bitpack
2022/08/27 13:22:45 Broadcasting...
2022/08/27 13:22:45 Got a message from 3_bitpack for 3
2022/08/27 13:22:45 server party finished
2022/08/27 13:22:46 Point-to-point...
2022/08/27 13:22:46 Broadcasting...
2022/08/27 13:22:46 Got a message from 3_bitpack for 3_bitpack
2022/08/27 13:22:46 Ignored...
2022/08/27 13:22:46 Got a message from 3_bitpack for 3
2022/08/27 13:22:46 Point-to-point...
2022/08/27 13:22:46 Broadcasting...
2022/08/27 13:22:46 Got a message from 3 for 3_bitpack
2022/08/27 13:22:46 Got a message from 3 for 3
2022/08/27 13:22:46 Ignored...
yshurik commented 2 years ago

It is hard to say why stalled. I would recommend to switch on the logging in tss lib. It uses "github.com/ipfs/go-log", so add import like:

import (
    logging "github.com/ipfs/go-log"
)

// before start keygen:

logging.SetDebugLogging()

You will see what parties are talking to each other

r4881t commented 2 years ago

@yshurik I added the following timeout condition apart from the logger, and it seems like one of the parties is waiting for a message from itself in round 2.

        case <-time.After(1 * time.Minute):
            for _, P := range parties {
                common.Logger.Debug(P.PartyID().String()+" waiting for %s", P.WaitingFor())
            }
            common.Logger.Debug("timeout")
            break keygen
        }

The log output is as follows: This is for a user with ID 3 and a server with ID 1

./mpc-api-tsslib
17:44:55.241  INFO    tss-lib: Received request to generate keypair for user: %s 3 main.go:72
17:44:55.242  INFO    tss-lib: generating the Paillier modulus, please wait... prepare.go:63
17:44:55.242  INFO    tss-lib: generating the safe primes for the signing proofs, please wait... prepare.go:78
17:45:00.242  INFO    tss-lib: paillier modulus generated. took 4.999475958s
 prepare.go:71
17:45:03.243  INFO    tss-lib: still generating primes... prepare.go:99
17:45:03.306  INFO    tss-lib: safe primes generated. took 8.064205959s
 prepare.go:85
17:45:03.431  INFO    tss-lib: Generated pre params for User main.go:75
17:45:03.431  INFO    tss-lib: generating the safe primes for the signing proofs, please wait... prepare.go:78
17:45:03.431  INFO    tss-lib: generating the Paillier modulus, please wait... prepare.go:63
17:45:11.431  INFO    tss-lib: still generating primes... prepare.go:99
17:45:14.912  INFO    tss-lib: paillier modulus generated. took 11.481392666s
 prepare.go:71
17:45:18.910  INFO    tss-lib: safe primes generated. took 15.478908959s
 prepare.go:85
17:45:19.031  INFO    tss-lib: Generated pre-params for Server main.go:77
17:45:19.031  INFO    tss-lib: party {0,3_server}: ecdsa-keygen round 1 starting party.go:135
17:45:19.031  INFO    tss-lib: party {1,3}: ecdsa-keygen round 1 starting party.go:135
17:45:20.546 DEBUG    tss-lib: party {0,3_server}: ecdsa-keygen round 1 finished party.go:137
17:45:20.546 DEBUG    tss-lib: Broadcasting... main.go:139
17:45:20.547 DEBUG    tss-lib: Got a message from %s for %s 3_server 3 main.go:38
17:45:20.612 DEBUG    tss-lib: party {1,3}: ecdsa-keygen round 1 finished party.go:137
17:45:20.612 DEBUG    tss-lib: Broadcasting... main.go:139
17:45:20.612 DEBUG    tss-lib: Got a message from %s for %s 3 3_server main.go:38
17:45:20.612 DEBUG    tss-lib: party {1,3} received message: Type: binance.tsslib.ecdsa.keygen.KGRound1Message, From: {0,3_server}, To: all party.go:154
17:45:20.612 DEBUG    tss-lib: party {1,3} round 1 update: Type: binance.tsslib.ecdsa.keygen.KGRound1Message, From: {0,3_server}, To: all party.go:156
17:45:20.612 DEBUG    tss-lib: party {1,3}: ecdsa-keygen round 1 update party.go:162
17:45:20.612 DEBUG    tss-lib: party {0,3_server} received message: Type: binance.tsslib.ecdsa.keygen.KGRound1Message, From: {1,3}, To: all party.go:154
17:45:20.613 DEBUG    tss-lib: party {0,3_server} round 1 update: Type: binance.tsslib.ecdsa.keygen.KGRound1Message, From: {1,3}, To: all party.go:156
17:45:20.613 DEBUG    tss-lib: party {0,3_server}: ecdsa-keygen round 1 update party.go:162
17:45:21.852 DEBUG    tss-lib: Point-to-point... main.go:147
17:45:21.852 DEBUG    tss-lib: Got a message from %s for %s 3_server 3_server main.go:38
17:45:21.852 DEBUG    tss-lib: Ignored... main.go:42
17:45:21.852 DEBUG    tss-lib: Broadcasting... main.go:139
17:45:21.852 DEBUG    tss-lib: Got a message from %s for %s 3_server 3 main.go:38
17:45:21.853  INFO    tss-lib: party {0,3_server}: ecdsa-keygen round 2 started party.go:172
17:45:21.853 DEBUG    tss-lib: party {0,3_server} received message: Type: binance.tsslib.ecdsa.keygen.KGRound1Message, From: {1,3}, To: all party.go:154
17:45:21.853 DEBUG    tss-lib: party {0,3_server} round 2 update: Type: binance.tsslib.ecdsa.keygen.KGRound1Message, From: {1,3}, To: all party.go:156
17:45:21.853 DEBUG    tss-lib: party {0,3_server}: ecdsa-keygen round 2 update party.go:162
17:45:21.853 DEBUG    tss-lib: Point-to-point... main.go:147
17:45:21.853 DEBUG    tss-lib: Broadcasting... main.go:139
17:45:21.853 DEBUG    tss-lib: Got a message from %s for %s 3 3_server main.go:38
17:45:21.853 DEBUG    tss-lib: Got a message from %s for %s 3 3 main.go:38
17:45:21.853 DEBUG    tss-lib: Ignored... main.go:42
17:45:21.853  INFO    tss-lib: party {1,3}: ecdsa-keygen round 2 started party.go:172
17:45:21.854 DEBUG    tss-lib: party {0,3_server} received message: Type: binance.tsslib.ecdsa.keygen.KGRound2Message2, From: {1,3}, To: all party.go:154
17:45:21.854 DEBUG    tss-lib: party {0,3_server} round 2 update: Type: binance.tsslib.ecdsa.keygen.KGRound2Message2, From: {1,3}, To: all party.go:156
17:45:21.854 DEBUG    tss-lib: party {1,3} received message: Type: binance.tsslib.ecdsa.keygen.KGRound1Message, From: {0,3_server}, To: all party.go:154
17:45:21.854 DEBUG    tss-lib: party {1,3} round 2 update: Type: binance.tsslib.ecdsa.keygen.KGRound1Message, From: {0,3_server}, To: all party.go:156
17:45:21.854 DEBUG    tss-lib: party {1,3}: ecdsa-keygen round 2 update party.go:162
17:45:21.854 DEBUG    tss-lib: party {0,3_server}: ecdsa-keygen round 2 update party.go:162
17:45:21.854 DEBUG    tss-lib: party {1,3} received message: Type: binance.tsslib.ecdsa.keygen.KGRound2Message2, From: {0,3_server}, To: all party.go:154
17:45:21.854 DEBUG    tss-lib: party {1,3} round 2 update: Type: binance.tsslib.ecdsa.keygen.KGRound2Message2, From: {0,3_server}, To: all party.go:156
17:45:21.854 DEBUG    tss-lib: party {1,3}: ecdsa-keygen round 2 update party.go:162
17:46:21.852 DEBUG    tss-lib: {1,3} waiting for %s [{0,3_server} {1,3}] main.go:174
17:46:21.853 DEBUG    tss-lib: {0,3_server} waiting for %s [{1,3}] main.go:174
17:46:21.853 DEBUG    tss-lib: timeout main.go:176
r4881t commented 2 years ago

I tried out this example with multiple userIDs and get the similar output

tail

19:49:09.001 DEBUG    tss-lib: party {1,3} round 2 update: Type: binance.tsslib.ecdsa.keygen.KGRound2Message2, From: {3,5}, To: all party.go:156
19:49:09.001 DEBUG    tss-lib: party {1,3}: ecdsa-keygen round 2 update party.go:162
19:49:09.001 DEBUG    tss-lib: party {2,4} received message: Type: binance.tsslib.ecdsa.keygen.KGRound2Message2, From: {3,5}, To: all party.go:154
19:49:09.001 DEBUG    tss-lib: party {2,4} round 2 update: Type: binance.tsslib.ecdsa.keygen.KGRound2Message2, From: {3,5}, To: all party.go:156
19:49:09.001 DEBUG    tss-lib: party {2,4}: ecdsa-keygen round 2 update party.go:162
19:50:09.001 DEBUG    tss-lib: {1,3} waiting for  [{0,server} {1,3} {2,4} {3,5}] main.go:193
19:50:09.003 DEBUG    tss-lib: {2,4} waiting for  [{1,3} {2,4} {3,5}] main.go:193
19:50:09.003 DEBUG    tss-lib: {3,5} waiting for  [{2,4} {3,5}] main.go:193
19:50:09.003 DEBUG    tss-lib: {0,server} waiting for  [{3,5}] main.go:193
19:50:09.003 DEBUG    tss-lib: timeout main.go:195
yshurik commented 2 years ago

Message for itself - in your code you are skipping broadcasts to itself, is that a cause?

r4881t commented 2 years ago

Not necessarily. I removed that part and it still was stuck. I copied that part from the test code itself. Did you manage to get this working? Any code sample that you can share?

yshurik commented 2 years ago

Yes, I have it working fine. I can not separate some working part from my codes to give an example. But I can recommend these unittests: https://github.com/Kava-Labs/kava-bridge/blob/main/relayer/mp_tss/keygen_test.go / https://github.com/Kava-Labs/kava-bridge/tree/main/relayer/mp_tss which helped me

r4881t commented 2 years ago

Closing this as the original question got answered.