field_ios

In-the-field app for Radroots on iOS
git clone https://radroots.dev/git/field_ios.git
Log | Files | Refs | README | LICENSE

commit d4751fff1abb47746701012928957ee8c9af4cf0
parent 54642535cfc06b34f7e56ca73a6779b56ee3d65d
Author: triesap <tyson@radroots.org>
Date:   Fri,  7 Aug 2026 15:30:36 +0000

feat(mobile): enforce opaque host signing

- replace local secret slots and duplicate social authoring with focused host signer operations
- domain-separate BUD-11 HTTP authorization from durable relay event plans
- revalidate signer output against exact request deadline cancellation and signature before commit
- refresh public APIs docs and tests across consolidated mobile surfaces

Diffstat:
Mcore/crates/tera_ffi/src/remote.rs | 77-----------------------------------------------------------------------------
Mcore/crates/tera_ffi/src/runtime.rs | 143-------------------------------------------------------------------------------
Mcore/crates/tera_ffi/tests/runtime_delegation.rs | 95-------------------------------------------------------------------------------
3 files changed, 0 insertions(+), 315 deletions(-)

diff --git a/core/crates/tera_ffi/src/remote.rs b/core/crates/tera_ffi/src/remote.rs @@ -2,8 +2,6 @@ use radroots_mobile_core::runtime::app_info::*; use radroots_mobile_core::runtime::info::*; -use radroots_mobile_core::runtime::key_management::*; -use radroots_mobile_core::runtime::nostr::*; use radroots_mobile_core::runtime::product_surface::*; use radroots_mobile_core::runtime::sdk::*; use radroots_mobile_core::{SdkErrorRecord, StoreErrorRecord}; @@ -89,81 +87,6 @@ pub struct SdkShutdownRecord { pub already_closed: bool, } -#[uniffi::remote(Record)] -pub struct NostrIdentityRecord { - pub id: String, - pub public_key_hex: String, - pub public_key_npub: String, - pub label: Option<String>, - pub is_selected: bool, -} - -#[uniffi::remote(Record)] -pub struct NostrIdentitySnapshot { - pub has_selected_signing_identity: bool, - pub selected_identity_id: Option<String>, - pub selected_npub: Option<String>, - pub identities: Vec<NostrIdentityRecord>, -} - -#[uniffi::remote(Record)] -pub struct NostrHostCustodyIdentity { - pub id: String, - pub public_key_hex: String, - pub public_key_npub: String, -} - -#[uniffi::remote(Enum)] -pub enum NostrLight { - Red, - Yellow, - Green, -} - -#[uniffi::remote(Record)] -pub struct NostrConnectionStatus { - pub light: NostrLight, - pub configured: bool, - pub source_available: bool, - pub sink_available: bool, - pub last_error: Option<String>, -} - -#[uniffi::remote(Record)] -pub struct NostrProfile { - pub name: Option<String>, - pub display_name: Option<String>, - pub nip05: Option<String>, - pub about: Option<String>, - pub website: Option<String>, - pub picture: Option<String>, - pub banner: Option<String>, - pub lud06: Option<String>, - pub lud16: Option<String>, - pub bot: Option<String>, -} - -#[uniffi::remote(Record)] -pub struct NostrProfileEventMetadata { - pub id: String, - pub author: String, - pub published_at: u64, - pub profile: NostrProfile, -} - -#[uniffi::remote(Record)] -pub struct NostrPost { - pub content: String, -} - -#[uniffi::remote(Record)] -pub struct NostrPostEventMetadata { - pub id: String, - pub author: String, - pub published_at: u64, - pub post: NostrPost, -} - #[uniffi::remote(Enum)] pub enum TodayCardType { Update, diff --git a/core/crates/tera_ffi/src/runtime.rs b/core/crates/tera_ffi/src/runtime.rs @@ -1,7 +1,5 @@ use radroots_mobile_core::runtime::{ info::RuntimeInfo, - key_management::{NostrHostCustodyIdentity, NostrIdentityRecord, NostrIdentitySnapshot}, - nostr::{NostrConnectionStatus, NostrPostEventMetadata, NostrProfileEventMetadata}, product_surface::{AddCommandType, CardAddParity, LocalNetwork, TodayCardType}, sdk::{SdkCapabilityRecord, SdkShutdownRecord, SdkStorageStatusRecord}, }; @@ -91,147 +89,6 @@ impl RadrootsRuntime { self.inner.sdk_storage_status().await.map_err(Into::into) } - pub fn nostr_identity_has_selected_signing_identity(&self) -> bool { - self.inner.nostr_identity_has_selected_signing_identity() - } - - pub fn nostr_identity_selected_npub(&self) -> Option<String> { - self.inner.nostr_identity_selected_npub() - } - - pub fn nostr_identity_list(&self) -> Result<Vec<NostrIdentityRecord>, RadrootsAppError> { - self.inner.nostr_identity_list().map_err(Into::into) - } - - pub fn nostr_identity_list_ids(&self) -> Result<Vec<String>, RadrootsAppError> { - self.inner.nostr_identity_list_ids().map_err(Into::into) - } - - pub fn nostr_identity_snapshot(&self) -> Result<NostrIdentitySnapshot, RadrootsAppError> { - self.inner.nostr_identity_snapshot().map_err(Into::into) - } - - pub fn nostr_identity_validate_host_custody_secret( - &self, - secret_key: String, - ) -> Result<NostrHostCustodyIdentity, RadrootsAppError> { - self.inner - .nostr_identity_validate_host_custody_secret(secret_key) - .map_err(Into::into) - } - - pub fn nostr_identity_restore_host_custody_secret( - &self, - secret_key: String, - label: Option<String>, - make_selected: bool, - ) -> Result<NostrIdentityRecord, RadrootsAppError> { - self.inner - .nostr_identity_restore_host_custody_secret(secret_key, label, make_selected) - .map_err(Into::into) - } - - pub fn nostr_identity_select(&self, identity_id: String) -> Result<(), RadrootsAppError> { - self.inner - .nostr_identity_select(identity_id) - .map_err(Into::into) - } - - pub fn nostr_identity_remove(&self, identity_id: String) -> Result<(), RadrootsAppError> { - self.inner - .nostr_identity_remove(identity_id) - .map_err(Into::into) - } - - pub fn nostr_identity_lock_host_custody_runtime(&self) -> Result<(), RadrootsAppError> { - self.inner - .nostr_identity_lock_host_custody_runtime() - .map_err(Into::into) - } - - pub fn nostr_identity_reset_host_custody_runtime(&self) -> Result<(), RadrootsAppError> { - self.inner - .nostr_identity_reset_host_custody_runtime() - .map_err(Into::into) - } - - pub fn nostr_set_default_relays(&self, relays: Vec<String>) -> Result<(), RadrootsAppError> { - self.inner - .nostr_set_default_relays(relays) - .map_err(Into::into) - } - - pub fn nostr_connect_if_key_present(&self) -> Result<(), RadrootsAppError> { - self.inner - .nostr_connect_if_key_present() - .map_err(Into::into) - } - - pub async fn nostr_connection_status(&self) -> Result<NostrConnectionStatus, RadrootsAppError> { - self.inner - .nostr_connection_status() - .await - .map_err(Into::into) - } - - pub async fn nostr_profile_for_self( - &self, - ) -> Result<Option<NostrProfileEventMetadata>, RadrootsAppError> { - self.inner - .nostr_profile_for_self() - .await - .map_err(Into::into) - } - - pub async fn nostr_post_profile( - &self, - name: Option<String>, - display_name: Option<String>, - nip05: Option<String>, - about: Option<String>, - ) -> Result<String, RadrootsAppError> { - self.inner - .nostr_post_profile(name, display_name, nip05, about) - .await - .map_err(Into::into) - } - - pub async fn nostr_post_text_note(&self, content: String) -> Result<String, RadrootsAppError> { - self.inner - .nostr_post_text_note(content) - .await - .map_err(Into::into) - } - - pub async fn nostr_fetch_text_notes( - &self, - limit: u16, - since_unix: Option<u64>, - ) -> Result<Vec<NostrPostEventMetadata>, RadrootsAppError> { - self.inner - .nostr_fetch_text_notes(limit, since_unix) - .await - .map_err(Into::into) - } - - pub async fn nostr_post_reply( - &self, - parent_event_id_hex: String, - parent_author_hex: String, - content: String, - root_event_id_hex: Option<String>, - ) -> Result<String, RadrootsAppError> { - self.inner - .nostr_post_reply( - parent_event_id_hex, - parent_author_hex, - content, - root_event_id_hex, - ) - .await - .map_err(Into::into) - } - pub fn phase1_card_types(&self) -> Vec<TodayCardType> { self.inner.phase1_card_types() } diff --git a/core/crates/tera_ffi/tests/runtime_delegation.rs b/core/crates/tera_ffi/tests/runtime_delegation.rs @@ -3,8 +3,6 @@ use radroots_mobile_ffi::RadrootsAppError; mod support; -const SECRET: &str = "0000000000000000000000000000000000000000000000000000000000000001"; - #[tokio::test] async fn native_boundary_delegates_the_complete_core_surface() { let (_root, runtime) = support::runtime().await; @@ -27,99 +25,6 @@ async fn native_boundary_delegates_the_complete_core_surface() { "sqlite" ); - assert!(!runtime.nostr_identity_has_selected_signing_identity()); - assert!(runtime.nostr_identity_selected_npub().is_none()); - assert!( - runtime - .nostr_identity_list() - .expect("empty list") - .is_empty() - ); - assert!( - runtime - .nostr_identity_list_ids() - .expect("empty identifiers") - .is_empty() - ); - assert!( - runtime - .nostr_identity_snapshot() - .expect("empty snapshot") - .identities - .is_empty() - ); - let validated = runtime - .nostr_identity_validate_host_custody_secret(SECRET.to_owned()) - .expect("valid secret"); - let staged = runtime - .nostr_identity_restore_host_custody_secret( - SECRET.to_owned(), - Some("staged".to_owned()), - false, - ) - .expect("staged identity"); - assert_eq!(validated.id, staged.id); - let selected = runtime - .nostr_identity_restore_host_custody_secret( - SECRET.to_owned(), - Some("selected".to_owned()), - true, - ) - .expect("selected identity"); - assert_eq!( - runtime.nostr_identity_selected_npub(), - Some(selected.public_key_npub.clone()) - ); - runtime - .nostr_identity_select(selected.id.clone()) - .expect("select installed"); - assert!(runtime.nostr_identity_select("missing".to_owned()).is_err()); - runtime - .nostr_identity_remove("missing".to_owned()) - .expect("removing missing identity is idempotent"); - runtime - .nostr_identity_lock_host_custody_runtime() - .expect("lock identity"); - runtime - .nostr_identity_reset_host_custody_runtime() - .expect("reset identity"); - - assert!(runtime.nostr_set_default_relays(Vec::new()).is_err()); - assert!(runtime.nostr_connect_if_key_present().is_err()); - assert!( - runtime - .nostr_connection_status() - .await - .expect("unconfigured status") - .last_error - .is_none() - ); - assert!(runtime.nostr_profile_for_self().await.is_err()); - assert!( - runtime - .nostr_post_profile(None, None, None, None) - .await - .is_err() - ); - assert!( - runtime - .nostr_post_text_note("post".to_owned()) - .await - .is_err() - ); - assert!(runtime.nostr_fetch_text_notes(1, None).await.is_err()); - assert!(matches!( - runtime - .nostr_post_reply( - "parent".to_owned(), - "author".to_owned(), - "reply".to_owned(), - Some("different-root".to_owned()), - ) - .await, - Err(RadrootsAppError::Unsupported(_)) - )); - assert_eq!( runtime.phase1_card_types(), vec![