app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit 36ac0acccc2ad5c840b52146209479e6a49a761f
parent 4aaee761afcf03c633ba7a6a071c1126bf4b1b62
Author: triesap <tyson@radroots.org>
Date:   Mon,  3 Aug 2026 22:35:47 +0000

domain: adopt validated account aggregates

- replace legacy signer flags with canonical identity and local binding aggregates
- verify persisted public identity and reject unsupported signer records
- update application snapshots, repositories, transitions, and DTO projection
- migrate domain and integration coverage to typed binding availability

Diffstat:
Mcore/crates/application/src/accounts.rs | 44++++++++++++++++++++------------------------
Mcore/crates/application/src/snapshot.rs | 14++++++--------
Mcore/crates/application/src/state_machine.rs | 14++++++--------
Mcore/crates/application/tests/redaction.rs | 18+++++++++---------
Mcore/crates/domain/src/account.rs | 112++++++++++++++++++++++++++++++++++---------------------------------------------
Mcore/crates/domain/src/lib.rs | 2+-
Mcore/crates/ffi/src/dto.rs | 29++++++++---------------------
Mcore/crates/storage/src/account_namespace.rs | 14++++++--------
Mcore/crates/storage/src/accounts.rs | 74+++++++++++++++++++++++++++-----------------------------------------------
Mcore/crates/storage/src/application_adapter.rs | 14++++++--------
Mcore/crates/storage/src/profiles.rs | 13+++++--------
Mcore/crates/storage/tests/redaction.rs | 15+++++++--------
12 files changed, 149 insertions(+), 214 deletions(-)

diff --git a/core/crates/application/src/accounts.rs b/core/crates/application/src/accounts.rs @@ -1,8 +1,8 @@ use std::sync::{Mutex, MutexGuard}; use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, KeyAvailability, Nsec, PublicKey, SafeError, SafeErrorCode, - SafeMessage, SecretKeyInput, SignerKind, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + Nsec, PublicKey, SafeError, SafeErrorCode, SafeMessage, SecretKeyInput, }; use radroots_studio_nostr::{generate_local_keypair, import_secret}; @@ -96,7 +96,8 @@ impl AppCore { Ok(()) => {} Err(error) if error.code() == SafeErrorCode::CredentialMissing - && account.key_availability() == KeyAvailability::CredentialMissing => {} + && account.signer().availability() + == BindingAvailability::CredentialMissing => {} Err(error) => return Err(error), } journal.update_operation( @@ -154,14 +155,12 @@ impl AppCore { let generated = generate_local_keypair()?; let (public_key, npub, secret, nsec) = generated.into_parts(); let account = AccountSummary::new( - public_key, - npub, - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::verify(public_key, npub.as_str().to_owned())?, + LocalSignerBinding::new(public_key, BindingAvailability::Available), None, AccountCreatedAt::new(clock.now()), None, - ); + )?; Self::persist_account_transaction( AccountOperationKind::Add, &account, @@ -201,12 +200,12 @@ impl AppCore { let imported = import_secret(input)?; let (public_key, npub, secret) = imported.into_parts(); if let Some(existing) = accounts.find_account(public_key)? { - if existing.key_availability() != KeyAvailability::CredentialMissing + if existing.signer().availability() != BindingAvailability::CredentialMissing || secrets.contains(public_key)? { return Err(account_exists()); } - let repaired = existing.with_key_availability(KeyAvailability::Available); + let repaired = existing.with_binding_availability(BindingAvailability::Available); Self::persist_account_transaction( AccountOperationKind::Import, &repaired, @@ -228,14 +227,12 @@ impl AppCore { return Err(account_exists()); } let account = AccountSummary::new( - public_key, - npub, - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::verify(public_key, npub.as_str().to_owned())?, + LocalSignerBinding::new(public_key, BindingAvailability::Available), None, AccountCreatedAt::new(clock.now()), None, - ); + )?; Self::persist_account_transaction( AccountOperationKind::Import, &account, @@ -566,8 +563,8 @@ mod tests { use std::sync::atomic::{AtomicBool, Ordering}; use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, KeyAvailability, PublicKey, SafeError, SafeErrorCode, - SafeMessage, SecretKeyInput, SignerKind, UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + PublicKey, SafeError, SafeErrorCode, SafeMessage, SecretKeyInput, UnixTimestamp, }; use super::InMemoryAccountRepository; @@ -795,14 +792,13 @@ mod tests { let imported = radroots_studio_nostr::import_secret(input()).expect("derive"); let (public_key, npub, _) = imported.into_parts(); let missing = AccountSummary::new( - public_key, - npub, - SignerKind::LocalSecret, - KeyAvailability::CredentialMissing, + AccountIdentity::verify(public_key, npub.as_str().to_owned()).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::CredentialMissing), None, AccountCreatedAt::new(FixedClock.now()), None, - ); + ) + .expect("missing account"); accounts.insert_account(&missing).expect("missing metadata"); accounts .save_selected_account(Some(public_key)) @@ -824,8 +820,8 @@ mod tests { ) .expect("repair"); assert_eq!( - receipt.account().key_availability(), - KeyAvailability::Available + receipt.account().signer().availability(), + BindingAvailability::Available ); assert!(secrets.contains(public_key).expect("credential")); assert_eq!(core.snapshot().accounts().len(), 1); diff --git a/core/crates/application/src/snapshot.rs b/core/crates/application/src/snapshot.rs @@ -278,8 +278,8 @@ const fn invalid_snapshot() -> SafeError { #[cfg(test)] mod tests { use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, KeyAvailability, Npub, PublicKey, SignerKind, - UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + PublicKey, UnixTimestamp, }; use super::{ @@ -287,18 +287,16 @@ mod tests { RelayConnectionState, SessionState, SnapshotRevision, }; - const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; - fn account(key_byte: u8) -> AccountSummary { + let public_key = PublicKey::from_bytes([key_byte; 32]); AccountSummary::new( - PublicKey::from_bytes([key_byte; 32]), - Npub::from_encoded(NPUB.to_owned()).expect("valid npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::Available), None, AccountCreatedAt::new(UnixTimestamp::from_seconds(1).expect("valid time")), None, ) + .expect("account") } #[test] diff --git a/core/crates/application/src/state_machine.rs b/core/crates/application/src/state_machine.rs @@ -357,8 +357,8 @@ const fn account_not_found() -> SafeError { #[cfg(test)] mod tests { use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, KeyAvailability, Npub, PublicKey, SafeError, - SafeErrorCode, SafeMessage, SignerKind, UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + PublicKey, SafeError, SafeErrorCode, SafeMessage, UnixTimestamp, }; use crate::{ @@ -366,18 +366,16 @@ mod tests { SessionState, StateMachine, StateTransition, }; - const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; - fn account(key_byte: u8) -> AccountSummary { + let public_key = PublicKey::from_bytes([key_byte; 32]); AccountSummary::new( - PublicKey::from_bytes([key_byte; 32]), - Npub::from_encoded(NPUB.to_owned()).expect("valid npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::Available), None, AccountCreatedAt::new(UnixTimestamp::from_seconds(1).expect("valid time")), None, ) + .expect("account") } fn active(account: AccountSummary) -> ActiveAccountSnapshot { diff --git a/core/crates/application/tests/redaction.rs b/core/crates/application/tests/redaction.rs @@ -2,14 +2,12 @@ use radroots_studio_application::{ AppSnapshot, RelayConfiguration, SessionState, SnapshotRevision, }; use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, KeyAvailability, Npub, PublicKey, SafeError, SafeErrorCode, - SafeMessage, SignerKind, UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + PublicKey, SafeError, SafeErrorCode, SafeMessage, UnixTimestamp, }; const SECRET_HEX: &str = "1111111111111111111111111111111111111111111111111111111111111111"; const SECRET_NSEC: &str = "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5"; -const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; - fn assert_redacted(text: &str) { assert!(!text.contains(SECRET_HEX)); assert!(!text.contains(SECRET_NSEC)); @@ -19,14 +17,16 @@ fn assert_redacted(text: &str) { #[test] fn redaction_guards_public_snapshot_and_safe_error_debug() { let account = AccountSummary::new( - PublicKey::from_bytes([2; 32]), - Npub::from_encoded(NPUB.to_owned()).expect("npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(PublicKey::from_bytes([2; 32])).expect("identity"), + LocalSignerBinding::new( + PublicKey::from_bytes([2; 32]), + BindingAvailability::Available, + ), None, AccountCreatedAt::new(UnixTimestamp::from_seconds(1).expect("time")), None, - ); + ) + .expect("account"); let snapshot = AppSnapshot::ready( SnapshotRevision::from_value(1), RelayConfiguration::default(), diff --git a/core/crates/domain/src/account.rs b/core/crates/domain/src/account.rs @@ -154,21 +154,6 @@ pub enum BindingRepairAction { RetryCredentialStore, } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum SignerKind { - LocalSecret, - WatchOnly, - RemoteNip46, -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum KeyAvailability { - Available, - CredentialMissing, - StoreUnavailable, - NotRequired, -} - #[derive(Clone, Debug, Eq, PartialEq)] pub struct AccountLabel(String); @@ -213,55 +198,52 @@ impl AccountCreatedAt { #[derive(Clone, Debug, Eq, PartialEq)] pub struct AccountSummary { - public_key: PublicKey, - npub: Npub, - signer_kind: SignerKind, - key_availability: KeyAvailability, + identity: AccountIdentity, + signer: LocalSignerBinding, label: Option<AccountLabel>, created_at: AccountCreatedAt, last_used_at: Option<UnixTimestamp>, } impl AccountSummary { - #[must_use] - pub const fn new( - public_key: PublicKey, - npub: Npub, - signer_kind: SignerKind, - key_availability: KeyAvailability, + /// Creates an account summary whose identity and signer binding refer to the same account. + /// + /// # Errors + /// + /// Returns an invalid-account-metadata error when the signer binding belongs to a different + /// public key. + pub fn new( + identity: AccountIdentity, + signer: LocalSignerBinding, label: Option<AccountLabel>, created_at: AccountCreatedAt, last_used_at: Option<UnixTimestamp>, - ) -> Self { - Self { - public_key, - npub, - signer_kind, - key_availability, + ) -> Result<Self, SafeError> { + if identity.public_key() != signer.account() { + return Err(invalid_account_metadata()); + } + Ok(Self { + identity, + signer, label, created_at, last_used_at, - } + }) } #[must_use] pub const fn public_key(&self) -> PublicKey { - self.public_key + self.identity.public_key() } #[must_use] pub fn npub(&self) -> &Npub { - &self.npub + self.identity.npub() } #[must_use] - pub const fn signer_kind(&self) -> SignerKind { - self.signer_kind - } - - #[must_use] - pub const fn key_availability(&self) -> KeyAvailability { - self.key_availability + pub const fn signer(&self) -> LocalSignerBinding { + self.signer } #[must_use] @@ -280,12 +262,10 @@ impl AccountSummary { } #[must_use] - pub fn with_key_availability(&self, key_availability: KeyAvailability) -> Self { + pub fn with_binding_availability(&self, availability: BindingAvailability) -> Self { Self { - public_key: self.public_key, - npub: self.npub.clone(), - signer_kind: self.signer_kind, - key_availability, + identity: self.identity.clone(), + signer: LocalSignerBinding::new(self.public_key(), availability), label: self.label.clone(), created_at: self.created_at, last_used_at: self.last_used_at, @@ -295,10 +275,8 @@ impl AccountSummary { #[must_use] pub fn with_last_used_at(&self, last_used_at: UnixTimestamp) -> Self { Self { - public_key: self.public_key, - npub: self.npub.clone(), - signer_kind: self.signer_kind, - key_availability: self.key_availability, + identity: self.identity.clone(), + signer: self.signer, label: self.label.clone(), created_at: self.created_at, last_used_at: Some(last_used_at), @@ -309,7 +287,7 @@ impl AccountSummary { pub fn display_label(&self) -> String { self.label .as_ref() - .map_or_else(|| self.npub.short(), |label| label.as_str().to_owned()) + .map_or_else(|| self.npub().short(), |label| label.as_str().to_owned()) } } @@ -322,27 +300,27 @@ const fn invalid_account_metadata() -> SafeError { #[cfg(test)] mod tests { + use crate::PublicKey; use crate::time::UnixTimestamp; - use crate::{Npub, PublicKey}; use super::{ AccountCreatedAt, AccountIdentity, AccountLabel, AccountSummary, BindingAvailability, - BindingRepairAction, KeyAvailability, LocalSignerBinding, SignerKind, + BindingRepairAction, LocalSignerBinding, }; - const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; const DERIVED_NPUB: &str = "npub1qurswpc8qurswpc8qurswpc8qurswpc8qurswpc8qurswpc8qursnvjvl7"; + const MISMATCHED_NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; fn account(label: Option<AccountLabel>) -> AccountSummary { + let public_key = PublicKey::from_bytes([7_u8; 32]); AccountSummary::new( - PublicKey::from_bytes([7_u8; 32]), - Npub::from_encoded(NPUB.to_owned()).expect("valid npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::Available), label, AccountCreatedAt::new(UnixTimestamp::from_seconds(10).expect("valid time")), None, ) + .expect("account") } #[test] @@ -361,7 +339,7 @@ mod tests { let unlabelled = account(None); assert_eq!(labelled.display_label(), "Farm"); - assert_eq!(unlabelled.display_label(), "npub10elfcs4…8qzvjptg"); + assert_eq!(unlabelled.display_label(), "npub1qurswpc…rsnvjvl7"); } #[test] @@ -369,13 +347,15 @@ mod tests { let account = account(None); let debug = format!("{account:?}"); - assert_eq!(account.signer_kind(), SignerKind::LocalSecret); - assert_eq!(account.key_availability(), KeyAvailability::Available); + assert_eq!( + account.signer().availability(), + BindingAvailability::Available + ); assert!(account.label().is_none()); assert!(account.last_used_at().is_none()); assert_eq!(account.created_at().timestamp().as_seconds(), 10); assert_eq!(account.public_key(), PublicKey::from_bytes([7_u8; 32])); - assert_eq!(account.npub().as_str(), NPUB); + assert_eq!(account.npub().as_str(), DERIVED_NPUB); assert!(!debug.contains("nsec1")); assert!(!debug.contains(&"11".repeat(32))); } @@ -390,9 +370,13 @@ mod tests { AccountIdentity::verify(public_key, DERIVED_NPUB.to_owned()).expect("verified"), identity ); - assert!(AccountIdentity::verify(public_key, NPUB.to_owned()).is_err()); + assert!(AccountIdentity::verify(public_key, MISMATCHED_NPUB.to_owned()).is_err()); assert!( - AccountIdentity::verify(PublicKey::from_bytes([8_u8; 32]), NPUB.to_owned()).is_err() + AccountIdentity::verify( + PublicKey::from_bytes([8_u8; 32]), + MISMATCHED_NPUB.to_owned() + ) + .is_err() ); } diff --git a/core/crates/domain/src/lib.rs b/core/crates/domain/src/lib.rs @@ -9,7 +9,7 @@ pub mod time; pub use account::{ AccountCreatedAt, AccountIdentity, AccountLabel, AccountSummary, BindingAvailability, - BindingRepairAction, KeyAvailability, LocalSignerBinding, SignerKind, + BindingRepairAction, LocalSignerBinding, }; pub use error::{SafeError, SafeErrorCode, SafeMessage}; pub use key::{Npub, Nsec, PublicKey, SecretKeyInput, SecretKeyInputKind}; diff --git a/core/crates/ffi/src/dto.rs b/core/crates/ffi/src/dto.rs @@ -2,9 +2,7 @@ use radroots_studio_application::{ ActiveAccountSnapshot, AppLifecycle, AppSnapshot, ProfileLoadState, RelayConnectionState, SessionState, }; -use radroots_studio_domain::{ - AccountSummary, KeyAvailability, ProfileMetadata, SafeError, SignerKind, -}; +use radroots_studio_domain::{AccountSummary, BindingAvailability, ProfileMetadata, SafeError}; #[derive(Clone, Debug, Eq, PartialEq, uniffi::Record)] pub struct SafeErrorDto { @@ -149,8 +147,8 @@ impl From<&AccountSummary> for AccountDto { public_key_hex: account.public_key().to_hex(), npub: account.npub().as_str().to_owned(), display_label: account.display_label(), - signer_kind: account.signer_kind().into(), - key_availability: account.key_availability().into(), + signer_kind: SignerKindDto::LocalSecret, + key_availability: account.signer().availability().into(), created_at_seconds: account.created_at().timestamp().as_seconds(), last_used_at_seconds: account .last_used_at() @@ -191,23 +189,12 @@ impl From<SafeError> for SafeErrorDto { } } -impl From<SignerKind> for SignerKindDto { - fn from(value: SignerKind) -> Self { - match value { - SignerKind::LocalSecret => Self::LocalSecret, - SignerKind::WatchOnly => Self::WatchOnly, - SignerKind::RemoteNip46 => Self::RemoteNip46, - } - } -} - -impl From<KeyAvailability> for KeyAvailabilityDto { - fn from(value: KeyAvailability) -> Self { +impl From<BindingAvailability> for KeyAvailabilityDto { + fn from(value: BindingAvailability) -> Self { match value { - KeyAvailability::Available => Self::Available, - KeyAvailability::CredentialMissing => Self::CredentialMissing, - KeyAvailability::StoreUnavailable => Self::StoreUnavailable, - KeyAvailability::NotRequired => Self::NotRequired, + BindingAvailability::Available => Self::Available, + BindingAvailability::CredentialMissing => Self::CredentialMissing, + BindingAvailability::StoreUnavailable => Self::StoreUnavailable, } } } diff --git a/core/crates/storage/src/account_namespace.rs b/core/crates/storage/src/account_namespace.rs @@ -80,24 +80,22 @@ mod tests { AccountNamespaceRepository, AccountPreferenceKey, AccountRepository, AppStateRepository, }; use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, KeyAvailability, Npub, PublicKey, SignerKind, - UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + PublicKey, UnixTimestamp, }; use crate::Database; - const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; - fn account(byte: u8) -> AccountSummary { + let public_key = PublicKey::from_bytes([byte; 32]); AccountSummary::new( - PublicKey::from_bytes([byte; 32]), - Npub::from_encoded(NPUB.to_owned()).expect("npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::Available), None, AccountCreatedAt::new(UnixTimestamp::from_seconds(i64::from(byte)).expect("time")), None, ) + .expect("account") } #[test] diff --git a/core/crates/storage/src/accounts.rs b/core/crates/storage/src/accounts.rs @@ -1,7 +1,7 @@ use radroots_studio_application::{AccountRepository, AppStateRepository}; use radroots_studio_domain::{ - AccountCreatedAt, AccountLabel, AccountSummary, KeyAvailability, Npub, PublicKey, SafeError, - SafeErrorCode, SafeMessage, SignerKind, UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountLabel, AccountSummary, BindingAvailability, + LocalSignerBinding, PublicKey, SafeError, SafeErrorCode, SafeMessage, UnixTimestamp, }; use rusqlite::{OptionalExtension, Row, params}; @@ -135,8 +135,8 @@ impl From<&AccountSummary> for EncodedAccount { Self { public_key: account.public_key().to_hex(), npub: account.npub().as_str().to_owned(), - signer_kind: encode_signer_kind(account.signer_kind()), - key_availability: encode_key_availability(account.key_availability()), + signer_kind: "local_secret", + key_availability: encode_key_availability(account.signer().availability()), label: account.label().map(|label| label.as_str().to_owned()), created_at: account.created_at().timestamp().as_seconds(), last_used_at: account.last_used_at().map(UnixTimestamp::as_seconds), @@ -147,8 +147,10 @@ impl From<&AccountSummary> for EncodedAccount { fn decode_account(row: &Row<'_>) -> rusqlite::Result<AccountSummary> { let public_key = PublicKey::from_hex(row.get::<_, String>(0)?.as_str()).map_err(|_| invalid_column(0))?; - let npub = Npub::from_encoded(row.get(1)?).map_err(|_| invalid_column(1))?; - let signer_kind = decode_signer_kind(row.get::<_, String>(2)?.as_str())?; + let npub: String = row.get(1)?; + if row.get::<_, String>(2)?.as_str() != "local_secret" { + return Err(invalid_column(2)); + } let key_availability = decode_key_availability(row.get::<_, String>(3)?.as_str())?; let label = row .get::<_, Option<String>>(4)? @@ -160,49 +162,29 @@ fn decode_account(row: &Row<'_>) -> rusqlite::Result<AccountSummary> { .map(|value| UnixTimestamp::from_seconds(value).ok_or_else(|| invalid_column(6))) .transpose()?; - Ok(AccountSummary::new( - public_key, - npub, - signer_kind, - key_availability, + AccountSummary::new( + AccountIdentity::verify(public_key, npub).map_err(|_| invalid_column(1))?, + LocalSignerBinding::new(public_key, key_availability), label, AccountCreatedAt::new(created_at), last_used_at, - )) -} - -const fn encode_signer_kind(value: SignerKind) -> &'static str { - match value { - SignerKind::LocalSecret => "local_secret", - SignerKind::WatchOnly => "watch_only", - SignerKind::RemoteNip46 => "remote_nip46", - } -} - -fn decode_signer_kind(value: &str) -> rusqlite::Result<SignerKind> { - match value { - "local_secret" => Ok(SignerKind::LocalSecret), - "watch_only" => Ok(SignerKind::WatchOnly), - "remote_nip46" => Ok(SignerKind::RemoteNip46), - _ => Err(invalid_column(2)), - } + ) + .map_err(|_| invalid_column(0)) } -const fn encode_key_availability(value: KeyAvailability) -> &'static str { +const fn encode_key_availability(value: BindingAvailability) -> &'static str { match value { - KeyAvailability::Available => "available", - KeyAvailability::CredentialMissing => "credential_missing", - KeyAvailability::StoreUnavailable => "store_unavailable", - KeyAvailability::NotRequired => "not_required", + BindingAvailability::Available => "available", + BindingAvailability::CredentialMissing => "credential_missing", + BindingAvailability::StoreUnavailable => "store_unavailable", } } -fn decode_key_availability(value: &str) -> rusqlite::Result<KeyAvailability> { +fn decode_key_availability(value: &str) -> rusqlite::Result<BindingAvailability> { match value { - "available" => Ok(KeyAvailability::Available), - "credential_missing" => Ok(KeyAvailability::CredentialMissing), - "store_unavailable" => Ok(KeyAvailability::StoreUnavailable), - "not_required" => Ok(KeyAvailability::NotRequired), + "available" => Ok(BindingAvailability::Available), + "credential_missing" => Ok(BindingAvailability::CredentialMissing), + "store_unavailable" => Ok(BindingAvailability::StoreUnavailable), _ => Err(invalid_column(3)), } } @@ -262,27 +244,25 @@ mod tests { use radroots_studio_application::{AccountRepository, AppStateRepository}; use radroots_studio_domain::{ - AccountCreatedAt, AccountLabel, AccountSummary, KeyAvailability, Npub, PublicKey, - SafeErrorCode, SignerKind, UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountLabel, AccountSummary, BindingAvailability, + LocalSignerBinding, PublicKey, SafeErrorCode, UnixTimestamp, }; use tempfile::tempdir; use crate::Database; - const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; - fn account(key_byte: u8, created_at: i64) -> AccountSummary { + let public_key = PublicKey::from_bytes([key_byte; 32]); AccountSummary::new( - PublicKey::from_bytes([key_byte; 32]), - Npub::from_encoded(NPUB.to_owned()).expect("valid npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::Available), Some(AccountLabel::parse("Farm account").expect("valid label")), AccountCreatedAt::new( UnixTimestamp::from_seconds(created_at).expect("valid timestamp"), ), None, ) + .expect("account") } #[test] diff --git a/core/crates/storage/src/application_adapter.rs b/core/crates/storage/src/application_adapter.rs @@ -193,25 +193,23 @@ mod tests { RelayConfiguration, SecretStore, SecretStoreOperation, SessionState, }; use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, KeyAvailability, Npub, PublicKey, SafeErrorCode, - SecretKeyInput, SignerKind, UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + PublicKey, SafeErrorCode, SecretKeyInput, UnixTimestamp, }; use tempfile::tempdir; use super::PersistentAppCore; - const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; - fn account() -> AccountSummary { + let public_key = PublicKey::from_bytes([4; 32]); AccountSummary::new( - PublicKey::from_bytes([4; 32]), - Npub::from_encoded(NPUB.to_owned()).expect("npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::Available), None, AccountCreatedAt::new(UnixTimestamp::from_seconds(1).expect("time")), None, ) + .expect("account") } struct FixedClock; diff --git a/core/crates/storage/src/profiles.rs b/core/crates/storage/src/profiles.rs @@ -150,24 +150,21 @@ mod tests { AccountRepository, CachedProfile, ProfileRefreshStatus, ProfileRepository, }; use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, EventId, KeyAvailability, Kind0ProfileCandidate, Npub, - ProfileMetadata, PublicKey, SignerKind, UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, EventId, + Kind0ProfileCandidate, LocalSignerBinding, ProfileMetadata, PublicKey, UnixTimestamp, }; use crate::Database; - const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; - fn account(public_key: PublicKey) -> AccountSummary { AccountSummary::new( - public_key, - Npub::from_encoded(NPUB.to_owned()).expect("npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::Available), None, AccountCreatedAt::new(UnixTimestamp::from_seconds(1).expect("time")), None, ) + .expect("account") } fn profile(public_key: PublicKey, id: u8, created_at: i64, name: &str) -> CachedProfile { diff --git a/core/crates/storage/tests/redaction.rs b/core/crates/storage/tests/redaction.rs @@ -2,15 +2,14 @@ use std::fs; use radroots_studio_application::{AccountOperationKind, AccountRepository, OperationJournal}; use radroots_studio_domain::{ - AccountCreatedAt, AccountSummary, KeyAvailability, Npub, PublicKey, SignerKind, UnixTimestamp, + AccountCreatedAt, AccountIdentity, AccountSummary, BindingAvailability, LocalSignerBinding, + PublicKey, UnixTimestamp, }; use radroots_studio_storage::Database; use tempfile::tempdir; const SECRET_HEX: &str = "1111111111111111111111111111111111111111111111111111111111111111"; const SECRET_NSEC: &str = "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5"; -const NPUB: &str = "npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg"; - fn assert_redacted(bytes: &[u8]) { assert!( !bytes @@ -31,15 +30,15 @@ fn redaction_guards_sqlite_schema_and_non_secret_records() { let path = directory.path().join("studio.sqlite3"); { let database = Database::open(&path).expect("database"); + let public_key = PublicKey::from_bytes([2; 32]); let account = AccountSummary::new( - PublicKey::from_bytes([2; 32]), - Npub::from_encoded(NPUB.to_owned()).expect("npub"), - SignerKind::LocalSecret, - KeyAvailability::Available, + AccountIdentity::derive(public_key).expect("identity"), + LocalSignerBinding::new(public_key, BindingAvailability::Available), None, AccountCreatedAt::new(UnixTimestamp::from_seconds(1).expect("time")), None, - ); + ) + .expect("account"); database.insert_account(&account).expect("account"); database .begin_operation(