use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use sha2::{Digest, Sha512};
use crate::curve::{CurvePoint, CurveScalar};
use crate::error::Error;
use crate::lagrange::compute_lagrange_coefficients;
use crate::SecretShare;
pub struct Nonces<S: CurveScalar> {
hiding: S,
binding: S,
}
impl<S: CurveScalar> Nonces<S> {
#[cfg(any(test, feature = "zf"))]
pub fn from_scalars(hiding: S, binding: S) -> Self {
Self { hiding, binding }
}
pub fn compute_response(
self,
rho: &S,
lambda: &S,
challenge: &S,
secret: &S,
) -> S {
let rho_e = rho.mul(&self.binding);
let lcs = lambda.mul(&challenge.mul(secret));
self.hiding.add(&rho_e).add(&lcs)
}
}
impl<S: CurveScalar> Drop for Nonces<S> {
fn drop(&mut self) {
self.hiding.zeroize();
self.binding.zeroize();
}
}
impl<S: CurveScalar> core::fmt::Debug for Nonces<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("Nonces([REDACTED])")
}
}
#[derive(Clone, Debug)]
pub struct SigningCommitments<P: CurvePoint> {
pub index: u32,
pub hiding: P,
pub binding: P,
}
impl<P: CurvePoint> SigningCommitments<P> {
#[inline]
pub fn byte_size() -> usize {
4 + 2 * P::COMPRESSED_SIZE
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(Self::byte_size());
buf.extend_from_slice(&self.index.to_le_bytes());
buf.extend_from_slice(self.hiding.compress().as_ref());
buf.extend_from_slice(self.binding.compress().as_ref());
buf
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() != Self::byte_size() {
return Err(Error::InvalidCommitment);
}
let index = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
if index == 0 {
return Err(Error::InvalidIndex);
}
let n = P::COMPRESSED_SIZE;
let hiding = P::decompress(&bytes[4..4 + n]).ok_or(Error::InvalidCommitment)?;
let binding =
P::decompress(&bytes[4 + n..4 + 2 * n]).ok_or(Error::InvalidCommitment)?;
Ok(Self {
index,
hiding,
binding,
})
}
}
pub struct SigningPackage<P: CurvePoint> {
message: Vec<u8>,
commitments: BTreeMap<u32, SigningCommitments<P>>,
encoded_commitments: Vec<u8>,
}
impl<P: CurvePoint> SigningPackage<P> {
pub fn new(
message: Vec<u8>,
commitments: Vec<SigningCommitments<P>>,
) -> Result<Self, Error> {
let mut map = BTreeMap::new();
for c in commitments {
if c.index == 0 {
return Err(Error::InvalidIndex);
}
if map.contains_key(&c.index) {
return Err(Error::DuplicateIndex(c.index));
}
map.insert(c.index, c);
}
if map.is_empty() {
return Err(Error::EmptyContributions);
}
let encoded = encode_commitments(&map);
Ok(Self {
message,
commitments: map,
encoded_commitments: encoded,
})
}
#[inline]
pub fn message(&self) -> &[u8] {
&self.message
}
#[inline]
pub fn num_signers(&self) -> usize {
self.commitments.len()
}
pub fn signer_indices(&self) -> Vec<u32> {
self.commitments.keys().copied().collect()
}
pub fn get_commitments(&self, index: u32) -> Option<&SigningCommitments<P>> {
self.commitments.get(&index)
}
pub fn binding_factor(&self, index: u32, group_pubkey: &P) -> P::Scalar {
compute_binding_factor::<P>(
index,
group_pubkey,
&self.message,
&self.encoded_commitments,
)
}
pub fn group_commitment(&self, group_pubkey: &P) -> P {
let mut r = P::identity();
for c in self.commitments.values() {
let rho = self.binding_factor(c.index, group_pubkey);
let bound = c.binding.mul_scalar(&rho);
r = r.add(&c.hiding);
r = r.add(&bound);
}
r
}
pub fn challenge(&self, group_commitment: &P, group_pubkey: &P) -> P::Scalar {
compute_challenge::<P>(group_commitment, group_pubkey, &self.message)
}
}
pub struct SignatureShare<S: CurveScalar> {
pub index: u32,
pub response: S,
}
impl<S: CurveScalar> SignatureShare<S> {
pub fn to_bytes(&self) -> [u8; 36] {
let mut buf = [0u8; 36];
buf[0..4].copy_from_slice(&self.index.to_le_bytes());
buf[4..36].copy_from_slice(&self.response.to_bytes());
buf
}
pub fn from_bytes(bytes: &[u8; 36]) -> Result<Self, Error> {
let index = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
if index == 0 {
return Err(Error::InvalidIndex);
}
let resp_bytes: [u8; 32] = bytes[4..36].try_into().unwrap();
let response =
S::from_canonical_bytes(&resp_bytes).ok_or(Error::InvalidResponse)?;
Ok(Self { index, response })
}
}
impl<S: CurveScalar> core::fmt::Debug for SignatureShare<S> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SignatureShare")
.field("index", &self.index)
.field("response", &"[REDACTED]")
.finish()
}
}
#[derive(Clone, Debug)]
pub struct Signature<P: CurvePoint> {
pub r: P,
pub z: P::Scalar,
}
impl<P: CurvePoint> Signature<P> {
#[inline]
pub fn byte_size() -> usize {
P::COMPRESSED_SIZE + 32
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(Self::byte_size());
buf.extend_from_slice(self.r.compress().as_ref());
buf.extend_from_slice(&self.z.to_bytes());
buf
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() != Self::byte_size() {
return Err(Error::InvalidCommitment);
}
let n = P::COMPRESSED_SIZE;
let r = P::decompress(&bytes[0..n]).ok_or(Error::InvalidCommitment)?;
let z_bytes: [u8; 32] = bytes[n..n + 32].try_into().unwrap();
let z = P::Scalar::from_canonical_bytes(&z_bytes)
.ok_or(Error::InvalidResponse)?;
Ok(Self { r, z })
}
}
fn encode_commitments<P: CurvePoint>(
commitments: &BTreeMap<u32, SigningCommitments<P>>,
) -> Vec<u8> {
let mut buf = Vec::with_capacity(commitments.len() * (4 + 2 * P::COMPRESSED_SIZE));
for c in commitments.values() {
buf.extend_from_slice(&c.index.to_le_bytes());
buf.extend_from_slice(c.hiding.compress().as_ref());
buf.extend_from_slice(c.binding.compress().as_ref());
}
buf
}
fn compute_binding_factor<P: CurvePoint>(
index: u32,
group_pubkey: &P,
message: &[u8],
encoded_commitments: &[u8],
) -> P::Scalar {
let mut h = Sha512::new();
h.update(b"frost-binding-v2");
h.update(group_pubkey.compress());
h.update((message.len() as u64).to_le_bytes());
h.update(message);
h.update((encoded_commitments.len() as u64).to_le_bytes());
h.update(encoded_commitments);
h.update(index.to_le_bytes());
let hash: [u8; 64] = h.finalize().into();
<P::Scalar as CurveScalar>::from_bytes_wide(&hash)
}
fn compute_challenge<P: CurvePoint>(
group_commitment: &P,
group_pubkey: &P,
message: &[u8],
) -> P::Scalar {
let mut h = Sha512::new();
h.update(b"frost-challenge-v1");
h.update(group_commitment.compress());
h.update(group_pubkey.compress());
h.update(message);
let hash: [u8; 64] = h.finalize().into();
P::Scalar::from_bytes_wide(&hash)
}
pub fn commit<P: CurvePoint, R: rand_core::RngCore + rand_core::CryptoRng>(
index: u32,
rng: &mut R,
) -> Result<(Nonces<P::Scalar>, SigningCommitments<P>), Error> {
if index == 0 {
return Err(Error::InvalidIndex);
}
let hiding = P::Scalar::random(rng);
let binding = P::Scalar::random(rng);
let commitments = SigningCommitments {
index,
hiding: P::generator().mul_scalar(&hiding),
binding: P::generator().mul_scalar(&binding),
};
Ok((Nonces { hiding, binding }, commitments))
}
pub fn sign_with_context<P: CurvePoint>(
ctx: &crate::SigningContext<'_>,
package: &SigningPackage<P>,
nonces: Nonces<P::Scalar>,
share: &SecretShare<P::Scalar>,
group_pubkey: &P,
) -> Result<SignatureShare<P::Scalar>, Error> {
if package.message() != ctx.encode().as_slice() {
return Err(Error::MessageMismatch);
}
sign(package, nonces, share, group_pubkey)
}
pub fn sign<P: CurvePoint>(
package: &SigningPackage<P>,
nonces: Nonces<P::Scalar>,
share: &SecretShare<P::Scalar>,
group_pubkey: &P,
) -> Result<SignatureShare<P::Scalar>, Error> {
let mine = package
.get_commitments(share.index)
.ok_or(Error::InvalidIndex)?;
if mine.hiding != P::generator().mul_scalar(&nonces.hiding)
|| mine.binding != P::generator().mul_scalar(&nonces.binding)
{
return Err(Error::UnexpectedCommitment);
}
let rho = package.binding_factor(share.index, group_pubkey);
let group_commitment = package.group_commitment(group_pubkey);
let challenge = package.challenge(&group_commitment, group_pubkey);
let indices = package.signer_indices();
let lagrange = compute_lagrange_coefficients::<P::Scalar>(&indices)?;
let my_pos = indices
.iter()
.position(|&i| i == share.index)
.ok_or(Error::InvalidIndex)?;
let lambda = &lagrange[my_pos];
let response = nonces
.hiding
.add(&rho.mul(&nonces.binding))
.add(&lambda.mul(&challenge).mul(share.scalar()));
Ok(SignatureShare {
index: share.index,
response,
})
}
pub fn aggregate<P: CurvePoint>(
package: &SigningPackage<P>,
shares: &[SignatureShare<P::Scalar>],
group_pubkey: &P,
verifier_shares: Option<&BTreeMap<u32, P>>,
) -> Result<Signature<P>, Error> {
if shares.len() < package.num_signers() {
return Err(Error::InsufficientContributions {
got: shares.len(),
need: package.num_signers(),
});
}
let group_commitment = package.group_commitment(group_pubkey);
let challenge = package.challenge(&group_commitment, group_pubkey);
if let Some(vshares) = verifier_shares {
let indices = package.signer_indices();
let lagrange = compute_lagrange_coefficients::<P::Scalar>(&indices)?;
for share in shares {
let pos = indices
.iter()
.position(|&i| i == share.index)
.ok_or(Error::InvalidIndex)?;
let yi = vshares
.get(&share.index)
.ok_or(Error::InvalidIndex)?;
let rho = package.binding_factor(share.index, group_pubkey);
let comm = package
.get_commitments(share.index)
.ok_or(Error::InvalidIndex)?;
let lhs = P::generator().mul_scalar(&share.response);
let rhs = comm
.hiding
.add(&comm.binding.mul_scalar(&rho))
.add(&yi.mul_scalar(&lagrange[pos].mul(&challenge)));
if lhs != rhs {
return Err(Error::InvalidResponse);
}
}
}
let mut z = P::Scalar::zero();
for share in shares {
z = z.add(&share.response);
}
Ok(Signature {
r: group_commitment,
z,
})
}
pub fn verify_signature<P: CurvePoint>(
group_pubkey: &P,
message: &[u8],
signature: &Signature<P>,
) -> bool {
let challenge = compute_challenge::<P>(&signature.r, group_pubkey, message);
let lhs = P::generator().mul_scalar(&signature.z);
let rhs = signature.r.add(&group_pubkey.mul_scalar(&challenge));
lhs == rhs
}
#[cfg(all(test, feature = "ristretto255"))]
mod tests {
use super::*;
use crate::SecretShare;
use curve25519_dalek::{ristretto::RistrettoPoint, scalar::Scalar};
use rand::rngs::OsRng;
fn shamir_split(secret: &Scalar, n: u32, t: u32) -> Vec<SecretShare<Scalar>> {
let mut rng = OsRng;
let mut coeffs = vec![*secret];
for _ in 1..t {
coeffs.push(Scalar::random(&mut rng));
}
(1..=n)
.map(|i| {
let x = Scalar::from(i);
let mut y = Scalar::ZERO;
let mut x_pow = Scalar::ONE;
for coeff in &coeffs {
y += coeff * x_pow;
x_pow *= x;
}
SecretShare::new(i, y).expect("index is 1-indexed by construction")
})
.collect()
}
fn public_share(share: &SecretShare<Scalar>) -> RistrettoPoint {
RistrettoPoint::generator().mul_scalar(share.scalar())
}
#[test]
fn test_frost_basic() {
let mut rng = OsRng;
let secret = Scalar::random(&mut rng);
let group_pubkey: RistrettoPoint =
RistrettoPoint::generator().mul_scalar(&secret);
let n = 5u32;
let t = 3u32;
let shares = shamir_split(&secret, n, t);
let message = b"the signed zcash transaction goes here";
let mut all_nonces = Vec::new();
let mut all_commitments = Vec::new();
for share in &shares[0..t as usize] {
let (nonces, commitments) = commit::<RistrettoPoint, _>(share.index, &mut rng).expect("index is 1-indexed by construction");
all_nonces.push(nonces);
all_commitments.push(commitments);
}
let package =
SigningPackage::new(message.to_vec(), all_commitments).unwrap();
let mut sig_shares = Vec::new();
for (share, nonces) in shares[0..t as usize]
.iter()
.zip(all_nonces)
{
let sig_share =
sign::<RistrettoPoint>(&package, nonces, share, &group_pubkey)
.unwrap();
sig_shares.push(sig_share);
}
let signature = aggregate::<RistrettoPoint>(
&package,
&sig_shares,
&group_pubkey,
None,
)
.unwrap();
assert!(
verify_signature(&group_pubkey, message, &signature),
"FROST signature should verify"
);
}
#[test]
fn test_frost_with_share_verification() {
let mut rng = OsRng;
let secret = Scalar::random(&mut rng);
let group_pubkey: RistrettoPoint =
RistrettoPoint::generator().mul_scalar(&secret);
let n = 7u32;
let t = 4u32;
let shares = shamir_split(&secret, n, t);
let message = b"withdrawal tx bytes";
let mut vshares = BTreeMap::new();
for s in &shares {
vshares.insert(s.index, public_share(s));
}
let active: Vec<&SecretShare<Scalar>> =
vec![&shares[0], &shares[2], &shares[4], &shares[6]];
let mut nonces_vec = Vec::new();
let mut commitments_vec = Vec::new();
for s in &active {
let (n, c) = commit::<RistrettoPoint, _>(s.index, &mut rng).expect("index is 1-indexed by construction");
nonces_vec.push(n);
commitments_vec.push(c);
}
let package =
SigningPackage::new(message.to_vec(), commitments_vec).unwrap();
let mut sig_shares = Vec::new();
for (s, nonces) in active.iter().zip(nonces_vec) {
sig_shares.push(
sign::<RistrettoPoint>(&package, nonces, s, &group_pubkey)
.unwrap(),
);
}
let signature = aggregate::<RistrettoPoint>(
&package,
&sig_shares,
&group_pubkey,
Some(&vshares),
)
.unwrap();
assert!(verify_signature(&group_pubkey, message, &signature));
}
#[test]
fn test_frost_wrong_message_fails() {
let mut rng = OsRng;
let secret = Scalar::random(&mut rng);
let group_pubkey: RistrettoPoint =
RistrettoPoint::generator().mul_scalar(&secret);
let shares = shamir_split(&secret, 5, 3);
let message = b"correct message";
let mut nonces_vec = Vec::new();
let mut commitments_vec = Vec::new();
for s in &shares[0..3] {
let (n, c) = commit::<RistrettoPoint, _>(s.index, &mut rng).expect("index is 1-indexed by construction");
nonces_vec.push(n);
commitments_vec.push(c);
}
let package =
SigningPackage::new(message.to_vec(), commitments_vec).unwrap();
let mut sig_shares = Vec::new();
for (s, nonces) in shares[0..3].iter().zip(nonces_vec) {
sig_shares.push(
sign::<RistrettoPoint>(&package, nonces, s, &group_pubkey)
.unwrap(),
);
}
let signature =
aggregate::<RistrettoPoint>(&package, &sig_shares, &group_pubkey, None)
.unwrap();
assert!(verify_signature(&group_pubkey, message, &signature));
assert!(
!verify_signature(&group_pubkey, b"wrong message", &signature),
"wrong message should not verify"
);
}
#[test]
fn test_frost_wrong_pubkey_fails() {
let mut rng = OsRng;
let secret = Scalar::random(&mut rng);
let group_pubkey: RistrettoPoint =
RistrettoPoint::generator().mul_scalar(&secret);
let shares = shamir_split(&secret, 5, 3);
let message = b"test";
let mut nonces_vec = Vec::new();
let mut commitments_vec = Vec::new();
for s in &shares[0..3] {
let (n, c) = commit::<RistrettoPoint, _>(s.index, &mut rng).expect("index is 1-indexed by construction");
nonces_vec.push(n);
commitments_vec.push(c);
}
let package =
SigningPackage::new(message.to_vec(), commitments_vec).unwrap();
let mut sig_shares = Vec::new();
for (s, nonces) in shares[0..3].iter().zip(nonces_vec) {
sig_shares.push(
sign::<RistrettoPoint>(&package, nonces, s, &group_pubkey)
.unwrap(),
);
}
let signature =
aggregate::<RistrettoPoint>(&package, &sig_shares, &group_pubkey, None)
.unwrap();
let wrong_pubkey: RistrettoPoint =
RistrettoPoint::generator().mul_scalar(&Scalar::random(&mut rng));
assert!(!verify_signature(&wrong_pubkey, message, &signature));
}
#[test]
fn test_frost_signature_serialization() {
let mut rng = OsRng;
let secret = Scalar::random(&mut rng);
let group_pubkey: RistrettoPoint =
RistrettoPoint::generator().mul_scalar(&secret);
let shares = shamir_split(&secret, 3, 2);
let message = b"roundtrip test";
let mut nonces_vec = Vec::new();
let mut commitments_vec = Vec::new();
for s in &shares[0..2] {
let (n, c) = commit::<RistrettoPoint, _>(s.index, &mut rng).expect("index is 1-indexed by construction");
nonces_vec.push(n);
commitments_vec.push(c);
}
let package =
SigningPackage::new(message.to_vec(), commitments_vec).unwrap();
let mut sig_shares = Vec::new();
for (s, nonces) in shares[0..2].iter().zip(nonces_vec) {
sig_shares.push(
sign::<RistrettoPoint>(&package, nonces, s, &group_pubkey)
.unwrap(),
);
}
let signature =
aggregate::<RistrettoPoint>(&package, &sig_shares, &group_pubkey, None)
.unwrap();
let bytes = signature.to_bytes();
let recovered =
Signature::<RistrettoPoint>::from_bytes(&bytes).unwrap();
assert!(verify_signature(&group_pubkey, message, &recovered));
}
#[test]
fn test_frost_bad_share_detected() {
let mut rng = OsRng;
let secret = Scalar::random(&mut rng);
let group_pubkey: RistrettoPoint =
RistrettoPoint::generator().mul_scalar(&secret);
let shares = shamir_split(&secret, 5, 3);
let mut vshares = BTreeMap::new();
for s in &shares {
vshares.insert(s.index, public_share(s));
}
let message = b"detect misbehaver";
let mut nonces_vec = Vec::new();
let mut commitments_vec = Vec::new();
for s in &shares[0..3] {
let (n, c) = commit::<RistrettoPoint, _>(s.index, &mut rng).expect("index is 1-indexed by construction");
nonces_vec.push(n);
commitments_vec.push(c);
}
let package =
SigningPackage::new(message.to_vec(), commitments_vec).unwrap();
let mut sig_shares = Vec::new();
for (s, nonces) in shares[0..3].iter().zip(nonces_vec) {
sig_shares.push(
sign::<RistrettoPoint>(&package, nonces, s, &group_pubkey)
.unwrap(),
);
}
sig_shares[1] = SignatureShare {
index: shares[1].index,
response: Scalar::random(&mut rng),
};
let result = aggregate::<RistrettoPoint>(
&package,
&sig_shares,
&group_pubkey,
Some(&vshares),
);
assert!(
matches!(result, Err(Error::InvalidResponse)),
"tampered share should be detected"
);
}
#[test]
fn test_frost_duplicate_commitments_rejected() {
let mut rng = OsRng;
let (_, c1) = commit::<RistrettoPoint, _>(1, &mut rng).expect("index is 1-indexed by construction");
let (_, c2) = commit::<RistrettoPoint, _>(1, &mut rng).expect("index is 1-indexed by construction"); let result =
SigningPackage::<RistrettoPoint>::new(b"test".to_vec(), vec![c1, c2]);
assert!(matches!(result, Err(Error::DuplicateIndex(1))));
}
}
#[cfg(all(test, feature = "pallas"))]
mod pallas_tests {
use super::*;
use crate::SecretShare;
use alloc::collections::BTreeMap;
use pasta_curves::group::ff::Field;
use pasta_curves::pallas::{Point, Scalar};
use rand::rngs::OsRng;
fn shamir_split(secret: &Scalar, n: u32, t: u32) -> Vec<SecretShare<Scalar>> {
let mut rng = OsRng;
let mut coeffs = vec![*secret];
for _ in 1..t {
coeffs.push(<Scalar as crate::curve::CurveScalar>::random(&mut rng));
}
(1..=n)
.map(|i| {
let x = Scalar::from(i as u64);
let mut y = Scalar::ZERO;
let mut x_pow = Scalar::ONE;
for coeff in &coeffs {
y += coeff * x_pow;
x_pow *= x;
}
SecretShare::new(i, y).expect("index is 1-indexed by construction")
})
.collect()
}
#[test]
fn test_pallas_frost() {
let mut rng = OsRng;
let secret = <Scalar as crate::curve::CurveScalar>::random(&mut rng);
let group_pubkey: Point = Point::generator().mul_scalar(&secret);
let n = 5u32;
let t = 3u32;
let shares = shamir_split(&secret, n, t);
let message = b"pallas frost withdrawal";
let mut nonces_vec = Vec::new();
let mut commitments_vec = Vec::new();
for s in &shares[0..t as usize] {
let (nonces, commitments) = commit::<Point, _>(s.index, &mut rng).expect("index is 1-indexed by construction");
nonces_vec.push(nonces);
commitments_vec.push(commitments);
}
let package =
SigningPackage::new(message.to_vec(), commitments_vec).unwrap();
let mut sig_shares = Vec::new();
for (s, nonces) in shares[0..t as usize]
.iter()
.zip(nonces_vec)
{
sig_shares.push(
sign::<Point>(&package, nonces, s, &group_pubkey).unwrap(),
);
}
let signature =
aggregate::<Point>(&package, &sig_shares, &group_pubkey, None)
.unwrap();
assert!(verify_signature(&group_pubkey, message, &signature));
}
#[test]
fn test_pallas_frost_with_dkg() {
use crate::dkg;
let mut rng = OsRng;
let n = 5u32;
let t = 3u32;
let dealers: Vec<dkg::Dealer<Point>> =
(1..=n).map(|i| dkg::Dealer::new(i, t, &mut rng).expect("index is 1-indexed by construction")).collect();
let commitments: Vec<&crate::reshare::DealerCommitment<Point>> =
dealers.iter().map(|d| d.commitment()).collect();
let mut secret_shares = Vec::new();
let mut group_key = None;
let mut vshares = BTreeMap::new();
for j in 1..=n {
let mut agg: dkg::Aggregator<Point> = dkg::Aggregator::all_dealers(j, n).unwrap();
for dealer in &dealers {
let subshare = dealer.generate_subshare(j).expect("index is 1-indexed by construction");
agg.add_subshare(subshare, commitments[(dealer.index() - 1) as usize])
.unwrap();
}
let share_scalar = agg.finalize().unwrap();
if group_key.is_none() {
group_key = Some(agg.derive_group_key().unwrap());
}
let ss = SecretShare::new(j, share_scalar).expect("index is 1-indexed by construction");
vshares.insert(j, Point::generator().mul_scalar(ss.scalar()));
secret_shares.push(ss);
}
let group_key = group_key.unwrap();
let message = b"dkg + frost integration test";
let active = &secret_shares[0..t as usize];
let mut nonces_vec = Vec::new();
let mut commitments_vec = Vec::new();
for s in active {
let (n, c) = commit::<Point, _>(s.index, &mut rng).expect("index is 1-indexed by construction");
nonces_vec.push(n);
commitments_vec.push(c);
}
let package =
SigningPackage::new(message.to_vec(), commitments_vec).unwrap();
let mut sig_shares = Vec::new();
for (s, nonces) in active.iter().zip(nonces_vec) {
sig_shares.push(
sign::<Point>(&package, nonces, s, &group_key).unwrap(),
);
}
let signature = aggregate::<Point>(
&package,
&sig_shares,
&group_key,
Some(&vshares),
)
.unwrap();
assert!(
verify_signature(&group_key, message, &signature),
"DKG + FROST should produce valid signature"
);
}
}