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

research/powdata: Add temporary XMR PowData code to be included in blocks

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

+ 2 - 0
script/research/powdata/.gitignore

@@ -0,0 +1,2 @@
+target/
+Cargo.lock

+ 13 - 0
script/research/powdata/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "xmrpowdata"
+version = "0.4.1"
+authors = ["Dyne.org foundation <foundation@dyne.org>"]
+license = "AGPL-3.0-only"
+edition = "2021"
+
+[workspace]
+
+[dependencies]
+monero = "0.21.0"
+tiny-keccak = { version = "2.0.2", features = ["keccak"] }
+thiserror = "2.0.12"

+ 5 - 0
script/research/powdata/src/error.rs

@@ -0,0 +1,5 @@
+#[derive(Debug, thiserror::Error)]
+pub enum MergeMineError {
+    #[error("Hashing of Monero data failed: {0}")]
+    HashingError(String),
+}

+ 65 - 0
script/research/powdata/src/lib.rs

@@ -0,0 +1,65 @@
+use monero::{
+    blockdata::transaction::RawExtraField,
+    consensus::Encodable,
+    cryptonote::hash::Hashable,
+    util::ringct::{RctSigBase, RctType},
+};
+use tiny_keccak::{Hasher, Keccak};
+
+mod error;
+
+mod merkle_tree;
+use merkle_tree::MerkleProof;
+
+#[derive(Clone)]
+pub struct MoneroPowData {
+    /// Monero Header fields
+    pub header: monero::BlockHeader,
+    /// RandomX VM key - length varies to a max len of 60.
+    /// TODO: Implement a type, or use randomx_key[0] to define len.
+    pub randomx_key: [u8; 64],
+    /// The number of transactions included in this Monero block.
+    /// This is used to produce the blockhashing_blob.
+    pub transaction_count: u16,
+    /// Transaction root
+    pub merkle_root: monero::Hash,
+    /// Coinbase Merkle proof hashes
+    pub coinbase_merkle_proof: MerkleProof,
+    /// Incomplete hashed state of the coinbase transaction
+    pub coinbase_tx_hasher: Keccak,
+    /// Extra field of the coinbase
+    pub coinbase_tx_extra: RawExtraField,
+    /// Aux chain Merkle proof hashes
+    pub aux_chain_merkle_proof: MerkleProof,
+}
+
+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 = monero::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(), monero::Hash::null()];
+        let encoder_final: Vec<u8> =
+            hashes.into_iter().flat_map(|h| Vec::from(&h.to_bytes()[..])).collect();
+        let coinbase_hash = monero::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()
+    }
+}

+ 260 - 0
script/research/powdata/src/merkle_tree.rs

@@ -0,0 +1,260 @@
+use monero::Hash;
+
+use crate::error::MergeMineError;
+
+const MAX_MERKLE_TREE_PROOF_SIZE: usize = 32;
+
+/// Returns the Keccak 256 hash of the byte input
+fn cn_fast_hash(data: &[u8]) -> Hash {
+    Hash::new(data)
+}
+
+/// Returns the Keccak 256 hash of 2 hashes
+fn cn_fast_hash2(hash1: &Hash, hash2: &Hash) -> Hash {
+    let mut tmp = [0u8; 64];
+    tmp[..32].copy_from_slice(hash1.as_bytes());
+    tmp[32..].copy_from_slice(hash2.as_bytes());
+    cn_fast_hash(&tmp)
+}
+
+/// Round down to power of two.
+/// Should error for count < 3 or if the count is unreasonably large
+/// for tree hash calculations.
+fn tree_hash_count(count: usize) -> Result<usize, MergeMineError> {
+    if count < 3 {
+        return Err(MergeMineError::HashingError(format!(
+            "Cannot calculate tree hash root. Expected count to be >3 but got {}",
+            count
+        )));
+    }
+
+    if count > 0x10000000 {
+        return Err(MergeMineError::HashingError(format!(
+            "Cannot calculate tree hash root. Expected count to be less than 0x10000000 but got {}",
+            count
+        )));
+    }
+
+    // Essentially we are doing 1 << floor(log2(count))
+    let mut pow: usize = 2;
+    while pow < count {
+        pow <<= 1;
+    }
+
+    Ok(pow >> 1)
+}
+
+/// Tree hash algorithm in Monero
+pub fn tree_hash(hashes: &[Hash]) -> Result<Hash, MergeMineError> {
+    if hashes.is_empty() {
+        return Err(MergeMineError::HashingError(
+            "Cannot calculate Merkle root, no hashes".to_string(),
+        ));
+    }
+
+    match hashes.len() {
+        1 => Ok(hashes[0]),
+        2 => Ok(cn_fast_hash2(&hashes[0], &hashes[1])),
+        n => {
+            let mut cnt = tree_hash_count(n)?;
+            let mut buf = vec![Hash::null(); cnt];
+
+            // c is the number of elements between the number of hashes
+            // and the next power of 2.
+            let c = 2 * cnt - hashes.len();
+
+            buf[..c].copy_from_slice(&hashes[..c]);
+
+            // hash the rest of the hashes together
+            let mut i: usize = c;
+            for b in &mut buf[c..cnt] {
+                *b = cn_fast_hash2(&hashes[i], &hashes[i + 1]);
+                i += 2;
+            }
+
+            if i != hashes.len() {
+                return Err(MergeMineError::HashingError(
+                    "Cannot calculate the Merkle root, hashes not equal to count".to_string(),
+                ));
+            }
+
+            while cnt > 2 {
+                cnt >>= 1;
+                let mut i = 0;
+                for j in 0..cnt {
+                    buf[j] = cn_fast_hash2(&buf[i], &buf[i + 1]);
+                    i += 2;
+                }
+            }
+
+            Ok(cn_fast_hash2(&buf[0], &buf[1]))
+        }
+    }
+}
+
+/// The Monero Merkle proof
+#[derive(Debug, Clone)]
+pub struct MerkleProof {
+    branch: Vec<Hash>,
+    path_bitmap: u32,
+}
+
+impl MerkleProof {
+    fn try_construct(branch: Vec<Hash>, path_bitmap: u32) -> Option<Self> {
+        if branch.len() >= MAX_MERKLE_TREE_PROOF_SIZE {
+            return None
+        }
+
+        Some(Self { branch, path_bitmap })
+    }
+
+    /// Returns the Merkle proof branch as a list of Monero hashes
+    #[inline]
+    pub fn branch(&self) -> &[Hash] {
+        &self.branch
+    }
+
+    /// Returns the path bitmap of the proof
+    pub fn path(&self) -> u32 {
+        self.path_bitmap
+    }
+
+    /// The coinbase must be the first transaction in the block, so
+    /// that you can't have multiple coinbases in a block. That means
+    /// the coinbase is always the leftmost branch in the Merkle tree.
+    /// This tests that the given proof is for the leftmost branch in
+    /// the Merkle tree.
+    pub fn check_coinbase_path(&self) -> bool {
+        if self.path_bitmap == 0b00000000 {
+            return true;
+        }
+        false
+    }
+
+    /// Calculates the Merkle root hash from the provided Monero hash
+    pub fn calculate_root_with_pos(&self, hash: &Hash, aux_chain_count: u8) -> (Hash, u32) {
+        let root = self.calculate_root(hash);
+        let pos = self.get_position_from_path(u32::from(aux_chain_count));
+        (root, pos)
+    }
+
+    pub fn calculate_root(&self, hash: &Hash) -> Hash {
+        if self.branch.is_empty() {
+            return *hash;
+        }
+
+        let mut root = *hash;
+        let depth = self.branch.len();
+        for d in 0..depth {
+            if (self.path_bitmap >> (depth - d - 1)) & 1 > 0 {
+                root = cn_fast_hash2(&self.branch[d], &root);
+            } else {
+                root = cn_fast_hash2(&root, &self.branch[d]);
+            }
+        }
+
+        root
+    }
+
+    pub fn get_position_from_path(&self, aux_chain_count: u32) -> u32 {
+        if aux_chain_count <= 1 {
+            return 0
+        }
+
+        let mut depth = 0;
+        let mut k = 1;
+
+        while k < aux_chain_count {
+            depth += 1;
+            k <<= 1;
+        }
+
+        k -= aux_chain_count;
+
+        let mut pos = 0;
+        let mut path = self.path_bitmap;
+
+        for _i in 1..depth {
+            pos = (pos << 1) | (path & 1);
+            path >>= 1;
+        }
+
+        if pos < k {
+            return pos
+        }
+
+        (((pos - k) << 1) | (path & 1)) + k
+    }
+}
+
+/// Creates a Merkle proof for the given hash within the set of hashes.
+/// This function returns None if the hash is not in hashes.
+/// This is a port of Monero's tree_branch function.
+#[allow(clippy::cognitive_complexity)]
+pub fn create_merkle_proof(hashes: &[Hash], hash: &Hash) -> Option<MerkleProof> {
+    match hashes.len() {
+        0 => None,
+        1 => {
+            if hashes[0] != *hash {
+                return None;
+            }
+            MerkleProof::try_construct(vec![], 0)
+        }
+        2 => hashes.iter().enumerate().find_map(|(pos, h)| {
+            if h != hash {
+                return None
+            }
+            let i = usize::from(pos == 0);
+            MerkleProof::try_construct(vec![hashes[i]], u32::from(pos != 0))
+        }),
+        len => {
+            let mut idx = hashes.iter().position(|node| node == hash)?;
+            let mut count = tree_hash_count(len).ok()?;
+
+            let mut ints = vec![Hash::null(); count];
+
+            let c = 2 * count - len;
+            ints[..c].copy_from_slice(&hashes[..c]);
+
+            let mut branch = vec![];
+            let mut path = 0u32;
+            let mut i = c;
+            for (j, val) in ints.iter_mut().enumerate().take(count).skip(c) {
+                // Left or right
+                if idx == i || idx == i + 1 {
+                    let ii = if idx == i { i + 1 } else { i };
+                    branch.push(hashes[ii]);
+                    path = (path << 1) | u32::from(idx != i);
+                    idx = j;
+                }
+                *val = cn_fast_hash2(&hashes[i], &hashes[i + 1]);
+                i += 2;
+            }
+
+            debug_assert_eq!(i, len);
+
+            while count > 2 {
+                count >>= 1;
+                let mut i = 0;
+                for j in 0..count {
+                    if idx == i || idx == i + 1 {
+                        let ii = if idx == i { i + 1 } else { i };
+                        branch.push(ints[ii]);
+                        path = (path << 1) | u32::from(idx != i);
+                        idx = j;
+                    }
+                    ints[j] = cn_fast_hash2(&ints[i], &ints[i + 1]);
+                    i += 2;
+                }
+            }
+
+            if idx == 0 || idx == 1 {
+                let ii = usize::from(idx == 0);
+                branch.push(ints[ii]);
+                path = (path << 1) | u32::from(idx != 0);
+            }
+
+            MerkleProof::try_construct(branch, path)
+        }
+    }
+}