script/research/powdata: moved into blockchain/monero

This commit is contained in:
skoupidi
2025-05-07 10:32:39 +03:00
parent 9fa1476a0a
commit ee0b139e45
11 changed files with 344 additions and 265 deletions

2
Cargo.lock generated
View File

@@ -2070,6 +2070,7 @@ dependencies = [
"lazy_static",
"libc",
"log",
"monero",
"num-bigint",
"pin-project-lite 0.2.16",
"plotters",
@@ -2088,6 +2089,7 @@ dependencies = [
"structopt",
"structopt-toml",
"thiserror 2.0.12",
"tiny-keccak",
"tinyjson",
"toml 0.8.22",
"tor-cell",

View File

@@ -127,6 +127,8 @@ sled-overlay = {version = "0.1.7", optional = true}
# Miner
randomx = {git = "https://codeberg.org/darkrenaissance/RandomX", optional = true}
monero = {version = "0.21.0", optional = true}
tiny-keccak = { version = "2.0.2", features = ["keccak"], optional = true }
[dev-dependencies]
clap = {version = "4.4.11", features = ["derive"]}
@@ -152,7 +154,9 @@ async-sdk = [
blockchain = [
"sled-overlay/serial",
"monero",
"num-bigint",
"tiny-keccak",
"darkfi-serial/num-bigint",

View File

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

View File

@@ -1,15 +0,0 @@
[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"
darkfi-serial = { path = "../../../src/serial" }

View File

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

View File

@@ -50,6 +50,9 @@ pub use contract_store::{
ContractStore, ContractStoreOverlay, SLED_BINCODE_TREE, SLED_CONTRACTS_TREE,
};
/// Monero definitions needed for merge mining
pub mod monero;
/// Structure holding all sled trees that define the concept of Blockchain.
#[derive(Clone)]
pub struct Blockchain {

View File

@@ -0,0 +1,107 @@
/* 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::{Cursor, Read, Result, Write};
#[allow(unused_imports)]
use tiny_keccak::{Hasher, Keccak};
#[repr(C)]
#[allow(unused)]
enum Mode {
Absorbing,
Squeezing,
}
#[repr(C)]
// https://docs.rs/tiny-keccak/latest/src/tiny_keccak/lib.rs.html#368
struct KeccakState {
buffer: [u8; 200],
offset: usize,
rate: usize,
delim: u8,
mode: Mode,
}
unsafe fn serialize_keccak<W: Write>(keccak: &Keccak, writer: &mut W) -> Result<()> {
let keccak_ptr = keccak as *const Keccak as *const KeccakState;
let keccak_state = &*keccak_ptr;
writer.write_all(&keccak_state.buffer)?;
writer.write_all(&(keccak_state.offset as u64).to_le_bytes())?;
writer.write_all(&(keccak_state.rate as u64).to_le_bytes())?;
writer.write_all(&[keccak_state.delim])?;
Ok(())
}
unsafe fn deserialize_keccak<R: Read>(reader: &mut R) -> Result<Keccak> {
let mut keccak = Keccak::v256();
let keccak_ptr = &mut keccak as *mut Keccak as *mut KeccakState;
let keccak_state = &mut *keccak_ptr;
reader.read_exact(&mut keccak_state.buffer)?;
let mut offset_bytes = [0u8; 8];
reader.read_exact(&mut offset_bytes)?;
keccak_state.offset = u64::from_le_bytes(offset_bytes) as usize;
let mut rate_bytes = [0u8; 8];
reader.read_exact(&mut rate_bytes)?;
keccak_state.rate = u64::from_le_bytes(rate_bytes) as usize;
let mut delim_byte = [0u8; 1];
reader.read_exact(&mut delim_byte)?;
keccak_state.delim = delim_byte[0];
keccak_state.mode = Mode::Absorbing;
Ok(keccak)
}
pub fn keccak_to_bytes(keccak: &Keccak) -> Vec<u8> {
let mut bytes = vec![];
unsafe { serialize_keccak(keccak, &mut bytes).unwrap() }
bytes
}
pub fn keccak_from_bytes(bytes: &[u8]) -> Keccak {
let mut cursor = Cursor::new(bytes);
unsafe { deserialize_keccak(&mut cursor).unwrap() }
}
#[test]
fn test_keccak_serde() {
let mut keccak = Keccak::v256();
keccak.update(b"foobar");
let ser = keccak_to_bytes(&keccak);
let mut digest1 = [0u8; 32];
keccak.finalize(&mut digest1);
let de = keccak_from_bytes(&ser);
let mut digest2 = [0u8; 32];
de.finalize(&mut digest2);
println!("{:?}", digest1);
println!("{:?}", digest2);
assert_eq!(digest1, digest2);
}

View File

@@ -0,0 +1,156 @@
/* 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::{Error, Read, Result, Write};
use darkfi_serial::{Decodable, Encodable, ReadExt};
use monero::{
consensus::{Decodable as XmrDecodable, Encodable as XmrEncodable},
Hash,
};
use super::utils::cn_fast_hash2;
const MAX_MERKLE_TREE_PROOF_SIZE: usize = 32;
/// The Monero Merkle proof
#[derive(Debug, Clone)]
pub struct MerkleProof {
branch: Vec<Hash>,
path_bitmap: u32,
}
impl Encodable for MerkleProof {
fn encode<S: Write>(&self, s: &mut S) -> Result<usize> {
let mut n = 0;
let len = self.branch.len() as u8;
n += len.encode(s)?;
for hash in &self.branch {
n += (*hash).consensus_encode(s)?;
}
n += self.path_bitmap.encode(s)?;
Ok(n)
}
}
impl Decodable for MerkleProof {
fn decode<D: Read>(d: &mut D) -> Result<Self> {
let len: u8 = d.read_u8()?;
let mut branch = Vec::with_capacity(len as usize);
for _ in 0..len {
branch.push(Hash::consensus_decode(d).map_err(|_| Error::other("Invalid XMR hash"))?);
}
let path_bitmap: u32 = Decodable::decode(d)?;
Ok(Self { branch, path_bitmap })
}
}
impl MerkleProof {
pub 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
}
}

View File

@@ -1,4 +1,22 @@
use std::io::{self, Read, Write};
/* 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::{Error, Read, Result, Write};
use darkfi_serial::{Decodable, Encodable};
use monero::{
@@ -6,18 +24,24 @@ use monero::{
consensus::{Decodable as XmrDecodable, Encodable as XmrEncodable},
cryptonote::hash::Hashable,
util::ringct::{RctSigBase, RctType},
BlockHeader, Hash,
};
use tiny_keccak::{Hasher, Keccak};
mod error;
mod merkle_proof;
use merkle_proof::MerkleProof;
mod merkle_tree;
use merkle_tree::MerkleProof;
mod keccak;
use keccak::{keccak_from_bytes, keccak_to_bytes};
mod utils;
/// This struct represents all the Proof of Work information required
/// for merge mining.
#[derive(Clone)]
pub struct MoneroPowData {
/// Monero Header fields
pub header: monero::BlockHeader,
pub header: 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],
@@ -25,7 +49,7 @@ pub struct MoneroPowData {
/// This is used to produce the blockhashing_blob.
pub transaction_count: u16,
/// Transaction root
pub merkle_root: monero::Hash,
pub merkle_root: Hash,
/// Coinbase Merkle proof hashes
pub coinbase_merkle_proof: MerkleProof,
/// Incomplete hashed state of the coinbase transaction
@@ -37,7 +61,7 @@ pub struct MoneroPowData {
}
impl Encodable for MoneroPowData {
fn encode<S: Write>(&self, s: &mut S) -> io::Result<usize> {
fn encode<S: Write>(&self, s: &mut S) -> Result<usize> {
let mut n = 0;
n += self.header.consensus_encode(s)?;
@@ -58,15 +82,15 @@ impl Encodable for MoneroPowData {
}
impl Decodable for MoneroPowData {
fn decode<D: Read>(d: &mut D) -> io::Result<Self> {
let header = monero::BlockHeader::consensus_decode(d)
.map_err(|_| io::Error::other("Invalid XMR header"))?;
fn decode<D: Read>(d: &mut D) -> Result<Self> {
let header =
BlockHeader::consensus_decode(d).map_err(|_| Error::other("Invalid XMR header"))?;
let randomx_key: [u8; 64] = Decodable::decode(d)?;
let transaction_count: u16 = Decodable::decode(d)?;
let merkle_root: [u8; 32] = Decodable::decode(d)?;
let merkle_root = monero::Hash::from_slice(&merkle_root);
let merkle_root = Hash::from_slice(&merkle_root);
let coinbase_merkle_proof: MerkleProof = Decodable::decode(d)?;
@@ -101,7 +125,7 @@ impl MoneroPowData {
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 final_prefix_hash = Hash::from_slice(&prefix_hash);
// let mut finalised_keccak = Keccak::v256();
let rct_sig_base = RctSigBase {
@@ -112,96 +136,12 @@ impl MoneroPowData {
out_pk: vec![],
};
let hashes = vec![final_prefix_hash, rct_sig_base.hash(), monero::Hash::null()];
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 = monero::Hash::new(encoder_final);
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()
}
}
#[repr(C)]
enum Mode {
Absorbing,
Squeezing,
}
#[repr(C)]
// https://docs.rs/tiny-keccak/latest/src/tiny_keccak/lib.rs.html#368
struct KeccakState {
buffer: [u8; 200],
offset: usize,
rate: usize,
delim: u8,
mode: Mode,
}
unsafe fn serialize_keccak<W: Write>(keccak: &Keccak, writer: &mut W) -> io::Result<()> {
let keccak_ptr = keccak as *const Keccak as *const KeccakState;
let keccak_state = &*keccak_ptr;
writer.write_all(&keccak_state.buffer)?;
writer.write_all(&(keccak_state.offset as u64).to_le_bytes())?;
writer.write_all(&(keccak_state.rate as u64).to_le_bytes())?;
writer.write_all(&[keccak_state.delim])?;
Ok(())
}
unsafe fn deserialize_keccak<R: Read>(reader: &mut R) -> io::Result<Keccak> {
let mut keccak = Keccak::v256();
let keccak_ptr = &mut keccak as *mut Keccak as *mut KeccakState;
let keccak_state = &mut *keccak_ptr;
reader.read_exact(&mut keccak_state.buffer)?;
let mut offset_bytes = [0u8; 8];
reader.read_exact(&mut offset_bytes)?;
keccak_state.offset = u64::from_le_bytes(offset_bytes) as usize;
let mut rate_bytes = [0u8; 8];
reader.read_exact(&mut rate_bytes)?;
keccak_state.rate = u64::from_le_bytes(rate_bytes) as usize;
let mut delim_byte = [0u8; 1];
reader.read_exact(&mut delim_byte)?;
keccak_state.delim = delim_byte[0];
keccak_state.mode = Mode::Absorbing;
Ok(keccak)
}
fn keccak_to_bytes(keccak: &Keccak) -> Vec<u8> {
let mut bytes = vec![];
unsafe { serialize_keccak(keccak, &mut bytes).unwrap() }
bytes
}
fn keccak_from_bytes(bytes: &[u8]) -> Keccak {
let mut cursor = io::Cursor::new(bytes);
unsafe { deserialize_keccak(&mut cursor).unwrap() }
}
#[test]
fn test_keccak_serde() {
let mut keccak = Keccak::v256();
keccak.update(b"foobar");
let ser = keccak_to_bytes(&keccak);
let mut digest1 = [0u8; 32];
keccak.finalize(&mut digest1);
let de = keccak_from_bytes(&ser);
let mut digest2 = [0u8; 32];
de.finalize(&mut digest2);
println!("{:?}", digest1);
println!("{:?}", digest2);
assert_eq!(digest1, digest2);
}

View File

@@ -1,22 +1,33 @@
use std::io::{self, Read, Write};
/* 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 darkfi_serial::{Decodable, Encodable, ReadExt};
use monero::{
consensus::{Decodable as XmrDecodable, Encodable as XmrEncodable},
Hash,
};
use monero::Hash;
use crate::error::MergeMineError;
const MAX_MERKLE_TREE_PROOF_SIZE: usize = 32;
use super::MerkleProof;
use crate::{Error, Result};
/// Returns the Keccak 256 hash of the byte input
fn cn_fast_hash(data: &[u8]) -> Hash {
pub 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 {
pub 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());
@@ -26,16 +37,17 @@ fn cn_fast_hash2(hash1: &Hash, hash2: &Hash) -> Hash {
/// 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> {
#[allow(unused)]
fn tree_hash_count(count: usize) -> Result<usize> {
if count < 3 {
return Err(MergeMineError::HashingError(format!(
return Err(Error::MoneroHashingError(format!(
"Cannot calculate tree hash root. Expected count to be >3 but got {}",
count
)));
}
if count > 0x10000000 {
return Err(MergeMineError::HashingError(format!(
return Err(Error::MoneroHashingError(format!(
"Cannot calculate tree hash root. Expected count to be less than 0x10000000 but got {}",
count
)));
@@ -51,9 +63,10 @@ fn tree_hash_count(count: usize) -> Result<usize, MergeMineError> {
}
/// Tree hash algorithm in Monero
pub fn tree_hash(hashes: &[Hash]) -> Result<Hash, MergeMineError> {
#[allow(unused)]
pub fn tree_hash(hashes: &[Hash]) -> Result<Hash> {
if hashes.is_empty() {
return Err(MergeMineError::HashingError(
return Err(Error::MoneroHashingError(
"Cannot calculate Merkle root, no hashes".to_string(),
));
}
@@ -79,7 +92,7 @@ pub fn tree_hash(hashes: &[Hash]) -> Result<Hash, MergeMineError> {
}
if i != hashes.len() {
return Err(MergeMineError::HashingError(
return Err(Error::MoneroHashingError(
"Cannot calculate the Merkle root, hashes not equal to count".to_string(),
));
}
@@ -98,138 +111,11 @@ pub fn tree_hash(hashes: &[Hash]) -> Result<Hash, MergeMineError> {
}
}
/// The Monero Merkle proof
#[derive(Debug, Clone)]
pub struct MerkleProof {
branch: Vec<Hash>,
path_bitmap: u32,
}
impl Encodable for MerkleProof {
fn encode<S: Write>(&self, s: &mut S) -> io::Result<usize> {
let mut n = 0;
let len = self.branch.len() as u8;
n += len.encode(s)?;
for hash in &self.branch {
n += (*hash).consensus_encode(s)?;
}
n += self.path_bitmap.encode(s)?;
Ok(n)
}
}
impl Decodable for MerkleProof {
fn decode<D: Read>(d: &mut D) -> io::Result<Self> {
let len: u8 = d.read_u8()?;
let mut branch = Vec::with_capacity(len as usize);
for _ in 0..len {
branch
.push(Hash::consensus_decode(d).map_err(|_| io::Error::other("Invalid XMR hash"))?);
}
let path_bitmap: u32 = Decodable::decode(d)?;
Ok(Self { branch, path_bitmap })
}
}
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)]
#[allow(unused)]
pub fn create_merkle_proof(hashes: &[Hash], hash: &Hash) -> Option<MerkleProof> {
match hashes.len() {
0 => None,

View File

@@ -336,6 +336,9 @@ pub enum Error {
#[error("Contracts states monotree root missmatch: {0} - {1}")]
ContractsStatesRootError(String, String),
#[error("Hashing of Monero data failed: {0}")]
MoneroHashingError(String),
// ===============
// Database errors
// ===============