helpers.rs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. * Copyright (C) 2021 The Tari Project (BSD-3)
  5. *
  6. * This program is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License as
  8. * published by the Free Software Foundation, either version 3 of the
  9. * License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Affero General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Affero General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. use std::{io, iter};
  20. use monero::{
  21. blockdata::transaction::{ExtraField, RawExtraField, SubField},
  22. consensus::Encodable as XmrEncodable,
  23. cryptonote::hash::Hashable,
  24. VarInt,
  25. };
  26. use primitive_types::U256;
  27. use sha2::{Digest, Sha256};
  28. use tiny_keccak::{Hasher, Keccak};
  29. use tracing::warn;
  30. use super::merkle_tree_parameters::MerkleTreeParameters;
  31. use crate::{
  32. blockchain::{
  33. header_store::HeaderHash,
  34. monero::{
  35. fixed_array::FixedByteArray,
  36. utils::{create_merkle_proof, tree_hash},
  37. MoneroPowData,
  38. },
  39. },
  40. Error,
  41. Error::MoneroMergeMineError,
  42. Result,
  43. };
  44. /// Deserializes the given hex-encoded string into a Monero block
  45. pub fn deserialize_monero_block_from_hex<T>(data: T) -> io::Result<monero::Block>
  46. where
  47. T: AsRef<[u8]>,
  48. {
  49. let bytes = hex::decode(data).map_err(|_| io::Error::other("Invalid hex data"))?;
  50. let obj = monero::consensus::deserialize::<monero::Block>(&bytes)
  51. .map_err(|_| io::Error::other("Invalid XMR block"))?;
  52. Ok(obj)
  53. }
  54. /// Serializes the given Monero block into a hex-encoded string
  55. pub fn serialize_monero_block_to_hex(obj: &monero::Block) -> io::Result<String> {
  56. let data = monero::consensus::serialize::<monero::Block>(obj);
  57. let bytes = hex::encode(data);
  58. Ok(bytes)
  59. }
  60. /// Create a set of ordered tx hashes from a Monero block
  61. pub fn create_ordered_tx_hashes_from_block(block: &monero::Block) -> Vec<monero::Hash> {
  62. iter::once(block.miner_tx.hash()).chain(block.tx_hashes.clone()).collect()
  63. }
  64. /// Creates a hex-encoded Monero blockhashing_blob
  65. pub fn create_blockhashing_blob(
  66. header: &monero::BlockHeader,
  67. merkle_root: &monero::Hash,
  68. transaction_count: u64,
  69. ) -> Vec<u8> {
  70. let mut blockhashing_blob = monero::consensus::serialize(header);
  71. blockhashing_blob.extend_from_slice(merkle_root.as_bytes());
  72. let mut count = monero::consensus::serialize(&VarInt(transaction_count));
  73. blockhashing_blob.append(&mut count);
  74. blockhashing_blob
  75. }
  76. /// Constructs [`MoneroPowData`] from the given block and seed
  77. pub fn construct_monero_data(
  78. block: monero::Block,
  79. seed: FixedByteArray,
  80. ordered_aux_chain_hashes: Vec<monero::Hash>,
  81. darkfi_hash: HeaderHash,
  82. ) -> Result<MoneroPowData> {
  83. let hashes = create_ordered_tx_hashes_from_block(&block);
  84. let root = tree_hash(&hashes)?;
  85. let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).ok_or_else(|| {
  86. MoneroMergeMineError(
  87. "create_merkle_proof returned None because the block had no coinbase".to_string(),
  88. )
  89. })?;
  90. let coinbase = block.miner_tx.clone();
  91. let mut keccak = Keccak::v256();
  92. let mut encoder_prefix = vec![];
  93. coinbase
  94. .prefix
  95. .version
  96. .consensus_encode(&mut encoder_prefix)
  97. .map_err(|e| MoneroMergeMineError(e.to_string()))?;
  98. coinbase
  99. .prefix
  100. .unlock_time
  101. .consensus_encode(&mut encoder_prefix)
  102. .map_err(|e| MoneroMergeMineError(e.to_string()))?;
  103. coinbase
  104. .prefix
  105. .inputs
  106. .consensus_encode(&mut encoder_prefix)
  107. .map_err(|e| MoneroMergeMineError(e.to_string()))?;
  108. coinbase
  109. .prefix
  110. .outputs
  111. .consensus_encode(&mut encoder_prefix)
  112. .map_err(|e| MoneroMergeMineError(e.to_string()))?;
  113. keccak.update(&encoder_prefix);
  114. let t_hash = monero::Hash::from_slice(darkfi_hash.as_slice());
  115. let aux_chain_merkle_proof = create_merkle_proof(&ordered_aux_chain_hashes, &t_hash).ok_or_else(|| {
  116. MoneroMergeMineError(
  117. "create_merkle_proof returned None, could not find darkfi hash in ordered aux chain hashes".to_string(),
  118. )
  119. })?;
  120. Ok(MoneroPowData {
  121. header: block.header,
  122. randomx_key: seed,
  123. transaction_count: hashes.len() as u16,
  124. merkle_root: root,
  125. coinbase_merkle_proof,
  126. coinbase_tx_extra: block.miner_tx.prefix.extra,
  127. coinbase_tx_hasher: keccak,
  128. aux_chain_merkle_proof,
  129. })
  130. }
  131. fn check_aux_chains(
  132. monero_data: &MoneroPowData,
  133. merge_mining_params: VarInt,
  134. aux_chain_merkle_root: &monero::Hash,
  135. darkfi_hash: HeaderHash,
  136. darkfi_genesis_hash: HeaderHash,
  137. ) -> bool {
  138. let df_hash = monero::Hash::from_slice(darkfi_hash.as_slice());
  139. if merge_mining_params == VarInt(0) {
  140. // Interpret 0 as only 1 chain
  141. if df_hash == *aux_chain_merkle_root {
  142. return true
  143. }
  144. }
  145. let merkle_tree_params = MerkleTreeParameters::from_varint(merge_mining_params);
  146. if merkle_tree_params.number_of_chains() == 0 {
  147. return false
  148. }
  149. let hash_position = U256::from_little_endian(
  150. &Sha256::new()
  151. .chain_update(darkfi_genesis_hash.as_slice())
  152. .chain_update(merkle_tree_params.aux_nonce().to_le_bytes())
  153. .chain_update((109_u8).to_le_bytes())
  154. .finalize(),
  155. )
  156. .low_u32() %
  157. u32::from(merkle_tree_params.number_of_chains());
  158. let (merkle_root, pos) = monero_data
  159. .aux_chain_merkle_proof
  160. .calculate_root_with_pos(&df_hash, merkle_tree_params.number_of_chains());
  161. if hash_position != pos {
  162. return false
  163. }
  164. merkle_root == *aux_chain_merkle_root
  165. }
  166. // Parsing an extra field from bytes will always return an extra field with sub-fields
  167. // that could be read, even if it does not represent the original extra field. As per
  168. // Monero consensus rules, an error here will not represent a failure to deserialize a
  169. // block, so no need to error here.
  170. fn parse_extra_field_truncate_on_error(raw_extra_field: &RawExtraField) -> ExtraField {
  171. match ExtraField::try_parse(raw_extra_field) {
  172. Ok(val) => val,
  173. Err(val) => {
  174. warn!(
  175. target: "validator::xmr::helpers",
  176. "[MERGEMINING] Some sub-fields could not be parsed from the Monero coinbase",
  177. );
  178. val
  179. }
  180. }
  181. }
  182. /// Extracts the Monero block hash from the coinbase transaction's extra field
  183. pub fn extract_aux_merkle_root_from_block(monero: &monero::Block) -> Result<Option<monero::Hash>> {
  184. // When we extract the merge mining hash, we do not care if
  185. // the extra field can be parsed without error.
  186. let extra_field = parse_extra_field_truncate_on_error(&monero.miner_tx.prefix.extra);
  187. // Only one merge mining tag is allowed
  188. let merge_mining_hashes: Vec<monero::Hash> = extra_field
  189. .0
  190. .iter()
  191. .filter_map(|item| {
  192. if let SubField::MergeMining(_depth, merge_mining_hash) = item {
  193. Some(*merge_mining_hash)
  194. } else {
  195. None
  196. }
  197. })
  198. .collect();
  199. if merge_mining_hashes.len() > 1 {
  200. return Err(Error::MoneroMergeMineError(
  201. "More than one merge mining tag found in coinbase".to_string(),
  202. ))
  203. }
  204. if let Some(merge_mining_hash) = merge_mining_hashes.into_iter().next() {
  205. Ok(Some(merge_mining_hash))
  206. } else {
  207. Ok(None)
  208. }
  209. }