Skip to content

Commit

Permalink
Merge pull request input-output-hk#1455 from input-output-hk/ensemble…
Browse files Browse the repository at this point in the history
…/1436-sign-cardano-transactions

Compute message to sign for Cardano transactions
  • Loading branch information
jpraynaud authored Jan 25, 2024
2 parents c762166 + 50c9bc7 commit ba96064
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 20 deletions.
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

0 comments on commit ba96064

Please sign in to comment.