Skip to content

Commit

Permalink
improve pairing client implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
borngraced committed Sep 4, 2024
1 parent 548a821 commit c3d8f4e
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 89 deletions.
39 changes: 33 additions & 6 deletions pairing_api/examples/pairing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ use {
},
relay_rpc::{
auth::{ed25519_dalek::SigningKey, AuthToken},
rpc::{params::Metadata, Params, Payload},
rpc::{
params::{pairing::PairingResponseParamsSuccess, Metadata},
Params,
Payload,
},
},
std::{sync::Arc, time::Duration},
structopt::StructOpt,
Expand Down Expand Up @@ -142,14 +146,37 @@ async fn spawn_published_message_recv_loop(
let topic = msg.topic.to_string();
let key = hex::decode(key.clone()).unwrap();
let message = decode_and_decrypt_type0(msg.message.as_bytes(), &key).unwrap();
let reponse = serde_json::from_str::<Payload>(&message).unwrap();
match reponse {
let response = serde_json::from_str::<Payload>(&message).unwrap();
match response {
Payload::Request(value) => match value.params {
Params::PairingDelete(_) => pairing_client.delete_pairing(&topic).await.unwrap(),
Params::PairingDelete(_) => {
// send a success response back to wc.
let request = PairingResponseParamsSuccess::PairingDelete(true);
pairing_client
.publish_response(&topic, request, msg.message_id)
.await
.unwrap();
// send a request to delete pairing from store.
pairing_client.delete_pairing(&topic).await.unwrap();
}
Params::PairingExtend(req) => {
pairing_client.update_expiry(&topic, req.expiry).await
let request = PairingResponseParamsSuccess::PairingExtend(true);
// send a success response back to wc.
pairing_client
.publish_response(&topic, request, msg.message_id)
.await
.unwrap();
// send a request to update pairing expiry in store.
pairing_client.update_expiry(&topic, req.expiry).await;
}
Params::PairingPing(_) => {
let request = PairingResponseParamsSuccess::PairingPing(true);
// send a success response back to wc.
pairing_client
.publish_response(&topic, request, msg.message_id)
.await
.unwrap();
}
Params::PairingPing(_) => pairing_client.ping_request(&topic).await.unwrap(),
_ => unimplemented!(),
},
Payload::Response(value) => {
Expand Down
20 changes: 20 additions & 0 deletions pairing_api/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ pub fn encrypt_and_encode<T>(
where
T: AsRef<[u8]>,
{
// validate sym_key len.
if key.len() != 32 {
return Err(PayloadError::SymKeyLen(key.len()));
}

let payload = Payload {
msg: msg.as_ref(),
aad: &[],
Expand All @@ -146,6 +151,11 @@ pub fn decode_and_decrypt_type0<T>(msg: T, key: &[u8]) -> Result<String, Payload
where
T: AsRef<[u8]>,
{
// validate sym_key len.
if key.len() != 32 {
return Err(PayloadError::SymKeyLen(key.len()));
}

let data = BASE64_STANDARD.decode(msg)?;
let decoded = EncodingParams::parse_decoded(&data)?;
if let EnvelopeType::Type1 { .. } = decoded.envelope_type {
Expand All @@ -162,6 +172,11 @@ where
}

fn encrypt(nonce: &Nonce, payload: Payload<'_, '_>, key: &[u8]) -> Result<Vec<u8>, PayloadError> {
// validate sym_key len.
if key.len() != 32 {
return Err(PayloadError::SymKeyLen(key.len()));
}

let cipher = ChaCha20Poly1305::new(key.into());
let sealed = cipher
.encrypt(nonce, payload)
Expand All @@ -181,6 +196,11 @@ fn encode(envelope_type: EnvelopeType, sealed: &[u8], init_vec: &InitVec) -> Str
}

fn decrypt(nonce: &Nonce, payload: Payload<'_, '_>, key: &[u8]) -> Result<Vec<u8>, PayloadError> {
// validate sym_key len.
if key.len() != 32 {
return Err(PayloadError::SymKeyLen(key.len()));
}

let cipher = ChaCha20Poly1305::new(key.into());
let unsealed = cipher
.decrypt(nonce, payload)
Expand Down
167 changes: 84 additions & 83 deletions pairing_api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ use {
rand::{rngs::OsRng, RngCore},
relay_client::{websocket::Client, MessageIdGenerator},
relay_rpc::{
domain::Topic,
domain::{MessageId, Topic},
rpc::{
params::{
pairing::PairingRequestParams,
pairing::{PairingRequestParams, PairingResponseParamsSuccess},
pairing_delete::PairingDeleteRequest,
pairing_extend::PairingExtendRequest,
pairing_ping::PairingPingRequest,
Expand All @@ -18,7 +18,10 @@ use {
Payload,
PublishError,
Request,
Response,
SubscriptionError,
SuccessfulResponse,
JSON_RPC_VERSION_STR,
},
},
serde::{Deserialize, Serialize},
Expand Down Expand Up @@ -169,26 +172,15 @@ impl PairingClient {
/// Activates the pairing associated with the given topic,
/// extends its expiry time, and sends a pairing extend request to the peer.
pub async fn activate(&self, topic: &str) -> Result<(), PairingClientError> {
let mut pairings = self.pairings.lock().await;
let pairing = pairings
.get_mut(topic)
.ok_or_else(|| PairingClientError::PairingNotFound)?;

let now = SystemTime::now();
let expiry = now + EXPIRY_30_DAYS;
let expiry = expiry
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();

// try to extend session before updating local store.
let sym_key = hex::decode(pairing.sym_key.clone()).map_err(|err| {
PairingClientError::EncodeError(format!("Failed to decode sym_key: {:?}", err))
})?;
let ping_request = PairingRequestParams::PairingExtend(PairingExtendRequest { expiry });
let irn_metadata = ping_request.irn_metadata();
self.publish_request(topic, ping_request, irn_metadata, &sym_key)
.await?;
self.publish_request(topic, ping_request).await?;

// Re-acquire the lock to update the pairing
let mut pairings = self.pairings.lock().await;
Expand All @@ -202,20 +194,26 @@ impl PairingClient {
}
}

/// Update pairing expiry
pub async fn update_expiry(&self, topic: &str, expiry: u64) {
let mut pairings = self.pairings.lock().await;
if let Some(pairing) = pairings.get_mut(topic) {
pairing.pairing.expiry = expiry;
}
}

/// Update pairing metadata
pub async fn update_metadata(&self, topic: &str, metadata: Metadata) {
let mut pairings = self.pairings.lock().await;
if let Some(pairing) = pairings.get_mut(topic) {
pairing.pairing.peer_metadata = metadata;
}
}

/// Deletes a pairing from the store and subscribe from topic.
/// This should be done only after completing all necessary actions,
/// such as handling responses and requests, since the pairing's sym_key
/// is required for encoding outgoing messages and decoding incoming ones.
pub async fn delete_pairing(&self, topic: &str) -> Result<(), PairingClientError> {
println!("Attempting to unsubscribe from topic: {topic}");
{
Expand All @@ -225,35 +223,21 @@ impl PairingClient {
.map_err(PairingClientError::SubscriptionError)?;
};

{};
let mut pairings = self.pairings.lock().await;
pairings.remove(topic);

println!("Unsubscribed from topic: {topic}");
Ok(())
}

/// Used to evaluate if peer is currently online. Timeout at 30 seconds
/// https://specs.walletconnect.com/2.0/specs/clients/core/pairing/rpc-methods#wc_pairingping
pub async fn ping_request(&self, topic: &str) -> Result<(), PairingClientError> {
println!("Attempting to ping topic: {}", topic);
let pairing = {
let pairings = self.pairings.lock().await;
pairings.get(topic).cloned()
};

if let Some(pairing) = pairing {
let sym_key = hex::decode(pairing.sym_key.clone()).map_err(|err| {
PairingClientError::EncodeError(format!("Failed to decode sym_key: {:?}", err))
})?;
let ping_request = PairingRequestParams::PairingPing(PairingPingRequest {});
let irn_metadata = ping_request.irn_metadata();
self.publish_request(topic, ping_request, irn_metadata, &sym_key)
.await?;
let ping_request = PairingRequestParams::PairingPing(PairingPingRequest {});
self.publish_request(topic, ping_request).await?;

return Ok(());
}

Err(PairingClientError::PairingNotFound)
Ok(())
}

/// Used to inform the peer to close and delete a pairing.
Expand All @@ -263,75 +247,94 @@ impl PairingClient {
/// https://specs.walletconnect.com/2.0/specs/clients/core/pairing/rpc-methods#wc_pairingdelete
pub async fn delete_request(&self, topic: &str) -> Result<(), PairingClientError> {
println!("Attempting to delete topic: {}", topic);
let pairing = {
let pairings = self.pairings.lock().await;
pairings.get(topic).cloned()
};
let delete_request = PairingRequestParams::PairingDelete(PairingDeleteRequest {
code: 6000,
message: "User requested disconnect".to_owned(),
});
self.publish_request(topic, delete_request).await?;

if let Some(pairing) = pairing {
let sym_key = hex::decode(pairing.sym_key.clone()).map_err(|err| {
PairingClientError::EncodeError(format!("Failed to decode sym_key: {:?}", err))
})?;
let delete_request = PairingRequestParams::PairingDelete(PairingDeleteRequest {
code: 6000,
message: "User requested disconnect".to_owned(),
});
let irn_metadata = delete_request.irn_metadata();
self.publish_request(topic, delete_request, irn_metadata, &sym_key)
.await?;

return Ok(());
}

Err(PairingClientError::PairingNotFound)
Ok(())
}

/// Used to update the lifetime of a pairing.
/// https://specs.walletconnect.com/2.0/specs/clients/core/pairing/rpc-methods#wc_pairingextend
pub async fn extend_request(&self, topic: &str, expiry: u64) -> Result<(), PairingClientError> {
println!("Attempting to extend topic: {}", topic);
let pairing = {
let pairings = self.pairings.lock().await;
pairings.get(topic).cloned()
};

if let Some(pairing) = pairing {
let sym_key = hex::decode(pairing.sym_key.clone()).map_err(|err| {
PairingClientError::EncodeError(format!("Failed to decode sym_key: {:?}", err))
})?;

let now = SystemTime::now();
let expiry = now + Duration::from_secs(expiry);
let expiry = expiry
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let extend_request =
PairingRequestParams::PairingExtend(PairingExtendRequest { expiry });
let irn_metadata = extend_request.irn_metadata();

self.publish_request(topic, extend_request, irn_metadata, &sym_key)
.await?;

return Ok(());
}
let now = SystemTime::now();
let expiry = now + Duration::from_secs(expiry);
let expiry = expiry
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs();
let extend_request = PairingRequestParams::PairingExtend(PairingExtendRequest { expiry });
self.publish_request(topic, extend_request).await?;

Err(PairingClientError::PairingNotFound)
Ok(())
}

/// Function to publish a request
async fn publish_request(
&self,
topic: &str,
params: PairingRequestParams,
irn_metadata: IrnMetadata,
key: &[u8],
) -> Result<(), PairingClientError> {
let irn_metadata = params.irn_metadata();
let message_id = MessageIdGenerator::new().next();
let request = Request::new(message_id, params.into());
let payload = serde_json::to_string(&Payload::Request(request))
// Publish the encrypted message
self.publish_payload(topic, irn_metadata, Payload::Request(request))
.await?;

println!("Otbound request sent!\n");

Ok(())
}

/// Function to publish a request response
pub async fn publish_response(
&self,
topic: &str,
params: PairingResponseParamsSuccess,
message_id: MessageId,
) -> Result<(), PairingClientError> {
let irn_metadata = params.irn_metadata();
let response = Response::Success(SuccessfulResponse {
id: message_id,
jsonrpc: JSON_RPC_VERSION_STR.into(),
result: serde_json::to_value(params)
.map_err(|err| PairingClientError::EncodeError(err.to_string()))?,
});

// Publish the encrypted message
self.publish_payload(topic, irn_metadata, Payload::Response(response))
.await?;

println!("\nOutbound request sent!");

Ok(())
}

async fn publish_payload(
&self,
topic: &str,
irn_metadata: IrnMetadata,
payload: Payload,
) -> Result<(), PairingClientError> {
// try to extend session before updating local store.
let sym_key = {
let pairings = self.pairings.lock().await;
let pairing = pairings
.get(topic)
.ok_or_else(|| PairingClientError::PairingNotFound)?;
hex::decode(pairing.sym_key.clone()).map_err(|err| {
PairingClientError::EncodeError(format!("Failed to decode sym_key: {:?}", err))
})?
};

let payload = serde_json::to_string(&payload)
.map_err(|err| PairingClientError::EncodeError(err.to_string()))?;
let message = encrypt_and_encode(crypto::EnvelopeType::Type0, payload, key)
let message = encrypt_and_encode(crypto::EnvelopeType::Type0, payload, &sym_key)
.map_err(|e| anyhow::anyhow!(e))
.map_err(|err| PairingClientError::EncodeError(err.to_string()))?;

Expand All @@ -352,8 +355,6 @@ impl PairingClient {
.map_err(PairingClientError::PingError)?;
};

println!("\nOutbound request sent!");

Ok(())
}

Expand Down
1 change: 1 addition & 0 deletions relay_rpc/src/rpc/params/pairing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ impl_relay_protocol_helpers!(PairingResponseParamsSuccess);
///
/// https://specs.walletconnect.com/2.0/specs/clients/core/pairing/rpc-methods
/// https://specs.walletconnect.com/2.0/specs/clients/core/pairing/data-structures
#[derive(Serialize, Deserialize)]
pub enum PairingRequestParams {
PairingExtend(PairingExtendRequest),
PairingDelete(PairingDeleteRequest),
Expand Down

0 comments on commit c3d8f4e

Please sign in to comment.