Преглед на файлове

validator/xmr: Add aux chain checks

parazyd преди 1 година
родител
ревизия
dc4e461379
променени са 3 файла, в които са добавени 207 реда и са изтрити 2 реда
  1. 101 1
      Cargo.lock
  2. 4 0
      Cargo.toml
  3. 102 1
      src/validator/xmr/helpers.rs

+ 101 - 1
Cargo.lock

@@ -907,6 +907,12 @@ version = "1.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06"
 
+[[package]]
+name = "byte-slice-cast"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
+
 [[package]]
 name = "bytecheck"
 version = "0.6.12"
@@ -1257,6 +1263,26 @@ version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
 
+[[package]]
+name = "const_format"
+version = "0.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd"
+dependencies = [
+ "const_format_proc_macros",
+]
+
+[[package]]
+name = "const_format_proc_macros"
+version = "0.2.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
 [[package]]
 name = "constant_time_eq"
 version = "0.3.1"
@@ -1798,6 +1824,7 @@ dependencies = [
  "pin-project-lite",
  "plotters",
  "prettytable-rs",
+ "primitive-types",
  "rand 0.8.5",
  "randomx",
  "rcgen",
@@ -1805,6 +1832,8 @@ dependencies = [
  "rustls-pemfile",
  "semver",
  "serde",
+ "sha2",
+ "simplelog",
  "sled-overlay",
  "smol",
  "socket2",
@@ -3420,7 +3449,7 @@ dependencies = [
  "rand 0.8.5",
  "sinsemilla",
  "subtle",
- "uint",
+ "uint 0.9.5",
 ]
 
 [[package]]
@@ -3772,6 +3801,26 @@ dependencies = [
  "png",
 ]
 
+[[package]]
+name = "impl-codec"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d40b9d5e17727407e55028eafc22b2dc68781786e6d7eb8a21103f5058e3a14"
+dependencies = [
+ "parity-scale-codec",
+]
+
+[[package]]
+name = "impl-trait-for-tuples"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.101",
+]
+
 [[package]]
 name = "incrementalmerkletree"
 version = "0.8.2"
@@ -4780,6 +4829,34 @@ dependencies = [
  "sha2",
 ]
 
+[[package]]
+name = "parity-scale-codec"
+version = "3.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa"
+dependencies = [
+ "arrayvec",
+ "bitvec",
+ "byte-slice-cast",
+ "const_format",
+ "impl-trait-for-tuples",
+ "parity-scale-codec-derive",
+ "rustversion",
+ "serde",
+]
+
+[[package]]
+name = "parity-scale-codec-derive"
+version = "3.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.101",
+]
+
 [[package]]
 name = "parking"
 version = "2.2.1"
@@ -5173,6 +5250,17 @@ dependencies = [
  "elliptic-curve",
 ]
 
+[[package]]
+name = "primitive-types"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5"
+dependencies = [
+ "fixed-hash",
+ "impl-codec",
+ "uint 0.10.0",
+]
+
 [[package]]
 name = "priority-queue"
 version = "2.5.0"
@@ -8154,6 +8242,18 @@ dependencies = [
  "static_assertions",
 ]
 
+[[package]]
+name = "uint"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e"
+dependencies = [
+ "byteorder",
+ "crunchy",
+ "hex",
+ "static_assertions",
+]
+
 [[package]]
 name = "unarray"
 version = "0.1.4"

+ 4 - 0
Cargo.toml

@@ -88,6 +88,7 @@ hex = {version = "0.4.3", optional = true}
 serde = {version = "1.0.219", features = ["derive"], optional = true}
 tinyjson = {version = "2.5.1", optional = true}
 httparse = {version = "1.10.1", optional = true}
+primitive-types = {version = "0.13.1", optional = true}
 semver = {version = "1.0.26", optional = true}
 structopt = {version= "0.3.26", optional = true}
 structopt-toml = {version= "0.5.1", optional = true}
@@ -118,6 +119,7 @@ blake3 = {version = "1.8.2", features = ["rayon"], optional = true}
 crypto_api_chachapoly = {version = "0.5.0", optional = true}
 halo2_proofs = {version = "0.3.1", features = ["circuit-params"], optional = true}
 halo2_gadgets = {version = "0.3.1", features = ["circuit-params"], optional = true}
+sha2 = {version = "0.10.9", optional = true}
 
 # Smart contract runtime
 darkfi-sdk = {path = "src/sdk", optional = true}
@@ -172,8 +174,10 @@ validator = [
     "hex",
     "lazy_static",
     "monero",
+    "primitive-types",
     "randomx",
     "smol",
+    "sha2",
 
     "blockchain",
     "system",

+ 102 - 1
src/validator/xmr/helpers.rs

@@ -19,9 +19,18 @@
 
 use std::{io, iter};
 
-use monero::{consensus::Encodable as XmrEncodable, cryptonote::hash::Hashable, VarInt};
+use log::warn;
+use monero::{
+    blockdata::transaction::{ExtraField, RawExtraField, SubField},
+    consensus::Encodable as XmrEncodable,
+    cryptonote::hash::Hashable,
+    VarInt,
+};
+use primitive_types::U256;
+use sha2::{Digest, Sha256};
 use tiny_keccak::{Hasher, Keccak};
 
+use super::merkle_tree_parameters::MerkleTreeParameters;
 use crate::{
     blockchain::{
         header_store::HeaderHash,
@@ -31,6 +40,7 @@ use crate::{
             MoneroPowData,
         },
     },
+    Error,
     Error::MoneroMergeMineError,
     Result,
 };
@@ -136,3 +146,94 @@ pub fn construct_monero_data(
         aux_chain_merkle_proof,
     })
 }
+
+fn check_aux_chains(
+    monero_data: &MoneroPowData,
+    merge_mining_params: VarInt,
+    aux_chain_merkle_root: &monero::Hash,
+    darkfi_hash: HeaderHash,
+    darkfi_genesis_hash: HeaderHash,
+) -> bool {
+    let df_hash = monero::Hash::from_slice(darkfi_hash.as_slice());
+
+    if merge_mining_params == VarInt(0) {
+        // Interpret 0 as only 1 chain
+        if df_hash == *aux_chain_merkle_root {
+            return true
+        }
+    }
+
+    let merkle_tree_params = MerkleTreeParameters::from_varint(merge_mining_params);
+    if merkle_tree_params.number_of_chains() == 0 {
+        return false
+    }
+
+    let hash_position = U256::from_little_endian(
+        &Sha256::new()
+            .chain_update(darkfi_genesis_hash.as_slice())
+            .chain_update(merkle_tree_params.aux_nonce().to_le_bytes())
+            .chain_update((109_u8).to_le_bytes())
+            .finalize(),
+    )
+    .low_u32() %
+        u32::from(merkle_tree_params.number_of_chains());
+
+    let (merkle_root, pos) = monero_data
+        .aux_chain_merkle_proof
+        .calculate_root_with_pos(&df_hash, merkle_tree_params.number_of_chains());
+
+    if hash_position != pos {
+        return false
+    }
+
+    merkle_root == *aux_chain_merkle_root
+}
+
+// Parsing an extra field from bytes will always return an extra field with sub-fields
+// that could be read, even if it does not represent the original extra field. As per
+// Monero consensus rules, an error here will not represent a failure to deserialize a
+// block, so no need to error here.
+fn parse_extra_field_truncate_on_error(raw_extra_field: &RawExtraField) -> ExtraField {
+    match ExtraField::try_parse(raw_extra_field) {
+        Ok(val) => val,
+        Err(val) => {
+            warn!(
+                target: "validator::xmr::helpers",
+                "[MERGEMINING] Some sub-fields could not be parsed from the Monero coinbase",
+            );
+            val
+        }
+    }
+}
+
+/// Extracts the Monero block hash from the coinbase transaction's extra field
+pub fn extract_aux_merkle_root_from_block(monero: &monero::Block) -> Result<Option<monero::Hash>> {
+    // When we extract the merge mining hash, we do not care if
+    // the extra field can be parsed without error.
+    let extra_field = parse_extra_field_truncate_on_error(&monero.miner_tx.prefix.extra);
+
+    // Only one merge mining tag is allowed
+    let merge_mining_hashes: Vec<monero::Hash> = extra_field
+        .0
+        .iter()
+        .filter_map(|item| {
+            if let SubField::MergeMining(_depth, merge_mining_hash) = item {
+                Some(*merge_mining_hash)
+            } else {
+                None
+            }
+        })
+        .collect();
+
+    if merge_mining_hashes.len() > 1 {
+        return Err(Error::MoneroMergeMineError(
+            "More than one merge mining tag found in coinbase".to_string(),
+        ))
+    }
+
+    if let Some(merge_mining_hash) = merge_mining_hashes.into_iter().next() {
+        Ok(Some(merge_mining_hash))
+    } else {
+        Ok(None)
+    }
+}