diff --git a/agent/src/main.rs b/agent/src/main.rs index 0352963..c500559 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -51,14 +51,20 @@ fn arg(flag: &str, default: &str) -> String { fn main() { let server = arg("--server", "https://tether.pecord.io"); - let room = arg("--room", "default"); let source = arg("--source", "agent"); let from = arg("--from", &format!("agent-{source}")); // Friendly name for other devices' nearby lists. let host = std::env::var("HOSTNAME").unwrap_or_else(|_| "tether-agent".into()); let name = arg("--name", &host); - // Shared identity: same-account devices pair on the LAN regardless of room. + // Shared identity: same-account devices pair on the LAN regardless of room, + // and share an account-derived room everywhere (so cross-network works too). let account = arg("--account", "pecord@gmail.com"); + let default_room = if account.is_empty() { + "default".to_string() + } else { + tethercore::room_for_account(account.clone()) + }; + let room = arg("--room", &default_room); eprintln!("tether-agent → {server} room={room} source={source}"); diff --git a/android/app/src/main/java/io/pecord/tether/TetherStore.kt b/android/app/src/main/java/io/pecord/tether/TetherStore.kt index 9a97f68..94605b6 100644 --- a/android/app/src/main/java/io/pecord/tether/TetherStore.kt +++ b/android/app/src/main/java/io/pecord/tether/TetherStore.kt @@ -19,7 +19,11 @@ data class FeedItem(val text: String, val source: String, val ts: Long, val id: class TetherStore(private val context: Context) { val serverURL = mutableStateOf(prefs().getString("serverURL", "https://tether.pecord.io")!!) val account = mutableStateOf(prefs().getString("account", "pecord@gmail.com")!!) - val room = mutableStateOf(RoomIdentity.derive(context)) + // Room follows the account → all our devices share one room everywhere. + val room = mutableStateOf( + if (account.value.isEmpty()) RoomIdentity.derive(context) + else uniffi.tethercore.roomForAccount(account.value) + ) val feed = mutableStateListOf() val connected = mutableStateOf(false) diff --git a/core/Cargo.lock b/core/Cargo.lock index dbd788d..015c5e8 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -20,7 +20,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -271,6 +271,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block-padding" version = "0.3.3" @@ -404,7 +413,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -426,7 +435,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -483,6 +492,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -492,6 +507,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -530,6 +554,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -546,7 +579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", "fiat-crypto", "rustc_version", @@ -577,7 +610,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] @@ -608,12 +641,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", +] + [[package]] name = "displaydoc" version = "0.2.6" @@ -653,7 +697,7 @@ dependencies = [ "rustls", "sec1", "sha1", - "sha2", + "sha2 0.10.9", "thiserror 1.0.69", "tokio", "webrtc-util", @@ -668,7 +712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", @@ -683,7 +727,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", @@ -977,7 +1021,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1019,6 +1063,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "hybrid-array" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.10.1" @@ -1307,7 +1360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -1487,7 +1540,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -1499,7 +1552,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -1590,7 +1643,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -1602,7 +1655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2175,8 +2228,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", ] [[package]] @@ -2186,8 +2239,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -2212,7 +2276,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -2397,6 +2461,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "sha2 0.11.0", "tokio", "uniffi", "webrtc", @@ -2799,7 +2864,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -3004,7 +3069,7 @@ dependencies = [ "sdp", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smol_str", "stun", "thiserror 1.0.69", diff --git a/core/Cargo.toml b/core/Cargo.toml index 51f818c..8352276 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -24,3 +24,4 @@ tokio = { version = "1", features = ["rt-multi-thread", "time", "sync", "macros" futures-util = "0.3" webrtc = "0.17.1" mdns-sd = "0.20.0" +sha2 = "0.11.0" diff --git a/core/src/lib.rs b/core/src/lib.rs index 8151bde..2df7fd1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -74,6 +74,20 @@ pub struct Peer { /// engine's `nearby()` (reader). Instant is last-seen for staleness pruning. type Discovered = Arc>>; +/// Canonical room id for a shared identity: sha256(account)[..4] as 8 hex chars. +/// Defined once here and exported so every client (Swift/Kotlin/agent) derives +/// the SAME room — that's what puts all of one account's devices together across +/// LAN, relay, and RTC. Empty account → empty (caller falls back to a device id). +#[uniffi::export] +pub fn room_for_account(account: String) -> String { + use sha2::{Digest, Sha256}; + if account.is_empty() { + return String::new(); + } + let digest = Sha256::digest(account.as_bytes()); + digest[..4].iter().map(|b| format!("{b:02x}")).collect() +} + #[derive(Clone)] struct Config { server: String, diff --git a/ios/MacTetherApp/Sources/MacTetherStore.swift b/ios/MacTetherApp/Sources/MacTetherStore.swift index b337042..6af1e6c 100644 --- a/ios/MacTetherApp/Sources/MacTetherStore.swift +++ b/ios/MacTetherApp/Sources/MacTetherStore.swift @@ -19,7 +19,11 @@ final class MacTetherStore { var serverURL: String = UserDefaults.standard.string(forKey: "serverURL") ?? "https://tether.pecord.io" // Prefilled shared identity — for now it's just us, so all devices pair. var account: String = UserDefaults.standard.string(forKey: "account") ?? "pecord@gmail.com" - var room: String = RoomIdentity.derive() + // Room follows the account → all our devices share one room everywhere. + var room: String = { + let a = UserDefaults.standard.string(forKey: "account") ?? "pecord@gmail.com" + return a.isEmpty ? RoomIdentity.derive() : roomForAccount(account: a) + }() var feed: [FeedItem] = [] var nearby: [Peer] = [] // tether devices on the LAN, for the picker var connected = false diff --git a/ios/TetherApp/Sources/TetherStore.swift b/ios/TetherApp/Sources/TetherStore.swift index 57874df..e4be746 100644 --- a/ios/TetherApp/Sources/TetherStore.swift +++ b/ios/TetherApp/Sources/TetherStore.swift @@ -23,7 +23,12 @@ final class TetherStore { // Prefilled shared identity — for now it's just us, so all devices pair. // Becomes the Sign-in-with-Apple `sub` once the dev program clears. var account: String = UserDefaults.standard.string(forKey: "account") ?? "pecord@gmail.com" - var room: String = RoomIdentity.derive() + // Room follows the account, so all our devices share one room everywhere + // (LAN, relay, RTC). Falls back to a device id only when signed out. + var room: String = { + let a = UserDefaults.standard.string(forKey: "account") ?? "pecord@gmail.com" + return a.isEmpty ? RoomIdentity.derive() : roomForAccount(account: a) + }() var feed: [FeedItem] = [] var connected = false var lastError: String?