Skip to content

Commit 482c98c

Browse files
authored
Merge pull request #67 from chain/feature/dealer-verify-shares
Have the dealer verify shares
2 parents f326f8c + 9a3d448 commit 482c98c

File tree

3 files changed

+162
-39
lines changed

3 files changed

+162
-39
lines changed

src/aggregated_range_proof/dealer.rs

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
use rand::Rng;
2+
13
use curve25519_dalek::ristretto::RistrettoPoint;
24
use curve25519_dalek::scalar::Scalar;
35
use curve25519_dalek::traits::Identity;
6+
47
use generators::GeneratorsView;
58
use inner_product_proof;
69
use proof_transcript::ProofTranscript;
10+
711
use util;
812

913
use super::messages::*;
@@ -25,12 +29,30 @@ impl Dealer {
2529
if !m.is_power_of_two() {
2630
return Err("m is not valid: must be a power of 2");
2731
}
32+
33+
// At the end of the protocol, the dealer will attempt to
34+
// verify the proof, and if it fails, determine which party's
35+
// shares were invalid.
36+
//
37+
// However, verifying the proof requires either knowledge of
38+
// all of the challenges, or a copy of the initial transcript
39+
// state.
40+
//
41+
// The dealer has all of the challenges, but using them for
42+
// verification would require duplicating the verification
43+
// logic. Instead, we keep a copy of the initial transcript
44+
// state.
45+
let initial_transcript = transcript.clone();
46+
47+
// Commit to aggregation parameters
2848
transcript.commit_u64(n as u64);
2949
transcript.commit_u64(m as u64);
50+
3051
Ok(DealerAwaitingValueCommitments {
3152
n,
3253
m,
3354
transcript,
55+
initial_transcript,
3456
gens,
3557
})
3658
}
@@ -42,6 +64,9 @@ pub struct DealerAwaitingValueCommitments<'a, 'b> {
4264
n: usize,
4365
m: usize,
4466
transcript: &'a mut ProofTranscript,
67+
/// The dealer keeps a copy of the initial transcript state, so
68+
/// that it can attempt to verify the aggregated proof at the end.
69+
initial_transcript: ProofTranscript,
4570
gens: GeneratorsView<'b>,
4671
}
4772

@@ -79,6 +104,7 @@ impl<'a, 'b> DealerAwaitingValueCommitments<'a, 'b> {
79104
n: self.n,
80105
m: self.m,
81106
transcript: self.transcript,
107+
initial_transcript: self.initial_transcript,
82108
gens: self.gens,
83109
value_challenge: value_challenge.clone(),
84110
},
@@ -91,6 +117,7 @@ pub struct DealerAwaitingPolyCommitments<'a, 'b> {
91117
n: usize,
92118
m: usize,
93119
transcript: &'a mut ProofTranscript,
120+
initial_transcript: ProofTranscript,
94121
gens: GeneratorsView<'b>,
95122
value_challenge: ValueChallenge,
96123
}
@@ -122,6 +149,7 @@ impl<'a, 'b> DealerAwaitingPolyCommitments<'a, 'b> {
122149
n: self.n,
123150
m: self.m,
124151
transcript: self.transcript,
152+
initial_transcript: self.initial_transcript,
125153
gens: self.gens,
126154
value_challenge: self.value_challenge,
127155
poly_challenge: poly_challenge.clone(),
@@ -135,31 +163,26 @@ pub struct DealerAwaitingProofShares<'a, 'b> {
135163
n: usize,
136164
m: usize,
137165
transcript: &'a mut ProofTranscript,
166+
initial_transcript: ProofTranscript,
138167
gens: GeneratorsView<'b>,
139168
value_challenge: ValueChallenge,
140169
poly_challenge: PolyChallenge,
141170
}
142171

143172
impl<'a, 'b> DealerAwaitingProofShares<'a, 'b> {
144-
pub fn receive_shares(
145-
self,
173+
/// Assembles proof shares into an `AggregatedProof`.
174+
///
175+
/// Used as a helper function by `receive_trusted_shares` (which
176+
/// just hands back the result) and `receive_shares` (which
177+
/// validates the proof shares.
178+
fn assemble_shares(
179+
&mut self,
146180
proof_shares: &[ProofShare],
147-
) -> Result<(AggregatedProof, Vec<ProofShareVerifier>), &'static str> {
181+
) -> Result<AggregatedProof, &'static str> {
148182
if self.m != proof_shares.len() {
149183
return Err("Length of proof shares doesn't match expected length m");
150184
}
151185

152-
let mut share_verifiers = Vec::new();
153-
for (j, proof_share) in proof_shares.iter().enumerate() {
154-
share_verifiers.push(ProofShareVerifier {
155-
proof_share: proof_share.clone(),
156-
n: self.n,
157-
j: j,
158-
value_challenge: self.value_challenge.clone(),
159-
poly_challenge: self.poly_challenge.clone(),
160-
});
161-
}
162-
163186
let value_commitments = proof_shares
164187
.iter()
165188
.map(|ps| ps.value_commitment.V)
@@ -221,7 +244,7 @@ impl<'a, 'b> DealerAwaitingProofShares<'a, 'b> {
221244
r_vec.clone(),
222245
);
223246

224-
let aggregated_proof = AggregatedProof {
247+
Ok(AggregatedProof {
225248
n: self.n,
226249
value_commitments,
227250
A,
@@ -232,8 +255,59 @@ impl<'a, 'b> DealerAwaitingProofShares<'a, 'b> {
232255
t_x_blinding,
233256
e_blinding,
234257
ipp_proof,
235-
};
258+
})
259+
}
260+
261+
/// Assemble the final aggregated proof from the given
262+
/// `proof_shares`, and validate that all input shares and the
263+
/// aggregated proof are well-formed. If the aggregated proof is
264+
/// not well-formed, this function detects which party submitted a
265+
/// malformed share and returns that information as part of the
266+
/// error.
267+
///
268+
/// XXX define error types so we can surface the blame info
269+
pub fn receive_shares<R: Rng>(
270+
mut self,
271+
rng: &mut R,
272+
proof_shares: &[ProofShare],
273+
) -> Result<AggregatedProof, &'static str> {
274+
let proof = self.assemble_shares(proof_shares)?;
236275

237-
Ok((aggregated_proof, share_verifiers))
276+
// See comment in `Dealer::new` for why we use `initial_transcript`
277+
if proof.verify(rng, &mut self.initial_transcript).is_ok() {
278+
Ok(proof)
279+
} else {
280+
// Create a list of bad shares
281+
let mut bad_shares = Vec::new();
282+
for (j, share) in proof_shares.iter().enumerate() {
283+
match share.verify_share(self.n, j, &self.value_challenge, &self.poly_challenge) {
284+
Ok(_) => {}
285+
Err(_) => bad_shares.push(j),
286+
}
287+
}
288+
// XXX pass this upwards
289+
println!("bad shares: {:?}", bad_shares);
290+
Err("proof failed to verify")
291+
}
292+
}
293+
294+
/// Assemble the final aggregated proof from the given
295+
/// `proof_shares`, but does not validate that they are well-formed.
296+
///
297+
/// ## WARNING
298+
///
299+
/// This function does **NOT** validate the proof shares. It is
300+
/// suitable for creating aggregated proofs when all parties are
301+
/// known by the dealer to be honest (for instance, when there's
302+
/// only one party playing all roles).
303+
///
304+
/// Otherwise, use `receive_shares`, which validates that all
305+
/// shares are well-formed, or else detects which party(ies)
306+
/// submitted malformed shares.
307+
pub fn receive_trusted_shares(
308+
mut self,
309+
proof_shares: &[ProofShare],
310+
) -> Result<AggregatedProof, &'static str> {
311+
self.assemble_shares(proof_shares)
238312
}
239313
}

src/aggregated_range_proof/messages.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,6 @@ impl ProofShare {
120120
}
121121
}
122122

123-
pub struct ProofShareVerifier {
124-
pub proof_share: ProofShare,
125-
pub n: usize,
126-
pub j: usize,
127-
pub value_challenge: ValueChallenge,
128-
pub poly_challenge: PolyChallenge,
129-
}
130-
131-
impl ProofShareVerifier {
132-
/// Returns whether the proof share is valid (Ok) or invalid (Err)
133-
pub fn verify_share(&self) -> Result<(), &'static str> {
134-
self.proof_share
135-
.verify_share(self.n, self.j, &self.value_challenge, &self.poly_challenge)
136-
}
137-
}
138-
139123
#[derive(Serialize, Deserialize, Clone, Debug)]
140124
pub struct AggregatedProof {
141125
pub n: usize,

src/aggregated_range_proof/mod.rs

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ pub mod party;
1313

1414
pub use self::messages::AggregatedProof;
1515

16-
struct SinglePartyAggregator {}
16+
pub struct SinglePartyAggregator {}
1717

1818
impl SinglePartyAggregator {
1919
/// Create an aggregated rangeproof of multiple values.
@@ -24,15 +24,14 @@ impl SinglePartyAggregator {
2424
/// The length of `values` must be a power of 2.
2525
///
2626
/// XXX this should allow proving about existing commitments.
27-
fn generate_proof<R: Rng>(
27+
pub fn generate_proof<R: Rng>(
2828
generators: &Generators,
2929
transcript: &mut ProofTranscript,
3030
rng: &mut R,
3131
values: &[u64],
3232
n: usize,
3333
) -> Result<AggregatedProof, &'static str> {
3434
use self::dealer::*;
35-
use self::messages::*;
3635
use self::party::*;
3736

3837
let dealer = Dealer::new(generators.all(), n, values.len(), transcript)?;
@@ -66,9 +65,7 @@ impl SinglePartyAggregator {
6665
.map(|p| p.apply_challenge(&poly_challenge))
6766
.collect();
6867

69-
let (proof, _) = dealer.receive_shares(&proof_shares)?;
70-
71-
Ok(proof)
68+
dealer.receive_trusted_shares(&proof_shares)
7269
}
7370
}
7471

@@ -180,4 +177,72 @@ mod tests {
180177
fn create_and_verify_n_64_m_8() {
181178
singleparty_create_and_verify_helper(64, 8);
182179
}
180+
181+
#[test]
182+
fn detect_dishonest_party_during_aggregation() {
183+
use self::dealer::*;
184+
use self::party::*;
185+
186+
// Simulate four parties, two of which will be dishonest and use a 64-bit value.
187+
let m = 4;
188+
let n = 32;
189+
190+
let generators = Generators::new(PedersenGenerators::default(), n, m);
191+
192+
let mut rng = OsRng::new().unwrap();
193+
let mut transcript = ProofTranscript::new(b"AggregatedRangeProofTest");
194+
195+
// Parties 0, 2 are honest and use a 32-bit value
196+
let v0 = rng.next_u32() as u64;
197+
let v0_blinding = Scalar::random(&mut rng);
198+
let party0 = Party::new(v0, v0_blinding, n, &generators).unwrap();
199+
200+
let v2 = rng.next_u32() as u64;
201+
let v2_blinding = Scalar::random(&mut rng);
202+
let party2 = Party::new(v2, v2_blinding, n, &generators).unwrap();
203+
204+
// Parties 1, 3 are dishonest and use a 64-bit value
205+
let v1 = rng.next_u64();
206+
let v1_blinding = Scalar::random(&mut rng);
207+
let party1 = Party::new(v1, v1_blinding, n, &generators).unwrap();
208+
209+
let v3 = rng.next_u64();
210+
let v3_blinding = Scalar::random(&mut rng);
211+
let party3 = Party::new(v3, v3_blinding, n, &generators).unwrap();
212+
213+
let dealer = Dealer::new(generators.all(), n, m, &mut transcript).unwrap();
214+
215+
let (party0, value_com0) = party0.assign_position(0, &mut rng);
216+
let (party1, value_com1) = party1.assign_position(1, &mut rng);
217+
let (party2, value_com2) = party2.assign_position(2, &mut rng);
218+
let (party3, value_com3) = party3.assign_position(3, &mut rng);
219+
220+
let (dealer, value_challenge) = dealer
221+
.receive_value_commitments(&[value_com0, value_com1, value_com2, value_com3])
222+
.unwrap();
223+
224+
let (party0, poly_com0) = party0.apply_challenge(&value_challenge, &mut rng);
225+
let (party1, poly_com1) = party1.apply_challenge(&value_challenge, &mut rng);
226+
let (party2, poly_com2) = party2.apply_challenge(&value_challenge, &mut rng);
227+
let (party3, poly_com3) = party3.apply_challenge(&value_challenge, &mut rng);
228+
229+
let (dealer, poly_challenge) = dealer
230+
.receive_poly_commitments(&[poly_com0, poly_com1, poly_com2, poly_com3])
231+
.unwrap();
232+
233+
let share0 = party0.apply_challenge(&poly_challenge);
234+
let share1 = party1.apply_challenge(&poly_challenge);
235+
let share2 = party2.apply_challenge(&poly_challenge);
236+
let share3 = party3.apply_challenge(&poly_challenge);
237+
238+
match dealer.receive_shares(&mut rng, &[share0, share1, share2, share3]) {
239+
Ok(_proof) => {
240+
panic!("The proof was malformed, but it was not detected");
241+
}
242+
Err(e) => {
243+
// XXX when we have error types, check that it was party 1 that did it
244+
assert_eq!(e, "proof failed to verify");
245+
}
246+
}
247+
}
183248
}

0 commit comments

Comments
 (0)