confium_privacy/
distributed_prf.rs1use hmac::{Hmac, KeyInit, Mac};
8use serde::{Deserialize, Serialize};
9use sha2::Sha256;
10
11type HmacSha256 = Hmac<Sha256>;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct PrfShare {
16 pub key: Vec<u8>,
17}
18
19impl PrfShare {
20 pub fn evaluate(&self, input: &[u8]) -> [u8; 32] {
22 let mut mac = HmacSha256::new_from_slice(&self.key).expect("HMAC key");
23 mac.update(input);
24 let result = mac.finalize().into_bytes();
25 let mut out = [0u8; 32];
26 let len = result.len().min(32);
27 out[..len].copy_from_slice(&result[..len]);
28 out
29 }
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct DistributedPrfResult {
35 pub output: [u8; 32],
36}
37
38pub fn distribute(secret: &[u8], threshold: usize, party_count: usize) -> Vec<PrfShare> {
41 let mut shares = Vec::with_capacity(party_count);
44 let mut xor_accum = vec![0u8; secret.len()];
45
46 for _ in 0..(party_count.saturating_sub(1)) {
48 let mut rand_share = vec![0u8; secret.len()];
49 for (i, byte) in rand_share.iter_mut().enumerate() {
50 *byte = rand_byte();
51 xor_accum[i] ^= *byte;
52 }
53 shares.push(PrfShare { key: rand_share });
54 }
55
56 let last: Vec<u8> = secret
58 .iter()
59 .zip(xor_accum.iter())
60 .map(|(s, x)| s ^ x)
61 .collect();
62 shares.push(PrfShare { key: last });
63
64 let _ = threshold; shares
66}
67
68pub fn combine(partials: &[[u8; 32]]) -> [u8; 32] {
70 let mut result = [0u8; 32];
71 for partial in partials {
72 for (i, &b) in partial.iter().enumerate() {
73 result[i] ^= b;
74 }
75 }
76 result
77}
78
79pub fn evaluate(
81 shares: &[PrfShare],
82 input: &[u8],
83 threshold: usize,
84) -> Result<DistributedPrfResult, String> {
85 if shares.len() < threshold {
86 return Err(format!("need {threshold} shares, got {}", shares.len()));
87 }
88 let partials: Vec<[u8; 32]> = shares[..threshold]
89 .iter()
90 .map(|s| s.evaluate(input))
91 .collect();
92 let output = combine(&partials);
93 Ok(DistributedPrfResult { output })
94}
95
96fn rand_byte() -> u8 {
97 use rand_core::{OsRng, RngCore};
98 let mut b = [0u8; 1];
99 OsRng.fill_bytes(&mut b);
100 b[0]
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn share_and_evaluate() {
109 let secret = b"my secret key";
110 let shares = distribute(secret, 3, 5);
111 let result = evaluate(&shares, b"input", 3).unwrap();
112 assert_eq!(result.output.len(), 32);
113 }
114
115 #[test]
116 fn insufficient_shares_rejected() {
117 let secret = b"key";
118 let shares = distribute(secret, 3, 5);
119 assert!(evaluate(&shares[0..2], b"input", 3).is_err());
120 }
121
122 #[test]
123 fn deterministic_evaluation() {
124 let secret = b"key";
125 let shares = distribute(secret, 2, 3);
126 let r1 = evaluate(&shares, b"in", 2).unwrap();
127 let r2 = evaluate(&shares, b"in", 2).unwrap();
128 assert_eq!(r1, r2);
129 }
130
131 #[test]
132 fn different_inputs_different_outputs() {
133 let secret = b"key";
134 let shares = distribute(secret, 2, 3);
135 let r1 = evaluate(&shares, b"input1", 2).unwrap();
136 let r2 = evaluate(&shares, b"input2", 2).unwrap();
137 assert_ne!(r1, r2);
138 }
139
140 #[test]
141 fn combine_xors_correctly() {
142 let a = [1u8; 32];
143 let b = [2u8; 32];
144 let result = combine(&[a, b]);
145 for r in &result {
146 assert_eq!(*r, 1 ^ 2);
147 }
148 }
149}