frostito 0.7.0

nested FROST, hardened DKG, and proactive resharing, on ZF frost-core
Documentation
frostito-0.7.0 has been yanked.

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.

[dependencies]
frostito = { git = "https://github.com/penumbrafi/frostito", features = ["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 curve25519_dalek::ristretto::RistrettoPoint as Point;
use frostito::{dkg, sealed};

let (epoch, t, n) = (7u64, 2u32, 3u32);

// round 1 — every participant deals
let dealer: dkg::Dealer<Point> = dkg::Dealer::new(me, t, &mut rng)?;
let my_package = dealer.round1_package(epoch, &mut rng);

let mut state: dkg::DkgState<Point> = dkg::DkgState::new(epoch, t, n);
for package in round1_packages {
    state.submit_commitment(package)?;   // verifies the proof of knowledge
}

// 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(&peer_digests, n as usize)?;

// round 2 — sealed per recipient, bound to sender, recipient and ceremony
let roster = sealed::SealedRoster::new(&x25519_pubkeys, session_id)?;
for j in 1..=n {
    let packet = sealed::seal_subshare::<Point>(
        &my_x25519_secret, &roster, 2,
        &dealer.generate_subshare(j)?, dealer.commitment(),
    )?;
    send_to(j, packet);
}

// aggregate over exactly the agreed dealer set
let mut agg = dkg::Aggregator::<Point>::from_agreed(me, &agreed)?;
for packet in inbox {
    let sub = sealed::open_subshare_agreed::<Point>(
        &my_x25519_secret, me, &roster, 2, &packet, &agreed,
    )?;
    let commitment = agreed.commitment(sub.dealer_index)?.clone();
    agg.add_subshare(sub, &commitment)?;
}

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 frostito::reshare::{Dealer, Aggregator};

// old custodians become dealers
let dealer = Dealer::new(index, current_share, new_threshold, &mut rng)?;
let commitment = dealer.commitment().clone();
let subshare = dealer.generate_subshare(player_index)?;

// 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 = Aggregator::new(player_index, &dealer_set)?;
aggregator.add_subshare(subshare, commitment)?;   // rejects dealers outside S
let (new_share, polynomial) = aggregator.finalize(&group_pubkey)?;

// `polynomial` is the epoch's public key package, identical on every player:
let verifying_share_j = polynomial.verifying_share(j);   // g^{s'_j}
assert!(polynomial.verify_share(player_index, &new_share));

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 disqualification
  • osst::sealed — encrypted, authenticated DKG round 2 (feature sealed)
  • osst::frost — plain FROST signing
  • osst::nested — nested FROST
  • osst::reshare — proactive secret sharing
  • osst::liveness — checkpoint proofs for holder participation
  • osst::context — epoch-bound signing contexts
  • osst::zf — Decaf377Sha512, a frost_core::Ciphersuite for decaf377 (feature zf-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