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 shared_traits::{IdentId, IdentIdRef, IdentityProvider};
|
||||||
pub use storage::ConversationKind;
|
pub use storage::ConversationKind;
|
||||||
pub use types::AddressedEnvelope;
|
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 }
|
chat-sqlite = { workspace = true }
|
||||||
components = { workspace = true }
|
components = { workspace = true }
|
||||||
crossbeam-channel = { workspace = true }
|
crossbeam-channel = { workspace = true }
|
||||||
|
crypto = { workspace = true }
|
||||||
libchat = { workspace = true }
|
libchat = { workspace = true }
|
||||||
logos-account = { workspace = true, features = ["dev"]}
|
logos-account = { workspace = true, features = ["dev"]}
|
||||||
|
shared-traits = { workspace = true }
|
||||||
|
|
||||||
# External dependencies (sorted)
|
# External dependencies (sorted)
|
||||||
|
hex = "0.4.3"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|||||||
@ -7,13 +7,13 @@ use libchat::{
|
|||||||
ChatError, ChatStorage, ConversationId, ConvoOutcome, Core, DeliveryService, IdentId,
|
ChatError, ChatStorage, ConversationId, ConvoOutcome, Core, DeliveryService, IdentId,
|
||||||
IdentIdRef, InboxOutcome, Introduction, PayloadOutcome, RegistrationService, StorageConfig,
|
IdentIdRef, InboxOutcome, Introduction, PayloadOutcome, RegistrationService, StorageConfig,
|
||||||
};
|
};
|
||||||
use logos_account::TestLogosAccount;
|
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
||||||
|
use crate::delegate::{self, DelegateCredential, DelegateSigner};
|
||||||
use crate::errors::ClientError;
|
use crate::errors::ClientError;
|
||||||
use crate::event::Event;
|
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 AccountAddressRef<'a> = &'a str;
|
||||||
type LocalSignerId = IdentId;
|
type LocalSignerId = IdentId;
|
||||||
|
|
||||||
@ -53,11 +53,12 @@ impl<T: Transport> ChatClient<T, EphemeralRegistry> {
|
|||||||
/// Create an in-memory, ephemeral client. Identity is lost on drop.
|
/// Create an in-memory, ephemeral client. Identity is lost on drop.
|
||||||
pub fn new(name: impl Into<String>, mut transport: T) -> (Self, Receiver<Event>) {
|
pub fn new(name: impl Into<String>, mut transport: T) -> (Self, Receiver<Event>) {
|
||||||
let inbound = transport.inbound();
|
let inbound = transport.inbound();
|
||||||
let ident = TestLogosAccount::new(name);
|
let delegate = DelegateSigner::random();
|
||||||
|
|
||||||
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
||||||
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
||||||
let core = Core::new_with_name(
|
let core = Core::new_with_name(
|
||||||
ident,
|
delegate,
|
||||||
transport,
|
transport,
|
||||||
EphemeralRegistry::new(),
|
EphemeralRegistry::new(),
|
||||||
wakeup_service,
|
wakeup_service,
|
||||||
@ -78,11 +79,11 @@ impl<T: Transport> ChatClient<T, EphemeralRegistry> {
|
|||||||
) -> Result<(Self, Receiver<Event>), ClientError> {
|
) -> Result<(Self, Receiver<Event>), ClientError> {
|
||||||
let store = ChatStorage::new(config).map_err(ChatError::from)?;
|
let store = ChatStorage::new(config).map_err(ChatError::from)?;
|
||||||
let inbound = transport.inbound();
|
let inbound = transport.inbound();
|
||||||
let ident = TestLogosAccount::new(name);
|
let delegate = DelegateSigner::random();
|
||||||
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
||||||
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
||||||
let core = Core::new_from_store(
|
let core = Core::new_from_store(
|
||||||
ident,
|
delegate,
|
||||||
transport,
|
transport,
|
||||||
EphemeralRegistry::new(),
|
EphemeralRegistry::new(),
|
||||||
wakeup_service,
|
wakeup_service,
|
||||||
@ -96,7 +97,7 @@ impl<T: Transport> ChatClient<T, EphemeralRegistry> {
|
|||||||
|
|
||||||
impl<T, R> ChatClient<T, R>
|
impl<T, R> ChatClient<T, R>
|
||||||
where
|
where
|
||||||
T: DeliveryService + Send + 'static,
|
T: Transport + Send + 'static,
|
||||||
R: RegistrationService + Send + 'static,
|
R: RegistrationService + Send + 'static,
|
||||||
{
|
{
|
||||||
/// Open or create a persistent client with a caller-supplied registration
|
/// 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 store = ChatStorage::new(config).map_err(ChatError::from)?;
|
||||||
let inbound = transport.inbound();
|
let inbound = transport.inbound();
|
||||||
let ident = TestLogosAccount::new(name);
|
let delegate = DelegateSigner::random();
|
||||||
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
let (wakeup_tx, wakeup_rx) = crossbeam_channel::unbounded();
|
||||||
let wakeup_service = ThreadedWakeupService::new(wakeup_tx);
|
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()?;
|
core.register_keypackage()?;
|
||||||
Ok(Self::spawn(core, inbound, wakeup_rx))
|
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(
|
fn spawn(
|
||||||
core: ClientCore<T, R>,
|
core: ClientCore<T, R>,
|
||||||
inbound: Receiver<Vec<u8>>,
|
inbound: Receiver<Vec<u8>>,
|
||||||
@ -290,9 +312,15 @@ fn events_from_inbound(result: PayloadOutcome) -> Vec<Event> {
|
|||||||
fn convo_events(outcome: ConvoOutcome) -> Vec<Event> {
|
fn convo_events(outcome: ConvoOutcome) -> Vec<Event> {
|
||||||
let ConvoOutcome { convo_id, content } = outcome;
|
let ConvoOutcome { convo_id, content } = outcome;
|
||||||
content
|
content
|
||||||
.map(|c| Event::MessageReceived {
|
.map(|c| {
|
||||||
convo_id: Arc::from(convo_id),
|
let data = hex::decode(c.encoded_credential).unwrap();
|
||||||
content: c.bytes,
|
let delegate_cred = DelegateCredential::from(data);
|
||||||
|
println!("{:?}", delegate_cred);
|
||||||
|
|
||||||
|
Event::MessageReceived {
|
||||||
|
convo_id: Arc::from(convo_id),
|
||||||
|
content: c.bytes,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect()
|
.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 {
|
pub enum ClientError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Chat(#[from] ChatError),
|
Chat(#[from] ChatError),
|
||||||
|
#[error("received credential could not be parsed")]
|
||||||
|
BadlyFormedCredential,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user