Add a MAX_DAILY_BRIDGES to limit invites per day
This commit is contained in:
parent
2f1c48c0ac
commit
ba70b1b4d4
|
@ -1074,6 +1074,7 @@ dependencies = [
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"hex_fmt",
|
"hex_fmt",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"prometheus",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
@ -1458,6 +1459,27 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "prometheus"
|
||||||
|
version = "0.13.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"fnv",
|
||||||
|
"lazy_static",
|
||||||
|
"memchr",
|
||||||
|
"parking_lot 0.12.1",
|
||||||
|
"protobuf",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "protobuf"
|
||||||
|
version = "2.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.32"
|
version = "1.0.32"
|
||||||
|
|
|
@ -123,12 +123,13 @@ fn use_last_context(lox_db: sled::Db) -> lox_context::LoxServerContext {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::lox_context::LoxServerContext;
|
use super::lox_context::LoxServerContext;
|
||||||
use super::DB;
|
|
||||||
use super::DbConfig;
|
use super::DbConfig;
|
||||||
|
use super::DB;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_write_context() {
|
fn test_write_context() {
|
||||||
let (mut lox_db, _context) = DB::open_new_or_existing_db(DbConfig::default(), None).unwrap();
|
let (mut lox_db, _context) =
|
||||||
|
DB::open_new_or_existing_db(DbConfig::default(), None).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
lox_db.db.is_empty(),
|
lox_db.db.is_empty(),
|
||||||
"db read from context that shouldn't exist"
|
"db read from context that shouldn't exist"
|
||||||
|
|
|
@ -6,7 +6,7 @@ use lox_library::{
|
||||||
blockage_migration, check_blockage, issue_invite, level_up, migration, open_invite,
|
blockage_migration, check_blockage, issue_invite, level_up, migration, open_invite,
|
||||||
redeem_invite, trust_promotion,
|
redeem_invite, trust_promotion,
|
||||||
},
|
},
|
||||||
BridgeAuth, BridgeDb, IssuerPubKey,
|
BridgeAuth, BridgeDb, ExceededMaxBridgesError, IssuerPubKey,
|
||||||
};
|
};
|
||||||
use rdsys_backend::proto::{Resource, ResourceState};
|
use rdsys_backend::proto::{Resource, ResourceState};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -317,10 +317,11 @@ impl LoxServerContext {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn gen_invite(&self) -> lox_utils::Invite {
|
fn gen_invite(&self) -> Result<lox_utils::Invite, ExceededMaxBridgesError> {
|
||||||
let mut obj = self.db.lock().unwrap();
|
let mut obj = self.db.lock().unwrap();
|
||||||
lox_utils::Invite {
|
match obj.invite() {
|
||||||
invite: obj.invite(),
|
Ok(invite) => Ok(lox_utils::Invite { invite }),
|
||||||
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,8 +383,14 @@ impl LoxServerContext {
|
||||||
// Generate and return an open invitation token
|
// Generate and return an open invitation token
|
||||||
pub fn generate_invite(self) -> Response<Body> {
|
pub fn generate_invite(self) -> Response<Body> {
|
||||||
let invite = self.gen_invite();
|
let invite = self.gen_invite();
|
||||||
match serde_json::to_string(&invite) {
|
match invite {
|
||||||
Ok(resp) => prepare_header(resp),
|
Ok(invite) => match serde_json::to_string(&invite) {
|
||||||
|
Ok(resp) => prepare_header(resp),
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error parsing Invite to JSON");
|
||||||
|
prepare_error_header(e.to_string())
|
||||||
|
}
|
||||||
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!("Error parsing Invite to JSON");
|
println!("Error parsing Invite to JSON");
|
||||||
prepare_error_header(e.to_string())
|
prepare_error_header(e.to_string())
|
||||||
|
|
|
@ -23,6 +23,7 @@ hex_fmt = "0.3"
|
||||||
aes-gcm = { version = "0.10", features =["aes"]}
|
aes-gcm = { version = "0.10", features =["aes"]}
|
||||||
base64 = "0.21"
|
base64 = "0.21"
|
||||||
time = "0.3.30"
|
time = "0.3.30"
|
||||||
|
prometheus = "0.13.3"
|
||||||
subtle = "2.5"
|
subtle = "2.5"
|
||||||
thiserror = "1.0.49"
|
thiserror = "1.0.49"
|
||||||
zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp" }
|
zkp = { git = "https://gitlab.torproject.org/onyinyang/lox-zkp" }
|
||||||
|
|
|
@ -22,6 +22,7 @@ pub mod cred;
|
||||||
pub mod dup_filter;
|
pub mod dup_filter;
|
||||||
pub mod migration_table;
|
pub mod migration_table;
|
||||||
|
|
||||||
|
use chrono::Duration;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use sha2::Sha512;
|
use sha2::Sha512;
|
||||||
|
|
||||||
|
@ -78,6 +79,12 @@ pub enum NoAvailableIDError {
|
||||||
ExhaustedIndexer,
|
ExhaustedIndexer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ExceededMaxBridgesError {
|
||||||
|
#[error("The maximum number of bridges has already been distributed today, please try again tomorrow!")]
|
||||||
|
ExceededMaxBridges,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct IssuerPrivKey {
|
pub struct IssuerPrivKey {
|
||||||
x0tilde: Scalar,
|
x0tilde: Scalar,
|
||||||
|
@ -123,7 +130,10 @@ impl IssuerPubKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Number of times a given invitation is ditributed
|
||||||
pub const OPENINV_K: u32 = 10;
|
pub const OPENINV_K: u32 = 10;
|
||||||
|
// TODO: Decide on maximum daily number of invitations to be distributed
|
||||||
|
pub const MAX_DAILY_BRIDGES: u32 = 100;
|
||||||
/// The BridgeDb. This will typically be a singleton object. The
|
/// The BridgeDb. This will typically be a singleton object. The
|
||||||
/// BridgeDb's role is simply to issue signed "open invitations" to
|
/// BridgeDb's role is simply to issue signed "open invitations" to
|
||||||
/// people who are not yet part of the system.
|
/// people who are not yet part of the system.
|
||||||
|
@ -136,7 +146,10 @@ pub struct BridgeDb {
|
||||||
/// The set of open-invitation buckets
|
/// The set of open-invitation buckets
|
||||||
openinv_buckets: HashSet<u32>,
|
openinv_buckets: HashSet<u32>,
|
||||||
distributed_buckets: Vec<u32>,
|
distributed_buckets: Vec<u32>,
|
||||||
current_k: u32,
|
#[serde(skip)]
|
||||||
|
today: DateTime<Utc>,
|
||||||
|
pub current_k: u32,
|
||||||
|
pub daily_bridges_distributed: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An open invitation is a [u8; OPENINV_LENGTH] where the first 32
|
/// An open invitation is a [u8; OPENINV_LENGTH] where the first 32
|
||||||
|
@ -159,7 +172,9 @@ impl BridgeDb {
|
||||||
pubkey,
|
pubkey,
|
||||||
openinv_buckets: Default::default(),
|
openinv_buckets: Default::default(),
|
||||||
distributed_buckets: Default::default(),
|
distributed_buckets: Default::default(),
|
||||||
|
today: Utc::now(),
|
||||||
current_k: 0,
|
current_k: 0,
|
||||||
|
daily_bridges_distributed: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,30 +204,39 @@ impl BridgeDb {
|
||||||
/// Produce an open invitation such that the next k users, where k is <
|
/// Produce an open invitation such that the next k users, where k is <
|
||||||
/// OPENINV_K, will receive the same open invitation bucket
|
/// OPENINV_K, will receive the same open invitation bucket
|
||||||
/// chosen randomly from the set of open-invitation buckets.
|
/// chosen randomly from the set of open-invitation buckets.
|
||||||
pub fn invite(&mut self) -> [u8; OPENINV_LENGTH] {
|
pub fn invite(&mut self) -> Result<[u8; OPENINV_LENGTH], ExceededMaxBridgesError> {
|
||||||
let mut res: [u8; OPENINV_LENGTH] = [0; OPENINV_LENGTH];
|
let mut res: [u8; OPENINV_LENGTH] = [0; OPENINV_LENGTH];
|
||||||
let mut rng = rand::thread_rng();
|
let mut rng = rand::thread_rng();
|
||||||
// Choose a random invitation id (a Scalar) and serialize it
|
// Choose a random invitation id (a Scalar) and serialize it
|
||||||
let id = Scalar::random(&mut rng);
|
let id = Scalar::random(&mut rng);
|
||||||
res[0..32].copy_from_slice(&id.to_bytes());
|
res[0..32].copy_from_slice(&id.to_bytes());
|
||||||
let bucket_num: u32;
|
let bucket_num: u32;
|
||||||
if self.current_k < OPENINV_K && !self.distributed_buckets.is_empty() {
|
if Utc::now() >= (self.today + Duration::days(1)) {
|
||||||
bucket_num = *self.distributed_buckets.last().unwrap();
|
self.today = Utc::now();
|
||||||
self.current_k += 1;
|
self.daily_bridges_distributed = 0;
|
||||||
} else {
|
}
|
||||||
// Choose a random bucket number (from the set of open
|
if self.daily_bridges_distributed < MAX_DAILY_BRIDGES {
|
||||||
// invitation buckets) and serialize it
|
if self.current_k < OPENINV_K && !self.distributed_buckets.is_empty() {
|
||||||
let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect();
|
bucket_num = *self.distributed_buckets.last().unwrap();
|
||||||
bucket_num = *openinv_vec[rng.gen_range(0..openinv_vec.len())];
|
self.current_k += 1;
|
||||||
self.mark_distributed(bucket_num);
|
} else {
|
||||||
self.remove_openinv(&bucket_num);
|
// Choose a random bucket number (from the set of open
|
||||||
self.current_k = 1;
|
// invitation buckets) and serialize it
|
||||||
|
let openinv_vec: Vec<&u32> = self.openinv_buckets.iter().collect();
|
||||||
|
bucket_num = *openinv_vec[rng.gen_range(0..openinv_vec.len())];
|
||||||
|
self.mark_distributed(bucket_num);
|
||||||
|
self.remove_openinv(&bucket_num);
|
||||||
|
self.current_k = 1;
|
||||||
|
self.daily_bridges_distributed += 1;
|
||||||
|
}
|
||||||
|
res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes());
|
||||||
|
// Sign the first 36 bytes and serialize it
|
||||||
|
let sig = self.keypair.sign(&res[0..(32 + 4)]);
|
||||||
|
res[(32 + 4)..].copy_from_slice(&sig.to_bytes());
|
||||||
|
Ok(res)
|
||||||
|
} else {
|
||||||
|
Err(ExceededMaxBridgesError::ExceededMaxBridges)
|
||||||
}
|
}
|
||||||
res[32..(32 + 4)].copy_from_slice(&bucket_num.to_le_bytes());
|
|
||||||
// Sign the first 36 bytes and serialize it
|
|
||||||
let sig = self.keypair.sign(&res[0..(32 + 4)]);
|
|
||||||
res[(32 + 4)..].copy_from_slice(&sig.to_bytes());
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify an open invitation. Returns the invitation id and the
|
/// Verify an open invitation. Returns the invitation id and the
|
||||||
|
|
|
@ -64,7 +64,7 @@ impl TestHarness {
|
||||||
|
|
||||||
fn open_invite(&mut self) -> (PerfStat, (cred::Lox, bridge_table::BridgeLine)) {
|
fn open_invite(&mut self) -> (PerfStat, (cred::Lox, bridge_table::BridgeLine)) {
|
||||||
// Issue an open invitation
|
// Issue an open invitation
|
||||||
let inv = self.bdb.invite();
|
let inv = self.bdb.invite().unwrap();
|
||||||
|
|
||||||
let req_start = Instant::now();
|
let req_start = Instant::now();
|
||||||
// Use it to get a Lox credential
|
// Use it to get a Lox credential
|
||||||
|
|
|
@ -10,7 +10,7 @@ fn test_bridgedb() {
|
||||||
for i in &[1u32, 5, 7, 12, 19, 20, 22] {
|
for i in &[1u32, 5, 7, 12, 19, 20, 22] {
|
||||||
bdb.insert_openinv(*i);
|
bdb.insert_openinv(*i);
|
||||||
}
|
}
|
||||||
let inv = bdb.invite();
|
let inv = bdb.invite().unwrap();
|
||||||
println!("{:?}", inv);
|
println!("{:?}", inv);
|
||||||
let res = BridgeDb::verify(inv, bdb.pubkey);
|
let res = BridgeDb::verify(inv, bdb.pubkey);
|
||||||
println!("{:?}", res);
|
println!("{:?}", res);
|
||||||
|
|
Loading…
Reference in New Issue