Skip to content

zac-williamson/mpclib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mpclib

PLEASE NOTE THIS LIBRARY IS NOT AUDITED. USE AT YOUR OWN RISK

A library that implements useful and simple multiparty computation protocols in Noir

Noir version compatibility

This library is tested to work as of Noir version 1.0.0-beta.3

Benchmarks

Benchmarks are ignored by git and checked on pull-request. As such, benchmarks may be generated with the following command.

# execute the following
./scripts/build-gates-report.sh

The benchmark will be generated at ./gates_report.json.

Installation

In your Nargo.toml file, add the version of this library you would like to install under dependency:

[dependencies]
LIBRARY = { tag = "v0.1.0", git = "https://github.com/noir-lang/LIBRARY_NAME" }

library

Library currently implements two protocols: a selective disclosure protocol and a random shuffle protocol.

Each round of an MPC protocol is intended to be represented via a separate circuit + proof. Recursion is required to wrap multiple MPC rounds into an atomic proof.

selective_disclosure usage

K participants. Each participant has a size-N array of field elements that are encrypted.

The following pseudocode is what the protocol enables, but imagine alice_data, bob_data, alice_mask, bob_mask are encrypted. Bob cannot view alice's data + mask and Alice cannot view bob's data + mask.

// example where K=2
let alice_data: [Field; N];
let bob_data: [Field; N];

let alice_mask: [bool; N];
let bob_mask: [bool; N];

let alice_data_to_bob: [Field];
let bob_data_to_alice; [Field];

for i in 0..N {
    if (bob_mask[i] == true)
    {
        alice_data_to_bob.push(alice_data[i]);
    }
    if (alice_mask[i] == true)
    {
        bob_data_to_alice.push(bob_data[i]);
    }
}

return (alice_data_to_bob, bob_data_to_alice)

An example of selective_disclosure being used in practise:

    let alice_secret = std::hash::poseidon2::Poseidon2::hash([0], 1);
    let alice_mask = std::hash::poseidon2::Poseidon2::hash([1], 1);
    let bob_secret = std::hash::poseidon2::Poseidon2::hash([2], 1);
    let bob_mask = std::hash::poseidon2::Poseidon2::hash([3], 1);

    let round_state: RoundState<4, 2> = RoundState {
        round_number: 1,
        user_encrypt_secret_hashes: [
            poseidon2([alice_secret, -1], 2),
            poseidon2([bob_secret, -1], 2),
        ],
        user_mask_secret_hashes: [poseidon2([alice_mask, -1], 2), poseidon2([bob_mask, -1], 2)],
        previous_output_states: [UserOutputState::default(); 2],
    };

    let previous_round_hash = round_state.hash();
    let alice_state = [0, 1, 2, 3];
    let alice_reveal = [0, 1, 0, 1];
    let bob_state = [-1, -2, -3, -4];
    let bob_reveal = [1, 0, 1, 0];

    // after executing. we have ciphertexts and mask commitments, but no mask reveal commitments
    let alice_output = execute_round(
        alice_state,
        alice_reveal,
        alice_secret,
        alice_mask,
        round_state,
        previous_round_hash,
        0,
    );

    let round_state = round_state.update([alice_output.0, round_state.previous_output_states[1]]);
    let previous_round_hash = round_state.hash();

    let bob_output = execute_round(
        bob_state,
        bob_reveal,
        bob_secret,
        bob_mask,
        round_state,
        previous_round_hash,
        1,
    );

    let round_state = round_state.update([round_state.previous_output_states[0], bob_output.0]);
    let previous_round_hash = round_state.hash();

    let alice_output = execute_round(
        alice_state,
        alice_reveal,
        alice_secret,
        alice_mask,
        round_state,
        previous_round_hash,
        0,
    );

    let round_state = round_state.update([alice_output.0, round_state.previous_output_states[1]]);
    let previous_round_hash = round_state.hash();

    let bob_output = execute_round(
        bob_state,
        bob_reveal,
        bob_secret,
        bob_mask,
        round_state,
        previous_round_hash,
        1,
    );

    let alice_decrypted = alice_output.1[0];
    let bob_decrypted = bob_output.1[0];

    assert_eq(alice_decrypted[0].is_some(), false);
    assert_eq(alice_decrypted[1].is_some(), true);
    assert_eq(alice_decrypted[2].is_some(), false);
    assert_eq(alice_decrypted[3].is_some(), true);

    assert_eq(alice_decrypted[1].unwrap_unchecked(), -2);
    assert_eq(alice_decrypted[3].unwrap_unchecked(), -4);

    assert_eq(bob_decrypted[0].is_some(), true);
    assert_eq(bob_decrypted[1].is_some(), false);
    assert_eq(bob_decrypted[2].is_some(), true);
    assert_eq(bob_decrypted[3].is_some(), false);

    assert_eq(bob_decrypted[0].unwrap_unchecked(), 0);
    assert_eq(bob_decrypted[2].unwrap_unchecked(), 2);

shuffle usage

fn test_poker_5players() {
    let player_secrets: [Field; 5] = [
        std::hash::poseidon2::Poseidon2::hash([8], 1),
        std::hash::poseidon2::Poseidon2::hash([1], 1),
        std::hash::poseidon2::Poseidon2::hash([2], 1),
        std::hash::poseidon2::Poseidon2::hash([3], 1),
        std::hash::poseidon2::Poseidon2::hash([4], 1),
    ];

    let secret_hashes: [Field; 5] = [
        std::hash::poseidon2::Poseidon2::hash([player_secrets[0]], 1),
        std::hash::poseidon2::Poseidon2::hash([player_secrets[1]], 1),
        std::hash::poseidon2::Poseidon2::hash([player_secrets[2]], 1),
        std::hash::poseidon2::Poseidon2::hash([player_secrets[3]], 1),
        std::hash::poseidon2::Poseidon2::hash([player_secrets[4]], 1),
    ];

    let mut shuffle_data: ShuffleData<52, _> = ShuffleData::new(secret_hashes);

    // STEP 1: SHUFFLE CARDS (5 proofs)
    // In MPC setting this would require 5 proofs - one per player secret
    for i in 0..5 {
        shuffle_data = shuffle_data.shuffle(player_secrets[i], i);
    }

    let mut player_card_data: [UnblindContainer<2, 5>; 5] = [
        UnblindContainer::new(0),
        UnblindContainer::new(1),
        UnblindContainer::new(2),
        UnblindContainer::new(3),
        UnblindContainer::new(4),
    ];

    // THE PRE-FLOP
    // PRE-FLOP 1: DEAL CARDS (5 proofs)
    // the outer loop here would, in a real world setting, take place in 5 separate circuits
    for player_index in 0..5 {
        for j in 0..5 {
            if (j != player_index) {
                let (s, b) = deal_and_unblind::<_, _, _>(
                    player_secrets[player_index],
                    shuffle_data,
                    player_card_data[j],
                    player_index,
                );
                shuffle_data = s;
                player_card_data[j] = b;
            }
        }
    }

    // PRE-FLOP 2: UNBLIND CARDS FOR EACH PLAYER (5 proofs)
    let player_indices: [[u32; 2]; 5] = [
        unblind_and_determine_index(player_secrets[0], shuffle_data, player_card_data[0], 0),
        unblind_and_determine_index(player_secrets[1], shuffle_data, player_card_data[1], 1),
        unblind_and_determine_index(player_secrets[2], shuffle_data, player_card_data[2], 2),
        unblind_and_determine_index(player_secrets[3], shuffle_data, player_card_data[3], 3),
        unblind_and_determine_index(player_secrets[4], shuffle_data, player_card_data[4], 4),
    ];
    println(f"player_indices {player_indices}");

    let dealer_idx: u32 = 2; // let's say player 2 is the dealer

    // THE FLOP
    let mut flop_public_card_data: UnblindContainer<3, 5> =
        UnblindContainer::new(dealer_idx as Field);

    // turn 1: players unblind the flop (4 proofs)
    for player_index in 0..5 {
        if (player_index != dealer_idx) {
            let (s, b) = deal_and_unblind::<_, _, _>(
                player_secrets[player_index],
                shuffle_data,
                flop_public_card_data,
                player_index,
            );
            shuffle_data = s;
            flop_public_card_data = b;
        }
    }

    // FLOP 2: DEALER REVEALS FLOP (1 proof)
    let flop_indices: [u32; 3] = unblind_and_determine_index(
        player_secrets[dealer_idx],
        shuffle_data,
        flop_public_card_data,
        dealer_idx,
    );
    println(f"flop_indices {flop_indices}");

    // THE TURN
    let mut turn_public_card_data: UnblindContainer<1, 5> =
        UnblindContainer::new(dealer_idx as Field);
    // turn 1: PLAYERS UNBLIND THE TURN CARD (4 proofs)
    for player_index in 0..5 {
        if (player_index != dealer_idx) {
            let (s, b) = deal_and_unblind::<_, _, _>(
                player_secrets[player_index],
                shuffle_data,
                turn_public_card_data,
                player_index,
            );
            shuffle_data = s;
            turn_public_card_data = b;
        }
    }
    // turn 2: DEALER REVEALS turn (1 proof)
    let turn_indices: [u32; 1] = unblind_and_determine_index(
        player_secrets[dealer_idx],
        shuffle_data,
        turn_public_card_data,
        dealer_idx,
    );
    println(f"turn_indices {turn_indices}");

    // THE RIVER
    let mut turn_public_card_data: UnblindContainer<1, 5> =
        UnblindContainer::new(dealer_idx as Field);
    // turn 1: PLAYERS UNBLIND THE RIVER CARD (4 proofs)
    for player_index in 0..5 {
        if (player_index != dealer_idx) {
            let (s, b) = deal_and_unblind::<_, _, _>(
                player_secrets[player_index],
                shuffle_data,
                turn_public_card_data,
                player_index,
            );
            shuffle_data = s;
            turn_public_card_data = b;
        }
    }
    // turn 2: DEALER REVEALS RIVER (1 proof)
    let river_indices: [u32; 1] = unblind_and_determine_index(
        player_secrets[dealer_idx],
        shuffle_data,
        turn_public_card_data,
        dealer_idx,
    );
    println(f"river_indices {river_indices}");

    let mut uniqueness_check: [bool; 52] = [false; 52];
    for i in 0..5 {
        for j in 0..2 {
            assert_eq(uniqueness_check[player_indices[i][j]], false);
            uniqueness_check[player_indices[i][j]] = true;
        }
    }
    for i in 0..3 {
        assert_eq(uniqueness_check[flop_indices[i]], false);
        uniqueness_check[flop_indices[i]] = true;
    }
    assert_eq(uniqueness_check[turn_indices[0]], false);
    uniqueness_check[turn_indices[0]] = true;
    assert_eq(uniqueness_check[river_indices[0]], false);
    uniqueness_check[river_indices[0]] = true;
}

About

a noir library for multiparty computation primitives

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages