frostito
threshold Schnorr: distributed key generation, FROST signing, nested FROST, and proactive resharing.
the signing math is being rooted in ZF frost-core;
what stays here is the part it does not cover — confidential authenticated DKG
round 2, dealer-equivocation detection, complaints, committee rotation, and
nested FROST.
using it
repository and cargo package are both frostito.
[]
= { = "https://github.com/penumbrafi/frostito", = ["sealed"] }
there are no release tags. pin a rev.
features
default build is std + ristretto255. everything else is off by default.
| feature | what it is |
|---|---|
sealed |
DKG round 2 over Noise_K: sub-shares encrypted and authenticated per recipient (osst::sealed). needs std |
zf |
ZF frost-core 3.0 as the signing core, with internals. zf-ristretto255 / zf-secp256k1 add ZF's ciphersuite for that backend; zf-decaf377 enables osst::zf, which supplies the decaf377 ciphersuite ZF does not ship |
no_std on every backend.
curves
| feature | curve | compatible with |
|---|---|---|
ristretto255 |
curve25519 | polkadot, sr25519 |
pallas |
pallas (curve generator) | generic pallas |
pallas |
pallas in the orchard spend-auth group (OrchardSpendAuthCurve) |
zcash orchard, ZF reddsa / frost-core FROST(Pallas) |
secp256k1 |
secp256k1 | bitcoin, ethereum |
decaf377 |
decaf377 | penumbra |
everything is generic over P: OsstPoint. the snippets below fix a concrete
point type.
distributed key generation
round 1: Feldman commitment plus a proof of knowledge of the constant term. echo round: participants compare digests of the round-1 set, so a dealer cannot hand two of them different commitments. round 2: one sealed sub-share per recipient.
use RistrettoPoint as Point;
use ;
let = ;
// round 1 — every participant deals
let dealer: Dealer = new?;
let my_package = dealer.round1_package;
let mut state: DkgState = new;
for package in round1_packages
// echo round — you broadcast `agreed.digest()`, and every peer must match.
// a mismatch is a dealer equivocating or a broadcast that is not reliable;
// either way the ceremony aborts.
let agreed = state.agreed_round1?;
agreed.confirm_all?;
// round 2 — sealed per recipient, bound to sender, recipient and ceremony
let roster = new?;
for j in 1..=n
// aggregate over exactly the agreed dealer set
let mut agg = from_agreed?;
for packet in inbox
let my_share = agg.finalize?; // s_me
let group_pubkey = agg.derive_group_key?; // Y
open_subshare_agreed discards the plaintext of a sub-share that fails the
Feldman check. open_subshare_agreed_with_evidence returns it, which is what
dkg::Complaint needs to be checkable by a third party. dkg::ComplaintTally
gates disqualification on t distinct accusers.
nested FROST
frostito::nested splits one outer FROST position among an inner group. the
outer share is never materialized as a scalar by any party. inner signers hold
the message and the full commitment set, so a coordinator cannot obtain a
share for a payload the signer has not seen.
worked flow: examples/narsil_nested.rs
(interleaved DKG, escrow authorization, outer aggregation).
docs/nested-frost-v1-vs-v2.svg shows what
the insecure v1 did differently; v1 itself is gone.
resharing
rotates the custodian set. the group public key is unchanged.
use ;
// old custodians become dealers
let dealer = new?;
let commitment = dealer.commitment.clone;
let subshare = dealer.generate_subshare?;
// every new custodian must agree on the dealer set S *before* aggregating
// (e.g. via a signed epoch manifest). players that aggregate over different
// subsets land on different polynomials that pass the group-key check
// individually but never sign together.
let mut aggregator = new?;
aggregator.add_subshare?; // rejects dealers outside S
let = aggregator.finalize?;
// `polynomial` is the epoch's public key package, identical on every player:
let verifying_share_j = polynomial.verifying_share; // g^{s'_j}
assert!;
ReshareState::dealer_set() gives the deterministic choice (the t_old
lowest committed dealer indices) for coordinators to put in the manifest.
modules
osst::dkg— distributed key generation over an agreed dealer set; complaints and disqualificationosst::sealed— encrypted, authenticated DKG round 2 (featuresealed)osst::frost— plain FROST signingosst::nested— nested FROSTosst::reshare— proactive secret sharingosst::liveness— checkpoint proofs for holder participationosst::context— epoch-bound signing contextsosst::zf—Decaf377Sha512, afrost_core::Ciphersuitefor decaf377 (featurezf-decaf377)osst::curve— curve backend traits
docs/frostito-design.svg is the architecture diagram.
canonical repo
canonical source: https://github.com/penumbrafi/frostito.
github.com/rotkonetworks/frostito mirrors main so existing pins
(rev = "14e38da") keep resolving.
the vendored copies in zcli (crates/osst) and zk.poker
(crates/frostito) are being replaced by a git dependency on this repo.
license
MIT OR Apache-2.0