-
Notifications
You must be signed in to change notification settings - Fork 25
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
test manipulated bit multiplication fails verification #1181
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -80,20 +80,143 @@ | |
|
||
#[cfg(all(test, unit_test))] | ||
mod test { | ||
use ipa_step_derive::CompactStep; | ||
|
||
use crate::{ | ||
error::Error, | ||
ff::boolean::Boolean, | ||
helpers::{Role, Role::H1}, | ||
protocol::{ | ||
basics::SecureMul, | ||
context::{dzkp_validator::DZKPValidator, Context, DZKPContext, UpgradableContext}, | ||
basics::{ | ||
mul::{dzkp_malicious::Field, semi_honest::multiplication_protocol, Replicated}, | ||
SecureMul, | ||
}, | ||
context::{ | ||
dzkp_field::DZKPCompatibleField, | ||
dzkp_validator::{DZKPValidator, Segment}, | ||
Context, DZKPContext, DZKPUpgradedMaliciousContext, UpgradableContext, | ||
}, | ||
RecordId, | ||
}, | ||
rand::{thread_rng, Rng}, | ||
secret_sharing::{replicated::semi_honest::AdditiveShare, SharedValueArray, Vectorizable}, | ||
test_fixture::{Reconstruct, Runner, TestWorld}, | ||
}; | ||
|
||
/// This function mirrors `zkp_multiply` except that on party cheats. | ||
/// | ||
/// The cheating party flips `prss_left` | ||
/// which causes a flip in `z_left` computed by the cheating party. | ||
/// This manipulated `z_left` is then sent to a different helper | ||
/// and included in the DZKP batch. | ||
pub async fn multiply_with_cheater<'a, F, const N: usize>( | ||
ctx: DZKPUpgradedMaliciousContext<'a>, | ||
record_id: RecordId, | ||
a: &Replicated<F, N>, | ||
b: &Replicated<F, N>, | ||
prss: &Replicated<F, N>, | ||
cheater: Role, | ||
) -> Result<Replicated<F, N>, Error> | ||
where | ||
F: Field + DZKPCompatibleField<N>, | ||
{ | ||
let mut prss_left = prss.left_arr().clone(); | ||
if ctx.role() == cheater { | ||
prss_left += <<F as Vectorizable<N>>::Array>::from_fn(|_| F::ONE); | ||
}; | ||
|
||
let z = | ||
multiplication_protocol(&ctx, record_id, a, b, &prss_left, prss.right_arr()).await?; | ||
// create segment | ||
let segment = Segment::from_entries( | ||
F::as_segment_entry(a.left_arr()), | ||
F::as_segment_entry(a.right_arr()), | ||
F::as_segment_entry(b.left_arr()), | ||
F::as_segment_entry(b.right_arr()), | ||
F::as_segment_entry(prss.left_arr()), | ||
F::as_segment_entry(prss.right_arr()), | ||
F::as_segment_entry(z.right_arr()), | ||
); | ||
|
||
// add segment to the batch that needs to be verified by the dzkp prover and verifiers | ||
ctx.push(record_id, segment); | ||
|
||
Ok(z) | ||
} | ||
Comment on lines
+131
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This approach leads to a lot of code duplication. Why not just invoke the normal protocol (which already does all of this segment entry stuff), and just mutate the result (apply an additive attack)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. These tests are running against reference implementation and not intended to be formal verification methods that explore all possible states the system can be it. There are better tools than our poor in-memory infra for that. Modelling the state of malicious adversaries does not seem very useful - there are infinite combinatorial states they can be it. The threat model allows them to delay response infinitely, respond with errors, run Byzantine attacks etc. Instead of focusing on what malicious adversaries can do, it may be easier to shift our attention to what honest helpers can see from such entities. This greatly simplifies testing imo, because there are only a few things a malicious actor can do:
For the first 3 we don't need to duplicate protocols (#1202 covers 2 and 3 and 1 will be covered after #1205). I could be wrong, but test here seems to be checking 3) so we could leverage our new infrastructure capabilities and avoid copy-pasting the segment construction logic. Both @danielmasny and @andyleiserson expressed interest in covering 4) which is an interesting exercise. I think code duplication is probably wrong for it in the long term as well - we need to figure out how to do it without cloning our MPC circuits. |
||
fn generate_share_from_three_bits(role: Role, i: usize) -> AdditiveShare<Boolean> { | ||
let (first_bit, second_bit) = match role { | ||
Role::H1 => (i % 2 == 0, (i >> 1) % 2 == 0), | ||
Role::H2 => ((i >> 1) % 2 == 0, (i >> 2) % 2 == 0), | ||
Role::H3 => ((i >> 2) % 2 == 0, i % 2 == 0), | ||
}; | ||
<AdditiveShare<Boolean>>::from((first_bit.into(), second_bit.into())) | ||
} | ||
|
||
fn all_combination_of_inputs(role: Role, i: usize) -> [AdditiveShare<Boolean>; 3] { | ||
// first three bits | ||
let a = generate_share_from_three_bits(role, i); | ||
// middle three bits | ||
let b = generate_share_from_three_bits(role, i >> 3); | ||
// last three bits | ||
let prss = generate_share_from_three_bits(role, i >> 6); | ||
|
||
[a, b, prss] | ||
} | ||
|
||
#[derive(CompactStep)] | ||
enum TestStep { | ||
#[step(count = 512)] | ||
Counter(usize), | ||
} | ||
|
||
#[tokio::test] | ||
async fn detect_cheating() { | ||
let world = TestWorld::default(); | ||
|
||
for i in 0..512 { | ||
let [(_, s_1), (_, s_2), (v_3, s_3)] = world | ||
.malicious((), |ctx, ()| async move { | ||
let [a, b, prss] = all_combination_of_inputs(ctx.role(), i); | ||
let validator = ctx.narrow(&TestStep::Counter(i)).dzkp_validator(10); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know what this "10" is / does. |
||
let mctx = validator.context(); | ||
let product = multiply_with_cheater( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I would rather have the other two parties call the normal protocol, and only one call |
||
mctx.set_total_records(1), | ||
RecordId::FIRST, | ||
&a, | ||
&b, | ||
&prss, | ||
H1, | ||
) | ||
.await | ||
.unwrap(); | ||
|
||
( | ||
validator.validate().await, | ||
[ | ||
bool::from(*a.left_arr().first()), | ||
bool::from(*a.right_arr().first()), | ||
bool::from(*b.left_arr().first()), | ||
bool::from(*b.right_arr().first()), | ||
bool::from(*prss.left_arr().first()), | ||
bool::from(*prss.right_arr().first()), | ||
bool::from(*product.left_arr().first()), | ||
bool::from(*product.right_arr().first()), | ||
], | ||
) | ||
}) | ||
.await; | ||
|
||
// H1 cheats means H3 fails | ||
// since always verifier on the left of the cheating prover fails | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes zero sense to me. I cannot think of any reason why just one verifier should fail. Both verifiers should be checking that all of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lets sync on this once you are back next week. |
||
match v_3 { | ||
Ok(()) => panic!("Got a result H1: {s_1:?}, H2: {s_2:?}, H3: {s_3:?}"), | ||
Err(ref err) => assert!(matches!(err, Error::DZKPValidationFailed)), | ||
} | ||
} | ||
} | ||
|
||
#[tokio::test] | ||
pub async fn simple() { | ||
async fn simple() { | ||
let world = TestWorld::default(); | ||
|
||
let mut rng = thread_rng(); | ||
|
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.
I find it just adds an extra level of confusion that we achieve this bit flip specifically through manipulating the prss... I would rather just mutate z after computing it.