1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
use transcript::{Transcript, RecommendedTranscript};
use ciphersuite::{group::GroupEncoding, Ciphersuite, Ristretto};

use borsh::{BorshSerialize, BorshDeserialize};

use serai_primitives::ExternalNetworkId;

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, BorshSerialize, BorshDeserialize)]
pub enum Service {
  Processor(ExternalNetworkId),
  Coordinator,
}

#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub struct QueuedMessage {
  pub from: Service,
  pub id: u64,
  pub msg: Vec<u8>,
  pub sig: Vec<u8>,
}

#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub struct Metadata {
  pub from: Service,
  pub to: Service,
  pub intent: Vec<u8>,
}

#[derive(Clone, PartialEq, Eq, Debug, BorshSerialize, BorshDeserialize)]
pub enum MessageQueueRequest {
  Queue { meta: Metadata, msg: Vec<u8>, sig: Vec<u8> },
  Next { from: Service, to: Service },
  Ack { from: Service, to: Service, id: u64, sig: Vec<u8> },
}

pub fn message_challenge(
  from: Service,
  from_key: <Ristretto as Ciphersuite>::G,
  to: Service,
  intent: &[u8],
  msg: &[u8],
  nonce: <Ristretto as Ciphersuite>::G,
) -> <Ristretto as Ciphersuite>::F {
  let mut transcript = RecommendedTranscript::new(b"Serai Message Queue v0.1 Message");
  transcript.domain_separate(b"metadata");
  transcript.append_message(b"from", borsh::to_vec(&from).unwrap());
  transcript.append_message(b"from_key", from_key.to_bytes());
  transcript.append_message(b"to", borsh::to_vec(&to).unwrap());
  transcript.append_message(b"intent", intent);
  transcript.domain_separate(b"message");
  transcript.append_message(b"msg", msg);
  transcript.domain_separate(b"signature");
  transcript.append_message(b"nonce", nonce.to_bytes());
  <Ristretto as Ciphersuite>::hash_to_F(b"message_challenge", &transcript.challenge(b"challenge"))
}

pub fn ack_challenge(
  to: Service,
  to_key: <Ristretto as Ciphersuite>::G,
  from: Service,
  id: u64,
  nonce: <Ristretto as Ciphersuite>::G,
) -> <Ristretto as Ciphersuite>::F {
  let mut transcript = RecommendedTranscript::new(b"Serai Message Queue v0.1 Acknowledgement");
  transcript.domain_separate(b"metadata");
  transcript.append_message(b"to", borsh::to_vec(&to).unwrap());
  transcript.append_message(b"to_key", to_key.to_bytes());
  transcript.append_message(b"from", borsh::to_vec(&from).unwrap());
  transcript.domain_separate(b"message");
  transcript.append_message(b"id", id.to_le_bytes());
  transcript.domain_separate(b"signature");
  transcript.append_message(b"nonce", nonce.to_bytes());
  <Ristretto as Ciphersuite>::hash_to_F(b"ack_challenge", &transcript.challenge(b"challenge"))
}