{"id":13630347,"url":"https://github.com/taurushq-io/frost-ed25519","last_synced_at":"2026-01-11T23:58:42.263Z","repository":{"id":48875503,"uuid":"315706390","full_name":"taurushq-io/frost-ed25519","owner":"taurushq-io","description":"Implementation of the FROST protocol for threshold Ed25519 signing","archived":false,"fork":false,"pushed_at":"2024-05-16T13:26:27.000Z","size":422,"stargazers_count":66,"open_issues_count":2,"forks_count":16,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-15T02:57:52.035Z","etag":null,"topics":["cryptography","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":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-11-24T17:32:04.000Z","updated_at":"2025-03-05T07:13:39.000Z","dependencies_parsed_at":"2024-06-19T05:22:15.909Z","dependency_job_id":"60b00e16-bca7-401e-a183-84518d52c458","html_url":"https://github.com/taurushq-io/frost-ed25519","commit_stats":null,"previous_names":["taurusgroup/frost-ed25519"],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Ffrost-ed25519","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Ffrost-ed25519/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Ffrost-ed25519/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/taurushq-io%2Ffrost-ed25519/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/taurushq-io","download_url":"https://codeload.github.com/taurushq-io/frost-ed25519/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248997095,"owners_count":21195797,"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","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","multi-party-computation","signature"],"created_at":"2024-08-01T22:01:39.607Z","updated_at":"2026-01-11T23:58:42.256Z","avatar_url":"https://github.com/taurushq-io.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# FROST-Ed25519\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 a [FROST](https://eprint.iacr.org/2020/852.pdf) threshold signature protocol for the Ed25519 signature scheme.\n\nOur FROST protocol implementation is also inspired from that in the IETF Draft [Threshold Modes in Elliptic Curves ](https://www.ietf.org/id/draft-hallambaker-threshold-05.html).\n\n## Ed25519\n\nEd25519 is an instance of the EdDSA construction, defined over the Edwards 25519 elliptic curve.\nFROST-Ed25519 is compatible with Ed25519, in the sense that public keys follow the same prescribed format,\nand that the same verification algorithm can be used.\n\nSpecifically, we implement the _PureEdDSA_ variant, as detailed in [RFC 8032](https://tools.ietf.org/html/rfc8032)\n(as opposed to HashEdDSA/Ed25519ph or ContextEdDSA/Ed25519ctx.).\n\n### Ristretto\n\nIn order to minimize the impact of the cofactor in the Edwards 25519 elliptic curve, we represent the curve points with the\n[Ristretto](https://ristretto.group/) encoding.\nOur implementation is taken from Filippo Valsorda's [branch](https://github.com/gtank/ristretto255/tree/filippo/edwards25519backend)\nof George Tankersley's [ristretto255](https://github.com/gtank/ristretto255).\nInternally it uses the [edwards25519](https://github.com/FiloSottile/edwards25519) package.\n\nWe add a `BytesEd25519()` method on group elements which allows us to recover an Ed25519 compatible encoding of the curve point.\nAs elements are represented internally by `edwards25519.Point`, we take this point `P` and remove the cofactor by computing `P' = [8^{-1}][8]P`.\nThe result is the canonical encoding of `P'`.\n\nFor clarity, we distinguish the following elements:\n\n- `B` is the base point of the Edwards 25519 elliptic curve\n- `G` is the generator of the Ristretto group\n\nThe integer `q` is equal to `2**252 + 27742317777372353535851937790883648493` and is the prime order of the Ristretto group `\u003cG\u003e`.\n\n### Keys\n\nThe Ed25519 standard defines the private signing key as a 32 byte _seed_ `x`.\nTaking the SHA-512 hash of `x` yields two 32 byte strings by splitting the output in two equal halves: `SHA-512(x) = s || prefix`.\nThe value `s` encodes 32 byte integer, while the `prefix` is used later for deterministic signature generation.\nThe public key is the canonical representation of the elliptic curve point `A = [s mod q] • B`.\n\nIn FROST-Ed25519, a group of `n` parties `P1, ..., Pn` each hold a _Shamir share_ `s_i` of the secret integer `s`.\nThese shares are represented as integers mod `q`.\nGiven any set of at least `t+1` distinct shares, it is possible to recover the original full secret key `s mod q`. \nThe integer `t` is the _threshold_ of the scheme, and defines the maximum number of parties that could act maliciously (i.e. collaborate to recover the key).\n\nIn FROST-Ed25519, the parties obtain their shares of `s` by executing a Distributed Key Generation (DKG) protocol.\nIn addition to receiving individual shares `s_i`, all parties also obtain the _group key_ `A = [s]•G`, and its associated public shares `{A_i = [s_i]•G}`.\n\nAfter a successful execution of the DKG protocol, each party `Pi` obtains:\n\n- a secret share `s_i` represented as a [`eddsa.SecretShare`](pkg/eddsa/secret_share.go) struct\n- a set of all public shares `{A_i}` stored in [`eddsa.Public`](pkg/eddsa/public.go) struct\n- the group key `A` represented as a [`eddsa.PublicKey`](pkg/eddsa/public_key.go), and stored in the `GroupKey` field of [`eddsa.Public`](pkg/eddsa/public.go).\n  Calling `PublicKey.ToEd25519()` returns an `ed25519.PublicKey` compatible with the Ed25519 standard.\n  \n### Signatures\n\nA FROST-Ed25519 signature for a message `M` is defined by a pair `(R,S)` where: \n\n- The nonce `R` represents a Ristretto group element, computed as `R = [r]•G` for some `r` mod `q`.\n- `S` is a scalar derived computed as\n\n```\nR = [r]•G\nk = SHA-512(R.BytesEd25519() || A.BytesEd25519() || M)\nS = (r + k * s) mod q\n```\n\nIn the original Ed25519 scheme, the nonce `R = [r]•B` is generated deterministically using the `prefix` in the key generation,\nand the integer `r` is computed as `r = H( prefix || M )`.\nFor threshold signing, it is harder to generate nonce in such a deterministic way.\nIn FROST-Ed25519, the nonce pair `(r,R)` is generated as detailed in the [FROST paper](https://eprint.iacr.org/2020/852.pdf)\n\nFor compatibility with Ed25519, `k` is computed by encoding `R` and `A` as their canonical representations in the edwards25519 curve (cofactor-less).\n\nSignatures are represented by the [`eddsa.Signature`](pkg/eddsa/signature.go) type.\n\n### Verification\n\nThe verification algorithm takes a public key `A`, the signed message `M`, and its signature `(R,S)`.\nIt does the following:\n\n- Recompute `k = SHA-512(R.BytesEd25519() || A.BytesEd25519() || M) mod q` \n- Verify the equality `R == [-k]•A + [S]•G`\n\nManual verification is not necessary in most cases, but is possible by calling `PublicKey.Verify(message []byte, signature *eddsa.Signature)`.\n\n_Note_: the cofactor is no longer an issue here, since we are considering points in the Ristretto group.\n\n### Compatibility with `ed25519`:\n\nThe goal of FROST-Ed25519 is to be compatible with the `ed25519` library included in Go.\nIn particular, the [`frost.PublicKey`](pkg/eddsa/public_key.go) and [`frost.Signature`](pkg/eddsa/signature.go) types can be converted to the `ed25119.PublicKey` and `[]byte` types respectively,\nby calling `.ToEd25519()`.\n\n### Example\n\nThe following example shows some possible interaction with the types described above:\n\n```go\nvar (\n    id          party.ID                // id of the party\n    secretShare *eddsa.SecretShare      // private output of DKG for party id\n    public      *eddsa.Public           // public output of DKG\n    message     []byte                  // message signed\n    groupSig    *eddsa.Signature        // signature produced by sign protocol for message\n)\n\ngroupKey := public.GroupKey\n// use the ed25519 library\ned25519.Verify(groupKey.ToEd25519(), message, groupSig.ToEd25519()) // = true\n\nsecretShare.ID == id    // = true\n```\n\n## Protocol version implemented\n\nThe FROST paper proposes two variants of the protocol. \nWe implement the \"single-round\" version of FROST, rather than the 4-round variant FROST-Interactive.\n\nThe single-round version does one \"offline\" round, followed by one \"online\" round, where the offline round does not need the message and can therefore be precomputed.\nFor simplicity, we group both steps together and achieve a 2 round protocol that requires less state handling.\nWe also ignore the role of _signature aggregator_ and instead let the parties broadcast the signature shares to each other to obtain the full signature.\n\nThis variant is the one that is proposed for practical implementations, however it does not have a full security proof, unlike FROST-Interactive (see [Section 6.2](https://eprint.iacr.org/2020/852.pdf) of the FROST paper).\n\n## Instructions\n\nThis FROST-Ed25519 implementation includes a round-based architecture for both the key generation and signing protocols.\nThe cryptographic protocols are defined in [pkg/frost/keygen]() and [pkg/frost/sign]().\nThey are handled by a [`State`](pkg/state/state.go) object that takes care of storing messages, passing them to the round at the right time, and reporting any error that may have occurred.\n\nUsers of this library should only interact with [`State`](pkg/state/state.go) types. \n\n### Basics\n\nEach party must be assigned a unique numerical [`party.ID`](pkg/frost/party/id.go) (internally represented as an `uint16`).\nA set of `party.ID`s is stored as a [`party.IDSlice`](pkg/frost/party/set.go) which wraps a slice and ensures sorting.\n\nOptionally, a `timeout` argument can be provided, to force the protocol to abort if the time duration between two received messages is longer than `timeout`.\nIf it is set to 0, then there is no limit.\n\nAppropriate [`State`](pkg/state/state.go)s can be created by calling the functions [`frost.NewKeygenState`](pkg/frost/frost.go) or [`frost.NewSignState`](pkg/frost/frost.go).\nThey both return the following:\n- A [`State`](pkg/state/state.go) object used to interact with the protocol\n- An `Output` object whose attributes are initialized to `nil`, and populated asynchronously when protocol has successfully completed.\n- An `error` indicating whether the state was successfully created.\n\nAn example of how to use the  [`State`](pkg/state/state.go) struct can be found in [example/main.go]().\n\n### Keygen\n\nThe key generation protocol we implement is as described in the original paper.\n\nCalling [`frost.NewKeygenState`](pkg/frost/frost.go) with the following arguments creates a [`State`](pkg/state/state.go) object that can execute the protocol. \n```go\nvar (\n    partyID     party.ID        // ID of the party initiating the key generation (`ID` type is an alias for `uint16`)\n    partyIDs    party.IDSlice   // sorted slice of all party IDs \n    threshold   party.Size      // maximum number of corrupted parties allowed (`threshold`+1 parties required for signing)\n    timeout     time.Duration   // maximum time allowed between two messages received. A duration of 0 indicates no timeout\n)\n\nstate, output, err := frost.NewKeygenState(partyID, partyIDs, threshold, timeout)\n```\n\nOnce the protocol has finished, the [`output`](pkg/frost/keygen/output.go) contains the following two fields:\n\n- [`Public`](pkg/eddsa/public.go)\n  contains the public key shares of all parties that participated in the protocol,\n  as well as the group key these define.\n- [`SecretKey`](pkg/eddsa/secret_share.go) is the party's share of the group's signing key.\n\n### Sign\n\n\n```go\nvar (\n        partyIDs    party.IDSlice       // slice of party IDs which will be performing the signing (must be of length at least `threshold`+1)\n        secret      *eddsa.SecretShare  // the secret key share obtained from the keygen protocol\n        public      *eddsa.Public       // contains the public information including the group key and individual public shares\n        message     []byte              // message in bytes to be signed (does not need to be prehashed)\n        timeout     time.Duration       // maximum time allowed between two messages received. A duration of 0 indicates no timeout\n)\n\nstate, output, err := frost.NewSignState(partySet, secret, public, message, timeout)\n```\n\nOnce the protocol has finished, the [`output`](pkg/frost/sign/output.go) contains a single field for the [`Signature`](pkg/eddsa/signature.go):\n\nThe Signature can be verified using Go's included `ed25519` library, by converting the group key and signature to compatible types.\n```go\ned25519.Verify(shares.GroupKey.ToEd25519(), message, output.Signature.ToEd25519())\n```\n\nor alternatively,\n\n\n### Transport Layer\n\nIf the round was successfully executed, `State.ProcessAll()` returns a slice [`[]*messages.Message`](pkg/messages/messages.go).\nIt is up to the user of this library to properly route messages between participants.\nThe ID's of the sender and destination party of a particular [`messages.Message`](pkg/messages/messages.go) can be found in the `From` and `To` field of the embedded [`messages.Header`](pkg/messages/header.go)\non the [`messages.Message`](pkg/messages/messages.go) object.\nUsers should first check if the message is intended for broadcast by calling `.IsBroadcast()`, since the `To` field is undefined in this case.\n\n```go\nvar msg messages.Message\ndata, err := msg.MarshalBinary()\nif err != nil {\n\t// handle marshalling error, but we cannot continue\n\treturn\n}\nif msg.IsBroadcast() {\n\t// send data to all parties except ourselves\n} else {\n\tdest := msg.To\n\t// send data to party with ID dest\n}\n```\n\nOn the reception, the message should be unmarshalled and then given to the `State`:\n```go\nvar data []byte\nvar msg messages.Message\nerr := msg.Unmarshal(data)\nif err != nil {\n\t// handle marshalling error, but we cannot continue\n\treturn\n}\nerr = state.HandleMessage(\u0026msg)\nif err != nil {\n\t// May indicate that an error occurred during transport\n\t// does not mean we should abort necessarily\n\treturn\n}\n```\n\n### Testing\n\nWe include unit tests for individual modules, as well as a bigger integration tests in [test/](test/).\nFull test coverage is however not guaranteed.\n\n### Example usage\n\nA simple example of how to use this library can be found in [test/sign_test.go](test/sign_test.go) and [test/keygen_test.go](test/keygen_test.go).\n\n## Security\n\nThis library was NOT designed to be free of side channels (timing, memory, oracles, and so on), and due to Go's intrinsic limitations most likely is not.\n\nThis library has yet to be audited and fully vetted for production usage.\nUse at your own risk.\n\nPlease report any critical security issue to security@taurusgroup.ch.\nWe encourage you to use our PGP key:\n\n```\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmDMEX3G3ARYJKwYBBAHaRw8BAQdA7sQCSqSkAmGylsLRJepXuAZKkcWA+EWRPeGa\n22cIXYC0KVRhdXJ1cyBTZWN1cml0eSA8c2VjdXJpdHlAdGF1cnVzZ3JvdXAuY2g+\niJAEExYIADgWIQQ0q1qzH0uLrdBgWQWfaUpuIE2KEAUCX3G3AQIbIwULCQgHAgYV\nCgkICwIEFgIDAQIeAQIXgAAKCRCfaUpuIE2KEFn9AP9uAyItJevrH8rV3K4zO25X\n7nOI8MQJagBMnGxP+FdF7QD8D3LndQy2AefifK44v8BOKHs0J/hXtkIJTFLu6IzG\nMwA=\n=QNKX\n-----END PGP PUBLIC KEY BLOCK-----\n```\n\nIssues that are not critical (not exploitable, DoS, and so on) can be reported as [GitHub Issues](https://github.com/taurusgroup/frost-ed25519/issues).\n\n\n## Dependencies \n\nOur package has a [minimal set](./go.mod) of third-party dependencies, mainly Valsorda's [edwards25519](https://filippo.io/edwards25519).\nWe also include the single `ristretto255` file from [PR 41](https://github.com/gtank/ristretto255/pull/41)\n\n## Intellectual property\n\nThis code is copyright (c) Taurus SA, 2021, and under Apache 2.0 license.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaurushq-io%2Ffrost-ed25519","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftaurushq-io%2Ffrost-ed25519","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftaurushq-io%2Ffrost-ed25519/lists"}