Skip to content

Inconsistent results for local and collaborative LowMC evaluations. #53

@CBackyx

Description

@CBackyx

Hello Rindal,

Thanks a lot for your great job in developing this library. It really helps me a lot!

I found that there is no unit test comparing the results of evaluating LowMC locally and evaluating the LowMC circuit jointly by three parties. So I just composed a minimal unit test to compare these two cases, in which I set the message to be encrypted as 0 and set all the round keys to 0 for simplicity.

However, I found that their encryption results are inconsistent. I wonder if this is to be expected or if I misunderstood the functionality/usage of the LowMC circuit. I would appreciate it a lot if you could answer this question.

Here is the unit test I wrote. It should compile if you append it to aby3-DB_tests/lowMC.cpp and include some necessary headers.

#include "aby3-DB/LowMC.h"
#include <iostream>
#include <cryptoTools/Common/Timer.h>
#include <cryptoTools/Common/TestCollection.h>
#include <cstdio>

#include "aby3/sh3/Sh3Encryptor.h"
#include "aby3/sh3/Sh3BinaryEvaluator.h"
#include "aby3/Circuit/CircuitLibrary.h"
#include "cryptoTools/Network/IOService.h"
#include "cryptoTools/Common/Log.h"
#include "cryptoTools/Crypto/PRNG.h"

#include <cryptoTools/Circuit/BetaLibrary.h>
#include <iomanip>
#include <atomic>
#include <vector>
#include <string>
#include <random>

std::vector<u64> bitsetToVector(const std::bitset<256>& myBitset) {
    std::vector<uint64_t> result;
    for (std::size_t i = 0; i < 256; i += 64) {
        uint64_t value = 0;
        for (std::size_t j = 0; j < 64; ++j) {
            value |= (myBitset[i + j] << j);
        }
        result.push_back(value);
    }

    return result;
}

void lowMC_CircuitEval_Simplest_test() {
    // >>>>> Eval locally
    LowMC2<> cipher2(false, 1);
    LowMC2<>::block m = 0;
    for (u64 i = 0; i < 13; ++i) cipher2.roundkeys[i] = 0;
    auto c2 = cipher2.encrypt(m);
    std::vector<u64> vec = bitsetToVector(c2);
    printf("Eval locally: ");
    for (u64 i = 0; i < 4; ++i) printf("%lu ", vec[i]);
    printf("\n");

    // >>>>> Eval jointly
    IOService ios;
    Session s01(ios, "127.0.0.1", SessionMode::Server, "01");
    Session s10(ios, "127.0.0.1", SessionMode::Client, "01");
    Session s02(ios, "127.0.0.1", SessionMode::Server, "02");
    Session s20(ios, "127.0.0.1", SessionMode::Client, "02");
    Session s12(ios, "127.0.0.1", SessionMode::Server, "12");
    Session s21(ios, "127.0.0.1", SessionMode::Client, "12");

    Channel chl01 = s01.addChannel("c");
    Channel chl10 = s10.addChannel("c");
    Channel chl02 = s02.addChannel("c");
    Channel chl20 = s20.addChannel("c");
    Channel chl12 = s12.addChannel("c");
    Channel chl21 = s21.addChannel("c");

    CommPkg comms[3], debugComm[3];
    comms[0] = { chl02, chl01 };
    comms[1] = { chl10, chl12 };
    comms[2] = { chl21, chl20 };

    BetaCircuit lowMCCir;

    LowMC2<> cipher1(false, 1);
    for (u64 i = 0; i < 13; ++i) cipher1.roundkeys[i] = 0;
    cipher1.to_enc_circuit(lowMCCir);
    lowMCCir.levelByAndDepth();

    u64 rounds = lowMCCir.mInputs.size() - 1;
    u64 blockSize = 256;
    u64 wordSize = blockSize / 64;

    std::vector<u64> serialized = {0, 0, 0, 0}; // The message to be encrypted
    u64 width = 1;
    i64Matrix kv(width, wordSize);
    std::vector<i64Matrix> keys(rounds);
    for (u64 i = 0; i < rounds; ++i) {
        keys[i].resize(1, wordSize);
        for (u64 j = 0; j < wordSize; ++j) keys[i](0, j) = 0; // The round keys
    }
    for (u64 i = 0; i < serialized.size(); ++i) {
        kv(i / wordSize, i % wordSize) = serialized[i];
    }

    bool success = true;

    auto routine = [&](int pIdx) {
        Sh3Runtime rt(pIdx, comms[pIdx]);
        Sh3Encryptor enc;
        enc.init(pIdx, toBlock(pIdx), toBlock((pIdx + 1) % 3));
        Sh3BinaryEvaluator eval;
        eval.mPrng.SetSeed(toBlock(pIdx));
        Sh3ShareGen gen;
        gen.init(toBlock(pIdx), toBlock((pIdx + 1) % 3));

        // Load the input to secret form
        sPackedBin KV(width, blockSize);
        sPackedBin encKV(width, blockSize);
        std::vector<sPackedBin> Keys(rounds);
        for (u64 i = 0; i < rounds; ++i) {
            Keys[i].reset(1, blockSize);
        }        

        auto task = rt.noDependencies();
        if (pIdx == 0) {
            task = enc.localPackedBinary(task, kv, KV);
        } else {
            task = enc.remotePackedBinary(task, KV);
        }
        if (pIdx == 0) {
            for (u64 i = 0; i < rounds; ++i) {
                task = enc.localPackedBinary(task, keys[i], Keys[i]);
            }
        } else {
            for (u64 i = 0; i < rounds; ++i) {
                task = enc.remotePackedBinary(task, Keys[i]);
            }            
        }
        task.get();

        // Eval the circuit and get the result
        eval.setCir(&lowMCCir, width, gen);
        eval.setInput(0, KV);
        for (u64 i = 0; i < rounds; ++i) {
            eval.setInput(i + 1, Keys[i]);
        }
        eval.asyncEvaluate(rt.noDependencies()).get();
        eval.getOutput(0, encKV);
        i64Matrix enckv(width, wordSize);
        enc.revealAll(rt.noDependencies(), encKV, enckv).get();

        // Compare the result
        if (pIdx == 0) {
            printf("Eval jointly: ");
            for (u64 i = 0; i < wordSize; ++i) printf("%lu ", enckv(0, i));
            printf("\n");
            for (u64 i = 0; i < wordSize; ++i) {
                if (vec[i] != enckv(0, i)) {
                    success = false;
                    break;
                }
            }
        }
    };

    auto t0 = std::thread(routine, 0);
    auto t1 = std::thread(routine, 1);
    auto t2 = std::thread(routine, 2);

    t0.join();
    t1.join();
    t2.join();    

    if (!success) {
        throw UnitTestFail();
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions