lox_extensions/lib.rs
1/*! Implementation of a new style of bridge authority for Tor that
2allows users to invite other users, while protecting the social graph
3from the bridge authority itself.
4
5We use uCMZ credentials (Orr`u, 2024 https://eprint.iacr.org/2024/1552.pdf) which improves issuer efficiency
6over our original CMZ14 credential (GGM version, which is more efficient, but
7makes a stronger security assumption) implementation: "Algebraic MACs and
8Keyed-Verification Anonymous Credentials" (Chase, Meiklejohn, and
9Zaverucha, CCS 2014)
10
11The notation follows that of the paper "Hyphae: Social Secret Sharing"
12(Lovecruft and de Valence, 2017), Section 4. */
13
14// We want Scalars to be lowercase letters, and Points and credentials
15// to be capital letters
16#![allow(non_snake_case)]
17
18#[cfg(feature = "bridgeauth")]
19use ed25519_dalek::{Signature, SignatureError, Signer, SigningKey, Verifier, VerifyingKey};
20use subtle::ConstantTimeEq;
21
22#[cfg(feature = "bridgeauth")]
23use chrono::{DateTime, Duration, Utc};
24#[cfg(feature = "bridgeauth")]
25use cmz::*;
26use curve25519_dalek::ristretto::RistrettoPoint as G;
27use group::Group;
28#[cfg(feature = "bridgeauth")]
29use rand::{rngs::OsRng, CryptoRng, Rng, RngCore};
30#[cfg(feature = "bridgeauth")]
31use std::collections::HashMap;
32type Scalar = <G as Group>::Scalar;
33#[cfg(feature = "bridgeauth")]
34use sha2::Sha512;
35
36pub mod bridge_table;
37pub mod dup_filter;
38pub mod lox_creds;
39pub mod migration_table;
40pub mod mock_auth;
41pub mod proto {
42 pub mod blockage_migration;
43 pub mod check_blockage;
44 pub mod errors;
45 pub mod issue_invite;
46 pub mod level_up;
47 pub mod migration;
48 pub mod open_invite;
49 pub mod redeem_invite;
50 pub mod trust_promotion;
51 pub mod update_cred;
52 pub mod update_invite;
53}
54
55#[cfg(feature = "bridgeauth")]
56use bridge_table::{
57 BridgeLine, BridgeTable, EncryptedBucket, MAX_BRIDGES_PER_BUCKET, MIN_BUCKET_REACHABILITY,
58};
59#[cfg(feature = "bridgeauth")]
60use lox_creds::*;
61#[cfg(feature = "bridgeauth")]
62use migration_table::{MigrationTable, MigrationType};
63#[cfg(feature = "bridgeauth")]
64use serde::{Deserialize, Serialize};
65#[cfg(feature = "bridgeauth")]
66use std::collections::HashSet;
67#[cfg(any(feature = "bridgeauth", test))]
68use thiserror::Error;
69
70// EXPIRY_DATE is set to EXPIRY_DATE days for open-entry and blocked buckets in order to match
71// the expiry date for Lox credentials.This particular value (EXPIRY_DATE) is chosen because
72// values that are 2^k − 1 make range proofs more efficient, but this can be changed to any value
73pub const EXPIRY_DATE: u32 = 511;
74
75/// ReplaceSuccess sends a signal to the lox-distributor to inform
76/// whether or not a bridge was successfully replaced
77#[derive(PartialEq, Eq)]
78#[cfg(feature = "bridgeauth")]
79pub enum ReplaceSuccess {
80 NotFound = 0,
81 NotReplaced = 1,
82 Replaced = 2,
83 Removed = 3,
84}
85
86/// This error is thrown if the number of buckets/keys in the bridge table
87/// exceeds u32 MAX.It is unlikely this error will ever occur.
88#[derive(Error, Debug)]
89#[cfg(feature = "bridgeauth")]
90pub enum NoAvailableIDError {
91 #[error("Find key exhausted with no available index found!")]
92 ExhaustedIndexer,
93}
94
95/// This error is thrown after the MAX_DAILY_BRIDGES threshold for bridges
96/// distributed in a day has been reached
97#[derive(Error, Debug)]
98#[cfg(any(feature = "bridgeauth", test))]
99pub enum OpenInvitationError {
100 #[error("The maximum number of bridges has already been distributed today, please try again tomorrow!")]
101 ExceededMaxBridges,
102
103 #[error("There are no bridges available for open invitations.")]
104 NoBridgesAvailable,
105}
106
107#[derive(Error, Debug)]
108#[cfg(feature = "bridgeauth")]
109pub enum BridgeTableError {
110 #[error("The bucket corresponding to key {0} was not in the bridge table")]
111 MissingBucket(u32),
112}
113
114/// Number of times a given invitation is ditributed
115pub const OPENINV_K: u32 = 10;
116/// TODO: Decide on maximum daily number of invitations to be distributed
117pub const MAX_DAILY_BRIDGES: u32 = 100;
118
119/// The BridgeDb. This will typically be a singleton object. The
120/// BridgeDb's role is simply to issue signed "open invitations" to
121/// people who are not yet part of the system.
122#[derive(Debug, Serialize, Deserialize)]
123#[cfg(feature = "bridgeauth")]
124pub struct BridgeDb {
125 /// The keypair for signing open invitations
126 keypair: SigningKey,
127 /// The public key for verifying open invitations
128 pub pubkey: VerifyingKey,
129 /// The set of open-invitation buckets
130 openinv_buckets: HashSet<u32>,
131 /// The set of open invitation buckets that have been distributed
132 distributed_buckets: Vec<u32>,
133 #[serde(skip)]
134 today: DateTime<Utc>,
135 pub current_k: u32,
136 pub daily_bridges_distributed: u32,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140#[cfg(feature = "bridgeauth")]
141pub struct OldKeyStore {
142 // /// Most recently outdated lox secret and private keys for verifying update_cred credentials
143 priv_key: CMZPrivkey<G>,
144 // /// The public key for verifying update_cred credentials
145 pub pub_key: CMZPubkey<G>,
146}
147
148#[derive(Debug, Default, Clone, Serialize, Deserialize)]
149#[cfg(feature = "bridgeauth")]
150pub struct OldKeys {
151 /// Most recently outdated lox secret and private keys for verifying update_cred credentials
152 lox_keys: Vec<OldKeyStore>,
153 /// Most recently outdated open_invitation VerifyingKey for verifying update_openinv tokens
154 bridgedb_key: Vec<VerifyingKey>,
155 /// Most recently outdated invitation secret and private keys for verifying update_inv credentials
156 invitation_keys: Vec<OldKeyStore>,
157}
158
159#[derive(Debug, Default, Clone, Serialize, Deserialize)]
160#[cfg(feature = "bridgeauth")]
161pub struct OldFilters {
162 /// Most recently outdated lox id filter
163 lox_filter: Vec<dup_filter::DupFilter<Scalar>>,
164 /// Most recently outdated open invitation filter
165 openinv_filter: Vec<dup_filter::DupFilter<Scalar>>,
166 /// Most recently outdated invitation filter
167 invitation_filter: Vec<dup_filter::DupFilter<Scalar>>,
168}
169
170/// An open invitation is a [u8; OPENINV_LENGTH] where the first 32
171/// bytes are the serialization of a random Scalar (the invitation id),
172/// the next 4 bytes are a little-endian bucket number, and the last
173/// SIGNATURE_LENGTH bytes are the signature on the first 36 bytes.
174pub const OPENINV_LENGTH: usize = 32 // the length of the random
175 // invitation id (a Scalar)
176 + 4 // the length of the u32 for the bucket number
177 + ed25519_dalek::SIGNATURE_LENGTH; // the length of the signature
178
179#[cfg(feature = "bridgeauth")]
180impl Default for BridgeDb {
181 fn default() -> Self {
182 Self::new()
183 }
184}
185
186#[cfg(feature = "bridgeauth")]
187impl BridgeDb {
188 /// Create the BridgeDb.
189 pub fn new() -> Self {
190 let mut csprng = OsRng {};
191 let keypair = SigningKey::generate(&mut csprng);
192 let pubkey = keypair.verifying_key();
193 Self {
194 keypair,
195 pubkey,
196 openinv_buckets: Default::default(),
197 distributed_buckets: Default::default(),
198 today: Utc::now(),
199 current_k: 0,
200 daily_bridges_distributed: 0,
201 }
202 }
203
204 pub fn openinv_length(&mut self) -> usize {
205 self.openinv_buckets.len()
206 }
207
208 /// Rotate Open Invitation keys
209 pub fn rotate_open_inv_keys(&mut self) -> VerifyingKey {
210 let mut csprng = OsRng {};
211 self.keypair = SigningKey::generate(&mut csprng);
212 self.pubkey = self.keypair.verifying_key();
213 self.pubkey
214 }
215
216 /// Insert an open-invitation bucket into the set
217 pub fn insert_openinv(&mut self, bucket: u32) {
218 self.openinv_buckets.insert(bucket);
219 }
220
221 /// Remove an open-invitation bucket from the set
222 pub fn remove_openinv(&mut self, bucket: &u32) {
223 self.openinv_buckets.remove(bucket);
224 }
225
226 /// Remove open invitation and/or otherwise distributed buckets that have
227 /// become blocked or are expired to free up the index for a new bucket
228 pub fn remove_blocked_or_expired_buckets(&mut self, bucket: &u32) {
229 if self.openinv_buckets.contains(bucket) {
230 println!("Removing a bucket that has not been distributed yet!");
231 self.openinv_buckets.remove(bucket);
232 } else if self.distributed_buckets.contains(bucket) {
233 self.distributed_buckets.retain(|&x| x != *bucket);
234 }
235 }
236
237 /// Mark a bucket as distributed
238 pub fn mark_distributed(&mut self, bucket: u32) {
239 self.distributed_buckets.push(bucket);
240 }
241
242 /// Produce an open invitation such that the next k users, where k is <
243 /// OPENINV_K, will receive the same open invitation bucket
244 /// chosen randomly from the set of open-invitation buckets.
245 pub fn invite(&mut self) -> Result<[u8; OPENINV_LENGTH], OpenInvitationError> {
246 let mut res: [u8; OPENINV_LENGTH] = [0; OPENINV_LENGTH];
247 let mut rng = rand::rngs::OsRng;
248 // Choose a random invitation id (a Scalar) and serialize it
249 let id = Scalar::random(&mut rng);
250 res[0..32].copy_from_slice(&id.to_bytes());
251 let bucket_num: u32;
252 if Utc::now() >= (self.today + Duration::days(1)) {
253 self.today = Utc::now();
254 self.daily_bridges_distributed = 0;
255 }
256 if self.daily_bridges_distributed < MAX_DAILY_BRIDGES {
257 if self.current_k < OPENINV_K && !self.distributed_buckets.is_empty() {
258 bucket_num = *self.distributed_buckets.last().unwrap();
259 self.current_k += 1;
260 } else {
261 if self.openinv_buckets.is_empty() {
262 return Err(OpenInvitationError::NoBridgesAvailable);
263 }
264 // Choose a random bucket number (from the set of open
265 // invitation buckets) and serialize it
266 let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect();
267 bucket_num = *openinv_vec[rng.gen_range(0..openinv_vec.len())];
268 self.mark_distributed(bucket_num);
269 self.remove_openinv(&bucket_num);
270 self.current_k = 1;
271 self.daily_bridges_distributed += 1;
272 }
273 res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes());
274 // Sign the first 36 bytes and serialize it
275 let sig = self.keypair.sign(&res[0..(32 + 4)]);
276 res[(32 + 4)..].copy_from_slice(&sig.to_bytes());
277 Ok(res)
278 } else {
279 Err(OpenInvitationError::ExceededMaxBridges)
280 }
281 }
282
283 /// Verify an open invitation. Returns the invitation id and the
284 /// bucket number if the signature checked out. It is up to the
285 /// caller to then check that the invitation id has not been used
286 /// before.
287 pub fn verify(
288 invitation: [u8; OPENINV_LENGTH],
289 pubkey: VerifyingKey,
290 ) -> Result<(Scalar, u32), SignatureError> {
291 // Pull out the signature and verify it
292 let sig = Signature::try_from(&invitation[(32 + 4)..])?;
293 pubkey.verify(&invitation[0..(32 + 4)], &sig)?;
294 // The signature passed. Pull out the bucket number and then
295 // the invitation id
296 let bucket = u32::from_le_bytes(invitation[32..(32 + 4)].try_into().unwrap());
297 let s = Scalar::from_canonical_bytes(invitation[0..32].try_into().unwrap());
298 if s.is_some().into() {
299 Ok((s.unwrap(), bucket))
300 } else {
301 // It should never happen that there's a valid signature on
302 // an invalid serialization of a Scalar, but check anyway.
303 Err(SignatureError::new())
304 }
305 }
306
307 /// For testing only: manually advance the day by the given number
308 /// of days
309 pub fn advance_days(&mut self, days: u16) {
310 if days > 0 {
311 self.today += Duration::days(days.into()); // Reset daily bridges distributed count
312 self.daily_bridges_distributed = 0;
313 }
314 }
315}
316
317/// The bridge authority. This will typically be a singleton object.
318#[cfg(feature = "bridgeauth")]
319#[derive(Default, Debug, Serialize, Deserialize)]
320pub struct BridgeAuth {
321 /// The private key for the main Lox credential
322 lox_priv: CMZPrivkey<G>,
323 /// The public key for the main Lox credential
324 pub lox_pub: CMZPubkey<G>,
325 /// The private key for migration credentials
326 migration_priv: CMZPrivkey<G>,
327 /// The public key for migration credentials
328 pub migration_pub: CMZPubkey<G>,
329 /// The private key for migration key credentials
330 migrationkey_priv: CMZPrivkey<G>,
331 /// The public key for migration key credentials
332 pub migrationkey_pub: CMZPubkey<G>,
333 /// The private key for bucket reachability credentials
334 reachability_priv: CMZPrivkey<G>,
335 /// The public key for bucket reachability credentials
336 pub reachability_pub: CMZPubkey<G>,
337 /// The private key for invitation credentials
338 invitation_priv: CMZPrivkey<G>,
339 /// The public key for invitation credentials
340 pub invitation_pub: CMZPubkey<G>,
341
342 /// The public key of the BridgeDb issuing open invitations
343 pub bridgedb_pub: VerifyingKey,
344
345 /// The bridge table
346 bridge_table: BridgeTable,
347
348 // Map of bridge fingerprint to values needed to verify TP reports
349 //pub tp_bridge_infos: HashMap<String, BridgeVerificationInfo>,
350 /// The migration tables
351 trustup_migration_table: MigrationTable,
352 blockage_migration_table: MigrationTable,
353
354 /// Duplicate filter for open invitations
355 bridgedb_pub_filter: dup_filter::DupFilter<Scalar>,
356 /// Duplicate filter for Lox credential ids
357 id_filter: dup_filter::DupFilter<Scalar>,
358 /// Duplicate filter for Invitation credential ids
359 inv_id_filter: dup_filter::DupFilter<Scalar>,
360 /// Duplicate filter for trust promotions (from untrusted level 0 to
361 /// trusted level 1)
362 trust_promotion_filter: dup_filter::DupFilter<Scalar>,
363 // Outdated Lox Keys to be populated with the old Lox private and public keys
364 // after a key rotation
365 old_keys: OldKeys,
366 old_filters: OldFilters,
367
368 /// For testing only: offset of the true time to the simulated time
369 #[serde(skip)]
370 time_offset: time::Duration,
371}
372
373#[cfg(feature = "bridgeauth")]
374impl BridgeAuth {
375 pub fn new(bridgedb_pub: VerifyingKey, rng: &mut (impl CryptoRng + RngCore)) -> Self {
376 // Initialization
377 cmz_group_init(G::hash_from_bytes::<Sha512>(b"CMZ Generator A"));
378 // Create the private and public keys for each of the types of
379 // credential with 'true' to indicate uCMZ
380 let (lox_priv, lox_pub) = Lox::gen_keys(rng, true);
381 let (migration_priv, migration_pub) = Migration::gen_keys(rng, true);
382 let (migrationkey_priv, migrationkey_pub) = MigrationKey::gen_keys(rng, true);
383 let (reachability_priv, reachability_pub) = BucketReachability::gen_keys(rng, true);
384 let (invitation_priv, invitation_pub) = Invitation::gen_keys(rng, true);
385 Self {
386 lox_priv,
387 lox_pub,
388 migration_priv,
389 migration_pub,
390 migrationkey_priv,
391 migrationkey_pub,
392 reachability_priv,
393 reachability_pub,
394 invitation_priv,
395 invitation_pub,
396 bridgedb_pub,
397 bridge_table: Default::default(),
398 // tp_bridge_infos: HashMap::<String, BridgeVerificationInfo>::new(),
399 trustup_migration_table: MigrationTable::new(MigrationType::TrustUpgrade),
400 blockage_migration_table: MigrationTable::new(MigrationType::Blockage),
401 bridgedb_pub_filter: Default::default(),
402 id_filter: Default::default(),
403 inv_id_filter: Default::default(),
404 trust_promotion_filter: Default::default(),
405 time_offset: time::Duration::ZERO,
406 old_keys: Default::default(),
407 old_filters: Default::default(),
408 }
409 }
410
411 pub fn rotate_lox_keys(&mut self, rng: &mut (impl CryptoRng + RngCore)) {
412 let (updated_lox_priv, updated_lox_pub) = Lox::gen_keys(rng, true);
413 // Store the old keys until the next key rotation (this should happen no more than 511 days after the
414 // last rotation to ensure that all credentials issued with the old key can be updated
415 self.old_keys.lox_keys.push(OldKeyStore {
416 priv_key: self.lox_priv.clone(),
417 pub_key: self.lox_pub.clone(),
418 });
419 // Move the old lox id filter to the old_lox_id_filter
420 self.old_filters.lox_filter.push(self.id_filter.clone());
421 // TODO: Commit to the new keys and post the commitment somewhere public that can be verified
422 // by users, ideally
423 self.lox_priv = updated_lox_priv;
424 self.lox_pub = updated_lox_pub;
425 self.id_filter = Default::default();
426 }
427
428 pub fn rotate_invitation_keys(&mut self, rng: &mut (impl CryptoRng + RngCore)) {
429 let (updated_invitation_priv, updated_invitation_pub) = Invitation::gen_keys(rng, true);
430 // Store the old keys until the next key rotation (this should happen no more than 511 days after the
431 // last rotation to ensure that all credentials issued with the old key can be updated
432 self.old_keys.invitation_keys.push(OldKeyStore {
433 priv_key: self.invitation_priv.clone(),
434 pub_key: self.invitation_pub.clone(),
435 });
436 // Move the old invitation id filter to the old_invitation_id_filter
437 self.old_filters
438 .invitation_filter
439 .push(self.inv_id_filter.clone());
440 // TODO: Commit to the new keys and post the commitment somewhere public that can be verified
441 // by users, ideally
442 self.invitation_priv = updated_invitation_priv;
443 self.invitation_pub = updated_invitation_pub;
444 self.inv_id_filter = Default::default();
445 }
446
447 pub fn rotate_bridgedb_keys(&mut self, new_bridgedb_pub: VerifyingKey) {
448 // Store the old verifying key until the next key rotation (this should happen no more often than the
449 // we would reasonably expect a user to redeem an open invitation token to ensure that all invitations
450 // issued with the old key can be updated)
451 self.old_keys.bridgedb_key.push(self.bridgedb_pub);
452 // Move the old lox id filter to the old_lox_id_filter
453 self.old_filters
454 .openinv_filter
455 .push(self.bridgedb_pub_filter.clone());
456 // TODO: Commit to the new keys and post the commitment somewhere public that can be verified
457 // by users, ideally
458 self.bridgedb_pub = new_bridgedb_pub;
459 self.bridgedb_pub_filter = Default::default();
460 }
461
462 /// Insert a set of open invitation bridges.
463 ///
464 /// Each of the bridges will be given its own open invitation
465 /// bucket, and the BridgeDb will be informed. A single bucket
466 /// containing all of the bridges will also be created, with a trust
467 /// upgrade migration from each of the single-bridge buckets.
468 pub fn add_openinv_bridges(
469 &mut self,
470 bridges: [BridgeLine; MAX_BRIDGES_PER_BUCKET],
471 bdb: &mut BridgeDb,
472 ) -> Result<(), NoAvailableIDError> {
473 let bindex = self.find_next_available_key(bdb)?;
474 self.bridge_table.new_bucket(bindex, &bridges);
475 let mut single = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET];
476 for b in bridges.iter() {
477 let sindex = self.find_next_available_key(bdb)?;
478 single[0] = *b;
479 self.bridge_table.new_bucket(sindex, &single);
480 self.bridge_table.open_inv_keys.push((sindex, self.today()));
481 bdb.insert_openinv(sindex);
482 self.trustup_migration_table.table.insert(sindex, bindex);
483 }
484 Ok(())
485 }
486
487 pub fn is_empty(&self) -> bool {
488 self.bridge_table.buckets.is_empty()
489 }
490
491 pub fn reachable_length(&self) -> usize {
492 self.bridge_table.reachable.len()
493 }
494
495 pub fn unallocated_length(&self) -> usize {
496 self.bridge_table.unallocated_bridges.len()
497 }
498
499 pub fn spares_length(&self) -> usize {
500 self.bridge_table.spares.len()
501 }
502
503 pub fn openinv_length(&self, bdb: &mut BridgeDb) -> usize {
504 bdb.openinv_length()
505 }
506
507 /// Insert a hot spare bucket of bridges
508 pub fn add_spare_bucket(
509 &mut self,
510 bucket: [BridgeLine; MAX_BRIDGES_PER_BUCKET],
511 bdb: &mut BridgeDb,
512 ) -> Result<(), NoAvailableIDError> {
513 let index = self.find_next_available_key(bdb)?;
514 self.bridge_table.new_bucket(index, &bucket);
515 self.bridge_table.spares.insert(index);
516 Ok(())
517 }
518
519 /// When syncing the Lox bridge table with rdsys, this function returns any bridges
520 /// that are found in the Lox bridge table that are not found in the Vector
521 /// of bridges received from rdsys through the Lox distributor.
522 pub fn find_and_remove_unaccounted_for_bridges(
523 &mut self,
524 accounted_for_bridges: Vec<u64>,
525 ) -> Vec<BridgeLine> {
526 let mut unaccounted_for: Vec<BridgeLine> = Vec::new();
527 for (k, _v) in self.bridge_table.reachable.clone() {
528 if !accounted_for_bridges.contains(&k.uid_fingerprint) {
529 unaccounted_for.push(k);
530 }
531 }
532 unaccounted_for
533 }
534
535 /// Allocate single left over bridges to an open invitation bucket
536 pub fn allocate_bridges(
537 &mut self,
538 distributor_bridges: &mut Vec<BridgeLine>,
539 bdb: &mut BridgeDb,
540 ) {
541 while let Some(bridge) = distributor_bridges.pop() {
542 self.bridge_table.unallocated_bridges.push(bridge);
543 }
544 while self.bridge_table.unallocated_bridges.len() >= MAX_BRIDGES_PER_BUCKET {
545 let mut bucket = [BridgeLine::default(); MAX_BRIDGES_PER_BUCKET];
546 for bridge in bucket.iter_mut() {
547 *bridge = self.bridge_table.unallocated_bridges.pop().unwrap();
548 }
549 match self.add_openinv_bridges(bucket, bdb) {
550 Ok(_) => continue,
551 Err(e) => {
552 println!("Error: {:?}", e);
553 for bridge in bucket {
554 self.bridge_table.unallocated_bridges.push(bridge);
555 }
556 }
557 }
558 }
559 }
560
561 // Update the details of a bridge in the bridge table. This assumes that the IP and Port
562 // of a given bridge remains the same and thus can be updated.
563 // First we must retrieve the list of reachable bridges, then we must search for any matching our partial key
564 // which will include the IP and Port. Finally we can replace the original bridge with the updated bridge.
565 // Returns true if the bridge has successfully updated
566 pub fn bridge_update(&mut self, bridge: &BridgeLine) -> bool {
567 let mut res: bool = false; //default False to assume that update failed
568 let reachable_bridges = self.bridge_table.reachable.clone();
569 for reachable_bridge in reachable_bridges {
570 if reachable_bridge.0.uid_fingerprint == bridge.uid_fingerprint {
571 // Now we must remove the old bridge from the table and insert the new bridge in its place
572 // i.e., in the same bucket and with the same permissions.
573 let positions = self.bridge_table.reachable.get(&reachable_bridge.0);
574 if let Some(v) = positions {
575 for (bucketnum, offset) in v.iter() {
576 let mut bridgelines = match self.bridge_table.buckets.get(bucketnum) {
577 Some(bridgelines) => *bridgelines,
578 None => return res,
579 };
580 assert!(bridgelines[*offset] == reachable_bridge.0);
581 bridgelines[*offset] = *bridge;
582 self.bridge_table.buckets.insert(*bucketnum, bridgelines);
583 /* #[cfg(feature = "blockage-detection")]
584 let (fingerprint_str, bucket) =
585 self.get_tp_bucket_and_fingerprint(bridge, bucketnum);
586 // Add bucket to existing entry or add new entry
587 #[cfg(feature = "blockage-detection")]
588 match self.tp_bridge_infos.get_mut(&fingerprint_str) {
589 Some(info) => {
590 info.buckets.insert(bucket);
591 }
592 None => {
593 let mut buckets = HashSet::<Scalar>::new();
594 buckets.insert(bucket);
595 self.tp_bridge_infos.insert(
596 fingerprint_str,
597 BridgeVerificationInfo {
598 bridge_line: *bridge,
599 buckets,
600 },
601 );
602 }
603 }; */
604 if !self.bridge_table.buckets.contains_key(bucketnum) {
605 return res;
606 }
607 }
608 res = true;
609 } else {
610 return res;
611 }
612 // We must also remove the old bridge from the reachable bridges table
613 // and add the new bridge
614 self.bridge_table.reachable.remove(&reachable_bridge.0);
615 self.bridge_table
616 .reachable
617 .insert(*bridge, reachable_bridge.1);
618 return res;
619 }
620 }
621 // Also check the unallocated bridges just in case there is a bridge that should be updated there
622 let unallocated_bridges = self.bridge_table.unallocated_bridges.clone();
623 for (i, unallocated_bridge) in unallocated_bridges.iter().enumerate() {
624 if unallocated_bridge.uid_fingerprint == bridge.uid_fingerprint {
625 // Now we must remove the old bridge from the unallocated bridges and insert the new bridge
626 // in its place
627 self.bridge_table.unallocated_bridges.remove(i);
628 self.bridge_table.unallocated_bridges.push(*bridge);
629 res = true;
630 }
631 }
632 // If this is returned, we assume that the bridge wasn't found in the bridge table
633 // and therefore should be treated as a "new bridge"
634 res
635 }
636
637 // Repurpose a bucket of spares into unallocated bridges
638 pub fn dissolve_spare_bucket(&mut self, key: u32) -> Result<(), BridgeTableError> {
639 self.bridge_table.spares.remove(&key);
640 // Get the actual bridges from the spare bucket
641 let spare_bucket = self
642 .bridge_table
643 .buckets
644 .remove(&key)
645 .ok_or(BridgeTableError::MissingBucket(key))?;
646 for bridge in spare_bucket.iter() {
647 self.bridge_table.unallocated_bridges.push(*bridge);
648 // Mark bucket as unreachable while it is unallocated
649 self.bridge_table.reachable.remove(bridge);
650 }
651 self.bridge_table.keys.remove(&key);
652 self.bridge_table.recycleable_keys.push(key);
653 Ok(())
654 }
655
656 // Removes an unallocated bridge and returns it if it was present
657 pub fn remove_unallocated(&mut self, bridge: &BridgeLine) -> Option<BridgeLine> {
658 // #[cfg(feature = "blockage-detection")]
659 // let fingerprint_str = self.fingerprint_hasher(bridge.unhashed_fingerprint);
660 match self
661 .bridge_table
662 .unallocated_bridges
663 .iter()
664 .position(|x| x == bridge)
665 {
666 Some(index) => Some({
667 // #[cfg(feature = "blockage-detection")]
668 // self.tp_bridge_infos.remove_entry(&fingerprint_str);
669 self.bridge_table.unallocated_bridges.swap_remove(index)
670 }),
671 None => None,
672 }
673 }
674
675 /// Attempt to remove a bridge that is failing tests and replace it with a bridge from
676 /// available_bridge or from a spare bucket
677 pub fn bridge_replace(
678 &mut self,
679 bridge: &BridgeLine,
680 available_bridge: Option<BridgeLine>,
681 ) -> ReplaceSuccess {
682 let reachable_bridges = &self.bridge_table.reachable.clone();
683 let Some(positions) = reachable_bridges.get(bridge) else {
684 match self.remove_unallocated(bridge) {
685 Some(_) => {
686 return ReplaceSuccess::Removed;
687 }
688 None => {
689 return ReplaceSuccess::NotFound;
690 }
691 }
692 };
693 // Check if the bridge is in a spare bucket first, if it is, dissolve the bucket
694 if let Some(spare) = self
695 .bridge_table
696 .spares
697 .iter()
698 .find(|x| positions.iter().any(|(bucketnum, _)| &bucketnum == x))
699 .cloned()
700 {
701 let Ok(_) = self.dissolve_spare_bucket(spare) else {
702 return ReplaceSuccess::NotReplaced;
703 };
704 // Next Check if the bridge is in the unallocated bridges and remove the bridge if so
705 // Bridges in spare buckets should have been moved to the unallocated bridges
706 match self.remove_unallocated(bridge) {
707 Some(_) => {
708 return ReplaceSuccess::Removed;
709 }
710 None => {
711 return ReplaceSuccess::NotFound;
712 }
713 }
714 }
715 // select replacement:
716 // - first try the given bridge
717 // - second try to pick one from the set of available bridges
718 // - third dissolve a spare bucket to create more available bridges
719 let Some(replacement) = available_bridge.or_else(|| {
720 self.bridge_table.unallocated_bridges.pop().or_else(|| {
721 let spare = self
722 .bridge_table
723 .spares
724 .iter()
725 // in case bridge is a spare, avoid replacing it with itself
726 .find(|x| !positions.iter().any(|(bucketnum, _)| &bucketnum == x))
727 .cloned()?;
728 let Ok(_) = self.dissolve_spare_bucket(spare) else {
729 return None;
730 };
731 self.bridge_table.unallocated_bridges.pop()
732 })
733 }) else {
734 // If there are no available bridges that can be assigned here, the only thing
735 // that can be done is return an indication that updating the gone bridge
736 // didn't work.
737 // In this case, we do not mark the bridge as unreachable or remove it from the
738 // reachable bridges so that we can still find it when a new bridge does become available
739 println!("No available bridges");
740 return ReplaceSuccess::NotReplaced;
741 };
742 for (bucketnum, offset) in positions.iter() {
743 let mut bridgelines = match self.bridge_table.buckets.get(bucketnum) {
744 Some(bridgelines) => *bridgelines,
745 None => return ReplaceSuccess::NotFound,
746 };
747 assert!(bridgelines[*offset] == *bridge);
748 bridgelines[*offset] = replacement;
749 self.bridge_table.buckets.insert(*bucketnum, bridgelines);
750 // Remove the bridge from the reachable bridges and add new bridge
751 self.bridge_table
752 .reachable
753 .insert(replacement, positions.clone());
754 // Remove the bridge from the bucket
755 self.bridge_table.reachable.remove(bridge);
756 }
757 ReplaceSuccess::Replaced
758 }
759
760 /* pub fn get_bridge_verification_info(
761 &mut self,
762 bridge_str: &String,
763 ) -> Option<&BridgeVerificationInfo> {
764 self.tp_bridge_infos.get(bridge_str)
765 }
766
767 // Remove Bridge Info for blocked bridge and return the bridgeline with the given fingerprint
768 pub fn block_by_string(&mut self, bridge_str: &String) -> Option<BridgeLine> {
769 if let Some(bridge_verification_info) = self.tp_bridge_infos.remove(bridge_str) {
770 return Some(bridge_verification_info.bridge_line);
771 }
772 None
773 }
774 */
775
776 /// Mark a bridge as blocked
777 ///
778 /// This bridge will be removed from each of the buckets that
779 /// contains it. If any of those are open-invitation buckets, the
780 /// trust upgrade migration for that bucket will be removed and the
781 /// BridgeDb will be informed to stop handing out that bridge. If
782 /// any of those are trusted buckets where the number of reachable
783 /// bridges has fallen below the threshold, a blockage migration
784 /// from that bucket to a spare bucket will be added, and the spare
785 /// bucket will be removed from the list of hot spares. In
786 /// addition, if the blocked bucket was the _target_ of a blockage
787 /// migration, change the target to the new (formerly spare) bucket.
788 /// Returns true if sucessful, or false if it needed a hot spare but
789 /// there was none available.
790 pub fn bridge_blocked(&mut self, bridge: &BridgeLine, bdb: &mut BridgeDb) -> bool {
791 let mut res: bool = true;
792 if self.remove_unallocated(bridge).is_some() {
793 return true;
794 }
795 if let Some(positions) = self.bridge_table.reachable.get(bridge) {
796 for (bucketnum, offset) in positions.iter() {
797 // Count how many bridges in this bucket are reachable
798 let mut bucket = match self.bridge_table.buckets.get(bucketnum) {
799 Some(bridgelines) => *bridgelines,
800 None => return false, // This should not happen
801 };
802 // Remove the bridge from the bucket
803 assert!(bucket[*offset] == *bridge);
804 bucket[*offset] = BridgeLine::default();
805
806 // If this is an open invitation bucket, there is only one bridge, remove bucket
807 if bdb.openinv_buckets.contains(bucketnum)
808 || bdb.distributed_buckets.contains(bucketnum)
809 {
810 bdb.remove_blocked_or_expired_buckets(bucketnum);
811 self.trustup_migration_table.table.remove(bucketnum);
812 continue;
813 }
814
815 // If this bucket still has an acceptable number of bridges, continue
816 let numreachable = bucket
817 .iter()
818 .filter(|br| self.bridge_table.reachable.contains_key(br))
819 .count();
820 if numreachable != MIN_BUCKET_REACHABILITY {
821 // No
822 continue;
823 }
824
825 // Remove any trust upgrade migrations to this bucket
826 self.trustup_migration_table
827 .table
828 .retain(|_, &mut v| v != *bucketnum);
829
830 // If there are no spares, delete blockage migrations leading to this bucket
831 if self.bridge_table.spares.is_empty() {
832 res = false;
833 self.blockage_migration_table
834 .table
835 .retain(|_, &mut v| v != *bucketnum);
836 continue;
837 }
838 // Get the first spare and remove it from the spares
839 // set.
840 let spare = *self.bridge_table.spares.iter().next().unwrap();
841 self.bridge_table.spares.remove(&spare);
842 self.bridge_table
843 .blocked_keys
844 .push((*bucketnum, self.today()));
845 // Add a blockage migration from this bucket to the spare
846 self.blockage_migration_table
847 .table
848 .insert(*bucketnum, spare);
849 // Change any blockage migrations with this bucket
850 // as the destination to the spare
851 for (_, v) in self.blockage_migration_table.table.iter_mut() {
852 if *v == *bucketnum {
853 *v = spare;
854 }
855 }
856 }
857 }
858 self.bridge_table.reachable.remove(bridge);
859
860 res
861 }
862
863 // Since buckets are moved around in the bridge_table, finding a lookup key that
864 // does not overwrite existing bridges could become an issue.We keep a list
865 // of recycleable lookup keys from buckets that have been removed and prioritize
866 // this list before increasing the counter
867 fn find_next_available_key(&mut self, bdb: &mut BridgeDb) -> Result<u32, NoAvailableIDError> {
868 self.clean_up_expired_buckets(bdb);
869 if self.bridge_table.recycleable_keys.is_empty() {
870 let mut test_index = 1;
871 let mut test_counter = self.bridge_table.counter.wrapping_add(test_index);
872 let mut i = 0;
873 while self.bridge_table.buckets.contains_key(&test_counter) && i < 5000 {
874 test_index += 1;
875 test_counter = self.bridge_table.counter.wrapping_add(test_index);
876 i += 1;
877 if i == 5000 {
878 return Err(NoAvailableIDError::ExhaustedIndexer);
879 }
880 }
881 self.bridge_table.counter = self.bridge_table.counter.wrapping_add(test_index);
882 Ok(self.bridge_table.counter)
883 } else {
884 Ok(self.bridge_table.recycleable_keys.pop().unwrap())
885 }
886 }
887
888 // This function looks for and removes buckets so their indexes can be reused
889 // This should include buckets that have been blocked for a sufficiently long period
890 // that we no longer want to allow migration to, or else, open-entry buckets that
891 // have been unblocked long enough to become trusted and who's users' credentials
892 // would have expired (after EXPIRY_DATE)
893 pub fn clean_up_expired_buckets(&mut self, bdb: &mut BridgeDb) {
894 // First check if there are any blocked indexes that are old enough to be replaced
895 self.clean_up_blocked();
896 // Next do the same for open_invitations buckets
897 self.clean_up_open_entry(bdb);
898 }
899
900 /// Cleans up exipred blocked buckets
901 fn clean_up_blocked(&mut self) {
902 // If there are expired blockages, separate them from the fresh blockages
903 #[allow(clippy::type_complexity)]
904 let (expired, fresh): (Vec<(u32, u32)>, Vec<(u32, u32)>) = self
905 .bridge_table
906 .blocked_keys
907 .iter()
908 .partition(|&x| x.1 + EXPIRY_DATE < self.today());
909 for item in expired {
910 let key = item.0;
911 // check each single bridge line and ensure none are still marked as reachable.
912 // if any are still reachable, remove from reachable bridges.
913 // When syncing resources, we will likely have to reallocate this bridge but if it hasn't already been
914 // blocked, this might be fine?
915 let bridgelines = self.bridge_table.buckets.get(&key).unwrap();
916 for bridgeline in bridgelines {
917 // If the bridge hasn't been set to default, assume it's still reachable
918 if bridgeline.port > 0 {
919 // Move to unallocated bridges
920 self.bridge_table.unallocated_bridges.push(*bridgeline);
921 // Make sure bridge is removed from reachable bridges
922 self.bridge_table.reachable.remove(bridgeline);
923 }
924 }
925 // Then remove the bucket and keys at the specified index
926 self.bridge_table.buckets.remove(&key);
927 self.bridge_table.keys.remove(&key);
928 //and add them to the recyclable keys
929 self.bridge_table.recycleable_keys.push(key);
930 // Remove the expired blocked bucket from the blockage migration table,
931 // assuming that anyone that has still not attempted to migrate from their
932 // blocked bridge after the EXPIRY_DATE probably doesn't still need to migrate.
933 self.blockage_migration_table.table.retain(|&k, _| k != key);
934 }
935 // Finally, update the blocked_keys vector to only include the fresh keys
936 self.bridge_table.blocked_keys = fresh
937 }
938
939 /// Cleans up expired open invitation buckets
940 fn clean_up_open_entry(&mut self, bdb: &mut BridgeDb) {
941 // Separate exipred from fresh open invitation indexes
942 #[allow(clippy::type_complexity)]
943 let (expired, fresh): (Vec<(u32, u32)>, Vec<(u32, u32)>) = self
944 .bridge_table
945 .open_inv_keys
946 .iter()
947 .partition(|&x| x.1 + EXPIRY_DATE < self.today());
948 for item in expired {
949 let key = item.0;
950 // We should check that the items were actually distributed before they are removed
951 if !bdb.distributed_buckets.contains(&key) {
952 // TODO: Add prometheus metric for this?
953 println!("This bucket was not actually distributed!");
954 }
955 bdb.remove_blocked_or_expired_buckets(&key);
956 // Remove any trust upgrade migrations from this
957 // bucket
958 self.trustup_migration_table.table.retain(|&k, _| k != key);
959 self.bridge_table.buckets.remove(&key);
960 self.bridge_table.keys.remove(&key);
961 //and add them to the recyclable keys
962 self.bridge_table.recycleable_keys.push(key);
963 }
964 // update the open_inv_keys vector to only include the fresh keys
965 self.bridge_table.open_inv_keys = fresh
966 }
967
968 /// Get today's (real or simulated) date as u32
969 pub fn today(&self) -> u32 {
970 // We will not encounter negative Julian dates (~6700 years ago)
971 // or ones larger than 32 bits
972 (time::OffsetDateTime::now_utc().date() + self.time_offset)
973 .to_julian_day()
974 .try_into()
975 .unwrap()
976 }
977
978 /// Get today's (real or simulated) date as a DateTime<Utc> value
979 pub fn today_date(&self) -> DateTime<Utc> {
980 Utc::now()
981 }
982
983 /// Get a reference to the encrypted bridge table.
984 ///
985 /// Be sure to call this function when you want the latest version
986 /// of the table, since it will put fresh Bucket Reachability
987 /// credentials in the buckets each day.
988 pub fn enc_bridge_table(&mut self) -> &HashMap<u32, EncryptedBucket> {
989 let today = self.today();
990 if self.bridge_table.date_last_enc != today {
991 self.bridge_table
992 .encrypt_table(today, &self.reachability_priv);
993 }
994 &self.bridge_table.encbuckets
995 }
996
997 // For testing only: manually advance the day by the given number
998 // of days
999 #[cfg(feature = "test")]
1000 pub fn advance_days(&mut self, days: u16) {
1001 self.time_offset += time::Duration::days(days.into());
1002 }
1003}
1004
1005pub fn scalar_u32(s: &Scalar) -> Option<u32> {
1006 // Check that the top 28 bytes of the Scalar are 0
1007 let sbytes: &[u8; 32] = s.as_bytes();
1008 if sbytes[4..].ct_eq(&[0u8; 28]).unwrap_u8() == 0 {
1009 return None;
1010 }
1011 Some(u32::from_le_bytes(sbytes[..4].try_into().unwrap()))
1012}