diff --git a/core/account/src/account.rs b/core/account/src/account.rs index 82c8c7d..5a0c6ef 100644 --- a/core/account/src/account.rs +++ b/core/account/src/account.rs @@ -61,11 +61,14 @@ impl AccountRegistry for TestAccountService { let log = verify_log(addr, signed)?; log.live_entries() .iter() - .map(|data| match data { - // A signed log endorsing a non-key is the account's error, - // not a lookup miss — surface it rather than skip it. - EntryData::Ed25519Key(bytes) => Ed25519VerifyingKey::from_bytes(bytes) - .map_err(|_| AccountError::Generic("endorsed key is invalid".into())), + .filter_map(|data| match data { + EntryData::Ed25519Key(bytes) => Some( + // A signed log endorsing a non-key is the account's error, + // not a lookup miss — surface it rather than skip it. + Ed25519VerifyingKey::from_bytes(bytes) + .map_err(|_| AccountError::Generic("endorsed key is invalid".into())), + ), + EntryData::Text(_) => None, }) .collect::, _>>() .map(Some) diff --git a/core/account/src/codec.rs b/core/account/src/codec.rs index 919cd65..b1d243e 100644 --- a/core/account/src/codec.rs +++ b/core/account/src/codec.rs @@ -3,10 +3,11 @@ //! apart. The bytes are opaque to the server except for the fixed-offset //! header the extension check reads. -use crypto::Ed25519VerifyingKey; - +use crate::AccountAddr; +use crate::account_log::{ + AccountEntry, AccountLog, EncodedAccountLog, EntryData, SignedAccountLog, +}; use crate::error::AccountLogError; -use crate::account_log::{AccountEntry, AccountLog, EncodedAccountLog, EntryData, SignedAccountLog}; /// Domain-separation tag, prepended to every signed payload: /// @@ -193,10 +194,11 @@ fn split_at_checked(body: &[u8], mid: usize) -> Result<(&[u8], &[u8]), AccountLo /// account: another account's validly-signed log won't verify under this key, /// so an untrusted server cannot substitute one. pub fn verify_log( - expected_account: &Ed25519VerifyingKey, + expected_account: &AccountAddr, log: &SignedAccountLog, ) -> Result { expected_account + .verifying_key() .verify(log.payload.as_bytes(), &log.signature) .map_err(|_| AccountLogError::SignatureInvalid)?; Ok(log.payload.decode()) @@ -332,7 +334,7 @@ mod tests { #[test] fn verify_accepts_well_formed_log() { let account_key = Ed25519SigningKey::generate(); - let account_pub = account_key.verifying_key(); + let addr = AccountAddr::from(&account_key.verifying_key()); let log = make_log(vec![key(1), key(2)]); let payload = log.encode(); @@ -341,7 +343,7 @@ mod tests { payload, }; - assert_eq!(verify_log(&account_pub, &signed).unwrap(), log); + assert_eq!(verify_log(&addr, &signed).unwrap(), log); } /// A log validly signed by account A, served as the answer to a query for @@ -356,7 +358,7 @@ mod tests { payload, }; - let other = Ed25519SigningKey::generate().verifying_key(); + let other = AccountAddr::from(&Ed25519SigningKey::generate().verifying_key()); assert!(matches!( verify_log(&other, &signed), Err(AccountLogError::SignatureInvalid) @@ -367,7 +369,7 @@ mod tests { #[test] fn verify_rejects_swapped_payload() { let account_key = Ed25519SigningKey::generate(); - let account_pub = account_key.verifying_key(); + let addr = AccountAddr::from(&account_key.verifying_key()); let signature = account_key.sign(make_log(vec![key(1)]).encode().as_bytes()); let signed = SignedAccountLog { @@ -375,7 +377,7 @@ mod tests { signature, }; assert!(matches!( - verify_log(&account_pub, &signed), + verify_log(&addr, &signed), Err(AccountLogError::SignatureInvalid) )); } diff --git a/core/account/src/error.rs b/core/account/src/error.rs index bb5a846..fd2302d 100644 --- a/core/account/src/error.rs +++ b/core/account/src/error.rs @@ -8,6 +8,8 @@ pub enum AccountError { MissingEntry(String), #[error("invalid account address")] InvalidAddress, + #[error(transparent)] + Log(#[from] AccountLogError), } /// Failures decoding, verifying, or replaying an account log. diff --git a/core/account/src/lib.rs b/core/account/src/lib.rs index 794417f..8e20073 100644 --- a/core/account/src/lib.rs +++ b/core/account/src/lib.rs @@ -1,22 +1,55 @@ +//! Account identity and the signed account log. +//! +//! An account is known by its [`AccountAddr`], an opaque routable id. The +//! account endorses device keys and data by appending to an [`AccountLog`], +//! signed whole on every update and verifiable against the account's address +//! — see that module for the design and its invariants. +//! +//! Applications read account state through [`AccountRegistry`]. + #[cfg(feature = "dev")] mod account; +mod account_log; mod addr; mod codec; mod directory; mod error; -mod account_log; use crypto::Ed25519VerifyingKey; + +pub use account_log::{AccountEntry, AccountLog, EncodedAccountLog, EntryData, SignedAccountLog}; +pub use addr::AccountAddr; +pub use codec::{ACCOUNT_LOG_DOMAIN, verify_extension, verify_log}; +pub use error::{AccountError, AccountLogError}; + pub use directory::{ AccountDirectory, BUNDLE_VERSION, BundleError, DecodedBundle, DeviceId, DeviceSet, Lamport, ResolveError, SignedDeviceBundle, decode_bundle_payload, encode_bundle_payload, resolve_device_ids, verify_bundle, }; -pub use codec::{ACCOUNT_LOG_DOMAIN, verify_extension, verify_log}; -pub use error::{AccountError, AccountLogError}; -pub use account_log::{AccountEntry, AccountLog, EncodedAccountLog, EntryData, SignedAccountLog}; -pub use addr::AccountAddr; #[cfg(feature = "dev")] -pub use account::TestLogosAccount; +pub use account::{TestAccountService, TestLogosAccount}; +/// What applications may ask about any account. +pub trait AccountRegistry { + type Error: std::fmt::Display + std::fmt::Debug; + + /// Keys currently endorsed by `addr`. `Ok(None)`: account never published. + fn associated_ed25519_keys( + &self, + addr: &AccountAddr, + ) -> Result>, Self::Error>; + + /// Is `signer` currently endorsed by `addr`? Provided — one derivation, + /// so implementations cannot diverge on what "associated" means. + fn is_ed25519_associated( + &self, + signer: &Ed25519VerifyingKey, + addr: &AccountAddr, + ) -> Result { + Ok(self + .associated_ed25519_keys(addr)? + .is_some_and(|keys| keys.contains(signer))) + } +}