Skip to main content

confium_privacy/
distributed_prg.rs

1//! Distributed pseudo-random generator (PRG).
2//!
3//! Threshold PRG for ceremony randomness. Each share produces a
4//! deterministic pseudo-random stream; combining shares produces
5//! the true randomness.
6
7use hmac::{Hmac, KeyInit, Mac};
8use sha2::Sha256;
9
10type HmacSha256 = Hmac<Sha256>;
11
12/// A PRG share.
13#[derive(Debug, Clone)]
14pub struct PrgShare {
15    /// Seed for this party.
16    pub seed: [u8; 32],
17}
18
19impl PrgShare {
20    /// Generate a pseudo-random block from this share.
21    pub fn next_block(&self, nonce: &[u8]) -> [u8; 32] {
22        let mut mac = HmacSha256::new_from_slice(&self.seed).expect("HMAC key");
23        mac.update(nonce);
24        let result = mac.finalize().into_bytes();
25        let mut out = [0u8; 32];
26        out.copy_from_slice(&result);
27        out
28    }
29}
30
31/// Combine T PRG shares (XOR) to produce the true random output.
32pub fn combine(shares: &[PrgShare], nonce: &[u8]) -> [u8; 32] {
33    let mut result = [0u8; 32];
34    for share in shares {
35        let block = share.next_block(nonce);
36        for (i, &b) in block.iter().enumerate() {
37            result[i] ^= b;
38        }
39    }
40    result
41}
42
43/// Generate PRG shares from a master seed.
44pub fn distribute(master_seed: &[u8; 32], party_count: usize) -> Vec<PrgShare> {
45    use rand_core::{OsRng, RngCore};
46    let mut shares = Vec::with_capacity(party_count);
47    let mut xor_accum = *master_seed;
48    for _ in 0..(party_count - 1) {
49        let mut seed = [0u8; 32];
50        OsRng.fill_bytes(&mut seed);
51        for (i, &b) in seed.iter().enumerate() {
52            xor_accum[i] ^= b;
53        }
54        shares.push(PrgShare { seed });
55    }
56    shares.push(PrgShare { seed: xor_accum });
57    shares
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn shares_combine_to_master() {
66        let master = [0x42u8; 32];
67        let shares = distribute(&master, 5);
68        let output = combine(&shares, b"nonce");
69        // XOR of shares should equal master (since shares XOR to master)
70        let mut expected = [0u8; 32];
71        for share in &shares {
72            for (i, &b) in share.next_block(b"nonce").iter().enumerate() {
73                expected[i] ^= b;
74            }
75        }
76        // Note: the combine function XORs all share outputs, which should
77        // produce the same result as the manual XOR above
78        assert_eq!(output, expected);
79    }
80
81    #[test]
82    fn deterministic_with_same_nonce() {
83        let master = [1u8; 32];
84        let shares = distribute(&master, 3);
85        let r1 = combine(&shares, b"nonce");
86        let r2 = combine(&shares, b"nonce");
87        assert_eq!(r1, r2);
88    }
89
90    #[test]
91    fn different_nonces_different_outputs() {
92        let master = [1u8; 32];
93        let shares = distribute(&master, 3);
94        let r1 = combine(&shares, b"nonce1");
95        let r2 = combine(&shares, b"nonce2");
96        assert_ne!(r1, r2);
97    }
98
99    #[test]
100    fn share_count_matches_party_count() {
101        let master = [0u8; 32];
102        let shares = distribute(&master, 4);
103        assert_eq!(shares.len(), 4);
104    }
105
106    #[test]
107    fn single_share() {
108        let master = [0u8; 32];
109        let shares = distribute(&master, 1);
110        assert_eq!(shares.len(), 1);
111        let output = combine(&shares, b"n");
112        let expected = shares[0].next_block(b"n");
113        assert_eq!(output, expected);
114    }
115}