confium-privacy 0.3.0

Privacy-preserving crypto: MPC, PSI, PIR, differential privacy, ring signatures
Documentation
//! Distributed pseudo-random generator (PRG).
//!
//! Threshold PRG for ceremony randomness. Each share produces a
//! deterministic pseudo-random stream; combining shares produces
//! the true randomness.

use hmac::{Hmac, Mac};
use sha2::Sha256;

type HmacSha256 = Hmac<Sha256>;

/// A PRG share.
#[derive(Debug, Clone)]
pub struct PrgShare {
    /// Seed for this party.
    pub seed: [u8; 32],
}

impl PrgShare {
    /// Generate a pseudo-random block from this share.
    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
    }
}

/// Combine T PRG shares (XOR) to produce the true random output.
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
}

/// Generate PRG shares from a master seed.
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");
        // XOR of shares should equal master (since shares XOR to master)
        let mut expected = [0u8; 32];
        for share in &shares {
            for (i, &b) in share.next_block(b"nonce").iter().enumerate() {
                expected[i] ^= b;
            }
        }
        // Note: the combine function XORs all share outputs, which should
        // produce the same result as the manual XOR above
        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);
    }
}