-
Notifications
You must be signed in to change notification settings - Fork 110
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
Subfield VOLE #127
Conversation
I made the test pass with interleaved mode and "field" As for the current code, I split the @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. |
There was a problem hiding this 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
z[j] = z[j] + buf[j]; | ||
|
||
F twoPowI = 0; | ||
*BitIterator((u8*)&twoPowI, ii) = 1; |
There was a problem hiding this comment.
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.
libOTe_Tests/SilentOT_Tests.cpp
Outdated
// } | ||
// }; | ||
|
||
using u128 = unsigned __int128; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not portable.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
// where each half defines one of the children. | ||
aes[keep].hashBlocks<8>(parent.data(), child.data()); | ||
|
||
if (d < pprf.mDepth - 1) { |
There was a problem hiding this comment.
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
do not use QuasiCyclicCode. We can just use |
As mentioned in the comments, It best to thing of I would suggest changing the type level template to be some |
Thank you for your review, for |
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 |
I wonder if we should define some virtual class |
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 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:
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 |
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, I realized that in the Btw the |
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. |
I got silent VOLE test passed with F=G=u128. The ExConvCode is indeed well templated and I only changed a few 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 |
|
As suggested above, we shouldn't even use |
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. |
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. |
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. |
I see, I will change the code. And for ExConv, I should also modify it to use inline member function, not operator |
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? |
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
When someone has a
and the derived classes actually look like
Now the
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 In some cases the compiler is smart and can look at your program and say "hay, even though 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 Consider the case where you want to allow the modulus to be many different values. If
So now all The alternative would be for |
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 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. |
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. |
Makes sense. So you think the second is better? I can do that directly. |
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. |
Makes sense. I will do more tests with larger fields and then do it. |
I got some issue with I'm not familiar with SoftSpokenOT and I wonder if there is any special reason to use it, not something simpler like IKNP? |
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. |
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. |
SoftSpokenMalOtExt.cpp:56 would fail. I am saying G is uint32, F is array of uint32, not fields. |
So the receiver has The receiver computes
where `Ri in F^n is random. The sender learns
When you sum the received messages you get
where You want to define |
Yeah it makes sense to have this new TypeTrait to unify subfield. |
For For ExConv, yes we can put TypeTrait in member functions, but what is the advantages to do this? |
For
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. |
I see, thank you. I will try my best to implement them |
For So I think we should be able to query
See https://eprint.iacr.org/2022/712 Also, independent of these I've realized there is an issue when we combine an vector
The code can then use this to "mix" the components of To facilitate this, we can have Also, maybe we should rename |
I updated 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. |
@lzjluzijie thanks for the PR. ended up refactoring lots of things (not just your code) but its all merged now. |
Thank you very much! The refactored code( 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 Alternatively, we can do Do you have any concerns about this approach? |
lets see, we have
You are proposing to switch the direction:
So for For This actually has a second (larger?) benefit, the rounds complexity of vole goes down to 2.
In the old way we had to do the |
Implements #122