From 344ac9cbfc1760264d7ff0afff46a306020d7b83 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Mon, 17 Jul 2023 17:40:34 -0400 Subject: [PATCH] Add ack signatures Also modifies message signatures to be binding to from, not just from's key. --- message-queue/src/main.rs | 11 ++++++++--- message-queue/src/messages.rs | 27 +++++++++++++++++++++++---- message-queue/src/queue.rs | 3 ++- processor/src/coordinator.rs | 29 +++++++++++++++++++++-------- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/message-queue/src/main.rs b/message-queue/src/main.rs index 96d8079f..17499f8e 100644 --- a/message-queue/src/main.rs +++ b/message-queue/src/main.rs @@ -42,7 +42,9 @@ lazy_static::lazy_static! { fn queue_message(meta: Metadata, msg: Vec, sig: SchnorrSignature) { { let from = (*KEYS).read().unwrap()[&meta.from]; - assert!(sig.verify(from, message_challenge(from, meta.to, &meta.intent, &msg, sig.R))); + assert!( + sig.verify(from, message_challenge(meta.from, from, meta.to, &meta.intent, &msg, sig.R)) + ); } // Assert one, and only one of these, is the coordinator @@ -85,8 +87,11 @@ fn get_next_message(service: Service, _expected: u64) -> Option { Acknowledges a message as received and handled, meaning it'll no longer be returned as the next message. */ -fn ack_message(service: Service, id: u64, _signature: SchnorrSignature) { - // TODO: Verify the signature +fn ack_message(service: Service, id: u64, sig: SchnorrSignature) { + { + let from = (*KEYS).read().unwrap()[&service]; + assert!(sig.verify(from, ack_challenge(service, from, id, sig.R))); + } // Is it: // The acknowledged message should be > last acknowledged OR diff --git a/message-queue/src/messages.rs b/message-queue/src/messages.rs index a6a6d0be..9ad393de 100644 --- a/message-queue/src/messages.rs +++ b/message-queue/src/messages.rs @@ -27,20 +27,39 @@ pub struct Metadata { } pub fn message_challenge( - from: ::G, + from: Service, + from_key: ::G, to: Service, intent: &[u8], msg: &[u8], nonce: ::G, ) -> ::F { - let mut transcript = RecommendedTranscript::new(b"Serai Message Queue v0.1"); + let mut transcript = RecommendedTranscript::new(b"Serai Message Queue v0.1 Message"); transcript.domain_separate(b"metadata"); - transcript.append_message(b"from", from.to_bytes()); + transcript.append_message(b"from", bincode::serialize(&from).unwrap()); + transcript.append_message(b"from_key", from_key.to_bytes()); transcript.append_message(b"to", bincode::serialize(&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()); - ::hash_to_F(b"challenge", &transcript.challenge(b"challenge")) + ::hash_to_F(b"message_challenge", &transcript.challenge(b"challenge")) +} + +pub fn ack_challenge( + from: Service, + from_key: ::G, + id: u64, + nonce: ::G, +) -> ::F { + let mut transcript = RecommendedTranscript::new(b"Serai Message Queue v0.1 Ackowledgement"); + transcript.domain_separate(b"metadata"); + transcript.append_message(b"from", bincode::serialize(&from).unwrap()); + transcript.append_message(b"from_key", from_key.to_bytes()); + 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()); + ::hash_to_F(b"ack_challenge", &transcript.challenge(b"challenge")) } diff --git a/message-queue/src/queue.rs b/message-queue/src/queue.rs index af189f43..60359fd1 100644 --- a/message-queue/src/queue.rs +++ b/message-queue/src/queue.rs @@ -46,7 +46,8 @@ impl Queue { } pub(crate) fn get_message(&self, id: u64) -> Option { - let msg = self.0.get(self.message_key(id)).map(|bytes| serde_json::from_slice(&bytes).unwrap()); + let msg: Option = + self.0.get(self.message_key(id)).map(|bytes| serde_json::from_slice(&bytes).unwrap()); if let Some(msg) = msg.as_ref() { assert_eq!(msg.id, id, "message stored at {id} has ID {}", msg.id); } diff --git a/processor/src/coordinator.rs b/processor/src/coordinator.rs index 80c40782..56767b72 100644 --- a/processor/src/coordinator.rs +++ b/processor/src/coordinator.rs @@ -15,7 +15,7 @@ use serde::{Serialize, Deserialize}; use messages::{ProcessorMessage, CoordinatorMessage}; use serai_client::primitives::NetworkId; -use message_queue::{Service, Metadata, QueuedMessage, message_challenge}; +use message_queue::{Service, Metadata, QueuedMessage, message_challenge, ack_challenge}; use reqwest::Client; #[derive(Clone, PartialEq, Eq, Debug)] @@ -125,10 +125,8 @@ impl MessageQueue { let id = msg.id; // Deserialize it into a CoordinatorMessage - let msg: CoordinatorMessage = serde_json::from_str( - &String::from_utf8(msg.msg).expect("msg wasn't valid UTF-8 (not JSON?)"), - ) - .expect("message wasn't a JSON-encoded CoordinatorMessage"); + let msg: CoordinatorMessage = + serde_json::from_slice(&msg.msg).expect("message wasn't a JSON-encoded CoordinatorMessage"); return Message { id, msg }; } } @@ -157,7 +155,14 @@ impl Coordinator for MessageQueue { let sig = SchnorrSignature::::sign( &self.priv_key, nonce, - message_challenge(self.pub_key, metadata.to, &metadata.intent, msg.as_bytes(), nonce_pub), + message_challenge( + metadata.from, + self.pub_key, + metadata.to, + &metadata.intent, + msg.as_bytes(), + nonce_pub, + ), ); self.queue(metadata, msg.into_bytes(), sig.serialize()).await; } @@ -167,8 +172,16 @@ impl Coordinator for MessageQueue { } async fn ack(&mut self, msg: Message) { - // TODO: Use a proper signature once message-queue checks ack signatures - MessageQueue::ack(self, msg.id, vec![0; 64]).await + // TODO: Should this use OsRng? Deterministic or deterministic + random may be better. + let nonce = Zeroizing::new(::F::random(&mut OsRng)); + let nonce_pub = Ristretto::generator() * nonce.deref(); + let sig = SchnorrSignature::::sign( + &self.priv_key, + nonce, + ack_challenge(Service::Processor(self.network), self.pub_key, msg.id, nonce_pub), + ); + + MessageQueue::ack(self, msg.id, sig.serialize()).await } }