@@ -95,7 +95,7 @@ async fn test_fhe_random_basic() -> Result<(), Box<dyn std::error::Error>> {
9595 TfheContractEvents :: FheRand ( TfheContract :: FheRand {
9696 caller : zero_address ( ) ,
9797 randType : to_ty ( rand_type) ,
98- seed : FixedBytes :: from ( [ 1_u8 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) ,
98+ seed : FixedBytes :: from ( [ 42_u8 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) ,
9999 result : output3,
100100 } ) ,
101101 true ,
@@ -123,18 +123,26 @@ async fn test_fhe_random_basic() -> Result<(), Box<dyn std::error::Error>> {
123123 first. value, second. value,
124124 "random generation must be deterministic for same seed"
125125 ) ;
126+ // FheBool::generate_oblivious_pseudo_random produces the same
127+ // plaintext for all seeds with a given key, so we can only check
128+ // seed-variance for non-bool types.
126129 if * rand_type != 0 {
127130 assert_ne ! (
128131 first. value, third. value,
129- "random generation must change when seed changes"
132+ "type {rand_type}: random generation must change when seed changes"
130133 ) ;
131134 }
132135 }
133136
134137 Ok ( ( ) )
135138}
136139
137- /// Verifies FheRandBounded produces values within the requested bounds.
140+ /// Verifies FheRandBounded produces values within the requested bounds
141+ /// and that different seeds yield different results for non-bool types
142+ /// (rejecting a constant-output implementation, e.g. one that always
143+ /// returns zero). Bool is excluded from seed-variance checks because
144+ /// `FheBool::generate_oblivious_pseudo_random` produces the same
145+ /// plaintext for all seeds with a given key.
138146///
139147/// Uses per-type bounds that match the old gRPC test to avoid edge cases
140148/// (e.g. upper_bound=1 produces 0 random bits, which behaves differently
@@ -172,7 +180,8 @@ async fn test_fhe_random_bounded() -> Result<(), Box<dyn std::error::Error>> {
172180 let tx_id = next_handle ( ) ;
173181 let mut tx = harness. listener_db . new_transaction ( ) . await ?;
174182
175- let output = next_handle ( ) ;
183+ // First sample with seed [1,0,...,0]
184+ let output1 = next_handle ( ) ;
176185 insert_event (
177186 & harness. listener_db ,
178187 & mut tx,
@@ -182,46 +191,88 @@ async fn test_fhe_random_bounded() -> Result<(), Box<dyn std::error::Error>> {
182191 upperBound : as_scalar_uint ( & bound) ,
183192 randType : to_ty ( rand_type) ,
184193 seed : FixedBytes :: from ( [ 1_u8 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) ,
185- result : output ,
194+ result : output1 ,
186195 } ) ,
187196 true ,
188197 )
189198 . await ?;
190- allow_handle ( & harness. listener_db , & mut tx, & output) . await ?;
199+ allow_handle ( & harness. listener_db , & mut tx, & output1) . await ?;
200+
201+ // Second sample with a different seed
202+ let output2 = next_handle ( ) ;
203+ insert_event (
204+ & harness. listener_db ,
205+ & mut tx,
206+ tx_id,
207+ TfheContractEvents :: FheRandBounded ( TfheContract :: FheRandBounded {
208+ caller : zero_address ( ) ,
209+ upperBound : as_scalar_uint ( & bound) ,
210+ randType : to_ty ( rand_type) ,
211+ seed : FixedBytes :: from ( [ 7_u8 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) ,
212+ result : output2,
213+ } ) ,
214+ true ,
215+ )
216+ . await ?;
217+ allow_handle ( & harness. listener_db , & mut tx, & output2) . await ?;
191218 tx. commit ( ) . await ?;
192219
193220 rand_types. push ( rand_type) ;
194221 bounds. push ( bound) ;
195- handles. push ( output ) ;
222+ handles. extend ( [ output1 , output2 ] ) ;
196223 }
197224
198225 wait_until_computed ( & harness. app ) . await ?;
199226 let decrypted = decrypt_handles ( & harness. pool , & handles) . await ?;
200227
201228 for ( idx, rand_type) in rand_types. iter ( ) . enumerate ( ) {
202- let result = & decrypted[ idx] ;
203- assert_eq ! ( result. output_type, * rand_type as i16 ) ;
229+ let base = idx * 2 ;
230+ let result1 = & decrypted[ base] ;
231+ let result2 = & decrypted[ base + 1 ] ;
232+ assert_eq ! ( result1. output_type, * rand_type as i16 ) ;
233+ assert_eq ! ( result2. output_type, * rand_type as i16 ) ;
204234
205- // Bool is special: only valid values are 0/1, and bound=2 means [0,2).
206235 if * rand_type == 0 {
236+ // FheBool::generate_oblivious_pseudo_random produces the same
237+ // plaintext for all seeds with a given key, so we can only
238+ // validate the value domain, not seed-variance.
239+ assert ! (
240+ result1. value == "true" || result1. value == "false" ,
241+ "bool rand_bounded should be true or false, got: {}" ,
242+ result1. value
243+ ) ;
207244 assert ! (
208- result . value == "true" || result . value == "false" ,
245+ result2 . value == "true" || result2 . value == "false" ,
209246 "bool rand_bounded should be true or false, got: {}" ,
210- result . value
247+ result2 . value
211248 ) ;
212249 continue ;
213250 }
214251
215- let result_num = BigInt :: from_str ( & result. value ) ?;
252+ let result1_num = BigInt :: from_str ( & result1. value ) ?;
253+ let result2_num = BigInt :: from_str ( & result2. value ) ?;
216254 assert ! (
217- result_num >= BigInt :: from( 0_u8 ) ,
218- "type {rand_type}: rand_bounded result should be >= 0, got {result_num }"
255+ result1_num >= BigInt :: from( 0_u8 ) ,
256+ "type {rand_type}: rand_bounded result should be >= 0, got {result1_num }"
219257 ) ;
220258 assert ! (
221- result_num < bounds[ idx] ,
222- "type {rand_type}: rand_bounded result {result_num } should be < bound {}" ,
259+ result1_num < bounds[ idx] ,
260+ "type {rand_type}: rand_bounded result {result1_num } should be < bound {}" ,
223261 bounds[ idx]
224262 ) ;
263+ assert ! (
264+ result2_num >= BigInt :: from( 0_u8 ) ,
265+ "type {rand_type}: rand_bounded result should be >= 0, got {result2_num}"
266+ ) ;
267+ assert ! (
268+ result2_num < bounds[ idx] ,
269+ "type {rand_type}: rand_bounded result {result2_num} should be < bound {}" ,
270+ bounds[ idx]
271+ ) ;
272+ assert_ne ! (
273+ result1_num, result2_num,
274+ "type {rand_type}: bounded random must vary with seed"
275+ ) ;
225276 }
226277
227278 Ok ( ( ) )
0 commit comments