use crate::{ConclaveError, ConclaveResult};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
pub struct FrostManager;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FrostKeyPackage {
pub min_signers: u32,
pub total_signers: u32,
pub identifier: String,
pub group_public_key: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FrostSignatureShare {
pub signer_id: u32,
pub share: Vec<u8>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FrostDkgRound1Package {
pub signer_id: u32,
pub commitments: Vec<String>, pub proof_of_knowledge: String, }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FrostDkgRound2Package {
pub signer_id: u32,
pub encrypted_shares: Vec<FrostEncryptedShare>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct FrostEncryptedShare {
pub receiver_id: u32,
pub encrypted_share: String, }
impl FrostManager {
pub fn generate_key_package(
min_signers: u32,
total_signers: u32,
identifier: &str,
) -> ConclaveResult<FrostKeyPackage> {
if min_signers == 0 || min_signers > total_signers || total_signers > 100 {
return Err(ConclaveError::InvalidPayload);
}
let mut hasher = Sha256::new();
hasher.update(identifier.as_bytes());
hasher.update(min_signers.to_be_bytes());
hasher.update(total_signers.to_be_bytes());
let group_pk = hasher.finalize();
Ok(FrostKeyPackage {
min_signers,
total_signers,
identifier: identifier.to_string(),
group_public_key: hex::encode(group_pk),
})
}
pub fn generate_dkg_round1(
&self,
signer_id: u32,
threshold: u32,
) -> ConclaveResult<FrostDkgRound1Package> {
if signer_id == 0 || threshold == 0 || threshold > 100 {
return Err(ConclaveError::InvalidPayload);
}
let mut commitments = Vec::with_capacity(threshold as usize);
for i in 0..threshold {
let mut hasher = Sha256::new();
hasher.update(signer_id.to_be_bytes());
hasher.update(i.to_be_bytes());
hasher.update(b"FROST_COEFFICIENT_COMMITMENT");
commitments.push(hex::encode(hasher.finalize()));
}
let mut pok_hasher = Sha256::new();
pok_hasher.update(signer_id.to_be_bytes());
pok_hasher.update(commitments[0].as_bytes());
pok_hasher.update(b"FROST_POK_v1");
let pok = pok_hasher.finalize();
Ok(FrostDkgRound1Package {
signer_id,
commitments,
proof_of_knowledge: hex::encode(pok),
})
}
pub fn verify_dkg_round1(&self, package: &FrostDkgRound1Package) -> ConclaveResult<bool> {
if package.signer_id == 0 || package.commitments.is_empty() {
return Err(ConclaveError::InvalidPayload);
}
let mut pok_hasher = Sha256::new();
pok_hasher.update(package.signer_id.to_be_bytes());
pok_hasher.update(package.commitments[0].as_bytes());
pok_hasher.update(b"FROST_POK_v1");
let expected_pok = hex::encode(pok_hasher.finalize());
Ok(package.proof_of_knowledge == expected_pok)
}
pub fn generate_dkg_round2(
&self,
signer_id: u32,
other_signer_ids: Vec<u32>,
round1_package: &FrostDkgRound1Package,
) -> ConclaveResult<FrostDkgRound2Package> {
if signer_id == 0 || other_signer_ids.is_empty() || round1_package.signer_id != signer_id {
return Err(ConclaveError::InvalidPayload);
}
if round1_package.commitments.is_empty() {
return Err(ConclaveError::InvalidPayload);
}
let mut encrypted_shares = Vec::with_capacity(other_signer_ids.len());
for receiver_id in other_signer_ids {
if receiver_id == signer_id || receiver_id == 0 {
continue;
}
let mut eval_hasher = Sha256::new();
eval_hasher.update(signer_id.to_be_bytes());
eval_hasher.update(receiver_id.to_be_bytes());
eval_hasher.update(b"FROST_DKG_ROUND2_SECRET_EVALUATION");
let secret_evaluation = eval_hasher.finalize();
let mut binding_hasher = Sha256::new();
binding_hasher.update(secret_evaluation);
for (k, commitment) in round1_package.commitments.iter().enumerate() {
binding_hasher.update(commitment.as_bytes());
binding_hasher.update(receiver_id.to_be_bytes());
binding_hasher.update((k as u32).to_be_bytes());
}
encrypted_shares.push(FrostEncryptedShare {
receiver_id,
encrypted_share: hex::encode(binding_hasher.finalize()),
});
}
Ok(FrostDkgRound2Package {
signer_id,
encrypted_shares,
})
}
pub fn verify_received_share(
&self,
receiver_id: u32,
round1_package: &FrostDkgRound1Package,
round2_package: &FrostDkgRound2Package,
) -> ConclaveResult<bool> {
if receiver_id == 0 {
return Err(ConclaveError::InvalidPayload);
}
if round1_package.signer_id != round2_package.signer_id {
return Err(ConclaveError::InvalidPayload);
}
if receiver_id == round1_package.signer_id {
return Err(ConclaveError::InvalidPayload);
}
if !self.verify_dkg_round1(round1_package)? {
return Ok(false);
}
let share = round2_package
.encrypted_shares
.iter()
.find(|s| s.receiver_id == receiver_id);
let Some(share) = share else {
return Err(ConclaveError::InvalidPayload);
};
if share.encrypted_share.len() != 64 {
return Ok(false);
}
let mut eval_hasher = Sha256::new();
eval_hasher.update(round2_package.signer_id.to_be_bytes());
eval_hasher.update(receiver_id.to_be_bytes());
eval_hasher.update(b"FROST_DKG_ROUND2_SECRET_EVALUATION");
let expected_secret_evaluation = eval_hasher.finalize();
let mut binding_hasher = Sha256::new();
binding_hasher.update(expected_secret_evaluation);
for (k, commitment) in round1_package.commitments.iter().enumerate() {
binding_hasher.update(commitment.as_bytes());
binding_hasher.update(receiver_id.to_be_bytes());
binding_hasher.update((k as u32).to_be_bytes());
}
let expected_share = hex::encode(binding_hasher.finalize());
Ok(share.encrypted_share == expected_share)
}
pub fn aggregate_signatures(
&self,
package: &FrostKeyPackage,
shares: Vec<FrostSignatureShare>,
message: &[u8],
) -> ConclaveResult<String> {
if shares.len() < package.min_signers as usize {
return Err(ConclaveError::CryptoError(
"Insufficient signature shares for threshold".to_string(),
));
}
let mut ids: Vec<u32> = shares.iter().map(|s| s.signer_id).collect();
ids.sort();
ids.dedup();
if ids.len() < package.min_signers as usize {
return Err(ConclaveError::CryptoError(
"Duplicate or insufficient unique shares".to_string(),
));
}
let mut aggregate_s = [0u8; 32];
for share in &shares {
if share.share.len() != 32 {
return Err(ConclaveError::InvalidPayload);
}
for (s_byte, share_byte) in aggregate_s.iter_mut().zip(share.share.iter()) {
*s_byte ^= *share_byte;
}
}
let mut final_hasher = Sha256::new();
final_hasher.update(aggregate_s);
final_hasher.update(message);
final_hasher.update(package.group_public_key.as_bytes());
let final_sig = final_hasher.finalize();
let mut result = vec![0u8; 32]; result.extend_from_slice(&final_sig);
Ok(hex::encode(result))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frost_key_generation_bounds() {
let result = FrostManager::generate_key_package(3, 2, "test");
assert!(result.is_err());
let ok = FrostManager::generate_key_package(2, 3, "test").unwrap();
assert_eq!(ok.min_signers, 2);
assert!(!ok.group_public_key.is_empty());
}
#[test]
fn test_frost_dkg_round1_generation() {
let mgr = FrostManager;
let package = mgr.generate_dkg_round1(1, 2).unwrap();
assert_eq!(package.signer_id, 1);
assert_eq!(package.commitments.len(), 2);
assert!(!package.proof_of_knowledge.is_empty());
assert!(mgr.verify_dkg_round1(&package).unwrap());
}
#[test]
fn test_frost_dkg_round2_generation() {
let mgr = FrostManager;
let round1 = mgr.generate_dkg_round1(1, 2).unwrap();
let package = mgr.generate_dkg_round2(1, vec![1, 2, 3], &round1).unwrap();
assert_eq!(package.signer_id, 1);
assert_eq!(package.encrypted_shares.len(), 2);
}
#[test]
fn test_frost_verify_received_share() {
let mgr = FrostManager;
let round1 = mgr.generate_dkg_round1(1, 2).unwrap();
let round2 = mgr.generate_dkg_round2(1, vec![1, 2, 3], &round1).unwrap();
let result = mgr.verify_received_share(2, &round1, &round2).unwrap();
assert!(result);
let fail = mgr.verify_received_share(4, &round1, &round2);
assert!(fail.is_err());
}
#[test]
fn test_frost_signature_aggregation_threshold() {
let mgr = FrostManager;
let package = FrostKeyPackage {
min_signers: 2,
total_signers: 3,
identifier: "vault-1".to_string(),
group_public_key: "group_pk".to_string(),
};
let shares = vec![FrostSignatureShare {
signer_id: 1,
share: vec![1; 32],
}];
let result = mgr.aggregate_signatures(&package, shares, b"hello");
assert!(result.is_err());
}
#[test]
fn test_frost_signature_aggregation_duplicates() {
let mgr = FrostManager;
let package = FrostKeyPackage {
min_signers: 2,
total_signers: 3,
identifier: "vault-1".to_string(),
group_public_key: "group_pk".to_string(),
};
let shares = vec![
FrostSignatureShare {
signer_id: 1,
share: vec![1; 32],
},
FrostSignatureShare {
signer_id: 1,
share: vec![1; 32],
},
];
let result = mgr.aggregate_signatures(&package, shares, b"hello");
assert!(result.is_err());
}
#[test]
fn test_frost_signature_aggregation_flow() {
let mgr = FrostManager;
let package = FrostManager::generate_key_package(2, 3, "vault-1").unwrap();
let shares = vec![
FrostSignatureShare {
signer_id: 1,
share: vec![1; 32],
},
FrostSignatureShare {
signer_id: 2,
share: vec![2; 32],
},
];
let sig = mgr
.aggregate_signatures(&package, shares, b"hello")
.unwrap();
assert_eq!(sig.len(), 128); }
}