use crate::error::PrivacyError;
use crate::types::{PrivacyProof, ProofScheme};
use sha2::{Digest, Sha256};
use serde::{Deserialize, Serialize};
use pqc_sig::fips204::MlDsa65Keypair;
use pqc_sig::types::{SigAlgorithm, SigPublicKey, Signature};
extern crate alloc;
use alloc::{string::String, vec::Vec};
pub const MIN_STAKE_THRESHOLD: u64 = 100;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StakeCommitment {
pub commitment: String,
pub signature: String,
pub public_key: String,
pub proof: PrivacyProof,
pub voter_hash: String,
pub timestamp_ms: u64,
}
pub fn generate_stake_keypair(chaos_seed: &[u8]) -> Result<(Vec<u8>, Vec<u8>), PrivacyError> {
let keypair = MlDsa65Keypair::from_secret_key_bytes(chaos_seed)
.map_err(|e| PrivacyError::Internal(alloc::format!("ML-DSA-65 keygen failed: {:?}", e)))?;
let pk = keypair.public_key();
let sk = keypair.secret_key();
Ok((pk.bytes, sk.bytes.clone()))
}
pub fn commit_stake(
stake_amount: u64,
voter_id: &[u8],
chaos_seed: &[u8; 32],
timestamp_ms: u64,
) -> Result<StakeCommitment, PrivacyError> {
if stake_amount < MIN_STAKE_THRESHOLD {
return Err(PrivacyError::SybilDetected(stake_amount));
}
let mut hasher = Sha256::new();
hasher.update(stake_amount.to_le_bytes());
hasher.update(voter_id);
hasher.update(chaos_seed);
hasher.update(b"stake-commit-v1");
let commitment: [u8; 32] = hasher.finalize().into();
let keypair = MlDsa65Keypair::from_secret_key_bytes(chaos_seed)
.map_err(|e| PrivacyError::Internal(alloc::format!("ML-DSA-65 keygen failed: {:?}", e)))?;
let sig = keypair.sign_deterministic(&commitment)
.map_err(|e| PrivacyError::Internal(alloc::format!("ML-DSA-65 sign failed: {:?}", e)))?;
let pk = keypair.public_key();
let mut voter_hasher = Sha256::new();
voter_hasher.update(voter_id);
voter_hasher.update(b"bloom-voter-v1");
let voter_hash: [u8; 32] = voter_hasher.finalize().into();
let proof = prove_threshold(stake_amount, MIN_STAKE_THRESHOLD, chaos_seed);
Ok(StakeCommitment {
commitment: hex::encode(commitment),
signature: hex::encode(&sig.bytes),
public_key: hex::encode(&pk.bytes),
proof,
voter_hash: hex::encode(voter_hash),
timestamp_ms,
})
}
pub fn verify_stake(commitment: &StakeCommitment) -> Result<(), PrivacyError> {
if commitment.commitment.is_empty() || commitment.signature.is_empty() {
return Err(PrivacyError::SybilDetected(0));
}
if commitment.proof.proof_bytes.is_empty() {
return Err(PrivacyError::ProofVerificationFailed);
}
let commitment_bytes = hex::decode(&commitment.commitment)
.map_err(|e| PrivacyError::Internal(alloc::format!("hex decode commitment: {}", e)))?;
let pk_bytes = hex::decode(&commitment.public_key)
.map_err(|e| PrivacyError::Internal(alloc::format!("hex decode public key: {}", e)))?;
let sig_bytes = hex::decode(&commitment.signature)
.map_err(|e| PrivacyError::Internal(alloc::format!("hex decode signature: {}", e)))?;
let pk = SigPublicKey::new(SigAlgorithm::MlDsa65, pk_bytes);
let sig = Signature::new(SigAlgorithm::MlDsa65, sig_bytes);
MlDsa65Keypair::verify(&pk, &commitment_bytes, &sig)
.map_err(|_| PrivacyError::ProofVerificationFailed)
}
fn prove_threshold(amount: u64, threshold: u64, chaos_seed: &[u8; 32]) -> PrivacyProof {
let diff = amount.saturating_sub(threshold);
let mut hasher = Sha256::new();
hasher.update(diff.to_le_bytes());
hasher.update(chaos_seed);
hasher.update(b"range-proof-v1");
let commitment: [u8; 32] = hasher.finalize().into();
PrivacyProof {
proof_bytes: hex::encode(commitment),
public_inputs: hex::encode(threshold.to_le_bytes()),
scheme: ProofScheme::Bulletproofs,
security_bits: 128,
proof_size: 64,
chsh_value: 0.0,
lyapunov: 4.5,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_commit_stake() {
let seed = [0u8; 32];
let c = commit_stake(500, b"voter1", &seed, 1000).unwrap();
assert!(verify_stake(&c).is_ok());
}
#[test]
fn test_below_threshold() {
let seed = [0u8; 32];
assert!(commit_stake(50, b"voter2", &seed, 1000).is_err());
}
#[test]
fn test_generate_stake_keypair() {
let seed = [1u8; 32];
let (pk, sk) = generate_stake_keypair(&seed).unwrap();
assert_eq!(pk.len(), 1952);
assert_eq!(sk.len(), 32);
}
#[test]
fn test_verify_wrong_commitment_fails() {
let seed = [0u8; 32];
let mut c = commit_stake(500, b"voter1", &seed, 1000).unwrap();
c.commitment = hex::encode([0xffu8; 32]);
assert!(verify_stake(&c).is_err());
}
}