1use std::{
20 io::{Error as IoError, Read, Result as IoResult, Write},
21 sync::Arc,
22};
23
24use rand::rngs::OsRng;
25use smol::lock::RwLock;
26use structopt::StructOpt;
27use tracing::info;
28use url::Url;
29
30use darkfi::{system::ExecutorPtr, Error, Result};
31use darkfi_sdk::crypto::{Keypair, PublicKey, SecretKey};
32use darkfi_serial::{
33 async_trait, AsyncDecodable, AsyncEncodable, AsyncRead, AsyncWrite, Decodable, Encodable,
34};
35
36use crate::{
37 bitcoin::{BitcoinBlockHash, BitcoinHashCache},
38 equix::{Challenge, EquiXBuilder, EquiXPow, Solution, SolverMemory, NONCE_LEN},
39};
40
41#[derive(Clone, Debug)]
42pub struct PowSettings {
43 pub equix_effort: u32,
45 pub btc_enabled: bool,
47 pub btc_hash_count: usize,
49 pub btc_timeout: u64,
51 pub btc_electrum_nodes: Vec<Url>,
53}
54
55impl Default for PowSettings {
56 fn default() -> Self {
57 Self {
58 equix_effort: 10000,
59 btc_enabled: true,
60 btc_hash_count: 144,
61 btc_timeout: 15,
62 btc_electrum_nodes: vec![],
63 }
64 }
65}
66
67#[derive(Clone, Debug, serde::Deserialize, structopt::StructOpt, structopt_toml::StructOptToml)]
68#[structopt()]
69#[serde(rename = "pow")]
70pub struct PowSettingsOpt {
71 #[structopt(long)]
73 pub equix_effort: Option<u32>,
74
75 #[structopt(long)]
77 pub btc_enabled: Option<bool>,
78
79 #[structopt(long)]
81 pub btc_hash_count: Option<usize>,
82
83 #[structopt(long)]
85 pub btc_timeout: Option<u64>,
86
87 #[structopt(long, use_delimiter = true)]
89 pub btc_electrum_nodes: Vec<Url>,
90}
91
92impl From<PowSettingsOpt> for PowSettings {
93 fn from(opt: PowSettingsOpt) -> Self {
94 let def = PowSettings::default();
95
96 Self {
97 equix_effort: opt.equix_effort.unwrap_or(def.equix_effort),
98 btc_enabled: opt.btc_enabled.unwrap_or(def.btc_enabled),
99 btc_hash_count: opt.btc_hash_count.unwrap_or(def.btc_hash_count),
100 btc_timeout: opt.btc_timeout.unwrap_or(def.btc_timeout),
101 btc_electrum_nodes: opt.btc_electrum_nodes,
102 }
103 }
104}
105
106pub struct FudPow {
108 pub settings: Arc<RwLock<PowSettings>>,
109 pub bitcoin_hash_cache: BitcoinHashCache,
110 equix_pow: EquiXPow,
111}
112impl FudPow {
113 pub fn new(settings: PowSettings, ex: ExecutorPtr) -> Self {
114 let pow_settings: Arc<RwLock<PowSettings>> = Arc::new(RwLock::new(settings));
115 let bitcoin_hash_cache = BitcoinHashCache::new(pow_settings.clone(), ex.clone());
116
117 Self {
118 settings: pow_settings,
119 bitcoin_hash_cache,
120 equix_pow: EquiXPow {
121 effort: 0, challenge: Challenge::new(&[], &[0u8; NONCE_LEN]),
123 equix: EquiXBuilder::default(),
124 mem: SolverMemory::default(),
125 },
126 }
127 }
128
129 pub async fn generate_node(&mut self) -> Result<(VerifiableNodeData, SecretKey)> {
131 info!(target: "fud::FudPow::generate_node()", "Generating a new node id...");
132
133 let keypair = Keypair::random(&mut OsRng);
135
136 let n = 3;
138 let btc_block_hash = if !self.settings.read().await.btc_enabled {
139 [0; 32]
140 } else {
141 let block_hashes = &self.bitcoin_hash_cache.block_hashes;
142 if block_hashes.is_empty() {
143 return Err(Error::Custom(
144 "Can't generate a node id without BTC block hashes".into(),
145 ));
146 }
147
148 let block_hash = if n > block_hashes.len() {
149 block_hashes.last()
150 } else {
151 block_hashes.get(block_hashes.len() - 1 - n)
152 };
153
154 if block_hash.is_none() {
155 return Err(Error::Custom("Could not find a recent BTC block hash".into()));
156 }
157 *block_hash.unwrap()
158 };
159
160 self.equix_pow.effort = self.settings.read().await.equix_effort;
162
163 self.equix_pow.challenge = Challenge::new(
165 &[keypair.public.to_bytes(), btc_block_hash].concat(),
166 &[0u8; NONCE_LEN],
167 );
168
169 info!(target: "fud::FudPow::generate_node()", "Equi-X Proof-of-Work starts...");
171 let solution =
172 self.equix_pow.run().map_err(|e| Error::Custom(format!("Equi-X error: {e}")))?;
173 info!(target: "fud::FudPow::generate_node()", "Equi-X Proof-of-Work is done");
174
175 Ok((
177 VerifiableNodeData {
178 public_key: keypair.public,
179 btc_block_hash,
180 nonce: self.equix_pow.challenge.nonce(),
181 solution,
182 },
183 keypair.secret,
184 ))
185 }
186
187 pub async fn verify_node(&mut self, node_data: &VerifiableNodeData) -> Result<()> {
189 let settings = self.settings.read().await;
190 self.equix_pow.effort = settings.equix_effort;
192
193 if settings.btc_enabled &&
195 !self.bitcoin_hash_cache.block_hashes.contains(&node_data.btc_block_hash)
196 {
197 return Err(Error::Custom(
198 "Error verifying node data: the BTC block hash is unknown".into(),
199 ))
200 }
201
202 self.equix_pow
204 .verify(&node_data.challenge(), &node_data.solution)
205 .map_err(|e| Error::Custom(format!("Error verifying Equi-X solution: {e}")))
206 }
207}
208
209#[derive(Debug, Clone)]
211pub struct VerifiableNodeData {
212 pub public_key: PublicKey,
213 pub btc_block_hash: BitcoinBlockHash,
214 pub nonce: [u8; NONCE_LEN],
215 pub solution: Solution,
216}
217
218impl VerifiableNodeData {
219 pub fn id(&self) -> blake3::Hash {
221 blake3::hash(&[self.challenge().to_bytes(), self.solution.to_bytes().to_vec()].concat())
222 }
223
224 pub fn challenge(&self) -> Challenge {
226 Challenge::new(&[self.public_key.to_bytes(), self.btc_block_hash].concat(), &self.nonce)
227 }
228}
229
230impl Encodable for VerifiableNodeData {
231 fn encode<S: Write>(&self, s: &mut S) -> IoResult<usize> {
232 let mut len = 0;
233 len += self.public_key.encode(s)?;
234 len += self.btc_block_hash.encode(s)?;
235 len += self.nonce.encode(s)?;
236 len += self.solution.to_bytes().encode(s)?;
237 Ok(len)
238 }
239}
240
241#[async_trait]
242impl AsyncEncodable for VerifiableNodeData {
243 async fn encode_async<S: AsyncWrite + Unpin + Send>(&self, s: &mut S) -> IoResult<usize> {
244 let mut len = 0;
245 len += self.public_key.encode_async(s).await?;
246 len += self.btc_block_hash.encode_async(s).await?;
247 len += self.nonce.encode_async(s).await?;
248 len += self.solution.to_bytes().encode_async(s).await?;
249 Ok(len)
250 }
251}
252
253impl Decodable for VerifiableNodeData {
254 fn decode<D: Read>(d: &mut D) -> IoResult<Self> {
255 Ok(Self {
256 public_key: PublicKey::decode(d)?,
257 btc_block_hash: BitcoinBlockHash::decode(d)?,
258 nonce: <[u8; NONCE_LEN]>::decode(d)?,
259 solution: Solution::try_from_bytes(&<[u8; Solution::NUM_BYTES]>::decode(d)?)
260 .map_err(|e| IoError::other(format!("Error parsing Equi-X solution: {e}")))?,
261 })
262 }
263}
264
265#[async_trait]
266impl AsyncDecodable for VerifiableNodeData {
267 async fn decode_async<D: AsyncRead + Unpin + Send>(d: &mut D) -> IoResult<Self> {
268 Ok(Self {
269 public_key: PublicKey::decode_async(d).await?,
270 btc_block_hash: BitcoinBlockHash::decode_async(d).await?,
271 nonce: <[u8; NONCE_LEN]>::decode_async(d).await?,
272 solution: Solution::try_from_bytes(
273 &<[u8; Solution::NUM_BYTES]>::decode_async(d).await?,
274 )
275 .map_err(|e| IoError::other(format!("Error parsing Equi-X solution: {e}")))?,
276 })
277 }
278}