xpx_chain_sdk/models/message/
message_secure.rs

1/*
2 * Copyright 2018 ProximaX Limited. All rights reserved.
3 * Use of this source code is governed by the Apache 2.0
4 * license that can be found in the LICENSE file.
5 */
6
7use anyhow::Result;
8
9use crate::{
10    account::{Account, PublicAccount},
11    crypto::{Ed25519BlockCipher, PublicKey},
12    errors_const,
13    helpers::{hex_decode, hex_encode, is_hex},
14};
15
16use super::{Message, MessageType, PlainMessage};
17
18/// An encrypted message model defines a secure message that has been encrypted using
19/// the Sirius's SDK libraries.
20///
21/// Please note:
22/// The strategy to encrypt and decrypt should be consistent between the different SDKs.
23/// A client may send a transaction using a different encryption strategy than the recipient.
24/// Even though the same encryption algorithm is used, the outcome may still be different
25/// depending on the encoding process.
26///
27/// The encryption strategy this SDK uses is UTF-8 and hex encodings:
28/// "plain text" string -> utf8 byte array -> encrypted byte array -> hex string
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SecureMessage {
31    /// Message type
32    #[serde(rename = "type")]
33    pub r#type: MessageType,
34    /// Message payload.
35    pub payload: String,
36}
37
38impl SecureMessage {
39    /// Creates an encrypted message from [plainTextMessage].
40    ///
41    /// The message is encrypted using a shared key generated by the [senderPrivateKey] and
42    /// the [recipient_public_key]. The recipient can decrypt this message using this shared key by
43    /// taking the sender's public key and the recipient's public key.
44    pub fn create(
45        sender_account: &Account,
46        recipient_public_account: &PublicAccount,
47        payload: &str,
48    ) -> Result<Self> {
49        ensure!(!payload.is_empty(), "message must not be empty.");
50
51        // Encrypts the message
52        let mut block_cipher = Ed25519BlockCipher::new(&sender_account.key_pair.secret);
53
54        let payload_vec = if is_hex(&payload) {
55            hex_decode(payload)
56        } else {
57            payload.as_bytes().to_vec()
58        };
59
60        let encrypted_payload = block_cipher.encrypt(
61            &payload_vec,
62            &PublicKey::from_bytes(recipient_public_account.to_builder())
63                .map_err(|err| anyhow!(err))?,
64        );
65
66        Ok(SecureMessage {
67            r#type: MessageType::SecureMessageType,
68            payload: hex_encode(&encrypted_payload),
69        })
70    }
71
72    /// It creates a `SecureMessage` from the payload hex without the 01 prefix.
73    /// The 01 prefix will be attached to the final payload.
74    ///
75    pub fn from_hex_payload(payload: &str) -> Result<Self> {
76        ensure!(is_hex(payload), errors_const::ERR_INVALID_PAYLOAD_HEX);
77
78        Ok(SecureMessage {
79            r#type: MessageType::SecureMessageType,
80            payload: payload.to_string(),
81        })
82    }
83
84    /// It creates a `SecureMessage` from the given bytes.
85    ///
86    pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
87        ensure!(!bytes.is_empty(), "bytes must not be empty.");
88        let payload = hex::encode(bytes);
89        Self::from_hex_payload(&payload)
90    }
91
92    /// Decrypts an `SecureMessage`.
93    ///
94    /// Returns a `PlainMessage`.
95    pub fn decrypt(
96        &self,
97        recipient_account: &Account,
98        sender_public_account: PublicAccount,
99    ) -> Result<PlainMessage> {
100        let mut block_cipher = Ed25519BlockCipher::new(&recipient_account.key_pair.secret);
101
102        let encrypted_payload = block_cipher
103            .decrypt(
104                &hex_decode(&self.payload),
105                &PublicKey::from_bytes(sender_public_account.to_builder())
106                    .map_err(|err| anyhow!(err))?,
107            )
108            .map_err(|err| anyhow!(err))?;
109
110        let payload = match String::from_utf8(encrypted_payload.to_owned()) {
111            Ok(payload) => payload,
112            Err(_) => hex_encode(&encrypted_payload),
113        };
114
115        Ok(PlainMessage::create(&payload))
116    }
117}
118
119#[typetag::serde]
120impl Message for SecureMessage {
121    fn message_type(&self) -> MessageType {
122        self.r#type
123    }
124    fn payload(&self) -> String {
125        self.payload.to_owned()
126    }
127    fn box_clone(&self) -> Box<dyn Message + 'static> {
128        Box::new((*self).clone())
129    }
130}
131
132impl core::fmt::Display for SecureMessage {
133    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
134        write!(
135            f,
136            "{}",
137            serde_json::to_string_pretty(&self).unwrap_or_default()
138        )
139    }
140}
141
142#[cfg(test)]
143pub mod tests {
144    use lazy_static::lazy_static;
145
146    use crate::account::Account;
147    use crate::message::SecureMessage;
148    use crate::network::NetworkType;
149
150    const SENDER_PRIVATE_KEY: &str =
151        "2602F4236B199B3DF762B2AAB46FC3B77D8DDB214F0B62538D3827576C46C108";
152    const RECIPIENT_PRIVATE_KEY: &str =
153        "B72F2950498111BADF276D6D9D5E345F04E0D5C9B8342DA983C3395B4CF18F08";
154
155    lazy_static! {
156        static ref SENDER: Account =
157            Account::from_hex_private_key(SENDER_PRIVATE_KEY, NetworkType::PrivateTest).unwrap();
158        static ref RECIPIENT: Account =
159            Account::from_hex_private_key(RECIPIENT_PRIVATE_KEY, NetworkType::PrivateTest).unwrap();
160    }
161
162    #[test]
163    fn test_should_create_from_a_dto() {
164        let payload = "test transaction";
165        let encrypted_message = SecureMessage::from_hex_payload(&payload);
166
167        assert!(encrypted_message.is_err());
168    }
169
170    #[test]
171    fn test_should_return_encrypted_message_dto() {
172        let encrypted_message = SENDER
173            .encrypt_message(RECIPIENT.public_account, "test transaction")
174            .unwrap();
175        let plain_message = RECIPIENT
176            .decrypt_message(SENDER.public_account, encrypted_message)
177            .unwrap();
178
179        assert_eq!(plain_message.payload, "test transaction");
180    }
181
182    #[test]
183    fn test_should_decrypt_message_from_raw_encrypted_message_payload() {
184        let encrypted_message = SENDER
185            .encrypt_message(RECIPIENT.public_account, "Testing simple transfer")
186            .unwrap();
187        let encrypted_message_from_payload =
188            SecureMessage::from_hex_payload(&encrypted_message.payload).unwrap();
189        let plain_message = RECIPIENT
190            .decrypt_message(SENDER.public_account, encrypted_message_from_payload)
191            .unwrap();
192
193        assert_eq!(plain_message.payload, "Testing simple transfer");
194    }
195}