Online message relay setup
Setup message relay server
The message relay server is a HTTP server that handled Websocket communication between 2-mobiles, which is written in Rust and built with Axum framework.
Step 0: Installation
- Install Rust
- Create Rust binary project.
- Add prerequisites crates in your
Cargo.toml:
[dependencies]
sl-mpc-mate = {version = "1.0.0-beta", features = ["simple-relay"]}
anyhow = "1"
axum = { version = "0.8.4", features = ["ws", "tokio", "http1"] }
futures-util = { version = "0.3.30", default-features = false }
tokio = { version = "1.43.0", features = [
"rt",
"rt-multi-thread",
"macros",
"signal",
] }
tokio-tungstenite = { version = "0.26" }
tower-http = { version = "0.6.2", features = ["trace", "cors"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt"] }
Step 1: Add module to handles Websocket message relay requests
- Create
src/relay_svc.rsfile, then add below code:
use std::sync::Arc;
use axum::{
extract::{
ws::{Message, WebSocketUpgrade},
State,
},
response::Response,
};
use futures_util::{SinkExt, StreamExt};
use crate::Inner;
pub async fn handler(
State(state): State<Arc<Inner>>,
ws: WebSocketUpgrade,
) -> Response {
ws.on_upgrade(|mut socket| async move {
let mut conn = state.relay.connect();
loop {
tokio::select! {
Some(msg) = conn.next() => {
if socket.send(Message::binary(msg)).await.is_err() {
break;
}
}
msg = socket.recv() => {
match msg {
None => break,
Some(Ok(Message::Binary(msg))) => {
println!("relay recv binary msg: {:?}", msg.len());
let _ = conn.send(msg.to_vec()).await;
}
Some(Ok(Message::Close(_))) => {
tracing::debug!("recv close from the client");
break;
}
Some(Err(err)) => {
tracing::error!("recv error {err}");
break;
}
_ => {}
}
}
}
}
tracing::info!("close ws connection");
})
}
Step 2: Create main server app and add the route for message relay handler
- In
src/main.rs, add below code:
use std::{env, future::IntoFuture, net::ToSocketAddrs, sync::Arc};
use axum::{routing::get, Router};
use tokio::{
signal::unix::{signal, SignalKind},
task::JoinSet,
};
use tower_http::{cors::CorsLayer, trace::TraceLayer};
use sl_mpc_mate::coord::SimpleMessageRelay;
mod relay_svc;
struct Inner {
pub(crate) relay: SimpleMessageRelay,
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
start_service().await?;
Ok(())
}
pub async fn start_service() -> anyhow::Result<()> {
let mut servers = JoinSet::new();
let state = {
let relay = SimpleMessageRelay::new();
Arc::new(Inner { relay })
};
let app = Router::new()
.route("/v1/msg-relay", get(relay_svc::handler))
.layer(CorsLayer::permissive())
.layer(TraceLayer::new_for_http())
.with_state(state);
for addrs in env::var("LISTEN")
.ok()
.unwrap_or_else(|| String::from(""))
.split(' ')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.chain(["localhost:8000"])
{
for addr in addrs.to_socket_addrs()? {
let listener = tokio::net::TcpListener::bind(addr).await?;
tracing::info!("listening on {}", listener.local_addr()?);
servers.spawn(
axum::serve(listener, app.clone().into_make_service())
.with_graceful_shutdown(shutdown())
.into_future(),
);
}
}
while servers.join_next().await.is_some() {}
Ok(())
}
async fn shutdown() {
let sigint = async {
signal(SignalKind::interrupt())
.expect("cant install SIGINT")
.recv()
.await;
};
let sigterm = async {
signal(SignalKind::terminate())
.expect("cant install SIGTERM")
.recv()
.await;
};
tokio::select! {
_ = sigint => {},
_ = sigterm => {},
};
}
Step 3: Run message relay server
cargo run
- Full code example could be found here: https://github.com/silence-laboratories/m2m-relay-demo
Setup the Mobile SDK (React Native)
- Keep in mind, in this mobile to mobile setup, there will be one initiator phone, and other to follow up with the initiator request.
Before Mobile to mobile MPC operation the both parties needs to know each others message signer's public key and instance id.
For the prerequisites information exchange, we can use
- QR code
- Push notification
- Through backend
- Or any other out-of-band channels
Key generation
Step 0: Install React Native SDK for both mobiles
- Follow the React Native SDK setup guide to install dependencies and configure each mobile project.
Step 1: Session creation
- On the initiator (1st party):
const client = new CloudWebSocketClient("MESSAGE_RELAY_ENDPOINT", false);
const ms = await createMessageSigner();
const session = await createEcdsaM2MSession({
client: client,
messageSigner: ms1,
});
- On the 2nd party, we have similar code:
const client = new CloudWebSocketClient("MESSAGE_RELAY_ENDPOINT", false);
const ms = await createMessageSigner();
const session1 = await createEcdsaM2MSession({
client: client,
messageSigner: ms,
});
What is message signer?
Silent Shard SDK secure communication between parties using the MessageSigner interface. Each party send message is signed with its own private key and other party could verify the message signature with the public key.
Currently Silent Shard SDK supports ED25519 and ECDSA_P256_SEC1 key types. We highly recommend, messageSigner generated in mobile TEE environment, which as iOS secure enclave or Android keystore. And persist through application lifecycle.
import { Ed25519 } from '@silencelaboratories/dkls-sdk';
import type { MessageSigner } from '@silencelaboratories/silent-shard-sdk';
export const createMessageSigner = async () => {
const ed = await Ed25519.create();
return {
keyType: 'ED25519',
publicKeyB64: ed.publicKeyB64,
sign(messageB64: string) {
return ed.sign(messageB64);
},
} as MessageSigner;
};