1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Client-side FHE context: key generation, encryption, and decryption.
//!
//! # Role in the two-party model
//!
//! The client generates key material, encrypts feature vectors with the
//! private [`ClientKey`], and decrypts the inference result. The private key
//! **never leaves the client**. Only the [`ServerContext`] (which contains
//! the [`ServerKey`] alone) is shared with the inference server.
//!
//! ```text
//! ┌──────────── Client ─────────────┐ ┌───────── Server ──────────┐
//! │ ClientContext::generate() │ │ │
//! │ ├─ ClientKey (private) │ │ ServerContext │
//! │ └─ ServerKey ─────────────┼──────► │ └─ ServerKey │
//! │ │ │ │
//! │ client.encrypt(&features) ─────┼──────► │ FheEvaluator::predict() │
//! │ │ ◄───── │ │
//! │ client.decrypt_score(&score) │ │ │
//! └─────────────────────────────────┘ └───────────────────────────┘
//! ```
//!
//! # Encoding
//!
//! `f32` features are encoded as fixed-point `i32` values scaled by
//! [`SCALE`] before encryption. This gives three decimal places of precision
//! and supports feature values across the full practical range (i32 holds
//! up to ±2,147,483 before scaling, far beyond any normalised feature). The
//! same scale factor is applied to leaf values by [`FheEvaluator`], so
//! [`ClientContext::decrypt_score`] simply divides the decrypted integer by
//! [`SCALE`] to recover the original float range.
//!
//! [`FheEvaluator`]: super::evaluator::FheEvaluator
//! [`ServerContext`]: super::server::ServerContext
//! [`ClientKey`]: tfhe::ClientKey
//! [`ServerKey`]: tfhe::ServerKey
use *;
use ;
use crateError;
use ServerContext;
/// Fixed-point scale factor applied to `f32` features before encryption.
///
/// An `f32` value `v` is stored as `round(v * SCALE)` clamped to `i32`.
/// The [`FheEvaluator`](super::evaluator::FheEvaluator) must scale plaintext
/// leaf values by the same factor so that [`ClientContext::decrypt_score`]
/// produces the correct result.
pub const SCALE: f32 = 1000.0;
/// An encrypted feature vector produced by [`ClientContext::encrypt`].
///
/// Each element is an `FheInt32` representing one feature scaled by [`SCALE`].
pub type EncryptedInput = ;
/// An encrypted raw ensemble score produced by [`FheEvaluator`].
///
/// Stored as a scaled `FheInt32`; decrypt and divide by [`SCALE`] to
/// recover the original float score.
///
/// [`FheEvaluator`]: super::evaluator::FheEvaluator
pub type EncryptedScore = FheInt32;
// ---------------------------------------------------------------------------
// ClientContext
// ---------------------------------------------------------------------------
/// Client-side FHE context — holds the private key and is never shared.
///
/// Responsible for key generation, feature encryption, and score decryption.
/// Call [`server_context`](Self::server_context) to obtain a [`ServerContext`]
/// that can safely be handed to the inference server.
///
/// # Example
///
/// ```no_run
/// use weirwood::fhe::{ClientContext, FheEvaluator};
/// use weirwood::eval::Evaluator as _;
/// use weirwood::model::WeirwoodTree;
///
/// // --- Client ---
/// let client = ClientContext::generate()?;
/// let server_ctx = client.server_context(); // only the ServerKey is shared
///
/// let model = WeirwoodTree::from_json_file("model.json")?;
/// let features = vec![1.5_f32, 0.3, -2.1];
/// let ciphertext = client.encrypt(&features);
///
/// // --- "Send server_ctx and ciphertext to the server" ---
///
/// // --- Server ---
/// let evaluator = FheEvaluator::new(server_ctx); // installs key on worker threads
/// let encrypted_score = evaluator.predict(&model, &ciphertext);
///
/// // --- "Send encrypted_score back to the client" ---
///
/// // --- Client ---
/// let score = client.decrypt_score(&encrypted_score);
/// # Ok::<(), weirwood::Error>(())
/// ```