use alloc::vec;
use alloc::vec::Vec;
use sha2::{Digest, Sha512};
use crate::curve::{CurvePoint, CurveScalar};
use crate::dkg;
use crate::error::Error;
use crate::lagrange::compute_lagrange_coefficients;
use crate::reshare::DealerCommitment;
use crate::SecretShare;
pub struct CoefficientDkg<P: CurvePoint> {
pub coeff_index: u32,
pub dealers: Vec<dkg::Dealer<P>>,
}
pub struct InnerShare<S: CurveScalar> {
pub holder_index: u32,
pub coefficient_shares: Vec<S>,
}
impl<S: CurveScalar> InnerShare<S> {
pub fn eval_at(&self, x: u32) -> S {
let x_scalar = S::from_u32(x);
let mut result = S::zero();
let mut x_pow = S::one();
for alpha in &self.coefficient_shares {
result = result.add(&alpha.mul(&x_pow));
x_pow = x_pow.mul(&x_scalar);
}
result
}
}
pub type InterleavedDkg<P> = (Vec<InnerShare<<P as CurvePoint>::Scalar>>, Vec<P>);
pub fn interleaved_dkg<P: CurvePoint, R: rand_core::RngCore + rand_core::CryptoRng>(
inner_n: u32,
inner_t: u32,
outer_t: u32,
rng: &mut R,
) -> Result<InterleavedDkg<P>, Error> {
let mut coeff_dkgs: Vec<CoefficientDkg<P>> = Vec::with_capacity(outer_t as usize);
for j in 0..outer_t {
let dealers: Vec<dkg::Dealer<P>> = (1..=inner_n)
.map(|k| dkg::Dealer::new(k, inner_t, rng).expect("index is 1-indexed by construction"))
.collect();
coeff_dkgs.push(CoefficientDkg {
coeff_index: j,
dealers,
});
}
let mut coeff_commitments: Vec<P> = Vec::with_capacity(outer_t as usize);
for dkg_j in &coeff_dkgs {
let mut commitment = P::identity();
for dealer in &dkg_j.dealers {
commitment = commitment.add(dealer.commitment().share_commitment());
}
coeff_commitments.push(commitment);
}
let mut inner_shares: Vec<InnerShare<P::Scalar>> = Vec::with_capacity(inner_n as usize);
for k in 1..=inner_n {
let mut coefficient_shares = Vec::with_capacity(outer_t as usize);
for dkg_j in &coeff_dkgs {
let commitments: Vec<&DealerCommitment<P>> =
dkg_j.dealers.iter().map(|d| d.commitment()).collect();
let dealer_set: Vec<u32> = dkg_j.dealers.iter().map(|d| d.index()).collect();
let mut agg: dkg::Aggregator<P> = dkg::Aggregator::new(k, &dealer_set)?;
for dealer in &dkg_j.dealers {
let subshare = dealer.generate_subshare(k).expect("index is 1-indexed by construction");
agg.add_subshare(subshare, commitments[(dealer.index() - 1) as usize])?;
}
coefficient_shares.push(agg.finalize()?);
}
inner_shares.push(InnerShare {
holder_index: k,
coefficient_shares,
});
}
Ok((inner_shares, coeff_commitments))
}
pub fn split_evaluation_for_inner<P: CurvePoint, R: rand_core::RngCore + rand_core::CryptoRng>(
evaluation: &P::Scalar,
inner_n: u32,
inner_t: u32,
rng: &mut R,
) -> (Vec<(u32, P::Scalar)>, DealerCommitment<P>) {
let mut coeffs = vec![evaluation.clone()];
for _ in 1..inner_t {
coeffs.push(P::Scalar::random(rng));
}
let commitment = DealerCommitment::from_polynomial(1, &coeffs).expect("index is 1-indexed by construction");
let shares = (1..=inner_n)
.map(|k| {
let x = P::Scalar::from_u32(k);
let mut result = P::Scalar::zero();
let mut x_pow = P::Scalar::one();
for c in &coeffs {
result = result.add(&c.mul(&x_pow));
x_pow = x_pow.mul(&x);
}
(k, result)
})
.collect();
(shares, commitment)
}
pub fn verify_split_piece<P: CurvePoint>(
commitment: &DealerCommitment<P>,
holder_index: u32,
piece: &P::Scalar,
) -> bool {
commitment.verify_subshare(holder_index, piece)
}
pub fn combine_shares<S: CurveScalar>(
inner_share: &InnerShare<S>,
nested_position: u32,
outer_eval_pieces: &[(u32, S)],
) -> S {
let mut result = inner_share.eval_at(nested_position);
for (_, piece) in outer_eval_pieces {
result = result.add(piece);
}
result
}
pub struct InnerNonces<S: CurveScalar> {
pub holder_index: u32,
pub session_id: [u8; 32],
pub(crate) hiding: S,
pub(crate) binding: S,
}
impl<S: CurveScalar> Drop for InnerNonces<S> {
fn drop(&mut self) {
self.hiding.zeroize();
self.binding.zeroize();
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct InnerCommitments<P: CurvePoint> {
pub holder_index: u32,
pub session_id: [u8; 32],
pub hiding: P,
pub binding: P,
}
pub fn inner_commit<P: CurvePoint, R: rand_core::RngCore + rand_core::CryptoRng>(
holder_index: u32,
session_id: [u8; 32],
rng: &mut R,
) -> (InnerNonces<P::Scalar>, InnerCommitments<P>) {
let hiding = P::Scalar::random(rng);
let binding = P::Scalar::random(rng);
let commitments = InnerCommitments {
holder_index,
session_id,
hiding: P::generator().mul_scalar(&hiding),
binding: P::generator().mul_scalar(&binding),
};
(
InnerNonces {
holder_index,
session_id,
hiding,
binding,
},
commitments,
)
}
pub struct InnerSignatureShare<S: CurveScalar> {
pub holder_index: u32,
pub response: S,
}
impl<S: CurveScalar> core::fmt::Debug for InnerSignatureShare<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("InnerSignatureShare")
.field("holder_index", &self.holder_index)
.field("response", &"[REDACTED]")
.finish()
}
}
#[cfg(all(test, feature = "ristretto255"))]
mod tests {
use super::*;
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;
use rand::rngs::OsRng;
type Point = RistrettoPoint;
#[test]
fn test_interleaved_dkg() {
let mut rng = OsRng;
let inner_n = 5u32;
let inner_t = 3u32;
let outer_t = 2u32;
let (inner_shares, coeff_commitments) =
interleaved_dkg::<Point, _>(inner_n, inner_t, outer_t, &mut rng).unwrap();
assert_eq!(inner_shares.len(), inner_n as usize);
assert_eq!(coeff_commitments.len(), outer_t as usize);
for (j, _) in coeff_commitments.iter().enumerate().take(outer_t as usize) {
let shares_j: Vec<(u32, Scalar)> = inner_shares
.iter()
.map(|s| (s.holder_index, s.coefficient_shares[j]))
.collect();
let active: Vec<u32> = shares_j[..inner_t as usize]
.iter()
.map(|s| s.0)
.collect();
let lambda = compute_lagrange_coefficients::<Scalar>(&active).unwrap();
let mut reconstructed = Scalar::ZERO;
for (i, (_, val)) in shares_j[..inner_t as usize].iter().enumerate() {
reconstructed += lambda[i] * val;
}
let expected_point = Point::generator().mul_scalar(&reconstructed);
assert_eq!(expected_point, coeff_commitments[j]);
}
}
#[test]
fn test_split_evaluation_with_feldman_verification() {
let mut rng = OsRng;
let secret = Scalar::random(&mut rng);
let inner_n = 5u32;
let inner_t = 3u32;
let (pieces, commitment) =
split_evaluation_for_inner::<Point, _>(&secret, inner_n, inner_t, &mut rng);
for &(k, ref piece) in &pieces {
assert!(
verify_split_piece::<Point>(&commitment, k, piece),
"piece {} failed feldman verification",
k
);
}
let tampered = Scalar::random(&mut rng);
assert!(!verify_split_piece::<Point>(&commitment, 1, &tampered));
}
}
pub fn inner_precommit<P: CurvePoint>(c: &InnerCommitments<P>) -> [u8; 32] {
let mut h = Sha512::new();
h.update(b"frostito-inner-precommit-v2");
h.update(c.holder_index.to_le_bytes());
h.update(c.session_id);
h.update(c.hiding.compress());
h.update(c.binding.compress());
let full: [u8; 64] = h.finalize().into();
let mut out = [0u8; 32];
out.copy_from_slice(&full[..32]);
out
}
pub fn verify_inner_precommit<P: CurvePoint>(
precommit: &[u8; 32],
revealed: &InnerCommitments<P>,
) -> bool {
let computed = inner_precommit(revealed);
let mut diff = 0u8;
for (a, b) in computed.iter().zip(precommit.iter()) {
diff |= a ^ b;
}
diff == 0
}
pub fn aggregate_inner_commitment_pair<P: CurvePoint>(
session_id: &[u8; 32],
precommits: &[(u32, [u8; 32])],
inner_commitments: &[InnerCommitments<P>],
) -> Result<(P, P), Error> {
if inner_commitments.is_empty() {
return Err(Error::EmptyContributions);
}
let mut seen: Vec<u32> = Vec::with_capacity(inner_commitments.len());
let mut d = P::identity();
let mut e = P::identity();
for c in inner_commitments {
if c.holder_index == 0 {
return Err(Error::InvalidIndex);
}
if &c.session_id != session_id {
return Err(Error::SessionMismatch);
}
if seen.contains(&c.holder_index) {
return Err(Error::DuplicateIndex(c.holder_index));
}
seen.push(c.holder_index);
let pre = precommits
.iter()
.find(|(k, _)| *k == c.holder_index)
.map(|(_, p)| p)
.ok_or(Error::PrecommitMismatch(c.holder_index))?;
if !verify_inner_precommit::<P>(pre, c) {
return Err(Error::PrecommitMismatch(c.holder_index));
}
d = d.add(&c.hiding);
e = e.add(&c.binding);
}
Ok((d, e))
}
pub fn verify_nested_commitment<C>(
package: &frost_core::SigningPackage<C>,
nested_index: u32,
session_id: &[u8; 32],
precommits: &[(u32, [u8; 32])],
inner_commitments: &[InnerCommitments<frost_core::Element<C>>],
) -> Result<(), Error>
where
C: crate::curve::NestedSuite,
{
let id: frost_core::Identifier<C> = u16::try_from(nested_index)
.map_err(|_| Error::InvalidIndex)?
.try_into()
.map_err(|_| Error::InvalidIndex)?;
let entry = package
.signing_commitment(&id)
.ok_or(Error::InvalidIndex)?;
let (d, e) = aggregate_inner_commitment_pair::<frost_core::Element<C>>(
session_id,
precommits,
inner_commitments,
)?;
if entry.hiding().value() != d || entry.binding().value() != e {
return Err(Error::UnexpectedCommitment);
}
Ok(())
}
pub struct NestedSigningRequest<'a, C: crate::curve::NestedSuite> {
pub package: &'a frost_core::SigningPackage<C>,
pub nested_index: u32,
pub session_id: [u8; 32],
pub inner_precommits: &'a [(u32, [u8; 32])],
pub inner_commitments: &'a [InnerCommitments<frost_core::Element<C>>],
pub active_indices: &'a [u32],
pub inner_threshold: u32,
}
#[derive(Clone)]
pub struct InnerSigningParams<S: CurveScalar> {
outer_binding: S,
outer_challenge: S,
outer_lambda: S,
}
impl<S: CurveScalar> InnerSigningParams<S> {
pub fn from_parts(outer_binding: S, outer_challenge: S, outer_lambda: S) -> Self {
Self {
outer_binding,
outer_challenge,
outer_lambda,
}
}
#[inline]
pub fn outer_binding(&self) -> &S {
&self.outer_binding
}
#[inline]
pub fn outer_challenge(&self) -> &S {
&self.outer_challenge
}
#[inline]
pub fn outer_lambda(&self) -> &S {
&self.outer_lambda
}
}
pub fn inner_sign<C>(
nonces: InnerNonces<frost_core::Scalar<C>>,
share: &SecretShare<frost_core::Scalar<C>>,
local_group_pubkey: &frost_core::VerifyingKey<C>,
approved_message: &[u8],
request: &NestedSigningRequest<'_, C>,
) -> Result<InnerSignatureShare<frost_core::Scalar<C>>, Error>
where
C: crate::curve::NestedSuite,
{
if request.package.message() != approved_message {
return Err(Error::MessageMismatch);
}
if (request.active_indices.len() as u64) < request.inner_threshold as u64 {
return Err(Error::InsufficientContributions {
got: request.active_indices.len(),
need: request.inner_threshold as usize,
});
}
for (i, &k) in request.active_indices.iter().enumerate() {
if k == 0 {
return Err(Error::InvalidIndex);
}
if request.active_indices[..i].contains(&k) {
return Err(Error::DuplicateIndex(k));
}
if !request
.inner_commitments
.iter()
.any(|c| c.holder_index == k)
{
return Err(Error::UnknownQuorumMember(k));
}
}
if nonces.session_id != request.session_id {
return Err(Error::SessionMismatch);
}
let mine = request
.inner_commitments
.iter()
.find(|c| c.holder_index == nonces.holder_index)
.ok_or(Error::UnexpectedCommitment)?;
if mine.session_id != request.session_id
|| mine.hiding != <frost_core::Element<C> as CurvePoint>::generator().mul_scalar(&nonces.hiding)
|| mine.binding != <frost_core::Element<C> as CurvePoint>::generator().mul_scalar(&nonces.binding)
{
return Err(Error::UnexpectedCommitment);
}
verify_nested_commitment::<C>(
request.package,
request.nested_index,
&request.session_id,
request.inner_precommits,
request.inner_commitments,
)?;
let params = crate::zf::inner_params_from_zf::<C>(
request.package,
local_group_pubkey,
request.nested_index,
)?;
let lagrange = compute_lagrange_coefficients::<frost_core::Scalar<C>>(request.active_indices)?;
let my_pos = request
.active_indices
.iter()
.position(|&i| i == share.index)
.ok_or(Error::InvalidIndex)?;
let mu_k = &lagrange[my_pos];
let rho_e = params.outer_binding.mul(&nonces.binding);
let weight = params.outer_lambda.mul(¶ms.outer_challenge).mul(mu_k);
let response = nonces.hiding.add(&rho_e).add(&weight.mul(share.scalar()));
Ok(InnerSignatureShare {
holder_index: nonces.holder_index,
response,
})
}
pub trait SpentSessions {
fn spend(&mut self, session_id: &[u8; 32], holder_index: u32) -> Result<(), Error>;
fn is_spent(&self, session_id: &[u8; 32], holder_index: u32) -> bool;
}
impl<T: SpentSessions + ?Sized> SpentSessions for &mut T {
fn spend(&mut self, session_id: &[u8; 32], holder_index: u32) -> Result<(), Error> {
(**self).spend(session_id, holder_index)
}
fn is_spent(&self, session_id: &[u8; 32], holder_index: u32) -> bool {
(**self).is_spent(session_id, holder_index)
}
}
#[derive(Clone, Debug, Default)]
pub struct MemorySpentSessions {
spent: alloc::collections::BTreeSet<([u8; 32], u32)>,
}
impl MemorySpentSessions {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.spent.len()
}
pub fn is_empty(&self) -> bool {
self.spent.is_empty()
}
}
impl SpentSessions for MemorySpentSessions {
fn spend(&mut self, session_id: &[u8; 32], holder_index: u32) -> Result<(), Error> {
if !self.spent.insert((*session_id, holder_index)) {
return Err(Error::SessionSpent);
}
Ok(())
}
fn is_spent(&self, session_id: &[u8; 32], holder_index: u32) -> bool {
self.spent.contains(&(*session_id, holder_index))
}
}
pub fn verify_inner_share<P: CurvePoint>(
sig: &InnerSignatureShare<P::Scalar>,
commitment: &InnerCommitments<P>,
public_share: &P,
params: &InnerSigningParams<P::Scalar>,
mu_k: &P::Scalar,
) -> bool {
let lhs = P::generator().mul_scalar(&sig.response);
let weight = params
.outer_lambda
.mul(¶ms.outer_challenge)
.mul(mu_k);
let rhs = commitment
.hiding
.add(&commitment.binding.mul_scalar(¶ms.outer_binding))
.add(&public_share.mul_scalar(&weight));
lhs == rhs
}
pub fn aggregate_inner_shares_verified<P: CurvePoint>(
sigs: &[InnerSignatureShare<P::Scalar>],
commitments: &[InnerCommitments<P>],
public_shares: &[(u32, P)],
params: &InnerSigningParams<P::Scalar>,
active_indices: &[u32],
) -> Result<P::Scalar, Vec<u32>> {
let lagrange = match compute_lagrange_coefficients::<P::Scalar>(active_indices) {
Ok(l) => l,
Err(_) => return Err(active_indices.to_vec()),
};
let mut bad = Vec::new();
let mut seen: Vec<u32> = Vec::with_capacity(sigs.len());
for sig in sigs {
if (!active_indices.contains(&sig.holder_index) || seen.contains(&sig.holder_index))
&& !bad.contains(&sig.holder_index) {
bad.push(sig.holder_index);
}
seen.push(sig.holder_index);
}
for &k in active_indices {
if !seen.contains(&k) && !bad.contains(&k) {
bad.push(k);
}
}
let mut z = P::Scalar::zero();
for sig in sigs {
let k = sig.holder_index;
let pos = active_indices.iter().position(|&i| i == k);
let commitment = commitments.iter().find(|c| c.holder_index == k);
let public = public_shares.iter().find(|(i, _)| *i == k).map(|(_, p)| p);
match (pos, commitment, public) {
(Some(pos), Some(commitment), Some(public)) => {
if verify_inner_share::<P>(sig, commitment, public, params, &lagrange[pos]) {
z = z.add(&sig.response);
} else if !bad.contains(&k) {
bad.push(k);
}
}
_ => {
if !bad.contains(&k) {
bad.push(k);
}
}
}
}
if bad.is_empty() {
Ok(z)
} else {
bad.sort_unstable();
Err(bad)
}
}