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

Protocol context is a trait #253

Merged

Conversation

akoshelev
Copy link
Collaborator

This change makes ProtocolContext a trait and implements it for two newly created structs that represent semi-honest and malicious contexts.

Each protocol can decide whether it supports semi-honest/malicious security model or both by injecting SemiHonestProtocolContext, MaliciousProtocolContext or generic type parameter C: ProtocolContext<F> accordingly.

There are a couple of important changes to the context API:

  • narrow no longer enforces "don't narrow to the same step twice" rule. There were two reasons for having this rule.
    • Don't use the same shared randomness more than once. Validate indices used in PRSS #208 protects against it
    • Make sure every communication is uniquely addressable by record_id + step. If someone narrows to the same step more than once and uses the same record id to communicate, Infra is in trouble. It won't be able to resolve these two communications and likely apply last-write-wins which is obviously not correct here. It is also mitigated because of acceptable record ranges introduced in Range of accepted records inside the send buffer #183. Now if protocol attempts to send a record outside of valid range, it will be caught. If it attempts to send the same record twice within the acceptable range, fsv will panic.
  • bind is gone. No need to explicitly bind context to a record because of the above

Need to clean up the following

* Remove `bind`
* Relax `narrow`
* Introduce a small context struct that clone will refer to
What is left:

* Introduce a small context struct that clone will refer to
@@ -0,0 +1,102 @@
use crate::ff::Field;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

moved context implementations to a separate folder

/// in 3 party MPC ring.
#[derive(Clone, Debug)]
pub struct MaliciousProtocolContext<'a, F: Field> {
inner: ContextInner<'a>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@martinthomson I remember we were talking about using references here, but I couldn't find a good way to do it. there must be something that holds onto this inner struct for the duration of protocol execution. It didn't seem very ergonomic but I could be missing something

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think the idea was to collect the static components of the context (role, prss, gateway) into GlobalContext so that a single &GlobalContext needs to be duplicated each time the context is refined, rather than multiple items.

This is something that I was planning to look at, but I haven't done anything with it yet. Hopefully it can be done in a way that doesn't change the context API.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

With #261 I think we can make it ergonomic to use because we can just stitch global contexts inside test world and create contexts on demand. For real world implementation I am not sure if we can make it better than just holding onto both global context and protocol context

fn mesh(&self) -> Mesh<'_, '_>;

/// Generates a new share of one
fn share_of_one(&self) -> <Self as ProtocolContext<F>>::Share;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

share_of_one is now defined inside protocol context trait. That allows malicious context to generate it without exposing the internal state (r_share)

@@ -115,18 +83,10 @@ impl Step {
{
let s = String::from(step.as_ref());
assert!(!s.contains('/'), "The string for a step cannot contain '/'");
assert!(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

step validation is gone

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hooray!

@@ -215,19 +216,19 @@ pub mod tests {

let result_shares = try_join_all([
multiply_two_shares_mostly_zeroes(
context[0].bind(record_id),
context[0].clone(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

no more bind calls

@@ -33,13 +32,15 @@ use futures::future::try_join_all;
///
/// ## Errors
/// It will propagate errors from multiplication protocol.
pub async fn bit_permutation<'a, F: Field, S: SecretSharing<F>>(
ctx: ProtocolContext<'a, S, F>,
pub async fn bit_permutation<
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is how the protocols would look like if they can work in semi-honest and malicious setting.

Note that there is no more SecureMul constraint because it is a supertrait for ProtocolContext.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's really nice. Thanks for eliminating those constraints!

@@ -36,7 +37,7 @@ impl<F: Field> Reshare<F> {
/// `to_helper.right` = (`rand_right`, part1 + part2) = (r0, part1 + part2)
pub async fn execute(
self,
ctx: &ProtocolContext<'_, Replicated<F>, F>,
ctx: &SemiHonestProtocolContext<'_, F>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this probably needs to be fixed later

Copy link
Collaborator

Choose a reason for hiding this comment

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

You mean the fact that it's a reference and not an actual object? @richajaindce

Copy link
Contributor

Choose a reason for hiding this comment

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

I changed it in #257 . Will rebase on this PR once we are happy with it

@@ -67,10 +70,10 @@ pub fn make_malicious_contexts<F: Field>(test_world: &TestWorld) -> [MaliciousCo
/// # Panics
/// Never, but then Rust doesn't know that; this is only needed because we don't have `each_ref()`.
#[must_use]
pub fn narrow_contexts<'a, F: Field, S: SecretSharing<F>>(
contexts: &[ProtocolContext<'a, S, F>; 3],
pub fn narrow_contexts<C: Debug + ProtocolContext<F, Share = S>, F: Field, S: SecretSharing<F>>(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Debug because there is a call to unwrap inside this function

Copy link
Member

Choose a reason for hiding this comment

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

It's good that ProtocolContext is not Debug in the normal case. It's OK to be Debug for tests, but we shouldn't need it for real code.


/// Context used by each helper to perform secure computation. Provides access to shared randomness
/// generator and communication channel.
pub trait ProtocolContext<F: Field>:
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

no more bind

Copy link
Collaborator

Choose a reason for hiding this comment

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

At the start of the meeting yesterday, @martinthomson and I were discussing checking for bound contexts using the type system. main...andyleiserson:ipa:context2 has some changes I was working on in that direction. I was thinking it might make sense to get rid of the separate record_id parameter and just pass it via the context.

I had also worked on some changes to enforce one-time use restrictions. Those are in andyleiserson/ipa@context2...andyleiserson:ipa:context3.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Hmm, with this change we don't need record_id inside the context, unless we want to change the way how we multiply shares. Currently it is ctx.multiply(record_id, a, b), do we want ctx.multiply(a, b)?

Copy link
Member

Choose a reason for hiding this comment

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

I kinda like what Andy has done here. The ctx.multiply(a, b) would be ctx.bind(record).multiply(a, b), which seems like a solid approach, though it looks like ctx.multiply(record, a, b) also works fine.

into_parts() taking a reference seems unfortunate; it would be good to understand what parts of that don't work; is it that these functions also narrow()? It seems like you might want something on which you can narrow only in addition to the usable bits if that is the case.

The ergonomics of into_parts leaves a little to be desired. It's not awful, but it loses some of the simplicity.

Copy link
Collaborator

Choose a reason for hiding this comment

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

into_parts() taking a reference seems unfortunate

Agreed, this was temporary so that I didn't have to update all references to the context all at once. I would want to fix it before merging anything.

It seems like you might want something on which you can narrow only in addition to the usable bits if that is the case.

Also agreed, this was on my to do list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I am a little hesitant because ctx.multiply(a, b) syntax invites us to solve the problem "how to bind once", while ctx.multiply(record, a, b) does not.

Copy link
Collaborator

Choose a reason for hiding this comment

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

By "how to bind once", do you mean keeping track of whether the context is already bound and issuing a warning if it is, similar to the "context narrowed more than once" warning that is removed by this PR?

My proposal is to keep track of that with the type system. So if it compiles, the context is not bound more than once. The benefit of this is there's less chance of a bug due to passing a wrong or inconsistent record_id somewhere.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I guess I am debating whether we need compile-time check vs the runtime check that already exists. There are some really nice things we can do with bounded contexts - for example implement multiplication and reveal for contexts that carry record_id , so nobody will be able to call multiply on Context<.., AnyRecord>. On the other hand, it requires some machinery and makes code a bit harder to understand? Let me add this topic to the agenda for the Thursday sync-up - there are some really nice things we can do with the model you proposed.

@akoshelev akoshelev marked this pull request as ready for review November 18, 2022 07:50
@akoshelev
Copy link
Collaborator Author

I think tests from #241 now can be enabled - I would expect Infra to panic if someone tries to send the same message twice. I can also add a test for that (Friday PT).

I also didn't update the panic message inside fsv to make it clear what went wrong for protocol authors.

Copy link
Member

@martinthomson martinthomson left a comment

Choose a reason for hiding this comment

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

This is definitely an improvement, but I think that we need to iterate on the structure of the Context implementations.

/// Context used by each helper to perform secure computation. Provides access to shared randomness
/// generator and communication channel.
pub trait ProtocolContext<F: Field>:
Clone + SecureMul<F, Share = <Self as ProtocolContext<F>>::Share> + Reveal
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Clone + SecureMul<F, Share = <Self as ProtocolContext<F>>::Share> + Reveal
Clone + SecureMul<F, Share = <Self as ProtocolContext<F>>::Share> + Reveal<Share = <Self as ProtocolContext<F>>::Share>

Reveal and SecureMul are different in that one takes F and the other does not. Can we work out which one we prefer? If we can avoid multiple type parameters, maybe that is better.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

There was a need to make Reveal work with any field as Context<F> was used to reveal Fp2 values. It is no longer the case, so I am going to change Reveal and make it look the same as SecureMul

src/protocol/context/mod.rs Outdated Show resolved Hide resolved
#[derive(Clone, Debug)]
struct ContextInner<'a> {
role: Role,
step: Step,
Copy link
Member

Choose a reason for hiding this comment

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

My thinking (see #139) had Step outside of the shared part. That is, the shared components are kept in the inner piece, but the part that needs to change is kept with each instance.

As long as the inner object is Sync, then we should be able to refer to it with Arc. Arc is just a pointer, so cloning it is as cheap as copying that and doing a relaxed atomic increment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I can take a stab at moving step away from inner.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

all right, I've opened a can of worms here. I tried to avoid Arc but I don't seem that I can do it. The most annoying thing is that we really need Arc for tests only. Arc causes double-indirection every time we need to dereference Gateway or Participant :(

Is using Arc ok for now or is there a better way to handle this? Also I think if we migrate all tests to use #261, we don't have this problem so should we push these first?

graph TD;
    TestWorld-->|owns|Gateway;
    ContextInner-->|borrows|Gateway;
    subgraph ephemeral
    Context-->|borrows|ContextInner;
    end
    Test-->|owns|Context;
    Test-->|owns|TestWorld;
Loading

Copy link
Member

@martinthomson martinthomson Nov 22, 2022

Choose a reason for hiding this comment

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

For now, let's go with Arc, but we can mark it as needing attention later.

The extra indirection doesn't bother me too much. It might be something we can fix in production with another generic attribute, maybe with conditional compilation (Arc for tests, ownershipsimple reference to Sync in production).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it is done and both contexts now use Arc. I've wasted another couple of hours trying to make MaliciousContext look like SemiHonest or, more precisely Context<F, Share = Replicated<F>>. So sad it didn't work (rust-lang/rust#20400)

src/protocol/context/mod.rs Outdated Show resolved Hide resolved
src/protocol/context/semi_honest.rs Show resolved Hide resolved
src/protocol/context/malicious.rs Outdated Show resolved Hide resolved
@@ -67,10 +70,10 @@ pub fn make_malicious_contexts<F: Field>(test_world: &TestWorld) -> [MaliciousCo
/// # Panics
/// Never, but then Rust doesn't know that; this is only needed because we don't have `each_ref()`.
#[must_use]
pub fn narrow_contexts<'a, F: Field, S: SecretSharing<F>>(
contexts: &[ProtocolContext<'a, S, F>; 3],
pub fn narrow_contexts<C: Debug + ProtocolContext<F, Share = S>, F: Field, S: SecretSharing<F>>(
Copy link
Member

Choose a reason for hiding this comment

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

It's good that ProtocolContext is not Debug in the normal case. It's OK to be Debug for tests, but we shouldn't need it for real code.

Copy link
Collaborator

@benjaminsavage benjaminsavage left a comment

Choose a reason for hiding this comment

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

This is a huge win. I can't wait to land this. Thanks @akoshelev!

src/helpers/messaging.rs Outdated Show resolved Hide resolved
src/protocol/context/malicious.rs Outdated Show resolved Hide resolved
@@ -3,7 +3,6 @@ mod batch;
mod boolean;
mod check_zero;
pub mod context;
pub mod context_traits;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice

Comment on lines -79 to -81
/// This tracks the different values that have been provided to `narrow()`.
#[cfg(debug_assertions)]
used: Arc<Mutex<HashSet<String>>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Very nice

@@ -115,18 +83,10 @@ impl Step {
{
let s = String::from(step.as_ref());
assert!(!s.contains('/'), "The string for a step cannot contain '/'");
assert!(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Hooray!

@@ -33,13 +32,15 @@ use futures::future::try_join_all;
///
/// ## Errors
/// It will propagate errors from multiplication protocol.
pub async fn bit_permutation<'a, F: Field, S: SecretSharing<F>>(
ctx: ProtocolContext<'a, S, F>,
pub async fn bit_permutation<
Copy link
Collaborator

Choose a reason for hiding this comment

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

That's really nice. Thanks for eliminating those constraints!

@@ -36,7 +37,7 @@ impl<F: Field> Reshare<F> {
/// `to_helper.right` = (`rand_right`, part1 + part2) = (r0, part1 + part2)
pub async fn execute(
self,
ctx: &ProtocolContext<'_, Replicated<F>, F>,
ctx: &SemiHonestProtocolContext<'_, F>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

You mean the fact that it's a reference and not an actual object? @richajaindce

@martinthomson martinthomson merged commit 219e283 into private-attribution:main Nov 22, 2022
@martinthomson
Copy link
Member

Progress!

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.

5 participants