pow.rs
1 /* This file is part of DarkFi (https://dark.fi) 2 * 3 * Copyright (C) 2020-2024 Dyne.org foundation 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU Affero General Public License as 7 * published by the Free Software Foundation, either version 3 of the 8 * License, or (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU Affero General Public License for more details. 14 * 15 * You should have received a copy of the GNU Affero General Public License 16 * along with this program. If not, see <https://www.gnu.org/licenses/>. 17 */ 18 19 use std::{ 20 io::{Error as IoError, Read, Result as IoResult, Write}, 21 sync::Arc, 22 }; 23 24 use rand::rngs::OsRng; 25 use smol::lock::RwLock; 26 use structopt::StructOpt; 27 use tracing::info; 28 use url::Url; 29 30 use darkfi::{system::ExecutorPtr, Error, Result}; 31 use darkfi_sdk::crypto::{Keypair, PublicKey, SecretKey}; 32 use darkfi_serial::{ 33 async_trait, AsyncDecodable, AsyncEncodable, AsyncRead, AsyncWrite, Decodable, Encodable, 34 }; 35 36 use crate::{ 37 bitcoin::{BitcoinBlockHash, BitcoinHashCache}, 38 equix::{Challenge, EquiXBuilder, EquiXPow, Solution, SolverMemory, NONCE_LEN}, 39 }; 40 41 #[derive(Clone, Debug)] 42 pub struct PowSettings { 43 /// Equi-X effort value 44 pub equix_effort: u32, 45 /// Number of latest BTC block hashes that are valid for fud's PoW 46 pub btc_hash_count: usize, 47 /// Electrum nodes timeout in seconds 48 pub btc_timeout: u64, 49 /// Electrum nodes used to fetch the latest block hashes (used in fud's PoW) 50 pub btc_electrum_nodes: Vec<Url>, 51 } 52 53 impl Default for PowSettings { 54 fn default() -> Self { 55 Self { 56 equix_effort: 10000, 57 btc_hash_count: 144, 58 btc_timeout: 15, 59 btc_electrum_nodes: vec![], 60 } 61 } 62 } 63 64 #[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)] 65 #[structopt()] 66 #[serde(rename = "pow")] 67 pub struct PowSettingsOpt { 68 /// Equi-X effort value 69 #[structopt(long)] 70 pub equix_effort: Option<u32>, 71 72 /// Number of latest BTC block hashes that are valid for fud's PoW 73 #[structopt(long)] 74 pub btc_hash_count: Option<usize>, 75 76 /// Electrum nodes timeout in seconds 77 #[structopt(long)] 78 pub btc_timeout: Option<u64>, 79 80 /// Electrum nodes used to fetch the latest block hashes (used in fud's PoW) 81 #[structopt(long, use_delimiter = true)] 82 pub btc_electrum_nodes: Vec<Url>, 83 } 84 85 impl From<PowSettingsOpt> for PowSettings { 86 fn from(opt: PowSettingsOpt) -> Self { 87 let def = PowSettings::default(); 88 89 Self { 90 equix_effort: opt.equix_effort.unwrap_or(def.equix_effort), 91 btc_hash_count: opt.btc_hash_count.unwrap_or(def.btc_hash_count), 92 btc_timeout: opt.btc_timeout.unwrap_or(def.btc_timeout), 93 btc_electrum_nodes: opt.btc_electrum_nodes, 94 } 95 } 96 } 97 98 /// Struct handling a [`EquiXPow`] instance to generate and verify [`VerifiableNodeData`]. 99 pub struct FudPow { 100 pub settings: Arc<RwLock<PowSettings>>, 101 pub bitcoin_hash_cache: BitcoinHashCache, 102 equix_pow: EquiXPow, 103 } 104 impl FudPow { 105 pub fn new(settings: PowSettings, ex: ExecutorPtr) -> Self { 106 let pow_settings: Arc<RwLock<PowSettings>> = Arc::new(RwLock::new(settings)); 107 let bitcoin_hash_cache = BitcoinHashCache::new(pow_settings.clone(), ex.clone()); 108 109 Self { 110 settings: pow_settings, 111 bitcoin_hash_cache, 112 equix_pow: EquiXPow { 113 effort: 0, // will be set when we call `generate_node()` 114 challenge: Challenge::new(&[], &[0u8; NONCE_LEN]), 115 equix: EquiXBuilder::default(), 116 mem: SolverMemory::default(), 117 }, 118 } 119 } 120 121 /// Generate a random keypair and run the PoW to get a [`VerifiableNodeData`]. 122 pub async fn generate_node(&mut self) -> Result<(VerifiableNodeData, SecretKey)> { 123 info!(target: "fud::FudPow::generate_node()", "Generating a new node id..."); 124 125 // Generate a random keypair 126 let keypair = Keypair::random(&mut OsRng); 127 128 // Get a recent Bitcoin block hash 129 let n = 3; 130 let btc_block_hash = { 131 let block_hashes = &self.bitcoin_hash_cache.block_hashes; 132 if block_hashes.is_empty() { 133 return Err(Error::Custom( 134 "Can't generate a node id without BTC block hashes".into(), 135 )); 136 } 137 138 let block_hash = if n > block_hashes.len() { 139 block_hashes.last() 140 } else { 141 block_hashes.get(block_hashes.len() - 1 - n) 142 }; 143 144 if block_hash.is_none() { 145 return Err(Error::Custom("Could not find a recent BTC block hash".into())); 146 } 147 *block_hash.unwrap() 148 }; 149 150 // Update the effort using the value from `self.settings` 151 self.equix_pow.effort = self.settings.read().await.equix_effort; 152 153 // Construct Equi-X challenge 154 self.equix_pow.challenge = Challenge::new( 155 &[keypair.public.to_bytes(), btc_block_hash].concat(), 156 &[0u8; NONCE_LEN], 157 ); 158 159 // Evaluate PoW 160 info!(target: "fud::FudPow::generate_node()", "Equi-X Proof-of-Work starts..."); 161 let solution = 162 self.equix_pow.run().map_err(|e| Error::Custom(format!("Equi-X error: {e}")))?; 163 info!(target: "fud::FudPow::generate_node()", "Equi-X Proof-of-Work is done"); 164 165 // Create the VerifiableNodeData 166 Ok(( 167 VerifiableNodeData { 168 public_key: keypair.public, 169 btc_block_hash, 170 nonce: self.equix_pow.challenge.nonce(), 171 solution, 172 }, 173 keypair.secret, 174 )) 175 } 176 177 /// Check if the Equi-X solution in a [`VerifiableNodeData`] is valid and has enough effort. 178 pub async fn verify_node(&mut self, node_data: &VerifiableNodeData) -> Result<()> { 179 // Update the effort using the value from `self.settings` 180 self.equix_pow.effort = self.settings.read().await.equix_effort; 181 182 // Verify if the Bitcoin block hash is known 183 if !self.bitcoin_hash_cache.block_hashes.contains(&node_data.btc_block_hash) { 184 return Err(Error::Custom( 185 "Error verifying node data: the BTC block hash is unknown".into(), 186 )) 187 } 188 189 // Verify the solution 190 self.equix_pow 191 .verify(&node_data.challenge(), &node_data.solution) 192 .map_err(|e| Error::Custom(format!("Error verifying Equi-X solution: {e}"))) 193 } 194 } 195 196 /// The data needed to verify a fud PoW. 197 #[derive(Debug, Clone)] 198 pub struct VerifiableNodeData { 199 pub public_key: PublicKey, 200 pub btc_block_hash: BitcoinBlockHash, 201 pub nonce: [u8; NONCE_LEN], 202 pub solution: Solution, 203 } 204 205 impl VerifiableNodeData { 206 /// The node id on the DHT. 207 pub fn id(&self) -> blake3::Hash { 208 blake3::hash(&[self.challenge().to_bytes(), self.solution.to_bytes().to_vec()].concat()) 209 } 210 211 /// The Equi-X challenge. 212 pub fn challenge(&self) -> Challenge { 213 Challenge::new(&[self.public_key.to_bytes(), self.btc_block_hash].concat(), &self.nonce) 214 } 215 } 216 217 impl Encodable for VerifiableNodeData { 218 fn encode<S: Write>(&self, s: &mut S) -> IoResult<usize> { 219 let mut len = 0; 220 len += self.public_key.encode(s)?; 221 len += self.btc_block_hash.encode(s)?; 222 len += self.nonce.encode(s)?; 223 len += self.solution.to_bytes().encode(s)?; 224 Ok(len) 225 } 226 } 227 228 #[async_trait] 229 impl AsyncEncodable for VerifiableNodeData { 230 async fn encode_async<S: AsyncWrite + Unpin + Send>(&self, s: &mut S) -> IoResult<usize> { 231 let mut len = 0; 232 len += self.public_key.encode_async(s).await?; 233 len += self.btc_block_hash.encode_async(s).await?; 234 len += self.nonce.encode_async(s).await?; 235 len += self.solution.to_bytes().encode_async(s).await?; 236 Ok(len) 237 } 238 } 239 240 impl Decodable for VerifiableNodeData { 241 fn decode<D: Read>(d: &mut D) -> IoResult<Self> { 242 Ok(Self { 243 public_key: PublicKey::decode(d)?, 244 btc_block_hash: BitcoinBlockHash::decode(d)?, 245 nonce: <[u8; NONCE_LEN]>::decode(d)?, 246 solution: Solution::try_from_bytes(&<[u8; Solution::NUM_BYTES]>::decode(d)?) 247 .map_err(|e| IoError::other(format!("Error parsing Equi-X solution: {e}")))?, 248 }) 249 } 250 } 251 252 #[async_trait] 253 impl AsyncDecodable for VerifiableNodeData { 254 async fn decode_async<D: AsyncRead + Unpin + Send>(d: &mut D) -> IoResult<Self> { 255 Ok(Self { 256 public_key: PublicKey::decode_async(d).await?, 257 btc_block_hash: BitcoinBlockHash::decode_async(d).await?, 258 nonce: <[u8; NONCE_LEN]>::decode_async(d).await?, 259 solution: Solution::try_from_bytes( 260 &<[u8; Solution::NUM_BYTES]>::decode_async(d).await?, 261 ) 262 .map_err(|e| IoError::other(format!("Error parsing Equi-X solution: {e}")))?, 263 }) 264 } 265 }