mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-09-01 07:04:07 +00:00
fix multiple bugs in the POC
This commit is contained in:
parent
484b43cd08
commit
3345a2a297
11 changed files with 133 additions and 267 deletions
10
rust/Cargo.lock
generated
10
rust/Cargo.lock
generated
|
|
@ -1801,7 +1801,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsignal-core"
|
name = "libsignal-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/signalapp/libsignal#622d0d52471f3cc9215fe4e9abaac1970018f79c"
|
source = "git+https://github.com/signalapp/libsignal?rev=44a6dd8fc9f9d6903b32842b80b3894a75678418#44a6dd8fc9f9d6903b32842b80b3894a75678418"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek",
|
"curve25519-dalek",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
|
@ -1819,8 +1819,8 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsignal-debug"
|
name = "libsignal-debug"
|
||||||
version = "0.99.3"
|
version = "0.97.5"
|
||||||
source = "git+https://github.com/signalapp/libsignal#622d0d52471f3cc9215fe4e9abaac1970018f79c"
|
source = "git+https://github.com/signalapp/libsignal?rev=44a6dd8fc9f9d6903b32842b80b3894a75678418#44a6dd8fc9f9d6903b32842b80b3894a75678418"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
@ -1828,7 +1828,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsignal-protocol"
|
name = "libsignal-protocol"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/signalapp/libsignal#622d0d52471f3cc9215fe4e9abaac1970018f79c"
|
source = "git+https://github.com/signalapp/libsignal?rev=44a6dd8fc9f9d6903b32842b80b3894a75678418#44a6dd8fc9f9d6903b32842b80b3894a75678418"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.9.2",
|
"aes 0.9.2",
|
||||||
"aes-gcm-siv",
|
"aes-gcm-siv",
|
||||||
|
|
@ -2776,7 +2776,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-crypto"
|
name = "signal-crypto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/signalapp/libsignal#622d0d52471f3cc9215fe4e9abaac1970018f79c"
|
source = "git+https://github.com/signalapp/libsignal?rev=44a6dd8fc9f9d6903b32842b80b3894a75678418#44a6dd8fc9f9d6903b32842b80b3894a75678418"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes 0.9.2",
|
"aes 0.9.2",
|
||||||
"cbc",
|
"cbc",
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ sqlx = { version = "0.9.0-alpha.1", default-features = false, features = [
|
||||||
libsqlite3-sys = { version = "0.35.0", features = [
|
libsqlite3-sys = { version = "0.35.0", features = [
|
||||||
"bundled-sqlcipher-vendored-openssl",
|
"bundled-sqlcipher-vendored-openssl",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1.44", features = ["full"] }
|
tokio = { version = "1.44", features = ["full", "macros"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
prost = "0.14.1"
|
prost = "0.14.1"
|
||||||
blahaj = "0.6.0"
|
blahaj = "0.6.0"
|
||||||
|
|
@ -43,7 +43,7 @@ chrono = { version = "0.4", features = ["serde"] }
|
||||||
zip = { version = "2.2.2", default-features = false, features = ["deflate"] }
|
zip = { version = "2.2.2", default-features = false, features = ["deflate"] }
|
||||||
scrypt = { version = "0.12", default-features = false }
|
scrypt = { version = "0.12", default-features = false }
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
libsignal-protocol = { git = "https://github.com/signalapp/libsignal", version = "0.1.0" }
|
libsignal-protocol = { git = "https://github.com/signalapp/libsignal", version = "0.1.0", rev = "44a6dd8fc9f9d6903b32842b80b3894a75678418" }
|
||||||
rand08 = { version = "0.8.5", package = "rand" }
|
rand08 = { version = "0.8.5", package = "rand" }
|
||||||
rand = "0.9.4"
|
rand = "0.9.4"
|
||||||
async-trait = "0.1.91"
|
async-trait = "0.1.91"
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
mod types;
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
#![allow(dead_code)]
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
/// Send from the person who tries to recover their account.
|
|
||||||
/// This can be done via a link, which will then be opened in the app of the contact.
|
|
||||||
/// The contact then has to manually select from which user he got the request.
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct RecoveryRequest {
|
|
||||||
pub temp_id: i64,
|
|
||||||
pub public_key: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used as envelope for TrustedFriendShare and RecoveryData
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct EncryptedEnvelope {
|
|
||||||
pub encrypted_data: Vec<u8>,
|
|
||||||
pub iv: Vec<u8>,
|
|
||||||
pub mac: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct User {
|
|
||||||
pub user_id: i64,
|
|
||||||
pub display_name: String,
|
|
||||||
pub avatar: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send from the trusted friend.
|
|
||||||
/// This is encrypted with the received public key.
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct TrustedFriendShare {
|
|
||||||
/// This allows to display the user which user has send him his recovery data.
|
|
||||||
pub trusted_friend: User,
|
|
||||||
/// This allows to display the userdata, showing that he is recovering the correct person.
|
|
||||||
pub share_user: User,
|
|
||||||
/// The minimum threshold required to decrypt the shares.
|
|
||||||
pub threshold: i32,
|
|
||||||
/// The actual share which will become: SecretSharedData
|
|
||||||
pub share: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct SecondFactorPin {
|
|
||||||
/// Required to try the PIN to get the share from the server.
|
|
||||||
/// This prevents that someone else can lock the pin, as the server only
|
|
||||||
/// allows 3 tries then after 1 day again 3 tries until the key is deleted.
|
|
||||||
pub unlock_token: Vec<u8>,
|
|
||||||
/// This never is send to the server but used to hash the pin before sending it to the server.
|
|
||||||
/// This prevents that the server every knows the short 4-digit PIN.
|
|
||||||
pub pin_seed: Vec<u8>,
|
|
||||||
/// The recovery data in case a second factor was used
|
|
||||||
/// The decryption key is loaded from the server either using the PIN or the MAIL
|
|
||||||
pub recovery_data_encrypted: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct SecondFactorMail {
|
|
||||||
/// The users selected mail which will be send to the server
|
|
||||||
/// To this mail the encryption key for the recovery_data is send
|
|
||||||
pub mail: String,
|
|
||||||
/// Required to try the PIN to get the share from the server.
|
|
||||||
/// This prevents that someone else can lock the pin, as the server only
|
|
||||||
/// allows 3 tries then after 1 day again 3 tries until the key is deleted.
|
|
||||||
pub unlock_token: Vec<u8>,
|
|
||||||
/// The recovery data in case a second factor was used
|
|
||||||
/// The decryption key is loaded from the server either using the PIN or the MAIL
|
|
||||||
pub recovery_data_encrypted: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub enum SecretSharedData {
|
|
||||||
None(RecoveryData),
|
|
||||||
Mail(SecondFactorMail),
|
|
||||||
Pin(SecondFactorPin),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The data which is recovered at the end.
|
|
||||||
/// The backup_master_key allows to recover the actual backup uploaded in the background to the server.
|
|
||||||
/// In case the backup is not available any more the user can use its user_id and his private_key to register as a new user.
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct RecoveryData {
|
|
||||||
pub user_id: i64,
|
|
||||||
pub master_key: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
pub(crate) mod backup_archive;
|
pub(crate) mod backup_archive;
|
||||||
pub(crate) mod backup_identity;
|
pub(crate) mod backup_identity;
|
||||||
pub(crate) mod backup_passwordless;
|
|
||||||
|
|
|
||||||
|
|
@ -12,29 +12,33 @@ impl FlutterUserDiscovery {
|
||||||
public_key: Vec<u8>,
|
public_key: Vec<u8>,
|
||||||
share_promotion: bool,
|
share_promotion: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
CURRENT_CALLBACK_ID.scope(callback_id, async move {
|
CURRENT_CALLBACK_ID
|
||||||
tracing::info!("Rust bridge: initialize_or_update started");
|
.scope(callback_id, async move {
|
||||||
let twonly = get_twonly_flutter()?;
|
tracing::info!("Rust bridge: initialize_or_update started");
|
||||||
tracing::info!("Rust bridge: getting user_discovery lock");
|
let twonly = get_twonly_flutter()?;
|
||||||
let user_discovery = twonly.user_discovery.get().await;
|
tracing::info!("Rust bridge: getting user_discovery lock");
|
||||||
tracing::info!("Rust bridge: calling initialize_or_update on protocols");
|
let user_discovery = twonly.user_discovery.get().await;
|
||||||
let res = user_discovery
|
tracing::info!("Rust bridge: calling initialize_or_update on protocols");
|
||||||
.initialize_or_update(threshold, user_id, public_key, share_promotion)
|
let res = user_discovery
|
||||||
.await;
|
.initialize_or_update(threshold, user_id, public_key, share_promotion)
|
||||||
tracing::info!("Rust bridge: initialize_or_update on protocols finished");
|
.await;
|
||||||
Ok(res?)
|
tracing::info!("Rust bridge: initialize_or_update on protocols finished");
|
||||||
}).await
|
Ok(res?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_current_version(callback_id: u32) -> Result<Vec<u8>> {
|
pub async fn get_current_version(callback_id: u32) -> Result<Vec<u8>> {
|
||||||
CURRENT_CALLBACK_ID.scope(callback_id, async move {
|
CURRENT_CALLBACK_ID
|
||||||
Ok(get_twonly_flutter()?
|
.scope(callback_id, async move {
|
||||||
.user_discovery
|
Ok(get_twonly_flutter()?
|
||||||
.get()
|
.user_discovery
|
||||||
.await
|
.get()
|
||||||
.get_current_version()
|
.await
|
||||||
.await?)
|
.get_current_version()
|
||||||
}).await
|
.await?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_new_messages(
|
pub async fn get_new_messages(
|
||||||
|
|
@ -42,14 +46,16 @@ impl FlutterUserDiscovery {
|
||||||
contact_id: i64,
|
contact_id: i64,
|
||||||
received_version: &[u8],
|
received_version: &[u8],
|
||||||
) -> Result<Vec<Vec<u8>>> {
|
) -> Result<Vec<Vec<u8>>> {
|
||||||
CURRENT_CALLBACK_ID.scope(callback_id, async move {
|
CURRENT_CALLBACK_ID
|
||||||
Ok(get_twonly_flutter()?
|
.scope(callback_id, async move {
|
||||||
.user_discovery
|
Ok(get_twonly_flutter()?
|
||||||
.get()
|
.user_discovery
|
||||||
.await
|
.get()
|
||||||
.get_new_messages(contact_id, received_version)
|
.await
|
||||||
.await?)
|
.get_new_messages(contact_id, received_version)
|
||||||
}).await
|
.await?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn should_request_new_messages(
|
pub async fn should_request_new_messages(
|
||||||
|
|
@ -57,14 +63,16 @@ impl FlutterUserDiscovery {
|
||||||
contact_id: i64,
|
contact_id: i64,
|
||||||
version: &[u8],
|
version: &[u8],
|
||||||
) -> Result<Option<Vec<u8>>> {
|
) -> Result<Option<Vec<u8>>> {
|
||||||
CURRENT_CALLBACK_ID.scope(callback_id, async move {
|
CURRENT_CALLBACK_ID
|
||||||
Ok(get_twonly_flutter()?
|
.scope(callback_id, async move {
|
||||||
.user_discovery
|
Ok(get_twonly_flutter()?
|
||||||
.get()
|
.user_discovery
|
||||||
.await
|
.get()
|
||||||
.should_request_new_messages(contact_id, version)
|
.await
|
||||||
.await?)
|
.should_request_new_messages(contact_id, version)
|
||||||
}).await
|
.await?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_new_messages(
|
pub async fn handle_new_messages(
|
||||||
|
|
@ -73,14 +81,16 @@ impl FlutterUserDiscovery {
|
||||||
public_key_verified_timestamp: Option<i64>,
|
public_key_verified_timestamp: Option<i64>,
|
||||||
messages: Vec<Vec<u8>>,
|
messages: Vec<Vec<u8>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
CURRENT_CALLBACK_ID.scope(callback_id, async move {
|
CURRENT_CALLBACK_ID
|
||||||
Ok(get_twonly_flutter()?
|
.scope(callback_id, async move {
|
||||||
.user_discovery
|
Ok(get_twonly_flutter()?
|
||||||
.get()
|
.user_discovery
|
||||||
.await
|
.get()
|
||||||
.handle_new_messages(contact_id, public_key_verified_timestamp, messages)
|
.await
|
||||||
.await?)
|
.handle_new_messages(contact_id, public_key_verified_timestamp, messages)
|
||||||
}).await
|
.await?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_verification_state_for_user(
|
pub async fn update_verification_state_for_user(
|
||||||
|
|
@ -88,13 +98,15 @@ impl FlutterUserDiscovery {
|
||||||
contact_id: i64,
|
contact_id: i64,
|
||||||
public_key_verified_timestamp: Option<i64>,
|
public_key_verified_timestamp: Option<i64>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
CURRENT_CALLBACK_ID.scope(callback_id, async move {
|
CURRENT_CALLBACK_ID
|
||||||
Ok(get_twonly_flutter()?
|
.scope(callback_id, async move {
|
||||||
.user_discovery
|
Ok(get_twonly_flutter()?
|
||||||
.get()
|
.user_discovery
|
||||||
.await
|
.get()
|
||||||
.update_verification_state_for_user(contact_id, public_key_verified_timestamp)
|
.await
|
||||||
.await?)
|
.update_verification_state_for_user(contact_id, public_key_verified_timestamp)
|
||||||
}).await
|
.await?)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
-- Signal Identities (Stores remote IdentityKeys)
|
-- Signal Identities (Stores remote IdentityKeys)
|
||||||
CREATE TABLE IF NOT EXISTS signal_identities (
|
CREATE TABLE IF NOT EXISTS signal_identities (
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
device_id INTEGER NOT NULL,
|
|
||||||
identity_key BLOB NOT NULL,
|
identity_key BLOB NOT NULL,
|
||||||
timestamp INTEGER NOT NULL,
|
timestamp INTEGER NOT NULL,
|
||||||
PRIMARY KEY (name, device_id)
|
PRIMARY KEY (name)
|
||||||
);
|
);
|
||||||
|
|
||||||
-- Signal PreKeys (Stores local one-time prekeys)
|
-- Signal PreKeys (Stores local one-time prekeys)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use crate::signal::store::DbSignalProtocolStore;
|
||||||
|
|
||||||
pub struct RustSignalEngine {
|
pub struct RustSignalEngine {
|
||||||
store: Arc<Mutex<DbSignalProtocolStore>>,
|
store: Arc<Mutex<DbSignalProtocolStore>>,
|
||||||
|
local_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FrbPreKeyBundle {
|
pub struct FrbPreKeyBundle {
|
||||||
|
|
@ -30,7 +31,7 @@ pub struct FrbPreKeyBundle {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RustSignalEngine {
|
impl RustSignalEngine {
|
||||||
pub async fn new() -> Result<Self> {
|
pub async fn new(local_name: String) -> Result<Self> {
|
||||||
let twonly = crate::bridge::get_twonly_flutter()?;
|
let twonly = crate::bridge::get_twonly_flutter()?;
|
||||||
let pool = twonly.rust_db.pool.clone();
|
let pool = twonly.rust_db.pool.clone();
|
||||||
|
|
||||||
|
|
@ -52,6 +53,7 @@ impl RustSignalEngine {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
store: Arc::new(Mutex::new(store)),
|
store: Arc::new(Mutex::new(store)),
|
||||||
|
local_name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,6 +61,7 @@ impl RustSignalEngine {
|
||||||
pool: sqlx::SqlitePool,
|
pool: sqlx::SqlitePool,
|
||||||
identity_key_pair_bytes: Vec<u8>,
|
identity_key_pair_bytes: Vec<u8>,
|
||||||
local_registration_id: u32,
|
local_registration_id: u32,
|
||||||
|
local_name: String,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
let identity_key_pair = IdentityKeyPair::try_from(&identity_key_pair_bytes[..])
|
let identity_key_pair = IdentityKeyPair::try_from(&identity_key_pair_bytes[..])
|
||||||
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
||||||
|
|
@ -67,6 +70,7 @@ impl RustSignalEngine {
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
store: Arc::new(Mutex::new(store)),
|
store: Arc::new(Mutex::new(store)),
|
||||||
|
local_name,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,6 +84,7 @@ impl RustSignalEngine {
|
||||||
&self,
|
&self,
|
||||||
pre_key_id: u32,
|
pre_key_id: u32,
|
||||||
signed_pre_key_id: u32,
|
signed_pre_key_id: u32,
|
||||||
|
kyber_pre_key_id: u32,
|
||||||
) -> Result<FrbPreKeyBundle> {
|
) -> Result<FrbPreKeyBundle> {
|
||||||
let mut store_guard = self.store.lock().await;
|
let mut store_guard = self.store.lock().await;
|
||||||
let store = &mut *store_guard;
|
let store = &mut *store_guard;
|
||||||
|
|
@ -110,7 +115,7 @@ impl RustSignalEngine {
|
||||||
let timestamp = Timestamp::from_epoch_millis(
|
let timestamp = Timestamp::from_epoch_millis(
|
||||||
SystemTime::now()
|
SystemTime::now()
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.unwrap()
|
.map_err(|e| TwonlyError::Signal(e.to_string()))?
|
||||||
.as_millis() as u64,
|
.as_millis() as u64,
|
||||||
);
|
);
|
||||||
store
|
store
|
||||||
|
|
@ -127,7 +132,6 @@ impl RustSignalEngine {
|
||||||
.await
|
.await
|
||||||
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
||||||
|
|
||||||
let kyber_pre_key_id = 1;
|
|
||||||
let kyber_key_pair = libsignal_protocol::kem::KeyPair::generate(
|
let kyber_key_pair = libsignal_protocol::kem::KeyPair::generate(
|
||||||
libsignal_protocol::kem::KeyType::Kyber1024,
|
libsignal_protocol::kem::KeyType::Kyber1024,
|
||||||
&mut csprng,
|
&mut csprng,
|
||||||
|
|
@ -143,7 +147,9 @@ impl RustSignalEngine {
|
||||||
&mut csprng,
|
&mut csprng,
|
||||||
)
|
)
|
||||||
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
||||||
let kyber_sig_arr: [u8; 64] = kyber_signature[..].try_into().unwrap();
|
let kyber_sig_arr: [u8; 64] = kyber_signature[..]
|
||||||
|
.try_into()
|
||||||
|
.map_err(|e: std::array::TryFromSliceError| TwonlyError::Signal(e.to_string()))?;
|
||||||
store
|
store
|
||||||
.kyber_pre_key_store
|
.kyber_pre_key_store
|
||||||
.save_kyber_pre_key(
|
.save_kyber_pre_key(
|
||||||
|
|
@ -196,8 +202,11 @@ impl RustSignalEngine {
|
||||||
let d_id = DeviceId::try_from(device_id)
|
let d_id = DeviceId::try_from(device_id)
|
||||||
.map_err(|_| TwonlyError::Generic(format!("Invalid device id: {}", device_id)))?;
|
.map_err(|_| TwonlyError::Generic(format!("Invalid device id: {}", device_id)))?;
|
||||||
let remote_address = ProtocolAddress::new(name, d_id);
|
let remote_address = ProtocolAddress::new(name, d_id);
|
||||||
let local_address =
|
let local_address = ProtocolAddress::new(
|
||||||
ProtocolAddress::new("local".to_string(), DeviceId::try_from(1).unwrap());
|
self.local_name.clone(),
|
||||||
|
DeviceId::try_from(1)
|
||||||
|
.map_err(|_| TwonlyError::Generic("Invalid device id 1".to_string()))?,
|
||||||
|
);
|
||||||
|
|
||||||
let identity_key = IdentityKey::decode(&bundle.identity_key)
|
let identity_key = IdentityKey::decode(&bundle.identity_key)
|
||||||
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
.map_err(|e| TwonlyError::Signal(e.to_string()))?;
|
||||||
|
|
@ -221,7 +230,9 @@ impl RustSignalEngine {
|
||||||
|
|
||||||
let pre_key_bundle = PreKeyBundle::new(
|
let pre_key_bundle = PreKeyBundle::new(
|
||||||
bundle.registration_id,
|
bundle.registration_id,
|
||||||
DeviceId::try_from(bundle.device_id).unwrap_or(DeviceId::try_from(1).unwrap()),
|
DeviceId::try_from(bundle.device_id).map_err(|_| {
|
||||||
|
TwonlyError::Generic(format!("Invalid device id: {}", bundle.device_id))
|
||||||
|
})?,
|
||||||
pre_key,
|
pre_key,
|
||||||
SignedPreKeyId::from(bundle.signed_pre_key_id),
|
SignedPreKeyId::from(bundle.signed_pre_key_id),
|
||||||
signed_pre_key_public,
|
signed_pre_key_public,
|
||||||
|
|
@ -261,8 +272,11 @@ impl RustSignalEngine {
|
||||||
let d_id = DeviceId::try_from(device_id)
|
let d_id = DeviceId::try_from(device_id)
|
||||||
.map_err(|_| TwonlyError::Generic(format!("Invalid device id: {}", device_id)))?;
|
.map_err(|_| TwonlyError::Generic(format!("Invalid device id: {}", device_id)))?;
|
||||||
let remote_address = ProtocolAddress::new(name, d_id);
|
let remote_address = ProtocolAddress::new(name, d_id);
|
||||||
let local_address =
|
let local_address = ProtocolAddress::new(
|
||||||
ProtocolAddress::new("local".to_string(), DeviceId::try_from(1).unwrap());
|
self.local_name.clone(),
|
||||||
|
DeviceId::try_from(1)
|
||||||
|
.map_err(|_| TwonlyError::Generic("Invalid device id 1".to_string()))?,
|
||||||
|
);
|
||||||
|
|
||||||
let mut csprng = rand::rng();
|
let mut csprng = rand::rng();
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
|
|
@ -295,8 +309,11 @@ impl RustSignalEngine {
|
||||||
let d_id = DeviceId::try_from(device_id)
|
let d_id = DeviceId::try_from(device_id)
|
||||||
.map_err(|_| TwonlyError::Generic(format!("Invalid device id: {}", device_id)))?;
|
.map_err(|_| TwonlyError::Generic(format!("Invalid device id: {}", device_id)))?;
|
||||||
let remote_address = ProtocolAddress::new(name, d_id);
|
let remote_address = ProtocolAddress::new(name, d_id);
|
||||||
let local_address =
|
let local_address = ProtocolAddress::new(
|
||||||
ProtocolAddress::new("local".to_string(), DeviceId::try_from(1).unwrap());
|
self.local_name.clone(),
|
||||||
|
DeviceId::try_from(1)
|
||||||
|
.map_err(|_| TwonlyError::Generic("Invalid device id 1".to_string()))?,
|
||||||
|
);
|
||||||
|
|
||||||
let plaintext = if is_prekey_message {
|
let plaintext = if is_prekey_message {
|
||||||
let message = PreKeySignalMessage::try_from(&ciphertext_bytes[..])
|
let message = PreKeySignalMessage::try_from(&ciphertext_bytes[..])
|
||||||
|
|
|
||||||
|
|
@ -57,21 +57,18 @@ impl IdentityKeyStore for DbIdentityKeyStore {
|
||||||
identity: &IdentityKey,
|
identity: &IdentityKey,
|
||||||
) -> Result<IdentityChange, SignalProtocolError> {
|
) -> Result<IdentityChange, SignalProtocolError> {
|
||||||
let name = address.name();
|
let name = address.name();
|
||||||
let device_id: u32 = address.device_id().into();
|
|
||||||
let identity_bytes = identity.serialize();
|
let identity_bytes = identity.serialize();
|
||||||
let timestamp = std::time::SystemTime::now()
|
let timestamp = std::time::SystemTime::now()
|
||||||
.duration_since(std::time::UNIX_EPOCH)
|
.duration_since(std::time::UNIX_EPOCH)
|
||||||
.unwrap()
|
.map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?
|
||||||
.as_millis() as i64;
|
.as_millis() as i64;
|
||||||
|
|
||||||
let existing: Option<(Vec<u8>,)> = sqlx::query_as(
|
let existing: Option<(Vec<u8>,)> =
|
||||||
"SELECT identity_key FROM signal_identities WHERE name = ? AND device_id = ?",
|
sqlx::query_as("SELECT identity_key FROM signal_identities WHERE name = ?")
|
||||||
)
|
.bind(name)
|
||||||
.bind(name)
|
.fetch_optional(&self.pool)
|
||||||
.bind(device_id)
|
.await
|
||||||
.fetch_optional(&self.pool)
|
.map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?;
|
||||||
.await
|
|
||||||
.map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?;
|
|
||||||
|
|
||||||
let changed = if let Some(row) = existing {
|
let changed = if let Some(row) = existing {
|
||||||
row.0 != identity_bytes.as_ref()
|
row.0 != identity_bytes.as_ref()
|
||||||
|
|
@ -79,9 +76,8 @@ impl IdentityKeyStore for DbIdentityKeyStore {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
sqlx::query("INSERT INTO signal_identities (name, device_id, identity_key, timestamp) VALUES (?, ?, ?, ?) ON CONFLICT(name, device_id) DO UPDATE SET identity_key = excluded.identity_key, timestamp = excluded.timestamp")
|
sqlx::query("INSERT INTO signal_identities (name, identity_key, timestamp) VALUES (?, ?, ?) ON CONFLICT(name) DO UPDATE SET identity_key = excluded.identity_key, timestamp = excluded.timestamp")
|
||||||
.bind(name)
|
.bind(name)
|
||||||
.bind(device_id)
|
|
||||||
.bind(identity_bytes.as_ref())
|
.bind(identity_bytes.as_ref())
|
||||||
.bind(timestamp)
|
.bind(timestamp)
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
|
|
@ -98,17 +94,14 @@ impl IdentityKeyStore for DbIdentityKeyStore {
|
||||||
_direction: Direction,
|
_direction: Direction,
|
||||||
) -> Result<bool, SignalProtocolError> {
|
) -> Result<bool, SignalProtocolError> {
|
||||||
let name = address.name();
|
let name = address.name();
|
||||||
let device_id: u32 = address.device_id().into();
|
|
||||||
let identity_bytes = identity.serialize();
|
let identity_bytes = identity.serialize();
|
||||||
|
|
||||||
let row: Option<(Vec<u8>,)> = sqlx::query_as(
|
let row: Option<(Vec<u8>,)> =
|
||||||
"SELECT identity_key FROM signal_identities WHERE name = ? AND device_id = ?",
|
sqlx::query_as("SELECT identity_key FROM signal_identities WHERE name = ?")
|
||||||
)
|
.bind(name)
|
||||||
.bind(name)
|
.fetch_optional(&self.pool)
|
||||||
.bind(device_id)
|
.await
|
||||||
.fetch_optional(&self.pool)
|
.map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?;
|
||||||
.await
|
|
||||||
.map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?;
|
|
||||||
|
|
||||||
if let Some((stored_key,)) = row {
|
if let Some((stored_key,)) = row {
|
||||||
Ok(stored_key == identity_bytes.as_ref())
|
Ok(stored_key == identity_bytes.as_ref())
|
||||||
|
|
@ -122,16 +115,13 @@ impl IdentityKeyStore for DbIdentityKeyStore {
|
||||||
address: &ProtocolAddress,
|
address: &ProtocolAddress,
|
||||||
) -> Result<Option<IdentityKey>, SignalProtocolError> {
|
) -> Result<Option<IdentityKey>, SignalProtocolError> {
|
||||||
let name = address.name();
|
let name = address.name();
|
||||||
let device_id: u32 = address.device_id().into();
|
|
||||||
|
|
||||||
let row: Option<(Vec<u8>,)> = sqlx::query_as(
|
let row: Option<(Vec<u8>,)> =
|
||||||
"SELECT identity_key FROM signal_identities WHERE name = ? AND device_id = ?",
|
sqlx::query_as("SELECT identity_key FROM signal_identities WHERE name = ?")
|
||||||
)
|
.bind(name)
|
||||||
.bind(name)
|
.fetch_optional(&self.pool)
|
||||||
.bind(device_id)
|
.await
|
||||||
.fetch_optional(&self.pool)
|
.map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?;
|
||||||
.await
|
|
||||||
.map_err(|_| SignalProtocolError::UntrustedIdentity(address.clone()))?;
|
|
||||||
|
|
||||||
if let Some((bytes,)) = row {
|
if let Some((bytes,)) = row {
|
||||||
let key = IdentityKey::decode(&bytes)
|
let key = IdentityKey::decode(&bytes)
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
|
|
||||||
# User Discovery
|
|
||||||
|
|
||||||
User Discovery is a feature that allows users to discover other users in a decentralized system without any central authority. It uses Shamir's Secret Sharing to securely share and discover user information.
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
The User Discovery module is composed of the following components:
|
|
||||||
|
|
||||||
- **UserDiscovery** - The main struct which initializes the user discovery and provides access to the user discovery functionality.
|
|
||||||
|
|
||||||
- **UserDiscoveryStore** - A trait which has to be implemented. It is used to store and retrieve the user discovery data.
|
|
||||||
- **UserDiscoveryUtils** - A trait which has to be implemented. It is used to perform signature verification and signing.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use crate::user_discovery::{UserDiscovery, UserID};
|
|
||||||
use crate::user_discovery::stores::InMemoryStore; // Replace with your persistent store
|
|
||||||
use crate::user_discovery::traits::tests::TestingUtils; // Replace with your utils
|
|
||||||
|
|
||||||
const THRESHOLD: u8 = 2;
|
|
||||||
|
|
||||||
// Initialize user discovery for Alice
|
|
||||||
const ALICE_ID: UserID = 1;
|
|
||||||
let alice_ud = UserDiscovery::new(InMemoryStore::default(), TestingUtils::default()).unwrap();
|
|
||||||
|
|
||||||
// Set threshold, user ID, and the user's public key
|
|
||||||
alice_ud.initialize_or_update(THRESHOLD, ALICE_ID, vec![0; 32]).unwrap();
|
|
||||||
|
|
||||||
// Initialize user discovery for Bob
|
|
||||||
const BOB_ID: UserID = 2;
|
|
||||||
let bob_ud = UserDiscovery::new(InMemoryStore::default(), TestingUtils::default()).unwrap();
|
|
||||||
bob_ud.initialize_or_update(THRESHOLD, BOB_ID, vec![0; 32]).unwrap();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Simulate network communication: Alice sends her current version to Bob
|
|
||||||
let bob_received_version_from_alice = alice_ud.get_current_version().unwrap();
|
|
||||||
|
|
||||||
// SEND FROM ALICE TO BOB: bob_received_version_from_alice
|
|
||||||
|
|
||||||
// Bob checks if he should request new messages
|
|
||||||
if bob_ud.should_request_new_messages(ALICE_ID, &bob_received_version_from_alice).unwrap() {
|
|
||||||
|
|
||||||
// Bob has a old version and must now request to get the new messages
|
|
||||||
|
|
||||||
// Bob fetches his current known version and sends it via the network to Alice
|
|
||||||
let bob_stored_alice_version = bob_ud.get_contact_version(ALICE_ID)
|
|
||||||
.unwrap()
|
|
||||||
.unwrap_or_else(|| vec![0, 0]); // Note: In practice use actual default encoded version
|
|
||||||
|
|
||||||
// SEND FROM BOB TO ALICE: bob_stored_alice_version
|
|
||||||
|
|
||||||
// Alice loads the new messages for Bob. These only conclude changes since the provided version.
|
|
||||||
let new_messages = alice_ud.get_new_messages(BOB_ID, &bob_stored_alice_version).unwrap();
|
|
||||||
|
|
||||||
// SEND FROM ALICE TO BOB: new_messages
|
|
||||||
|
|
||||||
// Bob processes the received user discovery messages
|
|
||||||
bob_ud.handle_user_discovery_messages(ALICE_ID, new_messages).unwrap();
|
|
||||||
|
|
||||||
// BOB is now able to promote ALICE to his other contacts
|
|
||||||
}
|
|
||||||
|
|
||||||
// <Involve more users>
|
|
||||||
|
|
||||||
// 4. Retrieve all newly discovered users and relationships
|
|
||||||
// In this example now new users where discovered, to see a more comprehensive example
|
|
||||||
// see the test in the `mod.rs` fil.
|
|
||||||
let discovered_users = bob_ud.get_all_announced_users().unwrap();
|
|
||||||
for (user, connections) in discovered_users {
|
|
||||||
println!("Discovered User: {} (Public ID: {})", user.user_id, user.public_id);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
@ -17,13 +17,21 @@ async fn test_twonly_api_100_messages() -> Result<(), Box<dyn std::error::Error>
|
||||||
let bob_identity_bytes = RustSignalEngine::generate_identity_key_pair()?;
|
let bob_identity_bytes = RustSignalEngine::generate_identity_key_pair()?;
|
||||||
|
|
||||||
// 3. Initialize engines with the DB pools
|
// 3. Initialize engines with the DB pools
|
||||||
let alice_engine =
|
let alice_engine = RustSignalEngine::new_with_pool(
|
||||||
RustSignalEngine::new_with_pool(alice_db.pool.clone(), alice_identity_bytes, 1234)?;
|
alice_db.pool.clone(),
|
||||||
let bob_engine =
|
alice_identity_bytes,
|
||||||
RustSignalEngine::new_with_pool(bob_db.pool.clone(), bob_identity_bytes, 5678)?;
|
1234,
|
||||||
|
"alice".to_string(),
|
||||||
|
)?;
|
||||||
|
let bob_engine = RustSignalEngine::new_with_pool(
|
||||||
|
bob_db.pool.clone(),
|
||||||
|
bob_identity_bytes,
|
||||||
|
5678,
|
||||||
|
"bob".to_string(),
|
||||||
|
)?;
|
||||||
|
|
||||||
// 4. Bob generates a bundle
|
// 4. Bob generates a bundle
|
||||||
let bob_bundle = bob_engine.generate_bundle(1, 1).await?;
|
let bob_bundle = bob_engine.generate_bundle(1, 1, 1).await?;
|
||||||
|
|
||||||
// 5. Alice processes Bob's bundle
|
// 5. Alice processes Bob's bundle
|
||||||
alice_engine
|
alice_engine
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue