| 
					
				 | 
			
			
				@@ -4,6 +4,8 @@ use crate::{ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 use rand::{Rng, SeedableRng}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 use rand_chacha::ChaCha20Rng; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 use std::{iter::once, mem::size_of}; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+use std::cell::RefCell; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+use thread_local::ThreadLocal; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 fn new_vec_raw<'a>( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     params: &'a Params, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -203,15 +205,15 @@ impl<'a> Query<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-pub struct Client<'a, T: Rng> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+pub struct Client<'a, T: Rng + Send> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     params: &'a Params, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     sk_gsw: PolyMatrixRaw<'a>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     sk_reg: PolyMatrixRaw<'a>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     sk_gsw_full: PolyMatrixRaw<'a>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     sk_reg_full: PolyMatrixRaw<'a>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    dg: DiscreteGaussian<'a, T>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    public_rng: ChaCha20Rng, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    public_seed: <ChaCha20Rng as SeedableRng>::Seed, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    dg: DiscreteGaussian<T>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public_rng: ThreadLocal<RefCell<ChaCha20Rng>>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public_seed: ThreadLocal<<ChaCha20Rng as SeedableRng>::Seed>, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 fn matrix_with_identity<'a>(p: &PolyMatrixRaw<'a>) -> PolyMatrixRaw<'a> { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -242,18 +244,17 @@ fn params_with_moduli(params: &Params, moduli: &Vec<u64>) -> Params { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-impl<'a, T: Rng> Client<'a, T> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn init(params: &'a Params, rng: &'a mut T) -> Self { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+impl<'a, T: Rng + Send> Client<'a, T> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    pub fn init(params: &'a Params, rnggen: fn() -> T) -> Self { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let sk_gsw_dims = params.get_sk_gsw(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let sk_reg_dims = params.get_sk_reg(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let sk_gsw = PolyMatrixRaw::zero(params, sk_gsw_dims.0, sk_gsw_dims.1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let sk_reg = PolyMatrixRaw::zero(params, sk_reg_dims.0, sk_reg_dims.1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let sk_gsw_full = matrix_with_identity(&sk_gsw); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let sk_reg_full = matrix_with_identity(&sk_reg); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut public_seed = [0u8; 32]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        rng.fill_bytes(&mut public_seed); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let public_rng = ChaCha20Rng::from_seed(public_seed); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let dg = DiscreteGaussian::init(params, rng); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let public_seed = ThreadLocal::new(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let dg = DiscreteGaussian::init(params, rnggen); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let public_rng = ThreadLocal::new(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         Self { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             params, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             sk_gsw, 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -271,24 +272,23 @@ impl<'a, T: Rng> Client<'a, T> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         &self.sk_reg 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn get_rng(&mut self) -> &mut T { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        &mut self.dg.rng 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    pub fn get_public_seed(&self) -> <ChaCha20Rng as SeedableRng>::Seed { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        *self.public_seed.get_or( || { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            let mut seed = [0u8; 32]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            self.dg.get_rng().fill_bytes(&mut seed); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            seed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn get_public_rng(&mut self) -> &mut ChaCha20Rng { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        &mut self.public_rng 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn get_public_seed(&mut self) -> <ChaCha20Rng as SeedableRng>::Seed { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        self.public_seed 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    fn get_fresh_gsw_public_key(&mut self, m: usize) -> PolyMatrixRaw<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    fn get_fresh_gsw_public_key(&self, m: usize) -> PolyMatrixRaw<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let params = self.params; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let n = params.n; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let a = PolyMatrixRaw::random_rng(params, 1, m, self.get_rng()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let e = PolyMatrixRaw::noise(params, n, m, &mut self.dg); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let a = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            let rng = &mut *self.dg.get_rng(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            PolyMatrixRaw::random_rng(params, 1, m, rng) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let e = PolyMatrixRaw::noise(params, n, m, &self.dg); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let a_inv = -&a; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let b_p = &self.sk_gsw.ntt() * &a.ntt(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let b = &e.ntt() + &b_p; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -296,10 +296,16 @@ impl<'a, T: Rng> Client<'a, T> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         p 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    fn get_regev_sample(&mut self) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    fn get_regev_sample(&self) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let params = self.params; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let a = PolyMatrixRaw::random_rng(params, 1, 1, self.get_public_rng()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let e = PolyMatrixRaw::noise(params, 1, 1, &mut self.dg); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let a = { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            let public_rng = &mut *self.public_rng.get_or(|| { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                RefCell::new(ChaCha20Rng::from_seed(self.get_public_seed())) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            }) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            .borrow_mut(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            PolyMatrixRaw::random_rng(params, 1, 1, public_rng) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let e = PolyMatrixRaw::noise(params, 1, 1, &self.dg); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let b_p = &self.sk_reg.ntt() * &a.ntt(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let b = &e.ntt() + &b_p; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let mut p = PolyMatrixNTT::zero(params, 2, 1); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -308,7 +314,7 @@ impl<'a, T: Rng> Client<'a, T> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         p 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    fn get_fresh_reg_public_key(&mut self, m: usize) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    fn get_fresh_reg_public_key(&self, m: usize) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let params = self.params; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let mut p = PolyMatrixNTT::zero(params, 2, m); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -319,29 +325,29 @@ impl<'a, T: Rng> Client<'a, T> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         p 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    fn encrypt_matrix_gsw(&mut self, ag: &PolyMatrixNTT<'a>) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    fn encrypt_matrix_gsw(&self, ag: &PolyMatrixNTT<'a>) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let mx = ag.cols; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let p = self.get_fresh_gsw_public_key(mx); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let res = &(p.ntt()) + &(ag.pad_top(1)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         res 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn encrypt_matrix_reg(&mut self, a: &PolyMatrixNTT<'a>) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    pub fn encrypt_matrix_reg(&self, a: &PolyMatrixNTT<'a>) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let m = a.cols; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let p = self.get_fresh_reg_public_key(m); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         &p + &a.pad_top(1) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn decrypt_matrix_reg(&mut self, a: &PolyMatrixNTT<'a>) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    pub fn decrypt_matrix_reg(&self, a: &PolyMatrixNTT<'a>) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         &self.sk_reg_full.ntt() * a 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn decrypt_matrix_gsw(&mut self, a: &PolyMatrixNTT<'a>) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    pub fn decrypt_matrix_gsw(&self, a: &PolyMatrixNTT<'a>) -> PolyMatrixNTT<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         &self.sk_gsw_full.ntt() * a 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     fn generate_expansion_params( 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        &mut self, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        &self, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         num_exp: usize, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         m_exp: usize, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     ) -> Vec<PolyMatrixNTT<'a>> { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -414,7 +420,7 @@ impl<'a, T: Rng> Client<'a, T> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         pp 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    pub fn generate_query(&mut self, idx_target: usize) -> Query<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    pub fn generate_query(&self, idx_target: usize) -> Query<'a> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let params = self.params; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let further_dims = params.db_dim_2; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let idx_dim0 = idx_target / (1 << further_dims); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -582,7 +588,8 @@ impl<'a, T: Rng> Client<'a, T> { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 #[cfg(test)] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 mod test { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-    use rand::thread_rng; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    use rand::SeedableRng; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    use rand_chacha::ChaCha20Rng; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     use super::*; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -598,8 +605,7 @@ mod test { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     #[test] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     fn init_is_correct() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let params = get_params(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut rng = thread_rng(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let client = Client::init(¶ms, &mut rng); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let client = Client::init(¶ms, ChaCha20Rng::from_entropy); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         assert_eq!(*client.params, params); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -607,8 +613,8 @@ mod test { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     #[test] 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     fn keygen_is_correct() { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let params = get_params(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut seeded_rng = get_static_seeded_rng(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut client = Client::init(¶ms, &mut seeded_rng); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let mut client = Client::init(¶ms, get_static_seeded_rng); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        client.get_public_seed(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let pub_params = client.generate_keys(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -639,8 +645,7 @@ mod test { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     fn public_parameters_serialization_is_correct_for_params(params: Params) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut seeded_rng = get_static_seeded_rng(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut client = Client::init(¶ms, &mut seeded_rng); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let mut client = Client::init(¶ms, get_static_seeded_rng); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let pub_params = client.generate_keys(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let serialized1 = pub_params.serialize(); 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -704,8 +709,7 @@ mod test { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     fn query_serialization_is_correct_for_params(params: Params) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut seeded_rng = get_static_seeded_rng(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        let mut client = Client::init(¶ms, &mut seeded_rng); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        let mut client = Client::init(¶ms, get_static_seeded_rng); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         _ = client.generate_keys(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         let query = client.generate_query(1); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 |