Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compute message to sign for Cardano transactions #1455

Merged
merged 4 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion mithril-aggregator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-aggregator"
version = "0.4.28"
version = "0.4.29"
description = "A Mithril Aggregator server"
authors = { workspace = true }
edition = { workspace = true }
Expand Down
17 changes: 12 additions & 5 deletions mithril-aggregator/src/artifact_builder/cardano_transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,23 @@ impl ArtifactBuilder<Beacon, CardanoTransactionsCommitment> for CardanoTransacti
.with_context(|| {
format!(
"Can not compute CardanoTransactionsCommitment artifact for signed_entity: {:?}",
SignedEntityType::CardanoTransactions(beacon)
SignedEntityType::CardanoTransactions(beacon.clone())
)
})?;

Ok(CardanoTransactionsCommitment::new(merkle_root.to_string()))
Ok(CardanoTransactionsCommitment::new(
merkle_root.to_string(),
beacon,
))
}
}

#[cfg(test)]
mod tests {
use mithril_common::{entities::ProtocolMessage, test_utils::fake_data};
use mithril_common::{
entities::ProtocolMessage,
test_utils::fake_data::{self},
};

use super::*;

Expand All @@ -65,10 +71,11 @@ mod tests {

let cardano_transaction_artifact_builder = CardanoTransactionsArtifactBuilder::new();
let artifact = cardano_transaction_artifact_builder
.compute_artifact(Beacon::default(), &certificate)
.compute_artifact(certificate.beacon.clone(), &certificate)
.await
.unwrap();
let artifact_expected = CardanoTransactionsCommitment::new("merkleroot".to_string());
let artifact_expected =
CardanoTransactionsCommitment::new("merkleroot".to_string(), certificate.beacon);
assert_eq!(artifact_expected, artifact);
}

Expand Down
3 changes: 2 additions & 1 deletion mithril-aggregator/src/services/signed_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,8 @@ mod tests {

#[tokio::test]
async fn build_artifact_for_cardano_transactions_store_nothing_in_db() {
let expected = CardanoTransactionsCommitment::new("merkle_root".to_string());
let expected =
CardanoTransactionsCommitment::new("merkle_root".to_string(), Beacon::default());
let mut mock_signed_entity_storer = MockSignedEntityStorer::new();
mock_signed_entity_storer
.expect_store_signed_entity()
Expand Down
3 changes: 2 additions & 1 deletion mithril-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-common"
version = "0.2.153"
version = "0.2.154"
description = "Common types, interfaces, and utilities for Mithril nodes."
authors = { workspace = true }
edition = { workspace = true }
Expand All @@ -22,6 +22,7 @@ async-trait = "0.1.73"
bech32 = "0.9.1"
blake2 = "0.10.6"
chrono = { version = "0.4.31", features = ["serde"] }
ckb-merkle-mountain-range = "0.6.0"
digest = "0.10.7"
ed25519-dalek = { version = "2.0.0", features = ["rand_core", "serde"] }
fixed = "1.24.0"
Expand Down
236 changes: 236 additions & 0 deletions mithril-common/src/crypto_helper/merkle_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
use anyhow::anyhow;
use blake2::{Blake2s256, Digest};
use ckb_merkle_mountain_range::{util::MemStore, Merge, MerkleProof, Result as MMRResult, MMR};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, ops::Deref};

use crate::{StdError, StdResult};

/// Alias for a byte
type Bytes = Vec<u8>;

/// Alias for a Merkle tree leaf position
type MKTreeLeafPosition = u64;

/// A node of a Merkle tree
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub struct MKTreeNode {
hash: Bytes,
}

impl MKTreeNode {
/// MKTreeNode factory
pub fn new(hash: Bytes) -> Self {
Self { hash }
}

/// Create a MKTreeNode from a hex representation
pub fn from_hex(hex: &str) -> StdResult<Self> {
let hash = hex::decode(hex)?;
Ok(Self { hash })
}

/// Create a hex representation of the MKTreeNode
pub fn to_hex(&self) -> String {
hex::encode(&self.hash)
}
}

impl Deref for MKTreeNode {
type Target = Bytes;

fn deref(&self) -> &Self::Target {
&self.hash
}
}

impl From<String> for MKTreeNode {
fn from(other: String) -> Self {
Self {
hash: other.as_str().into(),
}
}
}

impl From<&str> for MKTreeNode {
fn from(other: &str) -> Self {
Self {
hash: other.as_bytes().to_vec(),
}
}
}

impl TryFrom<MKTree<'_>> for MKTreeNode {
type Error = StdError;
fn try_from(other: MKTree) -> Result<Self, Self::Error> {
other.compute_root()
}
}

impl ToString for MKTreeNode {
fn to_string(&self) -> String {
String::from_utf8_lossy(&self.hash).to_string()
}
}

struct MergeMKTreeNode {}

impl Merge for MergeMKTreeNode {
type Item = MKTreeNode;

fn merge(lhs: &Self::Item, rhs: &Self::Item) -> MMRResult<Self::Item> {
let mut hasher = Blake2s256::new();
hasher.update(lhs.deref());
hasher.update(rhs.deref());
let hash_merge = hasher.finalize();

Ok(Self::Item::new(hash_merge.to_vec()))
}
}

/// A Merkle proof
#[derive(Serialize, Deserialize)]
pub struct MKProof {
inner_root: MKTreeNode,
inner_leaves: Vec<(MKTreeLeafPosition, MKTreeNode)>,
inner_proof_size: u64,
inner_proof_items: Vec<MKTreeNode>,
}

impl MKProof {
/// Verification of a Merkle proof
pub fn verify(&self) -> StdResult<()> {
MerkleProof::<MKTreeNode, MergeMKTreeNode>::new(
self.inner_proof_size,
self.inner_proof_items.clone(),
)
.verify(self.inner_root.to_owned(), self.inner_leaves.to_owned())?
.then_some(())
.ok_or(anyhow!("Invalid MKProof"))
}
}

/// A Merkle tree store
pub type MKTreeStore = MemStore<MKTreeNode>;

/// A Merkle tree
pub struct MKTree<'a> {
inner_leaves: HashMap<&'a MKTreeNode, MKTreeLeafPosition>,
inner_tree: MMR<MKTreeNode, MergeMKTreeNode, &'a MKTreeStore>,
}

impl<'a> MKTree<'a> {
/// MKTree factory
pub fn new(leaves: &'a [MKTreeNode], store: &'a MKTreeStore) -> StdResult<Self> {
let mut inner_tree = MMR::<MKTreeNode, MergeMKTreeNode, &MKTreeStore>::new(0, store);
let mut inner_leaves = HashMap::new();
for leaf in leaves {
let inner_tree_position = inner_tree.push(leaf.to_owned())?;
inner_leaves.insert(leaf, inner_tree_position);
}
inner_tree.commit()?;

Ok(Self {
inner_leaves,
inner_tree,
})
}

/// Number of leaves in the Merkle tree
pub fn total_leaves(&self) -> usize {
self.inner_leaves.len()
}

/// Generate root of the Merkle tree
pub fn compute_root(&self) -> StdResult<MKTreeNode> {
Ok(self.inner_tree.get_root()?)
}

/// Generate Merkle proof of memberships in the tree
pub fn compute_proof(&self, leaves: &[MKTreeNode]) -> StdResult<MKProof> {
let inner_leaves = leaves
.iter()
.map(|leaf| {
if let Some(leaf_position) = self.inner_leaves.get(leaf) {
Ok((*leaf_position, leaf.to_owned()))
} else {
Err(anyhow!("Leaf not found in the Merkle tree"))
}
})
.collect::<StdResult<Vec<_>>>()?;
let proof = self.inner_tree.gen_proof(
inner_leaves
.iter()
.map(|(leaf_position, _leaf)| *leaf_position)
.collect(),
)?;
return Ok(MKProof {
inner_root: self.compute_root()?,
inner_leaves,
inner_proof_size: proof.mmr_size(),
inner_proof_items: proof.proof_items().to_vec(),
});
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_golden_merkle_root() {
let leaves = vec!["golden-1", "golden-2", "golden-3", "golden-4", "golden-5"];
let leaves: Vec<MKTreeNode> = leaves.into_iter().map(|l| l.into()).collect();
let store = MKTreeStore::default();
let mktree = MKTree::new(&leaves, &store).expect("MKTree creation should not fail");
let mkroot = mktree
.compute_root()
.expect("MKRoot generation should not fail");
assert_eq!(
"3bbced153528697ecde7345a22e50115306478353619411523e804f2323fd921",
mkroot.to_hex()
);
}

#[test]
fn test_should_accept_valid_proof_generated_by_merkle_tree() {
let total_leaves = 100000;
let leaves = (0..total_leaves)
.map(|i| format!("test-{i}").into())
.collect::<Vec<_>>();
let store = MKTreeStore::default();
let mktree = MKTree::new(&leaves, &store).expect("MKTree creation should not fail");
let leaves_to_verify = &[leaves[0].to_owned(), leaves[3].to_owned()];
let proof = mktree
.compute_proof(leaves_to_verify)
.expect("MKProof generation should not fail");
proof.verify().expect("The MKProof should be valid");
}

#[test]
fn test_should_reject_invalid_proof_generated_by_merkle_tree() {
let total_leaves = 100000;
let leaves = (0..total_leaves)
.map(|i| format!("test-{i}").into())
.collect::<Vec<_>>();
let store = MKTreeStore::default();
let mktree = MKTree::new(&leaves, &store).expect("MKTree creation should not fail");
let leaves_to_verify = &[leaves[0].to_owned(), leaves[3].to_owned()];
let mut proof = mktree
.compute_proof(leaves_to_verify)
.expect("MKProof generation should not fail");
proof.inner_root = leaves[10].to_owned();
proof.verify().expect_err("The MKProof should be invalid");
}

#[test]
fn tree_node_from_to_string() {
let expected_str = "my_string";
let expected_string = expected_str.to_string();
let node_str: MKTreeNode = expected_str.into();
let node_string: MKTreeNode = expected_string.clone().into();

assert_eq!(node_str.to_string(), expected_str);
assert_eq!(node_string.to_string(), expected_string);
}
}
2 changes: 2 additions & 0 deletions mithril-common/src/crypto_helper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod codec;
mod conversions;
mod era;
mod genesis;
mod merkle_tree;
#[cfg(feature = "test_tools")]
pub mod tests_setup;
mod types;
Expand All @@ -22,6 +23,7 @@ pub use era::{
EraMarkersVerifierSignature, EraMarkersVerifierVerificationKey,
};
pub use genesis::{ProtocolGenesisError, ProtocolGenesisSigner, ProtocolGenesisVerifier};
pub use merkle_tree::{MKProof, MKTree, MKTreeNode, MKTreeStore};
pub use types::*;

/// The current protocol version
Expand Down
Loading