From 8d65b086963d2874ab5a614abcdc61ab08b08f56 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 3 Feb 2023 15:32:17 +0100 Subject: [PATCH 1/7] Add chain observer models --- mithril-common/src/chain_observer/mod.rs | 2 + mithril-common/src/chain_observer/model.rs | 136 +++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 mithril-common/src/chain_observer/model.rs diff --git a/mithril-common/src/chain_observer/mod.rs b/mithril-common/src/chain_observer/mod.rs index 27e9a1203a5..2e85adbd97f 100644 --- a/mithril-common/src/chain_observer/mod.rs +++ b/mithril-common/src/chain_observer/mod.rs @@ -4,8 +4,10 @@ mod cli_observer; #[cfg(any(test, feature = "test_only"))] mod fake_observer; mod interface; +mod model; pub use cli_observer::{CardanoCliChainObserver, CardanoCliRunner}; #[cfg(any(test, feature = "test_only"))] pub use fake_observer::FakeObserver; pub use interface::{ChainObserver, ChainObserverError}; +pub use model::{ChainAddress, TxDatum}; diff --git a/mithril-common/src/chain_observer/model.rs b/mithril-common/src/chain_observer/model.rs new file mode 100644 index 00000000000..2bf8bc7c513 --- /dev/null +++ b/mithril-common/src/chain_observer/model.rs @@ -0,0 +1,136 @@ +use serde_json::Value; +use std::{collections::HashMap, error::Error as StdError}; +use thiserror::Error; + +/// [ChainAddress] represents an on chain address +pub type ChainAddress = String; + +/// [TxDatum] related errors. +#[derive(Debug, Error)] +pub enum TxDatumError { + /// Generic [TxDatum] error. + #[error("general error {0}")] + _General(Box), + + /// Error raised when the content could not be parsed. + #[error("could not parse content: {0}")] + InvalidContent(Box), +} + +/// [TxDatum] represents transaction Datum +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TxDatum(pub String); + +impl TxDatum { + /// Retrieves the ith field of the datum with given type + pub fn get_field_raw_value( + &self, + type_name: &str, + index: usize, + ) -> Result> { + let tx_datum_raw = &self.0; + let v: HashMap = serde_json::from_str(tx_datum_raw).map_err(|e| { + TxDatumError::InvalidContent( + format!("Error: {e:?}, tx datum was = '{tx_datum_raw}'").into(), + ) + })?; + let fields = v.get("fields").ok_or_else(|| { + TxDatumError::InvalidContent( + format!("Error: missing 'fields' entry, tx datum was = '{tx_datum_raw}'").into(), + ) + })?.as_array().ok_or_else(|| { + TxDatumError::InvalidContent( + format!("Error: 'fields' entry is not correctly structured, tx datum was = '{tx_datum_raw}'").into(), + ) + })?; + let field_value = fields + .iter() + .filter(|&field| field.get(type_name).is_some()) + .nth(index) + .ok_or_else(|| { + TxDatumError::InvalidContent( + format!( + "Error: missing field at index {index}, tx datum was = '{tx_datum_raw}'" + ) + .into(), + ) + })? + .get(type_name) + .unwrap(); + + Ok(field_value.to_owned()) + } +} + +#[cfg(test)] +mod test { + use super::*; + + fn dummy_tx_datum() -> TxDatum { + TxDatum("{\"constructor\":0,\"fields\":[{\"bytes\":\"bytes0\"}, {\"int\":0}, {\"int\":1}, {\"bytes\":\"bytes1\"}, {\"bytes\":\"bytes2\"}, {\"int\":2}]}".to_string()) + } + + #[test] + fn test_can_retrieve_field_raw_value_bytes() { + let tx_datum = dummy_tx_datum(); + assert_eq!( + "bytes0", + tx_datum + .get_field_raw_value("bytes", 0) + .unwrap() + .as_str() + .unwrap() + ); + assert_eq!( + "bytes1", + tx_datum + .get_field_raw_value("bytes", 1) + .unwrap() + .as_str() + .unwrap() + ); + assert_eq!( + "bytes2", + tx_datum + .get_field_raw_value("bytes", 2) + .unwrap() + .as_str() + .unwrap() + ); + tx_datum + .get_field_raw_value("bytes", 100) + .expect_err("should have returned an error"); + } + + #[test] + fn test_can_retrieve_field_raw_value_int() { + let tx_datum = dummy_tx_datum(); + assert_eq!( + 0, + tx_datum + .get_field_raw_value("int", 0) + .unwrap() + .as_u64() + .unwrap() + ); + assert_eq!( + 1, + tx_datum + .get_field_raw_value("int", 1) + .unwrap() + .as_u64() + .unwrap() + ); + assert_eq!( + 2, + tx_datum + .get_field_raw_value("int", 2) + .unwrap() + .as_u64() + .unwrap() + ); + tx_datum + .get_field_raw_value("int", 100) + .expect_err("should have returned an error"); + } +} From f64ee9bfe67d9a9f3c24d01b27a3c00a19a50edd Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 3 Feb 2023 15:33:02 +0100 Subject: [PATCH 2/7] Retrieve utxo datums in chain observer --- mithril-common/src/beacon_provider.rs | 9 +- .../src/chain_observer/cli_observer.rs | 117 ++++++++++++++++++ .../src/chain_observer/fake_observer.rs | 8 ++ .../src/chain_observer/interface.rs | 8 ++ 4 files changed, 141 insertions(+), 1 deletion(-) diff --git a/mithril-common/src/beacon_provider.rs b/mithril-common/src/beacon_provider.rs index 27044873e8c..1bac48e8b39 100644 --- a/mithril-common/src/beacon_provider.rs +++ b/mithril-common/src/beacon_provider.rs @@ -79,7 +79,7 @@ impl BeaconProvider for BeaconProviderImpl { #[cfg(test)] mod tests { - use crate::chain_observer::{ChainObserver, ChainObserverError}; + use crate::chain_observer::{ChainAddress, ChainObserver, ChainObserverError, TxDatum}; use crate::digesters::DumbImmutableFileObserver; use crate::entities::{Epoch, StakeDistribution}; @@ -89,6 +89,13 @@ mod tests { #[async_trait] impl ChainObserver for DumbChainObserver { + async fn get_current_datums( + &self, + _address: &ChainAddress, + ) -> Result, ChainObserverError> { + Ok(Vec::new()) + } + async fn get_current_epoch(&self) -> Result, ChainObserverError> { Ok(Some(Epoch(42))) } diff --git a/mithril-common/src/chain_observer/cli_observer.rs b/mithril-common/src/chain_observer/cli_observer.rs index c4b88c1f885..9be0c18b5c5 100644 --- a/mithril-common/src/chain_observer/cli_observer.rs +++ b/mithril-common/src/chain_observer/cli_observer.rs @@ -1,18 +1,22 @@ use async_trait::async_trait; use nom::IResult; +use rand_core::RngCore; use serde_json::Value; +use std::collections::HashMap; use std::error::Error; use std::fs; use std::path::PathBuf; use tokio::process::Command; use crate::chain_observer::interface::*; +use crate::chain_observer::{ChainAddress, TxDatum}; use crate::crypto_helper::{KESPeriod, OpCert, SerDeShelleyFileFormat}; use crate::entities::{Epoch, StakeDistribution}; use crate::CardanoNetwork; #[async_trait] pub trait CliRunner { + async fn launch_utxo(&self, address: &str) -> Result>; async fn launch_stake_distribution(&self) -> Result>; async fn launch_stake_snapshot( &self, @@ -43,6 +47,27 @@ impl CardanoCliRunner { } } + fn random_out_file() -> PathBuf { + let mut rng = rand_core::OsRng; + std::env::temp_dir() + .join("cardano-cli-runner") + .join(format!("{}", rng.next_u64())) + } + + fn command_for_utxo(&self, address: &str, out_file: PathBuf) -> Command { + let mut command = self.get_command(); + command + .arg("query") + .arg("utxo") + .arg("--address") + .arg(address) + .arg("--out-file") + .arg(out_file); + self.post_config_command(&mut command); + + command + } + fn command_for_stake_distribution(&self) -> Command { let mut command = self.get_command(); command.arg("query").arg("stake-distribution"); @@ -110,6 +135,27 @@ impl CardanoCliRunner { #[async_trait] impl CliRunner for CardanoCliRunner { + async fn launch_utxo(&self, address: &str) -> Result> { + let out_file = Self::random_out_file(); + let output = self + .command_for_utxo(address, out_file.clone()) + .output() + .await?; + + if output.status.success() { + Ok(std::str::from_utf8(&output.stdout)?.trim().to_string()) + } else { + let message = String::from_utf8_lossy(&output.stderr); + + Err(format!( + "Error launching command {:?}, error = '{}'", + self.command_for_utxo(address, out_file), + message + ) + .into()) + } + } + async fn launch_stake_distribution(&self) -> Result> { let output = self.command_for_stake_distribution().output().await?; @@ -255,6 +301,30 @@ impl ChainObserver for CardanoCliChainObserver { } } + async fn get_current_datums( + &self, + address: &ChainAddress, + ) -> Result, ChainObserverError> { + let output = self + .cli_runner + .launch_utxo(address) + .await + .map_err(ChainObserverError::General)?; + let v: HashMap = serde_json::from_str(&output).map_err(|e| { + ChainObserverError::InvalidContent( + format!("Error: {e:?}, output was = '{output}'").into(), + ) + })?; + + Ok(v.values() + .filter_map(|v| { + v.get("inlineDatum") + .filter(|datum| !datum.is_null()) + .map(|datum| TxDatum(datum.to_string())) + }) + .collect()) + } + async fn get_current_stake_distribution( &self, ) -> Result, ChainObserverError> { @@ -337,6 +407,45 @@ mod tests { #[async_trait] impl CliRunner for TestCliRunner { + async fn launch_utxo( + &self, + _address: &str, + ) -> Result> { + let output = r#" +{ + "1fd4d3e131afe3c8b212772a3f3083d2fbc6b2a7b20e54e4ff08e001598818d8#0": { + "address": "addr_test1vpcr3he05gemue6eyy0c9clajqnnww8aa2l3jszjdlszjhq093qrn", + "datum": null, + "inlineDatum": { + "constructor": 0, + "fields": [ + { + "bytes": "5b0a20207b0a20202020226e616d65223a20227468616c6573222c0a202020202265706f6368223a203132330a20207d2c0a20207b0a20202020226e616d65223a20227079746861676f726173222c0a202020202265706f6368223a206e756c6c0a20207d0a5d0a" + } + ] + }, + "inlineDatumhash": "b97cbaa0dc5b41864c83c2f625d9bc2a5f3e6b5cd5071c14a2090e630e188c80", + "referenceScript": null, + "value": { + "lovelace": 10000000 + } + }, + "1fd4d3e131afe3c8b212772a3f3083d2fbc6b2a7b20e54e4ff08e001598818d8#1": { + "address": "addr_test1vpcr3he05gemue6eyy0c9clajqnnww8aa2l3jszjdlszjhq093qrn", + "datum": null, + "datumhash": null, + "inlineDatum": null, + "referenceScript": null, + "value": { + "lovelace": 9989656678 + } + } +} +"#; + + Ok(output.to_string()) + } + async fn launch_stake_distribution(&self) -> Result> { let output = r#" PoolId Stake frac @@ -471,6 +580,14 @@ pool1qz2vzszautc2c8mljnqre2857dpmheq7kgt6vav0s38tvvhxm6w 1.051e-6 ); } + #[tokio::test] + async fn test_get_current_datums() { + let observer = CardanoCliChainObserver::new(Box::new(TestCliRunner {})); + let address = "addrtest_123456".to_string(); + let datums = observer.get_current_datums(&address).await.unwrap(); + assert_eq!(vec![TxDatum("{\"constructor\":0,\"fields\":[{\"bytes\":\"5b0a20207b0a20202020226e616d65223a20227468616c6573222c0a202020202265706f6368223a203132330a20207d2c0a20207b0a20202020226e616d65223a20227079746861676f726173222c0a202020202265706f6368223a206e756c6c0a20207d0a5d0a\"}]}".to_string())], datums); + } + #[tokio::test] async fn test_get_current_stake_value() { let observer = CardanoCliChainObserver::new(Box::new(TestCliRunner {})); diff --git a/mithril-common/src/chain_observer/fake_observer.rs b/mithril-common/src/chain_observer/fake_observer.rs index 5501f78f60d..2b472ca0cdb 100644 --- a/mithril-common/src/chain_observer/fake_observer.rs +++ b/mithril-common/src/chain_observer/fake_observer.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use tokio::sync::RwLock; use crate::chain_observer::interface::*; +use crate::chain_observer::{ChainAddress, TxDatum}; use crate::crypto_helper::{KESPeriod, OpCert}; use crate::{entities::*, test_utils::fake_data}; @@ -57,6 +58,13 @@ impl Default for FakeObserver { #[async_trait] impl ChainObserver for FakeObserver { + async fn get_current_datums( + &self, + _address: &ChainAddress, + ) -> Result, ChainObserverError> { + Ok(Vec::new()) + } + async fn get_current_epoch(&self) -> Result, ChainObserverError> { Ok(self .current_beacon diff --git a/mithril-common/src/chain_observer/interface.rs b/mithril-common/src/chain_observer/interface.rs index c0774f24694..a9fec3e251f 100644 --- a/mithril-common/src/chain_observer/interface.rs +++ b/mithril-common/src/chain_observer/interface.rs @@ -7,6 +7,8 @@ use mockall::automock; use std::error::Error as StdError; use thiserror::Error; +use super::{ChainAddress, TxDatum}; + /// [ChainObserver] related errors. #[derive(Debug, Error)] pub enum ChainObserverError { @@ -23,6 +25,12 @@ pub enum ChainObserverError { #[automock] #[async_trait] pub trait ChainObserver: Sync + Send { + /// Retrive the datums associated to and address + async fn get_current_datums( + &self, + address: &ChainAddress, + ) -> Result, ChainObserverError>; + /// Retrieve the current epoch of the Cardano network async fn get_current_epoch(&self) -> Result, ChainObserverError>; From 13239ef8483c95bc0fd851274a426a6687100915 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Fri, 3 Feb 2023 18:09:16 +0100 Subject: [PATCH 3/7] Add Cardano chain EraReaderAdapter --- .../src/chain_observer/fake_observer.rs | 33 ++++- .../src/era/cardano_chain_adapter.rs | 133 ++++++++++++++++++ mithril-common/src/era/mod.rs | 1 + 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 mithril-common/src/era/cardano_chain_adapter.rs diff --git a/mithril-common/src/chain_observer/fake_observer.rs b/mithril-common/src/chain_observer/fake_observer.rs index 2b472ca0cdb..95ba2293bb4 100644 --- a/mithril-common/src/chain_observer/fake_observer.rs +++ b/mithril-common/src/chain_observer/fake_observer.rs @@ -17,6 +17,11 @@ pub struct FakeObserver { /// /// [get_current_epoch]: ChainObserver::get_current_epoch pub current_beacon: RwLock>, + + /// A list of [TxDatum], used by [get_current_datums] + /// + /// [get_current_datums]: ChainObserver::get_current_datums + pub datums: RwLock>, } impl FakeObserver { @@ -25,6 +30,7 @@ impl FakeObserver { Self { signers: RwLock::new(vec![]), current_beacon: RwLock::new(current_beacon), + datums: RwLock::new(vec![]), } } @@ -45,6 +51,13 @@ impl FakeObserver { let mut signers = self.signers.write().await; *signers = new_signers; } + + /// Set the datums that will used to compute the result of + /// [get_current_datums][ChainObserver::get_current_datums]. + pub async fn set_datums(&self, new_datums: Vec) { + let mut datums = self.datums.write().await; + *datums = new_datums; + } } impl Default for FakeObserver { @@ -62,7 +75,8 @@ impl ChainObserver for FakeObserver { &self, _address: &ChainAddress, ) -> Result, ChainObserverError> { - Ok(Vec::new()) + let datums = self.datums.read().await; + Ok(datums.to_vec()) } async fn get_current_epoch(&self) -> Result, ChainObserverError> { @@ -124,4 +138,21 @@ mod tests { "get current stake distribution should not fail and should not be empty" ); } + + #[tokio::test] + async fn test_get_current_datums() { + let fake_address = "addr_test_123456".to_string(); + let fake_datums = vec![ + TxDatum("tx_datum_1".to_string()), + TxDatum("tx_datum_2".to_string()), + ]; + let fake_observer = FakeObserver::new(None); + fake_observer.set_datums(fake_datums.clone()).await; + let datums = fake_observer + .get_current_datums(&fake_address) + .await + .expect("get_current_datums should not fail"); + + assert_eq!(fake_datums, datums); + } } diff --git a/mithril-common/src/era/cardano_chain_adapter.rs b/mithril-common/src/era/cardano_chain_adapter.rs new file mode 100644 index 00000000000..fd6388dcf66 --- /dev/null +++ b/mithril-common/src/era/cardano_chain_adapter.rs @@ -0,0 +1,133 @@ +use crate::chain_observer::ChainAddress; +use crate::crypto_helper::key_decode_hex; +use crate::{chain_observer::ChainObserver, entities::Epoch}; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::error::Error as StdError; +use std::sync::Arc; +use thiserror::Error; + +// TODO: remove EraMarker & EraReaderAdapter w/ they are available + +/// Value object that represents a tag of Era change. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct EraMarker { + /// Era name + pub name: String, + + /// Eventual information that advertises the Epoch of transition. + pub epoch: Option, +} + +impl EraMarker { + /// instanciate a new [EraMarker]. + pub fn new(name: &str, epoch: Option) -> Self { + let name = name.to_string(); + + Self { name, epoch } + } +} + +#[async_trait] +pub trait EraReaderAdapter: Sync + Send { + /// Read era markers from the underlying adapter. + async fn read(&self) -> Result, Box>; +} + +// TODO: Keep Cardano Chain Adapter part below + +type HexEncodeEraMarkerSignature = String; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +struct EraMarkersPayload { + markers: Vec, + signature: HexEncodeEraMarkerSignature, +} + +/// Cardano Chain adapter retrieves era markers on chain +pub struct CardanoChainAdapter { + address: ChainAddress, + chain_observer: Arc, +} + +impl CardanoChainAdapter { + /// CardanoChainAdapter factory + pub fn new(address: ChainAddress, chain_observer: Arc) -> Self { + Self { + address, + chain_observer, + } + } +} + +#[async_trait] +impl EraReaderAdapter for CardanoChainAdapter { + async fn read(&self) -> Result, Box> { + let tx_datums = self + .chain_observer + .get_current_datums(&self.address) + .await?; + let markers_list = tx_datums + .into_iter() + .filter_map(|datum| datum.get_field_raw_value("bytes", 0).ok()) + .filter_map(|field_value| field_value.as_str().map(|s| s.to_string())) + .filter_map(|field_value_str| key_decode_hex(&field_value_str).ok()) + .map(|era_markers_payload: EraMarkersPayload| era_markers_payload.markers) + .collect::>>(); + Ok(markers_list.first().unwrap_or(&Vec::new()).to_owned()) + } +} + +#[cfg(test)] +mod test { + use crate::chain_observer::{FakeObserver, TxDatum}; + use crate::crypto_helper::key_encode_hex; + + use super::*; + + fn dummy_tx_datums_from_markers_payload(payloads: Vec) -> Vec { + payloads + .into_iter() + .map(|payload| { + TxDatum(format!( + "{{\"constructor\":0,\"fields\":[{{\"bytes\":\"{}\"}}]}}", + key_encode_hex(payload).unwrap() + )) + }) + .collect() + } + + #[tokio::test] + async fn test_cardano_chain_adapter() { + let fake_address = "addr_test_123456".to_string(); + let era_marker_payload_1 = EraMarkersPayload { + markers: vec![ + EraMarker::new("thales", Some(Epoch(1))), + EraMarker::new("pythagors", None), + ], + signature: "".to_string(), + }; + let era_marker_payload_2 = EraMarkersPayload { + markers: vec![ + EraMarker::new("thales", Some(Epoch(1))), + EraMarker::new("pythagors", Some(Epoch(2))), + ], + signature: "".to_string(), + }; + let mut fake_datums = dummy_tx_datums_from_markers_payload(vec![ + era_marker_payload_1.clone(), + era_marker_payload_2, + ]); + fake_datums.push(TxDatum("not_valid_datum".to_string())); + let chain_observer = FakeObserver::default(); + chain_observer.set_datums(fake_datums.clone()).await; + let cardano_chain_adapter = + CardanoChainAdapter::new(fake_address, Arc::new(chain_observer)); + let markers = cardano_chain_adapter + .read() + .await + .expect("CardanoChainAdapter read should not fail"); + let expected_markers = era_marker_payload_1.markers.to_owned(); + assert_eq!(expected_markers, markers); + } +} diff --git a/mithril-common/src/era/mod.rs b/mithril-common/src/era/mod.rs index 51aee2ed78b..0dfebff7192 100644 --- a/mithril-common/src/era/mod.rs +++ b/mithril-common/src/era/mod.rs @@ -1,6 +1,7 @@ //! The module used for handling eras pub mod adapters; +mod cardano_chain_adapter; mod era_checker; mod era_reader; mod supported_era; From 0e834d98129a2df1e43c869c0fe658b683c75e0c Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Mon, 6 Feb 2023 16:50:26 +0100 Subject: [PATCH 4/7] Add EraMarkers sign/verify --- mithril-common/src/crypto_helper/era.rs | 178 ++++++++++++++++++ mithril-common/src/crypto_helper/mod.rs | 5 + .../src/era/cardano_chain_adapter.rs | 113 +++++++++-- 3 files changed, 281 insertions(+), 15 deletions(-) create mode 100644 mithril-common/src/crypto_helper/era.rs diff --git a/mithril-common/src/crypto_helper/era.rs b/mithril-common/src/crypto_helper/era.rs new file mode 100644 index 00000000000..a5680799a6b --- /dev/null +++ b/mithril-common/src/crypto_helper/era.rs @@ -0,0 +1,178 @@ +use ed25519_dalek::{ExpandedSecretKey, SignatureError}; +use rand_chacha_dalek_compat::rand_core::{self, CryptoRng, RngCore, SeedableRng}; +use rand_chacha_dalek_compat::ChaCha20Rng; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +/// Alias of [Ed25519:PublicKey](https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.PublicKey.html). +pub type EraMarkersVerifierVerificationKey = ed25519_dalek::PublicKey; + +/// Alias of [Ed25519:SecretKey](https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.SecretKey.html). +pub type EraMarkersVerifierSecretKey = ed25519_dalek::SecretKey; + +/// Alias of [Ed25519:Signature](https://docs.rs/ed25519-dalek/latest/ed25519_dalek/struct.Signature.html). +pub type EraMarkersVerifierSignature = ed25519_dalek::Signature; + +#[derive(Error, Debug)] +/// [EraMarkersSigner] and [EraMarkersVerifier] related errors. +pub enum EraMarkersVerifierError { + /// Error raised when a Signature verification fail + #[error("era markers signature verification error: '{0}'")] + SignatureVerification(#[from] SignatureError), +} + +/// A protocol Signer that is responsible for signing the +/// [Certificate](https://mithril.network/doc/mithril/mithril-protocol/certificates#the-certificate-chain-design) +#[derive(Debug, Serialize, Deserialize)] +pub struct EraMarkersSigner { + pub(crate) secret_key: EraMarkersVerifierSecretKey, +} + +impl EraMarkersSigner { + /// EraMarkersSigner factory + pub fn create_test_signer(mut rng: R) -> Self + where + R: CryptoRng + RngCore, + { + let secret_key = EraMarkersVerifierSecretKey::generate(&mut rng); + Self::from_secret_key(secret_key) + } + + /// EraMarkersSigner deterministic + pub fn create_deterministic_signer() -> Self { + let rng = ChaCha20Rng::from_seed([0u8; 32]); + Self::create_test_signer(rng) + } + + /// EraMarkersSigner non deterministic + pub fn create_non_deterministic_signer() -> Self { + let rng = rand_core::OsRng; + Self::create_test_signer(rng) + } + + /// EraMarkersSigner from EraMarkersVerifierSecretKey + pub fn from_secret_key(secret_key: EraMarkersVerifierSecretKey) -> Self { + Self { secret_key } + } + + /// Create a an expanded secret key + fn create_expanded_secret_key(&self) -> ExpandedSecretKey { + ExpandedSecretKey::from(&self.secret_key) + } + + /// Create a EraMarkersVerifierVerificationKey + fn create_verification_key( + &self, + expanded_secret_key: &ExpandedSecretKey, + ) -> EraMarkersVerifierVerificationKey { + let verification_key: EraMarkersVerifierVerificationKey = expanded_secret_key.into(); + verification_key + } + + /// Create a EraMarkersVerifier + pub fn create_verifier(&self) -> EraMarkersVerifier { + let expanded_secret_key = self.create_expanded_secret_key(); + let verification_key = self.create_verification_key(&expanded_secret_key); + EraMarkersVerifier::from_verification_key(verification_key) + } + + /// Signs a message and returns a EraMarkersVerifierSignature + pub fn sign(&self, message: &[u8]) -> EraMarkersVerifierSignature { + let expanded_secret_key = self.create_expanded_secret_key(); + let verification_key = self.create_verification_key(&expanded_secret_key); + expanded_secret_key.sign(message, &verification_key) + } +} + +/// An era merkers Verifier +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct EraMarkersVerifier { + pub(crate) verification_key: EraMarkersVerifierVerificationKey, +} + +impl EraMarkersVerifier { + /// EraMarkersVerifier from EraMarkersVerifierVerificationKey + pub fn from_verification_key(verification_key: EraMarkersVerifierVerificationKey) -> Self { + Self { verification_key } + } + + /// EraMarkersVerifier to EraMarkersVerifierVerificationKey + pub fn to_verification_key(&self) -> EraMarkersVerifierVerificationKey { + self.verification_key + } + + /// Verifies the signature of a message + pub fn verify( + &self, + message: &[u8], + signature: &EraMarkersVerifierSignature, + ) -> Result<(), EraMarkersVerifierError> { + Ok(self.verification_key.verify_strict(message, signature)?) + } +} + +#[cfg(test)] +mod tests { + use super::super::codec::{key_decode_hex, key_encode_hex}; + use super::*; + + #[test] + fn test_generate_test_deterministic_keypair() { + let signer = EraMarkersSigner::create_deterministic_signer(); + let verifier = signer.create_verifier(); + let signer_2 = EraMarkersSigner::create_deterministic_signer(); + let verifier_2 = signer.create_verifier(); + assert_eq!(signer.secret_key.as_bytes(), signer_2.secret_key.as_bytes()); + assert_eq!( + verifier.verification_key.as_bytes(), + verifier_2.verification_key.as_bytes() + ); + + println!( + "Deterministic Verification Key={}", + key_encode_hex(verifier.verification_key.as_bytes()).unwrap() + ); + println!( + "Deterministic Secret Key=={}", + key_encode_hex(signer.secret_key.as_bytes()).unwrap() + ); + } + + #[test] + fn test_generate_test_non_deterministic_keypair() { + let signer = EraMarkersSigner::create_non_deterministic_signer(); + let verifier = signer.create_verifier(); + + println!( + "Non Deterministic Verification Key={}", + key_encode_hex(verifier.verification_key.as_bytes()).unwrap() + ); + println!( + "Non Deterministic Secret Key=={}", + key_encode_hex(signer.secret_key.as_bytes()).unwrap() + ); + } + + #[test] + fn test_codec_keypair() { + let signer = EraMarkersSigner::create_deterministic_signer(); + let verifier = signer.create_verifier(); + let secret_key_encoded = key_encode_hex(signer.secret_key.as_bytes()).unwrap(); + let verification_key_encoded = + key_encode_hex(verifier.verification_key.as_bytes()).unwrap(); + let secret_key_decoded: EraMarkersVerifierSecretKey = + key_decode_hex(&secret_key_encoded).unwrap(); + let verification_key_decoded: EraMarkersVerifierVerificationKey = + key_decode_hex(&verification_key_encoded).unwrap(); + let signer_decoded = EraMarkersSigner::from_secret_key(secret_key_decoded); + let verifier_decoded = EraMarkersVerifier::from_verification_key(verification_key_decoded); + + let message: &[u8] = b"some message."; + let signature = signer_decoded.sign(message); + let verify_signature = verifier_decoded.verify(message, &signature); + assert!( + verify_signature.is_ok(), + "signature verification should not fail" + ); + } +} diff --git a/mithril-common/src/crypto_helper/mod.rs b/mithril-common/src/crypto_helper/mod.rs index 3901987632c..66d4346cc9a 100644 --- a/mithril-common/src/crypto_helper/mod.rs +++ b/mithril-common/src/crypto_helper/mod.rs @@ -3,6 +3,7 @@ mod cardano; mod codec; mod conversions; +mod era; mod genesis; #[cfg(any(test, feature = "test_only"))] pub mod tests_setup; @@ -12,6 +13,10 @@ mod types; pub use cardano::ColdKeyGenerator; pub use cardano::{KESPeriod, OpCert, SerDeShelleyFileFormat}; pub use codec::*; +pub use era::{ + EraMarkersSigner, EraMarkersVerifier, EraMarkersVerifierError, EraMarkersVerifierSecretKey, + EraMarkersVerifierSignature, EraMarkersVerifierVerificationKey, +}; pub use genesis::{ProtocolGenesisError, ProtocolGenesisSigner, ProtocolGenesisVerifier}; pub use types::*; diff --git a/mithril-common/src/era/cardano_chain_adapter.rs b/mithril-common/src/era/cardano_chain_adapter.rs index fd6388dcf66..fa70dae2d17 100644 --- a/mithril-common/src/era/cardano_chain_adapter.rs +++ b/mithril-common/src/era/cardano_chain_adapter.rs @@ -1,12 +1,20 @@ -use crate::chain_observer::ChainAddress; -use crate::crypto_helper::key_decode_hex; -use crate::{chain_observer::ChainObserver, entities::Epoch}; +use crate::{ + chain_observer::{ChainAddress, ChainObserver}, + crypto_helper::{ + key_decode_hex, EraMarkersSigner, EraMarkersVerifier, EraMarkersVerifierSignature, + EraMarkersVerifierVerificationKey, + }, + entities::Epoch, +}; use async_trait::async_trait; +use hex::{FromHex, ToHex}; use serde::{Deserialize, Serialize}; use std::error::Error as StdError; use std::sync::Arc; use thiserror::Error; +type GeneralError = Box; + // TODO: remove EraMarker & EraReaderAdapter w/ they are available /// Value object that represents a tag of Era change. @@ -31,38 +39,101 @@ impl EraMarker { #[async_trait] pub trait EraReaderAdapter: Sync + Send { /// Read era markers from the underlying adapter. - async fn read(&self) -> Result, Box>; + async fn read(&self) -> Result, GeneralError>; } // TODO: Keep Cardano Chain Adapter part below type HexEncodeEraMarkerSignature = String; +/// [EraMarkersPayload] related errors. +#[derive(Debug, Error)] +pub enum EraMarkersPayloadError { + /// Error raised when the message serialization fails + #[error("could not serialize message: {0}")] + SerializeMessage(GeneralError), + + /// Error raised when the signature deserialization fails + #[error("could not deserialize signature: {0}")] + DeserializeSignature(GeneralError), + + /// Error raised when the signature is invalid + #[error("could not verify signature: {0}")] + VerifySignature(GeneralError), + + /// Error raised when the signing the markers + #[error("could not create signature: {0}")] + CreateSignature(GeneralError), +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] struct EraMarkersPayload { markers: Vec, signature: HexEncodeEraMarkerSignature, } +impl EraMarkersPayload { + fn message_to_sign(&self) -> Result, EraMarkersPayloadError> { + serde_json::to_vec(&self.markers) + .map_err(|e| EraMarkersPayloadError::SerializeMessage(e.into())) + } + + fn verify_signature( + &self, + verification_key: EraMarkersVerifierVerificationKey, + ) -> Result<(), EraMarkersPayloadError> { + let signature = EraMarkersVerifierSignature::from_bytes( + &Vec::from_hex(&self.signature) + .map_err(|e| EraMarkersPayloadError::DeserializeSignature(e.into()))?, + ) + .map_err(|e| EraMarkersPayloadError::DeserializeSignature(e.into()))?; + let markers_verifier = EraMarkersVerifier::from_verification_key(verification_key); + markers_verifier + .verify(&self.message_to_sign()?, &signature) + .map_err(|e| EraMarkersPayloadError::VerifySignature(e.into())) + } + + #[cfg(any(test, feature = "test_only"))] + fn sign(self, signer: &EraMarkersSigner) -> Result { + let signature = signer + .sign( + &self + .message_to_sign() + .map_err(|e| EraMarkersPayloadError::CreateSignature(e.into()))?, + ) + .encode_hex::(); + Ok(Self { + markers: self.markers, + signature, + }) + } +} + /// Cardano Chain adapter retrieves era markers on chain pub struct CardanoChainAdapter { address: ChainAddress, chain_observer: Arc, + verification_key: EraMarkersVerifierVerificationKey, } impl CardanoChainAdapter { /// CardanoChainAdapter factory - pub fn new(address: ChainAddress, chain_observer: Arc) -> Self { + pub fn new( + address: ChainAddress, + chain_observer: Arc, + verification_key: EraMarkersVerifierVerificationKey, + ) -> Self { Self { address, chain_observer, + verification_key, } } } #[async_trait] impl EraReaderAdapter for CardanoChainAdapter { - async fn read(&self) -> Result, Box> { + async fn read(&self) -> Result, GeneralError> { let tx_datums = self .chain_observer .get_current_datums(&self.address) @@ -72,7 +143,12 @@ impl EraReaderAdapter for CardanoChainAdapter { .filter_map(|datum| datum.get_field_raw_value("bytes", 0).ok()) .filter_map(|field_value| field_value.as_str().map(|s| s.to_string())) .filter_map(|field_value_str| key_decode_hex(&field_value_str).ok()) - .map(|era_markers_payload: EraMarkersPayload| era_markers_payload.markers) + .filter_map(|era_markers_payload: EraMarkersPayload| { + era_markers_payload + .verify_signature(self.verification_key) + .ok() + .map(|_| era_markers_payload.markers) + }) .collect::>>(); Ok(markers_list.first().unwrap_or(&Vec::new()).to_owned()) } @@ -81,7 +157,7 @@ impl EraReaderAdapter for CardanoChainAdapter { #[cfg(test)] mod test { use crate::chain_observer::{FakeObserver, TxDatum}; - use crate::crypto_helper::key_encode_hex; + use crate::crypto_helper::{key_encode_hex, EraMarkersSigner}; use super::*; @@ -99,35 +175,42 @@ mod test { #[tokio::test] async fn test_cardano_chain_adapter() { + let era_markers_signer = EraMarkersSigner::create_deterministic_signer(); let fake_address = "addr_test_123456".to_string(); let era_marker_payload_1 = EraMarkersPayload { markers: vec![ EraMarker::new("thales", Some(Epoch(1))), - EraMarker::new("pythagors", None), + EraMarker::new("pythagoras", None), ], signature: "".to_string(), }; let era_marker_payload_2 = EraMarkersPayload { markers: vec![ EraMarker::new("thales", Some(Epoch(1))), - EraMarker::new("pythagors", Some(Epoch(2))), + EraMarker::new("pythagoras", Some(Epoch(2))), ], signature: "".to_string(), }; let mut fake_datums = dummy_tx_datums_from_markers_payload(vec![ - era_marker_payload_1.clone(), - era_marker_payload_2, + era_marker_payload_1, + era_marker_payload_2 + .clone() + .sign(&era_markers_signer) + .unwrap(), ]); fake_datums.push(TxDatum("not_valid_datum".to_string())); let chain_observer = FakeObserver::default(); chain_observer.set_datums(fake_datums.clone()).await; - let cardano_chain_adapter = - CardanoChainAdapter::new(fake_address, Arc::new(chain_observer)); + let cardano_chain_adapter = CardanoChainAdapter::new( + fake_address, + Arc::new(chain_observer), + era_markers_signer.create_verifier().to_verification_key(), + ); let markers = cardano_chain_adapter .read() .await .expect("CardanoChainAdapter read should not fail"); - let expected_markers = era_marker_payload_1.markers.to_owned(); + let expected_markers = era_marker_payload_2.markers.to_owned(); assert_eq!(expected_markers, markers); } } From 1e37af66cc0dfe3d65987f9482d3c2ffe59f44c1 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 7 Feb 2023 12:20:31 +0100 Subject: [PATCH 5/7] Move Cardano chain adpater to submodule --- .../cardano_chain.rs} | 32 ++----------------- mithril-common/src/era/adapters/mod.rs | 2 ++ mithril-common/src/era/era_reader.rs | 6 ++-- mithril-common/src/era/mod.rs | 1 - 4 files changed, 7 insertions(+), 34 deletions(-) rename mithril-common/src/era/{cardano_chain_adapter.rs => adapters/cardano_chain.rs} (89%) diff --git a/mithril-common/src/era/cardano_chain_adapter.rs b/mithril-common/src/era/adapters/cardano_chain.rs similarity index 89% rename from mithril-common/src/era/cardano_chain_adapter.rs rename to mithril-common/src/era/adapters/cardano_chain.rs index fa70dae2d17..d348916ec6e 100644 --- a/mithril-common/src/era/cardano_chain_adapter.rs +++ b/mithril-common/src/era/adapters/cardano_chain.rs @@ -4,7 +4,7 @@ use crate::{ key_decode_hex, EraMarkersSigner, EraMarkersVerifier, EraMarkersVerifierSignature, EraMarkersVerifierVerificationKey, }, - entities::Epoch, + era::{EraMarker, EraReaderAdapter}, }; use async_trait::async_trait; use hex::{FromHex, ToHex}; @@ -15,35 +15,6 @@ use thiserror::Error; type GeneralError = Box; -// TODO: remove EraMarker & EraReaderAdapter w/ they are available - -/// Value object that represents a tag of Era change. -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct EraMarker { - /// Era name - pub name: String, - - /// Eventual information that advertises the Epoch of transition. - pub epoch: Option, -} - -impl EraMarker { - /// instanciate a new [EraMarker]. - pub fn new(name: &str, epoch: Option) -> Self { - let name = name.to_string(); - - Self { name, epoch } - } -} - -#[async_trait] -pub trait EraReaderAdapter: Sync + Send { - /// Read era markers from the underlying adapter. - async fn read(&self) -> Result, GeneralError>; -} - -// TODO: Keep Cardano Chain Adapter part below - type HexEncodeEraMarkerSignature = String; /// [EraMarkersPayload] related errors. @@ -158,6 +129,7 @@ impl EraReaderAdapter for CardanoChainAdapter { mod test { use crate::chain_observer::{FakeObserver, TxDatum}; use crate::crypto_helper::{key_encode_hex, EraMarkersSigner}; + use crate::entities::Epoch; use super::*; diff --git a/mithril-common/src/era/adapters/mod.rs b/mithril-common/src/era/adapters/mod.rs index 54bef8b8b1e..c1f98e12baf 100644 --- a/mithril-common/src/era/adapters/mod.rs +++ b/mithril-common/src/era/adapters/mod.rs @@ -1,6 +1,8 @@ //! Module dedicated to EraReaderAdapter implementations. mod bootstrap; +mod cardano_chain; mod dummy; pub use bootstrap::BootstrapAdapter as EraReaderBootstrapAdapter; +pub use cardano_chain::CardanoChainAdapter; pub use dummy::DummyAdapter as EraReaderDummyAdapter; diff --git a/mithril-common/src/era/era_reader.rs b/mithril-common/src/era/era_reader.rs index b784ad6f669..d5cf1c7c43d 100644 --- a/mithril-common/src/era/era_reader.rs +++ b/mithril-common/src/era/era_reader.rs @@ -1,13 +1,13 @@ -use std::{error::Error as StdError, str::FromStr}; - use crate::entities::Epoch; use async_trait::async_trait; +use serde::{Deserialize, Serialize}; +use std::{error::Error as StdError, str::FromStr}; use thiserror::Error; use super::{supported_era::UnsupportedEraError, SupportedEra}; /// Value object that represents a tag of Era change. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct EraMarker { /// Era name pub name: String, diff --git a/mithril-common/src/era/mod.rs b/mithril-common/src/era/mod.rs index 0dfebff7192..51aee2ed78b 100644 --- a/mithril-common/src/era/mod.rs +++ b/mithril-common/src/era/mod.rs @@ -1,7 +1,6 @@ //! The module used for handling eras pub mod adapters; -mod cardano_chain_adapter; mod era_checker; mod era_reader; mod supported_era; From 762c1825974d2c76c15661845fce14475fe1a9d1 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Raynaud Date: Tue, 7 Feb 2023 17:18:54 +0100 Subject: [PATCH 6/7] Apply review comments --- .../src/chain_observer/cli_observer.rs | 14 ++--- mithril-common/src/chain_observer/model.rs | 23 +++++---- mithril-common/src/crypto_helper/era.rs | 5 +- .../src/era/adapters/cardano_chain.rs | 51 ++++++++++++------- .../mithril-devnet/devnet-mkfiles.sh | 2 +- 5 files changed, 58 insertions(+), 37 deletions(-) diff --git a/mithril-common/src/chain_observer/cli_observer.rs b/mithril-common/src/chain_observer/cli_observer.rs index 9be0c18b5c5..3d64efea43e 100644 --- a/mithril-common/src/chain_observer/cli_observer.rs +++ b/mithril-common/src/chain_observer/cli_observer.rs @@ -47,11 +47,13 @@ impl CardanoCliRunner { } } - fn random_out_file() -> PathBuf { + fn random_out_file() -> Result> { let mut rng = rand_core::OsRng; - std::env::temp_dir() - .join("cardano-cli-runner") - .join(format!("{}", rng.next_u64())) + let dir = std::env::temp_dir().join("cardano-cli-runner"); + if !dir.exists() { + fs::create_dir_all(&dir)?; + } + Ok(dir.join(format!("{}.out", rng.next_u64()))) } fn command_for_utxo(&self, address: &str, out_file: PathBuf) -> Command { @@ -136,14 +138,14 @@ impl CardanoCliRunner { #[async_trait] impl CliRunner for CardanoCliRunner { async fn launch_utxo(&self, address: &str) -> Result> { - let out_file = Self::random_out_file(); + let out_file = Self::random_out_file()?; let output = self .command_for_utxo(address, out_file.clone()) .output() .await?; if output.status.success() { - Ok(std::str::from_utf8(&output.stdout)?.trim().to_string()) + Ok(fs::read_to_string(out_file)?.trim().to_string()) } else { let message = String::from_utf8_lossy(&output.stderr); diff --git a/mithril-common/src/chain_observer/model.rs b/mithril-common/src/chain_observer/model.rs index 2bf8bc7c513..b957f3105ac 100644 --- a/mithril-common/src/chain_observer/model.rs +++ b/mithril-common/src/chain_observer/model.rs @@ -22,18 +22,20 @@ pub enum TxDatumError { pub struct TxDatum(pub String); impl TxDatum { - /// Retrieves the ith field of the datum with given type - pub fn get_field_raw_value( + /// Retrieves the nth field of the datum with given type + pub fn get_nth_field_by_type( &self, type_name: &str, index: usize, ) -> Result> { let tx_datum_raw = &self.0; + // 1- Parse the Utxo raw data to a hashmap let v: HashMap = serde_json::from_str(tx_datum_raw).map_err(|e| { TxDatumError::InvalidContent( format!("Error: {e:?}, tx datum was = '{tx_datum_raw}'").into(), ) })?; + // 2- Convert the 'fields' entry to a vec of json objects let fields = v.get("fields").ok_or_else(|| { TxDatumError::InvalidContent( format!("Error: missing 'fields' entry, tx datum was = '{tx_datum_raw}'").into(), @@ -43,6 +45,7 @@ impl TxDatum { format!("Error: 'fields' entry is not correctly structured, tx datum was = '{tx_datum_raw}'").into(), ) })?; + // 3- Filter the vec (keep the ones that match the given type), and retrieve the nth entry of this filtered vec let field_value = fields .iter() .filter(|&field| field.get(type_name).is_some()) @@ -76,7 +79,7 @@ mod test { assert_eq!( "bytes0", tx_datum - .get_field_raw_value("bytes", 0) + .get_nth_field_by_type("bytes", 0) .unwrap() .as_str() .unwrap() @@ -84,7 +87,7 @@ mod test { assert_eq!( "bytes1", tx_datum - .get_field_raw_value("bytes", 1) + .get_nth_field_by_type("bytes", 1) .unwrap() .as_str() .unwrap() @@ -92,13 +95,13 @@ mod test { assert_eq!( "bytes2", tx_datum - .get_field_raw_value("bytes", 2) + .get_nth_field_by_type("bytes", 2) .unwrap() .as_str() .unwrap() ); tx_datum - .get_field_raw_value("bytes", 100) + .get_nth_field_by_type("bytes", 100) .expect_err("should have returned an error"); } @@ -108,7 +111,7 @@ mod test { assert_eq!( 0, tx_datum - .get_field_raw_value("int", 0) + .get_nth_field_by_type("int", 0) .unwrap() .as_u64() .unwrap() @@ -116,7 +119,7 @@ mod test { assert_eq!( 1, tx_datum - .get_field_raw_value("int", 1) + .get_nth_field_by_type("int", 1) .unwrap() .as_u64() .unwrap() @@ -124,13 +127,13 @@ mod test { assert_eq!( 2, tx_datum - .get_field_raw_value("int", 2) + .get_nth_field_by_type("int", 2) .unwrap() .as_u64() .unwrap() ); tx_datum - .get_field_raw_value("int", 100) + .get_nth_field_by_type("int", 100) .expect_err("should have returned an error"); } } diff --git a/mithril-common/src/crypto_helper/era.rs b/mithril-common/src/crypto_helper/era.rs index a5680799a6b..9d7d7ab1edb 100644 --- a/mithril-common/src/crypto_helper/era.rs +++ b/mithril-common/src/crypto_helper/era.rs @@ -21,8 +21,7 @@ pub enum EraMarkersVerifierError { SignatureVerification(#[from] SignatureError), } -/// A protocol Signer that is responsible for signing the -/// [Certificate](https://mithril.network/doc/mithril/mithril-protocol/certificates#the-certificate-chain-design) +/// A cryptographic signer that is responsible for signing the [EreMarker]s #[derive(Debug, Serialize, Deserialize)] pub struct EraMarkersSigner { pub(crate) secret_key: EraMarkersVerifierSecretKey, @@ -84,7 +83,7 @@ impl EraMarkersSigner { } } -/// An era merkers Verifier +/// An era markers verifier that checks the authenticity of era markers stored on the chain #[derive(Debug, Serialize, Deserialize, Clone)] pub struct EraMarkersVerifier { pub(crate) verification_key: EraMarkersVerifierVerificationKey, diff --git a/mithril-common/src/era/adapters/cardano_chain.rs b/mithril-common/src/era/adapters/cardano_chain.rs index d348916ec6e..58796bc9ff2 100644 --- a/mithril-common/src/era/adapters/cardano_chain.rs +++ b/mithril-common/src/era/adapters/cardano_chain.rs @@ -28,6 +28,10 @@ pub enum EraMarkersPayloadError { #[error("could not deserialize signature: {0}")] DeserializeSignature(GeneralError), + /// Error raised when the signature is missing + #[error("could not verify signature: signature is missing")] + MissingSignature, + /// Error raised when the signature is invalid #[error("could not verify signature: {0}")] VerifySignature(GeneralError), @@ -37,45 +41,57 @@ pub enum EraMarkersPayloadError { CreateSignature(GeneralError), } +/// Era markers payload #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -struct EraMarkersPayload { +pub struct EraMarkersPayload { markers: Vec, - signature: HexEncodeEraMarkerSignature, + signature: Option, } impl EraMarkersPayload { - fn message_to_sign(&self) -> Result, EraMarkersPayloadError> { + fn message_to_bytes(&self) -> Result, EraMarkersPayloadError> { serde_json::to_vec(&self.markers) .map_err(|e| EraMarkersPayloadError::SerializeMessage(e.into())) } - fn verify_signature( + fn deserialize_signature(&self) -> Result { + EraMarkersVerifierSignature::from_bytes( + &Vec::from_hex( + self.signature + .as_ref() + .ok_or(EraMarkersPayloadError::MissingSignature)?, + ) + .map_err(|e| EraMarkersPayloadError::DeserializeSignature(e.into()))?, + ) + .map_err(|e| EraMarkersPayloadError::DeserializeSignature(e.into())) + } + + /// Verify the signature an era markers payload + pub fn verify_signature( &self, verification_key: EraMarkersVerifierVerificationKey, ) -> Result<(), EraMarkersPayloadError> { - let signature = EraMarkersVerifierSignature::from_bytes( - &Vec::from_hex(&self.signature) - .map_err(|e| EraMarkersPayloadError::DeserializeSignature(e.into()))?, - ) - .map_err(|e| EraMarkersPayloadError::DeserializeSignature(e.into()))?; let markers_verifier = EraMarkersVerifier::from_verification_key(verification_key); + markers_verifier - .verify(&self.message_to_sign()?, &signature) + .verify(&self.message_to_bytes()?, &self.deserialize_signature()?) .map_err(|e| EraMarkersPayloadError::VerifySignature(e.into())) } - #[cfg(any(test, feature = "test_only"))] - fn sign(self, signer: &EraMarkersSigner) -> Result { + /// Sign an era markers payload + #[allow(dead_code)] + pub fn sign(self, signer: &EraMarkersSigner) -> Result { let signature = signer .sign( &self - .message_to_sign() + .message_to_bytes() .map_err(|e| EraMarkersPayloadError::CreateSignature(e.into()))?, ) .encode_hex::(); + Ok(Self { markers: self.markers, - signature, + signature: Some(signature), }) } } @@ -111,7 +127,7 @@ impl EraReaderAdapter for CardanoChainAdapter { .await?; let markers_list = tx_datums .into_iter() - .filter_map(|datum| datum.get_field_raw_value("bytes", 0).ok()) + .filter_map(|datum| datum.get_nth_field_by_type("bytes", 0).ok()) .filter_map(|field_value| field_value.as_str().map(|s| s.to_string())) .filter_map(|field_value_str| key_decode_hex(&field_value_str).ok()) .filter_map(|era_markers_payload: EraMarkersPayload| { @@ -121,6 +137,7 @@ impl EraReaderAdapter for CardanoChainAdapter { .map(|_| era_markers_payload.markers) }) .collect::>>(); + Ok(markers_list.first().unwrap_or(&Vec::new()).to_owned()) } } @@ -154,14 +171,14 @@ mod test { EraMarker::new("thales", Some(Epoch(1))), EraMarker::new("pythagoras", None), ], - signature: "".to_string(), + signature: None, }; let era_marker_payload_2 = EraMarkersPayload { markers: vec![ EraMarker::new("thales", Some(Epoch(1))), EraMarker::new("pythagoras", Some(Epoch(2))), ], - signature: "".to_string(), + signature: None, }; let mut fake_datums = dummy_tx_datums_from_markers_payload(vec![ era_marker_payload_1, diff --git a/mithril-test-lab/mithril-devnet/devnet-mkfiles.sh b/mithril-test-lab/mithril-devnet/devnet-mkfiles.sh index 24df0d71338..facbaca421d 100755 --- a/mithril-test-lab/mithril-devnet/devnet-mkfiles.sh +++ b/mithril-test-lab/mithril-devnet/devnet-mkfiles.sh @@ -1004,7 +1004,7 @@ done NODE_IX=0 for NODE in ${POOL_NODES}; do NODE_ID=$(( $NODE_IX + 1)) -if [ `expr $NODE_IX % 2` == 0 || -z "${WITH_UNCERTIFIED_SIGNERS}" ]; then +if [ `expr $NODE_IX % 2` == 0 ] || [ -z "${WITH_UNCERTIFIED_SIGNERS}" ]; then # 50% of signers with key certification cat >> ${NODE}/info.json < Date: Wed, 8 Feb 2023 11:22:06 +0100 Subject: [PATCH 7/7] Bump 'mithril-common' crate version --- Cargo.lock | 2 +- mithril-common/Cargo.toml | 2 +- mithril-common/src/chain_observer/interface.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49cc06580d9..2680114c761 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2067,7 +2067,7 @@ dependencies = [ [[package]] name = "mithril-common" -version = "0.2.11" +version = "0.2.12" dependencies = [ "async-trait", "bech32", diff --git a/mithril-common/Cargo.toml b/mithril-common/Cargo.toml index 6709df96c21..12235e7b8cd 100644 --- a/mithril-common/Cargo.toml +++ b/mithril-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mithril-common" -version = "0.2.11" +version = "0.2.12" authors = { workspace = true } edition = { workspace = true } documentation = { workspace = true } diff --git a/mithril-common/src/chain_observer/interface.rs b/mithril-common/src/chain_observer/interface.rs index a9fec3e251f..cf580d083a8 100644 --- a/mithril-common/src/chain_observer/interface.rs +++ b/mithril-common/src/chain_observer/interface.rs @@ -25,7 +25,7 @@ pub enum ChainObserverError { #[automock] #[async_trait] pub trait ChainObserver: Sync + Send { - /// Retrive the datums associated to and address + /// Retrieve the datums associated to and address async fn get_current_datums( &self, address: &ChainAddress,