mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-06-30 21:20:09 +00:00
Add DelegateSigner
This commit is contained in:
parent
d99c09b57d
commit
c5ee262e33
@ -31,4 +31,4 @@ pub use service_traits::{DeliveryService, RegistrationService, WakeupService};
|
||||
pub use shared_traits::{IdentId, IdentIdRef, IdentityProvider};
|
||||
pub use storage::ConversationKind;
|
||||
pub use types::AddressedEnvelope;
|
||||
pub use utils::hex_trunc;
|
||||
pub use utils::{hex_trunc, trunc};
|
||||
|
||||
@ -71,3 +71,12 @@ pub fn hex_trunc(data: &[u8]) -> String {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn trunc(data: &str) -> String {
|
||||
if data.len() <= 8 {
|
||||
data.to_string()
|
||||
} else {
|
||||
format!("{}..{}", &data[..4], &data[data.len() - 4..])
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,10 +11,13 @@ crate-type = ["rlib"]
|
||||
chat-sqlite = { workspace = true }
|
||||
components = { workspace = true }
|
||||
crossbeam-channel = { workspace = true }
|
||||
crypto = { workspace = true }
|
||||
libchat = { workspace = true }
|
||||
logos-account = { workspace = true, features = ["dev"]}
|
||||
shared-traits = { workspace = true }
|
||||
|
||||
# External dependencies (sorted)
|
||||
hex = "0.4.3"
|
||||
parking_lot = "0.12"
|
||||
thiserror = "2"
|
||||
tracing = "0.1"
|
||||
|
||||
@ -7,13 +7,13 @@ use libchat::{
|
||||
ChatError, ChatStorage, ConversationId, ConvoOutcome, Core, DeliveryService, IdentId,
|
||||
IdentIdRef, InboxOutcome, Introduction, PayloadOutcome, RegistrationService, StorageConfig,
|
||||
};
|
||||
use logos_account::TestLogosAccount;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::delegate::{self, DelegateCredential, DelegateSigner};
|
||||
use crate::errors::ClientError;
|
||||
use crate::event::Event;
|
||||
|
||||
type ClientCore<T, R> = Core<(TestLogosAccount, T, R, ThreadedWakeupService, ChatStorage)>;
|
||||
type ClientCore<T, R> = Core<(DelegateSigner, T, R, ThreadedWakeupService, ChatStorage)>;
|
||||
type AccountAddressRef<'a> = &'a str;
|
||||
type LocalSignerId = IdentId;
|
||||
|
||||
@ -53,11 +53,12 @@ impl<T: Transport> ChatClient<T, EphemeralRegistry> {
|
||||
/// Create an in-memory, ephemeral client. Identity is lost on drop.
|
||||
pub fn new(name: impl Into<String>, mut transport: T) -> (Self, Receiver<Event>) {
|
||||
let inbound = transport.inbound();
|
||||
let ident = TestLogosAccount::new(name);
|
||||
let delegate = DelegateSigner::random();
|
||||
|
||||
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
||||
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
||||
let core = Core::new_with_name(
|
||||
ident,
|
||||
delegate,
|
||||
transport,
|
||||
EphemeralRegistry::new(),
|
||||
wakeup_service,
|
||||
@ -78,11 +79,11 @@ impl<T: Transport> ChatClient<T, EphemeralRegistry> {
|
||||
) -> Result<(Self, Receiver<Event>), ClientError> {
|
||||
let store = ChatStorage::new(config).map_err(ChatError::from)?;
|
||||
let inbound = transport.inbound();
|
||||
let ident = TestLogosAccount::new(name);
|
||||
let delegate = DelegateSigner::random();
|
||||
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
||||
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
||||
let core = Core::new_from_store(
|
||||
ident,
|
||||
delegate,
|
||||
transport,
|
||||
EphemeralRegistry::new(),
|
||||
wakeup_service,
|
||||
@ -96,7 +97,7 @@ impl<T: Transport> ChatClient<T, EphemeralRegistry> {
|
||||
|
||||
impl<T, R> ChatClient<T, R>
|
||||
where
|
||||
T: DeliveryService + Send + 'static,
|
||||
T: Transport + Send + 'static,
|
||||
R: RegistrationService + Send + 'static,
|
||||
{
|
||||
/// Open or create a persistent client with a caller-supplied registration
|
||||
@ -118,14 +119,35 @@ where
|
||||
{
|
||||
let store = ChatStorage::new(config).map_err(ChatError::from)?;
|
||||
let inbound = transport.inbound();
|
||||
let ident = TestLogosAccount::new(name);
|
||||
let delegate = DelegateSigner::random();
|
||||
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
||||
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
||||
let mut core = Core::new_from_store(ident, transport, registry, wakeup_service, store)?;
|
||||
let mut core = Core::new_from_store(delegate, transport, registry, wakeup_service, store)?;
|
||||
core.register_keypackage()?;
|
||||
Ok(Self::spawn(core, inbound, wakeup_rx))
|
||||
}
|
||||
|
||||
/// Create a client with ephemeral storage with the provided Transport and RegistrationService.
|
||||
pub fn new_ephemeral(
|
||||
delegate: DelegateSigner,
|
||||
mut transport: T,
|
||||
reg: R,
|
||||
) -> (Self, Receiver<Event>) {
|
||||
let inbound = transport.inbound();
|
||||
|
||||
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
||||
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
||||
let core = Core::new_with_name(
|
||||
delegate,
|
||||
transport,
|
||||
reg,
|
||||
wakeup_service,
|
||||
ChatStorage::in_memory(),
|
||||
)
|
||||
.unwrap();
|
||||
Self::spawn(core, inbound, wakeup_rx)
|
||||
}
|
||||
|
||||
fn spawn(
|
||||
core: ClientCore<T, R>,
|
||||
inbound: Receiver<Vec<u8>>,
|
||||
@ -290,9 +312,15 @@ fn events_from_inbound(result: PayloadOutcome) -> Vec<Event> {
|
||||
fn convo_events(outcome: ConvoOutcome) -> Vec<Event> {
|
||||
let ConvoOutcome { convo_id, content } = outcome;
|
||||
content
|
||||
.map(|c| Event::MessageReceived {
|
||||
convo_id: Arc::from(convo_id),
|
||||
content: c.bytes,
|
||||
.map(|c| {
|
||||
let data = hex::decode(c.encoded_credential).unwrap();
|
||||
let delegate_cred = DelegateCredential::from(data);
|
||||
println!("{:?}", delegate_cred);
|
||||
|
||||
Event::MessageReceived {
|
||||
convo_id: Arc::from(convo_id),
|
||||
content: c.bytes,
|
||||
}
|
||||
})
|
||||
.into_iter()
|
||||
.collect()
|
||||
|
||||
151
crates/client/src/delegate.rs
Normal file
151
crates/client/src/delegate.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use crypto::{Ed25519SigningKey, Ed25519VerifyingKey};
|
||||
use libchat::{IdentId, IdentityProvider};
|
||||
|
||||
use crate::ClientError;
|
||||
|
||||
type AccountAddr = String;
|
||||
|
||||
pub struct DelegateSigner {
|
||||
signing_key: Ed25519SigningKey,
|
||||
verifying_key: Ed25519VerifyingKey,
|
||||
identifier: IdentId,
|
||||
account_addr: Option<AccountAddr>,
|
||||
}
|
||||
|
||||
impl DelegateSigner {
|
||||
pub fn random() -> Self {
|
||||
let signing_key = Ed25519SigningKey::generate();
|
||||
let verifying_key = signing_key.verifying_key();
|
||||
let identifier = DelegateCredential::unassociated(&verifying_key).into();
|
||||
Self {
|
||||
signing_key,
|
||||
verifying_key,
|
||||
identifier,
|
||||
account_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn associate(&mut self, account_addr: AccountAddr) {
|
||||
self.identifier =
|
||||
DelegateCredential::associated(&self.verifying_key, account_addr.as_str()).into();
|
||||
self.account_addr = Some(account_addr);
|
||||
}
|
||||
|
||||
pub fn account_addr(&self) -> Option<&str> {
|
||||
self.account_addr.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl IdentityProvider for DelegateSigner {
|
||||
fn id(&self) -> libchat::IdentIdRef<'_> {
|
||||
&self.identifier
|
||||
}
|
||||
|
||||
fn display_name(&self) -> String {
|
||||
trunc(self.identifier.as_str())
|
||||
}
|
||||
|
||||
fn sign(&self, payload: &[u8]) -> crypto::Ed25519Signature {
|
||||
self.signing_key.sign(payload)
|
||||
}
|
||||
|
||||
fn public_key(&self) -> &Ed25519VerifyingKey {
|
||||
&self.verifying_key
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the senders information for received frames.
|
||||
#[derive(Debug)]
|
||||
pub struct DelegateCredential {
|
||||
delegate_id: Ed25519VerifyingKey,
|
||||
account_addr: Option<AccountAddr>,
|
||||
}
|
||||
|
||||
impl DelegateCredential {
|
||||
const TAG_DELEGATE_ID: u8 = 0x01;
|
||||
const TAG_ACCOUNT_ADDR: u8 = 0x02;
|
||||
|
||||
pub fn unassociated(delegate: &Ed25519VerifyingKey) -> Self {
|
||||
Self {
|
||||
delegate_id: delegate.clone(),
|
||||
account_addr: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn associated(delegate: &Ed25519VerifyingKey, account: &str) -> Self {
|
||||
Self {
|
||||
delegate_id: delegate.clone(),
|
||||
account_addr: Some(account.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_vec(self) -> Vec<u8> {
|
||||
let mut data = Vec::new();
|
||||
data.extend_from_slice(&[0x23, 0x23]);
|
||||
let key_bytes = self.delegate_id.as_ref();
|
||||
data.extend_from_slice(&[Self::TAG_DELEGATE_ID, key_bytes.len() as u8]);
|
||||
data.extend_from_slice(key_bytes);
|
||||
if let Some(addr) = self.account_addr {
|
||||
let addr_bytes = addr.as_bytes();
|
||||
data.extend_from_slice(&[Self::TAG_ACCOUNT_ADDR, addr_bytes.len() as u8]);
|
||||
data.extend_from_slice(addr_bytes);
|
||||
}
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DelegateCredential> for Vec<u8> {
|
||||
fn from(value: DelegateCredential) -> Self {
|
||||
value.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for DelegateCredential {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
assert_eq!(&value[..2], &[0x23, 0x23], "invalid magic bytes");
|
||||
let mut delegate_id = None;
|
||||
let mut account_addr = None;
|
||||
let mut i = 2;
|
||||
while i + 2 <= value.len() {
|
||||
let tag = value[i];
|
||||
let len = value[i + 1] as usize;
|
||||
i += 2;
|
||||
let v = &value[i..i + len];
|
||||
i += len;
|
||||
match tag {
|
||||
DelegateCredential::TAG_DELEGATE_ID => {
|
||||
let bytes: &[u8; 32] = v.try_into().expect("invalid delegate_id length");
|
||||
delegate_id = Some(
|
||||
Ed25519VerifyingKey::from_bytes(bytes).expect("invalid verifying key"),
|
||||
);
|
||||
}
|
||||
DelegateCredential::TAG_ACCOUNT_ADDR => {
|
||||
account_addr =
|
||||
Some(String::from_utf8(v.to_vec()).expect("invalid account_addr utf8"));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Self {
|
||||
delegate_id: delegate_id.expect("missing delegate_id TLV field"),
|
||||
account_addr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DelegateCredential> for IdentId {
|
||||
fn from(value: DelegateCredential) -> Self {
|
||||
IdentId::new(hex::encode(value.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<IdentId> for DelegateCredential {
|
||||
type Error = ClientError;
|
||||
|
||||
fn try_from(value: IdentId) -> Result<Self, Self::Error> {
|
||||
Ok(hex::decode(value.as_str())
|
||||
.map_err(|e| ClientError::BadlyFormedCredential)?
|
||||
.into())
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,4 +4,6 @@ use libchat::ChatError;
|
||||
pub enum ClientError {
|
||||
#[error(transparent)]
|
||||
Chat(#[from] ChatError),
|
||||
#[error("received credential could not be parsed")]
|
||||
BadlyFormedCredential,
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user