2023-11-28 13:18:08 -05:00
|
|
|
use curve25519_dalek::scalar::Scalar;
|
|
|
|
use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey};
|
2023-11-28 17:56:49 -05:00
|
|
|
use lox_library::bridge_table::{BridgeLine, MAX_BRIDGES_PER_BUCKET};
|
2023-12-05 19:55:33 -05:00
|
|
|
use lox_library::cred::Lox;
|
2023-11-28 13:18:08 -05:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use sha1::{Digest, Sha1};
|
|
|
|
use sha3::Sha3_256;
|
|
|
|
|
|
|
|
// for generating ed25519 keys during initial development
|
|
|
|
use rand::rngs::OsRng;
|
|
|
|
|
2023-12-05 18:05:44 -05:00
|
|
|
// TODO: These should be loaded from config file
|
|
|
|
pub const REQUIRE_BRIDGE_TOKEN: bool = true;
|
|
|
|
|
2023-11-28 13:18:08 -05:00
|
|
|
/// Get Julian date
|
|
|
|
pub fn today() -> u32 {
|
|
|
|
time::OffsetDateTime::now_utc()
|
|
|
|
.date()
|
|
|
|
.to_julian_day()
|
|
|
|
.try_into()
|
|
|
|
.unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get bridge line for a bridge, requires some oracle
|
|
|
|
pub fn get_bridge_line(fingerprint: &[u8; 20]) -> BridgeLine {
|
|
|
|
// TODO
|
|
|
|
// for now just return empty bridgeline
|
|
|
|
BridgeLine::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get verifying key for a bridge, requires some oracle
|
|
|
|
pub fn get_bridge_signing_pubkey(fingerprint: &[u8; 20]) -> VerifyingKey {
|
|
|
|
// TODO
|
|
|
|
// for now just return new pubkey
|
|
|
|
let mut csprng = OsRng {};
|
|
|
|
let keypair = SigningKey::generate(&mut csprng);
|
|
|
|
keypair.verifying_key()
|
|
|
|
}
|
|
|
|
|
2023-11-28 17:56:49 -05:00
|
|
|
/// Get bucket from hash of bucket ID, requires some oracle
|
|
|
|
pub fn get_bucket(beta_hash: &[u8; 32]) -> [BridgeLine; MAX_BRIDGES_PER_BUCKET] {
|
|
|
|
// TODO
|
|
|
|
// for now just return bucket of empty bridgelines
|
|
|
|
[
|
|
|
|
BridgeLine::default(),
|
|
|
|
BridgeLine::default(),
|
|
|
|
BridgeLine::default(),
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2023-11-28 13:18:08 -05:00
|
|
|
/// Proof that the user knows (and should be able to access) a given bridge
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub enum ProofOfBridgeKnowledge {
|
|
|
|
/// Hash of bridge line as proof of knowledge of bridge line
|
2023-12-05 19:55:33 -05:00
|
|
|
HashOfBridgeLine(HashOfBridgeLine),
|
2023-11-28 17:56:49 -05:00
|
|
|
/// Hash of bucket ID for Lox user
|
2023-12-05 19:55:33 -05:00
|
|
|
HashOfBucket(HashOfBucket),
|
2023-11-28 13:18:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ProofOfBridgeKnowledge {
|
2023-12-05 19:55:33 -05:00
|
|
|
pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool {
|
|
|
|
// TODO: It seems like there ought to be a cleaner way to do this?
|
|
|
|
match self {
|
|
|
|
ProofOfBridgeKnowledge::HashOfBridgeLine(bl_hash) => bl_hash.verify(bridge_fingerprint),
|
|
|
|
ProofOfBridgeKnowledge::HashOfBucket(bucket_hash) => {
|
|
|
|
bucket_hash.verify(bridge_fingerprint)
|
2023-11-28 13:18:08 -05:00
|
|
|
}
|
2023-12-05 19:55:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Hash of bridge line to prove knowledge of that bridge
|
|
|
|
#[derive(PartialEq, Serialize, Deserialize)]
|
|
|
|
pub struct HashOfBridgeLine {
|
|
|
|
hash: [u8; 32],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HashOfBridgeLine {
|
|
|
|
pub fn new(bl: BridgeLine) -> Self {
|
|
|
|
let mut hasher = Sha3_256::new();
|
|
|
|
hasher.update(bincode::serialize(&bl).unwrap());
|
|
|
|
let hash: [u8; 32] = hasher.finalize().into();
|
|
|
|
Self { hash }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool {
|
|
|
|
let bl = get_bridge_line(&bridge_fingerprint);
|
|
|
|
self == &HashOfBridgeLine::new(bl)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Hash of bucket ID to prove knowledge of bridges in that bucket
|
|
|
|
#[derive(PartialEq, Serialize, Deserialize)]
|
|
|
|
pub struct HashOfBucket {
|
|
|
|
hash: [u8; 32],
|
|
|
|
}
|
|
|
|
|
|
|
|
impl HashOfBucket {
|
|
|
|
pub fn new(bucket: Scalar) -> Self {
|
|
|
|
let mut hasher = Sha3_256::new();
|
|
|
|
hasher.update(bucket.to_bytes());
|
|
|
|
let hash: [u8; 32] = hasher.finalize().into();
|
|
|
|
Self { hash }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn verify(&self, bridge_fingerprint: [u8; 20]) -> bool {
|
|
|
|
let bucket = get_bucket(&self.hash);
|
|
|
|
for bl in bucket {
|
|
|
|
let mut hasher = Sha1::new();
|
|
|
|
hasher.update(bl.uid_fingerprint.to_le_bytes());
|
|
|
|
if bridge_fingerprint == <[u8; 20]>::from(hasher.finalize()) {
|
|
|
|
return true;
|
2023-11-28 17:56:49 -05:00
|
|
|
}
|
2023-11-28 13:18:08 -05:00
|
|
|
}
|
2023-12-05 19:55:33 -05:00
|
|
|
false
|
2023-11-28 13:18:08 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-05 19:55:33 -05:00
|
|
|
/// Reports from users about whether or not their bridges are blocked
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub enum Report {
|
|
|
|
/// Negative report indicating user was unable to connect
|
|
|
|
NegativeUserReport(NegativeUserReport),
|
|
|
|
/// Positive report indicating user was able to connect
|
|
|
|
PositiveUserReport(PositiveUserReport),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Report {
|
|
|
|
fn verify(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
Report::NegativeUserReport(report) => report.verify(),
|
|
|
|
Report::PositiveUserReport(report) => report.verify(),
|
|
|
|
}
|
|
|
|
}
|
2023-11-28 13:18:08 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
/// A report that the user was unable to connect to the bridge
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct NegativeUserReport {
|
|
|
|
/// hashed fingerprint (SHA-1 hash of 20-byte bridge ID)
|
|
|
|
pub fingerprint: [u8; 20],
|
|
|
|
/// some way to prove knowledge of bridge
|
2023-12-05 19:55:33 -05:00
|
|
|
bridge_pok: ProofOfBridgeKnowledge,
|
2023-11-28 13:18:08 -05:00
|
|
|
/// user's country code, may be an empty string
|
|
|
|
pub country: String,
|
|
|
|
/// today's Julian date
|
|
|
|
pub today: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NegativeUserReport {
|
2023-12-05 19:55:33 -05:00
|
|
|
pub fn new(bridge_id: [u8; 20], bridge_pok: ProofOfBridgeKnowledge, country: String) -> Self {
|
2023-11-28 13:18:08 -05:00
|
|
|
let mut hasher = Sha1::new();
|
|
|
|
hasher.update(bridge_id);
|
|
|
|
let fingerprint: [u8; 20] = hasher.finalize().into();
|
|
|
|
let today = today();
|
|
|
|
Self {
|
|
|
|
fingerprint,
|
|
|
|
bridge_pok,
|
|
|
|
country,
|
|
|
|
today,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-05 19:55:33 -05:00
|
|
|
pub fn from_bridgeline(
|
|
|
|
&self,
|
|
|
|
bridge_id: [u8; 20],
|
|
|
|
bridgeline: BridgeLine,
|
|
|
|
country: String,
|
|
|
|
) -> Self {
|
|
|
|
let bridge_pok =
|
|
|
|
ProofOfBridgeKnowledge::HashOfBridgeLine(HashOfBridgeLine::new(bridgeline));
|
|
|
|
NegativeUserReport::new(bridge_id, bridge_pok, country)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_bucket(&self, bridge_id: [u8; 20], bucket: Scalar, country: String) -> Self {
|
|
|
|
let mut hasher = Sha3_256::new();
|
|
|
|
hasher.update(bucket.to_bytes());
|
|
|
|
let bucket_hash: [u8; 32] = hasher.finalize().into();
|
|
|
|
let bridge_pok = ProofOfBridgeKnowledge::HashOfBucket(HashOfBucket { hash: bucket_hash });
|
|
|
|
NegativeUserReport::new(bridge_id, bridge_pok, country)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn from_lox_credential(&self, bridge_id: [u8; 20], cred: Lox, country: String) -> Self {
|
|
|
|
self.from_bucket(bridge_id, cred.bucket, country)
|
|
|
|
}
|
|
|
|
|
2023-11-28 13:18:08 -05:00
|
|
|
fn verify(&self) -> bool {
|
|
|
|
// possibly include check that self.today is recent as well
|
|
|
|
self.today <= today() && self.bridge_pok.verify(self.fingerprint)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A report that the user was able to connect to the bridge
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct PositiveUserReport {
|
|
|
|
/// hashed fingerprint (SHA-1 hash of 20-byte bridge ID)
|
|
|
|
pub fingerprint: [u8; 20],
|
|
|
|
/// token from the bridge indicating it was reached
|
2023-12-05 19:55:33 -05:00
|
|
|
bridge_token: Option<BridgeToken>,
|
2023-11-28 13:18:08 -05:00
|
|
|
// TODO: proof of level, something involving credential show
|
|
|
|
/// user's country code, may be an empty string
|
|
|
|
pub country: String,
|
|
|
|
/// today's Julian date
|
|
|
|
pub today: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PositiveUserReport {
|
2023-12-05 18:05:44 -05:00
|
|
|
pub fn new(bridge_id: [u8; 20], bridge_token: Option<BridgeToken>, country: String) -> Self {
|
2023-11-28 13:18:08 -05:00
|
|
|
let mut hasher = Sha1::new();
|
|
|
|
hasher.update(bridge_id);
|
|
|
|
let fingerprint: [u8; 20] = hasher.finalize().into();
|
|
|
|
let today = today();
|
|
|
|
Self {
|
|
|
|
fingerprint,
|
|
|
|
bridge_token,
|
|
|
|
country,
|
|
|
|
today,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn verify(&self) -> bool {
|
|
|
|
// possibly include check that self.today is recent as well
|
2023-12-05 18:05:44 -05:00
|
|
|
self.today <= today()
|
|
|
|
&& (!REQUIRE_BRIDGE_TOKEN || {
|
|
|
|
if self.bridge_token.is_none() {
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
let bt = self.bridge_token.as_ref().unwrap();
|
|
|
|
self.today == bt.unsigned_bridge_token.today && bt.verify()
|
|
|
|
}
|
|
|
|
})
|
2023-11-28 13:18:08 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An unsigned token which indicates that the bridge was reached
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct UnsignedBridgeToken {
|
|
|
|
/// hashed fingerprint (SHA-1 hash of 20-byte bridge ID)
|
|
|
|
pub fingerprint: [u8; 20],
|
|
|
|
/// client's country code
|
|
|
|
pub country: String,
|
|
|
|
/// today's Julian date
|
|
|
|
pub today: u32,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl UnsignedBridgeToken {
|
|
|
|
pub fn new(bridge_id: [u8; 20], country: String) -> Self {
|
|
|
|
let mut hasher = Sha1::new();
|
|
|
|
hasher.update(bridge_id);
|
|
|
|
let fingerprint: [u8; 20] = hasher.finalize().into();
|
|
|
|
let today = today();
|
|
|
|
Self {
|
|
|
|
fingerprint,
|
|
|
|
country,
|
|
|
|
today,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A signed token which indicates that the bridge was reached
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
pub struct BridgeToken {
|
|
|
|
/// the unsigned version of this token
|
|
|
|
pub unsigned_bridge_token: UnsignedBridgeToken,
|
|
|
|
/// signature from bridge's ed25519 key
|
|
|
|
pub sig: Signature,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BridgeToken {
|
|
|
|
pub fn new(unsigned_bridge_token: UnsignedBridgeToken, keypair: SigningKey) -> Self {
|
|
|
|
let sig = keypair.sign(&bincode::serialize(&unsigned_bridge_token).unwrap());
|
|
|
|
Self {
|
|
|
|
unsigned_bridge_token,
|
|
|
|
sig,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn verify(&self) -> bool {
|
|
|
|
let pubkey = get_bridge_signing_pubkey(&self.unsigned_bridge_token.fingerprint);
|
|
|
|
self.unsigned_bridge_token.today <= today()
|
|
|
|
&& pubkey
|
|
|
|
.verify(
|
|
|
|
&bincode::serialize(&self.unsigned_bridge_token).unwrap(),
|
|
|
|
&self.sig,
|
|
|
|
)
|
|
|
|
.is_ok()
|
|
|
|
}
|
|
|
|
}
|