Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subfield VOLE #127

Merged
merged 23 commits into from
Jan 23, 2024
Merged

Subfield VOLE #127

merged 23 commits into from
Jan 23, 2024

Conversation

lzjluzijie
Copy link
Contributor

@lzjluzijie lzjluzijie commented Oct 16, 2023

Implements #122

  • Noisy VOLE
  • PPRF
  • Coding
  • Silent VOLE
  • u128 is done
  • array<u32, 4>
  • any integer array
  • u64: cannot use SilentBaseType::BaseExtend
  • real fields?
  • better TypeTrait
  • clean up
  • Malicious
  • Merge new pprf
  • Unify implementations (implement non-template pprf,vole,ot etc using template)

@lzjluzijie
Copy link
Contributor Author

lzjluzijie commented Oct 17, 2023

I made the test pass with interleaved mode and "field" u128. I didn't find convenient prime field implementation so I kept using u128, I think it should be similar.

As for the current code, I split the getLevel to intermediate levels and the last level, and some other changes as well. The code is still WIP and very messy and not ready for review yet. I will do some clean up after everything is done.

@ladnir Could you take a look at the current PPRF implementation and the subfield test if you have time(mainly if my tests make sense)?

For the coding part, I felt it would take some time to change LinearCode or QuasiCyclicCode to work with any template field. I think I can try QuasiCyclicCode with block replaced by u128, since there are the same size, to verify the idea first?

Copy link
Member

@ladnir ladnir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a great start

libOTe/Vole/Subfield/NoisyVoleReceiver.h Outdated Show resolved Hide resolved
libOTe/Vole/Subfield/NoisyVoleReceiver.h Outdated Show resolved Hide resolved
z[j] = z[j] + buf[j];

F twoPowI = 0;
*BitIterator((u8*)&twoPowI, ii) = 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, you are assuming this is the correct thing. More generally there should be a function that the user can give you for doing this. Ideally this should work when the input isn't a POD datatype. For now this is ok.

// }
// };

using u128 = unsigned __int128;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not portable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I use this is if I use the class version commented above, I would get error

In file included from /home/halulu/dev/libOTe/out/install/linux/include/coproto/Socket/Socket.h:20,
                 from /home/halulu/dev/libOTe/out/install/linux/include/coproto/coproto.h:11,
                 from /home/halulu/dev/libOTe/libOTe_Tests/../libOTe/Tools/Coproto.h:9,
                 from /home/halulu/dev/libOTe/libOTe_Tests/../libOTe/Tools/SilentPprf.h:20,
                 from /home/halulu/dev/libOTe/libOTe_Tests/SilentOT_Tests.cpp:3:
/home/halulu/dev/libOTe/out/install/linux/include/coproto/Proto/Buffers.h: In instantiation of ‘macoro::coroutine_handle<void> coproto::internal::MoveSendProto<Container>::await_suspend(const H&) [with H = macoro::coroutine_handle<macoro::detail::task_promise<void, true> >; Container = std::vector<std::array<u128, 4>, std::allocator<std::array<u128, 4> > >]’:
/home/halulu/dev/libOTe/out/install/linux/include/macoro/coro_frame.h:152:29:   required from ‘auto macoro::await_suspend(Awaiter&, coroutine_handle<T>, enable_if_t<((! has_void_await_suspend<Awaiter, coroutine_handle<T> >::value) && (! has_bool_
await_suspend<Awaiter, coroutine_handle<T> >::value)), empty_state>) [with Awaiter = coproto::internal::MoveSendProto<std::vector<std::array<u128, 4>, std::allocator<std::array<u128, 4> > > >; T = detail::task_promise<void, true>; enable_if_t<((! has_void_await_suspend<Awaiter, coroutine_handle<T> >::value) && (! has_bool_await_suspend<Awaiter, coroutine_handle<T> >::value)), empty_state> = empty_state]’
/home/halulu/dev/libOTe/libOTe_Tests/../libOTe/Tools/SubfieldPprf.h:621:25:   required from ‘macoro::task<void> osuCrypto::SilentSubfieldPprfSender<G, F>::Expander::run() [with G = u128; F = u128]’
/home/halulu/dev/libOTe/libOTe_Tests/../libOTe/Tools/SubfieldPprf.h:300:75:   required from ‘macoro::task<void> osuCrypto::SilentSubfieldPprfSender<G, F>::expand(osuCrypto::Socket&, osuCrypto::span<const T>, osuCrypto::PRNG&, osuCrypto::MatrixView<F>, osuCrypto::PprfOutputFormat, bool, osuCrypto::u64) [with G = u128; F = u128; osuCrypto::Socket = coproto::Socket; osuCrypto::span<const T> = nonstd::span_lite::span<const u128, 18446744073709551615>; osuCrypto::u64 = long unsigned int]’    
/home/halulu/dev/libOTe/libOTe_Tests/SilentOT_Tests.cpp:1104:28:   required from here
/home/halulu/dev/libOTe/out/install/linux/include/coproto/Proto/Buffers.h:260:53: error: no matching function for call to ‘asSpan(std::vector<std::array<u128, 4>, std::allocator<std::array<u128, 4> > >&)’
  260 |                                 if (internal::asSpan(mContainer).size() == 0)
      |                                     ~~~~~~~~~~~~~~~~^~~~~~~~~~~~
compilation terminated due to -Wfatal-errors.

It seems chl cannot send it directly, how should I fix this?

Copy link
Member

@ladnir ladnir Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so if you send data, then the thing T you're sending either needs to be std::is_trivial<T>::value == true or you can send something that looks like a vector of trivials things, e.g. std::vector<T> where T is trivial. Specifically, when you pass something to coproto, it checks coproto::is_trivial_container<Container>::value == true where thats define at https://github.com/Visa-Research/coproto/blob/main/coproto/Common/TypeTraits.h#L160.

If this trait is true for your type, then that function internal::asSpan(mContainer) exists here https://github.com/Visa-Research/coproto/blob/main/coproto/Proto/Operation.h#L122

Maybe I should make this error message a bit more descriptive ;)

libOTe/Tools/SubfieldPprf.h Outdated Show resolved Hide resolved
libOTe/Tools/SubfieldPprf.h Outdated Show resolved Hide resolved
// where each half defines one of the children.
aes[keep].hashBlocks<8>(parent.data(), child.data());

if (d < pprf.mDepth - 1) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a nested if statement like this might have an impact on performance. Could be work checking

@ladnir
Copy link
Member

ladnir commented Oct 18, 2023

do not use QuasiCyclicCode. We can just use ExConv. When you are ready I can give some pointers on how.

@ladnir
Copy link
Member

ladnir commented Oct 18, 2023

As mentioned in the comments, It best to thing of F,G as some arbitrary type that you can assume nothing about. Whenever you need some functionality out of them, you should achieve this by a function call.

I would suggest changing the type level template to be some FieldTraits which has subtypes FieldTraits::F,FieldTraits::G. Whenever you want some functionality, you can call the function FieldTraits::functionality(...).

@lzjluzijie
Copy link
Contributor Author

Thank you for your review, for ExConv, I should read https://eprint.iacr.org/2023/882 ?

@ladnir
Copy link
Member

ladnir commented Oct 18, 2023

yeah, likely would be helpful. You don't need to understand the proof but the basic idea. So the intro and the start of section 5

@lzjluzijie
Copy link
Contributor Author

I wonder if we should define some virtual class Field that supports operator+, operator*, and fromBlock? I am not expert in cpp. I would implement this "interface" style if in other languages, but is this a good choice in cpp(might be slow than template)?

@ladnir
Copy link
Member

ladnir commented Oct 18, 2023

virtual would be the natural way of doing it but it has very bad performance.

The cost of a function call is orders of magnitude higher than say +. C++ often allows you to avoid this cost by having the compiler inline the function, essentially copy pastes the function body into the call sight so there is no function call. However, this can not work for virtual functions because the compiler does not know which "virtual" instances is currently being used.

You can still achieve this same type of "virtual" behavior with templates. In fact, templates are basically virtual functions where you are additionally telling the compiler exactly which type is being used.

So concretely, my suggestion is the following:

  • The caller will be responsible for defining a type traits class
struct TypeTrait
{
    using F = u128;
    using G = u8;

    F sampleF(PRNG& prng);
    F fromBlock(const block& blk);
    F getPowerOfTwo(u64 power);
    ...
};
  • each of the subfield classes will take a TypeTrait as a template parameter. You can then define everything else in terms of that.
  • TypeTrait will essentially form the "virtual" class that other languages use. However, the caller can define their TypeTrait to have these functions be inline and therefore the overhead of the function calls will disappear.
  • The caller is responsible for correctly implementing this "interface". There is no "base class" to inherit from. The caller is just expected to know what function to implement based on the documentation. If a function is missing, then the code doesn't compile.

make sense?

An example of this pattern can be found here. https://github.com/Visa-Research/volepsi/blob/main/volePSI/PxUtil.h#L550 In this case I called it Helper instead of TypeTraits and it has some other abstractions. But the same basic idea. It was used to allow the caller to call the OKVS for VOLE-PSI with basically any type as long as they provided the Helper. In particular, they could even call it when the "element", in out case F was some variable vector.

@lzjluzijie
Copy link
Contributor Author

lzjluzijie commented Oct 19, 2023

Update: I fixed some bugs in PPRF, and the u128 silent VOLE is working if comment out the final coding part. The code part is still not working, and I will continue working on it tomorrow. The code seems a little complicated, but I think it might work by changing some ^ to +. No need to review code at this time, but you could take a look at ExConvCode_encode_u128_test if my test make sense if you have time. Thank you.

I realized that in the ExConvCode_encode_basic_test test there exists code.dualEncode2<block, u8>(cc1, cc2);. Could you explain why there are dualEncode2 (for performance)? I looks like that code is already well templated, but my current test does not work. Does it make sense to change ^ to +?

Btw the ExConvCode_encode_basic_test got segmentation fault on my end when build mode is DEBUG, but was OK in RELEASE mode. I will look into it later.

@ladnir
Copy link
Member

ladnir commented Oct 19, 2023

That's right, basically just need to use plus. Encode2 is for performance. It just encodes twice. You can just call the single in code twice if you want.

@lzjluzijie
Copy link
Contributor Author

I got silent VOLE test passed with F=G=u128. The ExConvCode is indeed well templated and I only changed a few ^ to +. I will test it with other fields later.

As for make this more generic, I wonder why the current block does not support operator*, and operator+ seems adding left and right parts independently as u64. Shall we change it to behave like GF(2^128) elements, or we define some class F128: block and over write the operators?

@ladnir
Copy link
Member

ladnir commented Oct 20, 2023

block is defined that way due to its primary purpose being an abstract on the __m128i built in type. This type models special cpu instructions (aka intriniscs). It does not have 128 bit addition or multiple, so block doesn't. There is 2x 64bit addition as you mention. Maybe I shouldn't have overloaded operator + since it's a bit confusing. But I did so oh well ha.

@ladnir
Copy link
Member

ladnir commented Oct 20, 2023

As suggested above, we shouldn't even use operator+ or operator^. Instead there should be a type traits object that handles all the operations for you. You simply passing in the arguments. But this can happen at then end once the basic version is working

@lzjluzijie
Copy link
Contributor Author

lzjluzijie commented Oct 20, 2023

So we should do something like

struct TypeTrait
{
    using F = u128;
    using G = u8;
    ...
    static inline F Add(const F& a, const F& b)
    ...
};

and I was suggesting

struct F128: block {
  inline F128 operator+(const F128& rhs) const {
    ...
  }
};

struct TypeTrait
{
    using F = F128;
    using G = F128;
    ...
};

(not using virtual). Is there performance difference?

It seems there is not much difference, but I agree it's safe to use a TypeTrait with explict Add functions.

@ladnir
Copy link
Member

ladnir commented Oct 20, 2023

Yes, it will be about 10x faster. Maybe in some cases the compiler might be smart enough to remove this overhead but best not to relay on the compiler that way.

@ladnir
Copy link
Member

ladnir commented Oct 20, 2023

In simples example the compiler does this thing called devirtualization which basically can inline virtually functions. But there's no guarantee that it will happen. However, if you use inline functions, you are basically guaranteed there's no cost. The function will disappear.

@lzjluzijie
Copy link
Contributor Author

I see, I will change the code. And for ExConv, I should also modify it to use inline member function, not operator + or ^?

@lzjluzijie
Copy link
Contributor Author

One more question please: In my second example, F128 inherits block, but the operator function is also inline without virtual. Is this kind of inline different from the member function inline in the first example? If so, is it because of F128 inherits block?

@ladnir
Copy link
Member

ladnir commented Oct 20, 2023

Ok, i took a closer look at you example. Inheritance != virtual. You can inherit all you want. The functions can still be inline and they would behave the same as if you wrote new version of them. What has a performance overhead is when you use the virtual keyword on a function and then override it in some base class. Specifically, consider the example

struct base {
  virtual void foo() = 0;
};
struct derived0 : base {
void foo() override { std::cout << "derived0 << std::endl; }
};
struct derived1 : base {
void foo() override { std::cout << "derived1 << std::endl; }
};

When someone has a base* v; and they call v->foo(); what happens is that at the begining of derived0, derived1, there is a hidden pointer that points to something that called a v-table. This v-Table looks like:

struct vTableForDerived0{
      void(* baseFooFunctionPointer)(void* derived0);
};
struct vTableForDerived1{
      void(* baseFooFunctionPointer)(void* derived1);
};

and the derived classes actually look like

struct derived0 : base {
void* pointerToVTable;
void fooImpl() { std::cout << "derived0 << std::endl; }
};
struct derived1 : base {
void* pointerToVTable;
void fooImpl() { std::cout << "derived1 << std::endl; }
};

Now the v->foo(); call gets transformed into

auto* vTable = v->pointerToVTable;
vTable->baseFooFunctionPointer(v); // this calls derived0::fooImpl() or derived1::fooImple() 

so now it might be a bit clearer why doing virtual is a bit of a performance hit. You have to chase the pointer to the vtable and then you have to call baseFooFunctionPointer. So say instead of printing something we were just doing a + on a u64. Then you are doing all this, plus the function call which itself has to do like a bunch of stuff (flush variables our of registers, update the program counter, make space for a new stack frame, execute the call instruction, and then you do a simple +.

In some cases the compiler is smart and can look at your program and say "hay, even though v is a Base*, i know it's actually derived0 and therefore i can just call derived::fooImpl. I will even inline it". However, if there are many possible derived types and the compiler can't see which is the type being used, then it can't do this devirtualization.

In your example above you don't use virtual and therefore none of this stuff happens. They are just normal function calls. As you can see in your assembly, these get inlined (no function calls made).


Correct, you can define add like you did above and use that wherever you want to do a plus. Additionally, I would suggest not requiring that the TypeTrait object only have a static function. So these subfield protocols will take the normal F,G values an input and an instance of TypeTraits. You use this instance to do all the additions and everything else.

Consider the case where you want to allow the modulus to be many different values. If add is static, then this modulus must be somewhere within the F type. e.g.

struct F {
  u64 val;
  u64 modulus;
};

So now all Fs have to be twice as big. However, if TypeTraits is an actual object, you can simply stick the modulus in it. Now F is only a u64 in the example above.

The alternative would be for F to have modulus hardcoded into the "type". But this is a bit less flexible. Although maybe its good enough haha.

@lzjluzijie
Copy link
Contributor Author

lzjluzijie commented Oct 20, 2023

Thank you very much for your explanation. I understand that virtual is slow. Now we should have two approaches

The first is use operators with field elements(no virtual), for example

struct F128 {
    block b;

    inline F128 operator*(const F128& lhs, const F128& rhs) const {
        block r = lhs.b.gf128Mul(rhs.b);
        return F128(r);
    }
};

struct TypeTrait
{
    using F = F128;
    using G = F128;
    ...
   // no explict Add needed.
};

The second approach:

struct TypeTrait
{
    using F = block;
    using G = block;
    ...
    inline F Add(const F& a, const F& b)
    ...
};

// And protocols use member functions like Add, not operators

If my understanding is correct: both approaches should be similar performance, given there is no virtual and inlines are used. I think one advantage of the first approach that it needs less code. If we want to use primitive type for these protocol, we don't need to write Add functions in the second approach. However, the second approach might be safer. In the first case, users might have some unsafe pointer operation that make F* to block* and use wrong operator function. But I think inside libOTe we can make sure this never happen and it should be fine?

As for the modulus, I suggest that we can make F templated with modulus, and I think this is more convenient(in the first approach).

So I personally prefers the first approach. Again I don't know much about cpp, is there any issue with it? Thank you.

@ladnir
Copy link
Member

ladnir commented Oct 20, 2023

Sure, you can do the first if you want. I might change it later though. Regarding the user, you can give a default version of TypeTrait to assumes F, G are normal types with +, etc.

@lzjluzijie
Copy link
Contributor Author

Makes sense. So you think the second is better? I can do that directly.

@ladnir
Copy link
Member

ladnir commented Oct 20, 2023

I'd prefer the second because you are separating the data (F, G) from the operations (add, sample, etc). You can use the same datatype (eg block) for both a 128 bit prime field and GF128. But it's fine either way.

@lzjluzijie
Copy link
Contributor Author

Makes sense. I will do more tests with larger fields and then do it.

@lzjluzijie
Copy link
Contributor Author

I got some issue with genSilentBaseOts. If I configure it with SilentBaseType::Base and run BaseOT, it works normally. However, if I use SilentBaseType::BaseExtend with NoisyVOLE requiring 64 not 128 OTs, assertion in libOTe/TwoChooseOne/SoftSpokenOT/SoftSpokenMalOtExt.cpp:56 would fail.

I'm not familiar with SoftSpokenOT and I wonder if there is any special reason to use it, not something simpler like IKNP?

@lzjluzijie
Copy link
Contributor Author

I think we can do silent VOLE over general commutative rings, and current PPRF and ExConv code should support it. As for Noisy VOLE, the current method seems not working.

I have an idea: for G=u32 and F=u32^k, we can do k times of u32-u32 VOLE with the kth element of the type F delta from sender and same y array from receiver. The complexity should be similar, since we are still iterating every bit of delta once.

@ladnir
Copy link
Member

ladnir commented Oct 20, 2023

For Semi-honest iknp would be fine. For simplicity I use soft-spoken. It sends a bit less data as well...

Where is it failing exactly? I suspect is soft spoken is failing then iknp would too. This have the same api. Hard to say though.

For silent vole that's sounds right. For noisy vole, you're saying G=gf(32) and F=gf(k*32) doesn't work? The issue isn't clear to me.

@lzjluzijie
Copy link
Contributor Author

SoftSpokenMalOtExt.cpp:56 would fail. I am saying G is uint32, F is array of uint32, not fields.

@ladnir
Copy link
Member

ladnir commented Oct 21, 2023

So the receiver has a in G^n, the sender has X in F.

The receiver computes vi = a * F(2^i). The user can define * however they want, more or less. The receiver acts as OT sender

mi0 = Ri
mi1 = Ri + vi

where `Ri in F^n is random. The sender learns

mi_{Xi} = Ri + Xi *  F(2^i) * a

When you sum the received messages you get

B = sum_i mi_{Xi} 
  = sum_i Ri + Xi *  F(2^i) * a
  = sum_i Ri + sum_i Xi *  F(2^i) * a
  = C + X * a

where C = sum_i Ri.

You want to define G as the Z_{2^32} group and F as the vector space over it. This is fine. It should work.

@lzjluzijie
Copy link
Contributor Author

Yeah it makes sense to have this new TypeTrait to unify subfield.

@lzjluzijie
Copy link
Contributor Author

lzjluzijie commented Jan 12, 2024

For gAes, I don't know how to put it in non-header file while our implementations using it is in header files. The current compiling speed is quite slow, is there any way to put our templated implementations into cpp files?

For ExConv, yes we can put TypeTrait in member functions, but what is the advantages to do this?

@ladnir
Copy link
Member

ladnir commented Jan 12, 2024

For gAes, you would need to find a cpp file for it to live and remove the anonymous namespace. Then in the header you can declare it as extern, i.e.

    // A public PRF/PRG that we will use for deriving the GGM tree.
    extern const std::array<AES, 2> gAes;

extern means it exists somewhere but you don't know where until linking.

Yeah. for compiles time the issue is that every file cpp you include the impl recompile the whole thing. This compounded by the fact that i think ExConv takes awhile. To fix this we will need to split the template into a header "A" where is declared and a separate (less used) header "B" where its implemented. In some cpp file, you can then explicitly instantiate the versions of subfield that you want. This cpp file will include "B" and then instantiate the classes you want. These will be compiled only once in that single cpp file. I did this for the old ExConv code.

What the advantage of making ExConv not a template? it makes it so that you don't need to compile as many things over and over again. Also, there are several functions that take the ExConv as parameter that you had to turn into a template only to support ExConv. By making it not a template all these also become not a template.

@lzjluzijie
Copy link
Contributor Author

I see, thank you. I will try my best to implement them

@ladnir
Copy link
Member

ladnir commented Jan 12, 2024

For G=integer ring, e.g. G=u32, we will also need to be careful. It's been shown that rings (as opposed to fields) get less security. One can fix this by doubling the noise rate. However, that 2x the communication overhead. An alternative heuristic approach is to require all of the noise elements to be odd.

So I think we should be able to query TypeTrait about what type of field/ring is it, also, how many bits is G. We have the following cases:

  1. If it's binary, then we simply set the noise element to 1 and skip noisy vole.
  2. If it's a ring, we sample a random odd element (or double the noise but that's less efficient)
  3. If it's a field, we sample a random nonzero element.

See https://eprint.iacr.org/2022/712

Also, independent of these I've realized there is an issue when we combine an vector F with ExConv or ExAcc. These codes are binary and so operate over the components of F independently. This can effectively halves the noise along with some other issues. However, we can fix this by lightly modifying these codes. In particular, we can have a customization when F is a vector type that will linearly mix the the components of F. For example, if F= Z_32^3, we could define an invertible matrix M in Z_32^{3*3} such as

    | 1 2 3 |
M = | 2 3 1 |
    | 3 2 1 |

The code can then use this to "mix" the components of F, i.e. after each accumulate, multiply by M. This should be good enough.

To facilitate this, we can have TypeTraits define a function linearMix(const F& x) which multiplies by M if its a vector type. Otherwise it can just return x.

Also, maybe we should rename TypeTrait to VoleTrait. A bit more descriptive.

@lzjluzijie
Copy link
Contributor Author

I updated gAes, ExConv, and sampleBaseVoleValues. I added DefaultTrait and we can now use SilentSubfieldVoleReceiver<block> directly, and it should be ready to unify. I'm not sure about styling and the exact way you want to unify, can you take it(and combine malicious for block at the same time)?

As for the ring/linearMix issue, it makes sense to me. Since no one is using it now, I think we can do it in a separate PR.

@ladnir ladnir merged commit 8193dc3 into osu-crypto:master Jan 23, 2024
3 checks passed
@ladnir
Copy link
Member

ladnir commented Jan 23, 2024

@lzjluzijie thanks for the PR. ended up refactoring lots of things (not just your code) but its all merged now.

@lzjluzijie
Copy link
Contributor Author

Thank you very much! The refactored code(CoeffCtx) looks great andI learned a lot from you through this PR, especially cpp tricks.

I can next work on the noisy optimization I mentioned above, when F=G^m for m > 1.

Suppose G has k bits, and length of the noisy subfield VOLE (numPartitions) be n. Currently, for bit of the delta in F, the OT message has (2*) n elements in F, which is (2*) n*k*m bits each. Since F has k*m bits, the overall communication complexity is n*k^2*m^2.

Alternatively, we can do m times noisy VOLE with F'=G'=G. Each time the communication complexity is O(n*k^2), so overall is O(n*m*k^2). We should be able to use template to check if F!=G(which means F=G^m with m > 1), and use this approach.

Do you have any concerns about this approach?

@ladnir
Copy link
Member

ladnir commented Jan 23, 2024

lets see, we have log2 |G| = k, F = G^m and want to do a vole of size n. We currently have

  1. km OTs, each requiring s bits of communication to generate (for softspoken s=64 or s=32, for base ot s=512, for silent s=1).
  2. For each OT, we send a derandomization string of size kmn
  3. Overall we will have km * (km n + s) bits of communication.

You are proposing to switch the direction:

  1. nk OTs each with s comm.
  2. For each OT, we send a derandomization string of size km.
  3. Overall we will have kn * (km + s).

So for m=1,k=128, n=100 & softspoken s=32, the old way requires 128^2 *100 + 128^2 * 32=2,162,688 bits, the new way requires 128^2 *100 + 128 * 100 = 1,576,800. If n>128, then the new way is worse. Current LPN parameters has n=170 or so but you could argue thats too high, some say n=64 is fine or even less.

For m=4, k=32, n=100, the old way requires 128 * 128 * 100 + 128 * 32=1,642,496. The new way requires 32*100 (128 + 32)=512,000.

This actually has a second (larger?) benefit, the rounds complexity of vole goes down to 2.

Silent Sender         Silent Receiver
             base ot 1 
      ( pprf & c choice bits)
     <------------------------

              base ot 2,
            noisy vole msg
      ------------------------>

In the old way we had to do the pprf OT and c OT in different directions. This means the best you could do was 3 rounds (with base Ots)! Now we can even use OT extension to get 3 rounds and avoid doing a PK operation for each.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants