{"id":13630253,"url":"https://github.com/taurushq-io/multi-party-sig","last_synced_at":"2025-10-06T16:01:48.646Z","repository":{"id":37525992,"uuid":"339414796","full_name":"taurushq-io/multi-party-sig","owner":"taurushq-io","description":"Implementation of protocols for threshold signatures","archived":false,"fork":false,"pushed_at":"2025-08-21T13:23:16.000Z","size":2151,"stargazers_count":342,"open_issues_count":21,"forks_count":125,"subscribers_count":30,"default_branch":"main","last_synced_at":"2025-09-05T08:17:33.555Z","etag":null,"topics":["cryptography","elliptic-curves","multi-party-computation","signature"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/taurushq-io.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":"CITATION.cff","codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-02-16T13:54:00.000Z","updated_at":"2025-09-02T20:56:03.000Z","dependencies_parsed_at":"2023-02-19T07:45:51.690Z","dependency_job_id":"d2870232-0848-41e6-90c7-34bfd32a1460","html_url":"https://github.com/taurushq-io/multi-party-sig","commit_stats":{"total_commits":556,"total_committers":5,"mean_commits":111.2,"dds":"0.42985611510791366","last_synced_commit":"1c20cbbca313a5bbbdbeae497144294658257100"},"previous_names":["taurusgroup/cmp-ecdsa","taurusgroup/multi-party-sig"],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/taurushq-io/multi-party-sig","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Fmulti-party-sig","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Fmulti-party-sig/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Fmulti-party-sig/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Fmulti-party-sig/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/taurushq-io","download_url":"https://codeload.github.com/taurushq-io/multi-party-sig/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Fmulti-party-sig/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278638110,"owners_count":26019943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-06T02:00:05.630Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cryptography","elliptic-curves","multi-party-computation","signature"],"created_at":"2024-08-01T22:01:35.993Z","updated_at":"2025-10-06T16:01:48.621Z","avatar_url":"https://github.com/taurushq-io.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# multi-party-sig\n\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n\nA Go implementation of multi-party threshold signing for:\n\n- ECDSA, using the \"CGGMP\" protocol by [Canetti et al.](https://eprint.iacr.org/2021/060) for threshold ECDSA signing.\n  We implement both the 4 round \"online\" and the 7 round \"presigning\" protocols from the paper. The latter also supports identifiable aborts.\n  Implementation details are also documented in in [docs/Threshold.pdf](docs/Threshold.pdf).\n  Our implementation supports ECDSA with secp256k1, with other curves coming in the future.\n  \u003c!-- including  with some additions to improve its practical reliability, including the \"echo broadcast\" from [Goldwasser and Lindell](https://doi.org/10.1007/s00145-005-0319-z).  --\u003e\n\n- Schnorr signatures (as integrated in Bitcoin's Taproot), using the\n  [FROST](https://eprint.iacr.org/2020/852.pdf) protocol. Because of the linear structure\n  of Schnorr signatures, this protocol is less expensive than CMP. We've also\n  made the necessary adjustments to make our signatures compatible with\n  Taproot's specific point encoding, as specified in [BIP-0340](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).\n\n\u003e DISCLAIMER: Use at your own risk, this project needs further testing and auditing to be production-ready.\n\n## Features\n\n- **[BIP-32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) key derivation**.\n  Parties can convert their shares of a public key into shares of a child key,\n  as per BIP-32's key derivation spec. Only unhardened derivation is supported,\n  since hardened derivation would require hashing the secret key, which no party\n  has access to.\n- **Constant-time arithmetic**, via [saferith](https://github.com/cronokirby/saferith).\n  The CMP protocol requires Paillier encryption, as well as related ZK proofs\n  performing modular arithmetic. We use a constant-time implementation of this\n  arithmetic to mitigate timing-leaks\n- **Parallel processing.** When possible, we parallelize heavy computation to speed\n  up protocol execution.\n\n## Usage\n\n`multi-party-sig` was designed with the goal of supporting multiple threshold signature schemes.\nEach protocol can be invoked using one of the following functions:\n\n| Protocol Initialization                                                                                                              | Returns                                                    | Description                                                                                 |\n| ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------- |\n| [`cmp.Keygen(group curve.Curve, selfID party.ID, participants []party.ID, threshold int, pl *pool.Pool)`](protocols/cmp/cmp.go)      | [`*cmp.Config`](protocols/cmp/config/config.go)            | Generate a new ECDSA private key shared among all the given participants.                   |\n| [`cmp.Refresh(config *cmp.Config, pl *pool.Pool)`](protocols/cmp/cmp.go)                                                             | [`*cmp.Config`](protocols/cmp/config/config.go)            | Refreshes all shares of an existing ECDSA private key.                                      |\n| [`cmp.Sign(config *cmp.Config, signers []party.ID, messageHash []byte, pl *pool.Pool)`](protocols/cmp/cmp.go)                        | [`*ecdsa.Signature`](pkg/ecdsa/signature.go)               | Generates an ECDSA signature for `messageHash`.                                             |\n| [`cmp.Presign(config *cmp.Config, signers []party.ID, pl *pool.Pool)`](protocols/cmp/cmp.go)                                         | [`*ecdsa.PreSignature`](pkg/ecdsa/presignature.go)         | Generates a preprocessed ECDSA signature which does not depend on the message being signed. |\n| [`cmp.PresignOnline(config *cmp.Config, preSignature *ecdsa.PreSignature, messageHash []byte, pl *pool.Pool)`](protocols/cmp/cmp.go) | [`*ecdsa.Signature`](pkg/ecdsa/signature.go)               | Combines each party's `PreSignature` share to create an ECDSA signature for `messageHash`.  |\n| [`doerner.Keygen(group curve.Curve, receiver bool, selfID, otherID party.ID, pl *pool.Pool)`](protocols/doerner/doerner.go)          | [`*doerner.Config`](protocols/doerner/doerner.go)          | Generates a new ECDSA private key shared among two participants                             |\n| [`doerner.SignReceiver(config *ConfigReceiver, selfID, otherID party.ID, hash []byte, pl *pool.Pool)`](protocols/doerner/doerner.go) | [`*ecdsa.Signature`](pkg/ecdsa/signature.go)               | Generates a new ECDSA signature for a given message, using the Receiver's config            |\n| [`doerner.SignSender(config *ConfigSender, selfID, otherID party.ID, hash []byte, pl *pool.Pool)`](protocols/doerner/doerner.go)     | [`*ecdsa.Signature`](pkg/ecdsa/signature.go)               | Generates a new ECDSA signature for a given message, using the Sender's config              |\n| [`frost.Keygen(group curve.Curve, selfID party.ID, participants []party.ID, threshold int)`](protocols/frost/frost.go)               | [`*frost.Config`](protocols/frost/keygen/result.go)        | Generates a new Schnorr private key shared among all the given participants.                |\n| [`frost.KeygenTaproot(selfID party.ID, participants []party.ID, threshold int)`](protocols/frost/frost.go)                           | [`*frost.TaprootConfig`](protocols/frost/keygen/result.go) | Generates a new Taproot compatible private key shared among all the given participants.     |\n| [`frost.Sign(config *frost.Config, signers []party.ID, messageHash []byte)`](protocols/frost/frost.go)                               | [`*frost.Signature`](protocols/frost/sign/types.go)        | Generates a Schnorr signature for `messageHash`.                                            |\n| [`frost.SignTaproot(config *frost.TaprootConfig, signers []party.ID, messageHash []byte)`](protocols/frost/frost.go)                 | [`*taproot.Signature`](pkg/taproot/signature.go)           | Generates a Taproot compatibe Schnorr signature for `messageHash`.                          |\n\nIn general, `Keygen` and `Refresh` protocols return a `Config` struct which contains a single key share, as well as the other participants' public key shares, and the full signing public key.\nThe remaining arguments should be chosen as follows:\n\n- [`party.ID`](pkg/party/id.go) aliases a string and should uniquely identify each participant in the protocol.\n- [`curve.Curve`](pkg/math/curve/curve.go) represents the cryptogrpahic group over which the protocol is defined. Currently, the only option is [`curve.Secp256k1`](pkg/math/curve/secp256k1.go).\n- [`*pool.Pool`](pkg/pool/pool.go) can be used to paralelize certain operations during the protocol execution. This parameter may be nil, in which case the protocol will be run over a single thread.\n  A new `pool.Pool` can be created with `pl := pool.NewPool(numberOfThreads)`, and should be freed once the protocol has finished executing by calling `pl.Teardown()`.\n- `threshold` defines the maximum number of participants which may be corrupted at any given time. Generating a signature therefore requires `threshold+1` participants.\n- [`*ecdsa.PreSignature`](pkg/ecdsa/presignature.go) represents a preprocessed signature share which can be generated before the message to be signed is known.\n  When the message does become available, the signature can be generated in a single round.\n\nEach of the above protocols can be executed by creating a [`protocol.Handler`](pkg/protocol/handler.go) object.\nFor example, we can generate a new ECDSA key as follows:\n\n```go\nvar (\n  // sessionID should be agreed upon beforehand, and must be unique among all protocol executions.\n  // Alternatively, a counter may be used, which must be incremented after before every protocol start.\n  sessionID []byte\n  // group defines the cryptographic group over which\n  group := curve.Secp256k1{}\n  participants := []party.ID{\"a\", \"b\", \"c\", \"d\", \"e\"}\n  selfID := participants[0] // we run the protocol as \"a\"\n  threshold := 3 // 4 or more participants are required to generate a signature\n)\n\npl := pool.NewPool(0) // use the maximum number of threads.\ndefer pl.Teardown() // destroy the pool once the protocol is done.\n\nhandler, err := protocol.NewMultiHandler(cmp.Keygen(group, selfID, participants, threshold, pl), sessionID)\nif err != nil {\n  // the handler was not able to start the protocol, most likely due to incorrect configuration.\n}\n```\n\nMore examples of how to create handlers for various protocols can be found in [/example](/example).\nNote that for two-party protocols like Doerner, a [`protocol.TwoPartyHandler`](pkg/protocol/twoparty.go) should be created\ninstead, to manage the back and forth messages required.\n\nAfter the handler has been created, the user can start a loop for incoming/outgoing messages.\nMessages for other parties can be obtained by querying the channel returned by `handler.Listen()`.\nIf the channel is closed, then the user can assume the protocol has finished.\n\n```go\nfunc runProtocol(handler *protocol.Handler) {\n  // Message handling loop\n  for {\n    select {\n\n    // Message to be sent to other participants\n    case msgOut, ok := \u003c-handler.Listen():\n      // a closed channel indicates that the protocol has finished executing\n      if !ok {\n        return\n      }\n      if msgOut.Broadcast {\n        // ensure this message is reliably broadcast\n      }\n      for _, id := range participants {\n        if msgOut.IsFor(id) {\n          // send the message to `id`\n        }\n      }\n\n    // Incoming message\n    case msgIn := \u003c- Receive():\n      if !handler.CanAccept(msg) {\n        // basic header validation failed, the message may be intended for a different protocol execution.\n        continue\n      }\n      handler.Update(msgIn)\n    }\n  }\n}\n\n// runProtocol blocks until the protocol succeeds or aborts\nrunProtocol(handler)\n\n// obtain the final result, or a possible error\nresult, err := handler.Result()\nprotocolError := protocol.Error{}\nif errors.As(err, protocolError) {\n  // get the list of culprits by calling protocolError.Culprits\n}\n// if the error is nil, then we can cast the result to the expected return type\nconfig := result.(*cmp.Config)\n```\n\nIf an error has occurred, it will be returned as a [`protocol.Error`](pkg/protocol/error.go),\nwhich may contain information on the responsible participants, if possible.\n\nWhen the protocol successfully completes, the result must be cast to the appropriate type.\n\n### Network\n\nMost messages returned by the protocol can be transmitted through a point-to-point network guaranteeing authentication, integrity and confidentiality.\nThe user is responsible for delivering the message to all participants for which `Message.IsFor(recipient)` returns `true`.\n\nSome messages however require a _reliable_ broadcast channel, which guarantees that all participants agree on which messages were sent.\nThese messages will have their `Message.Broadcast` field set to `true`.\nThe `protocol.Handler` performs an additional check due to [Goldwasser \u0026 Lindell](https://eprint.iacr.org/2002/040),\nwhich ensures that the protocol aborts when some participants incorrectly broadcast these types of messages.\nUnfortunately, identifying the culprits in this case requires external assumption which cannot be handled by this library.\n\n## Known Issues\n\n###\n\n\u003c!-- ### Keygen\n\nThe [`protocols/keygen`](protocols/cmp/keygen) package can be used to perform a distributed key generation.\n\nA [`protocol.Handler`](pkg/protocol/handler.go) is created by specifying the list of `partyIDs` who will receive a share,\n\nand the `selfID` corresponding to this party's ID.\n\nThe `threshold` defines the maximum number of corrupt parties tolerated.\n\nThat is, the secret key may only be reconstructed using any `threshold+1` different key shares.\n\nThis is therefore also the minimum number of participants required to create a signature.\n\n```go\n\npartyIDs := []party.ID{\"a\", \"b\", \"c\", \"d\", \"e\"}\n\nselfID := party.ID(\"a\")\n\nthreshold := 3\n\nkeygenHandler, err := protocol.NewHandler(keygen.StartKeygen(partyIDs, threshold, selfID))\n\nresult, err := runProtocolHandler(keygenHandler)\n\nif err != nil {\n\n // investigate error\n\n}\n\nconfig := r.(\\*keygen.Config)\n\n```\n\nThe [`config`](/protocols/cmp/keygen/config.proto) object contains all necessary data to create a signature.\n\n`Config.PublicKey()` returns an `ecdsa.PublicKey` for which the parties can generate signatures.\n\n### Refresh\n\nParticipant's shares of the ECDSA private key can be refreshed after the initial key generation was successfully performed.\n\nIt requires all share holders to be present, and the result is a new [`keygen.Config`](/protocols/cmp/keygen/config.go).\n\nThe original ECDSA public key remains the same, but the secret is refreshed.\n\n```go\n\nrefreshHandler, err := protocol.NewHandler(keygen.StartRefresh(config))\n\nresult, err := runProtocolHandler(keygenHandler)\n\nif err != nil {\n\n // investigate error\n\n}\n\nrefreshedConfig := r.(\\*keygen.Config)\n\n```\n\n### Sign\n\nThe [`sign`](/protocols/cmp/sign) protocol implements the \"3 Round\" signing protocol from CGGMP21, without pre-signing or identifiable aborts.\n\nBoth these features may be implemented in a future version of `multi-party-sig`.\n\nThe resulting signature is a valid ECDSA key.\n\n```go\n\nmessage := []byte(\"hello, world\")\n\n// since threshold is 3, we need for or more parties to\n\nsigners := []party.ID{\"a\", \"b\", \"c\", \"d\"}\n\nsignHandler, err := protocol.NewHandler(sign.StartSign(refreshedConfig, signers, message))\n\nresult, err := runProtocolHandler(signHandler)\n\nif err != nil {\n\n // investigate error\n\n}\n\nsignature := r.(\\*ecdsa.Signature)\n\nsignature.Verify(refreshedConfig.PublicPoint(), message)\n\n``` --\u003e\n\n## Intellectual property\n\nThis code is copyright (c) Adrian Hamelink and Taurus SA, 2021, and under Apache 2.0 license.\n\nOn potential patents: the company that sponsored the development of the CMP\nprotocol [stated](https://apnews.com/press-release/pr-newswire/26aab91e254bc254d331ceafc20b9859)\nthat it \"will not be applying for patents on this technology.\"\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaurushq-io%2Fmulti-party-sig","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftaurushq-io%2Fmulti-party-sig","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaurushq-io%2Fmulti-party-sig/lists"}