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 crate::frost;
use curve25519_dalek::ristretto::RistrettoPoint;
use curve25519_dalek::scalar::Scalar;
use rand::rngs::OsRng;
type Point = RistrettoPoint;
const SESSION: [u8; 32] = [0xA5; 32];
#[test]
fn from_outer_matches_the_hand_rolled_derivation() {
let mut rng = OsRng;
let msg = b"outer context derivation";
let secret = <Scalar as CurveScalar>::random(&mut rng);
let group_pubkey = <Point as CurvePoint>::generator().mul_scalar(&secret);
let (_, commits_1) = frost::commit::<Point, _>(1, &mut rng).expect("index is 1-indexed by construction");
let (_, commits_2) = frost::commit::<Point, _>(2, &mut rng).expect("index is 1-indexed by construction");
let package =
frost::SigningPackage::new(msg.to_vec(), vec![commits_1, commits_2]).unwrap();
let rho_2 = package.binding_factor(2, &group_pubkey);
let r_outer = package.group_commitment(&group_pubkey);
let challenge = package.challenge(&r_outer, &group_pubkey);
let indices = package.signer_indices();
let outer_lagrange = compute_lagrange_coefficients::<Scalar>(&indices).unwrap();
let pos_2 = indices.iter().position(|&i| i == 2).unwrap();
let derived = InnerSigningParamsV2::from_outer::<Point>(&package, &group_pubkey, 2).unwrap();
assert_eq!(derived.outer_binding, rho_2);
assert_eq!(derived.outer_challenge, challenge);
assert_eq!(derived.outer_lambda, outer_lagrange[pos_2]);
let derived_1 =
InnerSigningParamsV2::from_outer::<Point>(&package, &group_pubkey, 1).unwrap();
assert_ne!(derived_1.outer_binding, derived.outer_binding);
assert_ne!(derived_1.outer_lambda, derived.outer_lambda);
assert_eq!(derived_1.outer_challenge, derived.outer_challenge);
assert!(matches!(
InnerSigningParamsV2::from_outer::<Point>(&package, &group_pubkey, 7),
Err(Error::InvalidIndex)
));
}
#[test]
fn nested_v2_equals_flat_frost() {
let mut rng = OsRng;
let msg = b"settlement: a=1000 b=0";
let secret = Scalar::random(&mut rng);
let a1 = Scalar::random(&mut rng);
let eval = |x: u32| {
let x = <Scalar as CurveScalar>::from_u32(x);
secret.add(&a1.mul(&x))
};
let sigma_1 = eval(1);
let sigma_2 = eval(2); let group_pubkey = <Point as CurvePoint>::generator().mul_scalar(&secret);
let share_1 = SecretShare::new(1, sigma_1).expect("index is 1-indexed by construction");
let share_2_flat = SecretShare::new(2, sigma_2).expect("index is 1-indexed by construction");
let inner_t = 3u32;
let inner_n = 5u32;
let (inner_pieces, dealer_commitment) =
split_evaluation_for_inner::<Point, _>(&sigma_2, inner_n, inner_t, &mut rng);
for (k, piece) in &inner_pieces {
assert!(
verify_split_piece::<Point>(&dealer_commitment, *k, piece),
"inner piece {} failed Feldman verification",
k
);
}
let quorum: Vec<u32> = vec![1, 2, 3];
let inner_shares: Vec<SecretShare<Scalar>> = quorum
.iter()
.map(|k| {
let (_, piece) = inner_pieces.iter().find(|(i, _)| i == k).unwrap();
SecretShare::new(*k, *piece).expect("index is 1-indexed by construction")
})
.collect();
{
let lag = compute_lagrange_coefficients::<Scalar>(&quorum).unwrap();
let mut recon = <Scalar as CurveScalar>::zero();
for (i, s) in inner_shares.iter().enumerate() {
recon = recon.add(&lag[i].mul(s.scalar()));
}
assert_eq!(recon, sigma_2, "inner quorum must reconstruct the outer share");
}
let mut inner_nonces = Vec::new();
let mut inner_commitments = Vec::new();
let mut precommits = Vec::new();
for &k in &quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
precommits.push(inner_precommit(&c));
inner_nonces.push(n);
inner_commitments.push(c);
}
for (pre, revealed) in precommits.iter().zip(inner_commitments.iter()) {
assert!(verify_inner_precommit::<Point>(pre, revealed));
}
{
let mut tampered = inner_commitments[0].clone();
tampered.hiding = tampered.hiding.add(&<Point as CurvePoint>::generator());
assert!(!verify_inner_precommit::<Point>(&precommits[0], &tampered));
}
let d_sum = inner_nonces
.iter()
.fold(<Scalar as CurveScalar>::zero(), |acc, n| acc.add(&n.hiding));
let e_sum = inner_nonces
.iter()
.fold(<Scalar as CurveScalar>::zero(), |acc, n| acc.add(&n.binding));
let inner_precommits: Vec<(u32, [u8; 32])> = inner_commitments
.iter()
.map(|c| (c.holder_index, inner_precommit::<Point>(c)))
.collect();
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &inner_precommits, &inner_commitments)
.unwrap();
assert_eq!(d_nested, <Point as CurvePoint>::generator().mul_scalar(&d_sum));
assert_eq!(e_nested, <Point as CurvePoint>::generator().mul_scalar(&e_sum));
let (nonces_1, commits_1) = frost::commit::<Point, _>(1, &mut rng).expect("index is 1-indexed by construction");
let commits_2 = frost::SigningCommitments {
index: 2,
hiding: d_nested,
binding: e_nested,
};
let package =
frost::SigningPackage::new(msg.to_vec(), vec![commits_1, commits_2.clone()]).unwrap();
let rho_2 = package.binding_factor(2, &group_pubkey);
let r_outer = package.group_commitment(&group_pubkey);
let challenge = package.challenge(&r_outer, &group_pubkey);
let indices = package.signer_indices();
let outer_lagrange = compute_lagrange_coefficients::<Scalar>(&indices).unwrap();
let pos_2 = indices.iter().position(|&i| i == 2).unwrap();
#[allow(deprecated)]
let params = InnerSigningParamsV2::from_coordinator_checked::<Point>(
&rho_2,
&challenge,
&outer_lagrange[pos_2],
&package,
&group_pubkey,
2,
)
.unwrap();
#[allow(deprecated)]
let mismatched = InnerSigningParamsV2::from_coordinator_checked::<Point>(
&rho_2,
&Scalar::random(&mut rng),
&outer_lagrange[pos_2],
&package,
&group_pubkey,
2,
);
assert!(matches!(mismatched, Err(Error::ChallengeMismatch)));
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &inner_precommits,
inner_commitments: &inner_commitments,
active_indices: &quorum,
inner_threshold: 3,
};
let inner_lagrange = compute_lagrange_coefficients::<Scalar>(&quorum).unwrap();
let public_shares: Vec<(u32, Point)> = inner_shares
.iter()
.map(|s| {
(
s.index,
<Point as CurvePoint>::generator().mul_scalar(s.scalar()),
)
})
.collect();
let mut inner_sigs = Vec::new();
for (n, share) in inner_nonces.into_iter().zip(inner_shares.iter()) {
let sig = inner_sign_v2::<Point>(n, share, &group_pubkey, msg, &request).unwrap();
let pos = quorum.iter().position(|&i| i == share.index).unwrap();
let commitment = inner_commitments
.iter()
.find(|c| c.holder_index == share.index)
.unwrap();
let public = &public_shares
.iter()
.find(|(i, _)| *i == share.index)
.unwrap()
.1;
assert!(
verify_inner_share::<Point>(&sig, commitment, public, ¶ms, &inner_lagrange[pos]),
"holder {} produced an unverifiable share",
share.index
);
inner_sigs.push(sig);
}
let z_nested = aggregate_inner_shares_verified::<Point>(
&inner_sigs,
&inner_commitments,
&public_shares,
¶ms,
&quorum,
)
.expect("all inner shares must verify");
let z_flat = d_sum
.add(&rho_2.mul(&e_sum))
.add(¶ms.outer_lambda.mul(&challenge).mul(&sigma_2));
assert_eq!(
z_nested, z_flat,
"nested share must equal the flat FROST share bit-for-bit"
);
let sig_1 = frost::sign::<Point>(&package, nonces_1, &share_1, &group_pubkey).unwrap();
let sig_2 = frost::SignatureShare {
index: 2,
response: z_nested,
};
let signature =
frost::aggregate::<Point>(&package, &[sig_1, sig_2], &group_pubkey, None).unwrap();
assert!(
frost::verify_signature::<Point>(&group_pubkey, msg, &signature),
"nested-produced signature must verify under the group key"
);
let _ = share_2_flat;
}
#[test]
fn v2_names_the_dishonest_inner_holder() {
let mut rng = OsRng;
let msg = b"m";
let sigma_2 = Scalar::random(&mut rng);
let quorum: Vec<u32> = vec![1, 2, 3];
let (inner_pieces, _) = split_evaluation_for_inner::<Point, _>(&sigma_2, 5, 3, &mut rng);
let inner_shares: Vec<SecretShare<Scalar>> = quorum
.iter()
.map(|k| {
let (_, piece) = inner_pieces.iter().find(|(i, _)| i == k).unwrap();
SecretShare::new(*k, *piece).expect("index is 1-indexed by construction")
})
.collect();
let mut inner_nonces = Vec::new();
let mut inner_commitments = Vec::new();
for &k in &quorum {
let (n, c) = inner_commit::<Point, _>(k, SESSION, &mut rng);
inner_nonces.push(n);
inner_commitments.push(c);
}
let inner_precommits: Vec<(u32, [u8; 32])> = inner_commitments
.iter()
.map(|c| (c.holder_index, inner_precommit::<Point>(c)))
.collect();
let (d_nested, e_nested) =
aggregate_inner_commitment_pair::<Point>(&SESSION, &inner_precommits, &inner_commitments)
.unwrap();
let group_pubkey = <Point as CurvePoint>::generator().mul_scalar(&sigma_2);
let (_, commits_1) = frost::commit::<Point, _>(1, &mut rng).expect("index is 1-indexed by construction");
let commits_2 = frost::SigningCommitments {
index: 2,
hiding: d_nested,
binding: e_nested,
};
let package =
frost::SigningPackage::new(msg.to_vec(), vec![commits_1, commits_2]).unwrap();
let r_outer = package.group_commitment(&group_pubkey);
let indices = package.signer_indices();
let outer_lagrange = compute_lagrange_coefficients::<Scalar>(&indices).unwrap();
let pos_2 = indices.iter().position(|&i| i == 2).unwrap();
let _ = (&r_outer, &outer_lagrange, pos_2);
let params = InnerSigningParamsV2::from_outer::<Point>(&package, &group_pubkey, 2).unwrap();
let request = NestedSigningRequest {
package: &package,
nested_index: 2,
session_id: SESSION,
inner_precommits: &inner_precommits,
inner_commitments: &inner_commitments,
active_indices: &quorum,
inner_threshold: 3,
};
let public_shares: Vec<(u32, Point)> = inner_shares
.iter()
.map(|s| {
(
s.index,
<Point as CurvePoint>::generator().mul_scalar(s.scalar()),
)
})
.collect();
let mut sigs = Vec::new();
for (n, share) in inner_nonces.into_iter().zip(inner_shares.iter()) {
sigs.push(inner_sign_v2::<Point>(n, share, &group_pubkey, msg, &request).unwrap());
}
sigs[1].response = sigs[1].response.add(&<Scalar as CurveScalar>::one());
let err = aggregate_inner_shares_verified::<Point>(
&sigs,
&inner_commitments,
&public_shares,
¶ms,
&quorum,
)
.expect_err("a tampered share must be rejected");
assert_eq!(err, vec![2], "the cheating holder must be identified");
}
#[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<P: CurvePoint>(
package: &crate::frost::SigningPackage<P>,
nested_index: u32,
session_id: &[u8; 32],
precommits: &[(u32, [u8; 32])],
inner_commitments: &[InnerCommitments<P>],
) -> Result<(), Error> {
let entry = package
.get_commitments(nested_index)
.ok_or(Error::InvalidIndex)?;
let (d, e) = aggregate_inner_commitment_pair::<P>(session_id, precommits, inner_commitments)?;
if entry.hiding != d || entry.binding != e {
return Err(Error::UnexpectedCommitment);
}
Ok(())
}
pub struct NestedSigningRequest<'a, P: CurvePoint> {
pub package: &'a crate::frost::SigningPackage<P>,
pub nested_index: u32,
pub session_id: [u8; 32],
pub inner_precommits: &'a [(u32, [u8; 32])],
pub inner_commitments: &'a [InnerCommitments<P>],
pub active_indices: &'a [u32],
pub inner_threshold: u32,
}
#[derive(Clone)]
pub struct InnerSigningParamsV2<S: CurveScalar> {
outer_binding: S,
outer_challenge: S,
outer_lambda: S,
}
impl<S: CurveScalar> InnerSigningParamsV2<S> {
#[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 from_outer<P: CurvePoint<Scalar = S>>(
package: &crate::frost::SigningPackage<P>,
group_pubkey: &P,
nested_index: u32,
) -> Result<Self, Error> {
let indices = package.signer_indices();
let pos = indices
.iter()
.position(|&i| i == nested_index)
.ok_or(Error::InvalidIndex)?;
let lagrange = compute_lagrange_coefficients::<S>(&indices)?;
let group_commitment = package.group_commitment(group_pubkey);
Ok(Self {
outer_binding: package.binding_factor(nested_index, group_pubkey),
outer_challenge: package.challenge(&group_commitment, group_pubkey),
outer_lambda: lagrange[pos].clone(),
})
}
#[deprecated(
since = "0.5.0",
note = "validates self-consistency, not the provenance of `package` or `group_pubkey`: \
a substituted group key passes. Prefer `from_outer`, with `group_pubkey` taken \
from the signer's own key package."
)]
pub fn from_coordinator_checked<P: CurvePoint<Scalar = S>>(
supplied_binding: &S,
supplied_challenge: &S,
supplied_lambda: &S,
package: &crate::frost::SigningPackage<P>,
group_pubkey: &P,
nested_index: u32,
) -> Result<Self, Error> {
let local = Self::from_outer::<P>(package, group_pubkey, nested_index)?;
if &local.outer_binding != supplied_binding
|| &local.outer_challenge != supplied_challenge
|| &local.outer_lambda != supplied_lambda
{
return Err(Error::ChallengeMismatch);
}
Ok(local)
}
}
pub fn inner_sign_v2<P: CurvePoint>(
nonces: InnerNonces<P::Scalar>,
share: &SecretShare<P::Scalar>,
local_group_pubkey: &P,
approved_message: &[u8],
request: &NestedSigningRequest<'_, P>,
) -> Result<InnerSignatureShare<P::Scalar>, Error> {
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 != P::generator().mul_scalar(&nonces.hiding)
|| mine.binding != P::generator().mul_scalar(&nonces.binding)
{
return Err(Error::UnexpectedCommitment);
}
verify_nested_commitment::<P>(
request.package,
request.nested_index,
&request.session_id,
request.inner_precommits,
request.inner_commitments,
)?;
let params = InnerSigningParamsV2::from_outer::<P>(
request.package,
local_group_pubkey,
request.nested_index,
)?;
let lagrange = compute_lagrange_coefficients::<P::Scalar>(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;
}
#[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 inner_sign_v2_spending<P: CurvePoint, S: SpentSessions + ?Sized>(
store: &mut S,
nonces: InnerNonces<P::Scalar>,
share: &SecretShare<P::Scalar>,
local_group_pubkey: &P,
approved_message: &[u8],
request: &NestedSigningRequest<'_, P>,
) -> Result<InnerSignatureShare<P::Scalar>, Error> {
store.spend(&nonces.session_id, nonces.holder_index)?;
inner_sign_v2::<P>(
nonces,
share,
local_group_pubkey,
approved_message,
request,
)
}
pub fn inner_sign_v2_with_context<P: CurvePoint>(
nonces: InnerNonces<P::Scalar>,
share: &SecretShare<P::Scalar>,
local_group_pubkey: &P,
ctx: &crate::SigningContext<'_>,
request: &NestedSigningRequest<'_, P>,
) -> Result<InnerSignatureShare<P::Scalar>, Error> {
inner_sign_v2::<P>(nonces, share, local_group_pubkey, &ctx.encode(), request)
}
pub fn verify_inner_share<P: CurvePoint>(
sig: &InnerSignatureShare<P::Scalar>,
commitment: &InnerCommitments<P>,
public_share: &P,
params: &InnerSigningParamsV2<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: &InnerSigningParamsV2<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)
}
}