fix multiple bugs in the POC

This commit is contained in:
otsmr 2026-08-02 14:40:44 +02:00
parent 484b43cd08
commit 3345a2a297
11 changed files with 133 additions and 267 deletions

10
rust/Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -1 +0,0 @@
mod types;

View file

@ -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>,
}

View file

@ -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;

View file

@ -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
} }
} }

View file

@ -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)

View file

@ -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[..])

View file

@ -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)

View file

@ -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);
}
```

View file

@ -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