diff --git a/src/kernel_sequencer/Cargo.lock b/src/kernel_sequencer/Cargo.lock index d45b79b312f5fdfee81f6f8dd919bf0fd20826bb..961f7abb77d4ddce3bc79b9e4ac1bcd330ad10ab 100644 --- a/src/kernel_sequencer/Cargo.lock +++ b/src/kernel_sequencer/Cargo.lock @@ -346,6 +346,7 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" name = "kernel-sequencer" version = "0.1.0" dependencies = [ + "hex", "nom", "tezos-smart-rollup-core", "tezos-smart-rollup-debug", diff --git a/src/kernel_sequencer/Cargo.toml b/src/kernel_sequencer/Cargo.toml index 5e8c913cd9a3057b682504c325e785a92e1586a4..c1432364f2f6baa4b9637d995a0e3c2a9de4092e 100644 --- a/src/kernel_sequencer/Cargo.toml +++ b/src/kernel_sequencer/Cargo.toml @@ -50,6 +50,9 @@ version = "0.2.0" version = "0.5.0" default-features = false +[dev-dependencies.hex] +version = "0.4.3" + [features] default = [] diff --git a/src/kernel_sequencer/src/delayed_inbox.rs b/src/kernel_sequencer/src/delayed_inbox.rs index 252fd41cbf975cb0102e7865b6b38353ba60f2c7..0b2b979cf5f207557726f671286c48d19c374577 100644 --- a/src/kernel_sequencer/src/delayed_inbox.rs +++ b/src/kernel_sequencer/src/delayed_inbox.rs @@ -6,7 +6,6 @@ use tezos_data_encoding::enc::BinWriter; use tezos_data_encoding::nom::NomReader; use tezos_smart_rollup_debug::debug_msg; -use tezos_smart_rollup_encoding::smart_rollup::SmartRollupAddress; use tezos_smart_rollup_host::{ input::Message, metadata::{RollupMetadata, RAW_ROLLUP_ADDRESS_SIZE}, @@ -14,7 +13,7 @@ use tezos_smart_rollup_host::{ }; use crate::{ - message::{Framed, KernelMessage, Sequence, SequencerMsg, SetSequencer}, + message::{Framed, KernelMessage, Sequence, SequencerMsg, SetSequencer, UnverifiedSigned}, queue::Queue, routing::FilterBehavior, state::{update_state, State}, @@ -61,24 +60,24 @@ pub fn read_input( match message { Err(_) => {} Ok((_, message)) => match message { - KernelMessage::Sequencer(Framed { - destination, - payload: SequencerMsg::Sequence(sequence), - }) => handle_sequence_message( - host, - sequence, - destination, - &raw_rollup_address, - state, - ), - KernelMessage::Sequencer(Framed { - destination, - payload: SequencerMsg::SetSequencer(set_sequence), - }) => handle_set_sequencer_message( - set_sequence, - destination, - &raw_rollup_address, - ), + KernelMessage::Sequencer(sequencer_msg) => { + debug_msg!(host, "Received a sequence message {:?}", &sequencer_msg); + + let Ok(payload) = extract_payload( + sequencer_msg, + &raw_rollup_address, + state, + ) else { continue;}; + + match payload { + SequencerMsg::Sequence(sequence) => { + handle_sequence_message(sequence) + } + SequencerMsg::SetSequencer(set_sequencer) => { + handle_set_sequencer_message(set_sequencer) + } + } + } KernelMessage::DelayedMessage(user_message) => { let _ = handle_message( host, @@ -97,37 +96,47 @@ pub fn read_input( } } -/// Handle Sequence message -fn handle_sequence_message( - host: &impl Runtime, - sequence: Sequence, - destination: SmartRollupAddress, +/// Extracts the payload of the message sent by the sequencer. +/// +/// The destination has to match the current rollup address. +/// The state of the kernel has to be `Sequenced`. +/// The signature has to be valid. +fn extract_payload( + sequencer_msg: UnverifiedSigned>, rollup_address: &[u8; RAW_ROLLUP_ADDRESS_SIZE], state: State, -) { +) -> Result { + // Check if state is sequenced. + let State::Sequenced(sequencer_address) = state else { + return Err(RuntimeError::HostErr( + tezos_smart_rollup_host::Error::GenericInvalidAccess, + )); + }; + + let body = sequencer_msg.body(&sequencer_address)?; + + let Framed { + destination, + payload, + } = body; + + // Verify if the destination is for this rollup. if destination.hash().as_ref() != rollup_address { - return; + return Err(RuntimeError::HostErr( + tezos_smart_rollup_host::Error::GenericInvalidAccess, + )); } - debug_msg!( - host, - "Received a sequence message {:?} targeting our rollup", - sequence - ); - - let State::Sequenced(_) = state else {return;}; + Ok(payload) +} +/// Handle Sequence message +fn handle_sequence_message(_sequence: Sequence) { // process the sequence } -fn handle_set_sequencer_message( - _set_sequencer: SetSequencer, - destination: SmartRollupAddress, - rollup_address: &[u8; RAW_ROLLUP_ADDRESS_SIZE], -) { - if destination.hash().as_ref() == rollup_address { - // process the set sequencer message - } +fn handle_set_sequencer_message(_set_sequencer: SetSequencer) { + // process the set sequencer message } /// Handle messages diff --git a/src/kernel_sequencer/src/message.rs b/src/kernel_sequencer/src/message.rs index 8f5f85825d9dace34e302f04add0086cd2ec7b59..05c57d08ea6cd624929f94ac02318efaed5ed0d9 100644 --- a/src/kernel_sequencer/src/message.rs +++ b/src/kernel_sequencer/src/message.rs @@ -8,13 +8,71 @@ use nom::{ combinator::{all_consuming, map}, sequence::preceded, }; -use tezos_crypto_rs::hash::Signature; +#[cfg(test)] +use tezos_crypto_rs::hash::SecretKeyEd25519; +use tezos_crypto_rs::PublicKeySignatureVerifier; +use tezos_crypto_rs::{blake2b, hash::Signature}; use tezos_data_encoding::{ enc::{self, BinResult, BinWriter}, nom::{NomReader, NomResult}, }; use tezos_smart_rollup_encoding::public_key::PublicKey; use tezos_smart_rollup_encoding::smart_rollup::SmartRollupAddress; +use tezos_smart_rollup_host::runtime::RuntimeError; + +#[derive(Debug, Clone, PartialEq, Eq, BinWriter, NomReader)] +pub struct UnverifiedSigned +where + A: NomReader + BinWriter, +{ + body: A, + signature: Signature, +} + +impl UnverifiedSigned +where + A: NomReader + BinWriter, +{ + /// Returns the hash of the body. + fn hash_body(body: &A) -> Result, RuntimeError> { + // Get the bytes of the body. + let mut bytes = Vec::new(); + body.bin_write(&mut bytes) + .map_err(|_| RuntimeError::DecodingError)?; + + // Returns the hash of the body. + blake2b::digest_256(&bytes).map_err(|_| RuntimeError::DecodingError) + } + + /// Returns the hash of the signed message. + /// + /// It's equivalent of the body's hash. + pub fn hash(&self) -> Result, RuntimeError> { + UnverifiedSigned::hash_body(&self.body) + } + + /// Returns the body of the message and verifies the signature. + pub fn body(self, public_key: &PublicKey) -> Result { + let hash = &self.hash()?; + let is_correct = public_key.verify_signature(&self.signature, hash); + match is_correct { + Ok(true) => Ok(self.body), + _ => Err(RuntimeError::DecodingError), + } + } + + /// Return a signed body + #[cfg(test)] + fn sign_ed25519(body: A, secret: &SecretKeyEd25519) -> Result { + use tezos_smart_rollup_host::Error; + + let hash = UnverifiedSigned::hash_body(&body)?; + let signature = secret + .sign(hash) + .map_err(|_| RuntimeError::HostErr(Error::GenericInvalidAccess))?; + Ok(UnverifiedSigned { body, signature }) + } +} /// Framing protocol v0 /// @@ -58,7 +116,6 @@ pub struct Sequence { delayed_messages_suffix: u32, #[encoding(dynamic, list)] messages: Vec, - signature: Signature, } /// Message to set the appropriate sequencer @@ -70,7 +127,6 @@ pub struct SetSequencer { nonce: u32, admin_public_key: PublicKey, sequencer_public_key: PublicKey, - signature: Signature, } #[derive(NomReader, BinWriter, Debug, Clone, Eq, PartialEq)] @@ -81,7 +137,7 @@ pub enum SequencerMsg { #[derive(Debug, Clone, PartialEq, Eq)] pub enum KernelMessage { - Sequencer(Framed), + Sequencer(UnverifiedSigned>), DelayedMessage(Vec), } @@ -128,7 +184,7 @@ impl NomReader for KernelMessage { fn nom_read(input: &[u8]) -> NomResult { all_consuming(alt(( all_consuming(map( - preceded(tag([1]), Framed::::nom_read), + preceded(tag([1]), UnverifiedSigned::>::nom_read), KernelMessage::Sequencer, )), map( @@ -155,11 +211,12 @@ impl BinWriter for KernelMessage { #[cfg(test)] mod tests { - use crate::message::{Framed, SequencerMsg}; + use crate::message::{Bytes, Framed, SequencerMsg, UnverifiedSigned}; use super::{KernelMessage, Sequence}; use crate::message::SetSequencer; - use tezos_crypto_rs::hash::{SecretKeyEd25519, SeedEd25519}; + use tezos_crypto_rs::hash::{PublicKeyEd25519, SecretKeyEd25519, SeedEd25519, Signature}; + use tezos_crypto_rs::PublicKeySignatureVerifier; use tezos_data_encoding::enc::{self, BinWriter}; use tezos_data_encoding::nom::NomReader; use tezos_smart_rollup_encoding::public_key::PublicKey; @@ -181,7 +238,7 @@ mod tests { let (_, secret) = key_pair("edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB"); let signature = secret.sign([0x0]).expect("sign should work"); - let sequence = KernelMessage::Sequencer(Framed { + let body = Framed { destination: SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb") .expect("decoding should work"), payload: SequencerMsg::Sequence(Sequence { @@ -189,9 +246,10 @@ mod tests { delayed_messages_prefix: 0, delayed_messages_suffix: 0, messages: Vec::default(), - signature, }), - }); + }; + + let sequence = KernelMessage::Sequencer(UnverifiedSigned { body, signature }); // Serializing let mut bin: Vec = Vec::new(); @@ -209,16 +267,17 @@ mod tests { key_pair("edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB"); let signature = secret.sign([0x0]).expect("sign should work"); - let sequence = KernelMessage::Sequencer(Framed { + let body = Framed { destination: SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb") .expect("decoding should work"), payload: SequencerMsg::SetSequencer(SetSequencer { nonce: 0, admin_public_key: public_key.clone(), sequencer_public_key: public_key, - signature, }), - }); + }; + + let sequence = KernelMessage::Sequencer(UnverifiedSigned { body, signature }); // Serializing let mut bin: Vec = Vec::new(); @@ -249,17 +308,18 @@ mod tests { let (_, secret) = key_pair("edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB"); let signature = secret.sign([0x0]).expect("sign should work"); - let sequence = KernelMessage::Sequencer(Framed { + let body = Framed { destination: SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb") .expect("decoding should work"), payload: SequencerMsg::Sequence(Sequence { nonce: 0, - signature, delayed_messages_prefix: 5, delayed_messages_suffix: 5, messages: Vec::default(), }), - }); + }; + + let sequence = KernelMessage::Sequencer(UnverifiedSigned { body, signature }); // Serializing let mut bin: Vec = Vec::new(); @@ -273,4 +333,255 @@ mod tests { assert!(remaining.is_empty()); assert_eq!(msg_read, KernelMessage::DelayedMessage(bin)) } + + /// Deserialize a string to a KernelMessage + fn from_string(hex: &str) -> KernelMessage { + let hex = hex::decode(hex).expect("valid hexadecimal"); + let (_, msg) = KernelMessage::nom_read(&hex).expect("deserialization should work"); + msg + } + + #[test] + fn test_deserialization_empty_sequence() { + let kernel_message = from_string("01006227a8721213bd7ddb9b56227e3acb01161b1e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let rollup_address = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let sign = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + match kernel_message { + KernelMessage::Sequencer(UnverifiedSigned { + body: + Framed { + destination, + payload: + SequencerMsg::Sequence(Sequence { + nonce, + delayed_messages_prefix, + delayed_messages_suffix, + messages, + }), + }, + signature, + }) => { + assert_eq!(destination, rollup_address); + assert_eq!(nonce, 0); + assert_eq!(delayed_messages_prefix, 0); + assert_eq!(delayed_messages_suffix, 0); + assert_eq!(messages, vec![]); + assert_eq!(signature, sign) + } + _ => panic!("Wrong message encoding"), + } + } + + #[test] + fn test_deserialization_one_empty_message_sequence() { + let kernel_message = from_string("01006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let signature = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + let expected = KernelMessage::Sequencer(UnverifiedSigned { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![Bytes { + inner: b"".to_vec(), + }], + }), + }, + signature, + }); + + assert_eq!(kernel_message, expected); + } + + #[test] + fn test_deserialization_one_message_sequence() { + let kernel_message = from_string("01006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000090000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let signature = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + let expected = KernelMessage::Sequencer(UnverifiedSigned { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![Bytes { + inner: b"hello".to_vec(), + }], + }), + }, + signature, + }); + + assert_eq!(kernel_message, expected); + } + + #[test] + fn test_deserialization_two_messages_sequence() { + let kernel_message = from_string("01006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000120000000568656c6c6f00000005776f726c6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let signature = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + let expected = KernelMessage::Sequencer(UnverifiedSigned { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![ + Bytes { + inner: b"hello".to_vec(), + }, + Bytes { + inner: b"world".to_vec(), + }, + ], + }), + }, + signature, + }); + + assert_eq!(kernel_message, expected); + } + + #[test] + fn test_incorrect_sequence_signature() { + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let nonce = 0; + let delayed_messages_prefix = 0; + let delayed_messages_suffix = 0; + let messages = vec![]; + let signature = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + let message = UnverifiedSigned { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce, + delayed_messages_prefix, + delayed_messages_suffix, + messages, + }), + }, + signature: signature.clone(), + }; + + let hash = message.hash().expect("hash failure"); + + let public_key = PublicKey::Ed25519( + PublicKeyEd25519::from_base58_check( + "edpkuDMUm7Y53wp4gxeLBXuiAhXZrLn8XB1R83ksvvesH8Lp8bmCfK", + ) + .unwrap(), + ); + + let is_correct = public_key.verify_signature(&signature, &hash); + + assert!(matches!(is_correct, Ok(false) | Err(_))); + } + + #[test] + fn test_correct_sequence_signature() { + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + + let seed = SeedEd25519::from_base58_check( + "edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB", + ) + .unwrap(); + + let (public_key, secret_key) = seed.keypair().unwrap(); + + let body = Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![], + }), + }; + + let sequence = + UnverifiedSigned::sign_ed25519(body, &secret_key).expect("error when signing body"); + + let hash = sequence.hash().unwrap(); + let is_correct = public_key.verify_signature(&sequence.signature, &hash); + assert!(matches!(is_correct, Ok(true))); + } + + #[test] + fn test_correct_non_empty_sequence_signature() { + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + + let seed = SeedEd25519::from_base58_check( + "edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB", + ) + .unwrap(); + + let (public_key, secret_key) = seed.keypair().unwrap(); + + let body = Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![Bytes { + inner: b"hello".to_vec(), + }], + }), + }; + + let sequence = + UnverifiedSigned::sign_ed25519(body, &secret_key).expect("error when signing body"); + + let hash = sequence.hash().unwrap(); + let is_correct = public_key.verify_signature(&sequence.signature, &hash); + assert!(matches!(is_correct, Ok(true))); + } + + #[test] + fn test_correct_signature_encoding() { + let signed_msg = hex::decode("006227a8721213bd7ddb9b56227e3acb01161b1e67000000000000000000000000000000000f0000000b68656c6c6f20776f726c64bc682e5a009f3ee1dc1280d6b538aa53c626c29c5d5c528c8dd92092915aa24d414cc4f7ac81be8a4eb6a6046f9beea09c3c5a54374d68e6bfbad6cb51edc306").unwrap(); + let (_, signed_msg): (&[u8], UnverifiedSigned>) = + UnverifiedSigned::nom_read(&signed_msg).unwrap(); + + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let seed = SeedEd25519::from_base58_check( + "edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB", + ) + .unwrap(); + let (public_key, _) = seed.keypair().unwrap(); + + let body = Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![Bytes { + inner: b"hello world".to_vec(), + }], + }), + }; + let hash = UnverifiedSigned::hash_body(&body).unwrap(); + + let is_correct = public_key.verify_signature(&signed_msg.signature, &hash); + + assert!(matches!(is_correct, Ok(true))); + } } diff --git a/tezt/tests/sc_sequencer.ml b/tezt/tests/sc_sequencer.ml index e0aaab0f2b4ec6355a780fb5638ac2b3ed61a922..218911aee93528f820348026614af73e8d0df03a 100644 --- a/tezt/tests/sc_sequencer.ml +++ b/tezt/tests/sc_sequencer.ml @@ -201,11 +201,13 @@ let test_delayed_inbox_consumed = let expected_sequences = List.map (fun (delayed_inbox_prefix, delayed_inbox_suffix) -> Format.sprintf - "Received a sequence message Sequence { nonce: 0, \ - delayed_messages_prefix: %d, delayed_messages_suffix: %d, messages: \ - [], signature: \ + "Received a sequence message UnverifiedSigned { body: Framed { \ + destination: SmartRollupAddress { hash: SmartRollupHash(\"%s\") }, \ + payload: Sequence(Sequence { nonce: 0, delayed_messages_prefix: %d, \ + delayed_messages_suffix: %d, messages: [] }) }, signature: \ Signature(\"edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q\") \ - } targeting our rollup" + }" + (Sc_rollup_repr.Address.to_b58check sc_rollup_address) delayed_inbox_prefix delayed_inbox_suffix) @@ [(4, 1); (7, 1)]