use hmac::{Hmac, Mac};
use sha2::Sha256;
type HmacSha256 = Hmac<Sha256>;
#[derive(Debug, Clone)]
pub struct PrgShare {
pub seed: [u8; 32],
}
impl PrgShare {
pub fn next_block(&self, nonce: &[u8]) -> [u8; 32] {
let mut mac = HmacSha256::new_from_slice(&self.seed).expect("HMAC key");
mac.update(nonce);
let result = mac.finalize().into_bytes();
let mut out = [0u8; 32];
out.copy_from_slice(&result);
out
}
}
pub fn combine(shares: &[PrgShare], nonce: &[u8]) -> [u8; 32] {
let mut result = [0u8; 32];
for share in shares {
let block = share.next_block(nonce);
for (i, &b) in block.iter().enumerate() {
result[i] ^= b;
}
}
result
}
pub fn distribute(master_seed: &[u8; 32], party_count: usize) -> Vec<PrgShare> {
use rand_core::{OsRng, RngCore};
let mut shares = Vec::with_capacity(party_count);
let mut xor_accum = *master_seed;
for _ in 0..(party_count - 1) {
let mut seed = [0u8; 32];
OsRng.fill_bytes(&mut seed);
for (i, &b) in seed.iter().enumerate() {
xor_accum[i] ^= b;
}
shares.push(PrgShare { seed });
}
shares.push(PrgShare { seed: xor_accum });
shares
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn shares_combine_to_master() {
let master = [0x42u8; 32];
let shares = distribute(&master, 5);
let output = combine(&shares, b"nonce");
let mut expected = [0u8; 32];
for share in &shares {
for (i, &b) in share.next_block(b"nonce").iter().enumerate() {
expected[i] ^= b;
}
}
assert_eq!(output, expected);
}
#[test]
fn deterministic_with_same_nonce() {
let master = [1u8; 32];
let shares = distribute(&master, 3);
let r1 = combine(&shares, b"nonce");
let r2 = combine(&shares, b"nonce");
assert_eq!(r1, r2);
}
#[test]
fn different_nonces_different_outputs() {
let master = [1u8; 32];
let shares = distribute(&master, 3);
let r1 = combine(&shares, b"nonce1");
let r2 = combine(&shares, b"nonce2");
assert_ne!(r1, r2);
}
#[test]
fn share_count_matches_party_count() {
let master = [0u8; 32];
let shares = distribute(&master, 4);
assert_eq!(shares.len(), 4);
}
#[test]
fn single_share() {
let master = [0u8; 32];
let shares = distribute(&master, 1);
assert_eq!(shares.len(), 1);
let output = combine(&shares, b"n");
let expected = shares[0].next_block(b"n");
assert_eq!(output, expected);
}
}