Join our public slack channel for support, discussions and more...
Contents

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:

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 sends 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 receives 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

Contents