use std::sync::Arc;
use crate::errors::HopError;
use crate::internal::crypto::{
generate_keypair, hash, sign, signing_key_from_ticket_secret, SignatureScheme,
};
use crate::internal::resolver::{self, HopEnvironment};
use crate::internal::scale_multi::{encode_multi_signature, encode_multi_signer};
use crate::internal::ticket_internals::{pack_ticket, unpack_hash, unpack_scheme, unpack_secret};
use crate::internal::transport::WsJsonRpcTransport;
use crate::ticket::HopTicket;
const MAX_DATA_SIZE: usize = 64 * 1024 * 1024; const SCHEME: SignatureScheme = SignatureScheme::Sr25519;
pub struct HopClient {
runtime: tokio::runtime::Runtime,
transport: Arc<WsJsonRpcTransport>,
}
impl HopClient {
pub fn connect(env: HopEnvironment) -> Result<Self, HopError> {
let endpoint = resolver::resolve(env)?;
let runtime = tokio::runtime::Runtime::new()
.map_err(|e| HopError::Network(format!("Failed to create runtime: {e}")))?;
let transport = runtime.block_on(async { WsJsonRpcTransport::connect(&endpoint).await })?;
Ok(Self {
runtime,
transport: Arc::new(transport),
})
}
pub fn send(&self, data: Vec<u8>, recipient_count: u32) -> Result<Vec<Arc<HopTicket>>, HopError> {
if data.is_empty() {
return Err(HopError::DataTooLarge("Data must not be empty".into()));
}
if data.len() > MAX_DATA_SIZE {
return Err(HopError::DataTooLarge(format!(
"Data is {} bytes, max is {} bytes (64 MiB)",
data.len(),
MAX_DATA_SIZE
)));
}
let count = recipient_count as usize;
let keypairs: Vec<_> = (0..count).map(|_| generate_keypair(SCHEME)).collect();
let encoded_keys: Vec<Vec<u8>> = keypairs
.iter()
.map(|kp| encode_multi_signer(&kp.public_key, kp.scheme))
.collect();
let proof = Vec::new();
self.runtime.block_on(async {
self.transport.submit(&data, &encoded_keys, &proof).await
})?;
let data_hash = hash(&data);
let tickets: Vec<Arc<HopTicket>> = keypairs
.iter()
.map(|kp| {
let mut secret = [0u8; 32];
secret.copy_from_slice(&kp.ticket_secret);
let raw = pack_ticket(kp.scheme, &data_hash, &secret);
Arc::new(HopTicket::from_raw(raw))
})
.collect();
Ok(tickets)
}
pub fn claim(&self, ticket: Arc<HopTicket>) -> Result<Vec<u8>, HopError> {
let raw = ticket.raw();
let scheme = unpack_scheme(raw)
.ok_or_else(|| HopError::InvalidTicket("Unknown scheme byte".into()))?;
let data_hash = unpack_hash(raw);
let ticket_secret = unpack_secret(raw);
let signing_key = signing_key_from_ticket_secret(&ticket_secret, scheme);
let raw_sig = sign(&data_hash, &signing_key, scheme);
let signature = encode_multi_signature(&raw_sig, scheme);
self.runtime
.block_on(async { self.transport.claim(&data_hash, &signature).await })
}
pub fn destroy(&self) {
self.runtime.block_on(async {
self.transport.close().await;
});
}
}