Example: Multithreading: Cryptocurrency mining
This example shows you how you can solve real world cryptographic problems inside EDP enclaves without losing performance benefits of a multi-threaded solution.
This example, verbatim taken from mpsc-crypto-mining uses std::sync::mpsc
with multiple threads of execution.
The computation-heavy algorithm used in this example is a basic implementation of "Proof of Work", a concept behind the mining process for many cryptocurrencies.
This example runs unchanged in an enclave when built with EDP.
Threading configuration
For SGX, the maximum number of threads has to be configured while converting the ELF to SGXS format.
During the conversion process, memory is allocated for the stack and local storage of each of the threads mentioned.
If you are testing this example using ftxsgx-runner-cargo
, you can override the default conversion parameters like number of threads by adding package metadata in your Cargo.toml
as follows:
1
2
[package.metadata.fortanix-sgx]
threads=5
Example code
The code below shows how a multi-threaded solution runs unchanged for EDP enclaves.
This example iteratively solves a cryptographic problem of finding a number x such that Sha256::hash(42 * x)
ends in 6 zeros.
To improve the search time, it spawns 4 producer threads which search for the solution as follows:
- Thread 0 will start at number 0 and check 0, 4, 8, 12 and so on.
- Thread 1 will start at number 1 and check 1, 5, 9, 13 and so on.
- Thread 2 will start at number 2 and check 2, 6, 10, 14 and so on.
- Thread 3 will start at number 3 and check 3, 7, 11, 15 and so on.
Verifying if a number solves the cryptographic problem
The snippet below shows the use of the easy_hash
crate to check if a given number solves the cryptographic problem or not.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use easy_hash::{Sha256, Hasher, HashResult};
const BASE: usize = 42;
static DIFFICULTY: &'static str = "000000";
struct Solution(usize, String);
fn verify_number(number: usize) -> Option<Solution> {
let hash: String = Sha256::hash((number * BASE).to_string()
.as_bytes()).hex();
if hash.ends_with(DIFFICULTY) {
Some(Solution(number, hash))
} else {
None
}
}
Producer thread handler
Below is the handler routine for a producer thread which iterates over its search space marked by its start_at
.
If it finds a solution it send
s the solution over the mpsc
channel and exits. If it does not, after every 1000
iterations it checks if other threads have found a solution and exits if they have.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
fn search_for_solution(start_at: usize, sender: mpsc::Sender<Solution>,
is_solution_found: Arc<AtomicBool>) {
let mut iteration_no = 0;
for number in (start_at..).step_by(THREADS) {
if let Some(solution) = verify_number(number) {
is_solution_found.store(true, Ordering::Relaxed);
match sender.send(solution) {
Ok(_) => {},
Err(_) => println!(
"Receiver has stopped listening, dropping "
"worker number {}.",
start_at),
}
return;
} else if iteration_no % 1000 == 0 && is_solution_found.load(
Ordering::Relaxed) {
return;
}
iteration_no += 1;
}
}
main function
The main function creates an mpsc
channel and spawns 4 producers and receive
s a solution from one of them.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
use std::thread;
use std::sync::{mpsc, Arc};
use std::sync::atomic::{AtomicBool, Ordering};
const THREADS: usize = 4;
fn main() {
println!("Attempting to find a number, which - while multiplied by "
"{} and hashed using SHA-256 - will result in a hash ending "
"with {}. \n Please wait...", BASE, DIFFICULTY);
let is_solution_found = Arc::new(AtomicBool::new(false));
let (sender, receiver) = mpsc::channel();
for i in 0..THREADS {
let sender_n = sender.clone();
let is_solution_found = is_solution_found.clone();
thread::spawn(move || {
search_for_solution(i, sender_n, is_solution_found);
});
}
match receiver.recv() {
Ok(Solution(i, hash)) => {
println!("Found the solution.");
println!("The number is: {}.", i);
println!("Result hash: {}.", hash);
},
Err(_) => panic!(
"Worker threads disconnected before the solution was found!"),
}
}
The complete code is available on GitHub