mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-08-25 15:11:15 +00:00
Use AccountAddr as the API type instead of Ed25519VerifyingKey
- verify_log verifies against an address; the wrapped key is crate-private - AccountRegistry answers in keys, keyed by address; the log stays internal - crate doc presents the address as an opaque routable id - AccountError::Log converts log errors at the API boundary Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cb19632207
commit
3e11bfdd50
@@ -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::<Result<Vec<_>, _>>()
|
||||
.map(Some)
|
||||
|
||||
@@ -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<AccountLog, AccountLogError> {
|
||||
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)
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
+39
-6
@@ -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<Option<Vec<Ed25519VerifyingKey>>, 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<bool, Self::Error> {
|
||||
Ok(self
|
||||
.associated_ed25519_keys(addr)?
|
||||
.is_some_and(|keys| keys.contains(signer)))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user