mirror of
https://github.com/sonr-io/crypto.git
synced 2026-09-17 01:26:23 +00:00
391 lines
17 KiB
Plaintext
391 lines
17 KiB
Plaintext
---
|
||||
|
|
title: Threshold Ed25519
|
|||
|
|
description: t-of-n Ed25519 signing whose output verifies under a stock Ed25519 verifier, plus FROST threshold Schnorr on top of a dkg/frost result.
|
|||
|
|
sidebar:
|
|||
|
|
order: 5
|
|||
|
|
icon: key-round
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
Two packages, two different bargains.
|
|||
|
|
|
|||
|
|
`ted25519/ted25519` produces **byte-for-byte standard Ed25519 signatures**. A verifier that has
|
|||
|
|
never heard of threshold cryptography — `crypto/ed25519`, a chain node, a JWT library — accepts
|
|||
|
|
them. That compatibility is the whole reason to use it, and it is what forces the package's
|
|||
|
|
unusual, and dangerous, nonce protocol.
|
|||
|
|
|
|||
|
|
`ted25519/frost` produces FROST threshold Schnorr signatures. Cleaner protocol, three tidy rounds,
|
|||
|
|
works over any curve — but the output is a `(Z, C)` pair, not an Ed25519 signature, and needs
|
|||
|
|
`frost.Verify`.
|
|||
|
|
|
|||
|
|
<CardGroup cols={2}>
|
|||
|
|
<Card title="ted25519/ted25519" icon="key-round">
|
|||
|
|
Pick this when the signature must be accepted by existing Ed25519 verifiers. Ed25519 only. Requires
|
|||
|
|
strict per-message nonce discipline.
|
|||
|
|
</Card>
|
|||
|
|
<Card title="ted25519/frost" icon="fingerprint">
|
|||
|
|
Pick this when you control the verifier. Curve-agnostic, three rounds, consumes a
|
|||
|
|
[`dkg/frost`](/threshold/dkg) result directly.
|
|||
|
|
</Card>
|
|||
|
|
</CardGroup>
|
|||
|
|
|
|||
|
|
## Standard-compatible: `ted25519/ted25519`
|
|||
|
|
|
|||
|
|
The package is a fork of Go's `crypto/ed25519` (itself a port of SUPERCOP `ref10`) with the
|
|||
|
|
threshold pieces added. It keeps the standard sizes:
|
|||
|
|
|
|||
|
|
| Constant | Value |
|
|||
|
|
| --- | --- |
|
|||
|
|
| `PublicKeySize` | 32 |
|
|||
|
|
| `PrivateKeySize` | 64 (seed ‖ public key) |
|
|||
|
|
| `SignatureSize` | 64 (R ‖ s) |
|
|||
|
|
| `SeedSize` | 32 |
|
|||
|
|
|
|||
|
|
Single-party helpers are drop-in: `GenerateKey(rand io.Reader)`, `NewKeyFromSeed(seed []byte)`,
|
|||
|
|
`Sign(priv, msg)`, `Verify(pub, msg, sig)`, plus `PrivateKey.Public()`, `.Seed()`, and a
|
|||
|
|
`crypto.Signer` implementation.
|
|||
|
|
|
|||
|
|
### Why the seed must be expanded before splitting
|
|||
|
|
|
|||
|
|
Standard Ed25519 signing hashes the seed to derive the actual scalar. That hash destroys linearity:
|
|||
|
|
shares of the *seed* are not shares of the *signing scalar*, so partial signatures would not
|
|||
|
|
aggregate. `ExpandSeed(seed []byte) []byte` applies that transform up front, and the split happens
|
|||
|
|
on the expanded value. `ThresholdSign` therefore skips the expansion step that ordinary Ed25519
|
|||
|
|
signing performs — which is exactly why it cannot be replaced with `Sign`.
|
|||
|
|
|
|||
|
|
### t-of-n signing
|
|||
|
|
|
|||
|
|
Every party contributes a nonce, all nonce shares are summed, and each party produces a partial
|
|||
|
|
signature under the summed nonce. `Aggregate` interpolates the `s` components.
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
import (
|
|||
|
|
"github.com/sonr-io/crypto/ted25519/ted25519"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func thresholdSign() error {
|
|||
|
|
config := ted25519.ShareConfiguration{T: 2, N: 3}
|
|||
|
|
|
|||
|
|
// 1. Shared key generation (trusted dealer — see the caveat below).
|
|||
|
|
pub, secretShares, keyCommitments, err := ted25519.GenerateSharedKey(&config)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Each holder can check its own share against the VSS commitments.
|
|||
|
|
for _, s := range secretShares {
|
|||
|
|
ok, err := s.VerifyVSS(keyCommitments, &config)
|
|||
|
|
if err != nil || !ok {
|
|||
|
|
return fmt.Errorf("bad share")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
message := ted25519.Message("test message")
|
|||
|
|
|
|||
|
|
// 2. Every party generates a nonce FOR THIS MESSAGE and shares it out.
|
|||
|
|
noncePub1, nonceShares1, _, err := ted25519.GenerateSharedNonce(&config, secretShares[0], pub, message)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
noncePub2, nonceShares2, _, err := ted25519.GenerateSharedNonce(&config, secretShares[1], pub, message)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
noncePub3, nonceShares3, _, err := ted25519.GenerateSharedNonce(&config, secretShares[2], pub, message)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. Sum the nonce shares index-wise, and the nonce pubkeys in the group.
|
|||
|
|
nonceShares := []*ted25519.NonceShare{
|
|||
|
|
nonceShares1[0].Add(nonceShares2[0]).Add(nonceShares3[0]),
|
|||
|
|
nonceShares1[1].Add(nonceShares2[1]).Add(nonceShares3[1]),
|
|||
|
|
nonceShares1[2].Add(nonceShares2[2]).Add(nonceShares3[2]),
|
|||
|
|
}
|
|||
|
|
noncePub := ted25519.GeAdd(ted25519.GeAdd(noncePub1, noncePub2), noncePub3)
|
|||
|
|
|
|||
|
|
// 4. Each party produces a partial signature.
|
|||
|
|
sig1 := ted25519.TSign(message, secretShares[0], pub, nonceShares[0], noncePub)
|
|||
|
|
sig2 := ted25519.TSign(message, secretShares[1], pub, nonceShares[1], noncePub)
|
|||
|
|
|
|||
|
|
// 5. Any T partials aggregate into a complete signature.
|
|||
|
|
sig, err := ted25519.Aggregate([]*ted25519.PartialSignature{sig1, sig2}, &config)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 6. And it verifies under the ordinary Ed25519 verifier.
|
|||
|
|
ok, err := ted25519.Verify(pub, message, sig)
|
|||
|
|
if err != nil || !ok {
|
|||
|
|
return fmt.Errorf("signature failed verification")
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
:::success[This is a plain Ed25519 signature]
|
|||
|
|
`sig` is 64 bytes of `R ‖ s` over the 32-byte public key `pub`. `crypto/ed25519.Verify` accepts it
|
|||
|
|
too. Nothing downstream needs to know a threshold protocol produced it — that is this package's
|
|||
|
|
entire value proposition.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
Note that **every** participant must run `GenerateSharedNonce`, not just the `T` who will sign.
|
|||
|
|
The nonce is the sum of all `N` contributions; a missing contribution changes `noncePub` and every
|
|||
|
|
partial signature becomes invalid.
|
|||
|
|
|
|||
|
|
### Types
|
|||
|
|
|
|||
|
|
<TypeTable
|
|||
|
|
type={{
|
|||
|
|
"ShareConfiguration.T": { type: "int", required: true, description: "Threshold — partial signatures needed to aggregate." },
|
|||
|
|
"ShareConfiguration.N": { type: "int", required: true, description: "Total shares issued." },
|
|||
|
|
"KeyShare": { type: "struct{ *v1.ShamirShare }", required: true, description: "A share of the expanded signing scalar. Construct with NewKeyShare(identifier byte, secret []byte); serialise with Bytes(), restore with KeyShareFromBytes." },
|
|||
|
|
"NonceShare": { type: "struct{ *KeyShare }", required: true, description: "A share of a per-message nonce. Add(other) sums two shares with the same identifier. NewNonceShare / NonceShareFromBytes mirror KeyShare." },
|
|||
|
|
"Commitments": { type: "[]curves.Point", required: true, description: "VSS commitments to the polynomial coefficients. CommitmentsToBytes / CommitmentsFromBytes for transport." },
|
|||
|
|
"PartialSignature.ShareIdentifier": { type: "byte", required: true, description: "Which signer produced this partial — the x-coordinate." },
|
|||
|
|
"PartialSignature.Sig": { type: "[]byte", required: true, description: "64 bytes, R ‖ s. R() and S() slice it; Bytes() returns identifier ‖ Sig." },
|
|||
|
|
}}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
Supporting functions: `PublicKeyFromBytes(bytes []byte)` (length-checks 32 bytes and returns them),
|
|||
|
|
`GeAdd(a, b PublicKey) PublicKey` (group addition of two public keys, used to sum nonce pubkeys),
|
|||
|
|
`Reconstruct(keyShares []*KeyShare, config *ShareConfiguration) ([]byte, error)`, and
|
|||
|
|
`ThresholdSign(expandedSecretKeyShare []byte, publicKey PublicKey, message []byte, rShare []byte, R PublicKey) []byte` —
|
|||
|
|
the raw form behind `TSign`, taking **little-endian** scalar bytes.
|
|||
|
|
|
|||
|
|
:::danger[Never reuse a nonce share across messages]
|
|||
|
|
Ed25519, like every Schnorr-family scheme, leaks the signing key if two signatures over
|
|||
|
|
*different* messages share the same nonce `R`. Given `s₁ = r + c₁·sk` and `s₂ = r + c₂·sk` with the
|
|||
|
|
same `r`, anyone computes `sk = (s₁ − s₂)/(c₁ − c₂)`. In a threshold setting this is worse, not
|
|||
|
|
better: the attacker only needs the two aggregate signatures, which are public.
|
|||
|
|
|
|||
|
|
`GenerateSharedNonce` takes the message `m` as an argument for exactly this reason — the nonce is
|
|||
|
|
derived per message. The source is explicit that determinism was deliberately avoided:
|
|||
|
|
|
|||
|
|
> We _must_ introduce randomness to the HKDF to make the output non-deterministic because
|
|||
|
|
> deterministic nonces open up threshold schemes to potential nonce-reuse attacks. We continue to
|
|||
|
|
> use the HKDF that takes in context about what is going to be signed as it adds some protection
|
|||
|
|
> against bad local randomness.
|
|||
|
|
|
|||
|
|
Concretely, the HKDF is keyed on `keyShare ‖ 32 fresh random bytes` with
|
|||
|
|
`info = "ted25519nonce" ‖ publicKey ‖ message`. So the rules are:
|
|||
|
|
|
|||
|
|
- Call `GenerateSharedNonce` **once per message per party**. Never cache the result.
|
|||
|
|
- Never persist a `NonceShare`. If a signing attempt aborts, discard every nonce share and start a
|
|||
|
|
fresh nonce round — do not retry with the old one.
|
|||
|
|
- Never sign two different messages with the same `noncePub`.
|
|||
|
|
- Do not "optimise" by making the nonce deterministic in the message. Two parties disagreeing about
|
|||
|
|
the message set while sharing a nonce is the same catastrophe.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::danger[Not constant time, on secret values, by the source's own admission]
|
|||
|
|
Two `WARN` comments sit directly on the secret-handling paths:
|
|||
|
|
|
|||
|
|
- In `generateSharableNonce`: *"WARN: This operation is not constant time and we are dealing with a
|
|||
|
|
secret value"* — the rejection-sampling loop that reduces the nonce into the field.
|
|||
|
|
- In `NonceShare.Add`: *"WARN: This is not constant time and deals with secrets"* — the
|
|||
|
|
`big.Int`-backed `curves.Element` addition.
|
|||
|
|
|
|||
|
|
`TSign`, `Aggregate`, and `Reconstruct` go through the same arithmetic. Do not run this where an
|
|||
|
|
attacker can measure your timing.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::warning[GenerateSharedKey is dealer-based, and there is no DKG for this package]
|
|||
|
|
`GenerateSharedKey` samples a key, expands it, and splits it — all in one process. There is no
|
|||
|
|
distributed alternative that yields standard-Ed25519-compatible shares in this repository.
|
|||
|
|
`dkg/frost` produces shares of a `curves.Scalar` in the modern representation, which is not
|
|||
|
|
interchangeable with the little-endian, field-reduced, expanded-seed representation this package
|
|||
|
|
requires. If dealerless generation is a requirement, use `ted25519/frost` and accept the
|
|||
|
|
non-standard signature format.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::warning[Built on the legacy sharing layer, with its truncation defect]
|
|||
|
|
`KeyShare` embeds `*v1.ShamirShare` and `Reconstruct` calls `v1.Shamir.Combine`, which
|
|||
|
|
[interpolates only the first `T` shares you pass](/threshold/secret-sharing) and silently ignores
|
|||
|
|
the rest. `VerifyVSS` also requires `len(commitments) >= config.T` and returns
|
|||
|
|
`(false, error)` rather than a typed failure — check both return values.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::info[Endianness will bite you]
|
|||
|
|
The Ed25519 reference code is little-endian; `curves.Field`/`curves.Element` are big-endian. The
|
|||
|
|
package reverses bytes at nearly every boundary (`ThresholdSign` documents that
|
|||
|
|
`expandedSecretKeyShare` and `rShare` "must be little-endian"). If you hand-roll anything with
|
|||
|
|
these types rather than using `TSign`, expect to get this wrong at least once. Prefer the high-level
|
|||
|
|
helpers.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::note[The 2-of-2 example in the tests is not a protocol]
|
|||
|
|
`twobytwo_test.go` demonstrates a simpler additive scheme with a file comment saying so plainly:
|
|||
|
|
*"We don't intend to use it and it is not modeled off of any specific known protocol."* Do not copy
|
|||
|
|
it into production.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
## FROST Schnorr: `ted25519/frost`
|
|||
|
|
|
|||
|
|
Three rounds implementing the signing half of
|
|||
|
|
[eprint 2020/852](https://eprint.iacr.org/2020/852.pdf), consuming a
|
|||
|
|
[`dkg/frost`](/threshold/dkg) participant directly. Despite the directory name it is not
|
|||
|
|
Ed25519-specific — it is generic over `curves.Curve`, and Ed25519 is one available challenge
|
|||
|
|
derivation.
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
import (
|
|||
|
|
"github.com/sonr-io/crypto/core/curves"
|
|||
|
|
dkg "github.com/sonr-io/crypto/dkg/frost"
|
|||
|
|
"github.com/sonr-io/crypto/sharing"
|
|||
|
|
"github.com/sonr-io/crypto/ted25519/frost"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// participants is the output of a completed dkg/frost run, keyed by id.
|
|||
|
|
func frostSign(curve *curves.Curve, participants map[uint32]*dkg.DkgParticipant) error {
|
|||
|
|
threshold, limit := uint32(2), uint32(3)
|
|||
|
|
|
|||
|
|
// Choose the signing set and precompute its Lagrange coefficients once.
|
|||
|
|
signerIds := []uint32{1, 3}
|
|||
|
|
scheme, err := sharing.NewShamir(threshold, limit, curve)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
lCoeffs, err := scheme.LagrangeCoeffs(signerIds)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
signers := make(map[uint32]*frost.Signer, len(signerIds))
|
|||
|
|
for _, id := range signerIds {
|
|||
|
|
signers[id], err = frost.NewSigner(
|
|||
|
|
participants[id], id, threshold, lCoeffs, signerIds,
|
|||
|
|
&frost.Ed25519ChallengeDeriver{},
|
|||
|
|
)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Round 1: commit to nonces -------------------------------------
|
|||
|
|
round2Input := make(map[uint32]*frost.Round1Bcast, len(signers))
|
|||
|
|
for id := range signers {
|
|||
|
|
out, err := signers[id].SignRound1()
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
round2Input[id] = out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Round 2: partial signatures -----------------------------------
|
|||
|
|
msg := []byte("message")
|
|||
|
|
round3Input := make(map[uint32]*frost.Round2Bcast, len(signers))
|
|||
|
|
for id := range signers {
|
|||
|
|
out, err := signers[id].SignRound2(msg, round2Input)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
round3Input[id] = out
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// --- Round 3: aggregate --------------------------------------------
|
|||
|
|
for id := range signers {
|
|||
|
|
out, err := signers[id].SignRound3(round3Input)
|
|||
|
|
if err != nil {
|
|||
|
|
return err
|
|||
|
|
}
|
|||
|
|
// Every signer derives the identical signature (out.Z, out.C).
|
|||
|
|
_ = out
|
|||
|
|
}
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
<Steps>
|
|||
|
|
<Step title="SignRound1() (*Round1Bcast, error)">
|
|||
|
|
The signer samples two secret nonces `d_i`, `e_i` and **broadcasts their commitments**
|
|||
|
|
`Round1Bcast{Di, Ei curves.Point}` — the two group elements only. Both secret nonces stay local.
|
|||
|
|
Two commitments rather than one is what lets FROST bind the final nonce to the whole signing set
|
|||
|
|
without an extra round.
|
|||
|
|
</Step>
|
|||
|
|
<Step title="SignRound2(msg []byte, round2Input map[uint32]*Round1Bcast) (*Round2Bcast, error)">
|
|||
|
|
Consumes every signer's round-1 broadcast (keyed by signer id, **including your own**), derives the
|
|||
|
|
binding factors and the joint nonce `R`, derives the challenge `c` via the injected
|
|||
|
|
`ChallengeDerive`, and **broadcasts** `Round2Bcast{Zi curves.Scalar, Vki curves.Point}` — this
|
|||
|
|
signer's partial signature `Zi` and its verification-key share `Vki`, which lets peers attribute
|
|||
|
|
and check the partial.
|
|||
|
|
</Step>
|
|||
|
|
<Step title="SignRound3(round3Input map[uint32]*Round2Bcast) (*Round3Bcast, error)">
|
|||
|
|
Consumes every partial, validates each against its `Vki`, and sums them. Returns
|
|||
|
|
`Round3Bcast{R curves.Point, Z, C curves.Scalar}`. Every honest signer produces the identical
|
|||
|
|
`Z` and `C`, so there is no separate coordinator role — whoever needs the signature simply keeps
|
|||
|
|
its own round-3 output.
|
|||
|
|
</Step>
|
|||
|
|
</Steps>
|
|||
|
|
|
|||
|
|
### Verifying
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
sig := &frost.Signature{Z: out.Z, C: out.C}
|
|||
|
|
ok, err := frost.Verify(curve, &frost.Ed25519ChallengeDeriver{}, vk, msg, sig)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`vk` is the joint verification key from DKG (`participant.VerificationKey`). The same
|
|||
|
|
`ChallengeDerive` used for signing must be used for verification.
|
|||
|
|
|
|||
|
|
```go
|
|||
|
|
type ChallengeDerive interface {
|
|||
|
|
DeriveChallenge(msg []byte, pubKey curves.Point, r curves.Point) (curves.Scalar, error)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`Ed25519ChallengeDeriver` is the implementation shipped in this package. A Mina-flavoured deriver
|
|||
|
|
lives with the [chain signature schemes](/signatures/chain-schemes). Supplying your own is how you
|
|||
|
|
adapt FROST to another verifier's challenge convention — and is also how you break interoperability
|
|||
|
|
if you get it wrong.
|
|||
|
|
|
|||
|
|
`Round1Bcast` and `Round2Bcast` each provide `Encode() ([]byte, error)` and
|
|||
|
|
`Decode(input []byte) error` for transport. `Round3Bcast` does not — it is a local output, not a
|
|||
|
|
message.
|
|||
|
|
|
|||
|
|
### Caveats
|
|||
|
|
|
|||
|
|
:::warning[Lagrange coefficients pin the signing set]
|
|||
|
|
`NewSigner` takes `lcoeffs map[uint32]curves.Scalar` and `cosigners []uint32`. The coefficients are
|
|||
|
|
precomputed for that exact set — the constructor doc cites this as the optimisation from paragraph 3
|
|||
|
|
of section 3 of the FROST draft. **Every signer must be constructed with the same `cosigners` and
|
|||
|
|
the same `lcoeffs`.** Change the set and you must build fresh `Signer` values with fresh
|
|||
|
|
coefficients; reusing stale coefficients yields a signature that fails verification, with no
|
|||
|
|
diagnostic pointing at the cause.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::danger[One Signer, one signature]
|
|||
|
|
The nonces sampled in `SignRound1` are single-use, and the round counter enforces it: calling
|
|||
|
|
`SignRound1` twice on the same `Signer` returns an error. Construct a new `Signer` per signature.
|
|||
|
|
Never persist a `Signer` between signatures, and never reuse round-1 broadcasts for a second
|
|||
|
|
message — that is nonce reuse and it discloses the signing share.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::warning[Inherits dkg/frost's context-string defect]
|
|||
|
|
`frost.NewSigner` takes a `*dkg.DkgParticipant` whose `ctx` was collapsed to a single byte at
|
|||
|
|
construction time. That flaw belongs to
|
|||
|
|
[DKG](/threshold/dkg) and is not repaired here — the signing rounds provide no additional
|
|||
|
|
cross-session replay protection.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
:::note[No aggregate-only role, no identifiable abort]
|
|||
|
|
Every signer runs all three rounds; there is no lightweight aggregator. And a failed partial-signature
|
|||
|
|
check aborts without producing transferable evidence of which signer cheated.
|
|||
|
|
:::
|
|||
|
|
|
|||
|
|
## Next
|
|||
|
|
|
|||
|
|
<CardGroup cols={2}>
|
|||
|
|
<Card title="Distributed Key Generation" href="/threshold/dkg" icon="git-branch">
|
|||
|
|
`ted25519/frost` needs a completed `dkg/frost` participant as its input.
|
|||
|
|
</Card>
|
|||
|
|
<Card title="Chain Signature Schemes" href="/signatures/chain-schemes" icon="link">
|
|||
|
|
Where the Mina challenge deriver lives.
|
|||
|
|
</Card>
|
|||
|
|
<Card title="Secret Sharing" href="/threshold/secret-sharing" icon="split">
|
|||
|
|
`LagrangeCoeffs`, and the legacy `v1` layer `ted25519/ted25519` is built on.
|
|||
|
|
</Card>
|
|||
|
|
<Card title="Threshold ECDSA" href="/threshold/threshold-ecdsa" icon="pen-tool">
|
|||
|
|
The 2-of-2 ECDSA side of the house.
|
|||
|
|
</Card>
|
|||
|
|
</CardGroup>
|