Skip to content
This repository has been archived by the owner on Jan 14, 2020. It is now read-only.

GrinRelay Security Specification

Gary Yu edited this page Jul 23, 2019 · 1 revision

RECENT CHANGES:

  • (23 Jul 2019) Creation for Grin Relay v1.0.x

General Information

Grin Relay is just a relay service, it's completely the client's responsibility to ensure the message transmitted has well designed encryption and make the user info secured.

Here is one reference of the message encryption/decryption.

With the following scheme, the messages are p2p encrypted and ONLY the corresponding parties can decrypt and read these messages.

As a relay service, Grin Relay doesn't involve into this encryption procedure and never have the ability to read these user messages.

1. Encrypted Messages Between Wallet Client and GrinRelay Server

Refer to this message structure:

pub struct GrinboxMessage {
	#[serde(default)]
	pub destination: Option<GrinboxAddress>,
	encrypted_message: String,
	salt: String,
	nonce: String,
}

Except the destination address info, the payload is encrypted as encrypted_message.

An encrypted_message of a transaction slate looks like a completely randomized string. The message decryption need a 32-byte key and it will be decrypted by Authenticated Encryption with Associated Data AEAD CHACHA20_POLY1305 algorithm.

The reference code looks like:

	pub fn decrypt_with_key(&self, key: &[u8; 32]) -> Result<String> {
		let mut encrypted_message =
			from_hex(self.encrypted_message.clone()).map_err(|_| ErrorKind::Decryption)?;
		let nonce = from_hex(self.nonce.clone()).map_err(|_| ErrorKind::Decryption)?;

		let opening_key = aead::OpeningKey::new(&aead::CHACHA20_POLY1305, key)
			.map_err(|_| ErrorKind::Decryption)?;
		let decrypted_data =
			aead::open_in_place(&opening_key, &nonce, &[], 0, &mut encrypted_message)
				.map_err(|_| ErrorKind::Decryption)?;

		String::from_utf8(decrypted_data.to_vec()).map_err(|_| ErrorKind::Decryption.into())
	}

About the 32-byte encryption/decryption key, ONLY the transaction sender and receiver know it, Because it depends on both parties's private/public key of their Grin Relay addresses.

The key generation procedure reference code looks like:

		let mut common_secret = receiver_public_key.clone();
		common_secret
			.mul_assign(&secp, secret_key)
			.map_err(|_| ErrorKind::Encryption)?;
		let common_secret_ser = common_secret.serialize_vec(&secp, true);
		let common_secret_slice = &common_secret_ser[1..33];

		let salt: [u8; 8] = thread_rng().gen();
		let nonce: [u8; 12] = thread_rng().gen();
		let mut key = [0; 32];
		pbkdf2::derive(&digest::SHA512, 10000, &salt, common_secret_slice, &mut key);

2. Message Signature

To identify a message indeed comes from an address, Grin Relay messages use Schnorr Signature mechanism.

A typical encapsulated message looks like this:

{
  "str": {
    "destination": {
      "public_key": "tn1-qgfaqdqy-vm8ryd2k6zfp6cm-359cs4gnudxhljm-d0v38yut4u9r7rg-93d4jp",
      "domain": "relay.grin.icu",
      "port": null
    },
    "encrypted_message": "7f1e20a6cc14 ... 03773a57bcf7",
    "salt": "72608d400cc840c7",
    "nonce": "e1147663174c0efbc3b46f5c"
  },
  "challenge": "",
  "signature": "304502210089ccdebdf06145f9203df8bed6bdd6f1e434c34689376c0aba728e2f9234e79d02201372e973c43feb8a7033d41459e17baef8ce6dc8c2f61ae2b26466d274dc9b88"
}

The "encrypted_message" part has been explained at previous chapter.

The "signature" here is the said Schnorr Signature. Grin Relay server will validate each message signature to make sure the message indeed comes from the message issuer's address.

The reference code of this signature validation looks like:

	fn post_slate(
		&self,
		from: String,
		to: String,
		str: String,
		signature: String,
		message_expiration_in_seconds: Option<u32>,
	) -> GrinboxResponse {
		let from_address = GrinboxAddress::from_str_raw(&from);
		...

		let mut challenge = String::new();
		challenge.push_str(&str);

		let mut result = self.verify_signature(&from_address.public_key, &challenge, &signature);
		...

		if result.is_err() {
			return AsyncServer::error(GrinboxError::InvalidSignature);
		}