Просмотр исходного кода

validator/xmr: Add creation of MoneroPowData from monero::Block

parazyd 1 год назад
Родитель
Сommit
51a9a7de77

+ 1 - 0
Cargo.lock

@@ -1788,6 +1788,7 @@ dependencies = [
  "futures-rustls",
  "futures-rustls",
  "halo2_gadgets",
  "halo2_gadgets",
  "halo2_proofs",
  "halo2_proofs",
+ "hex",
  "httparse",
  "httparse",
  "lazy_static",
  "lazy_static",
  "libc",
  "libc",

+ 3 - 0
Cargo.toml

@@ -84,6 +84,7 @@ x509-parser = {version = "0.17.0", features = ["validate", "verify"], optional =
 
 
 # Encoding
 # Encoding
 bs58 = {version = "0.5.1", optional = true}
 bs58 = {version = "0.5.1", optional = true}
+hex = {version = "0.4.3", optional = true}
 serde = {version = "1.0.219", features = ["derive"], optional = true}
 serde = {version = "1.0.219", features = ["derive"], optional = true}
 tinyjson = {version = "2.5.1", optional = true}
 tinyjson = {version = "2.5.1", optional = true}
 httparse = {version = "1.10.1", optional = true}
 httparse = {version = "1.10.1", optional = true}
@@ -168,7 +169,9 @@ blockchain = [
 
 
 validator = [
 validator = [
     "crypto_api_chachapoly",
     "crypto_api_chachapoly",
+    "hex",
     "lazy_static",
     "lazy_static",
+    "monero",
     "randomx",
     "randomx",
     "smol",
     "smol",
 
 

+ 4 - 0
src/blockchain/header_store.rs

@@ -62,6 +62,10 @@ impl HeaderHash {
     pub fn as_string(&self) -> String {
     pub fn as_string(&self) -> String {
         blake3::Hash::from_bytes(self.0).to_string()
         blake3::Hash::from_bytes(self.0).to_string()
     }
     }
+
+    pub fn as_slice(&self) -> &[u8] {
+        self.0.as_slice()
+    }
 }
 }
 
 
 impl FromStr for HeaderHash {
 impl FromStr for HeaderHash {

+ 158 - 0
src/blockchain/monero/fixed_array.rs

@@ -0,0 +1,158 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 Dyne.org foundation
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{
+    io::{self, Read, Write},
+    ops::Deref,
+};
+
+#[cfg(feature = "async-serial")]
+use darkfi_serial::{
+    async_trait, AsyncDecodable, AsyncEncodable, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt,
+};
+use darkfi_serial::{Decodable, Encodable, ReadExt, WriteExt};
+
+const MAX_ARR_SIZE: usize = 60;
+
+/// A fixed-size byte array for RandomX that can be serialized and deserialized.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct FixedByteArray {
+    elems: [u8; MAX_ARR_SIZE],
+    len: u8,
+}
+
+impl FixedByteArray {
+    /// Create a new FixedByteArray with the preset length.
+    /// The array will be zeroed.
+    pub fn new() -> Self {
+        Default::default()
+    }
+
+    /// Returns the array as a slice of bytes.
+    pub fn as_slice(&self) -> &[u8] {
+        &self[..self.len()]
+    }
+
+    /// Returns true if the array is full.
+    #[inline]
+    pub fn is_full(&self) -> bool {
+        self.len() == MAX_ARR_SIZE
+    }
+
+    /// Returns the length of the array.
+    #[inline]
+    pub fn len(&self) -> usize {
+        self.len as usize
+    }
+
+    /// Returns true if the array is empty.
+    #[inline]
+    pub fn is_empty(&self) -> bool {
+        self.len == 0
+    }
+
+    pub fn to_vec(&self) -> Vec<u8> {
+        self.as_slice().to_vec()
+    }
+}
+
+impl Deref for FixedByteArray {
+    type Target = [u8];
+
+    fn deref(&self) -> &Self::Target {
+        &self.elems[..self.len as usize]
+    }
+}
+
+impl Default for FixedByteArray {
+    fn default() -> Self {
+        Self { elems: [0u8; MAX_ARR_SIZE], len: 0 }
+    }
+}
+
+impl Encodable for FixedByteArray {
+    fn encode<S: Write>(&self, s: &mut S) -> io::Result<usize> {
+        let mut n = 1;
+        s.write_u8(self.len)?;
+        let data = self.as_slice();
+        for item in data.iter().take(self.len as usize) {
+            s.write_u8(*item)?;
+            n += 1;
+        }
+
+        Ok(n)
+    }
+}
+
+#[cfg(feature = "async-serial")]
+#[async_trait]
+impl AsyncEncodable for FixedByteArray {
+    async fn encode_async<S: AsyncWrite + Unpin + Send>(&self, s: &mut S) -> io::Result<usize> {
+        let mut n = 1;
+        s.write_u8_async(self.len).await?;
+        let data = self.as_slice();
+        for item in data.iter().take(self.len as usize) {
+            s.write_u8_async(*item).await?;
+            n += 1;
+        }
+
+        Ok(n)
+    }
+}
+
+impl Decodable for FixedByteArray {
+    fn decode<D: Read>(d: &mut D) -> io::Result<Self> {
+        let len = d.read_u8()? as usize;
+        if len > MAX_ARR_SIZE {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidInput,
+                format!("length exceeded max of 60 bytes for FixedByteArray: {}", len),
+            ));
+        }
+
+        let mut elems = [0u8; MAX_ARR_SIZE];
+        #[allow(clippy::needless_range_loop)]
+        for i in 0..len {
+            elems[i] = d.read_u8()?;
+        }
+
+        Ok(Self { elems, len: len as u8 })
+    }
+}
+
+#[cfg(feature = "async-serial")]
+#[async_trait]
+impl AsyncDecodable for FixedByteArray {
+    async fn decode_async<D: AsyncRead + Unpin + Send>(d: &mut D) -> io::Result<Self> {
+        let len = d.read_u8_async().await? as usize;
+        if len > MAX_ARR_SIZE {
+            return Err(io::Error::new(
+                io::ErrorKind::InvalidInput,
+                format!("length exceeded max of 60 bytes for FixedByteArray: {}", len),
+            ));
+        }
+
+        let mut elems = [0u8; MAX_ARR_SIZE];
+        #[allow(clippy::needless_range_loop)]
+        for i in 0..len {
+            elems[i] = d.read_u8_async().await?;
+        }
+
+        Ok(Self { elems, len: len as u8 })
+    }
+}

+ 10 - 40
src/blockchain/monero/mod.rs

@@ -27,19 +27,20 @@ use darkfi_serial::{Decodable, Encodable};
 use monero::{
 use monero::{
     blockdata::transaction::RawExtraField,
     blockdata::transaction::RawExtraField,
     consensus::{Decodable as XmrDecodable, Encodable as XmrEncodable},
     consensus::{Decodable as XmrDecodable, Encodable as XmrEncodable},
-    cryptonote::hash::Hashable,
-    util::ringct::{RctSigBase, RctType},
     BlockHeader, Hash,
     BlockHeader, Hash,
 };
 };
 use tiny_keccak::{Hasher, Keccak};
 use tiny_keccak::{Hasher, Keccak};
 
 
-mod merkle_proof;
+pub mod fixed_array;
+use fixed_array::FixedByteArray;
+
+pub mod merkle_proof;
 use merkle_proof::MerkleProof;
 use merkle_proof::MerkleProof;
 
 
-mod keccak;
+pub mod keccak;
 use keccak::{keccak_from_bytes, keccak_to_bytes};
 use keccak::{keccak_from_bytes, keccak_to_bytes};
 
 
-mod utils;
+pub mod utils;
 
 
 /// This struct represents all the Proof of Work information required
 /// This struct represents all the Proof of Work information required
 /// for merge mining.
 /// for merge mining.
@@ -49,7 +50,7 @@ pub struct MoneroPowData {
     pub header: BlockHeader,
     pub header: BlockHeader,
     /// RandomX VM key - length varies to a max len of 60.
     /// RandomX VM key - length varies to a max len of 60.
     /// TODO: Implement a type, or use randomx_key[0] to define len.
     /// TODO: Implement a type, or use randomx_key[0] to define len.
-    pub randomx_key: [u8; 64],
+    pub randomx_key: FixedByteArray,
     /// The number of transactions included in this Monero block.
     /// The number of transactions included in this Monero block.
     /// This is used to produce the blockhashing_blob.
     /// This is used to produce the blockhashing_blob.
     pub transaction_count: u16,
     pub transaction_count: u16,
@@ -139,11 +140,11 @@ impl Decodable for MoneroPowData {
         let header =
         let header =
             BlockHeader::consensus_decode(d).map_err(|_| Error::other("Invalid XMR header"))?;
             BlockHeader::consensus_decode(d).map_err(|_| Error::other("Invalid XMR header"))?;
 
 
-        let randomx_key: [u8; 64] = Decodable::decode(d)?;
+        let randomx_key: FixedByteArray = Decodable::decode(d)?;
         let transaction_count: u16 = Decodable::decode(d)?;
         let transaction_count: u16 = Decodable::decode(d)?;
 
 
         let merkle_root =
         let merkle_root =
-            Hash::consensus_decode(d).map_err(|_| Error::other("Invamid XMR hash"))?;
+            Hash::consensus_decode(d).map_err(|_| Error::other("Invalid XMR hash"))?;
 
 
         let coinbase_merkle_proof: MerkleProof = Decodable::decode(d)?;
         let coinbase_merkle_proof: MerkleProof = Decodable::decode(d)?;
 
 
@@ -176,7 +177,7 @@ impl AsyncDecodable for MoneroPowData {
         let header = BlockHeader::consensus_decode(&mut buf)
         let header = BlockHeader::consensus_decode(&mut buf)
             .map_err(|_| Error::other("Invalid XMR header"))?;
             .map_err(|_| Error::other("Invalid XMR header"))?;
 
 
-        let randomx_key: [u8; 64] = AsyncDecodable::decode_async(d).await?;
+        let randomx_key: FixedByteArray = AsyncDecodable::decode_async(d).await?;
         let transaction_count: u16 = AsyncDecodable::decode_async(d).await?;
         let transaction_count: u16 = AsyncDecodable::decode_async(d).await?;
 
 
         let buf: Vec<u8> = AsyncDecodable::decode_async(d).await?;
         let buf: Vec<u8> = AsyncDecodable::decode_async(d).await?;
@@ -205,34 +206,3 @@ impl AsyncDecodable for MoneroPowData {
         })
         })
     }
     }
 }
 }
-
-impl MoneroPowData {
-    /// Returns true if the coinbase Merkle proof produces the `merkle_root` hash.
-    pub fn is_coinbase_valid_merkle_root(&self) -> bool {
-        let mut finalised_prefix_keccak = self.coinbase_tx_hasher.clone();
-        let mut encoder_extra_field = vec![];
-        self.coinbase_tx_extra.consensus_encode(&mut encoder_extra_field).unwrap();
-        finalised_prefix_keccak.update(&encoder_extra_field);
-        let mut prefix_hash: [u8; 32] = [0; 32];
-        finalised_prefix_keccak.finalize(&mut prefix_hash);
-
-        let final_prefix_hash = Hash::from_slice(&prefix_hash);
-
-        // let mut finalised_keccak = Keccak::v256();
-        let rct_sig_base = RctSigBase {
-            rct_type: RctType::Null,
-            txn_fee: Default::default(),
-            pseudo_outs: vec![],
-            ecdh_info: vec![],
-            out_pk: vec![],
-        };
-
-        let hashes = vec![final_prefix_hash, rct_sig_base.hash(), Hash::null()];
-        let encoder_final: Vec<u8> =
-            hashes.into_iter().flat_map(|h| Vec::from(&h.to_bytes()[..])).collect();
-        let coinbase_hash = Hash::new(encoder_final);
-
-        let merkle_root = self.coinbase_merkle_proof.calculate_root(&coinbase_hash);
-        (self.merkle_root == merkle_root) && self.coinbase_merkle_proof.check_coinbase_path()
-    }
-}

+ 10 - 0
src/error.rs

@@ -94,6 +94,10 @@ pub enum Error {
     #[error(transparent)]
     #[error(transparent)]
     Bs58DecodeError(#[from] bs58::decode::Error),
     Bs58DecodeError(#[from] bs58::decode::Error),
 
 
+    #[cfg(feature = "hex")]
+    #[error(transparent)]
+    HexDecodeError(#[from] hex::FromHexError),
+
     #[error("Bad operation type byte")]
     #[error("Bad operation type byte")]
     BadOperationType,
     BadOperationType,
 
 
@@ -339,6 +343,12 @@ pub enum Error {
     #[error("Hashing of Monero data failed: {0}")]
     #[error("Hashing of Monero data failed: {0}")]
     MoneroHashingError(String),
     MoneroHashingError(String),
 
 
+    #[error("Cannot have zero merge-mining chains")]
+    MoneroNumberOfChainZero,
+
+    #[error("MergeMineError: {0}")]
+    MoneroMergeMineError(String),
+
     // ===============
     // ===============
     // Database errors
     // Database errors
     // ===============
     // ===============

+ 3 - 0
src/validator/mod.rs

@@ -43,6 +43,9 @@ use consensus::{Consensus, Fork, Proposal};
 pub mod pow;
 pub mod pow;
 use pow::PoWModule;
 use pow::PoWModule;
 
 
+/// Monero infrastructure
+pub mod xmr;
+
 /// Verification functions
 /// Verification functions
 pub mod verification;
 pub mod verification;
 use verification::{
 use verification::{

+ 138 - 0
src/validator/xmr/helpers.rs

@@ -0,0 +1,138 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 Dyne.org foundation
+ * Copyright (C) 2021 The Tari Project (BSD-3)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use std::{io, iter};
+
+use monero::{consensus::Encodable as XmrEncodable, cryptonote::hash::Hashable, VarInt};
+use tiny_keccak::{Hasher, Keccak};
+
+use crate::{
+    blockchain::{
+        header_store::HeaderHash,
+        monero::{
+            fixed_array::FixedByteArray,
+            utils::{create_merkle_proof, tree_hash},
+            MoneroPowData,
+        },
+    },
+    Error::MoneroMergeMineError,
+    Result,
+};
+
+/// Deserializes the given hex-encoded string into a Monero block
+pub fn deserialize_monero_block_from_hex<T>(data: T) -> io::Result<monero::Block>
+where
+    T: AsRef<[u8]>,
+{
+    let bytes = hex::decode(data).map_err(|_| io::Error::other("Invalid hex data"))?;
+    let obj = monero::consensus::deserialize::<monero::Block>(&bytes)
+        .map_err(|_| io::Error::other("Invalid XMR block"))?;
+    Ok(obj)
+}
+
+/// Serializes the given Monero block into a hex-encoded string
+pub fn serialize_monero_block_to_hex(obj: &monero::Block) -> io::Result<String> {
+    let data = monero::consensus::serialize::<monero::Block>(obj);
+    let bytes = hex::encode(data);
+    Ok(bytes)
+}
+
+/// Create a set of ordered tx hashes from a Monero block
+pub fn create_ordered_tx_hashes_from_block(block: &monero::Block) -> Vec<monero::Hash> {
+    iter::once(block.miner_tx.hash()).chain(block.tx_hashes.clone()).collect()
+}
+
+/// Creates a hex-encoded Monero blockhashing_blob
+pub fn create_blockhashing_blob(
+    header: &monero::BlockHeader,
+    merkle_root: &monero::Hash,
+    transaction_count: u64,
+) -> Vec<u8> {
+    let mut blockhashing_blob = monero::consensus::serialize(header);
+    blockhashing_blob.extend_from_slice(merkle_root.as_bytes());
+    let mut count = monero::consensus::serialize(&VarInt(transaction_count));
+    blockhashing_blob.append(&mut count);
+    blockhashing_blob
+}
+
+/// Constructs [`MoneroPowData`] from the given block and seed
+pub fn construct_monero_data(
+    block: monero::Block,
+    seed: FixedByteArray,
+    ordered_aux_chain_hashes: Vec<monero::Hash>,
+    darkfi_hash: HeaderHash,
+) -> Result<MoneroPowData> {
+    let hashes = create_ordered_tx_hashes_from_block(&block);
+    let root = tree_hash(&hashes)?;
+
+    let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).ok_or_else(|| {
+        MoneroMergeMineError(
+            "create_merkle_proof returned None because the block had no coinbase".to_string(),
+        )
+    })?;
+
+    let coinbase = block.miner_tx.clone();
+
+    let mut keccak = Keccak::v256();
+    let mut encoder_prefix = vec![];
+
+    coinbase
+        .prefix
+        .version
+        .consensus_encode(&mut encoder_prefix)
+        .map_err(|e| MoneroMergeMineError(e.to_string()))?;
+
+    coinbase
+        .prefix
+        .unlock_time
+        .consensus_encode(&mut encoder_prefix)
+        .map_err(|e| MoneroMergeMineError(e.to_string()))?;
+
+    coinbase
+        .prefix
+        .inputs
+        .consensus_encode(&mut encoder_prefix)
+        .map_err(|e| MoneroMergeMineError(e.to_string()))?;
+
+    coinbase
+        .prefix
+        .outputs
+        .consensus_encode(&mut encoder_prefix)
+        .map_err(|e| MoneroMergeMineError(e.to_string()))?;
+
+    keccak.update(&encoder_prefix);
+
+    let t_hash = monero::Hash::from_slice(darkfi_hash.as_slice());
+    let aux_chain_merkle_proof = create_merkle_proof(&ordered_aux_chain_hashes, &t_hash).ok_or_else(|| {
+        MoneroMergeMineError(
+            "create_merkle_proof returned None, could not find darkfi hash in ordered aux chain hashes".to_string(),
+        )
+    })?;
+
+    Ok(MoneroPowData {
+        header: block.header,
+        randomx_key: seed,
+        transaction_count: hashes.len() as u16,
+        merkle_root: root,
+        coinbase_merkle_proof,
+        coinbase_tx_extra: block.miner_tx.prefix.extra,
+        coinbase_tx_hasher: keccak,
+        aux_chain_merkle_proof,
+    })
+}

+ 386 - 0
src/validator/xmr/merkle_tree_parameters.rs

@@ -0,0 +1,386 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 Dyne.org foundation
+ * Copyright (C) 2021 The Tari Project (BSD-3)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use monero::VarInt;
+
+use crate::{Error, Result};
+
+/// Based on <https://github.com/SChernykh/p2pool/blob/master/docs/MERGE_MINING.MD#merge-mining-tx_extra-tag-format>
+#[derive(Debug, Clone, PartialEq)]
+pub struct MerkleTreeParameters {
+    number_of_chains: u8,
+    aux_nonce: u32,
+}
+
+impl MerkleTreeParameters {
+    pub fn new(number_of_chains: u8, aux_nonce: u32) -> Result<Self> {
+        if number_of_chains == 0u8 {
+            return Err(Error::MoneroNumberOfChainZero)
+        }
+
+        Ok(Self { number_of_chains, aux_nonce })
+    }
+
+    pub fn from_varint(merkle_tree_varint: VarInt) -> Self {
+        let bits = get_decode_bits(merkle_tree_varint.0);
+
+        let number_of_chains = get_aux_chain_count(merkle_tree_varint.0, bits);
+        let aux_nonce = get_aux_nonce(merkle_tree_varint.0, bits);
+
+        Self { number_of_chains, aux_nonce }
+    }
+
+    pub fn to_varint(&self) -> VarInt {
+        // 1 is encoded as 0
+        let num = self.number_of_chains.saturating_sub(1);
+        let size = u8::try_from(num.leading_zeros())
+            .expect("This can't fail, u8 can only have 8 leading 0s which will fit in 255");
+        // size must be >0, so saturating sub should be safe.
+        let mut size_bits = encode_bits(7u8.saturating_sub(size));
+        let mut n_bits = encode_aux_chain_count(self.number_of_chains);
+        let mut nonce_bits = encode_aux_nonce(self.aux_nonce);
+        // This won't underflow as max size will be size_bits(3) + n_bits(8) + nonce_bits(32) = 43
+        let mut zero_bits = vec![0; 64 - size_bits.len() - n_bits.len() - nonce_bits.len()];
+        zero_bits.append(&mut nonce_bits);
+        zero_bits.append(&mut n_bits);
+        zero_bits.append(&mut size_bits);
+
+        let num: u64 = zero_bits.iter().fold(0, |result, &bit| (result << 1) ^ u64::from(bit));
+        VarInt(num)
+    }
+
+    pub fn number_of_chains(&self) -> u8 {
+        self.number_of_chains
+    }
+
+    pub fn aux_nonce(&self) -> u32 {
+        self.aux_nonce
+    }
+}
+
+fn get_decode_bits(num: u64) -> u8 {
+    let bits_num: Vec<u8> = (0..=2).rev().map(|n| ((num >> n) & 1) as u8).collect();
+    bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit)
+}
+
+fn encode_bits(num: u8) -> Vec<u8> {
+    (0..=2).rev().map(|n| (num >> n) & 1).collect()
+}
+
+fn get_aux_chain_count(num: u64, bits: u8) -> u8 {
+    let end = 3 + bits;
+    let bits_num: Vec<u8> = (3..=end).rev().map(|n| ((num >> n) & 1) as u8).collect();
+    (bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit)).saturating_add(1)
+}
+
+fn encode_aux_chain_count(num: u8) -> Vec<u8> {
+    // 1 is encoded as 0
+    let num = num.saturating_sub(1);
+    if num == 0 {
+        return vec![0]
+    }
+
+    let size = u8::try_from(num.leading_zeros())
+        .expect("This can't fail, u8 can only have 8 leading 0s which will fit in 255");
+    let bit_length = 8 - size;
+    (0..bit_length).rev().map(|n| (num >> n) & 1).collect()
+}
+
+fn get_aux_nonce(num: u64, bits: u8) -> u32 {
+    // 0,1,2 is storing bits, then amount of bits, then start at next bit to read
+    let start = 3 + bits + 1;
+    let end = start + 32;
+    let bits_num: Vec<u32> = (start..=end).rev().map(|n| ((num >> n) & 1) as u32).collect();
+    bits_num.iter().fold(0, |result, &bit| (result << 1) ^ bit)
+}
+
+fn encode_aux_nonce(num: u32) -> Vec<u8> {
+    (0..=31).rev().map(|n| ((num >> n) & 1) as u8).collect()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_en_decode_bits() {
+        let num = 24u64; // 11000
+        let bit = get_decode_bits(num);
+        assert_eq!(bit, 0);
+        let bits = encode_bits(0);
+        let arr = vec![0, 0, 0];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000000000000000000000000000000000000000000000000000000101;
+        let bit = get_decode_bits(num);
+        assert_eq!(bit, 5);
+        let bits = encode_bits(5);
+        let arr = vec![1, 0, 1];
+        assert_eq!(bits, arr);
+
+        let num = 0b0100000000000000000000000000000000000000000000000000000000000110;
+        let bit = get_decode_bits(num);
+        assert_eq!(bit, 6);
+        let bits = encode_bits(6);
+        let arr = vec![1, 1, 0];
+        assert_eq!(bits, arr);
+
+        let num = 0b1010000000000000000000000000000000000000000000000000000000000111;
+        let bit = get_decode_bits(num);
+        assert_eq!(bit, 7);
+        let bits = encode_bits(7);
+        let arr = vec![1, 1, 1];
+        assert_eq!(bits, arr);
+
+        let num = 0b0011000000000000000000000000000000000000000000000000000000000001;
+        let bit = get_decode_bits(num);
+        assert_eq!(bit, 1);
+        let bits = encode_bits(1);
+        let arr = vec![0, 0, 1];
+        assert_eq!(bits, arr);
+    }
+
+    #[test]
+    fn test_get_decode_aux_chain() {
+        let num = 24u64; // 11000
+        let aux_number = get_aux_chain_count(num, 0);
+        assert_eq!(aux_number, 2);
+        let bits = encode_aux_chain_count(2);
+        let arr: Vec<u8> = vec![1];
+        assert_eq!(bits, arr);
+
+        let num = 0b1101111111100000000000000000000000000000000000000000011111110000;
+        let aux_number = get_aux_chain_count(num, 7);
+        assert_eq!(aux_number, 255);
+        let bits = encode_aux_chain_count(255);
+        let arr = vec![1, 1, 1, 1, 1, 1, 1, 0];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000100000000000000000000000000000000000000000000000101101;
+        let aux_number = get_aux_chain_count(num, 3);
+        assert_eq!(aux_number, 6);
+        let bits = encode_aux_chain_count(6);
+        let arr = vec![1, 0, 1];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000000000000000000000000000000000000000000000000000011101;
+        let aux_number = get_aux_chain_count(num, 2);
+        assert_eq!(aux_number, 4);
+        let bits = encode_aux_chain_count(4);
+        let arr = vec![1, 1];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100111000000000000000000000000000000000000000000000000000000101;
+        let aux_number = get_aux_chain_count(num, 1);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_chain_count(1);
+        let arr = vec![0];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000100000000000000000000000000000000000000000000000000111101;
+        let aux_number = get_aux_chain_count(num, 3);
+        assert_eq!(aux_number, 8);
+        let bits = encode_aux_chain_count(8);
+        let arr = vec![1, 1, 1];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000001000000000000000000000000000000000000000000000001111101;
+        let aux_number = get_aux_chain_count(num, 4);
+        assert_eq!(aux_number, 16);
+        let bits = encode_aux_chain_count(16);
+        let arr = vec![1, 1, 1, 1];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000010000000000000000000000000000000000000000000001111000101;
+        let aux_number = get_aux_chain_count(num, 7);
+        assert_eq!(aux_number, 121);
+        let bits = encode_aux_chain_count(121);
+        let arr = vec![1, 1, 1, 1, 0, 0, 0];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000100000000000000000000000000000000000000000000001100000101;
+        let aux_number = get_aux_chain_count(num, 7);
+        assert_eq!(aux_number, 97);
+        let bits = encode_aux_chain_count(97);
+        let arr = vec![1, 1, 0, 0, 0, 0, 0];
+        assert_eq!(bits, arr);
+
+        let num = 0b1111000110000000000000000000000000000000000000000000000111000101;
+        let aux_number = get_aux_chain_count(num, 6);
+        assert_eq!(aux_number, 57);
+        let bits = encode_aux_chain_count(57);
+        let arr = vec![1, 1, 1, 0, 0, 0];
+        assert_eq!(bits, arr);
+    }
+
+    #[test]
+    #[allow(clippy::too_many_lines)]
+    fn test_get_decode_aux_nonce() {
+        let num = 24u64; // 11000
+        let aux_number = get_aux_nonce(num, 0);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000100000000101;
+        let aux_number = get_aux_nonce(num, 7);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000010000000101;
+        let aux_number = get_aux_nonce(num, 6);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000001000000101;
+        let aux_number = get_aux_nonce(num, 5);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000000100000101;
+        let aux_number = get_aux_nonce(num, 4);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000000010000101;
+        let aux_number = get_aux_nonce(num, 3);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000000001000101;
+        let aux_number = get_aux_nonce(num, 2);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000000000100101;
+        let aux_number = get_aux_nonce(num, 1);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000000000010101;
+        let aux_number = get_aux_nonce(num, 0);
+        assert_eq!(aux_number, 1);
+        let bits = encode_aux_nonce(1);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000000000000000000000000000000000000010000000101;
+        let aux_number = get_aux_nonce(num, 7);
+        assert_eq!(aux_number, 0);
+        let bits = encode_aux_nonce(0);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            0, 0, 0,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000001111111111111111111111111111111110000000101;
+        let aux_number = get_aux_nonce(num, 7);
+        assert_eq!(aux_number, u32::MAX);
+        let bits = encode_aux_nonce(u32::MAX);
+        let arr = vec![
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+            1, 1, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000001111111111100011111111111111111110000000101;
+        let aux_number = get_aux_nonce(num, 7);
+        assert_eq!(aux_number, 4293132287);
+        let bits = encode_aux_nonce(4293132287);
+        let arr = vec![
+            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+            1, 1, 1,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b1100000000110000000001010101010101010101010101010101010000000101;
+        let aux_number = get_aux_nonce(num, 7);
+        assert_eq!(aux_number, 2863311530);
+        let bits = encode_aux_nonce(2863311530);
+        let arr = vec![
+            1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
+            0, 1, 0,
+        ];
+        assert_eq!(bits, arr);
+
+        let num = 0b110000000011000000000000000000000000011110011110111010000000101;
+        let aux_number = get_aux_nonce(num, 7);
+        assert_eq!(aux_number, 31214);
+        let bits = encode_aux_nonce(31214);
+        let arr = vec![
+            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1,
+            1, 1, 0,
+        ];
+        assert_eq!(bits, arr);
+    }
+
+    #[test]
+    fn merkle_complete() {
+        let num = VarInt(24);
+        let merkle_tree_params = MerkleTreeParameters::from_varint(num);
+        assert_eq!(merkle_tree_params.aux_nonce, 1);
+        assert_eq!(merkle_tree_params.number_of_chains, 2);
+
+        let ser_num = merkle_tree_params.to_varint();
+        assert_eq!(ser_num, VarInt(24));
+    }
+}

+ 74 - 0
src/validator/xmr/mod.rs

@@ -0,0 +1,74 @@
+/* This file is part of DarkFi (https://dark.fi)
+ *
+ * Copyright (C) 2020-2025 Dyne.org foundation
+ * Copyright (C) 2021 The Tari Project (BSD-3)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+use monero::{
+    consensus::Encodable,
+    cryptonote::hash::Hashable,
+    util::ringct::{RctSigBase, RctType},
+    Hash,
+};
+use tiny_keccak::Hasher;
+
+use crate::blockchain::monero::MoneroPowData;
+
+mod helpers;
+use helpers::create_blockhashing_blob;
+
+mod merkle_tree_parameters;
+
+impl MoneroPowData {
+    /// Returns true if the coinbase Merkle proof produces the `merkle_root` hash.
+    pub fn is_coinbase_valid_merkle_root(&self) -> bool {
+        let mut finalised_prefix_keccak = self.coinbase_tx_hasher.clone();
+        let mut encoder_extra_field = vec![];
+        self.coinbase_tx_extra.consensus_encode(&mut encoder_extra_field).unwrap();
+        finalised_prefix_keccak.update(&encoder_extra_field);
+        let mut prefix_hash: [u8; 32] = [0u8; 32];
+        finalised_prefix_keccak.finalize(&mut prefix_hash);
+
+        let final_prefix_hash = Hash::from_slice(&prefix_hash);
+
+        // let mut finalised_keccak = Keccak::v256();
+        let rct_sig_base = RctSigBase {
+            rct_type: RctType::Null,
+            txn_fee: Default::default(),
+            pseudo_outs: vec![],
+            ecdh_info: vec![],
+            out_pk: vec![],
+        };
+
+        let hashes = vec![final_prefix_hash, rct_sig_base.hash(), Hash::null()];
+        let encoder_final: Vec<u8> =
+            hashes.into_iter().flat_map(|h| Vec::from(&h.to_bytes()[..])).collect();
+        let coinbase_hash = Hash::new(encoder_final);
+
+        let merkle_root = self.coinbase_merkle_proof.calculate_root(&coinbase_hash);
+        (self.merkle_root == merkle_root) && self.coinbase_merkle_proof.check_coinbase_path()
+    }
+
+    /// Returns the blockhashing_blob for the Monero block
+    pub fn to_blockhashing_blob(&self) -> Vec<u8> {
+        create_blockhashing_blob(&self.header, &self.merkle_root, u64::from(self.transaction_count))
+    }
+
+    /// Returns the RandomX VM key
+    pub fn randomx_key(&self) -> &[u8] {
+        self.randomx_key.as_slice()
+    }
+}