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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! This example shows how to use the high-level Secret api to create a circuit and then
//! execute it with the messages being exchanged via Tcp.
//! Run the example via `cargo run --example`.
//!
//! To view the logging output, set the environment variable `RUST_LOG` as specified
//! [here](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/struct.EnvFilter.html).
use std::time::Duration;

use anyhow::Result;
use bitvec::bitvec;
use bitvec::prelude::*;
use tokio::time::sleep;
use tracing_subscriber::EnvFilter;

use seec::channel::sub_channels_for;
use seec::circuit::{dyn_layers::Circuit, DefaultIdx, ExecutableCircuit};
use seec::executor::{Executor, Input, Message};
use seec::mul_triple::boolean::insecure_provider::InsecureMTProvider;
use seec::mul_triple::MTProvider;
use seec::protocols::boolean_gmw::BooleanGmw;
use seec::secret::inputs;
use seec::CircuitBuilder;

fn build_circuit() {
    // The `inputs` method is a convenience method to create n input gates for the circuit.
    // It returns a Vec<Secret>. In the following, we use try_into() to convert it into
    // an array to destructure it
    let [a, b, c, d]: [_; 4] = inputs::<DefaultIdx>(4).try_into().unwrap();
    // a,b,c,d are `Secret`s representing the output share of a gate. They support
    // the standard std::ops traits like BitAnd and BitXor (and their Assign variants) which
    // are used to implicitly build the circuit.

    // Creates a new Xor gate with the input of a and b. The output is a new Secret
    // representing the output of the new gate.
    let xor = a ^ b;
    // To use a Secret multiple times (connect to gate represented by it to multiple
    // different ones), simply use a reference to it (only possibile on the rhs).
    let and = c & &d;
    // we can still use d but not c
    let mut tmp = and ^ d;
    // BitAnd and BitXor are also supported, as is Not. See the Secret documentation for
    // all operations.
    tmp &= !xor;
    // `output()` consumes the Secret and creates a new Output gate with its output.
    // It returns the gate_id of the Output gate (this is usually not needed).
    let _out_gate_id = tmp.output();
}

#[tracing::instrument(skip(circuit), err)]
async fn party(circuit: Circuit, party_id: usize) -> Result<bool> {
    // Create a new insecure MTProvider. This will simply provide both parties with multiplication
    // triples consisting of zeros. The sha256 and trusted_party_mts examples show how to use
    // a trusted provider. This example also shows how to use dynamic dispatch for the MTProvider.
    let mt_provider: Box<dyn MTProvider<Output = _, Error = _> + Send> =
        InsecureMTProvider::default().into_dyn();
    // Create a new Executor for the circuit. It's important that the party_id is either 0 or 1,
    // as otherwise wrong results might be computed. In the future, there might be support for more
    // than two parties
    let exec_circuit = ExecutableCircuit::DynLayers(circuit);
    let mut executor = Executor::<BooleanGmw, _>::new(&exec_circuit, party_id, mt_provider).await?;

    // Create inputs as a BitVector. The inputs will be used for the input gate with the
    // corresponding index.
    let inputs = bitvec![usize, Lsb0; 0, 1, 1, 0];
    // `circuit.input_count()` input count can be used to determine how big the input should be
    assert_eq!(exec_circuit.input_count(), inputs.len());

    // When using the Tcp transport, one party is essentially a server and needs to `listen` for
    // new connections. The other party then `connect`s to it. If party 1 connects before party 0
    // listens, an error will be returned.
    let (mut sender, _bytes_written, mut receiver, _bytes_read) = match party_id {
        0 => seec_channel::tcp::listen("127.0.0.1:7766").await?,
        1 => seec_channel::tcp::connect("127.0.0.1:7766").await?,
        illegal => anyhow::bail!("Illegal party id {illegal}. Must be 0 or 1."),
    };

    let (mut sender, mut receiver) =
        sub_channels_for!(&mut sender, &mut receiver, 8, Message<BooleanGmw>).await?;
    // Execute the circuit and await its result (in the form of a BitVec)
    let output = executor
        .execute(Input::Scalar(inputs), &mut sender, &mut receiver)
        .await?
        .into_scalar()
        .unwrap();

    assert_eq!(exec_circuit.output_count(), output.len());
    // As there is only one output gate, we simply return the first element
    Ok(output[0])
}

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging, see top of file for instructions on how to get output.
    tracing_subscriber::fmt()
        .with_env_filter(EnvFilter::from_default_env())
        .init();

    // Create the Circuit. The operations on the Secret will use a lazily initialized
    // global CircuitBuilder from which we can get the constructed Circuit
    build_circuit();
    // Save the circuit in .dot format for easy inspection and debugging
    // circuit.lock().save_dot("examples/simple-circuit.dot")?;
    // In an actual setting, we would have two parties on different hosts, each constructing the
    // same circuit and evaluating it. To simulate that, we convert the shared Circuit into
    // an owned Circuit and clone it. The conversion will fail if there are any outstanding clones
    // of the SharedCircuit, e.g. when there are Secrets still alive.
    // Each party has their own but identical circuit.
    let circuit_party_0: Circuit = CircuitBuilder::global_into_circuit();
    let circuit_party_1 = circuit_party_0.clone();
    // Spawn separate tasks for each party
    let party0 = tokio::spawn(async { party(circuit_party_0, 0).await.unwrap() });
    // Sleep a little to ensure the server is started. In practice party 1 should retry the
    // connection
    sleep(Duration::from_millis(100)).await;
    let party1 = tokio::spawn(async { party(circuit_party_1, 1).await.unwrap() });
    // Join the handles returned by tokio::spawn to wait for the completion of the protocol
    let (out_0, out_1) = tokio::try_join!(party0, party1)?;
    // Xor the individual shares to get the final output
    let out = out_0 ^ out_1;
    println!("Output of the circuit: {out}");
    Ok(())
}