darkfi/blockchain/monero/
mod.rs1use std::{
20 fmt,
21 io::{self, Cursor, Error, Read, Write},
22 iter,
23};
24
25use darkfi_sdk::{hex::decode_hex, AsHex};
26#[cfg(feature = "async-serial")]
27use darkfi_serial::{async_trait, AsyncDecodable, AsyncEncodable, AsyncRead, AsyncWrite};
28use darkfi_serial::{Decodable, Encodable};
29use monero::{
30 blockdata::transaction::{ExtraField, RawExtraField, SubField},
31 consensus::{Decodable as XmrDecodable, Encodable as XmrEncodable},
32 cryptonote::hash::Hashable,
33 util::ringct::{RctSigBase, RctType},
34 BlockHeader,
35};
36use tiny_keccak::{Hasher, Keccak};
37use tracing::warn;
38
39use crate::{Error::MoneroMergeMineError, Result};
40
41pub mod fixed_array;
42use fixed_array::{FixedByteArray, MaxSizeVec};
43
44pub mod merkle_proof;
45use merkle_proof::MerkleProof;
46
47pub mod keccak;
48use keccak::{keccak_from_bytes, keccak_to_bytes};
49
50pub mod utils;
51use utils::{create_blockhashing_blob, create_merkle_proof, tree_hash};
52
53pub mod merkle_tree_parameters;
54pub use merkle_tree_parameters::MerkleTreeParameters;
55
56pub type AuxChainHashes = MaxSizeVec<monero::Hash, 128>;
57
58#[derive(Clone)]
61pub struct MoneroPowData {
62 pub header: BlockHeader,
64 pub randomx_key: FixedByteArray,
66 pub transaction_count: u16,
69 pub merkle_root: monero::Hash,
71 pub coinbase_merkle_proof: MerkleProof,
73 pub coinbase_tx_hasher: Keccak,
75 pub coinbase_tx_extra: RawExtraField,
77 pub aux_chain_merkle_proof: MerkleProof,
79}
80
81impl MoneroPowData {
82 pub fn new(
84 block: monero::Block,
85 seed: FixedByteArray,
86 aux_chain_merkle_proof: MerkleProof,
87 ) -> Result<Self> {
88 let hashes = create_ordered_tx_hashes_from_block(&block);
89 let root = tree_hash(&hashes)?;
90 let hash =
91 hashes.first().ok_or(MoneroMergeMineError("No hashes for Merkle proof".to_string()))?;
92
93 let coinbase_merkle_proof = create_merkle_proof(&hashes, hash).ok_or_else(|| {
94 MoneroMergeMineError(
95 "create_merkle_proof returned None because the block has no coinbase".to_string(),
96 )
97 })?;
98
99 let coinbase = block.miner_tx.clone();
100
101 let mut keccak = Keccak::v256();
102 let mut encoder_prefix = vec![];
103 coinbase.prefix.version.consensus_encode(&mut encoder_prefix)?;
104 coinbase.prefix.unlock_time.consensus_encode(&mut encoder_prefix)?;
105 coinbase.prefix.inputs.consensus_encode(&mut encoder_prefix)?;
106 coinbase.prefix.outputs.consensus_encode(&mut encoder_prefix)?;
107 keccak.update(&encoder_prefix);
108
109 Ok(Self {
110 header: block.header,
111 randomx_key: seed,
112 transaction_count: hashes.len() as u16,
113 merkle_root: root,
114 coinbase_merkle_proof,
115 coinbase_tx_extra: block.miner_tx.prefix.extra,
116 coinbase_tx_hasher: keccak,
117 aux_chain_merkle_proof,
118 })
119 }
120
121 pub fn is_coinbase_valid_merkle_root(&self) -> bool {
124 let mut finalised_prefix_keccak = self.coinbase_tx_hasher.clone();
125 let mut encoder_extra_field = vec![];
126
127 self.coinbase_tx_extra.consensus_encode(&mut encoder_extra_field).unwrap();
128 finalised_prefix_keccak.update(&encoder_extra_field);
129 let mut prefix_hash: [u8; 32] = [0u8; 32];
130 finalised_prefix_keccak.finalize(&mut prefix_hash);
131
132 let final_prefix_hash = monero::Hash::from_slice(&prefix_hash);
133
134 let rct_sig_base = RctSigBase {
136 rct_type: RctType::Null,
137 txn_fee: Default::default(),
138 pseudo_outs: vec![],
139 ecdh_info: vec![],
140 out_pk: vec![],
141 };
142
143 let hashes = vec![final_prefix_hash, rct_sig_base.hash(), monero::Hash::null()];
144
145 let encoder_final: Vec<u8> =
146 hashes.into_iter().flat_map(|h| Vec::from(&h.to_bytes()[..])).collect();
147
148 let coinbase_hash = monero::Hash::new(encoder_final);
149
150 let merkle_root = self.coinbase_merkle_proof.calculate_root(&coinbase_hash);
151 (self.merkle_root == merkle_root) && self.coinbase_merkle_proof.check_coinbase_path()
152 }
153
154 pub fn to_block_hashing_blob(&self) -> Vec<u8> {
156 create_blockhashing_blob(&self.header, &self.merkle_root, u64::from(self.transaction_count))
157 }
158
159 pub fn randomx_key(&self) -> &[u8] {
161 self.randomx_key.as_slice()
162 }
163}
164
165impl fmt::Debug for MoneroPowData {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 let mut digest = [0u8; 32];
168 self.coinbase_tx_hasher.clone().finalize(&mut digest);
169 f.debug_struct("MoneroPowData")
170 .field("header", &self.header)
171 .field("randomx_key", &self.randomx_key)
172 .field("transaction_count", &self.transaction_count)
173 .field("merkle_root", &self.merkle_root)
174 .field("coinbase_merkle_proof", &self.coinbase_merkle_proof)
175 .field("coinbase_tx_extra", &self.coinbase_tx_extra)
176 .field("aux_chain_merkle_proof", &self.aux_chain_merkle_proof)
177 .finish()
178 }
179}
180
181impl Encodable for MoneroPowData {
182 fn encode<S: Write>(&self, s: &mut S) -> io::Result<usize> {
183 let mut n = 0;
184
185 let mut buf = vec![];
190 self.header.consensus_encode(&mut buf)?;
191 n += buf.encode(s)?;
192
193 n += self.randomx_key.encode(s)?;
194 n += self.transaction_count.encode(s)?;
195
196 let mut buf = vec![];
197 self.merkle_root.consensus_encode(&mut buf)?;
198 n += buf.encode(s)?;
199
200 n += self.coinbase_merkle_proof.encode(s)?;
201
202 let buf = keccak_to_bytes(&self.coinbase_tx_hasher);
205 n += buf.encode(s)?;
206
207 n += self.coinbase_tx_extra.0.encode(s)?;
208 n += self.aux_chain_merkle_proof.encode(s)?;
209
210 Ok(n)
211 }
212}
213
214#[cfg(feature = "async-serial")]
215#[async_trait]
216impl AsyncEncodable for MoneroPowData {
217 async fn encode_async<S: AsyncWrite + Unpin + Send>(&self, s: &mut S) -> io::Result<usize> {
218 let mut n = 0;
219
220 let mut buf = vec![];
223 self.header.consensus_encode(&mut buf)?;
224 n += buf.encode_async(s).await?;
225
226 n += self.randomx_key.encode_async(s).await?;
227 n += self.transaction_count.encode_async(s).await?;
228
229 let mut buf = vec![];
230 self.merkle_root.consensus_encode(&mut buf)?;
231 n += buf.encode_async(s).await?;
232
233 n += self.coinbase_merkle_proof.encode_async(s).await?;
234
235 let buf = keccak_to_bytes(&self.coinbase_tx_hasher);
238 n += buf.encode_async(s).await?;
239
240 n += self.coinbase_tx_extra.0.encode_async(s).await?;
241 n += self.aux_chain_merkle_proof.encode_async(s).await?;
242
243 Ok(n)
244 }
245}
246
247impl Decodable for MoneroPowData {
248 fn decode<D: Read>(d: &mut D) -> io::Result<Self> {
249 let buf: Vec<u8> = Decodable::decode(d)?;
250 let mut buf = Cursor::new(buf);
251 let header = BlockHeader::consensus_decode(&mut buf)
252 .map_err(|_| Error::other("Invalid XMR header"))?;
253
254 let randomx_key: FixedByteArray = Decodable::decode(d)?;
255 let transaction_count: u16 = Decodable::decode(d)?;
256
257 let buf: Vec<u8> = Decodable::decode(d)?;
258 let mut buf = Cursor::new(buf);
259 let merkle_root = monero::Hash::consensus_decode(&mut buf)
260 .map_err(|_| Error::other("Invalid XMR hash"))?;
261
262 let coinbase_merkle_proof: MerkleProof = Decodable::decode(d)?;
263
264 let buf: Vec<u8> = Decodable::decode(d)?;
265 let coinbase_tx_hasher = keccak_from_bytes(&buf);
266
267 let coinbase_tx_extra: Vec<u8> = Decodable::decode(d)?;
268 let coinbase_tx_extra = RawExtraField(coinbase_tx_extra);
269 let aux_chain_merkle_proof: MerkleProof = Decodable::decode(d)?;
270
271 Ok(Self {
272 header,
273 randomx_key,
274 transaction_count,
275 merkle_root,
276 coinbase_merkle_proof,
277 coinbase_tx_hasher,
278 coinbase_tx_extra,
279 aux_chain_merkle_proof,
280 })
281 }
282}
283
284#[cfg(feature = "async-serial")]
285#[async_trait]
286impl AsyncDecodable for MoneroPowData {
287 async fn decode_async<D: AsyncRead + Unpin + Send>(d: &mut D) -> io::Result<Self> {
288 let buf: Vec<u8> = AsyncDecodable::decode_async(d).await?;
289 let mut buf = Cursor::new(buf);
290 let header = BlockHeader::consensus_decode(&mut buf)
291 .map_err(|_| Error::other("Invalid XMR header"))?;
292
293 let randomx_key: FixedByteArray = AsyncDecodable::decode_async(d).await?;
294 let transaction_count: u16 = AsyncDecodable::decode_async(d).await?;
295
296 let buf: Vec<u8> = AsyncDecodable::decode_async(d).await?;
297 let mut buf = Cursor::new(buf);
298 let merkle_root = monero::Hash::consensus_decode(&mut buf)
299 .map_err(|_| Error::other("Invalid XMR hash"))?;
300
301 let coinbase_merkle_proof: MerkleProof = AsyncDecodable::decode_async(d).await?;
302
303 let buf: Vec<u8> = AsyncDecodable::decode_async(d).await?;
304 let coinbase_tx_hasher = keccak_from_bytes(&buf);
305
306 let coinbase_tx_extra: Vec<u8> = AsyncDecodable::decode_async(d).await?;
307 let coinbase_tx_extra = RawExtraField(coinbase_tx_extra);
308 let aux_chain_merkle_proof: MerkleProof = AsyncDecodable::decode_async(d).await?;
309
310 Ok(Self {
311 header,
312 randomx_key,
313 transaction_count,
314 merkle_root,
315 coinbase_merkle_proof,
316 coinbase_tx_hasher,
317 coinbase_tx_extra,
318 aux_chain_merkle_proof,
319 })
320 }
321}
322
323pub fn create_ordered_tx_hashes_from_block(block: &monero::Block) -> Vec<monero::Hash> {
325 iter::once(block.miner_tx.hash()).chain(block.tx_hashes.clone()).collect()
326}
327
328pub fn insert_aux_chain_mr_and_info_into_block<T: AsRef<[u8]>>(
330 block: &mut monero::Block,
331 aux_chain_mr: T,
332 aux_chain_count: u8,
333 aux_nonce: u32,
334) -> Result<()> {
335 if aux_chain_count == 0 {
336 return Err(MoneroMergeMineError("Zero aux chains".to_string()))
337 }
338
339 if aux_chain_mr.as_ref().len() != monero::Hash::len_bytes() {
340 return Err(MoneroMergeMineError("Aux chain root invalid length".to_string()))
341 }
342
343 let mut extra_field = match ExtraField::try_parse(&block.miner_tx.prefix.extra) {
346 Ok(v) => v,
347 Err(e) => return Err(MoneroMergeMineError(e.to_string())),
348 };
349
350 for item in &extra_field.0 {
352 if let SubField::MergeMining(_, _) = item {
353 return Err(MoneroMergeMineError("More than one mm tag in coinbase".to_string()))
354 }
355 }
356
357 let hash = monero::Hash::from_slice(aux_chain_mr.as_ref());
364 let encoded = if aux_chain_count == 1 {
365 monero::VarInt(0)
366 } else {
367 let mt_params = MerkleTreeParameters::new(aux_chain_count, aux_nonce)?;
368 mt_params.to_varint()
369 };
370 extra_field.0.insert(0, SubField::MergeMining(encoded, hash));
371
372 block.miner_tx.prefix.extra = extra_field.into();
373
374 let blocktemplate_ser = monero::consensus::serialize(block);
376 let blocktemplate_hex = blocktemplate_ser.hex();
377 let blocktemplate_bytes = decode_hex(&blocktemplate_hex)
378 .collect::<std::result::Result<Vec<_>, _>>()
379 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
380 let de_block: monero::Block = match monero::consensus::deserialize(&blocktemplate_bytes) {
381 Ok(v) => v,
382 Err(e) => return Err(io::Error::new(io::ErrorKind::InvalidData, e).into()),
383 };
384
385 if block != &de_block {
386 return Err(MoneroMergeMineError("Blocks don't match after serialization".to_string()))
387 }
388
389 Ok(())
390}
391
392pub fn monero_block_deserialize(blob: &str) -> Result<monero::Block> {
417 let bytes: Vec<u8> = decode_hex(blob)
418 .collect::<std::result::Result<Vec<_>, _>>()
419 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
420
421 let mut reader = Cursor::new(bytes);
422
423 match monero::Block::consensus_decode(&mut reader) {
424 Ok(v) => Ok(v),
425 Err(e) => Err(io::Error::new(io::ErrorKind::InvalidData, e).into()),
426 }
427}
428
429fn parse_extra_field_truncate_on_error(raw_extra_field: &RawExtraField) -> ExtraField {
435 match ExtraField::try_parse(raw_extra_field) {
436 Ok(v) => v,
437 Err(v) => {
438 warn!(
439 target: "blockchain::monero::parse_extra_field_truncate_on_error",
440 "[BLOCKCHAIN] Some Monero tx_extra subfields could not be parsed",
441 );
442 v
443 }
444 }
445}
446
447pub fn extract_aux_merkle_root_from_block(monero: &monero::Block) -> Result<Option<monero::Hash>> {
449 extract_aux_merkle_root(&monero.miner_tx.prefix.extra)
450}
451
452pub fn extract_aux_merkle_root(extra_field: &RawExtraField) -> Result<Option<monero::Hash>> {
454 let extra_field = parse_extra_field_truncate_on_error(extra_field);
455 let merge_mining_hashes: Vec<monero::Hash> = extra_field
457 .0
458 .iter()
459 .filter_map(|item| {
460 if let SubField::MergeMining(_depth, merge_mining_hash) = item {
461 Some(*merge_mining_hash)
462 } else {
463 None
464 }
465 })
466 .collect();
467
468 if merge_mining_hashes.len() > 1 {
469 return Err(MoneroMergeMineError("More than one MM tag found in coinbase".to_string()))
470 }
471
472 if let Some(merge_mining_hash) = merge_mining_hashes.into_iter().next() {
473 Ok(Some(merge_mining_hash))
474 } else {
475 Ok(None)
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use std::str::FromStr;
483
484 const XMR_BLOCK: &str = "1010f881efca0644a1185eeccb2629b316ec0d41659111299ad1b736a3b0d8eac8bbc6384dc5c84bb6010002a0e2b10101ffe4e1b1010180e0a596bb1103f1d23951bd28ce2bfad791f2350e2ac348e4620e19af3418653a1839cc5c8f2be14a010b204d874ed5087b649c711dd4479434a85dbf7e9bdfae26f5bc785964d4b45c0204751b43e10321082d5f403be836d45d026fbaa2a8e4b4a9d0d821f29d709321f8d764f32d446fa80000";
486 const SEED_HASH: &str = "f1d23951bd28ce2bfad791f2350e2ac348e4620e19af3418653a1839cc5c8f2b";
487
488 #[test]
491 fn test_monero_powdata_serde() {
492 let block = monero_block_deserialize(XMR_BLOCK).unwrap();
493 let seed = FixedByteArray::from_bytes(&hex::decode(SEED_HASH).unwrap()).unwrap();
494
495 let tx_hashes = &[
497 "d96756959949db23764592fea0bfe88c790e1fd131dabb676948b343aa9ecc24",
498 "77d1a87df131c36da4832a7ec382db9b8fe947576a60ec82cc1c66a220f6ee42",
499 ]
500 .iter()
501 .map(|hash| monero::Hash::from_str(hash).unwrap())
502 .collect::<Vec<_>>();
503
504 let aux_chain_merkle_proof = create_merkle_proof(tx_hashes, &tx_hashes[0]).unwrap();
505
506 let mut powdata = MoneroPowData::new(block, seed, aux_chain_merkle_proof).unwrap();
508
509 let local_ex = smol::LocalExecutor::new();
510
511 let ser_sync = darkfi_serial::serialize(&powdata);
512 let ser_async = smol::future::block_on(
513 local_ex.run(async { darkfi_serial::serialize_async(&powdata).await }),
514 );
515
516 assert_eq!(ser_sync, ser_async);
517
518 let mut de_sync: MoneroPowData = darkfi_serial::deserialize(&ser_async).unwrap();
519 let mut de_async: MoneroPowData = smol::future::block_on(
520 local_ex.run(async { darkfi_serial::deserialize_async(&ser_async).await.unwrap() }),
521 );
522
523 assert_eq!(de_sync.header, powdata.header);
524 assert_eq!(de_sync.randomx_key, powdata.randomx_key);
525 assert_eq!(de_sync.transaction_count, powdata.transaction_count);
526 assert_eq!(de_sync.merkle_root, powdata.merkle_root);
527 assert_eq!(de_sync.coinbase_merkle_proof.branch(), powdata.coinbase_merkle_proof.branch());
528 assert_eq!(de_sync.coinbase_merkle_proof.path(), powdata.coinbase_merkle_proof.path());
529 assert_eq!(de_sync.coinbase_tx_extra, powdata.coinbase_tx_extra);
530 assert_eq!(
531 de_sync.aux_chain_merkle_proof.branch(),
532 powdata.aux_chain_merkle_proof.branch()
533 );
534 assert_eq!(de_sync.aux_chain_merkle_proof.path(), powdata.aux_chain_merkle_proof.path());
535
536 assert_eq!(de_async.header, powdata.header);
537 assert_eq!(de_async.randomx_key, powdata.randomx_key);
538 assert_eq!(de_async.transaction_count, powdata.transaction_count);
539 assert_eq!(de_async.merkle_root, powdata.merkle_root);
540 assert_eq!(de_async.coinbase_merkle_proof.branch(), powdata.coinbase_merkle_proof.branch());
541 assert_eq!(de_async.coinbase_merkle_proof.path(), powdata.coinbase_merkle_proof.path());
542 assert_eq!(de_async.coinbase_tx_extra, powdata.coinbase_tx_extra);
543 assert_eq!(
544 de_async.aux_chain_merkle_proof.branch(),
545 powdata.aux_chain_merkle_proof.branch()
546 );
547 assert_eq!(de_async.aux_chain_merkle_proof.path(), powdata.aux_chain_merkle_proof.path());
548
549 powdata.coinbase_tx_hasher.update(b"hi");
551 let mut powdata_digest = vec![];
552 powdata.coinbase_tx_hasher.finalize(&mut powdata_digest);
553
554 de_sync.coinbase_tx_hasher.update(b"hi");
555 let mut de_sync_digest = vec![];
556 de_sync.coinbase_tx_hasher.finalize(&mut de_sync_digest);
557
558 de_async.coinbase_tx_hasher.update(b"hi");
559 let mut de_async_digest = vec![];
560 de_async.coinbase_tx_hasher.finalize(&mut de_async_digest);
561
562 assert_eq!(de_sync_digest, powdata_digest);
563 assert_eq!(de_async_digest, powdata_digest);
564 }
565}