fud/
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
19use std::{
20    io::{Error as IoError, Read, Result as IoResult, Write},
21    sync::Arc,
22};
23
24use log::info;
25use rand::rngs::OsRng;
26use smol::lock::RwLock;
27use structopt::StructOpt;
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    /// 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
53impl 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")]
67pub 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
85impl 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`].
99pub struct FudPow {
100    pub settings: Arc<RwLock<PowSettings>>,
101    pub bitcoin_hash_cache: BitcoinHashCache,
102    equix_pow: EquiXPow,
103}
104impl 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)]
198pub 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
205impl 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
217impl 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]
229impl 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
240impl 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]
253impl 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}