Skip to main content

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}