diff --git a/Cargo.lock b/Cargo.lock index cadcc92..cc547ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,41 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aead" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "0.7.19" @@ -105,6 +140,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.4.0" @@ -184,6 +229,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.8.0" @@ -194,6 +250,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -258,6 +323,16 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "glob" version = "0.3.0" @@ -332,6 +407,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "1.0.3" @@ -518,6 +602,18 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "polyval" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -843,6 +939,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "unsigned-varint" version = "0.7.1" @@ -870,6 +976,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" name = "waku" version = "0.1.0" dependencies = [ + "aes-gcm", "hex", "libsecp256k1", "multiaddr", diff --git a/waku/Cargo.toml b/waku/Cargo.toml index 07d8568..d9eed35 100644 --- a/waku/Cargo.toml +++ b/waku/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +aes-gcm = { version = "0.10", features = ["aes"] } hex = "0.4" libsecp256k1 = "0.7" multiaddr = "0.14" diff --git a/waku/src/general/mod.rs b/waku/src/general/mod.rs index b56c23a..5a52b18 100644 --- a/waku/src/general/mod.rs +++ b/waku/src/general/mod.rs @@ -11,6 +11,7 @@ pub type ContentTopic = String; pub type WakuMessageVersion = usize; /// Base58 encoded peer id pub type PeerId = String; +pub type MessageId = String; /// JsonResponse wrapper. /// `go-waku` ffi returns this type as a `char *` as per the [specification](https://rfc.vac.dev/spec/36/#jsonresponse-type) diff --git a/waku/src/node/relay.rs b/waku/src/node/relay.rs index 802d989..d98148d 100644 --- a/waku/src/node/relay.rs +++ b/waku/src/node/relay.rs @@ -1,6 +1,16 @@ -use crate::general::{Encoding, WakuContentTopic, WakuPubSubTopic}; +// std use std::ffi::{CStr, CString}; +use std::time::Duration; +// crates +use aes_gcm::{Aes256Gcm, Key}; +use libsecp256k1::{PublicKey, SecretKey}; +// internal +use crate::general::{ + Encoding, JsonResponse, MessageId, Result, WakuContentTopic, WakuMessage, WakuPubSubTopic, +}; +/// Create a content topic string according to [RFC 23](https://rfc.vac.dev/spec/23/) +/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_content_topicchar-applicationname-unsigned-int-applicationversion-char-contenttopicname-char-encoding) pub fn waku_create_content_topic( application_name: &str, application_version: usize, @@ -24,11 +34,13 @@ pub fn waku_create_content_topic( )) } .to_str() - .expect("&str from result should always be") + .expect("&str from result should always be extracted") .parse() .expect("Content topic data should be always parseable") } +/// Create a pubsub topic string according to [RFC 23](https://rfc.vac.dev/spec/23/) +/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_pubsub_topicchar-name-char-encoding) pub fn waku_create_pubsub_topic(topic_name: &str, enconding: Encoding) -> WakuPubSubTopic { unsafe { CStr::from_ptr(waku_sys::waku_pubsub_topic( @@ -41,7 +53,138 @@ pub fn waku_create_pubsub_topic(topic_name: &str, enconding: Encoding) -> WakuPu )) } .to_str() - .expect("&str from result should always be") + .expect("&str from result should always be extracted") .parse() .expect("Pubsub topic data should be always parseable") } + +/// Default pubsub topic used for exchanging waku messages defined in [RFC 10](https://rfc.vac.dev/spec/10/) +pub fn waku_dafault_pubsub_topic() -> WakuPubSubTopic { + unsafe { CStr::from_ptr(waku_sys::waku_default_pubsub_topic()) } + .to_str() + .expect("&str from result should always be extracted") + .parse() + .expect("Default pubsub topic should always be parseable") +} + +/// Publish a message using Waku Relay +/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publishchar-messagejson-char-pubsubtopic-int-timeoutms) +pub fn waku_relay_publish_message( + message: &WakuMessage, + pubsub_topic: Option<&str>, + timeout: Duration, +) -> Result { + let pubsub_topic = pubsub_topic + .map(ToString::to_string) + .unwrap_or_else(|| waku_dafault_pubsub_topic().to_string()); + let result = unsafe { + CStr::from_ptr(waku_sys::waku_relay_publish( + CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(), + CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(), + timeout + .as_millis() + .try_into() + .expect("Duration as milliseconds should fit in a i32"), + )) + } + .to_str() + .expect("&str from result should always be extracted"); + let message_id: JsonResponse = + serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); + message_id.into() +} + +/// Optionally sign, encrypt using asymmetric encryption and publish a message using Waku Relay +/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publish_enc_asymmetricchar-messagejson-char-pubsubtopic-char-publickey-char-optionalsigningkey-int-timeoutms) +pub fn waku_relay_publish_encrypt_asymmetric( + message: &WakuMessage, + pubsub_topic: Option<&str>, + public_key: &PublicKey, + signing_key: &SecretKey, + timeout: Duration, +) -> Result { + let pk = hex::encode(public_key.serialize()); + let sk = hex::encode(signing_key.serialize()); + let pubsub_topic = pubsub_topic + .map(ToString::to_string) + .unwrap_or_else(|| waku_dafault_pubsub_topic().to_string()); + let result = unsafe { + CStr::from_ptr(waku_sys::waku_relay_publish_enc_asymmetric( + CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(), + CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(), + CString::new(pk) + .expect("CString should build properly from hex encoded public key") + .into_raw(), + CString::new(sk) + .expect("CString should build properly from hex encoded signing key") + .into_raw(), + timeout + .as_millis() + .try_into() + .expect("Duration as milliseconds should fit in a i32"), + )) + .to_str() + .expect("Response should always succeed to load to a &str") + }; + let message_id: JsonResponse = + serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); + message_id.into() +} + +/// Optionally sign, encrypt using symmetric encryption and publish a message using Waku Relay +/// As per the [specification](https://rfc.vac.dev/spec/36/#extern-char-waku_relay_publish_enc_symmetricchar-messagejson-char-pubsubtopic-char-symmetrickey-char-optionalsigningkey-int-timeoutms) +pub fn waku_relay_publish_encrypt_symmetric( + message: &WakuMessage, + pubsub_topic: Option<&str>, + symmetric_key: &Key, + signing_key: &SecretKey, + timeout: Duration, +) -> Result { + let symk = hex::encode(symmetric_key.as_slice()); + let sk = hex::encode(signing_key.serialize()); + let pubsub_topic = pubsub_topic + .map(ToString::to_string) + .unwrap_or_else(|| waku_dafault_pubsub_topic().to_string()); + let result = unsafe { + CStr::from_ptr(waku_sys::waku_relay_publish_enc_symmetric( + CString::new( + serde_json::to_string(&message) + .expect("WakuMessages should always be able to success serializing"), + ) + .expect("CString should build properly from the serialized waku message") + .into_raw(), + CString::new(pubsub_topic) + .expect("CString should build properly from pubsub topic") + .into_raw(), + CString::new(symk) + .expect("CString should build properly from hex encoded symmetric key") + .into_raw(), + CString::new(sk) + .expect("CString should build properly from hex encoded signing key") + .into_raw(), + timeout + .as_millis() + .try_into() + .expect("Duration as milliseconds should fit in a i32"), + )) + .to_str() + .expect("Response should always succeed to load to a &str") + }; + let message_id: JsonResponse = + serde_json::from_str(result).expect("JsonResponse should always succeed to deserialize"); + message_id.into() +}